1
0
mirror of https://github.com/lana-k/sqliteviz.git synced 2025-12-07 02:28:54 +08:00

8 Commits

Author SHA1 Message Date
lana-k
a07f2d3d99 update plotly 2021-05-02 20:59:03 +02:00
lana-k
b9844b8696 refine pwa app icons 2021-05-02 20:46:27 +02:00
lana-k
464bff3db8 delete screenshots 2021-05-02 20:45:24 +02:00
lana-k
00e434e142 fix loading db after csv:
new tab is not opened now
2021-05-02 14:09:02 +02:00
lana-k
5d6280abec add default table in hint options 2021-05-02 14:04:46 +02:00
lana-k
7a39e905b9 trim csv column names 2021-04-30 20:49:37 +02:00
lana-k
297ea2c18a fix path to service worker 2021-04-30 19:42:26 +02:00
lana-k
1f2327a724 fix global css in index.html 2021-04-30 19:04:08 +02:00
24 changed files with 137 additions and 45 deletions

View File

@@ -4,20 +4,18 @@
# sqliteviz
Sqliteviz is a single-page PWA for fully client-side visualisation of SQLite databases or CSV files.
Sqliteviz is a single-page offline-first PWA for fully client-side visualisation of SQLite databases or CSV files.
This application allows to:
- run SQL queries in SQLite database and create all kinds of charts based on result set
- import CSV file into SQLite database and visualize imported data
- save queries and chart settings
With sqliteviz you can:
- run SQL queries against a SQLite database and create [Plotly][11] charts based on the result sets
- import a CSV file into a SQLite database and visualize imported data
- manage queries and chart settings and run them against different databases
- import/export queries and chart settings to/from a JSON file
- manipulate saved queries (rename, duplicate, delete)
- set predefined queries available for all users of sqliteviz on your server (read more about predefind queries on [Wiki][10])
- export modified SQLite database
- use it offline
- export a modified SQLite database
- use it offline from your OS application menu like any other desktop app
## Get started
The latest release of sqliteviz is running on [Github pages][6]. The simplest way to start is to use sqliteviz there.
## Quickstart
The latest release of sqliteviz is deployed on GitHub Pages at [lana-k.github.io/sqliteviz][6].
## Wiki
For user documentation, check out sqliteviz [Wiki][7].
@@ -33,8 +31,9 @@ It is built on top of [react-chart-editor][3], [sql.js][4] and [Vue-Codemirror][
[3]: https://github.com/plotly/react-chart-editor
[4]: https://github.com/sql-js/sql.js
[5]: https://github.com/vuejs/vue
[6]: https://lana-k.github.io/sqliteviz
[6]: https://lana-k.github.io/sqliteviz/
[7]: https://github.com/lana-k/sqliteviz/wiki
[8]: https://github.com/surmon-china/vue-codemirror#readme
[9]: https://www.papaparse.com/
[10]: https://github.com/lana-k/sqliteviz/wiki/Predefined-queries
[11]: https://github.com/plotly/plotly.js

30
package-lock.json generated
View File

@@ -14,7 +14,7 @@
"debounce": "^1.2.0",
"nanoid": "^3.1.12",
"papaparse": "^5.3.0",
"plotly.js": "^1.57.1",
"plotly.js": "^1.58.4",
"promise-worker": "^2.0.1",
"react": "^16.13.1",
"react-chart-editor": "^0.42.0",
@@ -10057,9 +10057,9 @@
}
},
"node_modules/gl-plot3d": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/gl-plot3d/-/gl-plot3d-2.4.6.tgz",
"integrity": "sha512-CkrNvDKu0p74Di2g2Oc9kU+s1Oe+wi4cIfHzXABp8DvfoRl0/bayqJ9q8EcRAqMeQQxQZYGvJkk4hlBwI758Jw==",
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/gl-plot3d/-/gl-plot3d-2.4.7.tgz",
"integrity": "sha512-mLDVWrl4Dj0O0druWyHUK5l7cBQrRIJRn2oROEgrRuOgbbrLAzsREKefwMO0bA0YqkiZMFMnV5VvPA9j57X5Xg==",
"dependencies": {
"3d-view": "^2.0.0",
"a-big-triangle": "^1.0.3",
@@ -15945,9 +15945,9 @@
}
},
"node_modules/plotly.js": {
"version": "1.57.1",
"resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-1.57.1.tgz",
"integrity": "sha512-23GlzClmOGT1lE86Ys0DLuxBM/fgRNzJqH9y7ZylO4VPwstPAlQd12DklXsuqOgCNSxnnWUaP+J7BaUOFplsUg==",
"version": "1.58.4",
"resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-1.58.4.tgz",
"integrity": "sha512-hdt/aEvkPjS1HJ7tJKcPqsqi9ErEZPhUFs4d2ANTLeBim+AmVcHzS1rtwr7ZrVCINgliW/+92u81omJoy+lbUw==",
"dependencies": {
"@plotly/d3-sankey": "0.7.2",
"@plotly/d3-sankey-circular": "0.33.1",
@@ -15979,7 +15979,7 @@
"gl-mat4": "^1.2.0",
"gl-mesh3d": "^2.3.1",
"gl-plot2d": "^1.4.5",
"gl-plot3d": "^2.4.6",
"gl-plot3d": "^2.4.7",
"gl-pointcloud2d": "^1.0.3",
"gl-scatter3d": "^1.2.3",
"gl-select-box": "^1.0.4",
@@ -32186,9 +32186,9 @@
}
},
"gl-plot3d": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/gl-plot3d/-/gl-plot3d-2.4.6.tgz",
"integrity": "sha512-CkrNvDKu0p74Di2g2Oc9kU+s1Oe+wi4cIfHzXABp8DvfoRl0/bayqJ9q8EcRAqMeQQxQZYGvJkk4hlBwI758Jw==",
"version": "2.4.7",
"resolved": "https://registry.npmjs.org/gl-plot3d/-/gl-plot3d-2.4.7.tgz",
"integrity": "sha512-mLDVWrl4Dj0O0druWyHUK5l7cBQrRIJRn2oROEgrRuOgbbrLAzsREKefwMO0bA0YqkiZMFMnV5VvPA9j57X5Xg==",
"requires": {
"3d-view": "^2.0.0",
"a-big-triangle": "^1.0.3",
@@ -37188,9 +37188,9 @@
}
},
"plotly.js": {
"version": "1.57.1",
"resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-1.57.1.tgz",
"integrity": "sha512-23GlzClmOGT1lE86Ys0DLuxBM/fgRNzJqH9y7ZylO4VPwstPAlQd12DklXsuqOgCNSxnnWUaP+J7BaUOFplsUg==",
"version": "1.58.4",
"resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-1.58.4.tgz",
"integrity": "sha512-hdt/aEvkPjS1HJ7tJKcPqsqi9ErEZPhUFs4d2ANTLeBim+AmVcHzS1rtwr7ZrVCINgliW/+92u81omJoy+lbUw==",
"requires": {
"@plotly/d3-sankey": "0.7.2",
"@plotly/d3-sankey-circular": "0.33.1",
@@ -37222,7 +37222,7 @@
"gl-mat4": "^1.2.0",
"gl-mesh3d": "^2.3.1",
"gl-plot2d": "^1.4.5",
"gl-plot3d": "^2.4.6",
"gl-plot3d": "^2.4.7",
"gl-pointcloud2d": "^1.0.3",
"gl-scatter3d": "^1.2.3",
"gl-select-box": "^1.0.4",

View File

@@ -15,7 +15,7 @@
"debounce": "^1.2.0",
"nanoid": "^3.1.12",
"papaparse": "^5.3.0",
"plotly.js": "^1.57.1",
"plotly.js": "^1.58.4",
"promise-worker": "^2.0.1",
"react": "^16.13.1",
"react-chart-editor": "^0.42.0",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/Logo48x48.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 798 B

After

Width:  |  Height:  |  Size: 774 B

View File

@@ -8,7 +8,7 @@
<link rel="manifest" href="<%= BASE_URL %>manifest.webmanifest">
<title><%= htmlWebpackPlugin.options.title %></title>
<style>
#loading-wrapper {
#sqliteviz-loading-wrapper {
position: fixed;
width: 100%;
height: 100%;
@@ -17,7 +17,7 @@
background-color: white;
}
#loading-text {
#sqliteviz-loading-text {
display: block;
position: absolute;
top: 50%;
@@ -28,7 +28,7 @@
font-size: 20px;
}
.svg-container {
#sqliteviz-loading-wrapper svg {
display: block;
position: absolute;
left: 50%;
@@ -36,7 +36,7 @@
transform: translate(-50%, -50%);
}
.loader-svg {
#sqliteviz-loading-wrapper circle {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
fill: none;
@@ -45,16 +45,16 @@
stroke: #119DFF;
}
.loader-svg.bg {
#sqliteviz-loading-wrapper circle.bg {
stroke: #C8D4E3;
}
.loader-svg.front {
#sqliteviz-loading-wrapper circle.front {
stroke-dasharray: 402px;
animation: loading 2s linear 0s infinite;
animation: sqliteviz-loading 2s linear 0s infinite;
}
@keyframes loading {
@keyframes sqliteviz-loading {
0% {
stroke-dasharray: 100px 402px;
stroke-dashoffset: 0;
@@ -75,17 +75,17 @@
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app">
<div id="loading-wrapper">
<div id="loading-text">LOADING</div>
<svg class="svg-container" height="170" width="170" viewBox="0 0 170 170">
<div id="sqliteviz-loading-wrapper">
<div id="sqliteviz-loading-text">LOADING</div>
<svg height="170" width="170" viewBox="0 0 170 170">
<circle
class="loader-svg bg"
class="bg"
cx="85"
cy="85"
r="80"
/>
<circle
class="loader-svg front"
class="front"
cx="85"
cy="85"
r="80"

View File

@@ -3,6 +3,16 @@
"description": "Sqliteviz is a single-page application for fully client-side visualisation of SQLite databases or CSV.",
"display": "fullscreen",
"icons": [
{
"src": "favicon.png",
"sizes": "32x32",
"type": "image/png"
},
{
"src": "Logo48x48.png",
"sizes": "48x48",
"type": "image/png"
},
{
"src": "Logo192x192.png",
"sizes": "192x192",

View File

@@ -13,7 +13,7 @@ function invokeServiceWorkerUpdateFlow (registration) {
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
const registration = await navigator.serviceWorker.register('/service-worker.js')
const registration = await navigator.serviceWorker.register('service-worker.js')
// ensure the case when the updatefound event was missed is also handled
// by re-invoking the prompt when there's a waiting Service Worker
if (registration.waiting) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -233,6 +233,7 @@ export default {
this.$modal.hide('parse')
const tabId = await this.$store.dispatch('addTab', { query: 'select * from csv_import' })
this.$store.commit('setCurrentTabId', tabId)
this.importCsvCompleted = false
}
if (this.$route.path !== '/editor') {
this.$router.push('/editor')

View File

@@ -1,5 +1,5 @@
<template>
<svg :class="['svg-container', animationClass ]" height="20" width="20" viewBox="0 0 20 20">
<svg :class="animationClass" height="20" width="20" viewBox="0 0 20 20">
<circle
class="loader-svg bg"
cx="10"

View File

@@ -10,7 +10,7 @@ export default {
getResult (source) {
const result = {}
if (source.meta.fields) {
result.columns = source.meta.fields
result.columns = source.meta.fields.map(col => col.trim())
result.values = source.data.map(row => {
const resultRow = []
result.columns.forEach(col => { resultRow.push(row[col]) })

View File

@@ -25,6 +25,10 @@ const hintOptions = {
}
return tables
},
get defaultTable () {
const schema = store.state.schema
return schema.length === 1 ? schema[0].name : null
},
completeSingle: false,
completeOnSingleClick: true,
alignWithWord: false

View File

@@ -838,4 +838,49 @@ describe('DbUploader.vue import CSV', () => {
expect(newDb.shutDown.calledOnce).to.equal(true)
expect(wrapper.find('[data-modal="parse"]').exists()).to.equal(false)
})
it("doesn't open new tab when load db after importing CSV", async () => {
fu.getFileFromUser.onCall(0).resolves({ type: 'text/csv', name: 'foo.csv' })
fu.getFileFromUser.onCall(1).resolves({ type: 'application/x-sqlite3', name: 'bar.sqlite3' })
sinon.stub(csv, 'parse').resolves({
delimiter: '|',
data: {
columns: ['col1', 'col2'],
values: [
[1, 'foo']
]
},
hasErrors: false,
messages: []
})
const schema = {}
const newDb = {
createDb: sinon.stub().resolves(schema),
createProgressCounter: sinon.stub().returns(1),
deleteProgressCounter: sinon.stub(),
loadDb: sinon.stub().resolves()
}
sinon.stub(database, 'getNewDatabase').returns(newDb)
await wrapper.find('.drop-area').trigger('click')
await csv.parse.returnValues[0]
await wrapper.vm.animationPromise
await wrapper.vm.$nextTick()
await wrapper.find('#csv-import').trigger('click')
await csv.parse.returnValues[1]
await wrapper.vm.$nextTick()
await wrapper.find('#csv-finish').trigger('click')
expect(actions.addTab.calledOnce).to.equal(true)
await actions.addTab.returnValues[0]
expect(mutations.setCurrentTabId.calledOnceWith(state, newTabId)).to.equal(true)
await wrapper.find('.drop-area').trigger('click')
await newDb.loadDb.returnValues[0]
expect(actions.addTab.calledOnce).to.equal(true)
expect(mutations.setCurrentTabId.calledOnce).to.equal(true)
})
})

View File

@@ -15,7 +15,7 @@ describe('csv.js', () => {
{ id: 2, name: 'bar' }
],
meta: {
fields: ['id', 'name']
fields: ['id', 'name ']
}
}
expect(csv.getResult(source)).to.eql({

View File

@@ -49,6 +49,39 @@ describe('hint.js', () => {
foo: ['fooId', 'name'],
bar: ['barId']
})
expect(CM.showHint.firstCall.args[2].defaultTable).to.equal(null)
})
it('Add default table if there is only one table in schema', () => {
// mock store state
const schema = [
{
name: 'foo',
columns: [
{ name: 'fooId', type: 'INTEGER' },
{ name: 'name', type: 'NVARCHAR(20)' }
]
}
]
sinon.stub(state, 'schema').value(schema)
// mock showHint and editor
sinon.stub(CM, 'showHint')
const editor = {
getTokenAt () {
return {
string: 'SELECT',
type: 'keyword'
}
},
getCursor: sinon.stub()
}
const clock = sinon.useFakeTimers()
hint.show(editor)
clock.tick(500)
expect(CM.showHint.firstCall.args[2].defaultTable).to.equal('foo')
})
it("Doesn't show hint when in string or space, or ';'", () => {