mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-06 10:08:52 +08:00
add CSV support #27
This commit is contained in:
@@ -12,7 +12,8 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off'
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
|
'no-case-declarations': 'off'
|
||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,13 +25,6 @@ module.exports = function (config) {
|
|||||||
included: false,
|
included: false,
|
||||||
served: true,
|
served: true,
|
||||||
nocache: false
|
nocache: false
|
||||||
},
|
|
||||||
{
|
|
||||||
pattern: 'node_modules/sql.js/dist/worker.sql-wasm.js',
|
|
||||||
watched: false,
|
|
||||||
included: false,
|
|
||||||
served: true,
|
|
||||||
nocache: false
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
@@ -136,6 +129,10 @@ module.exports = function (config) {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
test: /\.worker\.js$/,
|
||||||
|
loader: 'worker-loader'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
|
||||||
loader: 'url-loader'
|
loader: 'url-loader'
|
||||||
@@ -181,7 +178,8 @@ module.exports = function (config) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
proxies: {
|
proxies: {
|
||||||
'/js/': '/base/node_modules/sql.js/dist/'
|
'/_karma_webpack_/sql-wasm.wasm': '/base/node_modules/sql.js/dist/sql-wasm.wasm',
|
||||||
|
'/base/sql-wasm.wasm': '/base/node_modules/sql.js/dist/sql-wasm.wasm'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Fix the timezone
|
// Fix the timezone
|
||||||
|
|||||||
181
package-lock.json
generated
181
package-lock.json
generated
@@ -13,7 +13,9 @@
|
|||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"debounce": "^1.2.0",
|
"debounce": "^1.2.0",
|
||||||
"nanoid": "^3.1.12",
|
"nanoid": "^3.1.12",
|
||||||
|
"papaparse": "^5.3.0",
|
||||||
"plotly.js": "^1.57.1",
|
"plotly.js": "^1.57.1",
|
||||||
|
"promise-worker": "^2.0.1",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-chart-editor": "^0.42.0",
|
"react-chart-editor": "^0.42.0",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
@@ -37,6 +39,7 @@
|
|||||||
"@vue/test-utils": "^1.1.2",
|
"@vue/test-utils": "^1.1.2",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"chai": "^4.1.2",
|
"chai": "^4.1.2",
|
||||||
|
"chai-as-promised": "^7.1.1",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-plugin-import": "^2.20.2",
|
"eslint-plugin-import": "^2.20.2",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
@@ -46,7 +49,8 @@
|
|||||||
"karma": "^3.1.4",
|
"karma": "^3.1.4",
|
||||||
"karma-webpack": "^4.0.2",
|
"karma-webpack": "^4.0.2",
|
||||||
"vue-cli-plugin-ui-karma": "^0.2.5",
|
"vue-cli-plugin-ui-karma": "^0.2.5",
|
||||||
"vue-template-compiler": "^2.6.11"
|
"vue-template-compiler": "^2.6.11",
|
||||||
|
"worker-loader": "^3.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
@@ -1559,9 +1563,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
"version": "7.0.5",
|
"version": "7.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
|
||||||
"integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==",
|
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/json5": {
|
"node_modules/@types/json5": {
|
||||||
@@ -2510,15 +2514,19 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.12.3",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
"integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
"json-schema-traverse": "^0.4.1",
|
"json-schema-traverse": "^0.4.1",
|
||||||
"uri-js": "^4.2.2"
|
"uri-js": "^4.2.2"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/epoberezkin"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ajv-errors": {
|
"node_modules/ajv-errors": {
|
||||||
@@ -2528,10 +2536,13 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/ajv-keywords": {
|
"node_modules/ajv-keywords": {
|
||||||
"version": "3.5.1",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||||
"integrity": "sha512-KWcq3xN8fDjSB+IMoh2VaXVhRI0BBGxoYp3rx7Pkb6z0cFjYR9Q9l4yZqqals0/zsioCmocC5H6UvsGD4MoIBA==",
|
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"peerDependencies": {
|
||||||
|
"ajv": "^6.9.1"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/align-text": {
|
"node_modules/align-text": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
@@ -4296,6 +4307,18 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chai-as-promised": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"check-error": "^1.0.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"chai": ">= 2.1.2 < 5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||||
@@ -14826,6 +14849,11 @@
|
|||||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/papaparse": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-Lb7jN/4bTpiuGPrYy4tkKoUS8sTki8zacB5ke1p5zolhcSE4TlWgrlsxjrDTbG/dFVh07ck7X36hUf/b5V68pg=="
|
||||||
|
},
|
||||||
"node_modules/parallel-transform": {
|
"node_modules/parallel-transform": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
|
||||||
@@ -16287,6 +16315,11 @@
|
|||||||
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
|
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/promise-worker": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/promise-worker/-/promise-worker-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-jR7vHqMEwWJ15i9vA3qyCKwRHihyLJp1sAa3RyY5F35m3u5s2lQUfq0nzVjbA8Xc7+3mL3Y9+9MHBO9UFRpFxA=="
|
||||||
|
},
|
||||||
"node_modules/prop-types": {
|
"node_modules/prop-types": {
|
||||||
"version": "15.7.2",
|
"version": "15.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||||
@@ -21999,6 +22032,58 @@
|
|||||||
"errno": "~0.1.7"
|
"errno": "~0.1.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/worker-loader": {
|
||||||
|
"version": "3.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz",
|
||||||
|
"integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"loader-utils": "^2.0.0",
|
||||||
|
"schema-utils": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.13.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/webpack"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"webpack": "^4.0.0 || ^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/worker-loader/node_modules/loader-utils": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"big.js": "^5.2.2",
|
||||||
|
"emojis-list": "^3.0.0",
|
||||||
|
"json5": "^2.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/worker-loader/node_modules/schema-utils": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/json-schema": "^7.0.6",
|
||||||
|
"ajv": "^6.12.5",
|
||||||
|
"ajv-keywords": "^3.5.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.13.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/webpack"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/world-calendars": {
|
"node_modules/world-calendars": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/world-calendars/-/world-calendars-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/world-calendars/-/world-calendars-1.0.3.tgz",
|
||||||
@@ -23752,9 +23837,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/json-schema": {
|
"@types/json-schema": {
|
||||||
"version": "7.0.5",
|
"version": "7.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
|
||||||
"integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==",
|
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/json5": {
|
"@types/json5": {
|
||||||
@@ -24613,9 +24698,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "6.12.3",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
"integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
@@ -24631,10 +24716,11 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"ajv-keywords": {
|
"ajv-keywords": {
|
||||||
"version": "3.5.1",
|
"version": "3.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||||
"integrity": "sha512-KWcq3xN8fDjSB+IMoh2VaXVhRI0BBGxoYp3rx7Pkb6z0cFjYR9Q9l4yZqqals0/zsioCmocC5H6UvsGD4MoIBA==",
|
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"requires": {}
|
||||||
},
|
},
|
||||||
"align-text": {
|
"align-text": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
@@ -26184,6 +26270,15 @@
|
|||||||
"type-detect": "^4.0.5"
|
"type-detect": "^4.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"chai-as-promised": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"check-error": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||||
@@ -35229,6 +35324,11 @@
|
|||||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"papaparse": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-Lb7jN/4bTpiuGPrYy4tkKoUS8sTki8zacB5ke1p5zolhcSE4TlWgrlsxjrDTbG/dFVh07ck7X36hUf/b5V68pg=="
|
||||||
|
},
|
||||||
"parallel-transform": {
|
"parallel-transform": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
|
||||||
@@ -36516,6 +36616,11 @@
|
|||||||
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
|
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"promise-worker": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/promise-worker/-/promise-worker-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-jR7vHqMEwWJ15i9vA3qyCKwRHihyLJp1sAa3RyY5F35m3u5s2lQUfq0nzVjbA8Xc7+3mL3Y9+9MHBO9UFRpFxA=="
|
||||||
|
},
|
||||||
"prop-types": {
|
"prop-types": {
|
||||||
"version": "15.7.2",
|
"version": "15.7.2",
|
||||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||||
@@ -41542,6 +41647,40 @@
|
|||||||
"errno": "~0.1.7"
|
"errno": "~0.1.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"worker-loader": {
|
||||||
|
"version": "3.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/worker-loader/-/worker-loader-3.0.8.tgz",
|
||||||
|
"integrity": "sha512-XQyQkIFeRVC7f7uRhFdNMe/iJOdO6zxAaR3EWbDp45v3mDhrTi+++oswKNxShUNjPC/1xUp5DB29YKLhFo129g==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"loader-utils": "^2.0.0",
|
||||||
|
"schema-utils": "^3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"loader-utils": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"big.js": "^5.2.2",
|
||||||
|
"emojis-list": "^3.0.0",
|
||||||
|
"json5": "^2.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema-utils": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/json-schema": "^7.0.6",
|
||||||
|
"ajv": "^6.12.5",
|
||||||
|
"ajv-keywords": "^3.5.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"world-calendars": {
|
"world-calendars": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/world-calendars/-/world-calendars-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/world-calendars/-/world-calendars-1.0.3.tgz",
|
||||||
|
|||||||
@@ -14,7 +14,9 @@
|
|||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
"debounce": "^1.2.0",
|
"debounce": "^1.2.0",
|
||||||
"nanoid": "^3.1.12",
|
"nanoid": "^3.1.12",
|
||||||
|
"papaparse": "^5.3.0",
|
||||||
"plotly.js": "^1.57.1",
|
"plotly.js": "^1.57.1",
|
||||||
|
"promise-worker": "^2.0.1",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-chart-editor": "^0.42.0",
|
"react-chart-editor": "^0.42.0",
|
||||||
"react-dom": "^16.13.1",
|
"react-dom": "^16.13.1",
|
||||||
@@ -38,6 +40,7 @@
|
|||||||
"@vue/test-utils": "^1.1.2",
|
"@vue/test-utils": "^1.1.2",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"chai": "^4.1.2",
|
"chai": "^4.1.2",
|
||||||
|
"chai-as-promised": "^7.1.1",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
"eslint-plugin-import": "^2.20.2",
|
"eslint-plugin-import": "^2.20.2",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
@@ -47,6 +50,7 @@
|
|||||||
"karma": "^3.1.4",
|
"karma": "^3.1.4",
|
||||||
"karma-webpack": "^4.0.2",
|
"karma-webpack": "^4.0.2",
|
||||||
"vue-cli-plugin-ui-karma": "^0.2.5",
|
"vue-cli-plugin-ui-karma": "^0.2.5",
|
||||||
"vue-template-compiler": "^2.6.11"
|
"vue-template-compiler": "^2.6.11",
|
||||||
|
"worker-loader": "^3.0.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="db-upload-container">
|
<div class="db-upload-container">
|
||||||
<div class="drop-area-container">
|
<change-db-icon v-if="type === 'small'" @click.native="browse"/>
|
||||||
|
<div v-if="['regular', 'illustrated'].includes(type)" class="drop-area-container">
|
||||||
<div
|
<div
|
||||||
class="drop-area"
|
class="drop-area"
|
||||||
@dragover.prevent="state = 'dragover'"
|
@dragover.prevent="state = 'dragover'"
|
||||||
@@ -13,7 +14,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="illustrated" id="img-container">
|
<div v-if="type === 'illustrated'" id="img-container">
|
||||||
<img id="drop-file-top-img" :src="require('@/assets/images/top.svg')" />
|
<img id="drop-file-top-img" :src="require('@/assets/images/top.svg')" />
|
||||||
<img
|
<img
|
||||||
id="left-arm-img"
|
id="left-arm-img"
|
||||||
@@ -38,29 +39,141 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div id="error" class="error"></div>
|
<div id="error" class="error"></div>
|
||||||
|
|
||||||
|
<!--Parse csv dialog -->
|
||||||
|
<modal name="parse" classes="dialog" height="auto" width="60%" :clickToClose="false">
|
||||||
|
<div class="dialog-header">
|
||||||
|
Import CSV
|
||||||
|
<close-icon @click="cancelCsvImport"/>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-body">
|
||||||
|
<div class="chars">
|
||||||
|
<delimiter-selector
|
||||||
|
v-model="delimiter"
|
||||||
|
width="210px"
|
||||||
|
class="char-input"
|
||||||
|
@input="previewCSV"
|
||||||
|
:disabled="disableDialog"
|
||||||
|
/>
|
||||||
|
<text-field
|
||||||
|
label="Quote char"
|
||||||
|
hint="The character used to quote fields."
|
||||||
|
v-model="quoteChar"
|
||||||
|
width="93px"
|
||||||
|
:disabled="disableDialog"
|
||||||
|
class="char-input"
|
||||||
|
/>
|
||||||
|
<text-field
|
||||||
|
label="Escape char"
|
||||||
|
hint='The character used to escape the quote character within a field (e.g. "column with ""quotes"" in text").'
|
||||||
|
max-hint-width="242px"
|
||||||
|
v-model="escapeChar"
|
||||||
|
width="93px"
|
||||||
|
:disabled="disableDialog"
|
||||||
|
class="char-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<check-box
|
||||||
|
@click="header = $event"
|
||||||
|
:init="true"
|
||||||
|
label="Use first row as column headers"
|
||||||
|
:disabled="disableDialog"
|
||||||
|
/>
|
||||||
|
<sql-table
|
||||||
|
v-if="previewData"
|
||||||
|
:data-set="previewData"
|
||||||
|
height="160"
|
||||||
|
class="preview-table"
|
||||||
|
:preview="true"
|
||||||
|
/>
|
||||||
|
<div v-if="!previewData" class="no-data">No data</div>
|
||||||
|
<logs
|
||||||
|
class="import-csv-errors"
|
||||||
|
:messages="importCsvMessages"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-buttons-container">
|
||||||
|
<button
|
||||||
|
class="secondary"
|
||||||
|
:disabled="disableDialog"
|
||||||
|
@click="cancelCsvImport"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-show="!importCsvCompleted"
|
||||||
|
class="primary"
|
||||||
|
:disabled="disableDialog"
|
||||||
|
@click="loadFromCsv(file)"
|
||||||
|
>
|
||||||
|
Import
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-show="importCsvCompleted"
|
||||||
|
class="primary"
|
||||||
|
:disabled="disableDialog"
|
||||||
|
@click="finish"
|
||||||
|
>
|
||||||
|
Finish
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import fu from '@/fileUtils'
|
import fu from '@/fileUtils'
|
||||||
|
import csv from '@/csv'
|
||||||
|
import CloseIcon from '@/components/svg/close'
|
||||||
|
import TextField from '@/components/TextField'
|
||||||
|
import DelimiterSelector from '@/components/DelimiterSelector'
|
||||||
|
import CheckBox from '@/components/CheckBox'
|
||||||
|
import SqlTable from '@/components/SqlTable'
|
||||||
|
import Logs from '@/components/Logs'
|
||||||
|
import ChangeDbIcon from '@/components/svg/changeDb'
|
||||||
|
import time from '@/time'
|
||||||
|
import database from '@/database'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'DbUpload',
|
name: 'DbUpload',
|
||||||
props: {
|
props: {
|
||||||
illustrated: {
|
type: {
|
||||||
type: Boolean,
|
type: String,
|
||||||
required: false,
|
required: false,
|
||||||
default: false
|
default: 'regular',
|
||||||
|
validator: (value) => {
|
||||||
|
return ['regular', 'illustrated', 'small'].includes(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
ChangeDbIcon,
|
||||||
|
TextField,
|
||||||
|
DelimiterSelector,
|
||||||
|
CloseIcon,
|
||||||
|
CheckBox,
|
||||||
|
SqlTable,
|
||||||
|
Logs
|
||||||
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
state: '',
|
state: '',
|
||||||
animationPromise: Promise.resolve()
|
animationPromise: Promise.resolve(),
|
||||||
|
file: null,
|
||||||
|
schema: null,
|
||||||
|
delimiter: '',
|
||||||
|
quoteChar: '"',
|
||||||
|
escapeChar: '"',
|
||||||
|
header: false,
|
||||||
|
previewData: null,
|
||||||
|
importCsvMessages: [],
|
||||||
|
disableDialog: false,
|
||||||
|
importCsvCompleted: false,
|
||||||
|
newDb: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
if (this.illustrated) {
|
if (this.type === 'illustrated') {
|
||||||
this.animationPromise = new Promise((resolve) => {
|
this.animationPromise = new Promise((resolve) => {
|
||||||
this.$refs.fileImg.addEventListener('animationend', event => {
|
this.$refs.fileImg.addEventListener('animationend', event => {
|
||||||
if (event.animationName.startsWith('fly')) {
|
if (event.animationName.startsWith('fly')) {
|
||||||
@@ -70,29 +183,216 @@ export default {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
quoteChar () {
|
||||||
|
this.previewCSV()
|
||||||
|
},
|
||||||
|
|
||||||
|
escapeChar () {
|
||||||
|
this.previewCSV()
|
||||||
|
},
|
||||||
|
|
||||||
|
header () {
|
||||||
|
this.previewCSV()
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
cancelCsvImport () {
|
||||||
|
if (!this.disableDialog) {
|
||||||
|
this.$modal.hide('parse')
|
||||||
|
if (this.newDb) {
|
||||||
|
this.newDb.shutDown()
|
||||||
|
this.newDb = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async finish () {
|
||||||
|
this.$store.commit('setDb', this.newDb)
|
||||||
|
this.$store.commit('saveSchema', this.schema)
|
||||||
|
if (this.importCsvCompleted) {
|
||||||
|
this.$modal.hide('parse')
|
||||||
|
const tabId = await this.$store.dispatch('addTab', { query: 'select * from csv_import' })
|
||||||
|
this.$store.commit('setCurrentTabId', tabId)
|
||||||
|
}
|
||||||
|
if (this.$route.path !== '/editor') {
|
||||||
|
this.$router.push('/editor')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async previewCSV () {
|
||||||
|
this.importCsvCompleted = false
|
||||||
|
const config = {
|
||||||
|
preview: 3,
|
||||||
|
quoteChar: this.quoteChar || '"',
|
||||||
|
escapeChar: this.escapeChar,
|
||||||
|
header: this.header,
|
||||||
|
delimiter: this.delimiter
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const start = new Date()
|
||||||
|
const parseResult = await csv.parse(this.file, config)
|
||||||
|
const end = new Date()
|
||||||
|
this.previewData = parseResult.data
|
||||||
|
this.delimiter = parseResult.delimiter
|
||||||
|
|
||||||
|
// In parseResult.messages we can get parse errors
|
||||||
|
this.importCsvMessages = parseResult.messages
|
||||||
|
|
||||||
|
if (parseResult.messages.length === 0) {
|
||||||
|
this.importCsvMessages.push({
|
||||||
|
message: `Preview parsing is completed in ${time.getPeriod(start, end)}.`,
|
||||||
|
type: 'success'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.importCsvMessages = [{
|
||||||
|
message: err,
|
||||||
|
type: 'error'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
loadDb (file) {
|
loadDb (file) {
|
||||||
this.state = 'drop'
|
this.newDb = database.getNewDatabase()
|
||||||
return Promise.all([this.$db.loadDb(file), this.animationPromise])
|
return Promise.all([this.newDb.loadDb(file), this.animationPromise])
|
||||||
.then(([schema]) => {
|
.then(([schema]) => {
|
||||||
this.$store.commit('saveSchema', schema)
|
this.schema = schema
|
||||||
if (this.$route.path !== '/editor') {
|
this.finish()
|
||||||
this.$router.push('/editor')
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
browse () {
|
|
||||||
fu.getFileFromUser('.db,.sqlite,.sqlite3')
|
async loadFromCsv (file) {
|
||||||
.then(this.loadDb)
|
this.disableDialog = true
|
||||||
|
const config = {
|
||||||
|
quoteChar: this.quoteChar || '"',
|
||||||
|
escapeChar: this.escapeChar,
|
||||||
|
header: this.header,
|
||||||
|
delimiter: this.delimiter
|
||||||
|
}
|
||||||
|
const parseCsvMsg = {
|
||||||
|
message: 'Parsing CSV...',
|
||||||
|
type: 'info'
|
||||||
|
}
|
||||||
|
this.importCsvMessages.push(parseCsvMsg)
|
||||||
|
const parseCsvLoadingIndicator = setTimeout(() => { parseCsvMsg.type = 'loading' }, 1000)
|
||||||
|
|
||||||
|
const importMsg = {
|
||||||
|
message: 'Importing CSV into a SQLite database...',
|
||||||
|
type: 'info'
|
||||||
|
}
|
||||||
|
let importLoadingIndicator = null
|
||||||
|
|
||||||
|
const updateProgress = e => {
|
||||||
|
this.$set(importMsg, 'progress', e.detail)
|
||||||
|
}
|
||||||
|
this.newDb = database.getNewDatabase()
|
||||||
|
const progressCounterId = this.newDb.createProgressCounter(updateProgress)
|
||||||
|
|
||||||
|
try {
|
||||||
|
let start = new Date()
|
||||||
|
const parseResult = await csv.parse(this.file, config)
|
||||||
|
let end = new Date()
|
||||||
|
|
||||||
|
if (!parseResult.hasErrors) {
|
||||||
|
const rowCount = parseResult.data.values.length
|
||||||
|
let period = time.getPeriod(start, end)
|
||||||
|
parseCsvMsg.type = 'success'
|
||||||
|
|
||||||
|
if (parseResult.messages.length > 0) {
|
||||||
|
this.importCsvMessages = this.importCsvMessages.concat(parseResult.messages)
|
||||||
|
parseCsvMsg.message = `${rowCount} rows are parsed in ${period}.`
|
||||||
|
} else {
|
||||||
|
// Inform about csv parsing success
|
||||||
|
parseCsvMsg.message = `${rowCount} rows are parsed successfully in ${period}.`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loading indicator for csv parsing is not needed anymore
|
||||||
|
clearTimeout(parseCsvLoadingIndicator)
|
||||||
|
|
||||||
|
// Add info about import start
|
||||||
|
this.importCsvMessages.push(importMsg)
|
||||||
|
|
||||||
|
// Show import progress after 1 second
|
||||||
|
importLoadingIndicator = setTimeout(() => {
|
||||||
|
importMsg.type = 'loading'
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
// Create db with csv table and get schema
|
||||||
|
start = new Date()
|
||||||
|
this.schema = await this.newDb.createDb(file.name, parseResult.data, progressCounterId)
|
||||||
|
end = new Date()
|
||||||
|
if (this.schema.error) {
|
||||||
|
throw this.schema.error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inform about import success
|
||||||
|
period = time.getPeriod(start, end)
|
||||||
|
importMsg.message = `Importing CSV into a SQLite database is completed in ${period}.`
|
||||||
|
importMsg.type = 'success'
|
||||||
|
|
||||||
|
// Loading indicator for import is not needed anymore
|
||||||
|
clearTimeout(importLoadingIndicator)
|
||||||
|
|
||||||
|
this.importCsvCompleted = true
|
||||||
|
} else {
|
||||||
|
parseCsvMsg.message = 'Parsing ended with errors.'
|
||||||
|
parseCsvMsg.type = 'info'
|
||||||
|
this.importCsvMessages = this.importCsvMessages.concat(parseResult.messages)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (parseCsvMsg.type === 'loading') {
|
||||||
|
parseCsvMsg.type = 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (importMsg.type === 'loading') {
|
||||||
|
importMsg.type = 'info'
|
||||||
|
}
|
||||||
|
|
||||||
|
this.importCsvMessages.push({
|
||||||
|
message: err,
|
||||||
|
type: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTimeout(parseCsvLoadingIndicator)
|
||||||
|
clearTimeout(importLoadingIndicator)
|
||||||
|
this.newDb.deleteProgressCounter(progressCounterId)
|
||||||
|
this.disableDialog = false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async checkFile (file) {
|
||||||
|
this.state = 'drop'
|
||||||
|
if (file.type === 'text/csv') {
|
||||||
|
this.file = file
|
||||||
|
this.header = true
|
||||||
|
this.quoteChar = '"'
|
||||||
|
this.escapeChar = '"'
|
||||||
|
this.delimiter = ''
|
||||||
|
return Promise.all([this.previewCSV(), this.animationPromise])
|
||||||
|
.then(() => {
|
||||||
|
this.$modal.show('parse')
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.loadDb(file)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
browse () {
|
||||||
|
fu.getFileFromUser('.db,.sqlite,.sqlite3,.csv')
|
||||||
|
.then(this.checkFile)
|
||||||
|
},
|
||||||
|
|
||||||
drop (event) {
|
drop (event) {
|
||||||
this.loadDb(event.dataTransfer.files[0])
|
this.checkFile(event.dataTransfer.files[0])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
.db-upload-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
.drop-area-container {
|
.drop-area-container {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border: 1px dashed var(--color-border);
|
border: 1px dashed var(--color-border);
|
||||||
@@ -118,9 +418,9 @@ export default {
|
|||||||
|
|
||||||
#img-container {
|
#img-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: calc(50% - 120px);
|
top: 54px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, 0);
|
||||||
width: 450px;
|
width: 450px;
|
||||||
height: 338px;
|
height: 338px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
@@ -192,4 +492,35 @@ export default {
|
|||||||
@keyframes fly {
|
@keyframes fly {
|
||||||
100% { transform: rotate(360deg) scale(0.5); }
|
100% { transform: rotate(360deg) scale(0.5); }
|
||||||
}
|
}
|
||||||
|
/* Parse CSV dialog */
|
||||||
|
.chars {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.char-input {
|
||||||
|
margin-right: 44px;
|
||||||
|
}
|
||||||
|
.preview-table {
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-csv-errors {
|
||||||
|
height: 160px;
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
.no-data {
|
||||||
|
margin-top: 32px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 5px;
|
||||||
|
position: relative;
|
||||||
|
border: 1px solid var(--color-border-light);
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 160px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-base);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -8,9 +8,7 @@
|
|||||||
<tree-chevron :expanded="schemaVisible"/>
|
<tree-chevron :expanded="schemaVisible"/>
|
||||||
{{ dbName }}
|
{{ dbName }}
|
||||||
</div>
|
</div>
|
||||||
<div id="db-edit" @click="changeDb">
|
<db-upload id="db-edit" type="small" />
|
||||||
<change-db-icon />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-show="schemaVisible" class="schema">
|
<div v-show="schemaVisible" class="schema">
|
||||||
<table-description
|
<table-description
|
||||||
@@ -26,17 +24,16 @@
|
|||||||
<script>
|
<script>
|
||||||
import TableDescription from '@/components/TableDescription'
|
import TableDescription from '@/components/TableDescription'
|
||||||
import TextField from '@/components/TextField'
|
import TextField from '@/components/TextField'
|
||||||
import ChangeDbIcon from '@/components/svg/changeDb'
|
|
||||||
import TreeChevron from '@/components/svg/treeChevron'
|
import TreeChevron from '@/components/svg/treeChevron'
|
||||||
import fu from '@/fileUtils'
|
import dbUpload from '@/components/DbUpload'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Schema',
|
name: 'Schema',
|
||||||
components: {
|
components: {
|
||||||
TableDescription,
|
TableDescription,
|
||||||
TextField,
|
TextField,
|
||||||
ChangeDbIcon,
|
TreeChevron,
|
||||||
TreeChevron
|
dbUpload
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -59,17 +56,6 @@ export default {
|
|||||||
dbName () {
|
dbName () {
|
||||||
return this.$store.state.dbName
|
return this.$store.state.dbName
|
||||||
}
|
}
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
changeDb () {
|
|
||||||
fu.getFileFromUser('.db,.sqlite,.sqlite3')
|
|
||||||
.then(file => {
|
|
||||||
return this.$db.loadDb(file)
|
|
||||||
})
|
|
||||||
.then((schema) => {
|
|
||||||
this.$store.commit('saveSchema', schema)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -104,21 +104,16 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
// Run a command in the database
|
// Run a command in the database
|
||||||
execute () {
|
async execute () {
|
||||||
// this.$refs.output.textContent = 'Fetching results...' */
|
|
||||||
this.isGettingResults = true
|
this.isGettingResults = true
|
||||||
this.result = null
|
this.result = null
|
||||||
this.error = null
|
this.error = null
|
||||||
return this.$db.execute(this.query + ';')
|
try {
|
||||||
.then(result => {
|
this.result = await this.$store.state.db.execute(this.query + ';')
|
||||||
this.result = result
|
} catch (err) {
|
||||||
})
|
this.error = err
|
||||||
.catch(err => {
|
}
|
||||||
this.error = err
|
this.isGettingResults = false
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
this.isGettingResults = false
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
handleResize () {
|
handleResize () {
|
||||||
if (this.view === 'chart') {
|
if (this.view === 'chart') {
|
||||||
|
|||||||
78
src/csv.js
Normal file
78
src/csv.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import Papa from 'papaparse'
|
||||||
|
|
||||||
|
const hintsByCode = {
|
||||||
|
MissingQuotes: 'Edit your CSV so that the field has a closing quote char.',
|
||||||
|
TooFewFields: 'Add fields or try another delimiter.',
|
||||||
|
TooManyFields: 'Edit your CSV or try another delimiter.'
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
getResut (source) {
|
||||||
|
const result = {}
|
||||||
|
if (source.meta.fields) {
|
||||||
|
result.columns = source.meta.fields
|
||||||
|
result.values = source.data.map(row => {
|
||||||
|
const resultRow = []
|
||||||
|
result.columns.forEach(col => { resultRow.push(row[col]) })
|
||||||
|
return resultRow
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
result.values = source.data
|
||||||
|
result.columns = []
|
||||||
|
for (let i = 1; i <= source.data[0].length; i++) {
|
||||||
|
result.columns.push(`col ${i}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
|
||||||
|
parse (file, config = {}) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const defaultConfig = {
|
||||||
|
delimiter: '', // auto-detect
|
||||||
|
newline: '', // auto-detect
|
||||||
|
quoteChar: '"',
|
||||||
|
escapeChar: '"',
|
||||||
|
header: false,
|
||||||
|
transformHeader: undefined,
|
||||||
|
dynamicTyping: true,
|
||||||
|
preview: 0,
|
||||||
|
encoding: 'UTF-8',
|
||||||
|
worker: true,
|
||||||
|
comments: false,
|
||||||
|
step: undefined,
|
||||||
|
complete: results => {
|
||||||
|
const res = {
|
||||||
|
data: this.getResut(results),
|
||||||
|
delimiter: results.meta.delimiter,
|
||||||
|
hasErrors: false
|
||||||
|
}
|
||||||
|
res.messages = results.errors.map(msg => {
|
||||||
|
msg.type = msg.code === 'UndetectableDelimiter' ? 'info' : 'error'
|
||||||
|
if (msg.type === 'error') res.hasErrors = true
|
||||||
|
msg.hint = hintsByCode[msg.code]
|
||||||
|
return msg
|
||||||
|
})
|
||||||
|
resolve(res)
|
||||||
|
},
|
||||||
|
error: (error, file) => {
|
||||||
|
reject(error)
|
||||||
|
},
|
||||||
|
download: false,
|
||||||
|
downloadRequestHeaders: undefined,
|
||||||
|
downloadRequestBody: undefined,
|
||||||
|
skipEmptyLines: 'greedy',
|
||||||
|
chunk: undefined,
|
||||||
|
chunkSize: undefined,
|
||||||
|
fastMode: undefined,
|
||||||
|
beforeFirstChunk: undefined,
|
||||||
|
withCredentials: undefined,
|
||||||
|
transform: undefined,
|
||||||
|
delimitersToGuess: [',', '\t', '|', ';', Papa.RECORD_SEP, Papa.UNIT_SEP]
|
||||||
|
}
|
||||||
|
|
||||||
|
Papa.parse(file, { ...defaultConfig, ...config })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
149
src/database.js
149
src/database.js
@@ -1,59 +1,112 @@
|
|||||||
import sqliteParser from 'sqlite-parser'
|
import sqliteParser from 'sqlite-parser'
|
||||||
const worker = new Worker('js/worker.sql-wasm.js')
|
import fu from '@/fileUtils'
|
||||||
|
// We can import workers like so because of worker-loader:
|
||||||
|
// https://webpack.js.org/loaders/worker-loader/
|
||||||
|
import Worker from '@/db.worker.js'
|
||||||
|
|
||||||
|
// Use promise-worker in order to turn worker into the promise based one:
|
||||||
|
// https://github.com/nolanlawson/promise-worker
|
||||||
|
import PromiseWorker from 'promise-worker'
|
||||||
|
|
||||||
|
function getNewDatabase () {
|
||||||
|
const worker = new Worker()
|
||||||
|
return new Database(worker)
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
loadDb (file) {
|
getNewDatabase
|
||||||
return new Promise((resolve, reject) => {
|
}
|
||||||
const f = file
|
|
||||||
const r = new FileReader()
|
|
||||||
r.onload = () => {
|
|
||||||
// on 'action: open' completed
|
|
||||||
worker.onmessage = () => {
|
|
||||||
const getSchemaSql = `
|
|
||||||
SELECT name, sql
|
|
||||||
FROM sqlite_master
|
|
||||||
WHERE type='table' AND name NOT LIKE 'sqlite_%';`
|
|
||||||
|
|
||||||
this.execute(getSchemaSql)
|
let progressCounterIds = 0
|
||||||
.then(result => {
|
class Database {
|
||||||
// Parse DDL statements to get column names and types
|
constructor (worker) {
|
||||||
const parsedSchema = []
|
this.worker = worker
|
||||||
result.values.forEach(item => {
|
this.pw = new PromiseWorker(worker)
|
||||||
parsedSchema.push({
|
|
||||||
name: item[0],
|
|
||||||
columns: getColumns(item[1])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Return db name and schema
|
this.importProgresses = {}
|
||||||
resolve({
|
worker.addEventListener('message', e => {
|
||||||
dbName: file.name,
|
const progress = e.data.progress
|
||||||
schema: parsedSchema
|
if (progress !== undefined) {
|
||||||
})
|
const id = e.data.id
|
||||||
})
|
this.importProgresses[id].dispatchEvent(new CustomEvent('progress', {
|
||||||
}
|
detail: progress
|
||||||
|
}))
|
||||||
try {
|
|
||||||
worker.postMessage({ action: 'open', buffer: r.result }, [r.result])
|
|
||||||
} catch (exception) {
|
|
||||||
worker.postMessage({ action: 'open', buffer: r.result })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
r.readAsArrayBuffer(f)
|
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
execute (commands) {
|
|
||||||
return new Promise((resolve, reject) => {
|
shutDown () {
|
||||||
worker.onmessage = (event) => {
|
this.worker.terminate()
|
||||||
if (event.data.error) {
|
}
|
||||||
reject(event.data.error)
|
|
||||||
} else {
|
createProgressCounter (callback) {
|
||||||
// if it was more than one select - take only the first one
|
const id = progressCounterIds++
|
||||||
resolve(event.data.results[0])
|
this.importProgresses[id] = new EventTarget()
|
||||||
}
|
this.importProgresses[id].addEventListener('progress', callback)
|
||||||
}
|
return id
|
||||||
worker.postMessage({ action: 'exec', sql: commands })
|
}
|
||||||
|
|
||||||
|
deleteProgressCounter (id) {
|
||||||
|
delete this.importProgresses[id]
|
||||||
|
}
|
||||||
|
|
||||||
|
async createDb (name, data, progressCounterId) {
|
||||||
|
const result = await this.pw.postMessage({
|
||||||
|
action: 'import',
|
||||||
|
columns: data.columns,
|
||||||
|
values: data.values,
|
||||||
|
progressCounterId
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
throw result.error
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.getSchema(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadDb (file) {
|
||||||
|
const fileContent = await fu.readAsArrayBuffer(file)
|
||||||
|
const res = await this.pw.postMessage({ action: 'open', buffer: fileContent })
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
throw res.error
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getSchema(file.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSchema (name) {
|
||||||
|
const getSchemaSql = `
|
||||||
|
SELECT name, sql
|
||||||
|
FROM sqlite_master
|
||||||
|
WHERE type='table' AND name NOT LIKE 'sqlite_%';
|
||||||
|
`
|
||||||
|
const result = await this.execute(getSchemaSql)
|
||||||
|
// Parse DDL statements to get column names and types
|
||||||
|
const parsedSchema = []
|
||||||
|
result.values.forEach(item => {
|
||||||
|
parsedSchema.push({
|
||||||
|
name: item[0],
|
||||||
|
columns: getColumns(item[1])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Return db name and schema
|
||||||
|
return {
|
||||||
|
dbName: name,
|
||||||
|
schema: parsedSchema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute (commands) {
|
||||||
|
const results = await this.pw.postMessage({ action: 'exec', sql: commands })
|
||||||
|
|
||||||
|
if (results.error) {
|
||||||
|
throw results.error
|
||||||
|
}
|
||||||
|
// if it was more than one select - take only the first one
|
||||||
|
return results[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
98
src/db.worker.js
Normal file
98
src/db.worker.js
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import registerPromiseWorker from 'promise-worker/register'
|
||||||
|
import initSqlJs from 'sql.js/dist/sql-wasm.js'
|
||||||
|
import dbUtils from '@/dbUtils'
|
||||||
|
|
||||||
|
const sqlModuleReady = initSqlJs()
|
||||||
|
let db = null
|
||||||
|
|
||||||
|
function onModuleReady (SQL) {
|
||||||
|
function createDb (data) {
|
||||||
|
if (db != null) db.close()
|
||||||
|
db = new SQL.Database(data)
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = this
|
||||||
|
|
||||||
|
switch (data && data.action) {
|
||||||
|
case 'open':
|
||||||
|
const buff = data.buffer
|
||||||
|
createDb(buff && new Uint8Array(buff))
|
||||||
|
return {
|
||||||
|
ready: true
|
||||||
|
}
|
||||||
|
case 'exec':
|
||||||
|
if (db === null) {
|
||||||
|
createDb()
|
||||||
|
}
|
||||||
|
if (!data.sql) {
|
||||||
|
throw new Error('exec: Missing query string')
|
||||||
|
}
|
||||||
|
return db.exec(data.sql, data.params)
|
||||||
|
case 'each':
|
||||||
|
if (db === null) {
|
||||||
|
createDb()
|
||||||
|
}
|
||||||
|
const callback = function callback (row) {
|
||||||
|
return {
|
||||||
|
row: row,
|
||||||
|
finished: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const done = function done () {
|
||||||
|
return {
|
||||||
|
finished: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return db.each(data.sql, data.params, callback, done)
|
||||||
|
case 'import':
|
||||||
|
createDb()
|
||||||
|
const values = data.values
|
||||||
|
const columns = data.columns
|
||||||
|
const chunkSize = 1500
|
||||||
|
db.exec(dbUtils.getCreateStatement(columns, values))
|
||||||
|
const chunks = dbUtils.generateChunks(values, chunkSize)
|
||||||
|
const chunksAmount = Math.ceil(values.length / chunkSize)
|
||||||
|
let count = 0
|
||||||
|
const insertStr = dbUtils.getInsertStmt(columns)
|
||||||
|
const insertStmt = db.prepare(insertStr)
|
||||||
|
|
||||||
|
postMessage({ progress: 0, id: data.progressCounterId })
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
db.exec('BEGIN')
|
||||||
|
for (const row of chunk) {
|
||||||
|
insertStmt.run(row)
|
||||||
|
}
|
||||||
|
db.exec('COMMIT')
|
||||||
|
count++
|
||||||
|
postMessage({ progress: 100 * (count / chunksAmount), id: data.progressCounterId })
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
finish: true
|
||||||
|
}
|
||||||
|
case 'export':
|
||||||
|
return db.export()
|
||||||
|
case 'close':
|
||||||
|
if (db) {
|
||||||
|
db.close()
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
finished: true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error('Invalid action : ' + (data && data.action))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onError (err) {
|
||||||
|
return {
|
||||||
|
error: new Error(err.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerPromiseWorker(data => {
|
||||||
|
return sqlModuleReady
|
||||||
|
.then(onModuleReady.bind(data))
|
||||||
|
.catch(onError)
|
||||||
|
})
|
||||||
44
src/dbUtils.js
Normal file
44
src/dbUtils.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
export default {
|
||||||
|
* generateChunks (arr, size) {
|
||||||
|
const count = Math.ceil(arr.length / size)
|
||||||
|
|
||||||
|
for (let i = 0; i <= count - 1; i++) {
|
||||||
|
const start = size * i
|
||||||
|
const end = start + size
|
||||||
|
yield arr.slice(start, end)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getInsertStmt (columns) {
|
||||||
|
const colList = `"${columns.join('", "')}"`
|
||||||
|
const params = columns.map(() => '?').join(' ,')
|
||||||
|
return `INSERT INTO csv_import (${colList}) VALUES (${params});`
|
||||||
|
},
|
||||||
|
|
||||||
|
getCreateStatement (columns, values) {
|
||||||
|
let result = 'CREATE table csv_import('
|
||||||
|
columns.forEach((col, index) => {
|
||||||
|
// Get the first row of values to determine types
|
||||||
|
const value = values[0][index]
|
||||||
|
let type = ''
|
||||||
|
switch (typeof value) {
|
||||||
|
case 'number': {
|
||||||
|
type = 'REAL'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'boolean': {
|
||||||
|
type = 'INTEGER'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'string': {
|
||||||
|
type = 'TEXT'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default: type = 'TEXT'
|
||||||
|
}
|
||||||
|
result += `"${col}" ${type},`
|
||||||
|
})
|
||||||
|
result = result.replace(/.$/, ');')
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -50,5 +50,21 @@ export default {
|
|||||||
|
|
||||||
readFile (path) {
|
readFile (path) {
|
||||||
return fetch(path)
|
return fetch(path)
|
||||||
|
},
|
||||||
|
|
||||||
|
readAsArrayBuffer (file) {
|
||||||
|
const fileReader = new FileReader()
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
fileReader.onerror = () => {
|
||||||
|
fileReader.abort()
|
||||||
|
reject(new DOMException('Problem parsing input file.'))
|
||||||
|
}
|
||||||
|
|
||||||
|
fileReader.onload = () => {
|
||||||
|
resolve(fileReader.result)
|
||||||
|
}
|
||||||
|
fileReader.readAsArrayBuffer(file)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import router from './router'
|
|||||||
import store from './store'
|
import store from './store'
|
||||||
import { VuePlugin } from 'vuera'
|
import { VuePlugin } from 'vuera'
|
||||||
import VModal from 'vue-js-modal'
|
import VModal from 'vue-js-modal'
|
||||||
import db from '@/database'
|
|
||||||
|
|
||||||
import '@/assets/styles/variables.css'
|
import '@/assets/styles/variables.css'
|
||||||
import '@/assets/styles/buttons.css'
|
import '@/assets/styles/buttons.css'
|
||||||
@@ -17,7 +16,6 @@ Vue.use(VuePlugin)
|
|||||||
Vue.use(VModal)
|
Vue.use(VModal)
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
Vue.prototype.$db = db
|
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
router,
|
router,
|
||||||
|
|||||||
@@ -12,10 +12,17 @@ export const state = {
|
|||||||
currentTab: null,
|
currentTab: null,
|
||||||
currentTabId: null,
|
currentTabId: null,
|
||||||
untitledLastIndex: 0,
|
untitledLastIndex: 0,
|
||||||
predefinedQueries: []
|
predefinedQueries: [],
|
||||||
|
db: null
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
|
setDb (state, db) {
|
||||||
|
if (state.db) {
|
||||||
|
state.db.shutDown()
|
||||||
|
}
|
||||||
|
state.db = db
|
||||||
|
},
|
||||||
saveSchema (state, { dbName, schema }) {
|
saveSchema (state, { dbName, schema }) {
|
||||||
state.dbName = dbName
|
state.dbName = dbName
|
||||||
state.schema = schema
|
state.schema = schema
|
||||||
@@ -73,19 +80,18 @@ export const mutations = {
|
|||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
async addTab ({ state }, data) {
|
async addTab ({ state }, data) {
|
||||||
let tab
|
const tab = data ? JSON.parse(JSON.stringify(data)) : {}
|
||||||
// If no data then create a new blank one...
|
// If no data then create a new blank one...
|
||||||
if (!data) {
|
// No data.id means to create new tab, but not blank,
|
||||||
tab = {
|
// e.g. with 'select * from csv_import' query after csv import
|
||||||
id: nanoid(),
|
if (!data || !data.id) {
|
||||||
name: null,
|
tab.id = nanoid()
|
||||||
tempName: state.untitledLastIndex
|
tab.name = null
|
||||||
? `Untitled ${state.untitledLastIndex}`
|
tab.tempName = state.untitledLastIndex
|
||||||
: 'Untitled',
|
? `Untitled ${state.untitledLastIndex}`
|
||||||
isUnsaved: true
|
: 'Untitled'
|
||||||
}
|
tab.isUnsaved = true
|
||||||
} else {
|
} else {
|
||||||
tab = JSON.parse(JSON.stringify(data))
|
|
||||||
tab.isUnsaved = false
|
tab.isUnsaved = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
36
src/time.js
Normal file
36
src/time.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
export default {
|
||||||
|
getPeriod (start, end) {
|
||||||
|
let diff = end.getTime() - start.getTime()
|
||||||
|
let result = ''
|
||||||
|
|
||||||
|
const days = Math.floor(diff / (1000 * 60 * 60 * 24))
|
||||||
|
diff -= days * (1000 * 60 * 60 * 24)
|
||||||
|
if (days) {
|
||||||
|
result += days + ' d '
|
||||||
|
}
|
||||||
|
|
||||||
|
const hours = Math.floor(diff / (1000 * 60 * 60))
|
||||||
|
diff -= hours * (1000 * 60 * 60)
|
||||||
|
if (hours) {
|
||||||
|
result += hours + ' h '
|
||||||
|
}
|
||||||
|
|
||||||
|
const mins = Math.floor(diff / (1000 * 60))
|
||||||
|
diff -= mins * (1000 * 60)
|
||||||
|
if (mins) {
|
||||||
|
result += mins + ' m '
|
||||||
|
}
|
||||||
|
|
||||||
|
const seconds = Math.floor(diff / (1000))
|
||||||
|
diff -= seconds * (1000)
|
||||||
|
if (seconds) {
|
||||||
|
result += seconds + ' s '
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diff) {
|
||||||
|
result += diff + ' ms '
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.replace(/\s$/, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="dbloader-container">
|
<div id="dbloader-container">
|
||||||
<db-upload illustrated />
|
<db-upload type="illustrated" />
|
||||||
<div id="note">
|
<div id="note">
|
||||||
Sqliteviz is fully client-side. Your database never leaves your computer.
|
Sqliteviz is fully client-side. Your database never leaves your computer.
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import Vuex from 'vuex'
|
|||||||
import { shallowMount } from '@vue/test-utils'
|
import { shallowMount } from '@vue/test-utils'
|
||||||
import DbUpload from '@/components/DbUpload.vue'
|
import DbUpload from '@/components/DbUpload.vue'
|
||||||
import fu from '@/fileUtils'
|
import fu from '@/fileUtils'
|
||||||
|
import database from '@/database.js'
|
||||||
|
|
||||||
describe('DbUploader.vue', () => {
|
describe('DbUploader.vue', () => {
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
@@ -24,7 +25,10 @@ describe('DbUploader.vue', () => {
|
|||||||
|
|
||||||
// mock db loading
|
// mock db loading
|
||||||
const schema = {}
|
const schema = {}
|
||||||
const $db = { loadDb: sinon.stub().resolves(schema) }
|
const db = {
|
||||||
|
loadDb: sinon.stub().resolves(schema)
|
||||||
|
}
|
||||||
|
database.getNewDatabase = sinon.stub().returns(db)
|
||||||
|
|
||||||
// mock router
|
// mock router
|
||||||
const $router = { push: sinon.stub() }
|
const $router = { push: sinon.stub() }
|
||||||
@@ -33,12 +37,12 @@ describe('DbUploader.vue', () => {
|
|||||||
// mount the component
|
// mount the component
|
||||||
const wrapper = shallowMount(DbUpload, {
|
const wrapper = shallowMount(DbUpload, {
|
||||||
store,
|
store,
|
||||||
mocks: { $db, $router, $route }
|
mocks: { $router, $route }
|
||||||
})
|
})
|
||||||
|
|
||||||
await wrapper.find('.drop-area').trigger('click')
|
await wrapper.find('.drop-area').trigger('click')
|
||||||
expect($db.loadDb.calledOnceWith(file)).to.equal(true)
|
expect(db.loadDb.calledOnceWith(file)).to.equal(true)
|
||||||
await $db.loadDb.returnValues[0]
|
await db.loadDb.returnValues[0]
|
||||||
expect(mutations.saveSchema.calledOnceWith(state, schema)).to.equal(true)
|
expect(mutations.saveSchema.calledOnceWith(state, schema)).to.equal(true)
|
||||||
expect($router.push.calledOnceWith('/editor')).to.equal(true)
|
expect($router.push.calledOnceWith('/editor')).to.equal(true)
|
||||||
})
|
})
|
||||||
@@ -53,7 +57,10 @@ describe('DbUploader.vue', () => {
|
|||||||
|
|
||||||
// mock db loading
|
// mock db loading
|
||||||
const schema = {}
|
const schema = {}
|
||||||
const $db = { loadDb: sinon.stub().resolves(schema) }
|
const db = {
|
||||||
|
loadDb: sinon.stub().resolves(schema)
|
||||||
|
}
|
||||||
|
database.getNewDatabase = sinon.stub().returns(db)
|
||||||
|
|
||||||
// mock router
|
// mock router
|
||||||
const $router = { push: sinon.stub() }
|
const $router = { push: sinon.stub() }
|
||||||
@@ -62,7 +69,7 @@ describe('DbUploader.vue', () => {
|
|||||||
// mount the component
|
// mount the component
|
||||||
const wrapper = shallowMount(DbUpload, {
|
const wrapper = shallowMount(DbUpload, {
|
||||||
store,
|
store,
|
||||||
mocks: { $db, $router, $route }
|
mocks: { $router, $route }
|
||||||
})
|
})
|
||||||
|
|
||||||
// mock a file dropped by a user
|
// mock a file dropped by a user
|
||||||
@@ -74,8 +81,8 @@ describe('DbUploader.vue', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
await wrapper.find('.drop-area').trigger('drop', dropData)
|
await wrapper.find('.drop-area').trigger('drop', dropData)
|
||||||
expect($db.loadDb.calledOnceWith(file)).to.equal(true)
|
expect(db.loadDb.calledOnceWith(file)).to.equal(true)
|
||||||
await $db.loadDb.returnValues[0]
|
await db.loadDb.returnValues[0]
|
||||||
expect(mutations.saveSchema.calledOnceWith(state, schema)).to.equal(true)
|
expect(mutations.saveSchema.calledOnceWith(state, schema)).to.equal(true)
|
||||||
expect($router.push.calledOnceWith('/editor')).to.equal(true)
|
expect($router.push.calledOnceWith('/editor')).to.equal(true)
|
||||||
})
|
})
|
||||||
@@ -94,7 +101,10 @@ describe('DbUploader.vue', () => {
|
|||||||
|
|
||||||
// mock db loading
|
// mock db loading
|
||||||
const schema = {}
|
const schema = {}
|
||||||
const $db = { loadDb: sinon.stub().resolves(schema) }
|
const db = {
|
||||||
|
loadDb: sinon.stub().resolves(schema)
|
||||||
|
}
|
||||||
|
database.getNewDatabase = sinon.stub().returns(db)
|
||||||
|
|
||||||
// mock router
|
// mock router
|
||||||
const $router = { push: sinon.stub() }
|
const $router = { push: sinon.stub() }
|
||||||
@@ -103,11 +113,11 @@ describe('DbUploader.vue', () => {
|
|||||||
// mount the component
|
// mount the component
|
||||||
const wrapper = shallowMount(DbUpload, {
|
const wrapper = shallowMount(DbUpload, {
|
||||||
store,
|
store,
|
||||||
mocks: { $db, $router, $route }
|
mocks: { $router, $route }
|
||||||
})
|
})
|
||||||
|
|
||||||
await wrapper.find('.drop-area').trigger('click')
|
await wrapper.find('.drop-area').trigger('click')
|
||||||
await $db.loadDb.returnValues[0]
|
await db.loadDb.returnValues[0]
|
||||||
expect($router.push.called).to.equal(false)
|
expect($router.push.called).to.equal(false)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -184,7 +184,6 @@ describe('MainMenu.vue', () => {
|
|||||||
|
|
||||||
it('Ctrl R calls currentTab.execute if running is enabled and route.path is "/editor"',
|
it('Ctrl R calls currentTab.execute if running is enabled and route.path is "/editor"',
|
||||||
async () => {
|
async () => {
|
||||||
console.log('ctrl r')
|
|
||||||
const state = {
|
const state = {
|
||||||
currentTab: {
|
currentTab: {
|
||||||
query: 'SELECT * FROM foo',
|
query: 'SELECT * FROM foo',
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { mount, createLocalVue } from '@vue/test-utils'
|
|||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
import Schema from '@/components/Schema.vue'
|
import Schema from '@/components/Schema.vue'
|
||||||
import TableDescription from '@/components/TableDescription.vue'
|
import TableDescription from '@/components/TableDescription.vue'
|
||||||
import fu from '@/fileUtils.js'
|
|
||||||
|
|
||||||
const localVue = createLocalVue()
|
const localVue = createLocalVue()
|
||||||
localVue.use(Vuex)
|
localVue.use(Vuex)
|
||||||
@@ -99,75 +98,4 @@ describe('Schema.vue', () => {
|
|||||||
expect(tables.at(1).vm.name).to.equal('bar')
|
expect(tables.at(1).vm.name).to.equal('bar')
|
||||||
expect(tables.at(2).vm.name).to.equal('foobar')
|
expect(tables.at(2).vm.name).to.equal('foobar')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Change DB', async () => {
|
|
||||||
// mock store state and mutations
|
|
||||||
const mutations = {
|
|
||||||
saveSchema: sinon.stub()
|
|
||||||
}
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
dbName: 'fooDB',
|
|
||||||
schema: [
|
|
||||||
{
|
|
||||||
name: 'foo',
|
|
||||||
columns: [
|
|
||||||
{ name: 'foo_id', type: 'INTEGER' },
|
|
||||||
{ name: 'foo_title', type: 'NVARCHAR(24)' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'foo_prices',
|
|
||||||
columns: [
|
|
||||||
{ name: 'foo_id', type: 'INTEGER' },
|
|
||||||
{ name: 'foo_price', type: 'INTEGER' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
const store = new Vuex.Store({ state, mutations })
|
|
||||||
|
|
||||||
// stub getFileFromUser
|
|
||||||
const file = { file: 'hello' }
|
|
||||||
sinon.stub(fu, 'getFileFromUser').resolves(file)
|
|
||||||
|
|
||||||
// mock $db.loadDb()
|
|
||||||
const newSchema = {
|
|
||||||
dbName: 'barDB',
|
|
||||||
schema: [
|
|
||||||
{
|
|
||||||
name: 'bar',
|
|
||||||
columns: [
|
|
||||||
{ name: 'bar_id', type: 'INTEGER' },
|
|
||||||
{ name: 'bar_title', type: 'NVARCHAR(24)' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'bar_prices',
|
|
||||||
columns: [
|
|
||||||
{ name: 'bar_id', type: 'INTEGER' },
|
|
||||||
{ name: 'bar_price', type: 'INTEGER' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
const $db = {
|
|
||||||
loadDb: sinon.stub().resolves(newSchema)
|
|
||||||
}
|
|
||||||
|
|
||||||
// mount the component
|
|
||||||
const wrapper = mount(Schema, { store, localVue, mocks: { $db } })
|
|
||||||
|
|
||||||
// trigger the event
|
|
||||||
await wrapper.find('#db-edit').trigger('click')
|
|
||||||
|
|
||||||
expect(fu.getFileFromUser.calledOnceWith('.db,.sqlite,.sqlite3')).to.equal(true)
|
|
||||||
|
|
||||||
await fu.getFileFromUser.returnValues[0]
|
|
||||||
expect($db.loadDb.calledOnceWith(file)).to.equal(true)
|
|
||||||
|
|
||||||
await $db.loadDb.returnValues[0]
|
|
||||||
expect(mutations.saveSchema.calledOnceWith(state, newSchema)).to.equal(true)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -145,18 +145,17 @@ describe('Tab.vue', () => {
|
|||||||
it('Shows .result-in-progress message when executing query', (done) => {
|
it('Shows .result-in-progress message when executing query', (done) => {
|
||||||
// mock store state
|
// mock store state
|
||||||
const state = {
|
const state = {
|
||||||
currentTabId: 1
|
currentTabId: 1,
|
||||||
|
db: {
|
||||||
|
execute () { return new Promise(() => {}) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = new Vuex.Store({ state, mutations })
|
const store = new Vuex.Store({ state, mutations })
|
||||||
const $db = {
|
|
||||||
execute () { return new Promise(() => {}) }
|
|
||||||
}
|
|
||||||
// mount the component
|
// mount the component
|
||||||
const wrapper = mount(Tab, {
|
const wrapper = mount(Tab, {
|
||||||
store,
|
store,
|
||||||
stubs: ['chart'],
|
stubs: ['chart'],
|
||||||
mocks: { $db },
|
|
||||||
propsData: {
|
propsData: {
|
||||||
id: 1,
|
id: 1,
|
||||||
initName: 'foo',
|
initName: 'foo',
|
||||||
@@ -178,18 +177,17 @@ describe('Tab.vue', () => {
|
|||||||
it('Shows error when executing query ends with error', async () => {
|
it('Shows error when executing query ends with error', async () => {
|
||||||
// mock store state
|
// mock store state
|
||||||
const state = {
|
const state = {
|
||||||
currentTabId: 1
|
currentTabId: 1,
|
||||||
|
db: {
|
||||||
|
execute () { return Promise.reject(new Error('There is no table foo')) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = new Vuex.Store({ state, mutations })
|
const store = new Vuex.Store({ state, mutations })
|
||||||
const $db = {
|
|
||||||
execute () { return Promise.reject(new Error('There is no table foo')) }
|
|
||||||
}
|
|
||||||
// mount the component
|
// mount the component
|
||||||
const wrapper = mount(Tab, {
|
const wrapper = mount(Tab, {
|
||||||
store,
|
store,
|
||||||
stubs: ['chart'],
|
stubs: ['chart'],
|
||||||
mocks: { $db },
|
|
||||||
propsData: {
|
propsData: {
|
||||||
id: 1,
|
id: 1,
|
||||||
initName: 'foo',
|
initName: 'foo',
|
||||||
@@ -210,7 +208,10 @@ describe('Tab.vue', () => {
|
|||||||
it('Passes result to sql-table component', async () => {
|
it('Passes result to sql-table component', async () => {
|
||||||
// mock store state
|
// mock store state
|
||||||
const state = {
|
const state = {
|
||||||
currentTabId: 1
|
currentTabId: 1,
|
||||||
|
db: {
|
||||||
|
execute () { return Promise.resolve(result) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const store = new Vuex.Store({ state, mutations })
|
const store = new Vuex.Store({ state, mutations })
|
||||||
@@ -221,14 +222,11 @@ describe('Tab.vue', () => {
|
|||||||
[2, 'bar']
|
[2, 'bar']
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
const $db = {
|
|
||||||
execute () { return Promise.resolve(result) }
|
|
||||||
}
|
|
||||||
// mount the component
|
// mount the component
|
||||||
const wrapper = mount(Tab, {
|
const wrapper = mount(Tab, {
|
||||||
store,
|
store,
|
||||||
stubs: ['chart'],
|
stubs: ['chart'],
|
||||||
mocks: { $db },
|
|
||||||
propsData: {
|
propsData: {
|
||||||
id: 1,
|
id: 1,
|
||||||
initName: 'foo',
|
initName: 'foo',
|
||||||
|
|||||||
@@ -1,129 +1,113 @@
|
|||||||
import { expect } from 'chai'
|
import chai from 'chai'
|
||||||
|
import chaiAsPromised from 'chai-as-promised'
|
||||||
import initSqlJs from 'sql.js'
|
import initSqlJs from 'sql.js'
|
||||||
import db from '@/database.js'
|
import database from '@/database.js'
|
||||||
const config = {
|
chai.use(chaiAsPromised)
|
||||||
locateFile: filename => 'js/sql-wasm.wasm'
|
const expect = chai.expect
|
||||||
}
|
chai.should()
|
||||||
|
|
||||||
|
const db = database.getNewDatabase()
|
||||||
|
const getSQL = initSqlJs()
|
||||||
|
|
||||||
describe('database.js', () => {
|
describe('database.js', () => {
|
||||||
it('creates schema', () => {
|
it('creates schema', async () => {
|
||||||
return initSqlJs(config)
|
const SQL = await getSQL
|
||||||
.then(SQL => {
|
const tempDb = new SQL.Database()
|
||||||
const database = new SQL.Database()
|
tempDb.run(`CREATE TABLE test (
|
||||||
database.run(`
|
col1,
|
||||||
CREATE TABLE test (
|
col2 integer,
|
||||||
col1,
|
col3 decimal(5,2),
|
||||||
col2 integer,
|
col4 varchar(30)
|
||||||
col3 decimal(5,2),
|
)`)
|
||||||
col4 varchar(30)
|
|
||||||
)
|
|
||||||
`)
|
|
||||||
|
|
||||||
const data = database.export()
|
const data = tempDb.export()
|
||||||
const buffer = new Blob([data])
|
const buffer = new Blob([data])
|
||||||
return db.loadDb(buffer)
|
|
||||||
})
|
const { schema } = await db.loadDb(buffer)
|
||||||
.then(({ dbName, schema }) => {
|
expect(schema).to.have.lengthOf(1)
|
||||||
expect(schema).to.have.lengthOf(1)
|
expect(schema[0].name).to.equal('test')
|
||||||
expect(schema[0].name).to.equal('test')
|
expect(schema[0].columns[0].name).to.equal('col1')
|
||||||
expect(schema[0].columns[0].name).to.equal('col1')
|
expect(schema[0].columns[0].type).to.equal('N/A')
|
||||||
expect(schema[0].columns[0].type).to.equal('N/A')
|
expect(schema[0].columns[1].name).to.equal('col2')
|
||||||
expect(schema[0].columns[1].name).to.equal('col2')
|
expect(schema[0].columns[1].type).to.equal('integer')
|
||||||
expect(schema[0].columns[1].type).to.equal('integer')
|
expect(schema[0].columns[2].name).to.equal('col3')
|
||||||
expect(schema[0].columns[2].name).to.equal('col3')
|
expect(schema[0].columns[2].type).to.equal('decimal(5, 2)')
|
||||||
expect(schema[0].columns[2].type).to.equal('decimal(5, 2)')
|
expect(schema[0].columns[3].name).to.equal('col4')
|
||||||
expect(schema[0].columns[3].name).to.equal('col4')
|
expect(schema[0].columns[3].type).to.equal('varchar(30)')
|
||||||
expect(schema[0].columns[3].type).to.equal('varchar(30)')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('creates schema with virtual table', () => {
|
it('creates schema with virtual table', async () => {
|
||||||
return initSqlJs(config)
|
const SQL = await getSQL
|
||||||
.then(SQL => {
|
const tempDb = new SQL.Database()
|
||||||
const database = new SQL.Database()
|
tempDb.run(`
|
||||||
database.run(`
|
CREATE VIRTUAL TABLE test_virtual USING fts4(
|
||||||
CREATE VIRTUAL TABLE test_virtual USING fts4(
|
col1, col2,
|
||||||
col1, col2,
|
notindexed=col1, notindexed=col2,
|
||||||
notindexed=col1, notindexed=col2,
|
tokenize=unicode61 "tokenchars=.+#")
|
||||||
tokenize=unicode61 "tokenchars=.+#")
|
`)
|
||||||
`)
|
|
||||||
|
|
||||||
const data = database.export()
|
const data = tempDb.export()
|
||||||
const buffer = new Blob([data])
|
const buffer = new Blob([data])
|
||||||
return db.loadDb(buffer)
|
|
||||||
})
|
const { schema } = await db.loadDb(buffer)
|
||||||
.then(({ dbName, schema }) => {
|
expect(schema[0].name).to.equal('test_virtual')
|
||||||
expect(schema[0].name).to.equal('test_virtual')
|
expect(schema[0].columns[0].name).to.equal('col1')
|
||||||
expect(schema[0].columns[0].name).to.equal('col1')
|
expect(schema[0].columns[0].type).to.equal('N/A')
|
||||||
expect(schema[0].columns[0].type).to.equal('N/A')
|
expect(schema[0].columns[1].name).to.equal('col2')
|
||||||
expect(schema[0].columns[1].name).to.equal('col2')
|
expect(schema[0].columns[1].type).to.equal('N/A')
|
||||||
expect(schema[0].columns[1].type).to.equal('N/A')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns a query result', () => {
|
it('returns a query result', async () => {
|
||||||
return initSqlJs(config)
|
const SQL = await getSQL
|
||||||
.then(SQL => {
|
const tempDb = new SQL.Database()
|
||||||
const database = new SQL.Database()
|
tempDb.run(`
|
||||||
database.run(`
|
CREATE TABLE test (
|
||||||
CREATE TABLE test (
|
id integer,
|
||||||
id integer,
|
name varchar(100),
|
||||||
name varchar(100),
|
faculty varchar(100)
|
||||||
faculty varchar(100)
|
);
|
||||||
);
|
INSERT INTO test (id, name, faculty)
|
||||||
INSERT INTO test (id, name, faculty)
|
VALUES
|
||||||
VALUES
|
( 1, 'Harry Potter', 'Griffindor'),
|
||||||
( 1, 'Harry Potter', 'Griffindor'),
|
( 2, 'Draco Malfoy', 'Slytherin');
|
||||||
( 2, 'Draco Malfoy', 'Slytherin');
|
`)
|
||||||
`)
|
|
||||||
|
|
||||||
const data = database.export()
|
const data = tempDb.export()
|
||||||
const buffer = new Blob([data])
|
const buffer = new Blob([data])
|
||||||
return db.loadDb(buffer)
|
|
||||||
})
|
await db.loadDb(buffer)
|
||||||
.then(({ dbName, schema }) => {
|
const result = await db.execute('SELECT * from test')
|
||||||
return db.execute('SELECT * from test')
|
expect(result.columns).to.have.lengthOf(3)
|
||||||
})
|
expect(result.columns[0]).to.equal('id')
|
||||||
.then(result => {
|
expect(result.columns[1]).to.equal('name')
|
||||||
expect(result.columns).to.have.lengthOf(3)
|
expect(result.columns[2]).to.equal('faculty')
|
||||||
expect(result.columns[0]).to.equal('id')
|
expect(result.values).to.have.lengthOf(2)
|
||||||
expect(result.columns[1]).to.equal('name')
|
expect(result.values[0][0]).to.equal(1)
|
||||||
expect(result.columns[2]).to.equal('faculty')
|
expect(result.values[0][1]).to.equal('Harry Potter')
|
||||||
expect(result.values).to.have.lengthOf(2)
|
expect(result.values[0][2]).to.equal('Griffindor')
|
||||||
expect(result.values[0][0]).to.equal(1)
|
expect(result.values[1][0]).to.equal(2)
|
||||||
expect(result.values[0][1]).to.equal('Harry Potter')
|
expect(result.values[1][1]).to.equal('Draco Malfoy')
|
||||||
expect(result.values[0][2]).to.equal('Griffindor')
|
expect(result.values[1][2]).to.equal('Slytherin')
|
||||||
expect(result.values[1][0]).to.equal(2)
|
|
||||||
expect(result.values[1][1]).to.equal('Draco Malfoy')
|
|
||||||
expect(result.values[1][2]).to.equal('Slytherin')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns an error', () => {
|
it('returns an error', async () => {
|
||||||
return initSqlJs(config)
|
const SQL = await getSQL
|
||||||
.then(SQL => {
|
const tempDb = new SQL.Database()
|
||||||
const database = new SQL.Database()
|
tempDb.run(`
|
||||||
database.run(`
|
CREATE TABLE test (
|
||||||
CREATE TABLE test (
|
id integer,
|
||||||
id integer,
|
name varchar(100),
|
||||||
name varchar(100),
|
faculty varchar(100)
|
||||||
faculty varchar(100)
|
);
|
||||||
);
|
INSERT INTO test (id, name, faculty)
|
||||||
INSERT INTO test (id, name, faculty)
|
VALUES
|
||||||
VALUES
|
( 1, 'Harry Potter', 'Griffindor'),
|
||||||
( 1, 'Harry Potter', 'Griffindor'),
|
( 2, 'Draco Malfoy', 'Slytherin');
|
||||||
( 2, 'Draco Malfoy', 'Slytherin');
|
`)
|
||||||
`)
|
|
||||||
|
|
||||||
const data = database.export()
|
const data = tempDb.export()
|
||||||
const buffer = new Blob([data])
|
const buffer = new Blob([data])
|
||||||
return db.loadDb(buffer)
|
await db.loadDb(buffer)
|
||||||
})
|
await expect(db.execute('SELECT * from foo')).to.be.rejectedWith(/^no such table: foo$/)
|
||||||
.then(() => {
|
|
||||||
return db.execute('SELECT * from foo')
|
|
||||||
})
|
|
||||||
.catch(result => {
|
|
||||||
expect(result).to.equal('no such table: foo')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
17
tests/unit/dbUtils.spec.js
Normal file
17
tests/unit/dbUtils.spec.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { expect } from 'chai'
|
||||||
|
import dbUtils from '@/dbUtils.js'
|
||||||
|
|
||||||
|
describe('dbUtils.js', () => {
|
||||||
|
it('generator', () => {
|
||||||
|
const arr = ['1', '2', '3', '4', '5']
|
||||||
|
const size = 2
|
||||||
|
const chunks = dbUtils.generateChunks(arr, size)
|
||||||
|
const output = []
|
||||||
|
for (const chunk of chunks) {
|
||||||
|
output.push(chunk)
|
||||||
|
}
|
||||||
|
expect(output[0]).to.eql(['1', '2'])
|
||||||
|
expect(output[1]).to.eql(['3', '4'])
|
||||||
|
expect(output[2]).to.eql(['5'])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -8,7 +8,6 @@ module.exports = {
|
|||||||
// This wasm file will be fetched dynamically when we initialize sql.js
|
// This wasm file will be fetched dynamically when we initialize sql.js
|
||||||
// It is important that we do not change its name, and that it is in the same folder as the js
|
// It is important that we do not change its name, and that it is in the same folder as the js
|
||||||
{ from: 'node_modules/sql.js/dist/sql-wasm.wasm', to: 'js/' },
|
{ from: 'node_modules/sql.js/dist/sql-wasm.wasm', to: 'js/' },
|
||||||
{ from: 'node_modules/sql.js/dist/worker.sql-wasm.js', to: 'js/' },
|
|
||||||
{ from: 'LICENSE', to: './' }
|
{ from: 'LICENSE', to: './' }
|
||||||
])
|
])
|
||||||
]
|
]
|
||||||
@@ -22,5 +21,14 @@ module.exports = {
|
|||||||
.options({
|
.options({
|
||||||
limit: 10000
|
limit: 10000
|
||||||
})
|
})
|
||||||
|
|
||||||
|
config.module
|
||||||
|
.rule('worker')
|
||||||
|
.test(/\.worker\.js$/)
|
||||||
|
.use('worker-loader')
|
||||||
|
.loader('worker-loader')
|
||||||
|
.end()
|
||||||
|
|
||||||
|
config.module.rule('js').exclude.add(/\.worker\.js$/)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user