mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-06 10:08:52 +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']
|
||||
}
|
||||
}
|
||||
|
||||
60
.github/workflows/main.yml
vendored
60
.github/workflows/main.yml
vendored
@@ -3,43 +3,43 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: Create release
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
|
||||
- name: Update npm
|
||||
run: npm install -g npm@8
|
||||
- name: Update npm
|
||||
run: npm install -g npm@8
|
||||
|
||||
- name: npm install and build
|
||||
run: |
|
||||
npm install
|
||||
npm run build
|
||||
- name: npm install and build
|
||||
run: |
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
- name: Create archives
|
||||
run: |
|
||||
cd dist
|
||||
zip -9 -r ../dist.zip . -x "js/*.map" -x "/*.map"
|
||||
zip -9 -r ../dist_map.zip .
|
||||
- name: Create archives
|
||||
run: |
|
||||
cd dist
|
||||
zip -9 -r ../dist.zip . -x "js/*.map" -x "/*.map"
|
||||
zip -9 -r ../dist_map.zip .
|
||||
|
||||
- name: Create Release Notes
|
||||
run: |
|
||||
npm install github-release-notes@0.16.0 -g
|
||||
gren changelog --generate --config="/.github/workflows/config.grenrc.js"
|
||||
env:
|
||||
GREN_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Create Release Notes
|
||||
run: |
|
||||
npm install github-release-notes@0.16.0 -g
|
||||
gren changelog --generate --config="/.github/workflows/config.grenrc.js"
|
||||
env:
|
||||
GREN_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "dist.zip,dist_map.zip"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
bodyFile: "CHANGELOG.md"
|
||||
- name: Create release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: 'dist.zip,dist_map.zip'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
bodyFile: 'CHANGELOG.md'
|
||||
|
||||
40
.github/workflows/test.yml
vendored
40
.github/workflows/test.yml
vendored
@@ -3,35 +3,35 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'master'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'master'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
- name: Install browsers
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y chromium-browser firefox
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16.x
|
||||
- name: Install browsers
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y chromium-browser firefox
|
||||
|
||||
- name: Update npm
|
||||
run: npm install -g npm@8
|
||||
- name: Update npm
|
||||
run: npm install -g npm@8
|
||||
|
||||
- name: Install the project
|
||||
run: npm install
|
||||
- name: Install the project
|
||||
run: npm install
|
||||
|
||||
- name: Run lint
|
||||
run: npm run lint -- --no-fix
|
||||
- name: Run lint
|
||||
run: npm run lint -- --no-fix
|
||||
|
||||
- name: Run karma tests
|
||||
run: npm run test
|
||||
- name: Run karma tests
|
||||
run: npm run test
|
||||
|
||||
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
@@ -5,9 +5,10 @@
|
||||
# sqliteviz
|
||||
|
||||
Sqliteviz is a single-page offline-first PWA for fully client-side visualisation
|
||||
of SQLite databases, CSV, JSON or NDJSON files.
|
||||
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']
|
||||
}
|
||||
|
||||
44
index.html
44
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 {
|
||||
@@ -16,7 +16,7 @@
|
||||
top: 0;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
|
||||
#sqliteviz-loading-text {
|
||||
display: block;
|
||||
position: absolute;
|
||||
@@ -27,7 +27,7 @@
|
||||
font-family: sans-serif;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
|
||||
#sqliteviz-loading-wrapper svg {
|
||||
display: block;
|
||||
position: absolute;
|
||||
@@ -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
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ a custom version of [sql.js][1]. It allows sqliteviz to have more recent
|
||||
version of SQLite build with a number of useful extensions.
|
||||
|
||||
`Makefile` from [sql.js][1] is rewritten as more comprehensible `configure.py`
|
||||
and `build.py` Python scripts that run in `emscripten/emsdk` Docker container.
|
||||
and `build.py` Python scripts that run in `emscripten/emsdk` Docker container.
|
||||
|
||||
## Extension
|
||||
|
||||
@@ -84,15 +84,15 @@ described in [this message from SQLite Forum][12]:
|
||||
> amalgamation code and the extensions would thereafter be automatically
|
||||
> initialized on each connection.
|
||||
|
||||
[1]: https://github.com/sql-js/sql.js
|
||||
[2]: https://sqlite.org/amalgamation.html
|
||||
[3]: https://sqlite.org/src/dir?ci=trunk&name=ext/misc
|
||||
[4]: https://sqlite.org/fts5.html
|
||||
[5]: https://github.com/jakethaw/pivot_vtab
|
||||
[6]: https://sqlite.org/series.html
|
||||
[7]: https://sqlite.org/src/file/ext/misc/series.c
|
||||
[8]: https://sqlite.org/src/file/ext/misc/closure.c
|
||||
[9]: https://sqlite.org/src/file/ext/misc/uuid.c
|
||||
[1]: https://github.com/sql-js/sql.js
|
||||
[2]: https://sqlite.org/amalgamation.html
|
||||
[3]: https://sqlite.org/src/dir?ci=trunk&name=ext/misc
|
||||
[4]: https://sqlite.org/fts5.html
|
||||
[5]: https://github.com/jakethaw/pivot_vtab
|
||||
[6]: https://sqlite.org/series.html
|
||||
[7]: https://sqlite.org/src/file/ext/misc/series.c
|
||||
[8]: https://sqlite.org/src/file/ext/misc/closure.c
|
||||
[9]: https://sqlite.org/src/file/ext/misc/uuid.c
|
||||
[10]: https://sqlite.org/src/file/ext/misc/regexp.c
|
||||
[11]: https://charlesleifer.com/blog/querying-tree-structures-in-sqlite-using-python-and-the-transitive-closure-extension/
|
||||
[12]: https://sqlite.org/forum/forumpost/6ad7d4f4bebe5e06?raw
|
||||
|
||||
@@ -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,
|
||||
@@ -33,11 +39,11 @@ module.exports = function (config) {
|
||||
logLevel: config.LOG_INFO,
|
||||
browserConsoleLogOptions: { terminal: true, level: config.LOG_INFO },
|
||||
|
||||
preprocessors: { 'suite.js': [ 'webpack' ] },
|
||||
preprocessors: { 'suite.js': ['webpack'] },
|
||||
webpack: {
|
||||
mode: 'development',
|
||||
module: {
|
||||
noParse: [ __dirname + '/node_modules/benchmark/benchmark.js' ]
|
||||
noParse: [__dirname + '/node_modules/benchmark/benchmark.js']
|
||||
},
|
||||
node: { fs: 'empty' }
|
||||
},
|
||||
@@ -47,6 +53,5 @@ module.exports = function (config) {
|
||||
},
|
||||
|
||||
jsonToFileReporter: { outputPath: '.', fileName: 'suite-result.json' }
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "sqlite-webassembly-microbenchmark",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/core" : "^7.14.8",
|
||||
"@babel/core": "^7.14.8",
|
||||
"babel-loader": "^8.2.2",
|
||||
"benchmark": "^2.1.4",
|
||||
"lodash": "^4.17.4",
|
||||
@@ -11,7 +11,7 @@
|
||||
"karma": "^6.3.4",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-firefox-launcher": "^2.1.1",
|
||||
"karma-json-to-file-reporter" : "^1.0.1",
|
||||
"karma-json-to-file-reporter": "^1.0.1",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-webpack": "^4.0.2",
|
||||
"webpack": "^4.46.0",
|
||||
|
||||
@@ -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
|
||||
@@ -18,7 +17,7 @@ describe('SQLite build benchmark', function () {
|
||||
importToTable(selectDb, parsedCsv)
|
||||
})
|
||||
|
||||
function benchmarkImport () {
|
||||
function benchmarkImport() {
|
||||
const db = new sqlModule.Database()
|
||||
try {
|
||||
importToTable(db, parsedCsv)
|
||||
@@ -27,7 +26,7 @@ describe('SQLite build benchmark', function () {
|
||||
}
|
||||
}
|
||||
|
||||
function benchmarkSelect () {
|
||||
function benchmarkSelect() {
|
||||
const result = selectDb.exec(`
|
||||
SELECT county, AVG(avg_depth) avg_depth_c
|
||||
FROM (
|
||||
@@ -50,11 +49,9 @@ describe('SQLite build benchmark', function () {
|
||||
suite.add('select', { initCount: 3, minSamples: 50, fn: benchmarkSelect })
|
||||
await run(suite)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
function importToTable (db, parsedCsv, chunkSize = 1024) {
|
||||
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) => {
|
||||
@@ -89,11 +85,11 @@ function parseCsv(url) {
|
||||
})
|
||||
}
|
||||
|
||||
function chunkArray (arr, size) {
|
||||
function chunkArray(arr, size) {
|
||||
return arr.reduce(function (result, value, index) {
|
||||
const chunkIndex = Math.floor(index / size)
|
||||
|
||||
if(!(chunkIndex in result)) {
|
||||
if (!(chunkIndex in result)) {
|
||||
result[chunkIndex] = []
|
||||
}
|
||||
result[chunkIndex].push(value)
|
||||
@@ -102,8 +98,7 @@ function chunkArray (arr, size) {
|
||||
}, [])
|
||||
}
|
||||
|
||||
|
||||
function createSuite () {
|
||||
function createSuite() {
|
||||
// Combined workaround from:
|
||||
// - https://github.com/bestiejs/benchmark.js/issues/106
|
||||
// - https://github.com/bestiejs/benchmark.js/issues/237
|
||||
@@ -117,24 +112,26 @@ function createSuite () {
|
||||
return new bm.Suite()
|
||||
}
|
||||
|
||||
function run (suite) {
|
||||
function run(suite) {
|
||||
const suiteResult = new PromiseWrapper()
|
||||
suite
|
||||
.on('cycle', function (event) {
|
||||
console.info(String(event.target))
|
||||
})
|
||||
.on('complete', function () {
|
||||
console.log(JSON.stringify({
|
||||
browser: useragent(navigator.userAgent).browser,
|
||||
result: this.filter('successful')
|
||||
}))
|
||||
console.log(
|
||||
JSON.stringify({
|
||||
browser: useragent(navigator.userAgent).browser,
|
||||
result: this.filter('successful')
|
||||
})
|
||||
)
|
||||
suiteResult.resolve()
|
||||
})
|
||||
.on('error', function (event) {
|
||||
console.error('Benchmark failed', String(event.target))
|
||||
suiteResult.reject()
|
||||
})
|
||||
.run({async: true})
|
||||
.run({ async: true })
|
||||
|
||||
return suiteResult.promise
|
||||
}
|
||||
|
||||
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",
|
||||
|
||||
@@ -1 +1 @@
|
||||
[]
|
||||
[]
|
||||
|
||||
@@ -27,4 +27,4 @@
|
||||
"name": "sqliteviz",
|
||||
"short_name": "sqliteviz",
|
||||
"start_url": "index.html"
|
||||
}
|
||||
}
|
||||
|
||||
36
src/App.vue
36
src/App.vue
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div id="app">
|
||||
<router-view/>
|
||||
<modals-container/>
|
||||
<router-view />
|
||||
<modals-container />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -11,18 +11,18 @@ import { ModalsContainer } from 'vue-final-modal'
|
||||
|
||||
export default {
|
||||
components: { ModalsContainer },
|
||||
created () {
|
||||
created() {
|
||||
this.$store.commit('setInquiries', storedInquiries.getStoredInquiries())
|
||||
},
|
||||
computed: {
|
||||
inquiries () {
|
||||
inquiries() {
|
||||
return this.$store.state.inquiries
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
inquiries: {
|
||||
deep: true,
|
||||
handler () {
|
||||
handler() {
|
||||
storedInquiries.updateStorage(this.inquiries)
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
color: var(--color-text-base);
|
||||
font-size: 13px;
|
||||
padding: 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@
|
||||
border-radius: var(--border-radius-medium-2);
|
||||
}
|
||||
|
||||
.sqliteviz-select .multiselect__option .no-results {
|
||||
.sqliteviz-select .multiselect__option .no-results {
|
||||
color: var(--color-text-light-2);
|
||||
}
|
||||
|
||||
@@ -133,4 +133,4 @@
|
||||
|
||||
.sqliteviz-select.multiselect--disabled .multiselect__select {
|
||||
background: unset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
|
||||
/* Track */
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
background: transparent;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
|
||||
/* Handle */
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--color-accent);
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
height: calc(100% - 27px);
|
||||
}
|
||||
|
||||
@supports (-moz-appearance:none) {
|
||||
@supports (-moz-appearance: none) {
|
||||
.header-container {
|
||||
top: 0;
|
||||
padding-left: 6px;
|
||||
@@ -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;
|
||||
@@ -71,7 +72,7 @@ table.sqliteviz-table {
|
||||
}
|
||||
.sqliteviz-table tbody td {
|
||||
font-size: 13px;
|
||||
background-color:white;
|
||||
background-color: white;
|
||||
color: var(--color-text-base);
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
@@ -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,10 +4,10 @@
|
||||
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);
|
||||
white-space: nowrap;
|
||||
z-index: 999;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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-5:#f8f8f9;
|
||||
--color-gray-medium: #A2B1C6;
|
||||
--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-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)
|
||||
}
|
||||
},
|
||||
@@ -52,13 +56,13 @@ export default {
|
||||
}
|
||||
},
|
||||
emits: ['click'],
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
checked: this.init
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick () {
|
||||
onClick() {
|
||||
if (!this.disabled) {
|
||||
this.checked = !this.checked
|
||||
this.$emit('click', this.checked)
|
||||
@@ -86,7 +90,7 @@ export default {
|
||||
}
|
||||
|
||||
img {
|
||||
display: block;
|
||||
display: block;
|
||||
}
|
||||
.label {
|
||||
margin-left: 6px;
|
||||
@@ -106,6 +110,6 @@ img {
|
||||
|
||||
.disabled .unchecked,
|
||||
.disabled .unchecked:hover {
|
||||
background-color: var(--color-bg-light-2);
|
||||
background-color: var(--color-bg-light-2);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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"
|
||||
@@ -19,7 +19,7 @@
|
||||
<div class="name">{{ getSymbolName(modelValue) }}</div>
|
||||
</div>
|
||||
<div class="controls" @click.stop>
|
||||
<clear-icon @click="clear" :disabled="disabled"/>
|
||||
<clear-icon @click="clear" :disabled="disabled" />
|
||||
<drop-down-chevron
|
||||
:disabled="disabled"
|
||||
@click="!disabled && (showOptions = !showOptions)"
|
||||
@@ -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>
|
||||
@@ -49,7 +50,7 @@ export default {
|
||||
props: ['modelValue', 'width', 'disabled'],
|
||||
emits: ['update:modelValue'],
|
||||
components: { DropDownChevron, ClearIcon },
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
showOptions: false,
|
||||
options: [',', '\t', ' ', '|', ';', '\u001F', '\u001E'],
|
||||
@@ -58,7 +59,7 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
inputValue () {
|
||||
inputValue() {
|
||||
if (this.inputValue) {
|
||||
this.filled = true
|
||||
if (this.inputValue !== this.modelValue) {
|
||||
@@ -69,25 +70,25 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
created() {
|
||||
this.inputValue = this.modelValue
|
||||
},
|
||||
methods: {
|
||||
getSymbolName (str) {
|
||||
getSymbolName(str) {
|
||||
if (!str) {
|
||||
return ''
|
||||
}
|
||||
return ascii[str.charCodeAt(0).toString()].name
|
||||
},
|
||||
chooseOption (option) {
|
||||
chooseOption(option) {
|
||||
this.inputValue = option
|
||||
this.showOptions = false
|
||||
},
|
||||
onContainerClick (event) {
|
||||
onContainerClick(event) {
|
||||
this.$refs.delimiterInput.focus()
|
||||
},
|
||||
|
||||
clear () {
|
||||
clear() {
|
||||
if (!this.disabled) {
|
||||
this.inputValue = ''
|
||||
this.$refs.delimiterInput.focus()
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
>
|
||||
<div class="dialog-header">
|
||||
{{ typeName }} import
|
||||
<close-icon @click="cancelImport" :disabled="disableDialog"/>
|
||||
<close-icon @click="cancelImport" :disabled="disableDialog" />
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<text-field
|
||||
@@ -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
|
||||
@@ -130,7 +127,7 @@ export default {
|
||||
dialogName: String
|
||||
},
|
||||
emits: ['cancel', 'finish'],
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
disableDialog: false,
|
||||
disableImport: false,
|
||||
@@ -147,24 +144,24 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isJson () {
|
||||
isJson() {
|
||||
return fIo.isJSON(this.file)
|
||||
},
|
||||
isNdJson () {
|
||||
isNdJson() {
|
||||
return fIo.isNDJSON(this.file)
|
||||
},
|
||||
typeName () {
|
||||
typeName() {
|
||||
return this.isJson || this.isNdJson ? 'JSON' : 'CSV'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isJson () {
|
||||
isJson() {
|
||||
if (this.isJson) {
|
||||
this.delimiter = '\u001E'
|
||||
this.header = false
|
||||
}
|
||||
},
|
||||
isNdJson () {
|
||||
isNdJson() {
|
||||
if (this.isNdJson) {
|
||||
this.delimiter = '\u001E'
|
||||
this.header = false
|
||||
@@ -175,18 +172,17 @@ export default {
|
||||
if (!this.tableName) {
|
||||
return
|
||||
}
|
||||
this.db.validateTableName(this.tableName)
|
||||
.catch(err => {
|
||||
this.tableNameError = err.message + '. Try another table name.'
|
||||
})
|
||||
this.db.validateTableName(this.tableName).catch(err => {
|
||||
this.tableNameError = err.message + '. Try another table name.'
|
||||
})
|
||||
}, 400)
|
||||
},
|
||||
methods: {
|
||||
changeHeaderDisplaying (e) {
|
||||
changeHeaderDisplaying(e) {
|
||||
this.header = e
|
||||
this.preview()
|
||||
},
|
||||
cancelImport () {
|
||||
cancelImport() {
|
||||
if (!this.disableDialog) {
|
||||
if (this.addedTable) {
|
||||
this.db.execute(`DROP TABLE "${this.addedTable}"`)
|
||||
@@ -196,7 +192,7 @@ export default {
|
||||
this.$emit('cancel')
|
||||
}
|
||||
},
|
||||
reset () {
|
||||
reset() {
|
||||
this.header = !this.isJson && !this.isNdJson
|
||||
this.quoteChar = '"'
|
||||
this.escapeChar = '"'
|
||||
@@ -210,11 +206,11 @@ export default {
|
||||
this.addedTable = null
|
||||
this.tableNameError = ''
|
||||
},
|
||||
open () {
|
||||
open() {
|
||||
this.tableName = this.db.sanitizeTableName(fIo.getFileName(this.file))
|
||||
this.$modal.show(this.dialogName)
|
||||
},
|
||||
async preview () {
|
||||
async preview() {
|
||||
this.disableImport = false
|
||||
if (!this.file) {
|
||||
return
|
||||
@@ -257,13 +253,15 @@ export default {
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
this.importMessages = [{
|
||||
message: err,
|
||||
type: 'error'
|
||||
}]
|
||||
this.importMessages = [
|
||||
{
|
||||
message: err,
|
||||
type: 'error'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
async getJsonParseResult (file) {
|
||||
async getJsonParseResult(file) {
|
||||
const jsonContent = await fIo.getFileContent(file)
|
||||
const isEmpty = !jsonContent.trim()
|
||||
return {
|
||||
@@ -273,10 +271,10 @@ export default {
|
||||
},
|
||||
hasErrors: false,
|
||||
messages: [],
|
||||
rowCount: +(!isEmpty)
|
||||
rowCount: +!isEmpty
|
||||
}
|
||||
},
|
||||
async loadToDb (file) {
|
||||
async loadToDb(file) {
|
||||
if (!this.tableName) {
|
||||
this.tableNameError = "Table name can't be empty"
|
||||
return
|
||||
@@ -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,14 +347,19 @@ 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} ` +
|
||||
`into a SQLite database is completed in ${period}.`
|
||||
importMsg.message =
|
||||
`Importing ${this.typeName} ` +
|
||||
`into a SQLite database is completed in ${period}.`
|
||||
importMsg.type = 'success'
|
||||
|
||||
// Loading indicator for import is not needed anymore
|
||||
@@ -385,7 +392,7 @@ export default {
|
||||
this.db.deleteProgressCounter(progressCounterId)
|
||||
this.disableDialog = false
|
||||
},
|
||||
async finish () {
|
||||
async finish() {
|
||||
this.$modal.hide(this.dialogName)
|
||||
const stmt = this.getQueryExample()
|
||||
const tabId = await this.$store.dispatch('addTab', { query: stmt })
|
||||
@@ -394,20 +401,20 @@ export default {
|
||||
this.$emit('finish')
|
||||
events.send('inquiry.create', null, { auto: true })
|
||||
},
|
||||
getQueryExample () {
|
||||
getQueryExample() {
|
||||
return this.isNdJson
|
||||
? this.getNdJsonQueryExample()
|
||||
: this.isJson
|
||||
? this.getJsonQueryExample()
|
||||
: [
|
||||
'/*',
|
||||
` * Your CSV file has been imported into ${this.addedTable} table.`,
|
||||
' * You can run this SQL query to make all CSV records available for charting.',
|
||||
' */',
|
||||
`SELECT * FROM "${this.addedTable}"`
|
||||
` * Your CSV file has been imported into ${this.addedTable} table.`,
|
||||
' * You can run this SQL query to make all CSV records available for charting.',
|
||||
' */',
|
||||
`SELECT * FROM "${this.addedTable}"`
|
||||
].join('\n')
|
||||
},
|
||||
getNdJsonQueryExample () {
|
||||
getNdJsonQueryExample() {
|
||||
try {
|
||||
const firstRowJson = JSON.parse(this.previewData.values.doc[0])
|
||||
const firstKey = Object.keys(firstRowJson)[0]
|
||||
@@ -415,7 +422,7 @@ export default {
|
||||
'/*',
|
||||
` * Your NDJSON file has been imported into ${this.addedTable} table.`,
|
||||
` * Run this SQL query to get values of property ${firstKey} ` +
|
||||
'and make them available for charting.',
|
||||
'and make them available for charting.',
|
||||
' */',
|
||||
`SELECT doc->>'${firstKey}'`,
|
||||
`FROM "${this.addedTable}"`
|
||||
@@ -431,7 +438,7 @@ export default {
|
||||
].join('\n')
|
||||
}
|
||||
},
|
||||
getJsonQueryExample () {
|
||||
getJsonQueryExample() {
|
||||
try {
|
||||
const firstRowJson = JSON.parse(this.previewData.values.doc[0])
|
||||
const firstKey = Object.keys(firstRowJson)[0]
|
||||
@@ -439,7 +446,7 @@ export default {
|
||||
'/*',
|
||||
` * Your JSON file has been imported into ${this.addedTable} table.`,
|
||||
` * Run this SQL query to get values of property ${firstKey} ` +
|
||||
'and make them available for charting.',
|
||||
'and make them available for charting.',
|
||||
' */',
|
||||
'SELECT *',
|
||||
`FROM "${this.addedTable}"`,
|
||||
@@ -475,7 +482,7 @@ export default {
|
||||
}
|
||||
|
||||
#csv-json-table-name {
|
||||
margin-bottom: 24px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.chars {
|
||||
@@ -508,5 +515,4 @@ margin-bottom: 24px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
<template>
|
||||
<div class="db-uploader-container" :style="{ width }">
|
||||
<change-db-icon v-if="type === 'small'" @click="browse"/>
|
||||
<change-db-icon v-if="type === 'small'" @click="browse" />
|
||||
<div v-if="type === 'illustrated'" class="drop-area-container">
|
||||
<div
|
||||
class="drop-area"
|
||||
@dragover.prevent="state = 'dragover'"
|
||||
@dragleave.prevent="state=''"
|
||||
@dragleave.prevent="state = ''"
|
||||
@drop.prevent="drop"
|
||||
@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)
|
||||
}
|
||||
},
|
||||
@@ -83,7 +83,7 @@ export default {
|
||||
ChangeDbIcon,
|
||||
CsvJsonImport
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
state: '',
|
||||
animationPromise: Promise.resolve(),
|
||||
@@ -91,9 +91,9 @@ export default {
|
||||
newDb: null
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
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'
|
||||
@@ -104,26 +104,27 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
cancelImport () {
|
||||
cancelImport() {
|
||||
if (this.newDb) {
|
||||
this.newDb.shutDown()
|
||||
this.newDb = null
|
||||
}
|
||||
},
|
||||
|
||||
async finish () {
|
||||
async finish() {
|
||||
this.$store.commit('setDb', this.newDb)
|
||||
if (this.$route.path !== '/workspace') {
|
||||
this.$router.push('/workspace')
|
||||
}
|
||||
},
|
||||
|
||||
loadDb (file) {
|
||||
return Promise.all([this.newDb.loadDb(file), this.animationPromise])
|
||||
.then(this.finish)
|
||||
loadDb(file) {
|
||||
return Promise.all([this.newDb.loadDb(file), this.animationPromise]).then(
|
||||
this.finish
|
||||
)
|
||||
},
|
||||
|
||||
async checkFile (file) {
|
||||
async checkFile(file) {
|
||||
this.state = 'dropping'
|
||||
this.newDb = database.getNewDatabase()
|
||||
|
||||
@@ -140,16 +141,19 @@ 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')
|
||||
browse() {
|
||||
fIo
|
||||
.getFileFromUser('.db,.sqlite,.sqlite3,.csv,.json,.ndjson')
|
||||
.then(this.checkFile)
|
||||
},
|
||||
|
||||
drop (event) {
|
||||
drop(event) {
|
||||
this.checkFile(event.dataTransfer.files[0])
|
||||
}
|
||||
}
|
||||
@@ -243,11 +247,15 @@ export default {
|
||||
transform-origin: 0 56px;
|
||||
}
|
||||
#file-img.swing {
|
||||
transform-origin: -74px 139px;
|
||||
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>
|
||||
@@ -27,7 +32,7 @@ export default {
|
||||
components: { LoadingIndicator },
|
||||
mixins: [tooltipMixin],
|
||||
methods: {
|
||||
onClick () {
|
||||
onClick() {
|
||||
this.hideTooltip()
|
||||
this.$emit('click')
|
||||
}
|
||||
@@ -59,8 +64,8 @@ export default {
|
||||
fill: var(--color-accent);
|
||||
}
|
||||
|
||||
.icon-btn:disabled .icon :deep(path),
|
||||
.icon-btn:disabled .icon :deep(circle) {
|
||||
.icon-btn:disabled .icon :deep(path),
|
||||
.icon-btn:disabled .icon :deep(circle) {
|
||||
fill: var(--color-border);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,20 +3,23 @@
|
||||
:modal-id="name"
|
||||
class="dialog"
|
||||
:clickToClose="false"
|
||||
:contentTransition="{name: 'loading-dialog'}"
|
||||
:overlayTransition="{name: 'loading-dialog'}"
|
||||
:contentTransition="{ name: 'loading-dialog' }"
|
||||
:overlayTransition="{ name: 'loading-dialog' }"
|
||||
>
|
||||
<div class="dialog-header">
|
||||
{{ title }}
|
||||
<close-icon @click="$emit('cancel')" :disabled="loading"/>
|
||||
<close-icon @click="$emit('cancel')" :disabled="loading" />
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<div v-if="loading" class="loading-dialog-body">
|
||||
<loading-indicator :size="30" class="state-icon"/>
|
||||
<loading-indicator :size="30" class="state-icon" />
|
||||
{{ 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>
|
||||
@@ -57,7 +60,7 @@ export default {
|
||||
},
|
||||
emits: ['cancel', 'action'],
|
||||
watch: {
|
||||
loading () {
|
||||
loading() {
|
||||
if (this.loading) {
|
||||
this.$modal.show(this.name)
|
||||
}
|
||||
@@ -65,7 +68,7 @@ export default {
|
||||
},
|
||||
components: { LoadingIndicator, CloseIcon },
|
||||
methods: {
|
||||
cancel () {
|
||||
cancel() {
|
||||
this.$emit('cancel')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -33,22 +42,24 @@ export default {
|
||||
},
|
||||
emits: [],
|
||||
computed: {
|
||||
circleProgress () {
|
||||
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`
|
||||
},
|
||||
animationClass () {
|
||||
animationClass() {
|
||||
return this.progress === undefined ? 'loading' : 'progress'
|
||||
},
|
||||
radius () {
|
||||
radius() {
|
||||
return this.size / 2 - this.strokeWidth
|
||||
},
|
||||
offset () {
|
||||
return this.radius * 3.14 / 2
|
||||
offset() {
|
||||
return (this.radius * 3.14) / 2
|
||||
},
|
||||
strokeWidth () {
|
||||
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);
|
||||
@@ -70,7 +84,7 @@ export default {
|
||||
|
||||
.loading .loader-svg.front {
|
||||
will-change: transform;
|
||||
animation: fill-animation-loading 1s cubic-bezier(1,1,1,1) 0s infinite;
|
||||
animation: fill-animation-loading 1s cubic-bezier(1, 1, 1, 1) 0s infinite;
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
@@ -97,10 +111,10 @@ export default {
|
||||
}
|
||||
|
||||
.progress .loader-svg.bg {
|
||||
animation: bg-animation 1.5s cubic-bezier(1,1,1,1) 0s infinite;
|
||||
animation: bg-animation 1.5s cubic-bezier(1, 1, 1, 1) 0s infinite;
|
||||
}
|
||||
|
||||
@keyframes bg-animation{
|
||||
@keyframes bg-animation {
|
||||
0% {
|
||||
r: 8;
|
||||
}
|
||||
@@ -109,8 +123,7 @@ export default {
|
||||
r: 9;
|
||||
}
|
||||
100% {
|
||||
r: 8;
|
||||
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>
|
||||
@@ -21,11 +28,11 @@ export default {
|
||||
watch: {
|
||||
'messages.length': 'scrollToBottom'
|
||||
},
|
||||
mounted () {
|
||||
mounted() {
|
||||
this.scrollToBottom()
|
||||
},
|
||||
methods: {
|
||||
async scrollToBottom () {
|
||||
async scrollToBottom() {
|
||||
const container = this.$refs.logsContainer
|
||||
if (container) {
|
||||
await this.$nextTick()
|
||||
@@ -33,7 +40,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
serializeMessage (msg) {
|
||||
serializeMessage(msg) {
|
||||
let result = ''
|
||||
if (msg.row !== null && msg.row !== undefined) {
|
||||
if (msg.type === 'error') {
|
||||
@@ -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>
|
||||
@@ -87,17 +90,18 @@ export default {
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
container: null,
|
||||
paneBefore: this.before,
|
||||
paneAfter: this.after,
|
||||
beforeMinimising: !this.after.size || !this.before.size
|
||||
? this.default
|
||||
: {
|
||||
before: this.before.size,
|
||||
after: this.after.size
|
||||
},
|
||||
beforeMinimising:
|
||||
!this.after.size || !this.before.size
|
||||
? this.default
|
||||
: {
|
||||
before: this.before.size,
|
||||
after: this.after.size
|
||||
},
|
||||
dragging: false,
|
||||
movableSplitter: {
|
||||
top: 0,
|
||||
@@ -107,19 +111,23 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
styles () {
|
||||
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 () {
|
||||
movableSplitterStyle() {
|
||||
const style = { ...this.movableSplitter }
|
||||
style.top += '%'
|
||||
style.left += '%'
|
||||
return style
|
||||
},
|
||||
directionBeforeIconStyle () {
|
||||
directionBeforeIconStyle() {
|
||||
const expanded = this.paneBefore.size !== 0
|
||||
const translation = 'translate(-50%, -50%) '
|
||||
let rotation = ''
|
||||
@@ -134,7 +142,7 @@ export default {
|
||||
transform: translation + rotation
|
||||
}
|
||||
},
|
||||
directionAfterIconStyle () {
|
||||
directionAfterIconStyle() {
|
||||
const expanded = this.paneAfter.size !== 0
|
||||
const translation = 'translate(-50%, -50%)'
|
||||
let rotation = ''
|
||||
@@ -152,35 +160,43 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
bindEvents () {
|
||||
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 })
|
||||
unbindEvents() {
|
||||
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)
|
||||
}
|
||||
},
|
||||
|
||||
onMouseMove (event) {
|
||||
onMouseMove(event) {
|
||||
event.preventDefault()
|
||||
this.dragging = true
|
||||
this.movableSplitter.visibility = 'visible'
|
||||
this.moveSplitter(event)
|
||||
},
|
||||
|
||||
onMouseUp () {
|
||||
onMouseUp() {
|
||||
if (this.dragging) {
|
||||
const dragPercentage = this.horizontal
|
||||
? this.movableSplitter.top
|
||||
@@ -201,7 +217,7 @@ export default {
|
||||
this.unbindEvents()
|
||||
},
|
||||
|
||||
moveSplitter (event) {
|
||||
moveSplitter(event) {
|
||||
const splitterInfo = {
|
||||
container: this.container,
|
||||
paneBeforeMax: this.paneBefore.max,
|
||||
@@ -213,12 +229,13 @@ export default {
|
||||
this.movableSplitter[dir] = offset
|
||||
},
|
||||
|
||||
togglePane (pane) {
|
||||
togglePane(pane) {
|
||||
if (pane.size > 0) {
|
||||
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
|
||||
@@ -226,7 +243,7 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
mounted() {
|
||||
this.container = this.$refs.container
|
||||
}
|
||||
}
|
||||
@@ -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%;
|
||||
@@ -281,14 +304,14 @@ export default {
|
||||
|
||||
.movable-splitter {
|
||||
position: absolute;
|
||||
background-color:rgba(162, 177, 198, 0.5);
|
||||
background-color: rgba(162, 177, 198, 0.5);
|
||||
}
|
||||
|
||||
.splitpanes-vertical > .splitpanes-splitter,
|
||||
.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;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
export default {
|
||||
// Get the cursor position relative to the splitpane container.
|
||||
getCurrentMouseDrag (event, 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
|
||||
@@ -12,23 +11,35 @@ export default {
|
||||
},
|
||||
|
||||
// Returns the drag percentage of the splitter relative to the 2 panes it's inbetween.
|
||||
getCurrentDragPercentage (event, container, isHorisontal) {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ export default {
|
||||
components: { Paginate },
|
||||
props: ['pageCount', 'modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
page: this.modelValue,
|
||||
chevron: `
|
||||
@@ -39,10 +39,10 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
page () {
|
||||
page() {
|
||||
this.$emit('update:modelValue', this.page)
|
||||
},
|
||||
modelValue () {
|
||||
modelValue() {
|
||||
this.page = this.modelValue
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,7 @@ export default {
|
||||
|
||||
:deep(.paginator-next:hover path),
|
||||
:deep(.paginator-prev:hover path) {
|
||||
fill: var(--color-text-active);
|
||||
fill: var(--color-text-active);
|
||||
}
|
||||
:deep(.paginator-disabled path),
|
||||
:deep(.paginator-disabled:hover path) {
|
||||
|
||||
@@ -11,50 +11,50 @@
|
||||
>
|
||||
{{ th.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="table-container"
|
||||
ref="table-container"
|
||||
@scroll="onScrollTable"
|
||||
>
|
||||
<table
|
||||
ref="table"
|
||||
class="sqliteviz-table"
|
||||
tabindex="0"
|
||||
@keydown="onTableKeydown"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="(th, index) in columns" :key="index" ref="th">
|
||||
<div class="cell-data" :style="cellStyle">{{ th }}</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="rowIndex in currentPageData.count" :key="rowIndex">
|
||||
<td
|
||||
v-for="(col, colIndex) in columns"
|
||||
:data-col="colIndex"
|
||||
:data-row="pageSize * (currentPage - 1) + rowIndex - 1"
|
||||
:data-isNull="isNull(getCellValue(col, rowIndex))"
|
||||
:data-isBlob="isBlob(getCellValue(col, rowIndex))"
|
||||
:key="colIndex"
|
||||
:aria-selected="false"
|
||||
@click="onCellClick"
|
||||
>
|
||||
<div class="cell-data" :style="cellStyle">
|
||||
{{ getCellText(col, rowIndex) }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table
|
||||
ref="table"
|
||||
class="sqliteviz-table"
|
||||
tabindex="0"
|
||||
@keydown="onTableKeydown"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th v-for="(th, index) in columns" :key="index" ref="th">
|
||||
<div class="cell-data" :style="cellStyle">{{ th }}</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="rowIndex in currentPageData.count" :key="rowIndex">
|
||||
<td
|
||||
v-for="(col, colIndex) in columns"
|
||||
:data-col="colIndex"
|
||||
:data-row="pageSize * (currentPage - 1) + rowIndex - 1"
|
||||
:data-isNull="isNull(getCellValue(col, rowIndex))"
|
||||
:data-isBlob="isBlob(getCellValue(col, rowIndex))"
|
||||
:key="colIndex"
|
||||
:aria-selected="false"
|
||||
@click="onCellClick"
|
||||
>
|
||||
<div class="cell-data" :style="cellStyle">
|
||||
{{ getCellText(col, rowIndex) }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-footer">
|
||||
<div class="table-footer-count">
|
||||
{{ rowCount }} {{rowCount === 1 ? 'row' : 'rows'}} retrieved
|
||||
{{ rowCount }} {{ rowCount === 1 ? 'row' : 'rows' }} retrieved
|
||||
<span v-if="preview">for preview</span>
|
||||
<span v-if="time">in {{ time }}</span>
|
||||
</div>
|
||||
@@ -88,7 +88,7 @@ export default {
|
||||
selectedCellCoordinates: Object
|
||||
},
|
||||
emits: ['updateSelectedCell'],
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
header: null,
|
||||
tableWidth: null,
|
||||
@@ -98,20 +98,20 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns () {
|
||||
columns() {
|
||||
return this.dataSet.columns
|
||||
},
|
||||
rowCount () {
|
||||
rowCount() {
|
||||
return this.dataSet.values[this.columns[0]].length
|
||||
},
|
||||
cellStyle () {
|
||||
cellStyle() {
|
||||
const eq = this.tableWidth / this.columns.length
|
||||
return { maxWidth: `${Math.max(eq, 100)}px` }
|
||||
},
|
||||
pageCount () {
|
||||
pageCount() {
|
||||
return Math.ceil(this.rowCount / this.pageSize)
|
||||
},
|
||||
currentPageData () {
|
||||
currentPageData() {
|
||||
const start = (this.currentPage - 1) * this.pageSize
|
||||
let end = start + this.pageSize
|
||||
if (end > this.rowCount - 1) {
|
||||
@@ -124,31 +124,32 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
mounted() {
|
||||
this.resizeObserver = new ResizeObserver(this.calculateHeadersWidth)
|
||||
this.resizeObserver.observe(this.$refs.table)
|
||||
this.calculateHeadersWidth()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isBlob (value) {
|
||||
isBlob(value) {
|
||||
return value && ArrayBuffer.isView(value)
|
||||
},
|
||||
isNull (value) {
|
||||
isNull(value) {
|
||||
return value === null
|
||||
},
|
||||
getCellValue (col, rowIndex) {
|
||||
getCellValue(col, rowIndex) {
|
||||
return this.dataSet.values[col][rowIndex - 1 + this.currentPageData.start]
|
||||
},
|
||||
getCellText (col, rowIndex) {
|
||||
getCellText(col, rowIndex) {
|
||||
const value = this.getCellValue(col, rowIndex)
|
||||
if (this.isNull(value)) {
|
||||
return 'NULL'
|
||||
@@ -158,7 +159,7 @@ export default {
|
||||
}
|
||||
return value
|
||||
},
|
||||
calculateHeadersWidth () {
|
||||
calculateHeadersWidth() {
|
||||
this.tableWidth = this.$refs['table-container'].offsetWidth
|
||||
this.$nextTick(() => {
|
||||
this.header = this.$refs.th.map(th => {
|
||||
@@ -166,10 +167,11 @@ export default {
|
||||
})
|
||||
})
|
||||
},
|
||||
onScrollTable () {
|
||||
this.$refs['header-container'].scrollLeft = this.$refs['table-container'].scrollLeft
|
||||
onScrollTable() {
|
||||
this.$refs['header-container'].scrollLeft =
|
||||
this.$refs['table-container'].scrollLeft
|
||||
},
|
||||
onTableKeydown (e) {
|
||||
onTableKeydown(e) {
|
||||
const keyCodeMap = {
|
||||
37: 'left',
|
||||
39: 'right',
|
||||
@@ -187,10 +189,10 @@ export default {
|
||||
|
||||
this.moveFocusInTable(this.selectedCellElement, keyCodeMap[e.keyCode])
|
||||
},
|
||||
onCellClick (e) {
|
||||
onCellClick(e) {
|
||||
this.selectCell(e.target.closest('td'), false)
|
||||
},
|
||||
selectCell (cell, scrollTo = true) {
|
||||
selectCell(cell, scrollTo = true) {
|
||||
if (!cell) {
|
||||
if (this.selectedCellElement) {
|
||||
this.selectedCellElement.ariaSelected = 'false'
|
||||
@@ -213,7 +215,7 @@ export default {
|
||||
|
||||
this.$emit('updateSelectedCell', this.selectedCellElement)
|
||||
},
|
||||
moveFocusInTable (initialCell, direction) {
|
||||
moveFocusInTable(initialCell, direction) {
|
||||
const currentRowIndex = +initialCell.dataset.row
|
||||
const currentColIndex = +initialCell.dataset.col
|
||||
let newRowIndex, newColIndex
|
||||
@@ -242,22 +244,23 @@ 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)
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeUnmount () {
|
||||
beforeUnmount() {
|
||||
this.resizeObserver.unobserve(this.$refs.table)
|
||||
},
|
||||
watch: {
|
||||
currentPageData () {
|
||||
currentPageData() {
|
||||
this.calculateHeadersWidth()
|
||||
this.selectCell(null)
|
||||
},
|
||||
dataSet () {
|
||||
dataSet() {
|
||||
this.currentPage = 1
|
||||
}
|
||||
}
|
||||
@@ -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"] {
|
||||
box-shadow:inset 0 0 0 1px var(--color-accent);
|
||||
.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"
|
||||
@@ -75,7 +83,7 @@ input.error {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.text-field-label .hint{
|
||||
.text-field-label .hint {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -22px;
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="18" height="18" fill="white"/>
|
||||
<rect width="18" height="18" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
@@ -48,7 +48,7 @@ export default {
|
||||
props: ['tooltip'],
|
||||
emits: ['click'],
|
||||
methods: {
|
||||
onClick () {
|
||||
onClick() {
|
||||
this.hideTooltip()
|
||||
this.$emit('click')
|
||||
}
|
||||
|
||||
@@ -11,10 +11,9 @@
|
||||
13.5L16.35 6.75L17.9475 8.33625Z"
|
||||
fill="#506784"
|
||||
/>
|
||||
</svg>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
}
|
||||
export default {}
|
||||
</script>
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg
|
||||
class="db-edit-icon"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
@click.stop="onClick"
|
||||
@mouseenter="showTooltip"
|
||||
@mouseleave="hideTooltip"
|
||||
>
|
||||
<path
|
||||
d="M3 10.5V12.75C3 14.25 5.2875 15.54 8.25 15.75V13.5825L8.3475 13.5C5.34 13.32 3 12.045 3
|
||||
<div>
|
||||
<svg
|
||||
class="db-edit-icon"
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
@click.stop="onClick"
|
||||
@mouseenter="showTooltip"
|
||||
@mouseleave="hideTooltip"
|
||||
>
|
||||
<path
|
||||
d="M3 10.5V12.75C3 14.25 5.2875 15.54 8.25 15.75V13.5825L8.3475 13.5C5.34 13.32 3 12.045 3
|
||||
10.5ZM9 9.75C5.685 9.75 3 8.4075 3 6.75V9C3 10.6575 5.685 12 9 12C9.2925 12 9.5775 12
|
||||
9.87 12L12.75 9.09C11.55 9.54 10.2825 9.75 9 9.75ZM9 2.25C5.685 2.25 3 3.5925 3 5.25C3
|
||||
6.9075 5.685 8.25 9 8.25C12.315 8.25 15 6.9075 15 5.25C15 3.5925 12.315 2.25 9 2.25ZM15.75
|
||||
8.3475C15.6375 8.3475 15.5325 8.3925 15.4575 8.475L14.7075 9.225L16.245 10.725L16.995
|
||||
9.975C17.1525 9.825 17.16 9.57 16.995 9.3975L16.065 8.475C15.99 8.3925 15.885 8.3475 15.78
|
||||
8.3475H15.75ZM14.28 9.66L9.75 14.205V15.75H11.295L15.84 11.1975L14.28 9.66Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
<span class="icon-tooltip" :style="tooltipStyle" ref="tooltip">
|
||||
Load another database, CSV, JSON or NDJSON
|
||||
</span>
|
||||
</div>
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
<span class="icon-tooltip" :style="tooltipStyle" ref="tooltip">
|
||||
Load another database, CSV, JSON or NDJSON
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -35,7 +35,7 @@ export default {
|
||||
mixins: [tooltipMixin],
|
||||
emits: ['click'],
|
||||
methods: {
|
||||
onClick () {
|
||||
onClick() {
|
||||
this.hideTooltip()
|
||||
this.$emit('click')
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
@@ -17,7 +12,7 @@
|
||||
6.91686 13.5552 6.91522Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<circle cx="5.50049" cy="6.00339" r="1.5" fill="#A2B1C6"/>
|
||||
<circle cx="5.50049" cy="6.00339" r="1.5" fill="#A2B1C6" />
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
@@ -27,11 +22,10 @@
|
||||
1.21788ZM16.0374 2.71788L1.96424 2.713L1.96289 15.2773L16.036 15.2821L16.0374 2.71788Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
</svg>
|
||||
</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>
|
||||
|
||||
@@ -32,7 +32,7 @@ export default {
|
||||
props: ['tooltip', 'tooltipPosition'],
|
||||
emits: ['click'],
|
||||
methods: {
|
||||
onClick () {
|
||||
onClick() {
|
||||
this.hideTooltip()
|
||||
this.$emit('click')
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -34,7 +29,7 @@
|
||||
14.6699C9.53809 14.6699 9.73877 14.6157 9.88525 14.5073C10.0347 14.3959 10.1094 14.2407
|
||||
10.1094 14.0414ZM14.9258 14.0019L16.2002 9.34369H17.9229L15.7695 15.7421H14.082L11.9463
|
||||
9.34369H13.6558L14.9258 14.0019Z"
|
||||
fill="#A2B1C6"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
d="M3.03345 0.991333H4.89869V2.49133H3.03345V7.93074H1.53345V2.49133C1.53345 1.66633
|
||||
@@ -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>
|
||||
@@ -48,7 +52,7 @@ export default {
|
||||
emits: ['click'],
|
||||
mixins: [tooltipMixin],
|
||||
methods: {
|
||||
onClick () {
|
||||
onClick() {
|
||||
this.hideTooltip()
|
||||
this.$emit('click')
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
7.89917V9.2439L5.1626 10.0745ZM8.99023 13.3H7.93994L10.124 6.35229H11.1787L8.99023
|
||||
13.3ZM14.1099 10.0613L11.7192 9.24829V7.90356L15.582 9.4856V10.6545L11.7192
|
||||
12.2366V10.8918L14.1099 10.0613Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
d="M2.17041 0.0637207H16.2185V1.56372H2.17041V9.30354H0.67041V1.56372C0.67041 0.73872
|
||||
1.34541 0.0637207 2.17041 0.0637207Z"
|
||||
|
||||
@@ -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"
|
||||
@@ -14,14 +9,13 @@
|
||||
14.1914C14.8372 13.965 15.0161 13.5645 15.0161 12.8467V9.43008H13.1914L15.7661 5.13901Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path d="M6.41943 0H18.4194V4H6.41943V0Z" fill="#A2B1C6"/>
|
||||
<path d="M0.419434 6H4.41943V18H0.419434V6Z" fill="#A2B1C6"/>
|
||||
<path d="M0.419434 0H4.41943V4H0.419434V0Z" fill="#A2B1C6"/>
|
||||
<path d="M6.41943 0H18.4194V4H6.41943V0Z" fill="#A2B1C6" />
|
||||
<path d="M0.419434 6H4.41943V18H0.419434V6Z" fill="#A2B1C6" />
|
||||
<path d="M0.419434 0H4.41943V4H0.419434V0Z" fill="#A2B1C6" />
|
||||
</svg>
|
||||
</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,15 +1,10 @@
|
||||
<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"
|
||||
clip-rule="evenodd"
|
||||
d="M1.85303 11.3794L1.85303 7.80371L5.86304 7.80371L5.86304
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M1.85303 11.3794L1.85303 7.80371L5.86304 7.80371L5.86304
|
||||
11.3794L1.85303 11.3794ZM7.36304 11.3794L7.36304 7.80371L11.3428
|
||||
7.80371L11.3428 11.3794L7.36304 11.3794ZM12.8428 11.3794L16.853
|
||||
11.3794L16.853 7.80371L12.8428 7.80371L12.8428 11.3794ZM15.353
|
||||
@@ -29,24 +24,23 @@
|
||||
14.3121L6.76685 12.8794L4.67027 12.8794ZM8.26685 12.8794L8.26685
|
||||
14.3121L10.387 14.3121L10.387 12.8794L8.26685 12.8794ZM11.887
|
||||
12.8794L11.887 14.3121L14.0315 14.3121L14.0315 12.8794L11.887 12.8794Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2130_5292">
|
||||
<rect
|
||||
width="18"
|
||||
height="18"
|
||||
fill="white"
|
||||
transform="translate(0.353027 18.5916) rotate(-90)"
|
||||
/>
|
||||
width="18"
|
||||
height="18"
|
||||
fill="white"
|
||||
transform="translate(0.353027 18.5916) rotate(-90)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</svg>
|
||||
</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,24 +1,19 @@
|
||||
<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
|
||||
<path
|
||||
d="M3.53784 11.5846L3.53784 3.14734L11.9751 3.14734V7.676C12.4655 7.51991
|
||||
12.9771 7.47439 13.4751 7.53264V3.14734C13.4751 2.31891 12.8035 1.64734
|
||||
11.9751 1.64734L3.53784 1.64734C2.70941 1.64734 2.03784 2.31891 2.03784
|
||||
3.14734L2.03784 11.5846C2.03784 12.413 2.70942 13.0846 3.53784
|
||||
13.0846H10.0831C9.771 12.6184 9.58279 12.1055 9.51083
|
||||
11.5846H3.53784Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M14.7887 9.9291C15.4307 10.8837 15.1773 12.1779 14.2228
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M14.7887 9.9291C15.4307 10.8837 15.1773 12.1779 14.2228
|
||||
12.8199C13.2682 13.4618 11.974 13.2084 11.332 12.2539C10.69 11.2993
|
||||
10.9434 10.0051 11.898 9.3631C12.8525 8.72113 14.1468 8.97454 14.7887
|
||||
9.9291ZM14.4606 14.3901L16.6181 17.5982C16.8492 17.9419 17.3153 18.0331
|
||||
@@ -26,24 +21,23 @@
|
||||
13.5279C16.7949 12.3365 16.9801 10.4996 16.0334 9.092C14.9292 7.45002
|
||||
12.7029 7.01412 11.0609 8.1184C9.41891 9.22268 8.98302 11.449 10.0873
|
||||
13.0909C11.062 14.5403 12.9109 15.05 14.4606 14.3901Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2131_6054">
|
||||
<rect
|
||||
width="18"
|
||||
height="18"
|
||||
fill="white"
|
||||
transform="translate(0.5 18.5916) rotate(-90)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2131_6054">
|
||||
<rect
|
||||
width="18"
|
||||
height="18"
|
||||
fill="white"
|
||||
transform="translate(0.5 18.5916) rotate(-90)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ViewCellValueIcon'
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as dereference from 'react-chart-editor/lib/lib/dereference'
|
||||
import plotly from 'plotly.js'
|
||||
import { nanoid } from 'nanoid'
|
||||
|
||||
export function getOptionsFromDataSources (dataSources) {
|
||||
export function getOptionsFromDataSources(dataSources) {
|
||||
if (!dataSources) {
|
||||
return []
|
||||
}
|
||||
@@ -13,7 +13,7 @@ export function getOptionsFromDataSources (dataSources) {
|
||||
}))
|
||||
}
|
||||
|
||||
export function getOptionsForSave (state, dataSources) {
|
||||
export function getOptionsForSave(state, dataSources) {
|
||||
// we don't need to save the data, only settings
|
||||
// so we modify state.data using dereference
|
||||
const stateCopy = JSON.parse(JSON.stringify(state))
|
||||
@@ -25,7 +25,7 @@ export function getOptionsForSave (state, dataSources) {
|
||||
return stateCopy
|
||||
}
|
||||
|
||||
export async function getImageDataUrl (element, type) {
|
||||
export async function getImageDataUrl(element, type) {
|
||||
const chartElement = element.querySelector('.js-plotly-plot')
|
||||
return await plotly.toImage(chartElement, {
|
||||
format: type,
|
||||
@@ -34,7 +34,7 @@ export async function getImageDataUrl (element, type) {
|
||||
})
|
||||
}
|
||||
|
||||
export function getChartData (element) {
|
||||
export function getChartData(element) {
|
||||
const chartElement = element.querySelector('.js-plotly-plot')
|
||||
return {
|
||||
data: chartElement.data,
|
||||
@@ -42,7 +42,7 @@ export function getChartData (element) {
|
||||
}
|
||||
}
|
||||
|
||||
export function getHtml (options) {
|
||||
export function getHtml(options) {
|
||||
const chartId = nanoid()
|
||||
return `
|
||||
<script src="https://cdn.plot.ly/plotly-latest.js" charset="UTF-8"></script>
|
||||
|
||||
@@ -7,7 +7,7 @@ const hintsByCode = {
|
||||
}
|
||||
|
||||
export default {
|
||||
getResult (source, columns) {
|
||||
getResult(source, columns) {
|
||||
const result = {
|
||||
columns: columns || []
|
||||
}
|
||||
@@ -52,7 +52,7 @@ export default {
|
||||
return result
|
||||
},
|
||||
|
||||
prepareForExport (resultSet) {
|
||||
prepareForExport(resultSet) {
|
||||
const columns = resultSet.columns
|
||||
const rowCount = resultSet.values[columns[0]].length
|
||||
const result = {
|
||||
@@ -61,13 +61,15 @@ 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
|
||||
},
|
||||
|
||||
parse (file, config = {}) {
|
||||
parse(file, config = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const defaultConfig = {
|
||||
delimiter: '', // auto-detect
|
||||
@@ -122,7 +124,7 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
serialize (resultSet) {
|
||||
serialize(resultSet) {
|
||||
return Papa.unparse(this.prepareForExport(resultSet), { delimiter: '\t' })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ 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) {
|
||||
function _getDataSourcesFromSqlResult(sqlResult) {
|
||||
if (!sqlResult) {
|
||||
return {}
|
||||
}
|
||||
@@ -19,31 +21,30 @@ function _getDataSourcesFromSqlResult (sqlResult) {
|
||||
}
|
||||
|
||||
export default class Sql {
|
||||
constructor () {
|
||||
constructor() {
|
||||
this.db = null
|
||||
}
|
||||
|
||||
static build () {
|
||||
return sqlModuleReady
|
||||
.then(() => {
|
||||
return new Sql()
|
||||
})
|
||||
static build() {
|
||||
return sqlModuleReady.then(() => {
|
||||
return new Sql()
|
||||
})
|
||||
}
|
||||
|
||||
createDb (buffer) {
|
||||
createDb(buffer) {
|
||||
if (this.db != null) this.db.close()
|
||||
this.db = new SQL.Database(buffer)
|
||||
return this.db
|
||||
}
|
||||
|
||||
open (buffer) {
|
||||
open(buffer) {
|
||||
this.createDb(buffer && new Uint8Array(buffer))
|
||||
return {
|
||||
ready: true
|
||||
}
|
||||
}
|
||||
|
||||
exec (sql, params) {
|
||||
exec(sql, params) {
|
||||
if (this.db === null) {
|
||||
this.createDb()
|
||||
}
|
||||
@@ -59,7 +60,7 @@ export default class Sql {
|
||||
})
|
||||
}
|
||||
|
||||
import (tabName, data, progressCounterId, progressCallback, chunkSize = 1500) {
|
||||
import(tabName, data, progressCounterId, progressCallback, chunkSize = 1500) {
|
||||
if (this.db === null) {
|
||||
this.createDb()
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -88,11 +92,11 @@ export default class Sql {
|
||||
}
|
||||
}
|
||||
|
||||
export () {
|
||||
export() {
|
||||
return this.db.export()
|
||||
}
|
||||
|
||||
close () {
|
||||
close() {
|
||||
if (this.db) {
|
||||
this.db.close()
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
export default {
|
||||
* generateChunks (data, size) {
|
||||
*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)
|
||||
|
||||
@@ -13,13 +15,13 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
getInsertStmt (tabName, columns) {
|
||||
getInsertStmt(tabName, columns) {
|
||||
const colList = `"${columns.join('", "')}"`
|
||||
const params = columns.map(() => '?').join(', ')
|
||||
return `INSERT INTO "${tabName}" (${colList}) VALUES (${params});`
|
||||
},
|
||||
|
||||
getCreateStatement (tabName, data) {
|
||||
getCreateStatement(tabName, data) {
|
||||
let result = `CREATE table "${tabName}"(`
|
||||
for (const col in data) {
|
||||
// Get the first row of values to determine types
|
||||
@@ -38,7 +40,8 @@ export default {
|
||||
type = 'TEXT'
|
||||
break
|
||||
}
|
||||
default: type = 'TEXT'
|
||||
default:
|
||||
type = 'TEXT'
|
||||
}
|
||||
result += `"${col}" ${type}, `
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import Sql from './_sql'
|
||||
|
||||
const sqlReady = Sql.build()
|
||||
|
||||
function processMsg (sql) {
|
||||
function processMsg(sql) {
|
||||
const data = this
|
||||
switch (data && data.action) {
|
||||
case 'open':
|
||||
@@ -28,14 +28,12 @@ function processMsg (sql) {
|
||||
}
|
||||
}
|
||||
|
||||
function onError (error) {
|
||||
function onError(error) {
|
||||
return {
|
||||
error: error.message
|
||||
}
|
||||
}
|
||||
|
||||
registerPromiseWorker(data => {
|
||||
return sqlReady
|
||||
.then(processMsg.bind(data))
|
||||
.catch(onError)
|
||||
return sqlReady.then(processMsg.bind(data)).catch(onError)
|
||||
})
|
||||
|
||||
@@ -6,11 +6,10 @@ 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' }
|
||||
)
|
||||
function getNewDatabase() {
|
||||
const worker = new Worker(new URL('./_worker.js', import.meta.url), {
|
||||
type: 'module'
|
||||
})
|
||||
return new Database(worker)
|
||||
}
|
||||
|
||||
@@ -20,7 +19,7 @@ export default {
|
||||
|
||||
let progressCounterIds = 0
|
||||
class Database {
|
||||
constructor (worker) {
|
||||
constructor(worker) {
|
||||
this.dbName = null
|
||||
this.schema = null
|
||||
this.worker = worker
|
||||
@@ -31,29 +30,33 @@ class Database {
|
||||
const progress = e.data.progress
|
||||
if (progress !== undefined) {
|
||||
const id = e.data.id
|
||||
this.importProgresses[id].dispatchEvent(new CustomEvent('progress', {
|
||||
detail: progress
|
||||
}))
|
||||
this.importProgresses[id].dispatchEvent(
|
||||
new CustomEvent('progress', {
|
||||
detail: progress
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
shutDown () {
|
||||
shutDown() {
|
||||
this.worker.terminate()
|
||||
}
|
||||
|
||||
createProgressCounter (callback) {
|
||||
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
|
||||
}
|
||||
|
||||
deleteProgressCounter (id) {
|
||||
deleteProgressCounter(id) {
|
||||
delete this.importProgresses[id]
|
||||
}
|
||||
|
||||
async addTableFromCsv (tabName, data, progressCounterId) {
|
||||
async addTableFromCsv(tabName, data, progressCounterId) {
|
||||
const result = await this.pw.postMessage({
|
||||
action: 'import',
|
||||
data,
|
||||
@@ -68,9 +71,12 @@ class Database {
|
||||
this.refreshSchema()
|
||||
}
|
||||
|
||||
async loadDb (file) {
|
||||
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)
|
||||
@@ -85,7 +91,7 @@ class Database {
|
||||
})
|
||||
}
|
||||
|
||||
async refreshSchema () {
|
||||
async refreshSchema() {
|
||||
const getSchemaSql = `
|
||||
WITH columns as (
|
||||
SELECT
|
||||
@@ -103,7 +109,7 @@ class Database {
|
||||
this.schema = JSON.parse(result.values.objects[0])
|
||||
}
|
||||
|
||||
async execute (commands) {
|
||||
async execute(commands) {
|
||||
await this.pw.postMessage({ action: 'reopen' })
|
||||
const results = await this.pw.postMessage({ action: 'exec', sql: commands })
|
||||
|
||||
@@ -114,7 +120,7 @@ class Database {
|
||||
return results[results.length - 1]
|
||||
}
|
||||
|
||||
async export (fileName) {
|
||||
async export(fileName) {
|
||||
const data = await this.pw.postMessage({ action: 'export' })
|
||||
|
||||
if (data.error) {
|
||||
@@ -124,13 +130,15 @@ class Database {
|
||||
events.send('database.export', data.byteLength, { to: 'sqlite' })
|
||||
}
|
||||
|
||||
async validateTableName (name) {
|
||||
async validateTableName(name) {
|
||||
if (name.startsWith('sqlite_')) {
|
||||
throw new Error("Table name can't start with sqlite_")
|
||||
}
|
||||
|
||||
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)) {
|
||||
@@ -140,7 +148,7 @@ class Database {
|
||||
await this.execute(`BEGIN; CREATE TABLE "${name}"(id); ROLLBACK;`)
|
||||
}
|
||||
|
||||
sanitizeTableName (tabName) {
|
||||
sanitizeTableName(tabName) {
|
||||
return tabName
|
||||
.replace(/[^\w]/g, '_') // replace everything that is not letter, digit or _ with _
|
||||
.replace(/^(\d)/, '_$1') // add _ at beginning if starts with digit
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
_migrate (installedVersion, inquiries) {
|
||||
_migrate(installedVersion, inquiries) {
|
||||
if (installedVersion === 1) {
|
||||
inquiries.forEach(inquire => {
|
||||
inquire.viewType = 'chart'
|
||||
|
||||
@@ -7,7 +7,7 @@ const migrate = migration._migrate
|
||||
|
||||
export default {
|
||||
version: 2,
|
||||
getStoredInquiries () {
|
||||
getStoredInquiries() {
|
||||
let myInquiries = JSON.parse(localStorage.getItem('myInquiries'))
|
||||
if (!myInquiries) {
|
||||
const oldInquiries = localStorage.getItem('myQueries')
|
||||
@@ -22,7 +22,7 @@ export default {
|
||||
return (myInquiries && myInquiries.inquiries) || []
|
||||
},
|
||||
|
||||
duplicateInquiry (baseInquiry) {
|
||||
duplicateInquiry(baseInquiry) {
|
||||
const newInquiry = JSON.parse(JSON.stringify(baseInquiry))
|
||||
newInquiry.name = newInquiry.name + ' Copy'
|
||||
newInquiry.id = nanoid()
|
||||
@@ -32,21 +32,28 @@ export default {
|
||||
return newInquiry
|
||||
},
|
||||
|
||||
isTabNeedName (inquiryTab) {
|
||||
isTabNeedName(inquiryTab) {
|
||||
return inquiryTab.isPredefined || !inquiryTab.name
|
||||
},
|
||||
|
||||
updateStorage (inquiries) {
|
||||
localStorage.setItem('myInquiries', JSON.stringify({ version: this.version, inquiries }))
|
||||
updateStorage(inquiries) {
|
||||
localStorage.setItem(
|
||||
'myInquiries',
|
||||
JSON.stringify({ version: this.version, inquiries })
|
||||
)
|
||||
},
|
||||
|
||||
serialiseInquiries (inquiryList) {
|
||||
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) {
|
||||
deserialiseInquiries(str) {
|
||||
const inquiries = JSON.parse(str)
|
||||
let inquiryList = []
|
||||
if (!inquiries.version) {
|
||||
@@ -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()
|
||||
}
|
||||
@@ -68,24 +77,23 @@ export default {
|
||||
return inquiryList
|
||||
},
|
||||
|
||||
importInquiries () {
|
||||
return fu.importFile()
|
||||
.then(str => {
|
||||
const inquires = this.deserialiseInquiries(str)
|
||||
importInquiries() {
|
||||
return fu.importFile().then(str => {
|
||||
const inquires = this.deserialiseInquiries(str)
|
||||
|
||||
events.send('inquiry.import', inquires.length)
|
||||
events.send('inquiry.import', inquires.length)
|
||||
|
||||
return inquires
|
||||
})
|
||||
return inquires
|
||||
})
|
||||
},
|
||||
export (inquiryList, fileName) {
|
||||
export(inquiryList, fileName) {
|
||||
const jsonStr = this.serialiseInquiries(inquiryList)
|
||||
fu.exportToFile(jsonStr, fileName)
|
||||
|
||||
events.send('inquiry.export', inquiryList.length)
|
||||
},
|
||||
|
||||
async readPredefinedInquiries () {
|
||||
async readPredefinedInquiries() {
|
||||
const res = await fu.readFile('./inquiries.json')
|
||||
const data = await res.json()
|
||||
|
||||
|
||||
@@ -3,12 +3,14 @@ import time from '@/lib/utils/time'
|
||||
import events from '@/lib/utils/events'
|
||||
|
||||
export default class Tab {
|
||||
constructor (state, inquiry = {}) {
|
||||
constructor(state, inquiry = {}) {
|
||||
this.id = inquiry.id || nanoid()
|
||||
this.name = inquiry.id ? inquiry.name : null
|
||||
this.tempName = inquiry.name || (state.untitledLastIndex
|
||||
? `Untitled ${state.untitledLastIndex}`
|
||||
: 'Untitled')
|
||||
this.tempName =
|
||||
inquiry.name ||
|
||||
(state.untitledLastIndex
|
||||
? `Untitled ${state.untitledLastIndex}`
|
||||
: 'Untitled')
|
||||
this.query = inquiry.query
|
||||
this.viewOptions = inquiry.viewOptions || undefined
|
||||
this.isPredefined = inquiry.isPredefined
|
||||
@@ -28,7 +30,7 @@ export default class Tab {
|
||||
this.state = state
|
||||
}
|
||||
|
||||
async execute () {
|
||||
async execute() {
|
||||
this.isGettingResults = true
|
||||
this.result = null
|
||||
this.error = null
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@ import Lib from 'plotly.js/src/lib'
|
||||
import dataUrlToBlob from 'dataurl-to-blob'
|
||||
|
||||
export default {
|
||||
async copyText (str, notifyMessage) {
|
||||
async copyText(str, notifyMessage) {
|
||||
await navigator.clipboard.writeText(str)
|
||||
if (notifyMessage) {
|
||||
Lib.notifier(notifyMessage, 'long')
|
||||
}
|
||||
},
|
||||
|
||||
async copyImage (source) {
|
||||
async copyImage(source) {
|
||||
if (source instanceof HTMLCanvasElement) {
|
||||
return this._copyCanvas(source)
|
||||
} else {
|
||||
@@ -17,24 +17,29 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
async _copyBlob (blob) {
|
||||
async _copyBlob(blob) {
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({ // eslint-disable-line no-undef
|
||||
new ClipboardItem({
|
||||
// eslint-disable-line no-undef
|
||||
[blob.type]: blob
|
||||
})
|
||||
])
|
||||
},
|
||||
|
||||
async _copyFromDataUrl (url) {
|
||||
async _copyFromDataUrl(url) {
|
||||
const blob = dataUrlToBlob(url)
|
||||
await this._copyBlob(blob)
|
||||
Lib.notifier('Image copied to clipboard successfully', 'long')
|
||||
},
|
||||
|
||||
async _copyCanvas (canvas) {
|
||||
canvas.toBlob(async (blob) => {
|
||||
await this._copyBlob(blob)
|
||||
Lib.notifier('Image copied to clipboard successfully', 'long')
|
||||
}, 'image/png', 1)
|
||||
async _copyCanvas(canvas) {
|
||||
canvas.toBlob(
|
||||
async blob => {
|
||||
await this._copyBlob(blob)
|
||||
Lib.notifier('Image copied to clipboard successfully', 'long')
|
||||
},
|
||||
'image/png',
|
||||
1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
send (name, value, labels) {
|
||||
send(name, value, labels) {
|
||||
const event = new CustomEvent('sqliteviz-app-event', {
|
||||
detail: {
|
||||
name,
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
export default {
|
||||
isJSON (file) {
|
||||
isJSON(file) {
|
||||
return file && file.type === 'application/json'
|
||||
},
|
||||
isNDJSON (file) {
|
||||
isNDJSON(file) {
|
||||
return file && file.name.endsWith('.ndjson')
|
||||
},
|
||||
isDatabase (file) {
|
||||
isDatabase(file) {
|
||||
const dbTypes = ['application/vnd.sqlite3', 'application/x-sqlite3']
|
||||
return file.type
|
||||
? dbTypes.includes(file.type)
|
||||
: /\.(db|sqlite(3)?)+$/.test(file.name)
|
||||
},
|
||||
|
||||
getFileName (file) {
|
||||
getFileName(file) {
|
||||
return file.name.replace(/\.[^.]+$/, '')
|
||||
},
|
||||
|
||||
downloadFromUrl (url, fileName) {
|
||||
downloadFromUrl(url, fileName) {
|
||||
// Create downloader
|
||||
const downloader = document.createElement('a')
|
||||
downloader.href = url
|
||||
@@ -29,7 +29,7 @@ export default {
|
||||
URL.revokeObjectURL(url)
|
||||
},
|
||||
|
||||
async exportToFile (str, fileName, type = 'octet/stream') {
|
||||
async exportToFile(str, fileName, type = 'octet/stream') {
|
||||
const blob = new Blob([str], { type })
|
||||
const url = URL.createObjectURL(blob)
|
||||
this.downloadFromUrl(url, fileName)
|
||||
@@ -40,7 +40,7 @@ export default {
|
||||
* it will be an unsettled promise. But it's grabbed by
|
||||
* the garbage collector (tested with FinalizationRegistry).
|
||||
*/
|
||||
getFileFromUser (type) {
|
||||
getFileFromUser(type) {
|
||||
return new Promise(resolve => {
|
||||
const uploader = document.createElement('input')
|
||||
|
||||
@@ -56,14 +56,13 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
importFile () {
|
||||
return this.getFileFromUser('.json')
|
||||
.then(file => {
|
||||
return this.getFileContent(file)
|
||||
})
|
||||
importFile() {
|
||||
return this.getFileFromUser('.json').then(file => {
|
||||
return this.getFileContent(file)
|
||||
})
|
||||
},
|
||||
|
||||
getFileContent (file) {
|
||||
getFileContent(file) {
|
||||
const reader = new FileReader()
|
||||
return new Promise(resolve => {
|
||||
reader.onload = e => resolve(e.target.result)
|
||||
@@ -71,11 +70,11 @@ export default {
|
||||
})
|
||||
},
|
||||
|
||||
readFile (path) {
|
||||
readFile(path) {
|
||||
return fetch(path)
|
||||
},
|
||||
|
||||
readAsArrayBuffer (file) {
|
||||
readAsArrayBuffer(file) {
|
||||
const fileReader = new FileReader()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
export default {
|
||||
getPeriod (start, end) {
|
||||
getPeriod(start, end) {
|
||||
const diff = end.getTime() - start.getTime()
|
||||
const seconds = diff / 1000
|
||||
return seconds.toFixed(3) + 's'
|
||||
},
|
||||
|
||||
debounce (func, ms) {
|
||||
debounce(func, ms) {
|
||||
let timeout
|
||||
return function () {
|
||||
clearTimeout(timeout)
|
||||
@@ -13,9 +13,11 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
sleep (ms) {
|
||||
sleep(ms) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => { resolve() }, ms)
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, ms)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import events from '@/lib/utils/events'
|
||||
let refresh = false
|
||||
|
||||
function invokeServiceWorkerUpdateFlow (registration) {
|
||||
function invokeServiceWorkerUpdateFlow(registration) {
|
||||
const agree = confirm('New version of the app is available. Refresh now?')
|
||||
if (agree) {
|
||||
if (registration.waiting) {
|
||||
@@ -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) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import Tab from '@/lib/tab'
|
||||
import { nanoid } from 'nanoid'
|
||||
|
||||
export default {
|
||||
async addTab ({ state }, inquiry = {}) {
|
||||
async addTab({ state }, inquiry = {}) {
|
||||
// add new tab only if it was not already opened
|
||||
if (!state.tabs.some(openedTab => openedTab.id === inquiry.id)) {
|
||||
const tab = new Tab(state, JSON.parse(JSON.stringify(inquiry)))
|
||||
@@ -15,7 +15,7 @@ export default {
|
||||
|
||||
return inquiry.id
|
||||
},
|
||||
async saveInquiry ({ state }, { inquiryTab, newName }) {
|
||||
async saveInquiry({ state }, { inquiryTab, newName }) {
|
||||
const value = {
|
||||
id: inquiryTab.isPredefined ? nanoid() : inquiryTab.id,
|
||||
query: inquiryTab.query,
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -44,10 +46,10 @@ export default {
|
||||
|
||||
return value
|
||||
},
|
||||
addInquiry ({ state }, newInquiry) {
|
||||
addInquiry({ state }, newInquiry) {
|
||||
state.inquiries.push(newInquiry)
|
||||
},
|
||||
deleteInquiries ({ state, commit }, inquiryIdSet) {
|
||||
deleteInquiries({ state, commit }, inquiryIdSet) {
|
||||
state.inquiries = state.inquiries.filter(
|
||||
inquiry => !inquiryIdSet.has(inquiry.id)
|
||||
)
|
||||
@@ -62,9 +64,10 @@ export default {
|
||||
i--
|
||||
}
|
||||
},
|
||||
renameInquiry ({ state, commit }, { inquiryId, newName }) {
|
||||
const renamingInquiry = state.inquiries
|
||||
.find(inquiry => inquiry.id === inquiryId)
|
||||
renameInquiry({ state, commit }, { inquiryId, newName }) {
|
||||
const renamingInquiry = state.inquiries.find(
|
||||
inquiry => inquiry.id === inquiryId
|
||||
)
|
||||
|
||||
renamingInquiry.name = newName
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
export default {
|
||||
setDb (state, db) {
|
||||
setDb(state, db) {
|
||||
if (state.db) {
|
||||
state.db.shutDown()
|
||||
}
|
||||
state.db = db
|
||||
},
|
||||
|
||||
updateTab (state, { tab, newValues }) {
|
||||
updateTab(state, { tab, newValues }) {
|
||||
const { name, id, query, viewType, viewOptions, isSaved } = newValues
|
||||
const oldId = tab.id
|
||||
|
||||
@@ -14,19 +14,31 @@ 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
|
||||
}
|
||||
},
|
||||
|
||||
deleteTab (state, tab) {
|
||||
deleteTab(state, tab) {
|
||||
const index = state.tabs.indexOf(tab)
|
||||
// If closing tab is the current opened
|
||||
if (tab.id === state.currentTabId) {
|
||||
@@ -44,27 +56,29 @@ export default {
|
||||
}
|
||||
state.tabs.splice(index, 1)
|
||||
},
|
||||
setCurrentTabId (state, id) {
|
||||
setCurrentTabId(state, id) {
|
||||
try {
|
||||
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]
|
||||
updatePredefinedInquiries(state, inquiries) {
|
||||
state.predefinedInquiries = Array.isArray(inquiries)
|
||||
? inquiries
|
||||
: [inquiries]
|
||||
},
|
||||
setLoadingPredefinedInquiries (state, value) {
|
||||
setLoadingPredefinedInquiries(state, value) {
|
||||
state.loadingPredefinedInquiries = value
|
||||
},
|
||||
setPredefinedInquiriesLoaded (state, value) {
|
||||
setPredefinedInquiriesLoaded(state, value) {
|
||||
state.predefinedInquiriesLoaded = value
|
||||
},
|
||||
setInquiries (state, value) {
|
||||
setInquiries(state, value) {
|
||||
state.inquiries = value
|
||||
},
|
||||
setIsWorkspaceVisible (state, value) {
|
||||
setIsWorkspaceVisible(state, value) {
|
||||
state.isWorkspaceVisible = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
tooltipStyle: {
|
||||
visibility: 'hidden'
|
||||
@@ -7,13 +7,15 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tooltipElement () {
|
||||
tooltipElement() {
|
||||
return this.$refs.tooltip
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showTooltip (e, tooltipPosition) {
|
||||
const position = tooltipPosition ? tooltipPosition.split('-') : ['top', 'right']
|
||||
showTooltip(e, tooltipPosition) {
|
||||
const position = tooltipPosition
|
||||
? tooltipPosition.split('-')
|
||||
: ['top', 'right']
|
||||
const offset = 12
|
||||
|
||||
if (position[0] === 'top') {
|
||||
@@ -25,12 +27,13 @@ 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'
|
||||
},
|
||||
hideTooltip () {
|
||||
hideTooltip() {
|
||||
this.tooltipStyle.visibility = 'hidden'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<logs
|
||||
id="logs"
|
||||
:messages="messages"
|
||||
/>
|
||||
<button
|
||||
v-if="hasErrors"
|
||||
id="open-workspace-btn"
|
||||
class="secondary"
|
||||
@click="$router.push('/workspace?hide_schema=1')">
|
||||
Open workspace
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<logs id="logs" :messages="messages" />
|
||||
<button
|
||||
v-if="hasErrors"
|
||||
id="open-workspace-btn"
|
||||
class="secondary"
|
||||
@click="$router.push('/workspace?hide_schema=1')"
|
||||
>
|
||||
Open workspace
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -25,7 +23,7 @@ export default {
|
||||
components: {
|
||||
Logs
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
newDb: null,
|
||||
messages: [],
|
||||
@@ -34,11 +32,11 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasErrors () {
|
||||
hasErrors() {
|
||||
return this.dataMsg.type === 'error' || this.inquiryMsg.type === 'error'
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
async created() {
|
||||
const {
|
||||
data_url: dataUrl,
|
||||
data_format: dataFormat,
|
||||
@@ -65,7 +63,7 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadData (dataUrl, dataFormat) {
|
||||
async loadData(dataUrl, dataFormat) {
|
||||
this.newDb = database.getNewDatabase()
|
||||
if (dataUrl) {
|
||||
this.dataMsg = {
|
||||
@@ -95,7 +93,7 @@ export default {
|
||||
}
|
||||
this.$store.commit('setDb', this.newDb)
|
||||
},
|
||||
async getSqliteDb (dataUrl) {
|
||||
async getSqliteDb(dataUrl) {
|
||||
try {
|
||||
const filename = new URL(dataUrl).pathname.split('/').pop()
|
||||
const res = await fu.readFile(dataUrl)
|
||||
@@ -114,7 +112,7 @@ export default {
|
||||
this.dataMsg.type = 'error'
|
||||
}
|
||||
},
|
||||
async loadInquiries (inquiryUrl, inquiryIds = []) {
|
||||
async loadInquiries(inquiryUrl, inquiryIds = []) {
|
||||
if (!inquiryUrl) {
|
||||
return []
|
||||
}
|
||||
@@ -148,7 +146,7 @@ export default {
|
||||
// Loading indicator is not needed anymore
|
||||
clearTimeout(loadingInquiriesIndicator)
|
||||
},
|
||||
async openInquiries (inquiries, maximize) {
|
||||
async openInquiries(inquiries, maximize) {
|
||||
let tabToOpen = null
|
||||
const layout = maximize ? this.getLayout(maximize) : undefined
|
||||
for (const inquiry of inquiries) {
|
||||
@@ -167,7 +165,7 @@ export default {
|
||||
this.$store.state.currentTab.execute()
|
||||
},
|
||||
|
||||
getLayout (panelToMaximize) {
|
||||
getLayout(panelToMaximize) {
|
||||
if (panelToMaximize === 'dataView') {
|
||||
return {
|
||||
sqlEditor: 'hidden',
|
||||
@@ -190,7 +188,6 @@ export default {
|
||||
#logs {
|
||||
margin: 8px auto;
|
||||
max-width: 800px;
|
||||
|
||||
}
|
||||
|
||||
#open-workspace-btn {
|
||||
|
||||
@@ -5,22 +5,18 @@
|
||||
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')"/>
|
||||
<close-icon @click="$modal.hide('app-info')" />
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<div v-for="(item, index) in info" :key="index" class="info-item">
|
||||
{{item.name}}
|
||||
<div class="divider"/>
|
||||
{{ item.name }}
|
||||
<div class="divider" />
|
||||
<div class="options">
|
||||
<div v-for="(opt, index) in item.info" :key="index">
|
||||
{{opt}}
|
||||
{{ opt }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -36,7 +32,7 @@ import { version } from '../../../package.json'
|
||||
export default {
|
||||
name: 'AppDiagnosticInfo',
|
||||
components: { CloseIcon },
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
info: [
|
||||
{
|
||||
@@ -47,7 +43,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
async created () {
|
||||
async created() {
|
||||
const state = this.$store.state
|
||||
let result = (await state.db.execute('select sqlite_version()')).values
|
||||
this.info.push({
|
||||
@@ -94,7 +90,7 @@ export default {
|
||||
}
|
||||
.info-item {
|
||||
margin-bottom: 32px;
|
||||
font-size: 14px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.info-item:last-child {
|
||||
margin-bottom: 0;
|
||||
|
||||
@@ -10,13 +10,21 @@
|
||||
id="loading-predefined-status"
|
||||
v-if="$store.state.loadingPredefinedInquiries"
|
||||
>
|
||||
<loading-indicator/>
|
||||
<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>
|
||||
|
||||
@@ -46,27 +58,32 @@
|
||||
</div>
|
||||
|
||||
<div v-show="showedInquiries.length > 0" class="rounded-bg">
|
||||
<div class="header-container">
|
||||
<div>
|
||||
<div class="fixed-header" ref="name-th">
|
||||
<check-box ref="mainCheckBox" theme="light" @click="toggleSelectAll"/>
|
||||
<div class="name-th">Name</div>
|
||||
</div>
|
||||
<div class="fixed-header">
|
||||
Created at
|
||||
<div class="header-container">
|
||||
<div>
|
||||
<div class="fixed-header" ref="name-th">
|
||||
<check-box
|
||||
ref="mainCheckBox"
|
||||
theme="light"
|
||||
@click="toggleSelectAll"
|
||||
/>
|
||||
<div class="name-th">Name</div>
|
||||
</div>
|
||||
<div class="fixed-header">Created at</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table-container" :style="{ 'max-height': `${maxTableHeight}px` }">
|
||||
<table ref="table" class="sqliteviz-table">
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(inquiry, index) in showedInquiries"
|
||||
:key="inquiry.id"
|
||||
@click="openInquiry(index)"
|
||||
>
|
||||
<td ref="name-td">
|
||||
<div class="cell-data">
|
||||
<div
|
||||
class="table-container"
|
||||
:style="{ 'max-height': `${maxTableHeight}px` }"
|
||||
>
|
||||
<table ref="table" class="sqliteviz-table">
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(inquiry, index) in showedInquiries"
|
||||
:key="inquiry.id"
|
||||
@click="openInquiry(index)"
|
||||
>
|
||||
<td ref="name-td">
|
||||
<div class="cell-data">
|
||||
<check-box
|
||||
ref="rowCheckBox"
|
||||
:init="selectAll || selectedInquiriesIds.has(inquiry.id)"
|
||||
@@ -81,82 +98,89 @@
|
||||
@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>
|
||||
</td>
|
||||
<td>
|
||||
<div class="second-column">
|
||||
<div class="date-container">
|
||||
{{ createdAtFormatted(inquiry.createdAt) }}
|
||||
</div>
|
||||
<div class="icons-container">
|
||||
<rename-icon
|
||||
v-if="!inquiry.isPredefined"
|
||||
@click="showRenameDialog(inquiry.id)"
|
||||
/>
|
||||
<copy-icon @click="duplicateInquiry(index)"/>
|
||||
<export-icon
|
||||
@click="exportToFile([inquiry], `${inquiry.name}.json`)"
|
||||
tooltip="Export inquiry to file"
|
||||
tooltip-position="top-left"
|
||||
/>
|
||||
<delete-icon
|
||||
v-if="!inquiry.isPredefined"
|
||||
@click="showDeleteDialog((new Set()).add(inquiry.id))"
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<div class="second-column">
|
||||
<div class="date-container">
|
||||
{{ createdAtFormatted(inquiry.createdAt) }}
|
||||
</div>
|
||||
<div class="icons-container">
|
||||
<rename-icon
|
||||
v-if="!inquiry.isPredefined"
|
||||
@click="showRenameDialog(inquiry.id)"
|
||||
/>
|
||||
<copy-icon @click="duplicateInquiry(index)" />
|
||||
<export-icon
|
||||
@click="exportToFile([inquiry], `${inquiry.name}.json`)"
|
||||
tooltip="Export inquiry to file"
|
||||
tooltip-position="top-left"
|
||||
/>
|
||||
<delete-icon
|
||||
v-if="!inquiry.isPredefined"
|
||||
@click="showDeleteDialog(new Set().add(inquiry.id))"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--Rename Inquiry dialog -->
|
||||
<modal modal-id="rename" class="dialog" content-style="width: 560px;">
|
||||
<div class="dialog-header">
|
||||
Rename inquiry
|
||||
<close-icon @click="$modal.hide('rename')" />
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<text-field
|
||||
label="New inquiry name"
|
||||
:error-msg="errorMsg"
|
||||
v-model="newName"
|
||||
width="100%"
|
||||
/>
|
||||
</div>
|
||||
<div class="dialog-buttons-container">
|
||||
<button class="secondary" @click="$modal.hide('rename')">Cancel</button>
|
||||
<button class="primary" @click="renameInquiry">Rename</button>
|
||||
</div>
|
||||
</modal>
|
||||
|
||||
<!--Delete Inquiry dialog -->
|
||||
<modal modal-id="delete" class="dialog" content-style="width: 480px;">
|
||||
<div class="dialog-header">
|
||||
Delete {{ deleteGroup ? 'inquiries' : 'inquiry' }}
|
||||
<close-icon @click="$modal.hide('delete')" />
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
{{ deleteDialogMsg }}
|
||||
<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>
|
||||
<div class="dialog-buttons-container">
|
||||
<button class="secondary" @click="$modal.hide('delete')">Cancel</button>
|
||||
<button class="primary" @click="deleteInquiry">Delete</button>
|
||||
</div>
|
||||
</modal>
|
||||
</div>
|
||||
|
||||
<!--Rename Inquiry dialog -->
|
||||
<modal modal-id="rename" class="dialog" content-style="width: 560px;">
|
||||
<div class="dialog-header">
|
||||
Rename inquiry
|
||||
<close-icon @click="$modal.hide('rename')"/>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<text-field
|
||||
label="New inquiry name"
|
||||
:error-msg="errorMsg"
|
||||
v-model="newName"
|
||||
width="100%"
|
||||
/>
|
||||
</div>
|
||||
<div class="dialog-buttons-container">
|
||||
<button class="secondary" @click="$modal.hide('rename')">Cancel</button>
|
||||
<button class="primary" @click="renameInquiry">Rename</button>
|
||||
</div>
|
||||
</modal>
|
||||
|
||||
<!--Delete Inquiry dialog -->
|
||||
<modal modal-id="delete" class="dialog" content-style="width: 480px;">
|
||||
<div class="dialog-header">
|
||||
Delete {{ deleteGroup ? 'inquiries' : 'inquiry' }}
|
||||
<close-icon @click="$modal.hide('delete')"/>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
{{ deleteDialogMsg }}
|
||||
<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>
|
||||
<div class="dialog-buttons-container">
|
||||
<button class="secondary" @click="$modal.hide('delete')">Cancel</button>
|
||||
<button class="primary" @click="deleteInquiry">Delete</button>
|
||||
</div>
|
||||
</modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -185,7 +209,7 @@ export default {
|
||||
LoadingIndicator
|
||||
},
|
||||
mixins: [tooltipMixin],
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
filter: null,
|
||||
newName: null,
|
||||
@@ -201,47 +225,51 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
inquiries () {
|
||||
inquiries() {
|
||||
return this.$store.state.inquiries
|
||||
},
|
||||
predefinedInquiries () {
|
||||
predefinedInquiries() {
|
||||
return this.$store.state.predefinedInquiries.map(inquiry => {
|
||||
inquiry.isPredefined = true
|
||||
return inquiry
|
||||
})
|
||||
},
|
||||
predefinedInquiriesIds () {
|
||||
predefinedInquiriesIds() {
|
||||
return new Set(this.predefinedInquiries.map(inquiry => inquiry.id))
|
||||
},
|
||||
showedInquiries () {
|
||||
showedInquiries() {
|
||||
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
|
||||
},
|
||||
|
||||
allInquiries () {
|
||||
allInquiries() {
|
||||
return this.predefinedInquiries.concat(this.inquiries)
|
||||
},
|
||||
processedInquiryIndex () {
|
||||
return this.inquiries.findIndex(inquiry => inquiry.id === this.processedInquiryId)
|
||||
processedInquiryIndex() {
|
||||
return this.inquiries.findIndex(
|
||||
inquiry => inquiry.id === this.processedInquiryId
|
||||
)
|
||||
},
|
||||
deleteDialogMsg () {
|
||||
if (!this.deleteGroup && (
|
||||
this.processedInquiryIndex === null ||
|
||||
deleteDialogMsg() {
|
||||
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}?`
|
||||
@@ -249,14 +277,16 @@ export default {
|
||||
},
|
||||
watch: {
|
||||
showedInquiries: {
|
||||
handler () {
|
||||
this.selectedInquiriesIds = new Set(this.showedInquiries
|
||||
.filter(inquiry => this.selectedInquiriesIds.has(inquiry.id))
|
||||
.map(inquiry => inquiry.id)
|
||||
handler() {
|
||||
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) {
|
||||
@@ -268,9 +298,11 @@ export default {
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
const loadingPredefinedInquiries = this.$store.state.loadingPredefinedInquiries
|
||||
const predefinedInquiriesLoaded = this.$store.state.predefinedInquiriesLoaded
|
||||
async created() {
|
||||
const loadingPredefinedInquiries =
|
||||
this.$store.state.loadingPredefinedInquiries
|
||||
const predefinedInquiriesLoaded =
|
||||
this.$store.state.predefinedInquiriesLoaded
|
||||
if (!predefinedInquiriesLoaded && !loadingPredefinedInquiries) {
|
||||
try {
|
||||
this.$store.commit('setLoadingPredefinedInquiries', true)
|
||||
@@ -283,7 +315,7 @@ export default {
|
||||
this.$store.commit('setLoadingPredefinedInquiries', false)
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
mounted() {
|
||||
this.resizeObserver = new ResizeObserver(this.calcMaxTableHeight)
|
||||
this.resizeObserver.observe(this.$refs['my-inquiries-content'])
|
||||
|
||||
@@ -292,15 +324,15 @@ export default {
|
||||
this.calcNameWidth()
|
||||
this.calcMaxTableHeight()
|
||||
},
|
||||
beforeUnmount () {
|
||||
beforeUnmount() {
|
||||
this.resizeObserver.unobserve(this.$refs['my-inquiries-content'])
|
||||
this.tableResizeObserver.unobserve(this.$refs.table)
|
||||
},
|
||||
methods: {
|
||||
emitCreateTabEvent () {
|
||||
emitCreateTabEvent() {
|
||||
eventBus.$emit('createNewInquiry')
|
||||
},
|
||||
createdAtFormatted (value) {
|
||||
createdAtFormatted(value) {
|
||||
if (!value) {
|
||||
return ''
|
||||
}
|
||||
@@ -310,20 +342,24 @@ export default {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
}
|
||||
return new Date(value).toLocaleDateString('en-GB', dateOptions) + ' ' +
|
||||
new Date(value).toLocaleTimeString('en-GB', timeOptions)
|
||||
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]
|
||||
? this.$refs['name-td'][0].getBoundingClientRect().width
|
||||
: 0
|
||||
calcNameWidth() {
|
||||
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`
|
||||
},
|
||||
calcMaxTableHeight () {
|
||||
calcMaxTableHeight() {
|
||||
const freeSpace = this.$refs['my-inquiries-content'].offsetHeight - 200
|
||||
this.maxTableHeight = freeSpace - (freeSpace % 40) + 1
|
||||
},
|
||||
openInquiry (index) {
|
||||
openInquiry(index) {
|
||||
const tab = this.showedInquiries[index]
|
||||
setTimeout(() => {
|
||||
this.$store.dispatch('addTab', tab).then(id => {
|
||||
@@ -332,13 +368,13 @@ export default {
|
||||
})
|
||||
})
|
||||
},
|
||||
showRenameDialog (id) {
|
||||
showRenameDialog(id) {
|
||||
this.errorMsg = null
|
||||
this.processedInquiryId = id
|
||||
this.newName = this.inquiries[this.processedInquiryIndex].name
|
||||
this.$modal.show('rename')
|
||||
},
|
||||
renameInquiry () {
|
||||
renameInquiry() {
|
||||
if (!this.newName) {
|
||||
this.errorMsg = "Inquiry name can't be empty"
|
||||
return
|
||||
@@ -351,21 +387,26 @@ export default {
|
||||
// hide dialog
|
||||
this.$modal.hide('rename')
|
||||
},
|
||||
duplicateInquiry (index) {
|
||||
const newInquiry = storedInquiries.duplicateInquiry(this.showedInquiries[index])
|
||||
duplicateInquiry(index) {
|
||||
const newInquiry = storedInquiries.duplicateInquiry(
|
||||
this.showedInquiries[index]
|
||||
)
|
||||
this.$store.dispatch('addInquiry', newInquiry)
|
||||
},
|
||||
showDeleteDialog (idsSet) {
|
||||
showDeleteDialog(idsSet) {
|
||||
this.deleteGroup = idsSet.size > 1
|
||||
if (!this.deleteGroup) {
|
||||
this.processedInquiryId = idsSet.values().next().value
|
||||
}
|
||||
this.$modal.show('delete')
|
||||
},
|
||||
deleteInquiry () {
|
||||
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)) {
|
||||
@@ -379,27 +420,31 @@ export default {
|
||||
}
|
||||
this.selectedInquiriesCount = this.selectedInquiriesIds.size
|
||||
},
|
||||
exportToFile (inquiryList, fileName) {
|
||||
exportToFile(inquiryList, fileName) {
|
||||
storedInquiries.export(inquiryList, fileName)
|
||||
},
|
||||
exportSelectedInquiries () {
|
||||
const inquiryList = this.allInquiries.filter(
|
||||
inquiry => this.selectedInquiriesIds.has(inquiry.id)
|
||||
exportSelectedInquiries() {
|
||||
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))
|
||||
})
|
||||
importInquiries() {
|
||||
storedInquiries.importInquiries().then(importedInquiries => {
|
||||
this.$store.commit(
|
||||
'setInquiries',
|
||||
this.inquiries.concat(importedInquiries)
|
||||
)
|
||||
})
|
||||
},
|
||||
|
||||
toggleSelectAll (checked) {
|
||||
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,12 +452,13 @@ 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
|
||||
},
|
||||
|
||||
toggleRow (checked, id) {
|
||||
toggleRow(checked, id) {
|
||||
const isPredefined = this.predefinedInquiriesIds.has(id)
|
||||
if (checked) {
|
||||
this.selectedInquiriesIds.add(id)
|
||||
|
||||
@@ -34,7 +34,7 @@ export default {
|
||||
emits: ['click'],
|
||||
mixins: [tooltipMixin],
|
||||
methods: {
|
||||
onClick () {
|
||||
onClick() {
|
||||
this.hideTooltip()
|
||||
this.$emit('click')
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export default {
|
||||
emits: ['click'],
|
||||
mixins: [tooltipMixin],
|
||||
methods: {
|
||||
onClick () {
|
||||
onClick() {
|
||||
this.hideTooltip()
|
||||
this.$emit('click')
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ export default {
|
||||
emits: ['click'],
|
||||
mixins: [tooltipMixin],
|
||||
methods: {
|
||||
onClick () {
|
||||
onClick() {
|
||||
this.hideTooltip()
|
||||
this.$emit('click')
|
||||
}
|
||||
|
||||
@@ -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 />
|
||||
@@ -32,13 +28,13 @@
|
||||
<modal modal-id="save" class="dialog" content-style="width: 560px;">
|
||||
<div class="dialog-header">
|
||||
Save inquiry
|
||||
<close-icon @click="cancelSave"/>
|
||||
<close-icon @click="cancelSave" />
|
||||
</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"
|
||||
@@ -70,36 +66,39 @@ export default {
|
||||
CloseIcon,
|
||||
AppDiagnosticInfo
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
name: '',
|
||||
errorMsg: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentInquiry () {
|
||||
currentInquiry() {
|
||||
return this.$store.state.currentTab
|
||||
},
|
||||
isSaved () {
|
||||
isSaved() {
|
||||
return this.currentInquiry && this.currentInquiry.isSaved
|
||||
},
|
||||
isPredefined () {
|
||||
isPredefined() {
|
||||
return this.currentInquiry && this.currentInquiry.isPredefined
|
||||
},
|
||||
runDisabled () {
|
||||
return this.currentInquiry && (!this.$store.state.db || !this.currentInquiry.query)
|
||||
runDisabled() {
|
||||
return (
|
||||
this.currentInquiry &&
|
||||
(!this.$store.state.db || !this.currentInquiry.query)
|
||||
)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
created() {
|
||||
eventBus.$on('createNewInquiry', this.createNewInquiry)
|
||||
eventBus.$on('saveInquiry', this.checkInquiryBeforeSave)
|
||||
document.addEventListener('keydown', this._keyListener)
|
||||
},
|
||||
beforeUnmount () {
|
||||
beforeUnmount() {
|
||||
document.removeEventListener('keydown', this._keyListener)
|
||||
},
|
||||
methods: {
|
||||
createNewInquiry () {
|
||||
createNewInquiry() {
|
||||
this.$store.dispatch('addTab').then(id => {
|
||||
this.$store.commit('setCurrentTabId', id)
|
||||
if (this.$route.path !== '/workspace') {
|
||||
@@ -109,11 +108,11 @@ export default {
|
||||
|
||||
events.send('inquiry.create', null, { auto: false })
|
||||
},
|
||||
cancelSave () {
|
||||
cancelSave() {
|
||||
this.$modal.hide('save')
|
||||
eventBus.$off('inquirySaved')
|
||||
},
|
||||
checkInquiryBeforeSave () {
|
||||
checkInquiryBeforeSave() {
|
||||
this.errorMsg = null
|
||||
this.name = ''
|
||||
|
||||
@@ -123,10 +122,10 @@ export default {
|
||||
this.saveInquiry()
|
||||
}
|
||||
},
|
||||
async saveInquiry () {
|
||||
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
|
||||
@@ -168,7 +167,7 @@ export default {
|
||||
eventBus.$emit('inquirySaved')
|
||||
events.send('inquiry.save')
|
||||
},
|
||||
_keyListener (e) {
|
||||
_keyListener(e) {
|
||||
if (this.$route.path === '/workspace') {
|
||||
// Run query Ctrl+R or Ctrl+Enter
|
||||
if ((e.key === 'r' || e.key === 'Enter') && (e.ctrlKey || e.metaKey)) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div @click="colVisible = !colVisible" class="table-name">
|
||||
<tree-chevron :expanded="colVisible"/>
|
||||
<tree-chevron :expanded="colVisible" />
|
||||
{{ name }}
|
||||
</div>
|
||||
<div v-show="colVisible" class="columns">
|
||||
@@ -20,7 +20,7 @@ export default {
|
||||
name: 'TableDescription',
|
||||
components: { TreeChevron },
|
||||
props: ['name', 'columns'],
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
colVisible: false
|
||||
}
|
||||
@@ -29,7 +29,8 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.table-name, .column {
|
||||
.table-name,
|
||||
.column {
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<template>
|
||||
<div id="schema-container">
|
||||
<div id="schema-filter">
|
||||
<text-field placeholder="Search table" width="100%" v-model="filter"/>
|
||||
<text-field placeholder="Search table" width="100%" v-model="filter" />
|
||||
</div>
|
||||
<div id="db">
|
||||
<div @click="schemaVisible = !schemaVisible" class="db-name">
|
||||
<tree-chevron v-show="schema.length > 0" :expanded="schemaVisible"/>
|
||||
<tree-chevron v-show="schema.length > 0" :expanded="schemaVisible" />
|
||||
{{ dbName }}
|
||||
</div>
|
||||
<db-uploader id="db-edit" type="small" />
|
||||
<export-icon tooltip="Export database" @click="exportToFile"/>
|
||||
<add-table-icon @click="addCsvJson"/>
|
||||
<export-icon tooltip="Export database" @click="exportToFile" />
|
||||
<add-table-icon @click="addCsvJson" />
|
||||
</div>
|
||||
<div v-show="schemaVisible" class="schema">
|
||||
<table-description
|
||||
@@ -53,7 +53,7 @@ export default {
|
||||
AddTableIcon,
|
||||
CsvJsonImport
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
schemaVisible: true,
|
||||
filter: null,
|
||||
@@ -61,7 +61,7 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
schema () {
|
||||
schema() {
|
||||
if (!this.$store.state.db.schema) {
|
||||
return []
|
||||
}
|
||||
@@ -69,18 +69,19 @@ 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 () {
|
||||
dbName() {
|
||||
return this.$store.state.db.dbName
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
exportToFile () {
|
||||
exportToFile() {
|
||||
this.$store.state.db.export(`${this.dbName}.sqlite`)
|
||||
},
|
||||
async addCsvJson () {
|
||||
async addCsvJson() {
|
||||
this.file = await fIo.getFileFromUser('.csv,.json,.ndjson')
|
||||
await this.$nextTick()
|
||||
const csvJsonImportModal = this.$refs.addCsvJson
|
||||
@@ -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"
|
||||
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"
|
||||
@@ -21,7 +29,7 @@
|
||||
@render="onRender"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -38,15 +46,17 @@ 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'],
|
||||
components: {
|
||||
PlotlyEditor: applyPureReactInVue(ReactPlotlyEditor)
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
plotly,
|
||||
state: this.initOptions || {
|
||||
@@ -60,20 +70,23 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dataSourceOptions () {
|
||||
dataSourceOptions() {
|
||||
return chartHelper.getOptionsFromDataSources(this.dataSources)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
created() {
|
||||
// https://github.com/plotly/plotly.js/issues/4555
|
||||
plotly.setPlotConfig({
|
||||
notifyOnLogging: 1
|
||||
})
|
||||
this.$watch(
|
||||
() => this.state && this.state.data && this.state.data
|
||||
.map(trace => `${trace.type}${trace.mode ? '-' + trace.mode : ''}`)
|
||||
.join(','),
|
||||
(value) => {
|
||||
() =>
|
||||
this.state &&
|
||||
this.state.data &&
|
||||
this.state.data
|
||||
.map(trace => `${trace.type}${trace.mode ? '-' + trace.mode : ''}`)
|
||||
.join(','),
|
||||
value => {
|
||||
events.send('viz_plotly.render', null, {
|
||||
type: value,
|
||||
pivot: !!this.forPivot
|
||||
@@ -83,21 +96,21 @@ export default {
|
||||
)
|
||||
this.$emit('update:importToSvgEnabled', true)
|
||||
},
|
||||
mounted () {
|
||||
mounted() {
|
||||
this.resizeObserver = new ResizeObserver(this.handleResize)
|
||||
this.resizeObserver.observe(this.$refs.chartContainer)
|
||||
},
|
||||
activated () {
|
||||
activated() {
|
||||
this.useResizeHandler = true
|
||||
},
|
||||
deactivated () {
|
||||
deactivated() {
|
||||
this.useResizeHandler = false
|
||||
},
|
||||
beforeUnmount () {
|
||||
beforeUnmount() {
|
||||
this.resizeObserver.unobserve(this.$refs.chartContainer)
|
||||
},
|
||||
watch: {
|
||||
dataSources () {
|
||||
dataSources() {
|
||||
// we need to update state.data in order to update the graph
|
||||
// https://github.com/plotly/react-chart-editor/issues/948
|
||||
if (this.dataSources) {
|
||||
@@ -106,41 +119,44 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async handleResize () {
|
||||
async handleResize() {
|
||||
this.visible = false
|
||||
await this.$nextTick()
|
||||
this.visible = true
|
||||
},
|
||||
onRender (data, layout, frames) {
|
||||
onRender(data, layout, frames) {
|
||||
// TODO: check changes and enable Save button if needed
|
||||
},
|
||||
update (data, layout, frames) {
|
||||
update(data, layout, frames) {
|
||||
this.state = { data, layout, frames }
|
||||
this.$emit('update')
|
||||
},
|
||||
getOptionsForSave () {
|
||||
getOptionsForSave() {
|
||||
return chartHelper.getOptionsForSave(this.state, this.dataSources)
|
||||
},
|
||||
async saveAsPng () {
|
||||
async saveAsPng() {
|
||||
const url = await this.prepareCopy()
|
||||
this.$emit('loadingImageCompleted')
|
||||
fIo.downloadFromUrl(url, 'chart')
|
||||
},
|
||||
|
||||
async saveAsSvg () {
|
||||
async saveAsSvg() {
|
||||
const url = await this.prepareCopy('svg')
|
||||
fIo.downloadFromUrl(url, 'chart')
|
||||
},
|
||||
|
||||
saveAsHtml () {
|
||||
saveAsHtml() {
|
||||
fIo.exportToFile(
|
||||
chartHelper.getHtml(this.state),
|
||||
'chart.html',
|
||||
'text/html'
|
||||
)
|
||||
},
|
||||
async prepareCopy (type = 'png') {
|
||||
return await chartHelper.getImageDataUrl(this.$refs.plotlyEditor.$el, type)
|
||||
async prepareCopy(type = 'png') {
|
||||
return await chartHelper.getImageDataUrl(
|
||||
this.$refs.plotlyEditor.$el,
|
||||
type
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<template>
|
||||
<div :class="['pivot-sort-btn', direction] " @click="changeSorting">
|
||||
{{ modelValue.includes('key') ? 'key' : 'value' }}
|
||||
<sort-icon
|
||||
class="sort-icon"
|
||||
:horizontal="direction === 'col'"
|
||||
:asc="modelValue.includes('a_to_z')"
|
||||
/>
|
||||
<div :class="['pivot-sort-btn', direction]" @click="changeSorting">
|
||||
{{ modelValue.includes('key') ? 'key' : 'value' }}
|
||||
<sort-icon
|
||||
class="sort-icon"
|
||||
:horizontal="direction === 'col'"
|
||||
:asc="modelValue.includes('a_to_z')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -20,7 +20,7 @@ export default {
|
||||
SortIcon
|
||||
},
|
||||
methods: {
|
||||
changeSorting () {
|
||||
changeSorting() {
|
||||
if (this.modelValue === 'key_a_to_z') {
|
||||
this.$emit('update:modelValue', 'value_a_to_z')
|
||||
} else if (this.modelValue === 'value_a_to_z') {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="pivot-ui">
|
||||
<div :class="{collapsed}">
|
||||
<div :class="{ collapsed }">
|
||||
<div class="row">
|
||||
<label>Columns</label>
|
||||
<multiselect
|
||||
@@ -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',
|
||||
@@ -149,23 +154,35 @@ export default {
|
||||
Multiselect,
|
||||
PivotSortBtn
|
||||
},
|
||||
data () {
|
||||
const aggregatorName = (this.modelValue && this.modelValue.aggregatorName) || 'Count'
|
||||
const rendererName = (this.modelValue && this.modelValue.rendererName) || 'Table'
|
||||
data() {
|
||||
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'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
valCount () {
|
||||
valCount() {
|
||||
if (zeroValAggregators.includes(this.aggregator.name)) {
|
||||
return 0
|
||||
}
|
||||
@@ -176,47 +193,47 @@ export default {
|
||||
|
||||
return 1
|
||||
},
|
||||
renderers () {
|
||||
renderers() {
|
||||
return renderers
|
||||
},
|
||||
aggregators () {
|
||||
aggregators() {
|
||||
return aggregators
|
||||
},
|
||||
rowsToSelect () {
|
||||
rowsToSelect() {
|
||||
return this.keyNames.filter(key => !this.cols.includes(key))
|
||||
},
|
||||
colsToSelect () {
|
||||
colsToSelect() {
|
||||
return this.keyNames.filter(key => !this.rows.includes(key))
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
renderer () {
|
||||
renderer() {
|
||||
this.returnValue()
|
||||
},
|
||||
aggregator () {
|
||||
aggregator() {
|
||||
this.returnValue()
|
||||
},
|
||||
rows () {
|
||||
rows() {
|
||||
this.returnValue()
|
||||
},
|
||||
cols () {
|
||||
cols() {
|
||||
this.returnValue()
|
||||
},
|
||||
val1 () {
|
||||
val1() {
|
||||
this.returnValue()
|
||||
},
|
||||
val2 () {
|
||||
val2() {
|
||||
this.returnValue()
|
||||
},
|
||||
colOrder () {
|
||||
colOrder() {
|
||||
this.returnValue()
|
||||
},
|
||||
rowOrder () {
|
||||
rowOrder() {
|
||||
this.returnValue()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
returnValue () {
|
||||
returnValue() {
|
||||
const vals = []
|
||||
for (let i = 1; i <= this.valCount; i++) {
|
||||
vals.push(this[`val${i}`])
|
||||
@@ -281,7 +298,6 @@ export default {
|
||||
white-space: nowrap;
|
||||
margin: auto;
|
||||
cursor: pointer;
|
||||
|
||||
}
|
||||
|
||||
.switcher:hover {
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
<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.
|
||||
</div>
|
||||
<pivot-ui
|
||||
:key-names="columns"
|
||||
v-model="pivotOptions"
|
||||
@update="$emit('update')"
|
||||
/>
|
||||
<div ref="pivotOutput" class="pivot-output"/>
|
||||
<div
|
||||
ref="customChartOutput"
|
||||
v-show="viewCustomChart"
|
||||
class="custom-chart-output"
|
||||
>
|
||||
<chart
|
||||
ref="customChart"
|
||||
v-bind="customChartComponentProps"
|
||||
<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.
|
||||
</div>
|
||||
<pivot-ui
|
||||
:key-names="columns"
|
||||
v-model="pivotOptions"
|
||||
@update="$emit('update')"
|
||||
@loadingImageCompleted="$emit('loadingImageCompleted')"
|
||||
/>
|
||||
<div ref="pivotOutput" class="pivot-output" />
|
||||
<div
|
||||
ref="customChartOutput"
|
||||
v-show="viewCustomChart"
|
||||
class="custom-chart-output"
|
||||
>
|
||||
<chart
|
||||
ref="customChart"
|
||||
v-bind="customChartComponentProps"
|
||||
@update="$emit('update')"
|
||||
@loadingImageCompleted="$emit('loadingImageCompleted')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -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',
|
||||
@@ -48,7 +54,7 @@ export default {
|
||||
PivotUi,
|
||||
Chart
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
resizeObserver: null,
|
||||
pivotOptions: !this.initOptions
|
||||
@@ -83,25 +89,25 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns () {
|
||||
columns() {
|
||||
return Object.keys(this.dataSources || {})
|
||||
},
|
||||
|
||||
viewStandartChart () {
|
||||
viewStandartChart() {
|
||||
return this.pivotOptions.rendererName in $.pivotUtilities.plotly_renderers
|
||||
},
|
||||
|
||||
viewCustomChart () {
|
||||
viewCustomChart() {
|
||||
return this.pivotOptions.rendererName === 'Custom chart'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
dataSources () {
|
||||
dataSources() {
|
||||
this.show()
|
||||
},
|
||||
'pivotOptions.rendererName': {
|
||||
immediate: true,
|
||||
handler () {
|
||||
handler() {
|
||||
this.$emit(
|
||||
'update:importToPngEnabled',
|
||||
this.pivotOptions.rendererName !== 'TSV Export'
|
||||
@@ -115,22 +121,22 @@ export default {
|
||||
})
|
||||
}
|
||||
},
|
||||
pivotOptions () {
|
||||
pivotOptions() {
|
||||
this.show()
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
mounted() {
|
||||
this.show()
|
||||
// We need to detect resizing because plotly doesn't resize when resize its container
|
||||
// but it resize on window.resize (we will trigger it manualy in order to make plotly resize)
|
||||
this.resizeObserver = new ResizeObserver(this.handleResize)
|
||||
this.resizeObserver.observe(this.$refs.customChartOutput)
|
||||
},
|
||||
beforeUnmount () {
|
||||
beforeUnmount() {
|
||||
this.resizeObserver.unobserve(this.$refs.customChartOutput)
|
||||
},
|
||||
methods: {
|
||||
handleResize () {
|
||||
handleResize() {
|
||||
// hack: plotly changes size only on window.resize event,
|
||||
// so, we trigger it when container resizes (e.g. when move splitter)
|
||||
if (this.viewStandartChart) {
|
||||
@@ -138,7 +144,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
show () {
|
||||
show() {
|
||||
const options = { ...this.pivotOptions }
|
||||
if (this.viewStandartChart) {
|
||||
options.rendererOptions = {
|
||||
@@ -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 => {
|
||||
@@ -181,7 +189,7 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
getOptionsForSave () {
|
||||
getOptionsForSave() {
|
||||
const options = { ...this.pivotOptions }
|
||||
if (this.viewCustomChart) {
|
||||
const chartComponent = this.$refs.customChart
|
||||
@@ -192,20 +200,22 @@ export default {
|
||||
return options
|
||||
},
|
||||
|
||||
async saveAsPng () {
|
||||
async saveAsPng() {
|
||||
if (this.viewCustomChart) {
|
||||
this.$refs.customChart.saveAsPng()
|
||||
} 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')
|
||||
}
|
||||
},
|
||||
|
||||
async prepareCopy () {
|
||||
async prepareCopy() {
|
||||
if (this.viewCustomChart) {
|
||||
return await this.$refs.customChart.prepareCopy()
|
||||
}
|
||||
@@ -215,16 +225,19 @@ export default {
|
||||
return await pivotHelper.getPivotCanvas(this.$refs.pivotOutput)
|
||||
},
|
||||
|
||||
async saveAsSvg () {
|
||||
async saveAsSvg() {
|
||||
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')
|
||||
}
|
||||
},
|
||||
|
||||
saveAsHtml () {
|
||||
saveAsHtml() {
|
||||
if (this.viewCustomChart) {
|
||||
this.$refs.customChart.saveAsHtml()
|
||||
return
|
||||
|
||||
@@ -17,7 +17,7 @@ export const twoValAggregators = [
|
||||
'80% Lower Bound'
|
||||
]
|
||||
|
||||
export function _getDataSources (pivotData) {
|
||||
export function _getDataSources(pivotData) {
|
||||
const rowKeys = pivotData.getRowKeys()
|
||||
const colKeys = pivotData.getColKeys()
|
||||
|
||||
@@ -49,7 +49,7 @@ export function _getDataSources (pivotData) {
|
||||
return Object.assign(dataSources, dataSourcesByCols, dataSourcesByRows)
|
||||
}
|
||||
|
||||
function customChartRenderer (data, options) {
|
||||
function customChartRenderer(data, options) {
|
||||
const propsRef = options.getCustomComponentsProps()
|
||||
propsRef.dataSources = _getDataSources(data)
|
||||
return null
|
||||
@@ -69,19 +69,21 @@ export const renderers = Object.keys($.pivotUtilities.renderers).map(key => {
|
||||
}
|
||||
})
|
||||
|
||||
export const aggregators = Object.keys($.pivotUtilities.aggregators).map(key => {
|
||||
return {
|
||||
name: key,
|
||||
fun: $.pivotUtilities.aggregators[key]
|
||||
export const aggregators = Object.keys($.pivotUtilities.aggregators).map(
|
||||
key => {
|
||||
return {
|
||||
name: key,
|
||||
fun: $.pivotUtilities.aggregators[key]
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
export async function getPivotCanvas (pivotOutput) {
|
||||
export async function getPivotCanvas(pivotOutput) {
|
||||
const tableElement = pivotOutput.querySelector('.pvtTable')
|
||||
return await html2canvas(tableElement, { logging: false })
|
||||
}
|
||||
|
||||
export function getPivotHtml (pivotOutput) {
|
||||
export function getPivotHtml(pivotOutput) {
|
||||
return `
|
||||
<style>
|
||||
table.pvtTable {
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<pivot-icon />
|
||||
</icon-button>
|
||||
|
||||
<div class="side-tool-bar-divider"/>
|
||||
<div class="side-tool-bar-divider" />
|
||||
|
||||
<icon-button
|
||||
:disabled="!importToPngEnabled || loadingImage"
|
||||
@@ -67,20 +67,20 @@
|
||||
tooltip-position="top-left"
|
||||
@click="prepareCopy"
|
||||
>
|
||||
<clipboard-icon/>
|
||||
<clipboard-icon />
|
||||
</icon-button>
|
||||
</side-tool-bar>
|
||||
|
||||
<loading-dialog
|
||||
loadingMsg="Rendering the visualisation..."
|
||||
successMsg="Image is ready"
|
||||
actionBtnName="Copy"
|
||||
name="prepareCopy"
|
||||
title="Copy to clipboard"
|
||||
:loading="preparingCopy"
|
||||
@action="copyToClipboard"
|
||||
@cancel="cancelCopy"
|
||||
/>
|
||||
<loading-dialog
|
||||
loadingMsg="Rendering the visualisation..."
|
||||
successMsg="Image is ready"
|
||||
actionBtnName="Copy"
|
||||
name="prepareCopy"
|
||||
title="Copy to clipboard"
|
||||
:loading="preparingCopy"
|
||||
@action="copyToClipboard"
|
||||
@cancel="cancelCopy"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -117,7 +117,7 @@ export default {
|
||||
ClipboardIcon,
|
||||
loadingDialog
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
mode: this.initMode || 'chart',
|
||||
importToPngEnabled: true,
|
||||
@@ -129,18 +129,18 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
plotlyInPivot () {
|
||||
plotlyInPivot() {
|
||||
return this.mode === 'pivot' && this.$refs.viewComponent.viewCustomChart
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
mode () {
|
||||
mode() {
|
||||
this.$emit('update')
|
||||
this.importToPngEnabled = true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async saveAsPng () {
|
||||
async saveAsPng() {
|
||||
this.loadingImage = true
|
||||
/*
|
||||
setTimeout does its thing by putting its callback on the callback queue.
|
||||
@@ -160,10 +160,10 @@ export default {
|
||||
this.$refs.viewComponent.saveAsPng()
|
||||
this.exportSignal('png')
|
||||
},
|
||||
getOptionsForSave () {
|
||||
getOptionsForSave() {
|
||||
return this.$refs.viewComponent.getOptionsForSave()
|
||||
},
|
||||
async prepareCopy () {
|
||||
async prepareCopy() {
|
||||
if ('ClipboardItem' in window) {
|
||||
this.preparingCopy = true
|
||||
this.$modal.show('prepareCopy')
|
||||
@@ -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 {
|
||||
@@ -181,30 +181,30 @@ export default {
|
||||
} else {
|
||||
alert(
|
||||
"Your browser doesn't support copying images into the clipboard. " +
|
||||
'If you use Firefox you can enable it ' +
|
||||
'by setting dom.events.asyncClipboard.clipboardItem to true.'
|
||||
'If you use Firefox you can enable it ' +
|
||||
'by setting dom.events.asyncClipboard.clipboardItem to true.'
|
||||
)
|
||||
}
|
||||
},
|
||||
async copyToClipboard () {
|
||||
async copyToClipboard() {
|
||||
cIo.copyImage(this.dataToCopy)
|
||||
this.$modal.hide('prepareCopy')
|
||||
this.exportSignal('clipboard')
|
||||
},
|
||||
cancelCopy () {
|
||||
cancelCopy() {
|
||||
this.dataToCopy = null
|
||||
this.$modal.hide('prepareCopy')
|
||||
},
|
||||
|
||||
saveAsSvg () {
|
||||
saveAsSvg() {
|
||||
this.$refs.viewComponent.saveAsSvg()
|
||||
this.exportSignal('svg')
|
||||
},
|
||||
saveAsHtml () {
|
||||
saveAsHtml() {
|
||||
this.$refs.viewComponent.saveAsHtml()
|
||||
this.exportSignal('html')
|
||||
},
|
||||
exportSignal (to) {
|
||||
exportSignal(to) {
|
||||
const eventLabels = { type: to }
|
||||
|
||||
if (this.mode === 'chart' || this.plotlyInPivot) {
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
<template>
|
||||
<div class="record-navigator">
|
||||
<icon-button
|
||||
:disabled="modelValue === 0"
|
||||
tooltip="First row"
|
||||
tooltip-position="top-left"
|
||||
class="first"
|
||||
@click="$emit('update:modelValue', 0)"
|
||||
>
|
||||
<edge-arrow-icon :disabled="false" />
|
||||
</icon-button>
|
||||
<icon-button
|
||||
:disabled="modelValue === 0"
|
||||
tooltip="Previous row"
|
||||
tooltip-position="top-left"
|
||||
class="prev"
|
||||
@click="$emit('update:modelValue', modelValue - 1)"
|
||||
>
|
||||
<arrow-icon :disabled="false" />
|
||||
</icon-button>
|
||||
<icon-button
|
||||
:disabled="modelValue === total - 1"
|
||||
tooltip="Next row"
|
||||
tooltip-position="top-left"
|
||||
class="next"
|
||||
@click="$emit('update:modelValue', modelValue + 1)"
|
||||
>
|
||||
<arrow-icon :disabled="false" />
|
||||
</icon-button>
|
||||
<icon-button
|
||||
:disabled="modelValue === total - 1"
|
||||
tooltip="Last row"
|
||||
tooltip-position="top-left"
|
||||
class="last"
|
||||
@click="$emit('update:modelValue', total - 1)"
|
||||
>
|
||||
<edge-arrow-icon :disabled="false" />
|
||||
</icon-button>
|
||||
</div>
|
||||
<div class="record-navigator">
|
||||
<icon-button
|
||||
:disabled="modelValue === 0"
|
||||
tooltip="First row"
|
||||
tooltip-position="top-left"
|
||||
class="first"
|
||||
@click="$emit('update:modelValue', 0)"
|
||||
>
|
||||
<edge-arrow-icon :disabled="false" />
|
||||
</icon-button>
|
||||
<icon-button
|
||||
:disabled="modelValue === 0"
|
||||
tooltip="Previous row"
|
||||
tooltip-position="top-left"
|
||||
class="prev"
|
||||
@click="$emit('update:modelValue', modelValue - 1)"
|
||||
>
|
||||
<arrow-icon :disabled="false" />
|
||||
</icon-button>
|
||||
<icon-button
|
||||
:disabled="modelValue === total - 1"
|
||||
tooltip="Next row"
|
||||
tooltip-position="top-left"
|
||||
class="next"
|
||||
@click="$emit('update:modelValue', modelValue + 1)"
|
||||
>
|
||||
<arrow-icon :disabled="false" />
|
||||
</icon-button>
|
||||
<icon-button
|
||||
:disabled="modelValue === total - 1"
|
||||
tooltip="Last row"
|
||||
tooltip-position="top-left"
|
||||
class="last"
|
||||
@click="$emit('update:modelValue', total - 1)"
|
||||
>
|
||||
<edge-arrow-icon :disabled="false" />
|
||||
</icon-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -59,7 +59,7 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.record-navigator {
|
||||
display: flex;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.record-navigator .next,
|
||||
|
||||
@@ -9,11 +9,9 @@
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th/>
|
||||
<th />
|
||||
<th>
|
||||
<div class="cell-data">
|
||||
Row #{{ currentRowIndex + 1 }}
|
||||
</div>
|
||||
<div class="cell-data">Row #{{ currentRowIndex + 1 }}</div>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -39,11 +37,11 @@
|
||||
</div>
|
||||
<div class="table-footer">
|
||||
<div class="table-footer-count">
|
||||
{{ rowCount }} {{rowCount === 1 ? 'row' : 'rows'}} retrieved
|
||||
{{ rowCount }} {{ rowCount === 1 ? 'row' : 'rows' }} retrieved
|
||||
<span v-if="time">in {{ time }}</span>
|
||||
</div>
|
||||
|
||||
<row-navigator v-model="currentRowIndex" :total="rowCount"/>
|
||||
<row-navigator v-model="currentRowIndex" :total="rowCount" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -61,31 +59,32 @@ export default {
|
||||
selectedColumnIndex: Number
|
||||
},
|
||||
emits: ['updateSelectedCell'],
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
selectedCellElement: null,
|
||||
currentRowIndex: this.rowIndex
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
columns () {
|
||||
columns() {
|
||||
return this.dataSet.columns
|
||||
},
|
||||
rowCount () {
|
||||
rowCount() {
|
||||
return this.dataSet.values[this.columns[0]].length
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
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)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
async currentRowIndex () {
|
||||
async currentRowIndex() {
|
||||
await nextTick()
|
||||
if (this.selectedCellElement) {
|
||||
const previouslySelected = this.selectedCellElement
|
||||
@@ -95,16 +94,16 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isBlob (value) {
|
||||
isBlob(value) {
|
||||
return value && ArrayBuffer.isView(value)
|
||||
},
|
||||
isNull (value) {
|
||||
isNull(value) {
|
||||
return value === null
|
||||
},
|
||||
getCellValue (col) {
|
||||
getCellValue(col) {
|
||||
return this.dataSet.values[col][this.currentRowIndex]
|
||||
},
|
||||
getCellText (col) {
|
||||
getCellText(col) {
|
||||
const value = this.getCellValue(col)
|
||||
if (this.isNull(value)) {
|
||||
return 'NULL'
|
||||
@@ -114,7 +113,7 @@ export default {
|
||||
}
|
||||
return value
|
||||
},
|
||||
onTableKeydown (e) {
|
||||
onTableKeydown(e) {
|
||||
const keyCodeMap = {
|
||||
38: 'up',
|
||||
40: 'down'
|
||||
@@ -130,10 +129,10 @@ export default {
|
||||
|
||||
this.moveFocusInTable(this.selectedCellElement, keyCodeMap[e.keyCode])
|
||||
},
|
||||
onCellClick (e) {
|
||||
onCellClick(e) {
|
||||
this.selectCell(e.target.closest('td'), false)
|
||||
},
|
||||
selectCell (cell, scrollTo = true) {
|
||||
selectCell(cell, scrollTo = true) {
|
||||
if (!cell) {
|
||||
if (this.selectedCellElement) {
|
||||
this.selectedCellElement.ariaSelected = 'false'
|
||||
@@ -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) {
|
||||
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"
|
||||
@@ -60,7 +55,7 @@ export default {
|
||||
props: {
|
||||
cellValue: [String, Number, Uint8Array]
|
||||
},
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
formats: [
|
||||
{ text: 'Text', value: 'text' },
|
||||
@@ -82,13 +77,13 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isBlob () {
|
||||
isBlob() {
|
||||
return this.cellValue && ArrayBuffer.isView(this.cellValue)
|
||||
},
|
||||
isNull () {
|
||||
isNull() {
|
||||
return this.cellValue === null
|
||||
},
|
||||
cellText () {
|
||||
cellText() {
|
||||
const value = this.cellValue
|
||||
if (this.isNull) {
|
||||
return 'NULL'
|
||||
@@ -100,14 +95,14 @@ export default {
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentFormat () {
|
||||
currentFormat() {
|
||||
this.messages = []
|
||||
this.formattedJson = ''
|
||||
if (this.currentFormat === 'json') {
|
||||
this.formatJson(this.cellValue)
|
||||
}
|
||||
},
|
||||
cellValue () {
|
||||
cellValue() {
|
||||
this.messages = []
|
||||
if (this.currentFormat === 'json') {
|
||||
this.formatJson(this.cellValue)
|
||||
@@ -115,25 +110,24 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatJson (jsonStr) {
|
||||
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 = [{
|
||||
type: 'error',
|
||||
message: 'Can\'t parse JSON.'
|
||||
}]
|
||||
this.messages = [
|
||||
{
|
||||
type: 'error',
|
||||
message: "Can't parse JSON."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
copyToClipboard () {
|
||||
cIo.copyText(this.currentFormat === 'json'
|
||||
? this.formattedJson
|
||||
: this.cellValue,
|
||||
'The value is copied to clipboard.'
|
||||
copyToClipboard() {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
<template>
|
||||
<div class="run-result-panel" ref="runResultPanel">
|
||||
<component
|
||||
:is="viewValuePanelVisible ? 'splitpanes':'div'"
|
||||
<component
|
||||
:is="viewValuePanelVisible ? 'splitpanes' : 'div'"
|
||||
:before="{ size: 50, max: 100 }"
|
||||
:after="{ size: 50, max: 100 }"
|
||||
:default="{ before: 50, after: 50 }"
|
||||
class="run-result-panel-content"
|
||||
>
|
||||
<template #left-pane>
|
||||
<div :id="'run-result-left-pane-'+tab.id" class="result-set-container"/>
|
||||
<template #left-pane>
|
||||
<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
|
||||
@@ -33,7 +43,7 @@
|
||||
tooltip-position="top-left"
|
||||
@click="exportToCsv"
|
||||
>
|
||||
<export-to-csv-icon/>
|
||||
<export-to-csv-icon />
|
||||
</icon-button>
|
||||
|
||||
<icon-button
|
||||
@@ -43,7 +53,7 @@
|
||||
tooltip-position="top-left"
|
||||
@click="prepareCopy"
|
||||
>
|
||||
<clipboard-icon/>
|
||||
<clipboard-icon />
|
||||
</icon-button>
|
||||
|
||||
<icon-button
|
||||
@@ -54,7 +64,7 @@
|
||||
:active="viewRecord"
|
||||
@click="toggleViewRecord"
|
||||
>
|
||||
<row-icon/>
|
||||
<row-icon />
|
||||
</icon-button>
|
||||
|
||||
<icon-button
|
||||
@@ -65,7 +75,7 @@
|
||||
:active="viewValuePanelVisible"
|
||||
@click="toggleViewValuePanel"
|
||||
>
|
||||
<view-cell-value-icon/>
|
||||
<view-cell-value-icon />
|
||||
</icon-button>
|
||||
</side-tool-bar>
|
||||
|
||||
@@ -80,50 +90,46 @@
|
||||
@cancel="cancelCopy"
|
||||
/>
|
||||
|
||||
<teleport
|
||||
defer
|
||||
:to="resultSetTeleportTarget"
|
||||
:disabled="!enableTeleport"
|
||||
>
|
||||
<teleport defer :to="resultSetTeleportTarget" :disabled="!enableTeleport">
|
||||
<div>
|
||||
<div
|
||||
v-show="result === null && !isGettingResults && !error"
|
||||
class="table-preview result-before"
|
||||
>
|
||||
Run your query and get results here
|
||||
</div>
|
||||
<div v-if="isGettingResults" class="table-preview result-in-progress">
|
||||
<loading-indicator :size="30"/>
|
||||
Fetching results...
|
||||
</div>
|
||||
<div
|
||||
v-show="result === undefined && !isGettingResults && !error"
|
||||
class="table-preview result-empty"
|
||||
>
|
||||
No rows retrieved according to your query
|
||||
</div>
|
||||
<logs v-if="error" :messages="[error]"/>
|
||||
<sql-table
|
||||
v-if="result && !viewRecord"
|
||||
:data-set="result"
|
||||
:time="time"
|
||||
:pageSize="pageSize"
|
||||
:page="defaultPage"
|
||||
:selected-cell-coordinates="defaultSelectedCell"
|
||||
class="straight"
|
||||
@updateSelectedCell="onUpdateSelectedCell"
|
||||
/>
|
||||
<div
|
||||
v-show="result === null && !isGettingResults && !error"
|
||||
class="table-preview result-before"
|
||||
>
|
||||
Run your query and get results here
|
||||
</div>
|
||||
<div v-if="isGettingResults" class="table-preview result-in-progress">
|
||||
<loading-indicator :size="30" />
|
||||
Fetching results...
|
||||
</div>
|
||||
<div
|
||||
v-show="result === undefined && !isGettingResults && !error"
|
||||
class="table-preview result-empty"
|
||||
>
|
||||
No rows retrieved according to your query
|
||||
</div>
|
||||
<logs v-if="error" :messages="[error]" />
|
||||
<sql-table
|
||||
v-if="result && !viewRecord"
|
||||
:data-set="result"
|
||||
:time="time"
|
||||
:pageSize="pageSize"
|
||||
:page="defaultPage"
|
||||
:selected-cell-coordinates="defaultSelectedCell"
|
||||
class="straight"
|
||||
@updateSelectedCell="onUpdateSelectedCell"
|
||||
/>
|
||||
|
||||
<record
|
||||
ref="recordView"
|
||||
v-if="result && viewRecord"
|
||||
:data-set="result"
|
||||
:time="time"
|
||||
:selected-column-index="selectedCell ? +selectedCell.dataset.col : 0"
|
||||
:rowIndex="selectedCell ? +selectedCell.dataset.row : 0"
|
||||
@updateSelectedCell="onUpdateSelectedCell"
|
||||
/>
|
||||
</div>
|
||||
<record
|
||||
ref="recordView"
|
||||
v-if="result && viewRecord"
|
||||
:data-set="result"
|
||||
:time="time"
|
||||
:selected-column-index="selectedCell ? +selectedCell.dataset.col : 0"
|
||||
:rowIndex="selectedCell ? +selectedCell.dataset.row : 0"
|
||||
@updateSelectedCell="onUpdateSelectedCell"
|
||||
/>
|
||||
</div>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
@@ -158,7 +164,7 @@ export default {
|
||||
time: [String, Number]
|
||||
},
|
||||
emits: ['switchTo'],
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
resizeObserver: null,
|
||||
pageSize: 20,
|
||||
@@ -188,44 +194,45 @@ export default {
|
||||
Splitpanes
|
||||
},
|
||||
computed: {
|
||||
resultSetTeleportTarget () {
|
||||
resultSetTeleportTarget() {
|
||||
if (!this.enableTeleport) {
|
||||
return undefined
|
||||
}
|
||||
const base = `#${this.viewValuePanelVisible
|
||||
? 'run-result-left-pane'
|
||||
: 'run-result-result-set'
|
||||
const base = `#${
|
||||
this.viewValuePanelVisible
|
||||
? 'run-result-left-pane'
|
||||
: 'run-result-result-set'
|
||||
}`
|
||||
const tabIdPostfix = `-${this.tab.id}`
|
||||
return base + tabIdPostfix
|
||||
}
|
||||
},
|
||||
activated () {
|
||||
activated() {
|
||||
this.enableTeleport = true
|
||||
},
|
||||
deactivated () {
|
||||
deactivated() {
|
||||
this.enableTeleport = false
|
||||
},
|
||||
mounted () {
|
||||
mounted() {
|
||||
this.resizeObserver = new ResizeObserver(this.handleResize)
|
||||
this.resizeObserver.observe(this.$refs.runResultPanel)
|
||||
this.calculatePageSize()
|
||||
},
|
||||
beforeUnmount () {
|
||||
beforeUnmount() {
|
||||
this.resizeObserver.unobserve(this.$refs.runResultPanel)
|
||||
},
|
||||
watch: {
|
||||
result () {
|
||||
result() {
|
||||
this.defaultSelectedCell = null
|
||||
this.selectedCell = null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleResize () {
|
||||
handleResize() {
|
||||
this.calculatePageSize()
|
||||
},
|
||||
|
||||
calculatePageSize () {
|
||||
calculatePageSize() {
|
||||
const runResultPanel = this.$refs.runResultPanel
|
||||
// 27 - table footer hight
|
||||
// 5 - padding-bottom of rounded table container
|
||||
@@ -234,9 +241,10 @@ export default {
|
||||
this.pageSize = Math.max(Math.floor(freeSpace / 35), 20)
|
||||
},
|
||||
|
||||
exportToCsv () {
|
||||
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' }
|
||||
)
|
||||
@@ -245,9 +253,10 @@ export default {
|
||||
fIo.exportToFile(csv.serialize(this.result), 'result_set.csv', 'text/csv')
|
||||
},
|
||||
|
||||
async prepareCopy () {
|
||||
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 {
|
||||
@@ -270,27 +279,27 @@ export default {
|
||||
} else {
|
||||
alert(
|
||||
"Your browser doesn't support copying into the clipboard. " +
|
||||
'If you use Firefox you can enable it ' +
|
||||
'by setting dom.events.asyncClipboard.clipboardItem to true.'
|
||||
'If you use Firefox you can enable it ' +
|
||||
'by setting dom.events.asyncClipboard.clipboardItem to true.'
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
copyToClipboard () {
|
||||
copyToClipboard() {
|
||||
cIo.copyText(this.dataToCopy, 'CSV copied to clipboard successfully')
|
||||
this.$modal.hide('prepareCSVCopy')
|
||||
},
|
||||
|
||||
cancelCopy () {
|
||||
cancelCopy() {
|
||||
this.dataToCopy = null
|
||||
this.$modal.hide('prepareCSVCopy')
|
||||
},
|
||||
|
||||
toggleViewValuePanel () {
|
||||
toggleViewValuePanel() {
|
||||
this.viewValuePanelVisible = !this.viewValuePanelVisible
|
||||
},
|
||||
|
||||
toggleViewRecord () {
|
||||
toggleViewRecord() {
|
||||
if (this.viewRecord) {
|
||||
this.defaultSelectedCell = {
|
||||
row: this.$refs.recordView.currentRowIndex,
|
||||
@@ -304,7 +313,7 @@ export default {
|
||||
this.viewRecord = !this.viewRecord
|
||||
},
|
||||
|
||||
onUpdateSelectedCell (e) {
|
||||
onUpdateSelectedCell(e) {
|
||||
this.selectedCell = e
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
tooltip-position="top-left"
|
||||
@click="$emit('switchTo', 'table')"
|
||||
>
|
||||
<table-icon/>
|
||||
<table-icon />
|
||||
</icon-button>
|
||||
|
||||
<icon-button
|
||||
@@ -30,9 +30,9 @@
|
||||
<data-view-icon />
|
||||
</icon-button>
|
||||
|
||||
<div class="side-tool-bar-divider" v-if="$slots.default"/>
|
||||
<div class="side-tool-bar-divider" v-if="$slots.default" />
|
||||
|
||||
<slot/>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user