mirror of
https://github.com/lana-k/sqliteviz.git
synced 2026-03-24 23:16:18 +08:00
format
This commit is contained in:
12
.eslintrc.js
12
.eslintrc.js
@@ -2,12 +2,9 @@ module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
es2022: true,
|
||||
es2022: true
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/essential',
|
||||
'@vue/standard'
|
||||
],
|
||||
extends: ['eslint:recommended', 'plugin:vue/vue3-recommended', 'prettier'],
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
@@ -20,10 +17,7 @@ module.exports = {
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: [
|
||||
'**/__tests__/*.{j,t}s?(x)',
|
||||
'**/tests/**/*.spec.{j,t}s?(x)'
|
||||
],
|
||||
files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/**/*.spec.{j,t}s?(x)'],
|
||||
env: {
|
||||
mocha: true
|
||||
}
|
||||
|
||||
13
.github/workflows/config.grenrc.js
vendored
13
.github/workflows/config.grenrc.js
vendored
@@ -1,17 +1,14 @@
|
||||
module.exports = {
|
||||
dataSource: 'milestones',
|
||||
ignoreIssuesWith: [
|
||||
'wontfix',
|
||||
'duplicate'
|
||||
],
|
||||
ignoreIssuesWith: ['wontfix', 'duplicate'],
|
||||
milestoneMatch: 'v{{tag_name}}',
|
||||
template: {
|
||||
issue: '- {{name}} [{{text}}]({{url}})',
|
||||
changelogTitle: "",
|
||||
release: "{{body}}",
|
||||
changelogTitle: '',
|
||||
release: '{{body}}'
|
||||
},
|
||||
groupBy: {
|
||||
'Enhancements': ["enhancement", "internal"],
|
||||
'Bug fixes': ["bug"]
|
||||
Enhancements: ['enhancement', 'internal'],
|
||||
'Bug fixes': ['bug']
|
||||
}
|
||||
}
|
||||
|
||||
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@@ -40,6 +40,6 @@ jobs:
|
||||
- name: Create release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "dist.zip,dist_map.zip"
|
||||
artifacts: 'dist.zip,dist_map.zip'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
bodyFile: "CHANGELOG.md"
|
||||
bodyFile: 'CHANGELOG.md'
|
||||
|
||||
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
@@ -8,6 +8,7 @@ Sqliteviz is a single-page offline-first PWA for fully client-side visualisation
|
||||
of SQLite databases, CSV, JSON or NDJSON files.
|
||||
|
||||
With sqliteviz you can:
|
||||
|
||||
- run SQL queries against a SQLite database and create [Plotly][11] charts and pivot tables based on the result sets
|
||||
- import a CSV/JSON/NDJSON file into a SQLite database and visualize imported data
|
||||
- export result set to CSV file
|
||||
@@ -19,15 +20,19 @@ With sqliteviz you can:
|
||||
https://user-images.githubusercontent.com/24638357/128249848-f8fab0f5-9add-46e0-a9c1-dd5085a8623e.mp4
|
||||
|
||||
## Quickstart
|
||||
|
||||
The latest release of sqliteviz is deployed on [sqliteviz.com/app][6].
|
||||
|
||||
## Wiki
|
||||
|
||||
For user documentation, check out sqliteviz [documentation][7].
|
||||
|
||||
## Motivation
|
||||
|
||||
It's a kind of middleground between [Plotly Falcon][1] and [Redash][2].
|
||||
|
||||
## Components
|
||||
|
||||
It is built on top of [react-chart-editor][3], [PivotTable.js][12], [sql.js][4] and [Vue-Codemirror][8] in [Vue.js][5]. CSV parsing is performed with [Papa Parse][9].
|
||||
|
||||
[1]: https://github.com/plotly/falcon
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
presets: ['@vue/cli-plugin-babel/preset']
|
||||
}
|
||||
|
||||
40
index.html
40
index.html
@@ -1,11 +1,11 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="favicon.png">
|
||||
<link rel="manifest" href="manifest.webmanifest">
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<link rel="icon" href="favicon.png" />
|
||||
<link rel="manifest" href="manifest.webmanifest" />
|
||||
<title>sqliteviz</title>
|
||||
<style>
|
||||
#sqliteviz-loading-wrapper {
|
||||
@@ -38,15 +38,18 @@
|
||||
|
||||
#sqliteviz-loading-wrapper circle {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
fill: none;
|
||||
stroke-width: 5px;
|
||||
stroke-linecap: round;
|
||||
stroke: #119DFF;
|
||||
stroke: #119dff;
|
||||
}
|
||||
|
||||
#sqliteviz-loading-wrapper circle.bg {
|
||||
stroke: #C8D4E3;
|
||||
stroke: #c8d4e3;
|
||||
}
|
||||
|
||||
#sqliteviz-loading-wrapper circle.front {
|
||||
@@ -74,24 +77,17 @@
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but this app doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
<strong>
|
||||
We're sorry but this app doesn't work properly without JavaScript
|
||||
enabled. Please enable it to continue.
|
||||
</strong>
|
||||
</noscript>
|
||||
<div id="app">
|
||||
<div id="sqliteviz-loading-wrapper">
|
||||
<div id="sqliteviz-loading-text">LOADING</div>
|
||||
<svg height="170" width="170" viewBox="0 0 170 170">
|
||||
<circle
|
||||
class="bg"
|
||||
cx="85"
|
||||
cy="85"
|
||||
r="80"
|
||||
/>
|
||||
<circle
|
||||
class="front"
|
||||
cx="85"
|
||||
cy="85"
|
||||
r="80"
|
||||
/>
|
||||
<circle class="bg" cx="85" cy="85" r="80" />
|
||||
<circle class="front" cx="85" cy="85" r="80" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
10
jsconfig.json
Normal file
10
jsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ module.exports = function (config) {
|
||||
config: {
|
||||
resolve: {
|
||||
alias: {
|
||||
'vue': 'vue/dist/vue.esm-bundler.js'
|
||||
vue: 'vue/dist/vue.esm-bundler.js'
|
||||
}
|
||||
},
|
||||
server: {
|
||||
@@ -32,19 +32,19 @@ module.exports = function (config) {
|
||||
pattern: 'test.setup.js',
|
||||
type: 'module',
|
||||
watched: false,
|
||||
served: false,
|
||||
served: false
|
||||
},
|
||||
{
|
||||
pattern: 'tests/**/*.spec.js',
|
||||
type: 'module',
|
||||
watched: false,
|
||||
served: false,
|
||||
served: false
|
||||
},
|
||||
{
|
||||
pattern: 'src/assets/styles/*.css',
|
||||
type: 'css',
|
||||
watched: false,
|
||||
served: false,
|
||||
served: false
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
module.exports = function (config) {
|
||||
const timeout = 15 * 60 * 1000
|
||||
config.set({
|
||||
|
||||
frameworks: ['mocha'],
|
||||
|
||||
files: [
|
||||
'suite.js',
|
||||
{ pattern: 'node_modules/sql.js/dist/sql-wasm.wasm', served: true, included: false },
|
||||
{
|
||||
pattern: 'node_modules/sql.js/dist/sql-wasm.wasm',
|
||||
served: true,
|
||||
included: false
|
||||
},
|
||||
{ pattern: 'sample.csv', served: true, included: false }
|
||||
],
|
||||
|
||||
@@ -15,7 +18,10 @@ module.exports = function (config) {
|
||||
singleRun: true,
|
||||
|
||||
customLaunchers: {
|
||||
ChromiumHeadlessNoSandbox: { base: 'ChromiumHeadless', flags: ['--no-sandbox'] }
|
||||
ChromiumHeadlessNoSandbox: {
|
||||
base: 'ChromiumHeadless',
|
||||
flags: ['--no-sandbox']
|
||||
}
|
||||
},
|
||||
browsers: ['ChromiumHeadlessNoSandbox', 'FirefoxHeadless'],
|
||||
concurrency: 1,
|
||||
@@ -47,6 +53,5 @@ module.exports = function (config) {
|
||||
},
|
||||
|
||||
jsonToFileReporter: { outputPath: '.', fileName: 'suite-result.json' }
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import lodash from 'lodash'
|
||||
import Papa from 'papaparse'
|
||||
import useragent from 'ua-parser-js'
|
||||
|
||||
|
||||
describe('SQLite build benchmark', function () {
|
||||
let parsedCsv
|
||||
let sqlModule
|
||||
@@ -50,10 +49,8 @@ describe('SQLite build benchmark', function () {
|
||||
suite.add('select', { initCount: 3, minSamples: 50, fn: benchmarkSelect })
|
||||
await run(suite)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
function importToTable(db, parsedCsv, chunkSize = 1024) {
|
||||
const columnListString = parsedCsv.meta.fields.join(', ')
|
||||
db.exec(`CREATE TABLE csv_import(${columnListString})`)
|
||||
@@ -67,7 +64,6 @@ function importToTable (db, parsedCsv, chunkSize = 1024) {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
class PromiseWrapper {
|
||||
constructor() {
|
||||
this.promise = new Promise((resolve, reject) => {
|
||||
@@ -102,7 +98,6 @@ function chunkArray (arr, size) {
|
||||
}, [])
|
||||
}
|
||||
|
||||
|
||||
function createSuite() {
|
||||
// Combined workaround from:
|
||||
// - https://github.com/bestiejs/benchmark.js/issues/106
|
||||
@@ -124,10 +119,12 @@ function run (suite) {
|
||||
console.info(String(event.target))
|
||||
})
|
||||
.on('complete', function () {
|
||||
console.log(JSON.stringify({
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
browser: useragent(navigator.userAgent).browser,
|
||||
result: this.filter('successful')
|
||||
}))
|
||||
})
|
||||
)
|
||||
suiteResult.resolve()
|
||||
})
|
||||
.on('error', function (event) {
|
||||
|
||||
58
package-lock.json
generated
58
package-lock.json
generated
@@ -42,6 +42,7 @@
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^8.0.1",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
@@ -56,6 +57,7 @@
|
||||
"karma-spec-reporter": "^0.0.36",
|
||||
"karma-vite": "^1.0.5",
|
||||
"mocha": "^5.2.0",
|
||||
"prettier": "3.5.3",
|
||||
"process": "^0.11.10",
|
||||
"url-loader": "^4.1.1",
|
||||
"vite": "^5.4.14",
|
||||
@@ -3421,6 +3423,22 @@
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/component-compiler-utils/node_modules/prettier": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/component-compiler-utils/node_modules/yallist": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
|
||||
@@ -7967,6 +7985,18 @@
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-config-prettier": {
|
||||
"version": "10.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.1.tgz",
|
||||
"integrity": "sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"eslint-config-prettier": "bin/cli.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-import-resolver-custom-alias": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-import-resolver-custom-alias/-/eslint-import-resolver-custom-alias-1.3.2.tgz",
|
||||
@@ -14788,16 +14818,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
|
||||
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
"prettier": "bin/prettier.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
@@ -20029,6 +20058,23 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-cli-plugin-ui-karma/node_modules/prettier": {
|
||||
"version": "2.8.8",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
|
||||
"integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-cli-plugin-ui-karma/node_modules/read-pkg": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
"build": "vite build",
|
||||
"serve": "vite preview",
|
||||
"test": "karma start karma.conf.cjs",
|
||||
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src"
|
||||
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
|
||||
"format": "prettier . --write"
|
||||
},
|
||||
"dependencies": {
|
||||
"buffer": "^6.0.3",
|
||||
@@ -45,6 +46,7 @@
|
||||
"chai": "^4.1.2",
|
||||
"chai-as-promised": "^8.0.1",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^10.1.1",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
@@ -59,6 +61,7 @@
|
||||
"karma-spec-reporter": "^0.0.36",
|
||||
"karma-vite": "^1.0.5",
|
||||
"mocha": "^5.2.0",
|
||||
"prettier": "3.5.3",
|
||||
"process": "^0.11.10",
|
||||
"url-loader": "^4.1.1",
|
||||
"vite": "^5.4.14",
|
||||
|
||||
26
src/App.vue
26
src/App.vue
@@ -32,43 +32,43 @@ export default {
|
||||
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
src: url("@/assets/fonts/OpenSans-Regular.woff2");
|
||||
font-family: 'Open Sans';
|
||||
src: url('@/assets/fonts/OpenSans-Regular.woff2');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
src: url("@/assets/fonts/OpenSans-SemiBold.woff2");
|
||||
font-family: 'Open Sans';
|
||||
src: url('@/assets/fonts/OpenSans-SemiBold.woff2');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
src: url("@/assets/fonts/OpenSans-Bold.woff2");
|
||||
font-family: 'Open Sans';
|
||||
src: url('@/assets/fonts/OpenSans-Bold.woff2');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
src: url("@/assets/fonts/OpenSans-Italic.woff2");
|
||||
font-family: 'Open Sans';
|
||||
src: url('@/assets/fonts/OpenSans-Italic.woff2');
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
src: url("@/assets/fonts/OpenSans-SemiBoldItalic.woff2");
|
||||
font-family: 'Open Sans';
|
||||
src: url('@/assets/fonts/OpenSans-SemiBoldItalic.woff2');
|
||||
font-weight: 600;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
src: url("@/assets/fonts/OpenSans-BoldItalic.woff2");
|
||||
font-family: 'Open Sans';
|
||||
src: url('@/assets/fonts/OpenSans-BoldItalic.woff2');
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
}
|
||||
@@ -80,7 +80,7 @@ label,
|
||||
button,
|
||||
.plotly_editor *,
|
||||
.CodeMirror pre.CodeMirror-line {
|
||||
font-family: "Open Sans", Helvetica, Arial, sans-serif;
|
||||
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
@@ -59,5 +59,3 @@ button.secondary:disabled {
|
||||
text-shadow: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@ table.sqliteviz-table {
|
||||
margin-top: -35px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.sqliteviz-table thead th, .fixed-header {
|
||||
.sqliteviz-table thead th,
|
||||
.fixed-header {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
box-sizing: border-box;
|
||||
@@ -108,8 +109,8 @@ table.sqliteviz-table {
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.sqliteviz-table tbody td[data-isNull="true"],
|
||||
.sqliteviz-table tbody td[data-isBlob="true"] {
|
||||
.sqliteviz-table tbody td[data-isNull='true'],
|
||||
.sqliteviz-table tbody td[data-isBlob='true'] {
|
||||
color: var(--color-text-light-2);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
padding: 0 6px;
|
||||
line-height: 19px;;
|
||||
line-height: 19px;
|
||||
position: fixed;
|
||||
height: 19px;
|
||||
border-radius: var(--border-radius-medium);
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
:root {
|
||||
--color-white: #ffffff;
|
||||
--color-gray-light: #F3F6FA;
|
||||
--color-gray-light-2: #DFE8F3;
|
||||
--color-gray-light-3: #C8D4E3;
|
||||
--color-gray-light-4:#EBF0F8;
|
||||
--color-gray-light: #f3f6fa;
|
||||
--color-gray-light-2: #dfe8f3;
|
||||
--color-gray-light-3: #c8d4e3;
|
||||
--color-gray-light-4: #ebf0f8;
|
||||
--color-gray-light-5: #f8f8f9;
|
||||
--color-gray-medium: #A2B1C6;
|
||||
--color-gray-medium: #a2b1c6;
|
||||
--color-gray-dark: #506784;
|
||||
--color-blue-medium: #119DFF;
|
||||
--color-blue-dark: #0D76BF;
|
||||
--color-blue-dark-2: #2A3F5F;
|
||||
--color-red: #EF553B;
|
||||
--color-red-2: #DE350B;
|
||||
--color-red-light: #FFBDAD;
|
||||
--color-yellow: #FBEFCB;
|
||||
|
||||
|
||||
--color-blue-medium: #119dff;
|
||||
--color-blue-dark: #0d76bf;
|
||||
--color-blue-dark-2: #2a3f5f;
|
||||
--color-red: #ef553b;
|
||||
--color-red-2: #de350b;
|
||||
--color-red-light: #ffbdad;
|
||||
--color-yellow: #fbefcb;
|
||||
|
||||
--color-bg-light: var(--color-gray-light);
|
||||
--color-bg-light-2: var(--color-gray-light-2);
|
||||
@@ -48,6 +46,3 @@
|
||||
.plotly-editor--theme-provider {
|
||||
--sidebar-width: 112px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<template>
|
||||
<div
|
||||
:class="['checkbox-container', { 'checked': checked }, {'disabled': disabled}]"
|
||||
:class="[
|
||||
'checkbox-container',
|
||||
{ checked: checked },
|
||||
{ disabled: disabled }
|
||||
]"
|
||||
@click.stop="onClick"
|
||||
>
|
||||
<div v-show="!checked" class="unchecked" />
|
||||
@@ -31,7 +35,7 @@ export default {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'accent',
|
||||
validator: (value) => {
|
||||
validator: value => {
|
||||
return ['accent', 'light'].includes(value)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :class="{ 'disabled': disabled }">
|
||||
<div :class="{ disabled: disabled }">
|
||||
<div class="text-field-label">Delimiter</div>
|
||||
<div
|
||||
class="delimiter-selector-container"
|
||||
@@ -8,7 +8,7 @@
|
||||
>
|
||||
<div class="value">
|
||||
<input
|
||||
:class="{ 'filled': filled }"
|
||||
:class="{ filled: filled }"
|
||||
ref="delimiterInput"
|
||||
type="text"
|
||||
maxlength="1"
|
||||
@@ -33,7 +33,8 @@
|
||||
@click="chooseOption(option)"
|
||||
class="option"
|
||||
>
|
||||
<pre>{{option}}</pre><div>{{ getSymbolName(option) }}</div>
|
||||
<pre>{{ option }}</pre>
|
||||
<div>{{ getSymbolName(option) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -66,10 +66,7 @@
|
||||
class="preview-table"
|
||||
/>
|
||||
<div v-else class="no-data">No data</div>
|
||||
<logs
|
||||
class="import-errors"
|
||||
:messages="importMessages"
|
||||
/>
|
||||
<logs class="import-errors" :messages="importMessages" />
|
||||
</div>
|
||||
<div class="dialog-buttons-container">
|
||||
<button
|
||||
@@ -175,8 +172,7 @@ export default {
|
||||
if (!this.tableName) {
|
||||
return
|
||||
}
|
||||
this.db.validateTableName(this.tableName)
|
||||
.catch(err => {
|
||||
this.db.validateTableName(this.tableName).catch(err => {
|
||||
this.tableNameError = err.message + '. Try another table name.'
|
||||
})
|
||||
}, 400)
|
||||
@@ -257,10 +253,12 @@ export default {
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
this.importMessages = [{
|
||||
this.importMessages = [
|
||||
{
|
||||
message: err,
|
||||
type: 'error'
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
async getJsonParseResult(file) {
|
||||
@@ -273,7 +271,7 @@ export default {
|
||||
},
|
||||
hasErrors: false,
|
||||
messages: [],
|
||||
rowCount: +(!isEmpty)
|
||||
rowCount: +!isEmpty
|
||||
}
|
||||
},
|
||||
async loadToDb(file) {
|
||||
@@ -297,7 +295,9 @@ export default {
|
||||
})
|
||||
// Get *reactive* link to parsing message for later updates
|
||||
parsingMsg = this.importMessages[this.importMessages.length - 1]
|
||||
const parsingLoadingIndicator = setTimeout(() => { parsingMsg.type = 'loading' }, 1000)
|
||||
const parsingLoadingIndicator = setTimeout(() => {
|
||||
parsingMsg.type = 'loading'
|
||||
}, 1000)
|
||||
|
||||
let importMsg = {}
|
||||
let importLoadingIndicator = null
|
||||
@@ -321,7 +321,9 @@ export default {
|
||||
parsingMsg.type = 'success'
|
||||
|
||||
if (parseResult.messages.length > 0) {
|
||||
this.importMessages = this.importMessages.concat(parseResult.messages)
|
||||
this.importMessages = this.importMessages.concat(
|
||||
parseResult.messages
|
||||
)
|
||||
parsingMsg.message = `${rowCount} rows are parsed in ${period}.`
|
||||
} else {
|
||||
// Inform about parsing success
|
||||
@@ -345,13 +347,18 @@ export default {
|
||||
|
||||
// Add table
|
||||
start = new Date()
|
||||
await this.db.addTableFromCsv(this.tableName, parseResult.data, progressCounterId)
|
||||
await this.db.addTableFromCsv(
|
||||
this.tableName,
|
||||
parseResult.data,
|
||||
progressCounterId
|
||||
)
|
||||
end = new Date()
|
||||
|
||||
this.addedTable = this.tableName
|
||||
// Inform about import success
|
||||
period = time.getPeriod(start, end)
|
||||
importMsg.message = `Importing ${this.typeName} ` +
|
||||
importMsg.message =
|
||||
`Importing ${this.typeName} ` +
|
||||
`into a SQLite database is completed in ${period}.`
|
||||
importMsg.type = 'success'
|
||||
|
||||
@@ -508,5 +515,4 @@ margin-bottom: 24px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
@click="browse"
|
||||
>
|
||||
<div class="text">
|
||||
Drop the database, CSV, JSON or NDJSON file here
|
||||
or click to choose a file from your computer.
|
||||
Drop the database, CSV, JSON or NDJSON file here or click to choose a
|
||||
file from your computer.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -19,16 +19,16 @@
|
||||
<img id="drop-file-top-img" src="~@/assets/images/top.svg" />
|
||||
<img
|
||||
id="left-arm-img"
|
||||
:class="{'swing': state === 'dragover'}"
|
||||
:class="{ swing: state === 'dragover' }"
|
||||
src="~@/assets/images/leftArm.svg"
|
||||
/>
|
||||
<img
|
||||
id="file-img"
|
||||
ref="fileImg"
|
||||
:class="{
|
||||
'swing': state === 'dragover',
|
||||
'fly': state === 'dropping',
|
||||
'hidden': state === 'dropped'
|
||||
swing: state === 'dragover',
|
||||
fly: state === 'dropping',
|
||||
hidden: state === 'dropped'
|
||||
}"
|
||||
src="~@/assets/images/file.png"
|
||||
/>
|
||||
@@ -36,7 +36,7 @@
|
||||
<img id="body-img" src="~@/assets/images/body.svg" />
|
||||
<img
|
||||
id="right-arm-img"
|
||||
:class="{'swing': state === 'dragover'}"
|
||||
:class="{ swing: state === 'dragover' }"
|
||||
src="~@/assets/images/rightArm.svg"
|
||||
/>
|
||||
</div>
|
||||
@@ -68,7 +68,7 @@ export default {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'small',
|
||||
validator: (value) => {
|
||||
validator: value => {
|
||||
return ['illustrated', 'small'].includes(value)
|
||||
}
|
||||
},
|
||||
@@ -93,7 +93,7 @@ export default {
|
||||
},
|
||||
mounted() {
|
||||
if (this.type === 'illustrated') {
|
||||
this.animationPromise = new Promise((resolve) => {
|
||||
this.animationPromise = new Promise(resolve => {
|
||||
this.$refs.fileImg.addEventListener('animationend', event => {
|
||||
if (event.animationName.startsWith('fly')) {
|
||||
this.state = 'dropped'
|
||||
@@ -119,8 +119,9 @@ export default {
|
||||
},
|
||||
|
||||
loadDb(file) {
|
||||
return Promise.all([this.newDb.loadDb(file), this.animationPromise])
|
||||
.then(this.finish)
|
||||
return Promise.all([this.newDb.loadDb(file), this.animationPromise]).then(
|
||||
this.finish
|
||||
)
|
||||
},
|
||||
|
||||
async checkFile(file) {
|
||||
@@ -140,12 +141,15 @@ export default {
|
||||
await this.$nextTick()
|
||||
const csvJsonImportModal = this.$refs.addCsvJson
|
||||
csvJsonImportModal.reset()
|
||||
return Promise.all([csvJsonImportModal.preview(), this.animationPromise])
|
||||
.then(csvJsonImportModal.open)
|
||||
return Promise.all([
|
||||
csvJsonImportModal.preview(),
|
||||
this.animationPromise
|
||||
]).then(csvJsonImportModal.open)
|
||||
}
|
||||
},
|
||||
browse() {
|
||||
fIo.getFileFromUser('.db,.sqlite,.sqlite3,.csv,.json,.ndjson')
|
||||
fIo
|
||||
.getFileFromUser('.db,.sqlite,.sqlite3,.csv,.json,.ndjson')
|
||||
.then(this.checkFile)
|
||||
},
|
||||
|
||||
@@ -246,8 +250,12 @@ export default {
|
||||
transform-origin: -74px 139px;
|
||||
}
|
||||
@keyframes swing {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(-7deg); }
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(-7deg);
|
||||
}
|
||||
}
|
||||
|
||||
#file-img.fly {
|
||||
|
||||
@@ -10,7 +10,12 @@
|
||||
<div v-show="loading" class="icon-in-progress">
|
||||
<loading-indicator />
|
||||
</div>
|
||||
<span v-if="tooltip" class="icon-tooltip" :style="tooltipStyle" ref="tooltip">
|
||||
<span
|
||||
v-if="tooltip"
|
||||
class="icon-tooltip"
|
||||
:style="tooltipStyle"
|
||||
ref="tooltip"
|
||||
>
|
||||
{{ tooltip }}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
@@ -16,7 +16,10 @@
|
||||
{{ loadingMsg }}
|
||||
</div>
|
||||
<div v-else class="loading-dialog-body">
|
||||
<img src="~@/assets/images/success.svg" class="success-icon state-icon" />
|
||||
<img
|
||||
src="~@/assets/images/success.svg"
|
||||
class="success-icon state-icon"
|
||||
/>
|
||||
{{ successMsg }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<svg :class="animationClass" :height="size" :width="size" :viewBox="`0 0 ${size} ${size}`">
|
||||
<svg
|
||||
:class="animationClass"
|
||||
:height="size"
|
||||
:width="size"
|
||||
:viewBox="`0 0 ${size} ${size}`"
|
||||
>
|
||||
<circle
|
||||
class="loader-svg bg"
|
||||
:style="{ strokeWidth }"
|
||||
@@ -9,7 +14,11 @@
|
||||
/>
|
||||
<circle
|
||||
class="loader-svg front"
|
||||
:style="{ strokeDasharray: circleProgress, strokeDashoffset: offset, strokeWidth }"
|
||||
:style="{
|
||||
strokeDasharray: circleProgress,
|
||||
strokeDashoffset: offset,
|
||||
strokeWidth
|
||||
}"
|
||||
:cx="size / 2"
|
||||
:cy="size / 2"
|
||||
:r="radius"
|
||||
@@ -35,7 +44,9 @@ export default {
|
||||
computed: {
|
||||
circleProgress() {
|
||||
const circle = this.radius * 3.14 * 2
|
||||
const dash = this.progress ? (circle * this.progress) / 100 : circle * 1 / 3
|
||||
const dash = this.progress
|
||||
? (circle * this.progress) / 100
|
||||
: (circle * 1) / 3
|
||||
const space = circle - dash
|
||||
return `${dash}px, ${space}px`
|
||||
},
|
||||
@@ -46,7 +57,7 @@ export default {
|
||||
return this.size / 2 - this.strokeWidth
|
||||
},
|
||||
offset() {
|
||||
return this.radius * 3.14 / 2
|
||||
return (this.radius * 3.14) / 2
|
||||
},
|
||||
strokeWidth() {
|
||||
return this.size / 10
|
||||
@@ -58,7 +69,10 @@ export default {
|
||||
<style scoped>
|
||||
.loader-svg {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke: var(--color-accent);
|
||||
@@ -112,5 +126,4 @@ export default {
|
||||
r: 8;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
<template>
|
||||
<div class="logs-container" ref="logsContainer">
|
||||
<div v-for="(msg, index) in messages" :key="index" class="msg">
|
||||
<img v-if="msg.type === 'error'" src="~@/assets/images/error.svg">
|
||||
<img v-if="msg.type === 'info'" src="~@/assets/images/info.svg" width="20px">
|
||||
<img v-if="msg.type === 'success'" src="~@/assets/images/success.svg">
|
||||
<loading-indicator v-if="msg.type === 'loading'" :progress="msg.progress" />
|
||||
<img v-if="msg.type === 'error'" src="~@/assets/images/error.svg" />
|
||||
<img
|
||||
v-if="msg.type === 'info'"
|
||||
src="~@/assets/images/info.svg"
|
||||
width="20px"
|
||||
/>
|
||||
<img v-if="msg.type === 'success'" src="~@/assets/images/success.svg" />
|
||||
<loading-indicator
|
||||
v-if="msg.type === 'loading'"
|
||||
:progress="msg.progress"
|
||||
/>
|
||||
<span class="msg-text">{{ serializeMessage(msg) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,7 +51,7 @@ export default {
|
||||
}
|
||||
|
||||
result += msg.message
|
||||
if (!(/(\.|!|\?)$/.test(result))) {
|
||||
if (!/(\.|!|\?)$/.test(result)) {
|
||||
result += '.'
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
{ 'splitpanes-dragging': dragging }
|
||||
]"
|
||||
>
|
||||
<div class="movable-splitter" ref="movableSplitter" :style="movableSplitterStyle" />
|
||||
<div
|
||||
class="movable-splitter"
|
||||
ref="movableSplitter"
|
||||
:style="movableSplitterStyle"
|
||||
/>
|
||||
<div
|
||||
class="splitpanes-pane"
|
||||
ref="left"
|
||||
@@ -27,8 +31,11 @@
|
||||
:class="[
|
||||
'toggle-btns',
|
||||
{
|
||||
'both': after.max === 100 && before.max === 100 &&
|
||||
paneAfter.size > 0 && paneBefore.size > 0
|
||||
both:
|
||||
after.max === 100 &&
|
||||
before.max === 100 &&
|
||||
paneAfter.size > 0 &&
|
||||
paneBefore.size > 0
|
||||
}
|
||||
]"
|
||||
>
|
||||
@@ -41,7 +48,7 @@
|
||||
class="direction-icon"
|
||||
src="~@/assets/images/chevron.svg"
|
||||
:style="directionBeforeIconStyle"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="before.max === 100 && paneBefore.size > 0"
|
||||
@@ -52,16 +59,12 @@
|
||||
class="direction-icon"
|
||||
src="~@/assets/images/chevron.svg"
|
||||
:style="directionAfterIconStyle"
|
||||
>
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- splitter end -->
|
||||
<div
|
||||
class="splitpanes-pane"
|
||||
ref="right"
|
||||
:style="styles.after"
|
||||
>
|
||||
<div class="splitpanes-pane" ref="right" :style="styles.after">
|
||||
<slot name="right-pane" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -92,7 +95,8 @@ export default {
|
||||
container: null,
|
||||
paneBefore: this.before,
|
||||
paneAfter: this.after,
|
||||
beforeMinimising: !this.after.size || !this.before.size
|
||||
beforeMinimising:
|
||||
!this.after.size || !this.before.size
|
||||
? this.default
|
||||
: {
|
||||
before: this.before.size,
|
||||
@@ -109,8 +113,12 @@ export default {
|
||||
computed: {
|
||||
styles() {
|
||||
return {
|
||||
before: { [this.horizontal ? 'height' : 'width']: `${this.paneBefore.size}%` },
|
||||
after: { [this.horizontal ? 'height' : 'width']: `${this.paneAfter.size}%` }
|
||||
before: {
|
||||
[this.horizontal ? 'height' : 'width']: `${this.paneBefore.size}%`
|
||||
},
|
||||
after: {
|
||||
[this.horizontal ? 'height' : 'width']: `${this.paneAfter.size}%`
|
||||
}
|
||||
}
|
||||
},
|
||||
movableSplitterStyle() {
|
||||
@@ -154,21 +162,29 @@ export default {
|
||||
methods: {
|
||||
bindEvents() {
|
||||
// Passive: false to prevent scrolling while touch dragging.
|
||||
document.addEventListener('mousemove', this.onMouseMove, { passive: false })
|
||||
document.addEventListener('mousemove', this.onMouseMove, {
|
||||
passive: false
|
||||
})
|
||||
document.addEventListener('mouseup', this.onMouseUp)
|
||||
|
||||
if ('ontouchstart' in window) {
|
||||
document.addEventListener('touchmove', this.onMouseMove, { passive: false })
|
||||
document.addEventListener('touchmove', this.onMouseMove, {
|
||||
passive: false
|
||||
})
|
||||
document.addEventListener('touchend', this.onMouseUp)
|
||||
}
|
||||
},
|
||||
|
||||
unbindEvents() {
|
||||
document.removeEventListener('mousemove', this.onMouseMove, { passive: false })
|
||||
document.removeEventListener('mousemove', this.onMouseMove, {
|
||||
passive: false
|
||||
})
|
||||
document.removeEventListener('mouseup', this.onMouseUp)
|
||||
|
||||
if ('ontouchstart' in window) {
|
||||
document.removeEventListener('touchmove', this.onMouseMove, { passive: false })
|
||||
document.removeEventListener('touchmove', this.onMouseMove, {
|
||||
passive: false
|
||||
})
|
||||
document.removeEventListener('touchend', this.onMouseUp)
|
||||
}
|
||||
},
|
||||
@@ -218,7 +234,8 @@ export default {
|
||||
this.beforeMinimising.before = this.paneBefore.size
|
||||
this.beforeMinimising.after = this.paneAfter.size
|
||||
pane.size = 0
|
||||
const otherPane = pane === this.paneBefore ? this.paneAfter : this.paneBefore
|
||||
const otherPane =
|
||||
pane === this.paneBefore ? this.paneAfter : this.paneBefore
|
||||
otherPane.size = 100 - pane.size
|
||||
} else {
|
||||
this.paneBefore.size = this.beforeMinimising.before
|
||||
@@ -239,9 +256,15 @@ export default {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.splitpanes-vertical {flex-direction: row;}
|
||||
.splitpanes-horizontal {flex-direction: column;}
|
||||
.splitpanes-dragging * {user-select: none;}
|
||||
.splitpanes-vertical {
|
||||
flex-direction: row;
|
||||
}
|
||||
.splitpanes-horizontal {
|
||||
flex-direction: column;
|
||||
}
|
||||
.splitpanes-dragging * {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.splitpanes-pane {
|
||||
width: 100%;
|
||||
@@ -288,7 +311,7 @@ export default {
|
||||
.splitpanes-vertical > .movable-splitter {
|
||||
width: 8px;
|
||||
z-index: 5;
|
||||
height: 100%
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.splitpanes-horizontal > .splitpanes-splitter,
|
||||
@@ -339,20 +362,32 @@ export default {
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
.splitpanes-horizontal > .splitpanes-splitter .toggle-btns.both .toggle-btn:first-child {
|
||||
.splitpanes-horizontal
|
||||
> .splitpanes-splitter
|
||||
.toggle-btns.both
|
||||
.toggle-btn:first-child {
|
||||
border-radius: var(--border-radius-small) 0 0 var(--border-radius-small);
|
||||
}
|
||||
|
||||
.splitpanes-horizontal > .splitpanes-splitter .toggle-btns.both .toggle-btn:last-child {
|
||||
.splitpanes-horizontal
|
||||
> .splitpanes-splitter
|
||||
.toggle-btns.both
|
||||
.toggle-btn:last-child {
|
||||
border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0;
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.splitpanes-vertical > .splitpanes-splitter .toggle-btns.both .toggle-btn:first-child {
|
||||
.splitpanes-vertical
|
||||
> .splitpanes-splitter
|
||||
.toggle-btns.both
|
||||
.toggle-btn:first-child {
|
||||
border-radius: var(--border-radius-small) var(--border-radius-small) 0 0;
|
||||
}
|
||||
|
||||
.splitpanes-vertical > .splitpanes-splitter .toggle-btns.both .toggle-btn:last-child {
|
||||
.splitpanes-vertical
|
||||
> .splitpanes-splitter
|
||||
.toggle-btns.both
|
||||
.toggle-btn:last-child {
|
||||
border-radius: 0 0 var(--border-radius-small) var(--border-radius-small);
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,8 @@ export default {
|
||||
// Get the cursor position relative to the splitpane container.
|
||||
getCurrentMouseDrag(event, container) {
|
||||
const rect = container.getBoundingClientRect()
|
||||
const { clientX, clientY } = ('ontouchstart' in window && event.touches)
|
||||
? event.touches[0]
|
||||
: event
|
||||
const { clientX, clientY } =
|
||||
'ontouchstart' in window && event.touches ? event.touches[0] : event
|
||||
return {
|
||||
x: clientX - rect.left,
|
||||
y: clientY - rect.top
|
||||
@@ -15,20 +14,32 @@ export default {
|
||||
getCurrentDragPercentage(event, container, isHorisontal) {
|
||||
let drag = this.getCurrentMouseDrag(event, container)
|
||||
drag = drag[isHorisontal ? 'y' : 'x']
|
||||
const containerSize = container[isHorisontal ? 'clientHeight' : 'clientWidth']
|
||||
return drag * 100 / containerSize
|
||||
const containerSize =
|
||||
container[isHorisontal ? 'clientHeight' : 'clientWidth']
|
||||
return (drag * 100) / containerSize
|
||||
},
|
||||
|
||||
// Returns the new position in percents.
|
||||
calculateOffset (event, { container, isHorisontal, paneBeforeMax, paneAfterMax }) {
|
||||
const dragPercentage = this.getCurrentDragPercentage(event, container, isHorisontal)
|
||||
calculateOffset(
|
||||
event,
|
||||
{ container, isHorisontal, paneBeforeMax, paneAfterMax }
|
||||
) {
|
||||
const dragPercentage = this.getCurrentDragPercentage(
|
||||
event,
|
||||
container,
|
||||
isHorisontal
|
||||
)
|
||||
|
||||
const paneBeforeMaxReached = paneBeforeMax < 100 && (dragPercentage >= paneBeforeMax)
|
||||
const paneAfterMaxReached = paneAfterMax < 100 && (dragPercentage <= 100 - paneAfterMax)
|
||||
const paneBeforeMaxReached =
|
||||
paneBeforeMax < 100 && dragPercentage >= paneBeforeMax
|
||||
const paneAfterMaxReached =
|
||||
paneAfterMax < 100 && dragPercentage <= 100 - paneAfterMax
|
||||
|
||||
// Prevent dragging beyond pane max.
|
||||
if (paneBeforeMaxReached || paneAfterMaxReached) {
|
||||
return paneBeforeMaxReached ? paneBeforeMax : Math.max(100 - paneAfterMax, 0)
|
||||
return paneBeforeMaxReached
|
||||
? paneBeforeMax
|
||||
: Math.max(100 - paneAfterMax, 0)
|
||||
} else {
|
||||
return Math.min(Math.max(dragPercentage, 0), paneBeforeMax)
|
||||
}
|
||||
|
||||
@@ -131,8 +131,9 @@ export default {
|
||||
|
||||
if (this.selectedCellCoordinates) {
|
||||
const { row, col } = this.selectedCellCoordinates
|
||||
const cell = this.$refs.table
|
||||
.querySelector(`td[data-col="${col}"][data-row="${row}"]`)
|
||||
const cell = this.$refs.table.querySelector(
|
||||
`td[data-col="${col}"][data-row="${row}"]`
|
||||
)
|
||||
if (cell) {
|
||||
this.selectCell(cell)
|
||||
}
|
||||
@@ -167,7 +168,8 @@ export default {
|
||||
})
|
||||
},
|
||||
onScrollTable() {
|
||||
this.$refs['header-container'].scrollLeft = this.$refs['table-container'].scrollLeft
|
||||
this.$refs['header-container'].scrollLeft =
|
||||
this.$refs['table-container'].scrollLeft
|
||||
},
|
||||
onTableKeydown(e) {
|
||||
const keyCodeMap = {
|
||||
@@ -242,8 +244,9 @@ export default {
|
||||
newColIndex = currentColIndex
|
||||
}
|
||||
|
||||
const newCell = this.$refs.table
|
||||
.querySelector(`td[data-col="${newColIndex}"][data-row="${newRowIndex}"]`)
|
||||
const newCell = this.$refs.table.querySelector(
|
||||
`td[data-col="${newColIndex}"][data-row="${newRowIndex}"]`
|
||||
)
|
||||
if (newCell) {
|
||||
this.selectCell(newCell)
|
||||
}
|
||||
@@ -271,7 +274,7 @@ table.sqliteviz-table:focus {
|
||||
.sqliteviz-table tbody td:hover {
|
||||
background-color: var(--color-bg-light-3);
|
||||
}
|
||||
.sqliteviz-table tbody td[aria-selected="true"] {
|
||||
.sqliteviz-table tbody td[aria-selected='true'] {
|
||||
box-shadow: inset 0 0 0 1px var(--color-accent);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="label" :class="['text-field-label', { error: errorMsg }, {'disabled': disabled}]">
|
||||
<div
|
||||
v-if="label"
|
||||
:class="['text-field-label', { error: errorMsg }, { disabled: disabled }]"
|
||||
>
|
||||
{{ label }}
|
||||
<hint-icon class="hint" v-if="hint" :hint="hint" :max-width="maxHintWidth || '149px'"/>
|
||||
<hint-icon
|
||||
class="hint"
|
||||
v-if="hint"
|
||||
:hint="hint"
|
||||
:max-width="maxHintWidth || '149px'"
|
||||
/>
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
|
||||
@@ -15,6 +15,5 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
}
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
@@ -46,7 +41,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ChartIcon'
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<svg
|
||||
:class="['clear-icon', {'disabled': disabled}]"
|
||||
:class="['clear-icon', { disabled: disabled }]"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -21,7 +21,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ClearIcon',
|
||||
props: ['disabled']
|
||||
@@ -42,6 +41,6 @@ export default {
|
||||
}
|
||||
|
||||
.disabled.clear-icon:hover path {
|
||||
fill: #C8D4E3;
|
||||
fill: #c8d4e3;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||
<path
|
||||
d="M14.1917 1.3851H12.4806V0.703125C12.4806 0.314758 12.1658 0 11.7775 0H6.246C5.85764 0
|
||||
5.54288 0.314758 5.54288 0.703125V1.3851H3.83203C2.86276 1.3851 2.07422 2.17365 2.07422
|
||||
@@ -26,7 +21,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ClipboardIcon'
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<svg
|
||||
@click.stop="$emit('click')"
|
||||
:class="['icon', {'disabled': disabled }]"
|
||||
:class="['icon', { disabled: disabled }]"
|
||||
:width="size"
|
||||
:height="size"
|
||||
viewBox="0 0 14 14"
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
@@ -31,7 +26,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'DataViewIcon'
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<svg
|
||||
:class="['chevron-icon', {'disabled': disabled}]"
|
||||
:class="['chevron-icon', { disabled: disabled }]"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
@@ -15,7 +15,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'DropDownChevron',
|
||||
props: ['disabled']
|
||||
@@ -36,6 +35,6 @@ export default {
|
||||
}
|
||||
|
||||
.disabled.chevron-icon:hover path {
|
||||
fill: #C8D4E3;
|
||||
fill: #c8d4e3;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -21,6 +21,5 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
}
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<svg
|
||||
width="19"
|
||||
height="18"
|
||||
viewBox="0 0 19 18"
|
||||
fill="none"
|
||||
>
|
||||
<svg width="19" height="18" viewBox="0 0 19 18" fill="none">
|
||||
<path
|
||||
d="M6.07959 13.5756C6.05908 14.0209 5.93896 14.415 5.71924 14.7578C5.49951 15.0976 5.19043
|
||||
15.3613 4.79199 15.5488C4.39648 15.7363 3.94385 15.83 3.43408 15.83C2.59326 15.83
|
||||
@@ -55,7 +50,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ExportToCsvIcon'
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<svg
|
||||
width="19"
|
||||
height="18"
|
||||
viewBox="0 0 19 18"
|
||||
fill="none"
|
||||
>
|
||||
<svg width="19" height="18" viewBox="0 0 19 18" fill="none">
|
||||
<path
|
||||
d="M4.28369 13.9966C4.28369 13.7711 4.20312 13.5953 4.04199 13.4693C3.88379 13.3433 3.604
|
||||
13.213 3.20264 13.0782C2.80127 12.9434 2.47314 12.813 2.21826 12.6871C1.38916 12.2798
|
||||
@@ -54,7 +49,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ExportToSvgIcon'
|
||||
}
|
||||
|
||||
@@ -33,7 +33,11 @@
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
<span class="icon-tooltip" :style="{...tooltipStyle, maxWidth: maxWidth }" ref="tooltip">
|
||||
<span
|
||||
class="icon-tooltip"
|
||||
:style="{ ...tooltipStyle, maxWidth: maxWidth }"
|
||||
ref="tooltip"
|
||||
>
|
||||
{{ hint }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
@@ -21,7 +16,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'PivotIcon'
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||
<path
|
||||
d="M9 5.51953C6.57686 5.51953 4.60547 7.49092 4.60547 9.91406C4.60547 12.3372 6.57686
|
||||
14.3086 9 14.3086C11.4231 14.3086 13.3945 12.3372 13.3945 9.91406C13.3945 7.49092 11.4231
|
||||
@@ -30,7 +25,10 @@
|
||||
5.5195V15.0117Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path d="M15.1875 6.22266H13.7812V7.62891H15.1875V6.22266Z" fill="#A2B1C6"/>
|
||||
<path
|
||||
d="M15.1875 6.22266H13.7812V7.62891H15.1875V6.22266Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<svg
|
||||
width="19"
|
||||
height="19"
|
||||
viewBox="0 0 19 19"
|
||||
fill="none"
|
||||
>
|
||||
<svg width="19" height="19" viewBox="0 0 19 19" fill="none">
|
||||
<g clip-path="url(#clip0_2130_5292)">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
@@ -46,7 +41,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'RowIcon'
|
||||
}
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
<template>
|
||||
<svg
|
||||
width="12"
|
||||
height="13"
|
||||
viewBox="0 0 12 13"
|
||||
fill="none"
|
||||
>
|
||||
<path d="M11.1624 6.94358L0.770043 12.9436L0.770043 0.943573L11.1624 6.94358Z" fill="#A2B1C6"/>
|
||||
<svg width="12" height="13" viewBox="0 0 12 13" fill="none">
|
||||
<path
|
||||
d="M11.1624 6.94358L0.770043 12.9436L0.770043 0.943573L11.1624 6.94358Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'RunIcon'
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'SortIcon',
|
||||
props: {
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<svg
|
||||
width="18"
|
||||
height="19"
|
||||
viewBox="0 0 18 19"
|
||||
fill="none"
|
||||
>
|
||||
<svg width="18" height="19" viewBox="0 0 18 19" fill="none">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path
|
||||
d="M4.5 1.51343H10.5L15 6.01343V8.45284H13.5V6.76343H9.75V3.01343H4.5V8.45284H3V3.01343C3
|
||||
@@ -47,14 +42,18 @@
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="18" height="18" fill="white" transform="translate(0 0.0134277)"/>
|
||||
<rect
|
||||
width="18"
|
||||
height="18"
|
||||
fill="white"
|
||||
transform="translate(0 0.0134277)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'SqlEditorIcon'
|
||||
}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
>
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
@@ -41,7 +36,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'TableIcon'
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'treeChevron',
|
||||
props: {
|
||||
@@ -31,7 +30,7 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.chevron-icon {
|
||||
-webkit-transition: transform .15s ease-in-out;
|
||||
transition: transform .15s ease-in-out;
|
||||
-webkit-transition: transform 0.15s ease-in-out;
|
||||
transition: transform 0.15s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<svg
|
||||
width="19"
|
||||
height="19"
|
||||
viewBox="0 0 19 19"
|
||||
fill="none"
|
||||
>
|
||||
<svg width="19" height="19" viewBox="0 0 19 19" fill="none">
|
||||
<g clip-path="url(#clip0_2131_6054)">
|
||||
<path
|
||||
d="M3.53784 11.5846L3.53784 3.14734L11.9751 3.14734V7.676C12.4655 7.51991
|
||||
@@ -43,7 +38,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ViewCellValueIcon'
|
||||
}
|
||||
|
||||
@@ -61,7 +61,9 @@ export default {
|
||||
}
|
||||
|
||||
for (let rowNumber = 0; rowNumber < rowCount; rowNumber++) {
|
||||
result.data.push(columns.map(column => resultSet.values[column][rowNumber]))
|
||||
result.data.push(
|
||||
columns.map(column => resultSet.values[column][rowNumber])
|
||||
)
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
@@ -5,7 +5,9 @@ import wasmUrl from 'sql.js/dist/sql-wasm.wasm?url'
|
||||
let SQL = null
|
||||
const sqlModuleReady = initSqlJs({
|
||||
locateFile: () => wasmUrl
|
||||
}).then(sqlModule => { SQL = sqlModule })
|
||||
}).then(sqlModule => {
|
||||
SQL = sqlModule
|
||||
})
|
||||
|
||||
function _getDataSourcesFromSqlResult(sqlResult) {
|
||||
if (!sqlResult) {
|
||||
@@ -24,8 +26,7 @@ export default class Sql {
|
||||
}
|
||||
|
||||
static build() {
|
||||
return sqlModuleReady
|
||||
.then(() => {
|
||||
return sqlModuleReady.then(() => {
|
||||
return new Sql()
|
||||
})
|
||||
}
|
||||
@@ -80,7 +81,10 @@ export default class Sql {
|
||||
}
|
||||
this.db.exec('COMMIT')
|
||||
count++
|
||||
progressCallback({ progress: 100 * (count / chunksAmount), id: progressCounterId })
|
||||
progressCallback({
|
||||
progress: 100 * (count / chunksAmount),
|
||||
id: progressCounterId
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -2,7 +2,9 @@ export default {
|
||||
*generateChunks(data, size) {
|
||||
const matrix = Object.keys(data).map(col => data[col])
|
||||
const [row] = matrix
|
||||
const transposedMatrix = row.map((value, column) => matrix.map(row => row[column]))
|
||||
const transposedMatrix = row.map((value, column) =>
|
||||
matrix.map(row => row[column])
|
||||
)
|
||||
|
||||
const count = Math.ceil(transposedMatrix.length / size)
|
||||
|
||||
@@ -38,7 +40,8 @@ export default {
|
||||
type = 'TEXT'
|
||||
break
|
||||
}
|
||||
default: type = 'TEXT'
|
||||
default:
|
||||
type = 'TEXT'
|
||||
}
|
||||
result += `"${col}" ${type}, `
|
||||
}
|
||||
|
||||
@@ -35,7 +35,5 @@ function onError (error) {
|
||||
}
|
||||
|
||||
registerPromiseWorker(data => {
|
||||
return sqlReady
|
||||
.then(processMsg.bind(data))
|
||||
.catch(onError)
|
||||
return sqlReady.then(processMsg.bind(data)).catch(onError)
|
||||
})
|
||||
|
||||
@@ -7,10 +7,9 @@ import PromiseWorker from 'promise-worker'
|
||||
import events from '@/lib/utils/events'
|
||||
|
||||
function getNewDatabase() {
|
||||
const worker = new Worker(
|
||||
new URL('./_worker.js', import.meta.url),
|
||||
{ type: 'module' }
|
||||
)
|
||||
const worker = new Worker(new URL('./_worker.js', import.meta.url), {
|
||||
type: 'module'
|
||||
})
|
||||
return new Database(worker)
|
||||
}
|
||||
|
||||
@@ -31,9 +30,11 @@ class Database {
|
||||
const progress = e.data.progress
|
||||
if (progress !== undefined) {
|
||||
const id = e.data.id
|
||||
this.importProgresses[id].dispatchEvent(new CustomEvent('progress', {
|
||||
this.importProgresses[id].dispatchEvent(
|
||||
new CustomEvent('progress', {
|
||||
detail: progress
|
||||
}))
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -45,7 +46,9 @@ class Database {
|
||||
createProgressCounter(callback) {
|
||||
const id = progressCounterIds++
|
||||
this.importProgresses[id] = new EventTarget()
|
||||
this.importProgresses[id].addEventListener('progress', e => { callback(e.detail) })
|
||||
this.importProgresses[id].addEventListener('progress', e => {
|
||||
callback(e.detail)
|
||||
})
|
||||
return id
|
||||
}
|
||||
|
||||
@@ -70,7 +73,10 @@ class Database {
|
||||
|
||||
async loadDb(file) {
|
||||
const fileContent = file ? await fu.readAsArrayBuffer(file) : null
|
||||
const res = await this.pw.postMessage({ action: 'open', buffer: fileContent })
|
||||
const res = await this.pw.postMessage({
|
||||
action: 'open',
|
||||
buffer: fileContent
|
||||
})
|
||||
|
||||
if (res.error) {
|
||||
throw new Error(res.error)
|
||||
@@ -130,7 +136,9 @@ class Database {
|
||||
}
|
||||
|
||||
if (/[^\w]/.test(name)) {
|
||||
throw new Error('Table name can contain only letters, digits and underscores')
|
||||
throw new Error(
|
||||
'Table name can contain only letters, digits and underscores'
|
||||
)
|
||||
}
|
||||
|
||||
if (/^(\d)/.test(name)) {
|
||||
|
||||
@@ -37,13 +37,20 @@ export default {
|
||||
},
|
||||
|
||||
updateStorage(inquiries) {
|
||||
localStorage.setItem('myInquiries', JSON.stringify({ version: this.version, inquiries }))
|
||||
localStorage.setItem(
|
||||
'myInquiries',
|
||||
JSON.stringify({ version: this.version, inquiries })
|
||||
)
|
||||
},
|
||||
|
||||
serialiseInquiries(inquiryList) {
|
||||
const preparedData = JSON.parse(JSON.stringify(inquiryList))
|
||||
preparedData.forEach(inquiry => delete inquiry.isPredefined)
|
||||
return JSON.stringify({ version: this.version, inquiries: preparedData }, null, 4)
|
||||
return JSON.stringify(
|
||||
{ version: this.version, inquiries: preparedData },
|
||||
null,
|
||||
4
|
||||
)
|
||||
},
|
||||
|
||||
deserialiseInquiries(str) {
|
||||
@@ -59,7 +66,9 @@ export default {
|
||||
|
||||
// Generate new ids if they are the same as existing inquiries
|
||||
inquiryList.forEach(inquiry => {
|
||||
const allInquiriesIds = this.getStoredInquiries().map(inquiry => inquiry.id)
|
||||
const allInquiriesIds = this.getStoredInquiries().map(
|
||||
inquiry => inquiry.id
|
||||
)
|
||||
if (allInquiriesIds.includes(inquiry.id)) {
|
||||
inquiry.id = nanoid()
|
||||
}
|
||||
@@ -69,8 +78,7 @@ export default {
|
||||
},
|
||||
|
||||
importInquiries() {
|
||||
return fu.importFile()
|
||||
.then(str => {
|
||||
return fu.importFile().then(str => {
|
||||
const inquires = this.deserialiseInquiries(str)
|
||||
|
||||
events.send('inquiry.import', inquires.length)
|
||||
|
||||
@@ -6,7 +6,9 @@ export default class Tab {
|
||||
constructor(state, inquiry = {}) {
|
||||
this.id = inquiry.id || nanoid()
|
||||
this.name = inquiry.id ? inquiry.name : null
|
||||
this.tempName = inquiry.name || (state.untitledLastIndex
|
||||
this.tempName =
|
||||
inquiry.name ||
|
||||
(state.untitledLastIndex
|
||||
? `Untitled ${state.untitledLastIndex}`
|
||||
: 'Untitled')
|
||||
this.query = inquiry.query
|
||||
@@ -39,7 +41,8 @@ export default class Tab {
|
||||
this.time = time.getPeriod(start, new Date())
|
||||
|
||||
if (this.result && this.result.values) {
|
||||
events.send('resultset.create',
|
||||
events.send(
|
||||
'resultset.create',
|
||||
this.result.values[this.result.columns[0]].length
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ export default {
|
||||
|
||||
async _copyBlob(blob) {
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({ // eslint-disable-line no-undef
|
||||
new ClipboardItem({
|
||||
// eslint-disable-line no-undef
|
||||
[blob.type]: blob
|
||||
})
|
||||
])
|
||||
@@ -32,9 +33,13 @@ export default {
|
||||
},
|
||||
|
||||
async _copyCanvas(canvas) {
|
||||
canvas.toBlob(async (blob) => {
|
||||
canvas.toBlob(
|
||||
async blob => {
|
||||
await this._copyBlob(blob)
|
||||
Lib.notifier('Image copied to clipboard successfully', 'long')
|
||||
}, 'image/png', 1)
|
||||
},
|
||||
'image/png',
|
||||
1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,7 @@ export default {
|
||||
},
|
||||
|
||||
importFile() {
|
||||
return this.getFileFromUser('.json')
|
||||
.then(file => {
|
||||
return this.getFileFromUser('.json').then(file => {
|
||||
return this.getFileContent(file)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -15,7 +15,9 @@ export default {
|
||||
|
||||
sleep(ms) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => { resolve() }, ms)
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, ms)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ 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) {
|
||||
|
||||
@@ -31,7 +31,9 @@ export default {
|
||||
if (newName) {
|
||||
value.createdAt = new Date()
|
||||
} else {
|
||||
var inquiryIndex = myInquiries.findIndex(oldInquiry => oldInquiry.id === inquiryTab.id)
|
||||
var inquiryIndex = myInquiries.findIndex(
|
||||
oldInquiry => oldInquiry.id === inquiryTab.id
|
||||
)
|
||||
value.createdAt = myInquiries[inquiryIndex].createdAt
|
||||
}
|
||||
|
||||
@@ -63,8 +65,9 @@ export default {
|
||||
}
|
||||
},
|
||||
renameInquiry({ state, commit }, { inquiryId, newName }) {
|
||||
const renamingInquiry = state.inquiries
|
||||
.find(inquiry => inquiry.id === inquiryId)
|
||||
const renamingInquiry = state.inquiries.find(
|
||||
inquiry => inquiry.id === inquiryId
|
||||
)
|
||||
|
||||
renamingInquiry.name = newName
|
||||
|
||||
|
||||
@@ -14,12 +14,24 @@ export default {
|
||||
state.currentTabId = id
|
||||
}
|
||||
|
||||
if (id) { tab.id = id }
|
||||
if (name) { tab.name = name }
|
||||
if (query) { tab.query = query }
|
||||
if (viewType) { tab.viewType = viewType }
|
||||
if (viewOptions) { tab.viewOptions = viewOptions }
|
||||
if (isSaved !== undefined) { tab.isSaved = isSaved }
|
||||
if (id) {
|
||||
tab.id = id
|
||||
}
|
||||
if (name) {
|
||||
tab.name = name
|
||||
}
|
||||
if (query) {
|
||||
tab.query = query
|
||||
}
|
||||
if (viewType) {
|
||||
tab.viewType = viewType
|
||||
}
|
||||
if (viewOptions) {
|
||||
tab.viewOptions = viewOptions
|
||||
}
|
||||
if (isSaved !== undefined) {
|
||||
tab.isSaved = isSaved
|
||||
}
|
||||
if (isSaved) {
|
||||
// Saved inquiry is not predefined
|
||||
delete tab.isPredefined
|
||||
@@ -49,11 +61,13 @@ export default {
|
||||
state.currentTabId = id
|
||||
state.currentTab = state.tabs.find(tab => tab.id === id)
|
||||
} catch (e) {
|
||||
console.error('Can\'t open a tab id:' + id)
|
||||
console.error("Can't open a tab id:" + id)
|
||||
}
|
||||
},
|
||||
updatePredefinedInquiries(state, inquiries) {
|
||||
state.predefinedInquiries = Array.isArray(inquiries) ? inquiries : [inquiries]
|
||||
state.predefinedInquiries = Array.isArray(inquiries)
|
||||
? inquiries
|
||||
: [inquiries]
|
||||
},
|
||||
setLoadingPredefinedInquiries(state, value) {
|
||||
state.loadingPredefinedInquiries = value
|
||||
|
||||
@@ -13,7 +13,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
showTooltip(e, tooltipPosition) {
|
||||
const position = tooltipPosition ? tooltipPosition.split('-') : ['top', 'right']
|
||||
const position = tooltipPosition
|
||||
? tooltipPosition.split('-')
|
||||
: ['top', 'right']
|
||||
const offset = 12
|
||||
|
||||
if (position[0] === 'top') {
|
||||
@@ -25,7 +27,8 @@ export default {
|
||||
if (position[1] === 'right') {
|
||||
this.tooltipStyle.left = e.clientX + offset + 'px'
|
||||
} else {
|
||||
this.tooltipStyle.left = e.clientX - offset - this.tooltipElement.offsetWidth + 'px'
|
||||
this.tooltipStyle.left =
|
||||
e.clientX - offset - this.tooltipElement.offsetWidth + 'px'
|
||||
}
|
||||
|
||||
this.tooltipStyle.visibility = 'visible'
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<logs
|
||||
id="logs"
|
||||
:messages="messages"
|
||||
/>
|
||||
<logs id="logs" :messages="messages" />
|
||||
<button
|
||||
v-if="hasErrors"
|
||||
id="open-workspace-btn"
|
||||
class="secondary"
|
||||
@click="$router.push('/workspace?hide_schema=1')">
|
||||
@click="$router.push('/workspace?hide_schema=1')"
|
||||
>
|
||||
Open workspace
|
||||
</button>
|
||||
</div>
|
||||
@@ -190,7 +188,6 @@ export default {
|
||||
#logs {
|
||||
margin: 8px auto;
|
||||
max-width: 800px;
|
||||
|
||||
}
|
||||
|
||||
#open-workspace-btn {
|
||||
|
||||
@@ -5,11 +5,7 @@
|
||||
src="~@/assets/images/info.svg"
|
||||
@click="$modal.show('app-info')"
|
||||
/>
|
||||
<modal
|
||||
modal-id="app-info"
|
||||
class="dialog"
|
||||
content-class="app-info-modal"
|
||||
>
|
||||
<modal modal-id="app-info" class="dialog" content-class="app-info-modal">
|
||||
<div class="dialog-header">
|
||||
App info
|
||||
<close-icon @click="$modal.hide('app-info')" />
|
||||
|
||||
@@ -13,10 +13,18 @@
|
||||
<loading-indicator />
|
||||
Loading predefined inquiries...
|
||||
</div>
|
||||
<div id="my-inquiries-content" ref="my-inquiries-content" v-show="allInquiries.length > 0">
|
||||
<div
|
||||
id="my-inquiries-content"
|
||||
ref="my-inquiries-content"
|
||||
v-show="allInquiries.length > 0"
|
||||
>
|
||||
<div id="my-inquiries-toolbar">
|
||||
<div id="toolbar-buttons">
|
||||
<button id="toolbar-btns-import" class="toolbar" @click="importInquiries">
|
||||
<button
|
||||
id="toolbar-btns-import"
|
||||
class="toolbar"
|
||||
@click="importInquiries"
|
||||
>
|
||||
Import
|
||||
</button>
|
||||
<button
|
||||
@@ -37,7 +45,11 @@
|
||||
</button>
|
||||
</div>
|
||||
<div id="toolbar-search">
|
||||
<text-field placeholder="Search inquiry by name" width="300px" v-model="filter"/>
|
||||
<text-field
|
||||
placeholder="Search inquiry by name"
|
||||
width="300px"
|
||||
v-model="filter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -49,15 +61,20 @@
|
||||
<div class="header-container">
|
||||
<div>
|
||||
<div class="fixed-header" ref="name-th">
|
||||
<check-box ref="mainCheckBox" theme="light" @click="toggleSelectAll"/>
|
||||
<check-box
|
||||
ref="mainCheckBox"
|
||||
theme="light"
|
||||
@click="toggleSelectAll"
|
||||
/>
|
||||
<div class="name-th">Name</div>
|
||||
</div>
|
||||
<div class="fixed-header">
|
||||
Created at
|
||||
<div class="fixed-header">Created at</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-container" :style="{ 'max-height': `${maxTableHeight}px` }">
|
||||
<div
|
||||
class="table-container"
|
||||
:style="{ 'max-height': `${maxTableHeight}px` }"
|
||||
>
|
||||
<table ref="table" class="sqliteviz-table">
|
||||
<tbody>
|
||||
<tr
|
||||
@@ -81,9 +98,13 @@
|
||||
@mouseleave="hideTooltip"
|
||||
>
|
||||
Predefined
|
||||
<span class="icon-tooltip" :style="tooltipStyle" ref="tooltip">
|
||||
Predefined inquiries come from the server.
|
||||
These inquiries can’t be deleted or renamed.
|
||||
<span
|
||||
class="icon-tooltip"
|
||||
:style="tooltipStyle"
|
||||
ref="tooltip"
|
||||
>
|
||||
Predefined inquiries come from the server. These
|
||||
inquiries can’t be deleted or renamed.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,7 +127,7 @@
|
||||
/>
|
||||
<delete-icon
|
||||
v-if="!inquiry.isPredefined"
|
||||
@click="showDeleteDialog((new Set()).add(inquiry.id))"
|
||||
@click="showDeleteDialog(new Set().add(inquiry.id))"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -146,8 +167,11 @@
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
{{ deleteDialogMsg }}
|
||||
<div v-show="selectedInquiriesCount > selectedNotPredefinedCount" id="note">
|
||||
<img src="~@/assets/images/info.svg">
|
||||
<div
|
||||
v-show="selectedInquiriesCount > selectedNotPredefinedCount"
|
||||
id="note"
|
||||
>
|
||||
<img src="~@/assets/images/info.svg" />
|
||||
Note: Predefined inquiries you've selected won't be deleted
|
||||
</div>
|
||||
</div>
|
||||
@@ -217,7 +241,8 @@ export default {
|
||||
let showedInquiries = this.allInquiries
|
||||
if (this.filter) {
|
||||
showedInquiries = showedInquiries.filter(
|
||||
inquiry => inquiry.name.toUpperCase().indexOf(this.filter.toUpperCase()) >= 0
|
||||
inquiry =>
|
||||
inquiry.name.toUpperCase().indexOf(this.filter.toUpperCase()) >= 0
|
||||
)
|
||||
}
|
||||
return showedInquiries
|
||||
@@ -227,21 +252,24 @@ export default {
|
||||
return this.predefinedInquiries.concat(this.inquiries)
|
||||
},
|
||||
processedInquiryIndex() {
|
||||
return this.inquiries.findIndex(inquiry => inquiry.id === this.processedInquiryId)
|
||||
return this.inquiries.findIndex(
|
||||
inquiry => inquiry.id === this.processedInquiryId
|
||||
)
|
||||
},
|
||||
deleteDialogMsg() {
|
||||
if (!this.deleteGroup && (
|
||||
this.processedInquiryIndex === null ||
|
||||
if (
|
||||
!this.deleteGroup &&
|
||||
(this.processedInquiryIndex === null ||
|
||||
this.processedInquiryIndex < 0 ||
|
||||
this.processedInquiryIndex > this.inquiries.length
|
||||
)) {
|
||||
this.processedInquiryIndex > this.inquiries.length)
|
||||
) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const deleteItem = this.deleteGroup
|
||||
? `${this.selectedNotPredefinedCount} ${this.selectedNotPredefinedCount > 1
|
||||
? 'inquiries'
|
||||
: 'inquiry'}`
|
||||
? `${this.selectedNotPredefinedCount} ${
|
||||
this.selectedNotPredefinedCount > 1 ? 'inquiries' : 'inquiry'
|
||||
}`
|
||||
: `"${this.inquiries[this.processedInquiryIndex].name}"`
|
||||
|
||||
return `Are you sure you want to delete ${deleteItem}?`
|
||||
@@ -250,13 +278,15 @@ export default {
|
||||
watch: {
|
||||
showedInquiries: {
|
||||
handler() {
|
||||
this.selectedInquiriesIds = new Set(this.showedInquiries
|
||||
this.selectedInquiriesIds = new Set(
|
||||
this.showedInquiries
|
||||
.filter(inquiry => this.selectedInquiriesIds.has(inquiry.id))
|
||||
.map(inquiry => inquiry.id)
|
||||
)
|
||||
this.selectedInquiriesCount = this.selectedInquiriesIds.size
|
||||
this.selectedNotPredefinedCount = ([...this.selectedInquiriesIds]
|
||||
.filter(id => !this.predefinedInquiriesIds.has(id))).length
|
||||
this.selectedNotPredefinedCount = [...this.selectedInquiriesIds].filter(
|
||||
id => !this.predefinedInquiriesIds.has(id)
|
||||
).length
|
||||
|
||||
if (this.selectedInquiriesIds.size < this.showedInquiries.length) {
|
||||
if (this.$refs.mainCheckBox) {
|
||||
@@ -269,8 +299,10 @@ export default {
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const loadingPredefinedInquiries = this.$store.state.loadingPredefinedInquiries
|
||||
const predefinedInquiriesLoaded = this.$store.state.predefinedInquiriesLoaded
|
||||
const loadingPredefinedInquiries =
|
||||
this.$store.state.loadingPredefinedInquiries
|
||||
const predefinedInquiriesLoaded =
|
||||
this.$store.state.predefinedInquiriesLoaded
|
||||
if (!predefinedInquiriesLoaded && !loadingPredefinedInquiries) {
|
||||
try {
|
||||
this.$store.commit('setLoadingPredefinedInquiries', true)
|
||||
@@ -310,11 +342,15 @@ export default {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
}
|
||||
return new Date(value).toLocaleDateString('en-GB', dateOptions) + ' ' +
|
||||
return (
|
||||
new Date(value).toLocaleDateString('en-GB', dateOptions) +
|
||||
' ' +
|
||||
new Date(value).toLocaleTimeString('en-GB', timeOptions)
|
||||
)
|
||||
},
|
||||
calcNameWidth() {
|
||||
const nameWidth = this.$refs['name-td'] && this.$refs['name-td'][0]
|
||||
const nameWidth =
|
||||
this.$refs['name-td'] && this.$refs['name-td'][0]
|
||||
? this.$refs['name-td'][0].getBoundingClientRect().width
|
||||
: 0
|
||||
this.$refs['name-th'].style = `width: ${nameWidth}px`
|
||||
@@ -352,7 +388,9 @@ export default {
|
||||
this.$modal.hide('rename')
|
||||
},
|
||||
duplicateInquiry(index) {
|
||||
const newInquiry = storedInquiries.duplicateInquiry(this.showedInquiries[index])
|
||||
const newInquiry = storedInquiries.duplicateInquiry(
|
||||
this.showedInquiries[index]
|
||||
)
|
||||
this.$store.dispatch('addInquiry', newInquiry)
|
||||
},
|
||||
showDeleteDialog(idsSet) {
|
||||
@@ -365,7 +403,10 @@ export default {
|
||||
deleteInquiry() {
|
||||
this.$modal.hide('delete')
|
||||
if (!this.deleteGroup) {
|
||||
this.$store.dispatch('deleteInquiries', new Set().add(this.processedInquiryId))
|
||||
this.$store.dispatch(
|
||||
'deleteInquiries',
|
||||
new Set().add(this.processedInquiryId)
|
||||
)
|
||||
|
||||
// Clear checkbox
|
||||
if (this.selectedInquiriesIds.has(this.processedInquiryId)) {
|
||||
@@ -383,23 +424,27 @@ export default {
|
||||
storedInquiries.export(inquiryList, fileName)
|
||||
},
|
||||
exportSelectedInquiries() {
|
||||
const inquiryList = this.allInquiries.filter(
|
||||
inquiry => this.selectedInquiriesIds.has(inquiry.id)
|
||||
const inquiryList = this.allInquiries.filter(inquiry =>
|
||||
this.selectedInquiriesIds.has(inquiry.id)
|
||||
)
|
||||
|
||||
this.exportToFile(inquiryList, 'My sqliteviz inquiries.json')
|
||||
},
|
||||
|
||||
importInquiries() {
|
||||
storedInquiries.importInquiries()
|
||||
.then(importedInquiries => {
|
||||
this.$store.commit('setInquiries', this.inquiries.concat(importedInquiries))
|
||||
storedInquiries.importInquiries().then(importedInquiries => {
|
||||
this.$store.commit(
|
||||
'setInquiries',
|
||||
this.inquiries.concat(importedInquiries)
|
||||
)
|
||||
})
|
||||
},
|
||||
|
||||
toggleSelectAll(checked) {
|
||||
this.selectAll = checked
|
||||
this.$refs.rowCheckBox.forEach(item => { item.checked = checked })
|
||||
this.$refs.rowCheckBox.forEach(item => {
|
||||
item.checked = checked
|
||||
})
|
||||
|
||||
this.selectedInquiriesIds = checked
|
||||
? new Set(this.showedInquiries.map(inquiry => inquiry.id))
|
||||
@@ -407,8 +452,9 @@ export default {
|
||||
|
||||
this.selectedInquiriesCount = this.selectedInquiriesIds.size
|
||||
this.selectedNotPredefinedCount = checked
|
||||
? ([...this.selectedInquiriesIds].filter(id => !this.predefinedInquiriesIds.has(id)))
|
||||
.length
|
||||
? [...this.selectedInquiriesIds].filter(
|
||||
id => !this.predefinedInquiriesIds.has(id)
|
||||
).length
|
||||
: 0
|
||||
},
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<nav>
|
||||
<div id="nav-links">
|
||||
<a href="https://sqliteviz.com">
|
||||
<img src="~@/assets/images/logo_simple.svg">
|
||||
<img src="~@/assets/images/logo_simple.svg" />
|
||||
</a>
|
||||
<router-link to="/workspace">Workspace</router-link>
|
||||
<router-link to="/inquiries">Inquiries</router-link>
|
||||
@@ -18,11 +18,7 @@
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button
|
||||
id="create-btn"
|
||||
class="primary"
|
||||
@click="createNewInquiry"
|
||||
>
|
||||
<button id="create-btn" class="primary" @click="createNewInquiry">
|
||||
Create
|
||||
</button>
|
||||
<app-diagnostic-info />
|
||||
@@ -36,9 +32,9 @@
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<div v-show="isPredefined" id="save-note">
|
||||
<img src="~@/assets/images/info.svg">
|
||||
Note: Predefined inquiries can't be edited.
|
||||
That's why your modifications will be saved as a new inquiry. Enter the name for it.
|
||||
<img src="~@/assets/images/info.svg" />
|
||||
Note: Predefined inquiries can't be edited. That's why your
|
||||
modifications will be saved as a new inquiry. Enter the name for it.
|
||||
</div>
|
||||
<text-field
|
||||
label="Inquiry name"
|
||||
@@ -87,7 +83,10 @@ export default {
|
||||
return this.currentInquiry && this.currentInquiry.isPredefined
|
||||
},
|
||||
runDisabled() {
|
||||
return this.currentInquiry && (!this.$store.state.db || !this.currentInquiry.query)
|
||||
return (
|
||||
this.currentInquiry &&
|
||||
(!this.$store.state.db || !this.currentInquiry.query)
|
||||
)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -126,7 +125,7 @@ export default {
|
||||
async saveInquiry() {
|
||||
const isNeedName = storedInquiries.isTabNeedName(this.currentInquiry)
|
||||
if (isNeedName && !this.name) {
|
||||
this.errorMsg = 'Inquiry name can\'t be empty'
|
||||
this.errorMsg = "Inquiry name can't be empty"
|
||||
return
|
||||
}
|
||||
const dataSet = this.currentInquiry.result
|
||||
|
||||
@@ -29,7 +29,8 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.table-name, .column {
|
||||
.table-name,
|
||||
.column {
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,8 @@ export default {
|
||||
return !this.filter
|
||||
? this.$store.state.db.schema
|
||||
: this.$store.state.db.schema.filter(
|
||||
table => table.name.toUpperCase().indexOf(this.filter.toUpperCase()) !== -1
|
||||
table =>
|
||||
table.name.toUpperCase().indexOf(this.filter.toUpperCase()) !== -1
|
||||
)
|
||||
},
|
||||
dbName() {
|
||||
@@ -119,7 +120,8 @@ export default {
|
||||
background-image: linear-gradient(white 73%, rgba(255, 255, 255, 0));
|
||||
z-index: 2;
|
||||
}
|
||||
.schema, .db-name {
|
||||
.schema,
|
||||
.db-name {
|
||||
color: var(--color-text-base);
|
||||
font-size: 13px;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
<template>
|
||||
<div v-show="visible" class="chart-container" ref="chartContainer">
|
||||
<div class="warning chart-warning" v-show="!dataSources && visible">
|
||||
There is no data to build a chart. Run your SQL query and make sure the result is not empty.
|
||||
There is no data to build a chart. Run your SQL query and make sure the
|
||||
result is not empty.
|
||||
</div>
|
||||
<div class="chart" :style="{ height: !dataSources ? 'calc(100% - 40px)' : '100%' }">
|
||||
<div
|
||||
class="chart"
|
||||
:style="{ height: !dataSources ? 'calc(100% - 40px)' : '100%' }"
|
||||
>
|
||||
<PlotlyEditor
|
||||
v-show="visible"
|
||||
:data="state.data"
|
||||
:layout="state.layout"
|
||||
:frames="state.frames"
|
||||
:config="{ editable: true, displaylogo: false, modeBarButtonsToRemove: ['toImage'] }"
|
||||
:config="{
|
||||
editable: true,
|
||||
displaylogo: false,
|
||||
modeBarButtonsToRemove: ['toImage']
|
||||
}"
|
||||
:dataSources="dataSources"
|
||||
:dataSourceOptions="dataSourceOptions"
|
||||
:plotly="plotly"
|
||||
@@ -38,8 +46,10 @@ import events from '@/lib/utils/events'
|
||||
export default {
|
||||
name: 'Chart',
|
||||
props: [
|
||||
'dataSources', 'initOptions',
|
||||
'importToPngEnabled', 'importToSvgEnabled',
|
||||
'dataSources',
|
||||
'initOptions',
|
||||
'importToPngEnabled',
|
||||
'importToSvgEnabled',
|
||||
'forPivot'
|
||||
],
|
||||
emits: ['update:importToSvgEnabled', 'update', 'loadingImageCompleted'],
|
||||
@@ -70,10 +80,13 @@ export default {
|
||||
notifyOnLogging: 1
|
||||
})
|
||||
this.$watch(
|
||||
() => this.state && this.state.data && this.state.data
|
||||
() =>
|
||||
this.state &&
|
||||
this.state.data &&
|
||||
this.state.data
|
||||
.map(trace => `${trace.type}${trace.mode ? '-' + trace.mode : ''}`)
|
||||
.join(','),
|
||||
(value) => {
|
||||
value => {
|
||||
events.send('viz_plotly.render', null, {
|
||||
type: value,
|
||||
pivot: !!this.forPivot
|
||||
@@ -140,7 +153,10 @@ export default {
|
||||
)
|
||||
},
|
||||
async prepareCopy(type = 'png') {
|
||||
return await chartHelper.getImageDataUrl(this.$refs.plotlyEditor.$el, type)
|
||||
return await chartHelper.getImageDataUrl(
|
||||
this.$refs.plotlyEditor.$el,
|
||||
type
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,12 @@
|
||||
import $ from 'jquery'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
import PivotSortBtn from './PivotSortBtn'
|
||||
import { renderers, aggregators, zeroValAggregators, twoValAggregators } from '../pivotHelper'
|
||||
import {
|
||||
renderers,
|
||||
aggregators,
|
||||
zeroValAggregators,
|
||||
twoValAggregators
|
||||
} from '../pivotHelper'
|
||||
|
||||
export default {
|
||||
name: 'pivotUi',
|
||||
@@ -150,16 +155,28 @@ export default {
|
||||
PivotSortBtn
|
||||
},
|
||||
data() {
|
||||
const aggregatorName = (this.modelValue && this.modelValue.aggregatorName) || 'Count'
|
||||
const rendererName = (this.modelValue && this.modelValue.rendererName) || 'Table'
|
||||
const aggregatorName =
|
||||
(this.modelValue && this.modelValue.aggregatorName) || 'Count'
|
||||
const rendererName =
|
||||
(this.modelValue && this.modelValue.rendererName) || 'Table'
|
||||
return {
|
||||
collapsed: false,
|
||||
renderer: { name: rendererName, fun: $.pivotUtilities.renderers[rendererName] },
|
||||
aggregator: { name: aggregatorName, fun: $.pivotUtilities.aggregators[aggregatorName] },
|
||||
renderer: {
|
||||
name: rendererName,
|
||||
fun: $.pivotUtilities.renderers[rendererName]
|
||||
},
|
||||
aggregator: {
|
||||
name: aggregatorName,
|
||||
fun: $.pivotUtilities.aggregators[aggregatorName]
|
||||
},
|
||||
rows: (this.modelValue && this.modelValue.rows) || [],
|
||||
cols: (this.modelValue && this.modelValue.cols) || [],
|
||||
val1: (this.modelValue && this.modelValue.vals && this.modelValue.vals[0]) || '',
|
||||
val2: (this.modelValue && this.modelValue.vals && this.modelValue.vals[1]) || '',
|
||||
val1:
|
||||
(this.modelValue && this.modelValue.vals && this.modelValue.vals[0]) ||
|
||||
'',
|
||||
val2:
|
||||
(this.modelValue && this.modelValue.vals && this.modelValue.vals[1]) ||
|
||||
'',
|
||||
colOrder: (this.modelValue && this.modelValue.colOrder) || 'key_a_to_z',
|
||||
rowOrder: (this.modelValue && this.modelValue.rowOrder) || 'key_a_to_z'
|
||||
}
|
||||
@@ -281,7 +298,6 @@ export default {
|
||||
white-space: nowrap;
|
||||
margin: auto;
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
||||
.switcher:hover {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<div class="pivot-container">
|
||||
<div class="warning pivot-warning" v-show="!dataSources">
|
||||
There is no data to build a pivot. Run your SQL query and make sure the result is not empty.
|
||||
There is no data to build a pivot. Run your SQL query and make sure the
|
||||
result is not empty.
|
||||
</div>
|
||||
<pivot-ui
|
||||
:key-names="columns"
|
||||
@@ -37,7 +38,12 @@ import events from '@/lib/utils/events'
|
||||
|
||||
export default {
|
||||
name: 'pivot',
|
||||
props: ['dataSources', 'initOptions', 'importToPngEnabled', 'importToSvgEnabled'],
|
||||
props: [
|
||||
'dataSources',
|
||||
'initOptions',
|
||||
'importToPngEnabled',
|
||||
'importToSvgEnabled'
|
||||
],
|
||||
emits: [
|
||||
'loadingImageCompleted',
|
||||
'update',
|
||||
@@ -163,7 +169,9 @@ export default {
|
||||
|
||||
$(this.$refs.pivotOutput).pivot(
|
||||
function (callback) {
|
||||
const rowCount = !this.dataSources ? 0 : this.dataSources[this.columns[0]].length
|
||||
const rowCount = !this.dataSources
|
||||
? 0
|
||||
: this.dataSources[this.columns[0]].length
|
||||
for (let i = 1; i <= rowCount; i++) {
|
||||
const row = {}
|
||||
this.columns.forEach(col => {
|
||||
@@ -198,7 +206,9 @@ export default {
|
||||
} else {
|
||||
const source = this.viewStandartChart
|
||||
? await chartHelper.getImageDataUrl(this.$refs.pivotOutput, 'png')
|
||||
: (await pivotHelper.getPivotCanvas(this.$refs.pivotOutput)).toDataURL('image/png')
|
||||
: (
|
||||
await pivotHelper.getPivotCanvas(this.$refs.pivotOutput)
|
||||
).toDataURL('image/png')
|
||||
|
||||
this.$emit('loadingImageCompleted')
|
||||
fIo.downloadFromUrl(source, 'pivot')
|
||||
@@ -219,7 +229,10 @@ export default {
|
||||
if (this.viewCustomChart) {
|
||||
this.$refs.customChart.saveAsSvg()
|
||||
} else if (this.viewStandartChart) {
|
||||
const url = await chartHelper.getImageDataUrl(this.$refs.pivotOutput, 'svg')
|
||||
const url = await chartHelper.getImageDataUrl(
|
||||
this.$refs.pivotOutput,
|
||||
'svg'
|
||||
)
|
||||
fIo.downloadFromUrl(url, 'pivot')
|
||||
}
|
||||
},
|
||||
|
||||
@@ -69,12 +69,14 @@ export const renderers = Object.keys($.pivotUtilities.renderers).map(key => {
|
||||
}
|
||||
})
|
||||
|
||||
export const aggregators = Object.keys($.pivotUtilities.aggregators).map(key => {
|
||||
export const aggregators = Object.keys($.pivotUtilities.aggregators).map(
|
||||
key => {
|
||||
return {
|
||||
name: key,
|
||||
fun: $.pivotUtilities.aggregators[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
export async function getPivotCanvas(pivotOutput) {
|
||||
const tableElement = pivotOutput.querySelector('.pvtTable')
|
||||
|
||||
@@ -172,7 +172,7 @@ export default {
|
||||
await time.sleep(0)
|
||||
this.dataToCopy = await this.$refs.viewComponent.prepareCopy()
|
||||
const t1 = performance.now()
|
||||
if ((t1 - t0) < 950) {
|
||||
if (t1 - t0 < 950) {
|
||||
this.$modal.hide('prepareCopy')
|
||||
this.copyToClipboard()
|
||||
} else {
|
||||
|
||||
@@ -11,9 +11,7 @@
|
||||
<tr>
|
||||
<th />
|
||||
<th>
|
||||
<div class="cell-data">
|
||||
Row #{{ currentRowIndex + 1 }}
|
||||
</div>
|
||||
<div class="cell-data">Row #{{ currentRowIndex + 1 }}</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -78,8 +76,9 @@ export default {
|
||||
mounted() {
|
||||
const col = this.selectedColumnIndex
|
||||
const row = this.currentRowIndex
|
||||
const cell = this.$refs.table
|
||||
.querySelector(`td[data-col="${col}"][data-row="${row}"]`)
|
||||
const cell = this.$refs.table.querySelector(
|
||||
`td[data-col="${col}"][data-row="${row}"]`
|
||||
)
|
||||
if (cell) {
|
||||
this.selectCell(cell)
|
||||
}
|
||||
@@ -152,19 +151,21 @@ export default {
|
||||
|
||||
if (this.selectedCellElement && scrollTo) {
|
||||
this.selectedCellElement.scrollIntoView()
|
||||
this.selectedCellElement.closest('.table-container').scrollTo({ left: 0 })
|
||||
this.selectedCellElement
|
||||
.closest('.table-container')
|
||||
.scrollTo({ left: 0 })
|
||||
}
|
||||
|
||||
this.$emit('updateSelectedCell', this.selectedCellElement)
|
||||
},
|
||||
moveFocusInTable(initialCell, direction) {
|
||||
const currentColIndex = +initialCell.dataset.col
|
||||
const newColIndex = direction === 'up'
|
||||
? currentColIndex - 1
|
||||
: currentColIndex + 1
|
||||
const newColIndex =
|
||||
direction === 'up' ? currentColIndex - 1 : currentColIndex + 1
|
||||
|
||||
const newCell = this.$refs.table
|
||||
.querySelector(`td[data-col="${newColIndex}"][data-row="${this.currentRowIndex}"]`)
|
||||
const newCell = this.$refs.table.querySelector(
|
||||
`td[data-col="${newColIndex}"][data-row="${this.currentRowIndex}"]`
|
||||
)
|
||||
if (newCell) {
|
||||
this.selectCell(newCell)
|
||||
}
|
||||
@@ -180,7 +181,7 @@ table.sqliteviz-table:focus {
|
||||
.sqliteviz-table tbody td:hover {
|
||||
background-color: var(--color-bg-light-3);
|
||||
}
|
||||
.sqliteviz-table tbody td[aria-selected="true"] {
|
||||
.sqliteviz-table tbody td[aria-selected='true'] {
|
||||
box-shadow: inset 0 0 0 1px var(--color-accent);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,13 +12,7 @@
|
||||
{{ format.text }}
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="copy"
|
||||
@click="copyToClipboard"
|
||||
>
|
||||
Copy
|
||||
</button>
|
||||
<button type="button" class="copy" @click="copyToClipboard">Copy</button>
|
||||
</div>
|
||||
<div class="value-body">
|
||||
<codemirror
|
||||
@@ -30,7 +24,8 @@
|
||||
<pre
|
||||
v-if="currentFormat === 'text'"
|
||||
:class="['text-value', { 'meta-value': isNull || isBlob }]"
|
||||
>{{ cellText }}</pre>
|
||||
>{{ cellText }}</pre
|
||||
>
|
||||
<logs
|
||||
v-if="messages && messages.length > 0"
|
||||
:messages="messages"
|
||||
@@ -117,22 +112,21 @@ export default {
|
||||
methods: {
|
||||
formatJson(jsonStr) {
|
||||
try {
|
||||
this.formattedJson = JSON.stringify(
|
||||
JSON.parse(jsonStr), null, 4
|
||||
)
|
||||
this.formattedJson = JSON.stringify(JSON.parse(jsonStr), null, 4)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
this.formattedJson = ''
|
||||
this.messages = [{
|
||||
this.messages = [
|
||||
{
|
||||
type: 'error',
|
||||
message: 'Can\'t parse JSON.'
|
||||
}]
|
||||
message: "Can't parse JSON."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
copyToClipboard() {
|
||||
cIo.copyText(this.currentFormat === 'json'
|
||||
? this.formattedJson
|
||||
: this.cellValue,
|
||||
cIo.copyText(
|
||||
this.currentFormat === 'json' ? this.formattedJson : this.cellValue,
|
||||
'The value is copied to clipboard.'
|
||||
)
|
||||
}
|
||||
@@ -188,7 +182,7 @@ export default {
|
||||
background-color: var(--color-bg-light);
|
||||
}
|
||||
|
||||
.value-viewer-toolbar button[aria-selected="true"] {
|
||||
.value-viewer-toolbar button[aria-selected='true'] {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,16 +8,26 @@
|
||||
class="run-result-panel-content"
|
||||
>
|
||||
<template #left-pane>
|
||||
<div :id="'run-result-left-pane-'+tab.id" class="result-set-container"/>
|
||||
<div
|
||||
:id="'run-result-left-pane-' + tab.id"
|
||||
class="result-set-container"
|
||||
/>
|
||||
</template>
|
||||
<div :id="'run-result-result-set-'+tab.id" class="result-set-container"/>
|
||||
<div
|
||||
:id="'run-result-result-set-' + tab.id"
|
||||
class="result-set-container"
|
||||
/>
|
||||
<template #right-pane v-if="viewValuePanelVisible">
|
||||
<div class="value-viewer-container">
|
||||
<value-viewer
|
||||
v-show="selectedCell"
|
||||
:cellValue="selectedCell
|
||||
? result.values[result.columns[selectedCell.dataset.col]][selectedCell.dataset.row]
|
||||
: ''"
|
||||
:cellValue="
|
||||
selectedCell
|
||||
? result.values[result.columns[selectedCell.dataset.col]][
|
||||
selectedCell.dataset.row
|
||||
]
|
||||
: ''
|
||||
"
|
||||
/>
|
||||
<div v-show="!selectedCell" class="table-preview">
|
||||
No cell selected to view
|
||||
@@ -80,11 +90,7 @@
|
||||
@cancel="cancelCopy"
|
||||
/>
|
||||
|
||||
<teleport
|
||||
defer
|
||||
:to="resultSetTeleportTarget"
|
||||
:disabled="!enableTeleport"
|
||||
>
|
||||
<teleport defer :to="resultSetTeleportTarget" :disabled="!enableTeleport">
|
||||
<div>
|
||||
<div
|
||||
v-show="result === null && !isGettingResults && !error"
|
||||
@@ -192,7 +198,8 @@ export default {
|
||||
if (!this.enableTeleport) {
|
||||
return undefined
|
||||
}
|
||||
const base = `#${this.viewValuePanelVisible
|
||||
const base = `#${
|
||||
this.viewValuePanelVisible
|
||||
? 'run-result-left-pane'
|
||||
: 'run-result-result-set'
|
||||
}`
|
||||
@@ -236,7 +243,8 @@ export default {
|
||||
|
||||
exportToCsv() {
|
||||
if (this.result && this.result.values) {
|
||||
events.send('resultset.export',
|
||||
events.send(
|
||||
'resultset.export',
|
||||
this.result.values[this.result.columns[0]].length,
|
||||
{ to: 'csv' }
|
||||
)
|
||||
@@ -247,7 +255,8 @@ export default {
|
||||
|
||||
async prepareCopy() {
|
||||
if (this.result && this.result.values) {
|
||||
events.send('resultset.export',
|
||||
events.send(
|
||||
'resultset.export',
|
||||
this.result.values[this.result.columns[0]].length,
|
||||
{ to: 'clipboard' }
|
||||
)
|
||||
@@ -261,7 +270,7 @@ export default {
|
||||
await time.sleep(0)
|
||||
this.dataToCopy = csv.serialize(this.result)
|
||||
const t1 = performance.now()
|
||||
if ((t1 - t0) < 950) {
|
||||
if (t1 - t0 < 950) {
|
||||
this.$modal.hide('prepareCSVCopy')
|
||||
this.copyToClipboard()
|
||||
} else {
|
||||
|
||||
@@ -12,8 +12,10 @@ export function getHints (cm, options) {
|
||||
// Don't show the hint if there is only one option
|
||||
// and the replacingText is already equals to this option
|
||||
const replacedText = cm.getRange(result.from, result.to).toUpperCase()
|
||||
if (result.list.length === 1 &&
|
||||
_getHintText(result.list[0]).toUpperCase() === replacedText) {
|
||||
if (
|
||||
result.list.length === 1 &&
|
||||
_getHintText(result.list[0]).toUpperCase() === replacedText
|
||||
) {
|
||||
result.list = []
|
||||
}
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
runDisabled() {
|
||||
return (!this.$store.state.db || !this.query || this.isGettingResults)
|
||||
return !this.$store.state.db || !this.query || this.isGettingResults
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
||||
@@ -87,7 +87,9 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
topPaneSize: this.tab.maximize
|
||||
? this.tab.layout[this.tab.maximize] === 'above' ? 100 : 0
|
||||
? this.tab.layout[this.tab.maximize] === 'above'
|
||||
? 100
|
||||
: 0
|
||||
: 50,
|
||||
enableTeleport: this.$store.state.isWorkspaceVisible
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="index"
|
||||
@click="selectTab(tab.id)"
|
||||
:class="[{'tab-selected': (tab.id === selectedTabId)}, 'tab']"
|
||||
:class="[{ 'tab-selected': tab.id === selectedTabId }, 'tab']"
|
||||
>
|
||||
<div class="tab-name">
|
||||
<span v-show="!tab.isSaved" class="star">*</span>
|
||||
@@ -13,15 +13,15 @@
|
||||
<span v-else class="tab-untitled">{{ tab.tempName }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<close-icon class="close-icon" :size="10" @click="beforeCloseTab(tab)"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<tab
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
:tab="tab"
|
||||
<close-icon
|
||||
class="close-icon"
|
||||
:size="10"
|
||||
@click="beforeCloseTab(tab)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<tab v-for="tab in tabs" :key="tab.id" :tab="tab" />
|
||||
<div v-show="tabs.length === 0" id="start-guide">
|
||||
<span class="link" @click="emitCreateTabEvent">Create</span>
|
||||
new inquiry from scratch or open one from
|
||||
@@ -31,26 +31,33 @@
|
||||
<!--Close tab warning dialog -->
|
||||
<modal modal-id="close-warn" class="dialog" content-style="width: 560px;">
|
||||
<div class="dialog-header">
|
||||
Close tab {{
|
||||
Close tab
|
||||
{{
|
||||
closingTab !== null
|
||||
? (closingTab.name || `[${closingTab.tempName}]`)
|
||||
? closingTab.name || `[${closingTab.tempName}]`
|
||||
: ''
|
||||
}}
|
||||
<close-icon @click="$modal.hide('close-warn')" />
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
You have unsaved changes. Save changes in {{
|
||||
You have unsaved changes. Save changes in
|
||||
{{
|
||||
closingTab !== null
|
||||
? (closingTab.name || `[${closingTab.tempName}]`)
|
||||
? closingTab.name || `[${closingTab.tempName}]`
|
||||
: ''
|
||||
}} before closing?
|
||||
}}
|
||||
before closing?
|
||||
</div>
|
||||
<div class="dialog-buttons-container">
|
||||
<button class="secondary" @click="closeTab(closingTab)">
|
||||
Close without saving
|
||||
</button>
|
||||
<button class="secondary" @click="$modal.hide('close-warn')">Don't close</button>
|
||||
<button class="primary" @click="saveAndClose(closingTab)">Save and close</button>
|
||||
<button class="secondary" @click="$modal.hide('close-warn')">
|
||||
Don't close
|
||||
</button>
|
||||
<button class="primary" @click="saveAndClose(closingTab)">
|
||||
Save and close
|
||||
</button>
|
||||
</div>
|
||||
</modal>
|
||||
</div>
|
||||
|
||||
@@ -36,7 +36,10 @@ export default {
|
||||
},
|
||||
async beforeCreate() {
|
||||
const schema = this.$store.state.db.schema
|
||||
if ((!schema || schema.length === 0) && this.$store.state.tabs.length === 0) {
|
||||
if (
|
||||
(!schema || schema.length === 0) &&
|
||||
this.$store.state.tabs.length === 0
|
||||
) {
|
||||
const stmt = [
|
||||
'/*',
|
||||
' * Your database is empty. In order to start building charts',
|
||||
|
||||
@@ -3,7 +3,7 @@ import { createVfm, VueFinalModal, useVfm } from 'vue-final-modal'
|
||||
|
||||
config.global.plugins = [createVfm()]
|
||||
config.global.components = {
|
||||
'modal': VueFinalModal
|
||||
modal: VueFinalModal
|
||||
}
|
||||
const vfm = useVfm()
|
||||
config.global.mocks = {
|
||||
|
||||
@@ -13,9 +13,9 @@ describe('App.vue', () => {
|
||||
})
|
||||
|
||||
it('Gets inquiries', () => {
|
||||
sinon.stub(storedInquiries, 'getStoredInquiries').returns([
|
||||
{ id: 1 }, { id: 2 }, { id: 3 }
|
||||
])
|
||||
sinon
|
||||
.stub(storedInquiries, 'getStoredInquiries')
|
||||
.returns([{ id: 1 }, { id: 2 }, { id: 3 }])
|
||||
const state = {
|
||||
predefinedInquiries: [],
|
||||
inquiries: []
|
||||
@@ -33,7 +33,9 @@ describe('App.vue', () => {
|
||||
|
||||
it('Updates inquiries when they change in store', async () => {
|
||||
sinon.stub(storedInquiries, 'getStoredInquiries').returns([
|
||||
{ id: 1, name: 'foo' }, { id: 2, name: 'baz' }, { id: 3, name: 'bar' }
|
||||
{ id: 1, name: 'foo' },
|
||||
{ id: 2, name: 'baz' },
|
||||
{ id: 3, name: 'bar' }
|
||||
])
|
||||
sinon.spy(storedInquiries, 'updateStorage')
|
||||
|
||||
@@ -43,8 +45,7 @@ describe('App.vue', () => {
|
||||
}
|
||||
const store = createStore({ state, mutations })
|
||||
shallowMount(App, {
|
||||
global: { stubs: ['router-view'],
|
||||
plugins: [store] }
|
||||
global: { stubs: ['router-view'], plugins: [store] }
|
||||
})
|
||||
|
||||
store.state.inquiries.splice(0, 1, { id: 1, name: 'new foo name' })
|
||||
|
||||
@@ -50,9 +50,13 @@ describe('CheckBox', () => {
|
||||
props: { disabled: true }
|
||||
})
|
||||
expect(wrapper.find('.checkbox-container').classes()).to.include('disabled')
|
||||
expect(wrapper.find('.checkbox-container').classes()).to.not.include('checked')
|
||||
expect(wrapper.find('.checkbox-container').classes()).to.not.include(
|
||||
'checked'
|
||||
)
|
||||
await wrapper.trigger('click')
|
||||
expect(wrapper.emitted().click).to.equal(undefined)
|
||||
expect(wrapper.find('.checkbox-container').classes()).to.not.include('checked')
|
||||
expect(wrapper.find('.checkbox-container').classes()).to.not.include(
|
||||
'checked'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,7 +6,6 @@ import CsvJsonImport from '@/components/CsvJsonImport'
|
||||
import csv from '@/lib/csv'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
|
||||
describe('CsvJsonImport.vue', () => {
|
||||
let state = {}
|
||||
let actions = {}
|
||||
@@ -74,13 +73,15 @@ describe('CsvJsonImport.vue', () => {
|
||||
}
|
||||
},
|
||||
rowCount: 2,
|
||||
messages: [{
|
||||
messages: [
|
||||
{
|
||||
code: 'UndetectableDelimiter',
|
||||
message: 'Comma was used as a standart delimiter',
|
||||
row: 0,
|
||||
type: 'info',
|
||||
hint: undefined
|
||||
}]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
wrapper.vm.preview()
|
||||
@@ -89,24 +90,34 @@ describe('CsvJsonImport.vue', () => {
|
||||
await nextTick()
|
||||
expect(wrapper.find('.dialog.vfm').exists()).to.equal(true)
|
||||
expect(wrapper.find('.dialog-header').text()).to.equal('CSV import')
|
||||
expect(wrapper.find('#csv-json-table-name input').element.value).to.equal('my_data')
|
||||
expect(wrapper.findComponent({ name: 'delimiter-selector' }).props('modelValue')).to.equal('|')
|
||||
expect(wrapper.find('#csv-json-table-name input').element.value).to.equal(
|
||||
'my_data'
|
||||
)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'delimiter-selector' }).props('modelValue')
|
||||
).to.equal('|')
|
||||
expect(wrapper.find('#quote-char input').element.value).to.equal('"')
|
||||
expect(wrapper.find('#escape-char input').element.value).to.equal('"')
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.checked).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.checked).to.equal(
|
||||
true
|
||||
)
|
||||
const rows = wrapper.findAll('tbody tr')
|
||||
expect(rows).to.have.lengthOf(2)
|
||||
expect(rows[0].findAll('td')[0].text()).to.equal('foo')
|
||||
expect(rows[0].findAll('td')[1].text()).to.equal('1')
|
||||
expect(rows[1].findAll('td')[0].text()).to.equal('bar')
|
||||
expect(rows[1].findAll('td')[1].text()).to.equal('2')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.include('Information about row 0. Comma was used as a standart delimiter.')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.include('Preview parsing is completed in')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text()).to.include(
|
||||
'Information about row 0. Comma was used as a standart delimiter.'
|
||||
)
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text()).to.include(
|
||||
'Preview parsing is completed in'
|
||||
)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||
expect(wrapper.find('#import-start').attributes().disabled).to.equal(undefined)
|
||||
expect(wrapper.find('#import-start').attributes().disabled).to.equal(
|
||||
undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('disables import if no rows found', async () => {
|
||||
@@ -129,8 +140,9 @@ describe('CsvJsonImport.vue', () => {
|
||||
await nextTick()
|
||||
const rows = wrapper.findAll('tbody tr')
|
||||
expect(rows).to.have.lengthOf(0)
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.include('No rows to import.')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text()).to.include(
|
||||
'No rows to import.'
|
||||
)
|
||||
expect(wrapper.find('.no-data').isVisible()).to.equal(true)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||
@@ -177,8 +189,9 @@ describe('CsvJsonImport.vue', () => {
|
||||
expect(rows).to.have.lengthOf(1)
|
||||
expect(rows[0].findAll('td')[0].text()).to.equal('bar')
|
||||
expect(rows[0].findAll('td')[1].text()).to.equal('2')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.include('Preview parsing is completed in')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text()).to.include(
|
||||
'Preview parsing is completed in'
|
||||
)
|
||||
|
||||
parse.onCall(2).resolves({
|
||||
delimiter: ',',
|
||||
@@ -191,13 +204,15 @@ describe('CsvJsonImport.vue', () => {
|
||||
},
|
||||
rowCount: 1,
|
||||
hasErrors: true,
|
||||
messages: [{
|
||||
messages: [
|
||||
{
|
||||
code: 'MissingQuotes',
|
||||
message: 'Quote is missed',
|
||||
row: 0,
|
||||
type: 'error',
|
||||
hint: 'Edit your CSV so that the field has a closing quote char.'
|
||||
}]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
await wrapper.find('#quote-char input').setValue("'")
|
||||
@@ -207,13 +222,13 @@ describe('CsvJsonImport.vue', () => {
|
||||
expect(rows).to.have.lengthOf(1)
|
||||
expect(rows[0].findAll('td')[0].text()).to.equal('baz')
|
||||
expect(rows[0].findAll('td')[1].text()).to.equal('3')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.contain(
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text()).to.contain(
|
||||
'Error in row 0. Quote is missed. ' +
|
||||
'Edit your CSV so that the field has a closing quote char.'
|
||||
)
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.not.contain('Preview parsing is completed in')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text()).to.not.contain(
|
||||
'Preview parsing is completed in'
|
||||
)
|
||||
|
||||
parse.onCall(3).resolves({
|
||||
delimiter: ',',
|
||||
@@ -234,8 +249,9 @@ describe('CsvJsonImport.vue', () => {
|
||||
expect(rows).to.have.lengthOf(1)
|
||||
expect(rows[0].findAll('td')[0].text()).to.equal('qux')
|
||||
expect(rows[0].findAll('td')[1].text()).to.equal('4')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.contain('Preview parsing is completed in')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text()).to.contain(
|
||||
'Preview parsing is completed in'
|
||||
)
|
||||
|
||||
parse.onCall(4).resolves({
|
||||
delimiter: ',',
|
||||
@@ -257,8 +273,9 @@ describe('CsvJsonImport.vue', () => {
|
||||
expect(rows[0].findAll('td')[0].text()).to.equal('corge')
|
||||
expect(rows[0].findAll('td')[1].text()).to.equal('5')
|
||||
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.include('Preview parsing is completed in')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text()).to.include(
|
||||
'Preview parsing is completed in'
|
||||
)
|
||||
})
|
||||
|
||||
it('has proper state before parsing is complete', async () => {
|
||||
@@ -282,8 +299,10 @@ describe('CsvJsonImport.vue', () => {
|
||||
await nextTick()
|
||||
|
||||
let resolveParsing
|
||||
parse.onCall(1).returns(new Promise(resolve => {
|
||||
resolveParsing = () => resolve({
|
||||
parse.onCall(1).returns(
|
||||
new Promise(resolve => {
|
||||
resolveParsing = () =>
|
||||
resolve({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
@@ -295,29 +314,40 @@ describe('CsvJsonImport.vue', () => {
|
||||
rowCount: 1,
|
||||
messages: []
|
||||
})
|
||||
}))
|
||||
})
|
||||
)
|
||||
|
||||
await wrapper.find('#csv-json-table-name input').setValue('foo')
|
||||
await wrapper.find('#import-start').trigger('click')
|
||||
|
||||
// "Parsing CSV..." in the logs
|
||||
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text())
|
||||
.to.equal('Parsing CSV...')
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text()
|
||||
).to.equal('Parsing CSV...')
|
||||
|
||||
// After 1 second - loading indicator is shown
|
||||
await clock.tick(1000)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
||||
wrapper
|
||||
.findComponent({ name: 'logs' })
|
||||
.findComponent({ name: 'LoadingIndicator' })
|
||||
.exists()
|
||||
).to.equal(true)
|
||||
|
||||
// All the dialog controls are disabled
|
||||
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(true)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled
|
||||
).to.equal(true)
|
||||
expect(wrapper.find('#quote-char input').element.disabled).to.equal(true)
|
||||
expect(wrapper.find('#escape-char input').element.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(
|
||||
true
|
||||
)
|
||||
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
|
||||
expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
|
||||
true
|
||||
)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||
await resolveParsing()
|
||||
@@ -325,7 +355,10 @@ describe('CsvJsonImport.vue', () => {
|
||||
|
||||
// Loading indicator is not shown when parsing is compete
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
||||
wrapper
|
||||
.findComponent({ name: 'logs' })
|
||||
.findComponent({ name: 'LoadingIndicator' })
|
||||
.exists()
|
||||
).to.equal(false)
|
||||
})
|
||||
|
||||
@@ -365,26 +398,35 @@ describe('CsvJsonImport.vue', () => {
|
||||
await nextTick()
|
||||
|
||||
let resolveImport
|
||||
wrapper.vm.db.addTableFromCsv.onCall(0).returns(new Promise(resolve => {
|
||||
wrapper.vm.db.addTableFromCsv.onCall(0).returns(
|
||||
new Promise(resolve => {
|
||||
resolveImport = resolve
|
||||
}))
|
||||
})
|
||||
)
|
||||
|
||||
await wrapper.find('#csv-json-table-name input').setValue('foo')
|
||||
await wrapper.find('#import-start').trigger('click')
|
||||
await csv.parse.returnValues[1]
|
||||
|
||||
// Parsing success in the logs
|
||||
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text())
|
||||
.to.include('2 rows are parsed successfully in')
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text()
|
||||
).to.include('2 rows are parsed successfully in')
|
||||
|
||||
// All the dialog controls are disabled
|
||||
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(true)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled
|
||||
).to.equal(true)
|
||||
expect(wrapper.find('#quote-char input').element.disabled).to.equal(true)
|
||||
expect(wrapper.find('#escape-char input').element.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(
|
||||
true
|
||||
)
|
||||
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
|
||||
expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
|
||||
true
|
||||
)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||
await resolveImport()
|
||||
@@ -417,18 +459,22 @@ describe('CsvJsonImport.vue', () => {
|
||||
},
|
||||
rowCount: 2,
|
||||
hasErrors: false,
|
||||
messages: [{
|
||||
messages: [
|
||||
{
|
||||
code: 'UndetectableDelimiter',
|
||||
message: 'Comma was used as a standart delimiter',
|
||||
type: 'info',
|
||||
hint: undefined
|
||||
}]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
let resolveImport
|
||||
wrapper.vm.db.addTableFromCsv.onCall(0).returns(new Promise(resolve => {
|
||||
wrapper.vm.db.addTableFromCsv.onCall(0).returns(
|
||||
new Promise(resolve => {
|
||||
resolveImport = resolve
|
||||
}))
|
||||
})
|
||||
)
|
||||
|
||||
wrapper.vm.preview()
|
||||
wrapper.vm.open()
|
||||
@@ -446,13 +492,19 @@ describe('CsvJsonImport.vue', () => {
|
||||
expect(logs[2].text()).to.equals('Comma was used as a standart delimiter.')
|
||||
|
||||
// All the dialog controls are disabled
|
||||
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(true)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled
|
||||
).to.equal(true)
|
||||
expect(wrapper.find('#quote-char input').element.disabled).to.equal(true)
|
||||
expect(wrapper.find('#escape-char input').element.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(
|
||||
true
|
||||
)
|
||||
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
|
||||
expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
|
||||
true
|
||||
)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||
await resolveImport()
|
||||
@@ -485,12 +537,14 @@ describe('CsvJsonImport.vue', () => {
|
||||
},
|
||||
rowCount: 2,
|
||||
hasErrors: true,
|
||||
messages: [{
|
||||
messages: [
|
||||
{
|
||||
code: 'Error',
|
||||
message: 'Something is wrong',
|
||||
type: 'error',
|
||||
hint: undefined
|
||||
}]
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
wrapper.vm.preview()
|
||||
@@ -509,13 +563,19 @@ describe('CsvJsonImport.vue', () => {
|
||||
expect(logs[2].text()).to.equals('Something is wrong.')
|
||||
|
||||
// All the dialog controls are enabled
|
||||
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(false)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled
|
||||
).to.equal(false)
|
||||
expect(wrapper.find('#quote-char input').element.disabled).to.equal(false)
|
||||
expect(wrapper.find('#escape-char input').element.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(
|
||||
false
|
||||
)
|
||||
expect(wrapper.find('#import-cancel').element.disabled).to.equal(false)
|
||||
expect(wrapper.find('#import-finish').element.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
|
||||
false
|
||||
)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||
})
|
||||
@@ -551,8 +611,11 @@ describe('CsvJsonImport.vue', () => {
|
||||
})
|
||||
|
||||
let resolveImport = sinon.stub()
|
||||
wrapper.vm.db.addTableFromCsv = sinon.stub()
|
||||
.resolves(new Promise(resolve => { resolveImport = resolve }))
|
||||
wrapper.vm.db.addTableFromCsv = sinon.stub().resolves(
|
||||
new Promise(resolve => {
|
||||
resolveImport = resolve
|
||||
})
|
||||
)
|
||||
|
||||
wrapper.vm.preview()
|
||||
wrapper.vm.open()
|
||||
@@ -564,23 +627,33 @@ describe('CsvJsonImport.vue', () => {
|
||||
await csv.parse.returnValues[1]
|
||||
|
||||
// Parsing success in the logs
|
||||
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg')[2].text())
|
||||
.to.equal('Importing CSV into a SQLite database...')
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findAll('.msg')[2].text()
|
||||
).to.equal('Importing CSV into a SQLite database...')
|
||||
|
||||
// After 1 second - loading indicator is shown
|
||||
await clock.tick(1000)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
||||
wrapper
|
||||
.findComponent({ name: 'logs' })
|
||||
.findComponent({ name: 'LoadingIndicator' })
|
||||
.exists()
|
||||
).to.equal(true)
|
||||
|
||||
// All the dialog controls are disabled
|
||||
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(true)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled
|
||||
).to.equal(true)
|
||||
expect(wrapper.find('#quote-char input').element.disabled).to.equal(true)
|
||||
expect(wrapper.find('#escape-char input').element.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(
|
||||
true
|
||||
)
|
||||
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
|
||||
expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
|
||||
true
|
||||
)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||
expect(wrapper.vm.db.addTableFromCsv.getCall(0).args[0]).to.equal('foo') // table name
|
||||
@@ -589,7 +662,10 @@ describe('CsvJsonImport.vue', () => {
|
||||
await resolveImport()
|
||||
await wrapper.vm.db.addTableFromCsv.returnValues[0]
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
||||
wrapper
|
||||
.findComponent({ name: 'logs' })
|
||||
.findComponent({ name: 'LoadingIndicator' })
|
||||
.exists()
|
||||
).to.equal(false)
|
||||
})
|
||||
|
||||
@@ -635,16 +711,24 @@ describe('CsvJsonImport.vue', () => {
|
||||
// Import success in the logs
|
||||
const logs = wrapper.findComponent({ name: 'logs' }).findAll('.msg')
|
||||
expect(logs).to.have.lengthOf(3)
|
||||
expect(logs[2].text()).to.contain('Importing CSV into a SQLite database is completed in')
|
||||
expect(logs[2].text()).to.contain(
|
||||
'Importing CSV into a SQLite database is completed in'
|
||||
)
|
||||
|
||||
// All the dialog controls are enabled
|
||||
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(false)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled
|
||||
).to.equal(false)
|
||||
expect(wrapper.find('#quote-char input').element.disabled).to.equal(false)
|
||||
expect(wrapper.find('#escape-char input').element.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(
|
||||
false
|
||||
)
|
||||
expect(wrapper.find('#import-cancel').element.disabled).to.equal(false)
|
||||
expect(wrapper.find('#import-finish').element.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
|
||||
false
|
||||
)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(true)
|
||||
})
|
||||
|
||||
@@ -696,13 +780,19 @@ describe('CsvJsonImport.vue', () => {
|
||||
expect(logs[3].text()).to.equal('Error: fail.')
|
||||
|
||||
// All the dialog controls are enabled
|
||||
expect(wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled).to.equal(false)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'delimiter-selector' }).vm.disabled
|
||||
).to.equal(false)
|
||||
expect(wrapper.find('#quote-char input').element.disabled).to.equal(false)
|
||||
expect(wrapper.find('#escape-char input').element.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.disabled).to.equal(
|
||||
false
|
||||
)
|
||||
expect(wrapper.find('#import-cancel').element.disabled).to.equal(false)
|
||||
expect(wrapper.find('#import-finish').element.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
|
||||
false
|
||||
)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||
})
|
||||
|
||||
@@ -733,7 +823,9 @@ describe('CsvJsonImport.vue', () => {
|
||||
await clock.tick(100)
|
||||
expect(actions.addTab.calledOnce).to.equal(true)
|
||||
await actions.addTab.returnValues[0]
|
||||
expect(mutations.setCurrentTabId.calledOnceWith(state, newTabId)).to.equal(true)
|
||||
expect(mutations.setCurrentTabId.calledOnceWith(state, newTabId)).to.equal(
|
||||
true
|
||||
)
|
||||
expect(wrapper.find('.dialog.vfm').exists()).to.equal(false)
|
||||
expect(wrapper.emitted('finish')).to.have.lengthOf(1)
|
||||
})
|
||||
@@ -764,7 +856,9 @@ describe('CsvJsonImport.vue', () => {
|
||||
await wrapper.find('#import-cancel').trigger('click')
|
||||
await clock.tick(100)
|
||||
expect(wrapper.find('.dialog.vfm').exists()).to.equal(false)
|
||||
expect(wrapper.vm.db.execute.calledOnceWith('DROP TABLE "my_data"')).to.equal(true)
|
||||
expect(
|
||||
wrapper.vm.db.execute.calledOnceWith('DROP TABLE "my_data"')
|
||||
).to.equal(true)
|
||||
expect(wrapper.vm.db.refreshSchema.calledOnce).to.equal(true)
|
||||
expect(wrapper.emitted('cancel')).to.have.lengthOf(1)
|
||||
})
|
||||
@@ -783,23 +877,31 @@ describe('CsvJsonImport.vue', () => {
|
||||
await wrapper.find('#csv-json-table-name input').setValue('foo')
|
||||
await clock.tick(400)
|
||||
await nextTick()
|
||||
expect(wrapper.find('#csv-json-table-name .text-field-error').text()).to.equal('')
|
||||
expect(
|
||||
wrapper.find('#csv-json-table-name .text-field-error').text()
|
||||
).to.equal('')
|
||||
|
||||
wrapper.vm.db.validateTableName = sinon.stub().rejects(new Error('this is a bad table name'))
|
||||
wrapper.vm.db.validateTableName = sinon
|
||||
.stub()
|
||||
.rejects(new Error('this is a bad table name'))
|
||||
await wrapper.find('#csv-json-table-name input').setValue('bar')
|
||||
await clock.tick(400)
|
||||
await nextTick()
|
||||
expect(wrapper.find('#csv-json-table-name .text-field-error').text())
|
||||
.to.equal('this is a bad table name. Try another table name.')
|
||||
expect(
|
||||
wrapper.find('#csv-json-table-name .text-field-error').text()
|
||||
).to.equal('this is a bad table name. Try another table name.')
|
||||
|
||||
await wrapper.find('#csv-json-table-name input').setValue('')
|
||||
await clock.tick(400)
|
||||
await nextTick()
|
||||
expect(wrapper.find('#csv-json-table-name .text-field-error').text()).to.equal('')
|
||||
expect(
|
||||
wrapper.find('#csv-json-table-name .text-field-error').text()
|
||||
).to.equal('')
|
||||
|
||||
await wrapper.find('#import-start').trigger('click')
|
||||
expect(wrapper.find('#csv-json-table-name .text-field-error').text())
|
||||
.to.equal("Table name can't be empty")
|
||||
expect(
|
||||
wrapper.find('#csv-json-table-name .text-field-error').text()
|
||||
).to.equal("Table name can't be empty")
|
||||
expect(wrapper.vm.db.addTableFromCsv.called).to.equal(false)
|
||||
})
|
||||
})
|
||||
@@ -812,12 +914,14 @@ describe('CsvJsonImport.vue - json', () => {
|
||||
let wrapper
|
||||
const newTabId = 1
|
||||
const file = new File(
|
||||
[new Blob(
|
||||
[JSON.stringify({ foo: [1, 2, 3] }, null, 2)],
|
||||
{ type: 'application/json' }
|
||||
)],
|
||||
[
|
||||
new Blob([JSON.stringify({ foo: [1, 2, 3] }, null, 2)], {
|
||||
type: 'application/json'
|
||||
})
|
||||
],
|
||||
'my data.json',
|
||||
{ type: 'application/json' })
|
||||
{ type: 'application/json' }
|
||||
)
|
||||
|
||||
beforeEach(() => {
|
||||
clock = sinon.useFakeTimers()
|
||||
@@ -873,25 +977,25 @@ describe('CsvJsonImport.vue - json', () => {
|
||||
await nextTick()
|
||||
expect(wrapper.find('.dialog.vfm').exists()).to.equal(true)
|
||||
expect(wrapper.find('.dialog-header').text()).to.equal('JSON import')
|
||||
expect(wrapper.find('#csv-json-table-name input').element.value).to.equal('my_data')
|
||||
expect(wrapper.findComponent({ name: 'delimiter-selector' }).exists()).to.equal(false)
|
||||
expect(wrapper.find('#csv-json-table-name input').element.value).to.equal(
|
||||
'my_data'
|
||||
)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'delimiter-selector' }).exists()
|
||||
).to.equal(false)
|
||||
expect(wrapper.find('#quote-char input').exists()).to.equal(false)
|
||||
expect(wrapper.find('#escape-char input').exists()).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).exists()).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).exists()).to.equal(
|
||||
false
|
||||
)
|
||||
const rows = wrapper.findAll('tbody tr')
|
||||
expect(rows).to.have.lengthOf(1)
|
||||
expect(rows[0].findAll('td')[0].text()).to.equal([
|
||||
'{',
|
||||
' "foo": [',
|
||||
' 1,',
|
||||
' 2,',
|
||||
' 3',
|
||||
' ]',
|
||||
'}'
|
||||
].join('\n')
|
||||
expect(rows[0].findAll('td')[0].text()).to.equal(
|
||||
['{', ' "foo": [', ' 1,', ' 2,', ' 3', ' ]', '}'].join('\n')
|
||||
)
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text()).to.include(
|
||||
'Preview parsing is completed in'
|
||||
)
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.include('Preview parsing is completed in')
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||
})
|
||||
@@ -912,8 +1016,10 @@ describe('CsvJsonImport.vue - json', () => {
|
||||
})
|
||||
|
||||
let resolveParsing
|
||||
getJsonParseResult.onCall(1).returns(new Promise(resolve => {
|
||||
resolveParsing = () => resolve({
|
||||
getJsonParseResult.onCall(1).returns(
|
||||
new Promise(resolve => {
|
||||
resolveParsing = () =>
|
||||
resolve({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['doc'],
|
||||
@@ -925,7 +1031,8 @@ describe('CsvJsonImport.vue - json', () => {
|
||||
hasErrors: false,
|
||||
messages: []
|
||||
})
|
||||
}))
|
||||
})
|
||||
)
|
||||
|
||||
await wrapper.vm.preview()
|
||||
await wrapper.vm.open()
|
||||
@@ -937,19 +1044,25 @@ describe('CsvJsonImport.vue - json', () => {
|
||||
await nextTick()
|
||||
|
||||
// "Parsing JSON..." in the logs
|
||||
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text())
|
||||
.to.equal('Parsing JSON...')
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text()
|
||||
).to.equal('Parsing JSON...')
|
||||
|
||||
// After 1 second - loading indicator is shown
|
||||
await clock.tick(1000)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
||||
wrapper
|
||||
.findComponent({ name: 'logs' })
|
||||
.findComponent({ name: 'LoadingIndicator' })
|
||||
.exists()
|
||||
).to.equal(true)
|
||||
|
||||
// All the dialog controls are disabled
|
||||
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
|
||||
expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
|
||||
true
|
||||
)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||
await resolveParsing()
|
||||
@@ -957,7 +1070,10 @@ describe('CsvJsonImport.vue - json', () => {
|
||||
|
||||
// Loading indicator is not shown when parsing is compete
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
||||
wrapper
|
||||
.findComponent({ name: 'logs' })
|
||||
.findComponent({ name: 'LoadingIndicator' })
|
||||
.exists()
|
||||
).to.equal(false)
|
||||
})
|
||||
|
||||
@@ -965,8 +1081,11 @@ describe('CsvJsonImport.vue - json', () => {
|
||||
const getJsonParseResult = sinon.spy(wrapper.vm, 'getJsonParseResult')
|
||||
|
||||
let resolveImport = sinon.stub()
|
||||
wrapper.vm.db.addTableFromCsv = sinon.stub()
|
||||
.resolves(new Promise(resolve => { resolveImport = resolve }))
|
||||
wrapper.vm.db.addTableFromCsv = sinon.stub().resolves(
|
||||
new Promise(resolve => {
|
||||
resolveImport = resolve
|
||||
})
|
||||
)
|
||||
|
||||
await wrapper.vm.preview()
|
||||
await wrapper.vm.open()
|
||||
@@ -979,19 +1098,25 @@ describe('CsvJsonImport.vue - json', () => {
|
||||
await nextTick()
|
||||
|
||||
// Parsing success in the logs
|
||||
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg')[2].text())
|
||||
.to.equal('Importing JSON into a SQLite database...')
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findAll('.msg')[2].text()
|
||||
).to.equal('Importing JSON into a SQLite database...')
|
||||
|
||||
// After 1 second - loading indicator is shown
|
||||
await clock.tick(1000)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
||||
wrapper
|
||||
.findComponent({ name: 'logs' })
|
||||
.findComponent({ name: 'LoadingIndicator' })
|
||||
.exists()
|
||||
).to.equal(true)
|
||||
|
||||
// All the dialog controls are disabled
|
||||
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
|
||||
expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
|
||||
true
|
||||
)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||
expect(wrapper.vm.db.addTableFromCsv.getCall(0).args[0]).to.equal('foo') // table name
|
||||
@@ -1000,7 +1125,10 @@ describe('CsvJsonImport.vue - json', () => {
|
||||
await resolveImport()
|
||||
await wrapper.vm.db.addTableFromCsv.returnValues[0]
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
||||
wrapper
|
||||
.findComponent({ name: 'logs' })
|
||||
.findComponent({ name: 'LoadingIndicator' })
|
||||
.exists()
|
||||
).to.equal(false)
|
||||
})
|
||||
|
||||
@@ -1021,12 +1149,16 @@ describe('CsvJsonImport.vue - json', () => {
|
||||
// Import success in the logs
|
||||
const logs = wrapper.findComponent({ name: 'logs' }).findAll('.msg')
|
||||
expect(logs).to.have.lengthOf(3)
|
||||
expect(logs[2].text()).to.contain('Importing JSON into a SQLite database is completed in')
|
||||
expect(logs[2].text()).to.contain(
|
||||
'Importing JSON into a SQLite database is completed in'
|
||||
)
|
||||
|
||||
// All the dialog controls are enabled
|
||||
expect(wrapper.find('#import-cancel').element.disabled).to.equal(false)
|
||||
expect(wrapper.find('#import-finish').element.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
|
||||
false
|
||||
)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(true)
|
||||
})
|
||||
})
|
||||
@@ -1106,16 +1238,23 @@ describe('CsvJsonImport.vue - ndjson', () => {
|
||||
await nextTick()
|
||||
expect(wrapper.find('.dialog.vfm').exists()).to.equal(true)
|
||||
expect(wrapper.find('.dialog-header').text()).to.equal('JSON import')
|
||||
expect(wrapper.find('#csv-json-table-name input').element.value).to.equal('my_data')
|
||||
expect(wrapper.findComponent({ name: 'delimiter-selector' }).exists()).to.equal(false)
|
||||
expect(wrapper.find('#csv-json-table-name input').element.value).to.equal(
|
||||
'my_data'
|
||||
)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'delimiter-selector' }).exists()
|
||||
).to.equal(false)
|
||||
expect(wrapper.find('#quote-char input').exists()).to.equal(false)
|
||||
expect(wrapper.find('#escape-char input').exists()).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).exists()).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).exists()).to.equal(
|
||||
false
|
||||
)
|
||||
const rows = wrapper.findAll('tbody tr')
|
||||
expect(rows).to.have.lengthOf(1)
|
||||
expect(rows[0].findAll('td')[0].text()).to.equal('{ "foo": [ 1, 2, 3 ] }')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.include('Preview parsing is completed in')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text()).to.include(
|
||||
'Preview parsing is completed in'
|
||||
)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||
})
|
||||
@@ -1139,8 +1278,10 @@ describe('CsvJsonImport.vue - ndjson', () => {
|
||||
await nextTick()
|
||||
|
||||
let resolveParsing
|
||||
parse.onCall(1).returns(new Promise(resolve => {
|
||||
resolveParsing = () => resolve({
|
||||
parse.onCall(1).returns(
|
||||
new Promise(resolve => {
|
||||
resolveParsing = () =>
|
||||
resolve({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['doc'],
|
||||
@@ -1151,26 +1292,33 @@ describe('CsvJsonImport.vue - ndjson', () => {
|
||||
rowCount: 1,
|
||||
messages: []
|
||||
})
|
||||
}))
|
||||
})
|
||||
)
|
||||
|
||||
await wrapper.find('#csv-json-table-name input').setValue('foo')
|
||||
await wrapper.find('#import-start').trigger('click')
|
||||
await nextTick()
|
||||
|
||||
// "Parsing JSON..." in the logs
|
||||
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text())
|
||||
.to.equal('Parsing JSON...')
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findAll('.msg')[1].text()
|
||||
).to.equal('Parsing JSON...')
|
||||
|
||||
// After 1 second - loading indicator is shown
|
||||
await clock.tick(1000)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
||||
wrapper
|
||||
.findComponent({ name: 'logs' })
|
||||
.findComponent({ name: 'LoadingIndicator' })
|
||||
.exists()
|
||||
).to.equal(true)
|
||||
|
||||
// All the dialog controls are disabled
|
||||
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
|
||||
expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
|
||||
true
|
||||
)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||
await resolveParsing()
|
||||
@@ -1178,7 +1326,10 @@ describe('CsvJsonImport.vue - ndjson', () => {
|
||||
|
||||
// Loading indicator is not shown when parsing is compete
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
||||
wrapper
|
||||
.findComponent({ name: 'logs' })
|
||||
.findComponent({ name: 'LoadingIndicator' })
|
||||
.exists()
|
||||
).to.equal(false)
|
||||
})
|
||||
|
||||
@@ -1211,8 +1362,11 @@ describe('CsvJsonImport.vue - ndjson', () => {
|
||||
})
|
||||
|
||||
let resolveImport = sinon.stub()
|
||||
wrapper.vm.db.addTableFromCsv = sinon.stub()
|
||||
.resolves(new Promise(resolve => { resolveImport = resolve }))
|
||||
wrapper.vm.db.addTableFromCsv = sinon.stub().resolves(
|
||||
new Promise(resolve => {
|
||||
resolveImport = resolve
|
||||
})
|
||||
)
|
||||
|
||||
wrapper.vm.preview()
|
||||
wrapper.vm.open()
|
||||
@@ -1225,19 +1379,25 @@ describe('CsvJsonImport.vue - ndjson', () => {
|
||||
await nextTick()
|
||||
|
||||
// Parsing success in the logs
|
||||
expect(wrapper.findComponent({ name: 'logs' }).findAll('.msg')[2].text())
|
||||
.to.equal('Importing JSON into a SQLite database...')
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findAll('.msg')[2].text()
|
||||
).to.equal('Importing JSON into a SQLite database...')
|
||||
|
||||
// After 1 second - loading indicator is shown
|
||||
await clock.tick(1000)
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
||||
wrapper
|
||||
.findComponent({ name: 'logs' })
|
||||
.findComponent({ name: 'LoadingIndicator' })
|
||||
.exists()
|
||||
).to.equal(true)
|
||||
|
||||
// All the dialog controls are disabled
|
||||
expect(wrapper.find('#import-cancel').element.disabled).to.equal(true)
|
||||
expect(wrapper.find('#import-finish').element.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(true)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
|
||||
true
|
||||
)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(false)
|
||||
expect(wrapper.find('#import-start').isVisible()).to.equal(true)
|
||||
expect(wrapper.vm.db.addTableFromCsv.getCall(0).args[0]).to.equal('foo') // table name
|
||||
@@ -1246,7 +1406,10 @@ describe('CsvJsonImport.vue - ndjson', () => {
|
||||
await resolveImport()
|
||||
await wrapper.vm.db.addTableFromCsv.returnValues[0]
|
||||
expect(
|
||||
wrapper.findComponent({ name: 'logs' }).findComponent({ name: 'LoadingIndicator' }).exists()
|
||||
wrapper
|
||||
.findComponent({ name: 'logs' })
|
||||
.findComponent({ name: 'LoadingIndicator' })
|
||||
.exists()
|
||||
).to.equal(false)
|
||||
})
|
||||
|
||||
@@ -1291,12 +1454,16 @@ describe('CsvJsonImport.vue - ndjson', () => {
|
||||
// Import success in the logs
|
||||
const logs = wrapper.findComponent({ name: 'logs' }).findAll('.msg')
|
||||
expect(logs).to.have.lengthOf(3)
|
||||
expect(logs[2].text()).to.contain('Importing JSON into a SQLite database is completed in')
|
||||
expect(logs[2].text()).to.contain(
|
||||
'Importing JSON into a SQLite database is completed in'
|
||||
)
|
||||
|
||||
// All the dialog controls are enabled
|
||||
expect(wrapper.find('#import-cancel').element.disabled).to.equal(false)
|
||||
expect(wrapper.find('#import-finish').element.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(false)
|
||||
expect(wrapper.findComponent({ name: 'close-icon' }).vm.disabled).to.equal(
|
||||
false
|
||||
)
|
||||
expect(wrapper.find('#import-finish').isVisible()).to.equal(true)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -39,7 +39,7 @@ describe('DelimiterSelector', async () => {
|
||||
const wrapper = shallowMount(DelimiterSelector, {
|
||||
props: {
|
||||
modelValue: ',',
|
||||
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e })
|
||||
'onUpdate:modelValue': e => wrapper.setProps({ modelValue: e })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -51,7 +51,7 @@ describe('DelimiterSelector', async () => {
|
||||
const wrapper = mount(DelimiterSelector, {
|
||||
props: {
|
||||
modelValue: '|',
|
||||
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e })
|
||||
'onUpdate:modelValue': e => wrapper.setProps({ modelValue: e })
|
||||
},
|
||||
attachTo: document.body
|
||||
})
|
||||
@@ -68,7 +68,7 @@ describe('DelimiterSelector', async () => {
|
||||
const wrapper = mount(DelimiterSelector, {
|
||||
props: {
|
||||
modelValue: '|',
|
||||
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e })
|
||||
'onUpdate:modelValue': e => wrapper.setProps({ modelValue: e })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -96,7 +96,7 @@ describe('DelimiterSelector', async () => {
|
||||
props: {
|
||||
modelValue: '|',
|
||||
disabled: true,
|
||||
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e })
|
||||
'onUpdate:modelValue': e => wrapper.setProps({ modelValue: e })
|
||||
},
|
||||
attachTo: document.body
|
||||
})
|
||||
|
||||
@@ -7,7 +7,6 @@ import fu from '@/lib/utils/fileIo'
|
||||
import database from '@/lib/database'
|
||||
import { nextTick } from 'vue'
|
||||
|
||||
|
||||
describe('DbUploader.vue', () => {
|
||||
let state = {}
|
||||
let mutations = {}
|
||||
|
||||
@@ -19,7 +19,8 @@ describe('LoadingIndicator.vue', () => {
|
||||
})
|
||||
// The lendth of circle in the component is 50.24. If progress is 50% then resulting arc
|
||||
// should be 25.12
|
||||
expect(wrapper.find('.loader-svg.front').element.style.strokeDasharray)
|
||||
.to.equal('25.12px, 25.12px')
|
||||
expect(
|
||||
wrapper.find('.loader-svg.front').element.style.strokeDasharray
|
||||
).to.equal('25.12px, 25.12px')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -31,8 +31,9 @@ describe('Logs.vue', () => {
|
||||
})
|
||||
await nextTick()
|
||||
const height = wrapper.find('.logs-container').element.scrollHeight
|
||||
expect(wrapper.find('.logs-container').element.scrollTop)
|
||||
.to.equal(height - viewHeight)
|
||||
expect(wrapper.find('.logs-container').element.scrollTop).to.equal(
|
||||
height - viewHeight
|
||||
)
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
@@ -58,8 +59,9 @@ describe('Logs.vue', () => {
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
const height = wrapper.find('.logs-container').element.scrollHeight
|
||||
expect(wrapper.find('.logs-container').element.scrollTop)
|
||||
.to.equal(height - viewHeight)
|
||||
expect(wrapper.find('.logs-container').element.scrollTop).to.equal(
|
||||
height - viewHeight
|
||||
)
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
|
||||
@@ -18,8 +18,12 @@ describe('Splitpanes.vue', () => {
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('.splitpanes-pane')).to.have.lengthOf(2)
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('60%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('40%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
|
||||
'60%'
|
||||
)
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
|
||||
'40%'
|
||||
)
|
||||
})
|
||||
|
||||
it('renders correctly - horizontal', () => {
|
||||
@@ -37,8 +41,12 @@ describe('Splitpanes.vue', () => {
|
||||
})
|
||||
|
||||
expect(wrapper.findAll('.splitpanes-pane')).to.have.lengthOf(2)
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.height).to.equal('60%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.height).to.equal('40%')
|
||||
expect(
|
||||
wrapper.findAll('.splitpanes-pane')[0].element.style.height
|
||||
).to.equal('60%')
|
||||
expect(
|
||||
wrapper.findAll('.splitpanes-pane')[1].element.style.height
|
||||
).to.equal('40%')
|
||||
})
|
||||
|
||||
it('toggles correctly - no maximized initially', async () => {
|
||||
@@ -55,20 +63,36 @@ describe('Splitpanes.vue', () => {
|
||||
})
|
||||
|
||||
await wrapper.find('.toggle-btn').trigger('click')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('0%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('100%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
|
||||
'0%'
|
||||
)
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
|
||||
'100%'
|
||||
)
|
||||
|
||||
await wrapper.find('.toggle-btn').trigger('click')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('60%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('40%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
|
||||
'60%'
|
||||
)
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
|
||||
'40%'
|
||||
)
|
||||
|
||||
await wrapper.findAll('.toggle-btn')[1].trigger('click')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('100%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('0%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
|
||||
'100%'
|
||||
)
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
|
||||
'0%'
|
||||
)
|
||||
|
||||
await wrapper.find('.toggle-btn').trigger('click')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('60%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('40%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
|
||||
'60%'
|
||||
)
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
|
||||
'40%'
|
||||
)
|
||||
})
|
||||
|
||||
it('toggles correctly - with maximized initially', async () => {
|
||||
@@ -86,20 +110,36 @@ describe('Splitpanes.vue', () => {
|
||||
})
|
||||
|
||||
await wrapper.find('.toggle-btn').trigger('click')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('20%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('80%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
|
||||
'20%'
|
||||
)
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
|
||||
'80%'
|
||||
)
|
||||
|
||||
await wrapper.findAll('.toggle-btn')[0].trigger('click')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('0%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('100%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
|
||||
'0%'
|
||||
)
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
|
||||
'100%'
|
||||
)
|
||||
|
||||
await wrapper.find('.toggle-btn').trigger('click')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('20%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('80%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
|
||||
'20%'
|
||||
)
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
|
||||
'80%'
|
||||
)
|
||||
|
||||
await wrapper.findAll('.toggle-btn')[1].trigger('click')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('100%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('0%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
|
||||
'100%'
|
||||
)
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
|
||||
'0%'
|
||||
)
|
||||
|
||||
wrapper = shallowMount(Splitpanes, {
|
||||
slots: {
|
||||
@@ -113,20 +153,36 @@ describe('Splitpanes.vue', () => {
|
||||
})
|
||||
|
||||
await wrapper.find('.toggle-btn').trigger('click')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('50%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('50%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
|
||||
'50%'
|
||||
)
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
|
||||
'50%'
|
||||
)
|
||||
|
||||
await wrapper.findAll('.toggle-btn')[0].trigger('click')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('0%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('100%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
|
||||
'0%'
|
||||
)
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
|
||||
'100%'
|
||||
)
|
||||
|
||||
await wrapper.find('.toggle-btn').trigger('click')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('50%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('50%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
|
||||
'50%'
|
||||
)
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
|
||||
'50%'
|
||||
)
|
||||
|
||||
await wrapper.findAll('.toggle-btn')[1].trigger('click')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('100%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal('0%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
|
||||
'100%'
|
||||
)
|
||||
expect(wrapper.findAll('.splitpanes-pane')[1].element.style.width).to.equal(
|
||||
'0%'
|
||||
)
|
||||
})
|
||||
|
||||
it('drag - vertical', async () => {
|
||||
@@ -151,13 +207,17 @@ describe('Splitpanes.vue', () => {
|
||||
parent.style.height = '500px'
|
||||
|
||||
await wrapper.find('.splitpanes-splitter').trigger('mousedown')
|
||||
document.dispatchEvent(new MouseEvent('mousemove', {
|
||||
document.dispatchEvent(
|
||||
new MouseEvent('mousemove', {
|
||||
clientX: 300,
|
||||
clientY: 80
|
||||
}))
|
||||
})
|
||||
)
|
||||
document.dispatchEvent(new MouseEvent('mouseup'))
|
||||
await nextTick()
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('50%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
|
||||
'50%'
|
||||
)
|
||||
wrapper.unmount()
|
||||
root.remove()
|
||||
})
|
||||
@@ -186,14 +246,18 @@ describe('Splitpanes.vue', () => {
|
||||
parent.style.height = '500px'
|
||||
|
||||
await wrapper.find('.splitpanes-splitter').trigger('mousedown')
|
||||
document.dispatchEvent(new MouseEvent('mousemove', {
|
||||
document.dispatchEvent(
|
||||
new MouseEvent('mousemove', {
|
||||
clientX: 10,
|
||||
clientY: 250
|
||||
}))
|
||||
})
|
||||
)
|
||||
document.dispatchEvent(new MouseEvent('mouseup'))
|
||||
await nextTick()
|
||||
await nextTick()
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.height).to.equal('50%')
|
||||
expect(
|
||||
wrapper.findAll('.splitpanes-pane')[0].element.style.height
|
||||
).to.equal('50%')
|
||||
wrapper.unmount()
|
||||
root.remove()
|
||||
})
|
||||
@@ -225,16 +289,20 @@ describe('Splitpanes.vue', () => {
|
||||
await wrapper.find('.splitpanes-splitter').trigger('touchstart')
|
||||
const event = new TouchEvent('touchmove')
|
||||
Object.defineProperty(event, 'touches', {
|
||||
value: [{
|
||||
value: [
|
||||
{
|
||||
clientX: 10,
|
||||
clientY: 250
|
||||
}],
|
||||
}
|
||||
],
|
||||
writable: true
|
||||
})
|
||||
document.dispatchEvent(event)
|
||||
document.dispatchEvent(new MouseEvent('touchend'))
|
||||
await nextTick()
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.height).to.equal('50%')
|
||||
expect(
|
||||
wrapper.findAll('.splitpanes-pane')[0].element.style.height
|
||||
).to.equal('50%')
|
||||
wrapper.unmount()
|
||||
root.remove()
|
||||
delete window.ontouchstart
|
||||
@@ -265,16 +333,20 @@ describe('Splitpanes.vue', () => {
|
||||
await wrapper.find('.splitpanes-splitter').trigger('touchstart')
|
||||
const event = new TouchEvent('touchmove')
|
||||
Object.defineProperty(event, 'touches', {
|
||||
value: [{
|
||||
value: [
|
||||
{
|
||||
clientX: 300,
|
||||
clientY: 80
|
||||
}],
|
||||
}
|
||||
],
|
||||
writable: true
|
||||
})
|
||||
document.dispatchEvent(event)
|
||||
document.dispatchEvent(new MouseEvent('touchend'))
|
||||
await nextTick()
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal('50%')
|
||||
expect(wrapper.findAll('.splitpanes-pane')[0].element.style.width).to.equal(
|
||||
'50%'
|
||||
)
|
||||
wrapper.unmount()
|
||||
root.remove()
|
||||
delete window.ontouchstart
|
||||
|
||||
@@ -38,7 +38,11 @@ describe('splitter.js', () => {
|
||||
|
||||
document.body.appendChild(container)
|
||||
|
||||
const dragPercentage = splitter.getCurrentDragPercentage(event, container, isHorisontal)
|
||||
const dragPercentage = splitter.getCurrentDragPercentage(
|
||||
event,
|
||||
container,
|
||||
isHorisontal
|
||||
)
|
||||
expect(dragPercentage).to.equal(50)
|
||||
})
|
||||
|
||||
@@ -53,7 +57,11 @@ describe('splitter.js', () => {
|
||||
|
||||
document.body.appendChild(container)
|
||||
|
||||
const dragPercentage = splitter.getCurrentDragPercentage(event, container, isHorisontal)
|
||||
const dragPercentage = splitter.getCurrentDragPercentage(
|
||||
event,
|
||||
container,
|
||||
isHorisontal
|
||||
)
|
||||
expect(dragPercentage).to.equal(25)
|
||||
})
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('Pager.vue', () => {
|
||||
const wrapper = mount(Pager, {
|
||||
props: {
|
||||
pageCount: 5,
|
||||
'onUpdate:modelValue': (e) => wrapper.setProps({ modelValue: e })
|
||||
'onUpdate:modelValue': e => wrapper.setProps({ modelValue: e })
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -97,7 +97,8 @@ describe('chartHelper.js', () => {
|
||||
expect(doc.children[0].src).to.includes('plotly-latest.js')
|
||||
expect(doc.children[1].id).to.have.lengthOf(21)
|
||||
expect(doc.children[2].innerHTML).to.includes(doc.children[1].id)
|
||||
expect(doc.children[2].innerHTML)
|
||||
.to.includes('Plotly.newPlot(el, "plotly data", "plotly layout"')
|
||||
expect(doc.children[2].innerHTML).to.includes(
|
||||
'Plotly.newPlot(el, "plotly data", "plotly layout"'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -62,7 +62,9 @@ describe('_sql.js', () => {
|
||||
const data = tempDb.export()
|
||||
const sql = await Sql.build()
|
||||
sql.open(data)
|
||||
expect(() => { sql.exec() }).to.throw('exec: Missing query string')
|
||||
expect(() => {
|
||||
sql.exec()
|
||||
}).to.throw('exec: Missing query string')
|
||||
})
|
||||
|
||||
it('imports', async () => {
|
||||
|
||||
@@ -19,8 +19,9 @@ describe('_statements.js', () => {
|
||||
|
||||
it('getInsertStmt', () => {
|
||||
const columns = ['id', 'name']
|
||||
expect(stmts.getInsertStmt('foo', columns))
|
||||
.to.equal('INSERT INTO "foo" ("id", "name") VALUES (?, ?);')
|
||||
expect(stmts.getInsertStmt('foo', columns)).to.equal(
|
||||
'INSERT INTO "foo" ("id", "name") VALUES (?, ?);'
|
||||
)
|
||||
})
|
||||
|
||||
it('getCreateStatement', () => {
|
||||
|
||||
@@ -71,10 +71,12 @@ describe('database.js', () => {
|
||||
type: 'INTEGER'
|
||||
})
|
||||
|
||||
expect(schema[1].columns).to.eql([{
|
||||
expect(schema[1].columns).to.eql([
|
||||
{
|
||||
name: 'amount',
|
||||
type: 'INTEGER'
|
||||
}])
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('creates empty db with name database', async () => {
|
||||
@@ -114,7 +116,9 @@ describe('database.js', () => {
|
||||
buffer.name = 'foo.sqlite'
|
||||
|
||||
await db.loadDb(buffer)
|
||||
const result = await db.execute('SELECT * from test limit 1; SELECT * from test;')
|
||||
const result = await db.execute(
|
||||
'SELECT * from test limit 1; SELECT * from test;'
|
||||
)
|
||||
expect(result.values).to.eql({
|
||||
id: [1, 2],
|
||||
name: ['Harry Potter', 'Draco Malfoy'],
|
||||
@@ -141,7 +145,9 @@ describe('database.js', () => {
|
||||
const buffer = new Blob([data])
|
||||
buffer.name = 'foo.sqlite'
|
||||
await db.loadDb(buffer)
|
||||
await expect(db.execute('SELECT * from foo')).to.be.rejectedWith(/^no such table: foo$/)
|
||||
await expect(db.execute('SELECT * from foo')).to.be.rejectedWith(
|
||||
/^no such table: foo$/
|
||||
)
|
||||
})
|
||||
|
||||
it('adds table from csv', async () => {
|
||||
@@ -186,37 +192,44 @@ describe('database.js', () => {
|
||||
}
|
||||
const progressHandler = sinon.stub()
|
||||
const progressCounterId = db.createProgressCounter(progressHandler)
|
||||
await expect(db.addTableFromCsv('foo', data, progressCounterId)).to.be.rejected
|
||||
await expect(db.addTableFromCsv('foo', data, progressCounterId)).to.be
|
||||
.rejected
|
||||
})
|
||||
|
||||
it('progressCounters', () => {
|
||||
const firstHandler = sinon.stub()
|
||||
const firstId = db.createProgressCounter(firstHandler)
|
||||
db.worker.dispatchEvent(new MessageEvent('message', {
|
||||
db.worker.dispatchEvent(
|
||||
new MessageEvent('message', {
|
||||
data: {
|
||||
progress: 50,
|
||||
id: firstId
|
||||
}
|
||||
}))
|
||||
})
|
||||
)
|
||||
expect(firstHandler.calledOnceWith(50)).to.equal(true)
|
||||
|
||||
const secondHandler = sinon.stub()
|
||||
const secondId = db.createProgressCounter(secondHandler)
|
||||
db.worker.dispatchEvent(new MessageEvent('message', {
|
||||
db.worker.dispatchEvent(
|
||||
new MessageEvent('message', {
|
||||
data: {
|
||||
progress: 70,
|
||||
id: secondId
|
||||
}
|
||||
}))
|
||||
})
|
||||
)
|
||||
expect(firstId).to.not.equals(secondId)
|
||||
expect(secondHandler.calledOnceWith(70)).to.equal(true)
|
||||
|
||||
db.worker.dispatchEvent(new MessageEvent('message', {
|
||||
db.worker.dispatchEvent(
|
||||
new MessageEvent('message', {
|
||||
data: {
|
||||
progress: 80,
|
||||
id: firstId
|
||||
}
|
||||
}))
|
||||
})
|
||||
)
|
||||
expect(firstHandler.calledTwice).to.equal(true)
|
||||
expect(firstHandler.secondCall.calledWith(80)).to.equal(true)
|
||||
|
||||
@@ -268,12 +281,17 @@ describe('database.js', () => {
|
||||
|
||||
it('validateTableName', async () => {
|
||||
await db.execute('CREATE TABLE foo(id)')
|
||||
await expect(db.validateTableName('foo')).to.be.rejectedWith('table "foo" already exists')
|
||||
await expect(db.validateTableName('1foo'))
|
||||
.to.be.rejectedWith("Table name can't start with a digit")
|
||||
await expect(db.validateTableName('foo(05.08.2020)'))
|
||||
.to.be.rejectedWith('Table name can contain only letters, digits and underscores')
|
||||
await expect(db.validateTableName('sqlite_foo'))
|
||||
.to.be.rejectedWith("Table name can't start with sqlite_")
|
||||
await expect(db.validateTableName('foo')).to.be.rejectedWith(
|
||||
'table "foo" already exists'
|
||||
)
|
||||
await expect(db.validateTableName('1foo')).to.be.rejectedWith(
|
||||
"Table name can't start with a digit"
|
||||
)
|
||||
await expect(db.validateTableName('foo(05.08.2020)')).to.be.rejectedWith(
|
||||
'Table name can contain only letters, digits and underscores'
|
||||
)
|
||||
await expect(db.validateTableName('sqlite_foo')).to.be.rejectedWith(
|
||||
"Table name can't start with sqlite_"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -80,7 +80,6 @@ describe('SQLite extensions', function () {
|
||||
'sqrt(square(16))': [16],
|
||||
'ceil(-1.95) + ceil(1.95)': [1],
|
||||
'floor(-1.95) + floor(1.95)': [-1]
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
@@ -452,7 +451,15 @@ describe('SQLite extensions', function () {
|
||||
FROM dataset;
|
||||
`)
|
||||
expect(actual.values).to.eql({
|
||||
xx: [1], xy: [0], xz: [1], yx: [0], yy: [1], yz: [1], zx: [1], zy: [1], zz: [1]
|
||||
xx: [1],
|
||||
xy: [0],
|
||||
xz: [1],
|
||||
yx: [0],
|
||||
yy: [1],
|
||||
yz: [1],
|
||||
zx: [1],
|
||||
zy: [1],
|
||||
zz: [1]
|
||||
})
|
||||
})
|
||||
|
||||
@@ -475,7 +482,10 @@ describe('SQLite extensions', function () {
|
||||
|
||||
SELECT lua_inline(1), lua_full(1) - 1 < 0.000001;
|
||||
`)
|
||||
expect(actual.values).to.eql({ 'lua_inline(1)': [2], 'lua_full(1) - 1 < 0.000001': [1] })
|
||||
expect(actual.values).to.eql({
|
||||
'lua_inline(1)': [2],
|
||||
'lua_full(1) - 1 < 0.000001': [1]
|
||||
})
|
||||
})
|
||||
|
||||
it('supports aggregate Lua functions', async function () {
|
||||
@@ -534,6 +544,9 @@ describe('SQLite extensions', function () {
|
||||
|
||||
SELECT * FROM lua_match('%w+', 'hello world from Lua');
|
||||
`)
|
||||
expect(actual.values).to.eql({ idx: [1, 2, 3, 4], elm: ['hello', 'world', 'from', 'Lua'] })
|
||||
expect(actual.values).to.eql({
|
||||
idx: [1, 2, 3, 4],
|
||||
elm: ['hello', 'world', 'from', 'Lua']
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -19,7 +19,9 @@ describe('storedInquiries.js', () => {
|
||||
})
|
||||
|
||||
it('getStoredInquiries migrate and returns inquiries of v1', () => {
|
||||
localStorage.setItem('myQueries', JSON.stringify([
|
||||
localStorage.setItem(
|
||||
'myQueries',
|
||||
JSON.stringify([
|
||||
{
|
||||
id: '123',
|
||||
name: 'foo',
|
||||
@@ -32,7 +34,8 @@ describe('storedInquiries.js', () => {
|
||||
query: 'SELECT * FROM bar',
|
||||
chart: { here_are: 'bar chart settings' }
|
||||
}
|
||||
]))
|
||||
])
|
||||
)
|
||||
const inquiries = storedInquiries.getStoredInquiries()
|
||||
expect(inquiries).to.eql([
|
||||
{
|
||||
@@ -53,10 +56,7 @@ describe('storedInquiries.js', () => {
|
||||
})
|
||||
|
||||
it('updateStorage and getStoredInquiries', () => {
|
||||
const data = [
|
||||
{ id: 1 },
|
||||
{ id: 2 }
|
||||
]
|
||||
const data = [{ id: 1 }, { id: 2 }]
|
||||
storedInquiries.updateStorage(data)
|
||||
const inquiries = storedInquiries.getStoredInquiries()
|
||||
expect(inquiries).to.eql(data)
|
||||
@@ -77,7 +77,9 @@ describe('storedInquiries.js', () => {
|
||||
|
||||
const copy = storedInquiries.duplicateInquiry(base)
|
||||
expect(copy).to.have.property('id').which.not.equal(base.id)
|
||||
expect(copy).to.have.property('name').which.equal(base.name + ' Copy')
|
||||
expect(copy)
|
||||
.to.have.property('name')
|
||||
.which.equal(base.name + ' Copy')
|
||||
expect(copy).to.have.property('query').which.equal(base.query)
|
||||
expect(copy).to.have.property('viewType').which.equal(base.viewType)
|
||||
expect(copy).to.have.property('viewOptions').which.eql(base.viewOptions)
|
||||
@@ -197,14 +199,16 @@ describe('storedInquiries.js', () => {
|
||||
`
|
||||
|
||||
const inquiry = storedInquiries.deserialiseInquiries(str)
|
||||
expect(inquiry).to.eql([{
|
||||
expect(inquiry).to.eql([
|
||||
{
|
||||
id: 1,
|
||||
name: 'foo',
|
||||
query: 'select * from foo',
|
||||
viewType: 'chart',
|
||||
viewOptions: [],
|
||||
createdAt: '2020-11-03T14:17:49.524Z'
|
||||
}])
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('deserialiseInquiries generates new id to avoid duplication', () => {
|
||||
@@ -256,14 +260,16 @@ describe('storedInquiries.js', () => {
|
||||
sinon.stub(fu, 'importFile').returns(Promise.resolve(str))
|
||||
const inquiries = await storedInquiries.importInquiries()
|
||||
|
||||
expect(inquiries).to.eql([{
|
||||
expect(inquiries).to.eql([
|
||||
{
|
||||
id: 1,
|
||||
name: 'foo',
|
||||
query: 'select * from foo',
|
||||
viewType: 'chart',
|
||||
viewOptions: [],
|
||||
createdAt: '2020-11-03T14:17:49.524Z'
|
||||
}])
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('importInquiries', async () => {
|
||||
@@ -281,14 +287,16 @@ describe('storedInquiries.js', () => {
|
||||
sinon.stub(fu, 'importFile').returns(Promise.resolve(str))
|
||||
const inquiries = await storedInquiries.importInquiries()
|
||||
|
||||
expect(inquiries).to.eql([{
|
||||
expect(inquiries).to.eql([
|
||||
{
|
||||
id: 1,
|
||||
name: 'foo',
|
||||
query: 'select * from foo',
|
||||
viewType: 'chart',
|
||||
viewOptions: [],
|
||||
createdAt: '2020-11-03T14:17:49.524Z'
|
||||
}])
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('readPredefinedInquiries old', async () => {
|
||||
@@ -312,7 +320,8 @@ describe('storedInquiries.js', () => {
|
||||
viewType: 'chart',
|
||||
viewOptions: [],
|
||||
createdAt: '2020-11-03T14:17:49.524Z'
|
||||
}])
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('readPredefinedInquiries', async () => {
|
||||
@@ -340,6 +349,7 @@ describe('storedInquiries.js', () => {
|
||||
viewType: 'chart',
|
||||
viewOptions: [],
|
||||
createdAt: '2020-11-03T14:17:49.524Z'
|
||||
}])
|
||||
}
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user