mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-07 02:28:54 +08:00
Compare commits
46 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2c2344951 | ||
|
|
cbec91e78a | ||
|
|
816b0e6218 | ||
|
|
4ed93bbea7 | ||
|
|
3e3a70430f | ||
|
|
bce3854792 | ||
|
|
307cac06e6 | ||
|
|
c4b0bdc870 | ||
|
|
69e0b2129b | ||
|
|
4ebb3715d6 | ||
|
|
858e32c9fd | ||
|
|
42cce95ed0 | ||
|
|
61ffcc82d6 | ||
|
|
4716bcf258 | ||
|
|
23d2421584 | ||
|
|
4730afc68a | ||
|
|
6b902d5f00 | ||
|
|
a484c1e022 | ||
|
|
1ed5bf2fae | ||
|
|
da8dc71f23 | ||
|
|
ebac3d8f6c | ||
|
|
0336168bdc | ||
|
|
e38f482933 | ||
|
|
bcd98fe1ad | ||
|
|
ec3ec6f57a | ||
|
|
ae9e2bc03c | ||
|
|
fe11d446f3 | ||
|
|
e6bef139f1 | ||
|
|
d7e0e52da5 | ||
|
|
09cec13a9c | ||
|
|
0cae368350 | ||
|
|
88c62a1dfb | ||
|
|
ead861b610 | ||
|
|
949e6b626e | ||
|
|
6dc81f7be6 | ||
|
|
bb39dcbc2f | ||
|
|
131efb55df | ||
|
|
b33974c26c | ||
|
|
e9c97beb5a | ||
|
|
cabc4f8acd | ||
|
|
679a785d70 | ||
|
|
a91add40cc | ||
|
|
53b2d8372f | ||
|
|
f3e8448851 | ||
|
|
4213e9df5c | ||
|
|
9f32323a80 |
@@ -13,7 +13,8 @@ module.exports = {
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||
'no-case-declarations': 'off'
|
||||
'no-case-declarations': 'off',
|
||||
'max-len': [2, 100, 4, { ignoreUrls: true }]
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
|
||||
@@ -9,8 +9,9 @@ Sqliteviz is a single-page offline-first PWA for fully client-side visualisation
|
||||
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 file into a SQLite database and visualize imported data
|
||||
- export result set to CSV file
|
||||
- manage inquiries and run them against different databases
|
||||
- import/export inquiries to/from a JSON file
|
||||
- import/export inquiries from/to a JSON file
|
||||
- export a modified SQLite database
|
||||
- use it offline from your OS application menu like any other desktop app
|
||||
|
||||
|
||||
@@ -55,12 +55,15 @@ module.exports = function (config) {
|
||||
|
||||
junitReporter: {
|
||||
outputDir: 'report', // results will be saved as $outputDir/$browserName.xml
|
||||
outputFile: undefined, // if included, results will be saved as $outputDir/$browserName/$outputFile
|
||||
// if included, results will be saved as $outputDir/$browserName/$outputFile
|
||||
outputFile: undefined,
|
||||
suite: '', // suite will become the package name attribute in xml testsuite element
|
||||
useBrowserName: true, // add browser name to report and classes names
|
||||
nameFormatter: undefined, // function (browser, result) to customize the name attribute in xml testcase element
|
||||
classNameFormatter: undefined, // function (browser, result) to customize the classname attribute in xml testcase element
|
||||
properties: {} // key value pair of properties to add to the <properties> section of the report
|
||||
// function (browser, result) to customize the name attribute in xml testcase element
|
||||
nameFormatter: undefined,
|
||||
// function (browser, result) to customize the classname attribute in xml testcase element
|
||||
classNameFormatter: undefined,
|
||||
properties: {} // key value pairs add to the <properties> section of the report
|
||||
},
|
||||
|
||||
// web server port
|
||||
@@ -70,7 +73,8 @@ module.exports = function (config) {
|
||||
colors: true,
|
||||
|
||||
// level of logging
|
||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
|
||||
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN ||
|
||||
// config.LOG_INFO || config.LOG_DEBUG
|
||||
logLevel: config.LOG_INFO,
|
||||
|
||||
// enable / disable watching file and executing tests whenever any file changes
|
||||
@@ -80,7 +84,8 @@ module.exports = function (config) {
|
||||
FirefoxHeadlessTouch: {
|
||||
base: 'FirefoxHeadless',
|
||||
prefs: {
|
||||
'dom.w3c_touch_events.enabled': 1
|
||||
'dom.w3c_touch_events.enabled': 1,
|
||||
'dom.events.asyncClipboard.clipboardItem': true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -13,6 +13,21 @@ SQLite [amalgamation][2] extensions included:
|
||||
|
||||
1. [FTS5][4] -- virtual table module that provides full-text search
|
||||
functionality
|
||||
2. [FTS3/FTS4][15] -- older virtual table modules for full-text search
|
||||
3. [JSON1][16] -- scalar, aggregate and table-valued functions for managing JSON data
|
||||
|
||||
SQLite [contribution extensions][17]:
|
||||
|
||||
1. [extension-functions][18] -- mathematical and string extension functions for SQL queries.
|
||||
|
||||
Math: `acos`, `asin`, `atan`, `atn2`, `atan2`, `acosh`, `asinh`, `atanh`, `difference`,
|
||||
`degrees`, `radians`, `cos`, `sin`, `tan`, `cot`, `cosh`, `sinh`, `tanh`, `coth`,
|
||||
`exp`, `log`, `log10`, `power`, `sign`, `sqrt`, `square`, `ceil`, `floor`, `pi`.
|
||||
|
||||
String: `replicate`, `charindex`, `leftstr`, `rightstr`, `ltrim`, `rtrim`, `trim`,
|
||||
`replace`, `reverse`, `proper`, `padl`, `padr`, `padc`, `strfilter`.
|
||||
|
||||
Aggregate: `stdev`, `variance`, `mode`, `median`, `lower_quartile`, `upper_quartile`.
|
||||
|
||||
SQLite [miscellaneous extensions][3] included:
|
||||
|
||||
@@ -21,6 +36,9 @@ SQLite [miscellaneous extensions][3] included:
|
||||
[Querying Tree Structures in SQLite][11] ([closure.c][8])
|
||||
3. `uuid`, `uuid_str` and `uuid_blob` RFC-4122 UUID functions ([uuid.c][9])
|
||||
4. `regexp` (hence `REGEXP` operator) and `regexpi` functions ([regexp.c][10])
|
||||
5. `percentile` function ([percentile.c][13])
|
||||
6. `decimal`, `decimal_cmp`, `decimal_add`, `decimal_sub` and `decimal_mul` functions
|
||||
([decimal.c][14])
|
||||
|
||||
SQLite 3rd party extensions included:
|
||||
|
||||
@@ -29,6 +47,9 @@ SQLite 3rd party extensions included:
|
||||
To ease the step to have working clone locally, the build is committed into
|
||||
the repository.
|
||||
|
||||
Examples of queries involving these extensions can be found in the test suite in
|
||||
[sqliteExtensions.spec.js][19].
|
||||
|
||||
## Build method
|
||||
|
||||
Basically it's extended amalgamation and `SQLITE_EXTRA_INIT` concisely
|
||||
@@ -71,3 +92,10 @@ described in [this message from SQLite Forum][12]:
|
||||
[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
|
||||
[13]: https://sqlite.org/src/file/ext/misc/percentile.c
|
||||
[14]: https://sqlite.org/src/file/ext/misc/decimal.c
|
||||
[15]: https://sqlite.org/fts3.html
|
||||
[16]: https://sqlite.org/json1.html
|
||||
[17]: https://sqlite.org/contrib/
|
||||
[18]: https://sqlite.org/contrib//download/extension-functions.c?get=25
|
||||
[19]: https://github.com/lana-k/sqliteviz/blob/master/tests/lib/database/sqliteExtensions.spec.js
|
||||
|
||||
@@ -24,6 +24,8 @@ extension_urls = (
|
||||
('https://sqlite.org/src/raw/dbfd8543?at=closure.c', 'sqlite3_closure_init'),
|
||||
('https://sqlite.org/src/raw/5bb2264c?at=uuid.c', 'sqlite3_uuid_init'),
|
||||
('https://sqlite.org/src/raw/5853b0e5?at=regexp.c', 'sqlite3_regexp_init'),
|
||||
('https://sqlite.org/src/raw/b9086e22?at=percentile.c', 'sqlite3_percentile_init'),
|
||||
('https://sqlite.org/src/raw/09f967dc?at=decimal.c', 'sqlite3_decimal_init'),
|
||||
# Third-party extension
|
||||
# =====================
|
||||
('https://github.com/jakethaw/pivot_vtab/raw/08ab0797/pivot_vtab.c', 'sqlite3_pivotvtab_init'),
|
||||
|
||||
BIN
lib/sql-js/dist/sql-wasm.wasm
vendored
BIN
lib/sql-js/dist/sql-wasm.wasm
vendored
Binary file not shown.
295
package-lock.json
generated
295
package-lock.json
generated
@@ -1,16 +1,17 @@
|
||||
{
|
||||
"name": "sqliteviz",
|
||||
"version": "0.15.0",
|
||||
"version": "0.16.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sqliteviz",
|
||||
"version": "0.15.0",
|
||||
"version": "0.16.0",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"codemirror": "^5.57.0",
|
||||
"core-js": "^3.6.5",
|
||||
"dataurl-to-blob": "^0.0.1",
|
||||
"html2canvas": "^1.1.4",
|
||||
"jquery": "^3.6.0",
|
||||
"nanoid": "^3.1.12",
|
||||
@@ -22,7 +23,6 @@
|
||||
"react-chart-editor": "^0.45.0",
|
||||
"react-dom": "^16.13.1",
|
||||
"sql.js": "file:./lib/sql-js",
|
||||
"sqlite-parser": "^1.0.1",
|
||||
"vue": "^2.6.11",
|
||||
"vue-codemirror": "^4.0.6",
|
||||
"vue-js-modal": "^2.0.0-rc.6",
|
||||
@@ -5078,6 +5078,18 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/cli": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz",
|
||||
"integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=",
|
||||
"dependencies": {
|
||||
"exit": "0.1.2",
|
||||
"glob": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.2.5"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
|
||||
@@ -6747,6 +6759,14 @@
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/dataurl-to-blob": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dataurl-to-blob/-/dataurl-to-blob-0.0.1.tgz",
|
||||
"integrity": "sha1-B75KpV2G1MtWIk5Y4xgR9diFxMs=",
|
||||
"dependencies": {
|
||||
"jshint": "^2.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/date-format": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz",
|
||||
@@ -6756,6 +6776,11 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/date-now": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
|
||||
"integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs="
|
||||
},
|
||||
"node_modules/dateformat": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz",
|
||||
@@ -7334,7 +7359,6 @@
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
|
||||
"integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.0.1",
|
||||
"entities": "^2.0.0"
|
||||
@@ -7343,8 +7367,7 @@
|
||||
"node_modules/dom-serializer/node_modules/domelementtype": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
|
||||
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
|
||||
},
|
||||
"node_modules/domain-browser": {
|
||||
"version": "1.2.0",
|
||||
@@ -7359,8 +7382,7 @@
|
||||
"node_modules/domelementtype": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
|
||||
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "2.4.2",
|
||||
@@ -7781,8 +7803,7 @@
|
||||
"node_modules/entities": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz",
|
||||
"integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ=="
|
||||
},
|
||||
"node_modules/errno": {
|
||||
"version": "0.1.7",
|
||||
@@ -8724,6 +8745,14 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/exit": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
|
||||
"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=",
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/expand-braces": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz",
|
||||
@@ -12824,6 +12853,87 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/jshint": {
|
||||
"version": "2.13.1",
|
||||
"resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.1.tgz",
|
||||
"integrity": "sha512-vymzfR3OysF5P774x6zYv0bD4EpH6NWRxpq54wO9mA9RuY49yb1teKSICkLx2Ryx+mfzlVVNNbTBtsRtg78t7g==",
|
||||
"dependencies": {
|
||||
"cli": "~1.0.0",
|
||||
"console-browserify": "1.1.x",
|
||||
"exit": "0.1.x",
|
||||
"htmlparser2": "3.8.x",
|
||||
"lodash": "~4.17.21",
|
||||
"minimatch": "~3.0.2",
|
||||
"shelljs": "0.3.x",
|
||||
"strip-json-comments": "1.0.x"
|
||||
},
|
||||
"bin": {
|
||||
"jshint": "bin/jshint"
|
||||
}
|
||||
},
|
||||
"node_modules/jshint/node_modules/console-browserify": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
|
||||
"integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
|
||||
"dependencies": {
|
||||
"date-now": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/jshint/node_modules/domhandler": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz",
|
||||
"integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=",
|
||||
"dependencies": {
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/jshint/node_modules/domutils": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
|
||||
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
|
||||
"dependencies": {
|
||||
"dom-serializer": "0",
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/jshint/node_modules/entities": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
|
||||
"integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY="
|
||||
},
|
||||
"node_modules/jshint/node_modules/htmlparser2": {
|
||||
"version": "3.8.3",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
|
||||
"integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=",
|
||||
"dependencies": {
|
||||
"domelementtype": "1",
|
||||
"domhandler": "2.3",
|
||||
"domutils": "1.5",
|
||||
"entities": "1.0",
|
||||
"readable-stream": "1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/jshint/node_modules/isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
||||
},
|
||||
"node_modules/jshint/node_modules/readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
|
||||
"dependencies": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "~0.10.x"
|
||||
}
|
||||
},
|
||||
"node_modules/jshint/node_modules/string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
||||
},
|
||||
"node_modules/json-loader": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz",
|
||||
@@ -18832,6 +18942,17 @@
|
||||
"integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/shelljs": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz",
|
||||
"integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=",
|
||||
"bin": {
|
||||
"shjs": "bin/shjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/sigmund": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
|
||||
@@ -19472,17 +19593,6 @@
|
||||
"resolved": "lib/sql-js",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/sqlite-parser": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sqlite-parser/-/sqlite-parser-1.0.1.tgz",
|
||||
"integrity": "sha1-EQGD8mgvBKxsfYrQnEREbvl21ew=",
|
||||
"bin": {
|
||||
"sqlite-parser": "bin/sqlite-parser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/sshpk": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
|
||||
@@ -19868,6 +19978,17 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-json-comments": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz",
|
||||
"integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=",
|
||||
"bin": {
|
||||
"strip-json-comments": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/strongly-connected-components": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strongly-connected-components/-/strongly-connected-components-1.0.1.tgz",
|
||||
@@ -27703,6 +27824,15 @@
|
||||
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
|
||||
"dev": true
|
||||
},
|
||||
"cli": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz",
|
||||
"integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=",
|
||||
"requires": {
|
||||
"exit": "0.1.2",
|
||||
"glob": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"cli-cursor": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
|
||||
@@ -29153,12 +29283,25 @@
|
||||
"assert-plus": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"dataurl-to-blob": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dataurl-to-blob/-/dataurl-to-blob-0.0.1.tgz",
|
||||
"integrity": "sha1-B75KpV2G1MtWIk5Y4xgR9diFxMs=",
|
||||
"requires": {
|
||||
"jshint": "^2.9.2"
|
||||
}
|
||||
},
|
||||
"date-format": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz",
|
||||
"integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg=",
|
||||
"dev": true
|
||||
},
|
||||
"date-now": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
|
||||
"integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs="
|
||||
},
|
||||
"dateformat": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz",
|
||||
@@ -29639,7 +29782,6 @@
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
|
||||
"integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"domelementtype": "^2.0.1",
|
||||
"entities": "^2.0.0"
|
||||
@@ -29648,8 +29790,7 @@
|
||||
"domelementtype": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
|
||||
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -29662,8 +29803,7 @@
|
||||
"domelementtype": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
|
||||
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
|
||||
},
|
||||
"domhandler": {
|
||||
"version": "2.4.2",
|
||||
@@ -30058,8 +30198,7 @@
|
||||
"entities": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz",
|
||||
"integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ=="
|
||||
},
|
||||
"errno": {
|
||||
"version": "0.1.7",
|
||||
@@ -30845,6 +30984,11 @@
|
||||
"strip-eof": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"exit": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
|
||||
"integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw="
|
||||
},
|
||||
"expand-braces": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz",
|
||||
@@ -34375,6 +34519,86 @@
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
|
||||
},
|
||||
"jshint": {
|
||||
"version": "2.13.1",
|
||||
"resolved": "https://registry.npmjs.org/jshint/-/jshint-2.13.1.tgz",
|
||||
"integrity": "sha512-vymzfR3OysF5P774x6zYv0bD4EpH6NWRxpq54wO9mA9RuY49yb1teKSICkLx2Ryx+mfzlVVNNbTBtsRtg78t7g==",
|
||||
"requires": {
|
||||
"cli": "~1.0.0",
|
||||
"console-browserify": "1.1.x",
|
||||
"exit": "0.1.x",
|
||||
"htmlparser2": "3.8.x",
|
||||
"lodash": "~4.17.21",
|
||||
"minimatch": "~3.0.2",
|
||||
"shelljs": "0.3.x",
|
||||
"strip-json-comments": "1.0.x"
|
||||
},
|
||||
"dependencies": {
|
||||
"console-browserify": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
|
||||
"integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=",
|
||||
"requires": {
|
||||
"date-now": "^0.1.4"
|
||||
}
|
||||
},
|
||||
"domhandler": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz",
|
||||
"integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=",
|
||||
"requires": {
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
|
||||
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
|
||||
"requires": {
|
||||
"dom-serializer": "0",
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"entities": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz",
|
||||
"integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY="
|
||||
},
|
||||
"htmlparser2": {
|
||||
"version": "3.8.3",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
|
||||
"integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=",
|
||||
"requires": {
|
||||
"domelementtype": "1",
|
||||
"domhandler": "2.3",
|
||||
"domutils": "1.5",
|
||||
"entities": "1.0",
|
||||
"readable-stream": "1.1"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
|
||||
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
|
||||
"requires": {
|
||||
"core-util-is": "~1.0.0",
|
||||
"inherits": "~2.0.1",
|
||||
"isarray": "0.0.1",
|
||||
"string_decoder": "~0.10.x"
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
||||
}
|
||||
}
|
||||
},
|
||||
"json-loader": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz",
|
||||
@@ -39574,6 +39798,11 @@
|
||||
"integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==",
|
||||
"dev": true
|
||||
},
|
||||
"shelljs": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz",
|
||||
"integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E="
|
||||
},
|
||||
"sigmund": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz",
|
||||
@@ -40174,11 +40403,6 @@
|
||||
"sql.js": {
|
||||
"version": "file:lib/sql-js"
|
||||
},
|
||||
"sqlite-parser": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/sqlite-parser/-/sqlite-parser-1.0.1.tgz",
|
||||
"integrity": "sha1-EQGD8mgvBKxsfYrQnEREbvl21ew="
|
||||
},
|
||||
"sshpk": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
|
||||
@@ -40505,6 +40729,11 @@
|
||||
"integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=",
|
||||
"dev": true
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz",
|
||||
"integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E="
|
||||
},
|
||||
"strongly-connected-components": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strongly-connected-components/-/strongly-connected-components-1.0.1.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sqliteviz",
|
||||
"version": "0.15.1",
|
||||
"version": "0.17.0",
|
||||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"codemirror": "^5.57.0",
|
||||
"core-js": "^3.6.5",
|
||||
"dataurl-to-blob": "^0.0.1",
|
||||
"html2canvas": "^1.1.4",
|
||||
"jquery": "^3.6.0",
|
||||
"nanoid": "^3.1.12",
|
||||
@@ -23,7 +24,6 @@
|
||||
"react-chart-editor": "^0.45.0",
|
||||
"react-dom": "^16.13.1",
|
||||
"sql.js": "file:./lib/sql-js",
|
||||
"sqlite-parser": "^1.0.1",
|
||||
"vue": "^2.6.11",
|
||||
"vue-codemirror": "^4.0.6",
|
||||
"vue-js-modal": "^2.0.0-rc.6",
|
||||
|
||||
@@ -39,7 +39,10 @@
|
||||
/>
|
||||
<text-field
|
||||
label="Escape char"
|
||||
hint='The character used to escape the quote character within a field (e.g. "column with ""quotes"" in text").'
|
||||
hint='
|
||||
The character used to escape the quote character within a field
|
||||
(e.g. "column with ""quotes"" in text").
|
||||
'
|
||||
max-hint-width="242px"
|
||||
v-model="escapeChar"
|
||||
width="93px"
|
||||
@@ -100,7 +103,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import csv from './csv'
|
||||
import csv from '@/lib/csv'
|
||||
import CloseIcon from '@/components/svg/close'
|
||||
import TextField from '@/components/TextField'
|
||||
import DelimiterSelector from './DelimiterSelector'
|
||||
|
||||
83
src/components/LoadingDialog.vue
Normal file
83
src/components/LoadingDialog.vue
Normal file
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<modal
|
||||
:name="name"
|
||||
classes="dialog"
|
||||
height="auto"
|
||||
:clickToClose="false"
|
||||
>
|
||||
<div class="dialog-header">
|
||||
{{ title }}
|
||||
<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"/>
|
||||
{{ loadingMsg }}
|
||||
</div>
|
||||
<div v-else class="loading-dialog-body">
|
||||
<img :src="require('@/assets/images/success.svg')" class="success-icon state-icon" />
|
||||
{{ successMsg }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dialog-buttons-container">
|
||||
<button
|
||||
class="secondary"
|
||||
:disabled="loading"
|
||||
@click="$emit('cancel')"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="primary"
|
||||
:disabled="loading"
|
||||
@click="$emit('action')"
|
||||
>
|
||||
{{ actionBtnName }}
|
||||
</button>
|
||||
</div>
|
||||
</modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoadingIndicator from '@/components/LoadingIndicator'
|
||||
import CloseIcon from '@/components/svg/close'
|
||||
|
||||
export default {
|
||||
name: 'loadingDialog',
|
||||
props: {
|
||||
loadingMsg: String,
|
||||
successMsg: String,
|
||||
actionBtnName: String,
|
||||
name: String,
|
||||
title: String,
|
||||
loading: Boolean
|
||||
},
|
||||
watch: {
|
||||
loading () {
|
||||
if (this.loading) {
|
||||
this.$modal.show(this.name)
|
||||
}
|
||||
}
|
||||
},
|
||||
components: { LoadingIndicator, CloseIcon },
|
||||
methods: {
|
||||
cancel () {
|
||||
this.$emit('cancel')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loading-dialog-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.success-icon {
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.state-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -26,7 +26,10 @@
|
||||
<div
|
||||
: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
|
||||
}
|
||||
]"
|
||||
>
|
||||
<div
|
||||
|
||||
@@ -28,8 +28,11 @@ export default {
|
||||
return {
|
||||
page: this.value,
|
||||
chevron: `
|
||||
<svg width="9" height="9" viewBox="0 0 8 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.721924 9.93097L4.85292 5.79997L0.721924 1.66897L1.99992 0.399973L7.39992 5.79997L1.99992 11.2L0.721924 9.93097Z" fill="#506784"/>
|
||||
<svg width="9" height="9" viewBox="0 0 8 12" fill="none">
|
||||
<path
|
||||
d="M0.721924 9.93097L4.85292 5.79997L0.721924 1.66897L1.99992 0.399973L7.39992
|
||||
5.79997L1.99992 11.2L0.721924 9.93097Z" fill="#506784"
|
||||
/>
|
||||
</svg>
|
||||
`
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<tr v-for="rowIndex in currentPageData.count" :key="rowIndex">
|
||||
<td v-for="(col, colIndex) in columns" :key="colIndex">
|
||||
<div class="cell-data" :style="cellStyle">
|
||||
{{ dataSet[col][rowIndex - 1 + currentPageData.start] }}
|
||||
{{ dataSet.values[col][rowIndex - 1 + currentPageData.start] }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -74,10 +74,10 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
columns () {
|
||||
return Object.keys(this.dataSet)
|
||||
return this.dataSet.columns
|
||||
},
|
||||
rowCount () {
|
||||
return this.dataSet[this.columns[0]].length
|
||||
return this.dataSet.values[this.columns[0]].length
|
||||
},
|
||||
cellStyle () {
|
||||
const eq = this.tableWidth / this.columns.length
|
||||
|
||||
@@ -14,14 +14,15 @@
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="
|
||||
M13.6573 1.5H2.59985C1.77485 1.5 1.09985 2.175 1.09985 3V13.6649C1.09985 14.4899
|
||||
:d="
|
||||
`M13.6573 1.5H2.59985C1.77485 1.5 1.09985 2.175 1.09985 3V13.6649C1.09985 14.4899
|
||||
1.77485 15.1649 2.59985
|
||||
15.1649H9.84V13.6649H8.87866V9.08244H13.6573V9.83777H15.1573V3C15.1573
|
||||
2.17 14.4873 1.5 13.6573 1.5ZM13.6573
|
||||
7.58244V3H8.87866V7.58244H13.6573ZM7.37866 3H2.59985V7.58244H7.37866V3ZM2.59985
|
||||
9.08244V13.6649H7.37866V9.08244H2.59985ZM13.1702
|
||||
10.8434H15.6702V13.1717H18.0001V15.6717H15.6702V18H13.1702V15.6717H10.8401V13.1717H13.1702V10.8434Z
|
||||
10.8434H15.6702V13.1717H18.0001V15.6717H15.6702V18H13.1702V15.6717H10.8401V13.` +
|
||||
'1717H13.1702V10.8434Z'
|
||||
"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
|
||||
@@ -11,7 +11,13 @@
|
||||
@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"
|
||||
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>
|
||||
|
||||
@@ -5,12 +5,43 @@
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.41943 16V10H10.4194V16H8.41943Z" fill="#A2B1C6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.41943 16L2.41943 10H4.41943V16H2.41943Z" fill="#A2B1C6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.4194 16V7H13.4194V16H11.4194Z" fill="#A2B1C6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4194 16V8H16.4194V16H14.4194Z" fill="#A2B1C6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.41943 12V16H5.41943V12H7.41943Z" fill="#A2B1C6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.4332 1.80676L16.6265 6.00001L15.9194 6.70712L12.4055 3.19326L5.93169 9.1691L1.71436 5.55424L2.36515 4.79499L5.90707 7.83092L12.4332 1.80676Z" fill="#A2B1C6"/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M8.41943 16V10H10.4194V16H8.41943Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M2.41943 16L2.41943 10H4.41943V16H2.41943Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M11.4194 16V7H13.4194V16H11.4194Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M14.4194 16V8H16.4194V16H14.4194Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M7.41943 12V16H5.41943V12H7.41943Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M12.4332 1.80676L16.6265 6.00001L15.9194 6.70712L12.4055 3.19326L5.93169 9.1691L1.71436
|
||||
5.55424L2.36515 4.79499L5.90707 7.83092L12.4332 1.80676Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -8,7 +8,13 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14.3481 14.849C13.8791 15.318 13.1191 15.318 12.6511 14.849L10.0001 11.819L7.34907 14.848C6.88007 15.317 6.12007 15.317 5.65207 14.848C5.18307 14.379 5.18307 13.619 5.65207 13.151L8.41007 10.001L5.65107 6.84898C5.18207 6.37998 5.18207 5.62098 5.65107 5.15198C6.12007 4.68298 6.87907 4.68298 7.34807 5.15198L10.0001 8.18298L12.6511 5.15198C13.1201 4.68298 13.8791 4.68298 14.3481 5.15198C14.8171 5.62098 14.8171 6.38098 14.3481 6.84898L11.5901 10.001L14.3481 13.151C14.8171 13.62 14.8171 14.38 14.3481 14.849V14.849Z"
|
||||
d="M14.3481 14.849C13.8791 15.318 13.1191 15.318 12.6511 14.849L10.0001 11.819L7.34907
|
||||
14.848C6.88007 15.317 6.12007 15.317 5.65207 14.848C5.18307 14.379 5.18307 13.619
|
||||
5.65207 13.151L8.41007 10.001L5.65107 6.84898C5.18207 6.37998 5.18207 5.62098 5.65107
|
||||
5.15198C6.12007 4.68298 6.87907 4.68298 7.34807 5.15198L10.0001 8.18298L12.6511
|
||||
5.15198C13.1201 4.68298 13.8791 4.68298 14.3481 5.15198C14.8171 5.62098 14.8171 6.38098
|
||||
14.3481 6.84898L11.5901 10.001L14.3481 13.151C14.8171 13.62 14.8171 14.38 14.3481
|
||||
14.849V14.849Z"
|
||||
fill="#C8D4E3"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
33
src/components/svg/clipboard.vue
Normal file
33
src/components/svg/clipboard.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<template>
|
||||
<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
|
||||
3.14291V16.2422C2.07422 17.2115 2.86276 18 3.83203 18H14.1917C15.161 18 15.9495 17.2115
|
||||
15.9495 16.2422V3.14291C15.9495 2.17365 15.1609 1.3851 14.1917 1.3851V1.3851ZM11.0744
|
||||
1.40625V2.77034H6.94899C6.94899 2.30301 6.94899 1.91698 6.94899 1.40625H11.0744ZM14.5433
|
||||
16.2422C14.5433 16.4361 14.3855 16.5938 14.1917 16.5938H3.83203C3.63812 16.5938 3.48047
|
||||
16.4361 3.48047 16.2422V3.14291C3.48047 2.94914 3.63812 2.79135 3.83203
|
||||
2.79135H5.54274V3.47346C5.54274 3.86183 5.85764 4.17659 6.24586 4.17659H11.7775C12.1657
|
||||
4.17659 12.4806 3.86183 12.4806 3.47346V2.79135H14.1917C14.3855 2.79135 14.5433 2.94914
|
||||
14.5433 3.14291V16.2422ZM12.2745 8.20967C12.5491 8.48433 12.5491 8.92955 12.2745
|
||||
9.20407L8.5795 12.899C8.30498 13.1737 7.85976 13.1737 7.5851 12.899L5.74887
|
||||
11.0628C5.47435 10.7883 5.47435 10.3431 5.74887 10.0684C6.02353 9.7939 6.46861 9.7939
|
||||
6.74327 10.0684L8.08237 11.4075L11.2801 8.20981C11.5547 7.93515 11.9998 7.93515 12.2745
|
||||
8.20967V8.20967Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ClipboardIcon'
|
||||
}
|
||||
</script>
|
||||
@@ -9,7 +9,8 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14 1.41L12.59 0L7 5.59L1.41 0L0 1.41L5.59 7L0 12.59L1.41 14L7 8.41L12.59 14L14 12.59L8.41 7L14 1.41Z"
|
||||
d="M14 1.41L12.59 0L7 5.59L1.41 0L0 1.41L5.59 7L0 12.59L1.41 14L7 8.41L12.59 14L14
|
||||
12.59L8.41 7L14 1.41Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@@ -5,9 +5,28 @@
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.5552 6.91522C13.7584 6.91357 13.9535 6.99442 14.096 7.13926L17.2582 10.3541C17.5486 10.6494 17.5447 11.1242 17.2494 11.4147C16.9541 11.7051 16.4793 11.7012 16.1888 11.4059L13.57 8.74357L9.29577 13.2318C9.01977 13.5216 8.56484 13.5436 8.2621 13.2819L5.35435 10.7677L2.03285 13.7321C1.72382 14.0079 1.24971 13.981 0.973901 13.6719C0.69809 13.3629 0.725022 12.8888 1.03406 12.613L4.8471 9.20986C5.12827 8.95892 5.55198 8.95559 5.83705 9.20208L8.70249 11.6797L13.0182 7.14796C13.1583 7.00084 13.3521 6.91686 13.5552 6.91522Z" fill="#A2B1C6"/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M13.5552 6.91522C13.7584 6.91357 13.9535 6.99442 14.096 7.13926L17.2582 10.3541C17.5486
|
||||
10.6494 17.5447 11.1242 17.2494 11.4147C16.9541 11.7051 16.4793 11.7012 16.1888
|
||||
11.4059L13.57 8.74357L9.29577 13.2318C9.01977 13.5216 8.56484 13.5436 8.2621
|
||||
13.2819L5.35435 10.7677L2.03285 13.7321C1.72382 14.0079 1.24971 13.981 0.973901
|
||||
13.6719C0.69809 13.3629 0.725022 12.8888 1.03406 12.613L4.8471 9.20986C5.12827 8.95892
|
||||
5.55198 8.95559 5.83705 9.20208L8.70249 11.6797L13.0182 7.14796C13.1583 7.00084 13.3521
|
||||
6.91686 13.5552 6.91522Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<circle cx="5.50049" cy="6.00339" r="1.5" fill="#A2B1C6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.036 1.21788L1.96546 1.213C1.14046 1.213 0.465462 1.888 0.465462 2.713L0.464111 15.2821C0.464111 16.1071 1.13911 16.7821 1.96411 16.7821L16.0347 16.787C16.8674 16.787 17.5347 16.1121 17.5347 15.287L17.536 2.71788C17.536 1.88787 16.866 1.21788 16.036 1.21788ZM16.0374 2.71788L1.96424 2.713L1.96289 15.2773L16.036 15.2821L16.0374 2.71788Z" fill="#A2B1C6"/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M16.036 1.21788L1.96546 1.213C1.14046 1.213 0.465462 1.888 0.465462 2.713L0.464111
|
||||
15.2821C0.464111 16.1071 1.13911 16.7821 1.96411 16.7821L16.0347 16.787C16.8674 16.787
|
||||
17.5347 16.1121 17.5347 15.287L17.536 2.71788C17.536 1.88787 16.866 1.21788 16.036
|
||||
1.21788ZM16.0374 2.71788L1.96424 2.713L1.96289 15.2773L16.036 15.2821L16.0374 2.71788Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M6.175 7.15002L10 10.975L13.825 7.15002L15 8.33336L10 13.3334L5 8.33336L6.175 7.15002Z" fill="#C8D4E3"/>
|
||||
<path
|
||||
d="M6.175 7.15002L10 10.975L13.825 7.15002L15 8.33336L10 13.3334L5 8.33336L6.175 7.15002Z"
|
||||
fill="#C8D4E3"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -11,7 +11,9 @@
|
||||
@mouseleave="hideTooltip"
|
||||
>
|
||||
<path
|
||||
d="M10.5 1.5H4.5C3.675 1.5 3 2.175 3 3V15C3 15.825 3.675 16.5 4.5 16.5H13.5C14.325 16.5 15 15.825 15 15V6L10.5 1.5ZM13.5 15H4.5V3H9.75V6.75H13.5V15ZM12 8.25V13.575L10.425 12L8.325 14.1L6.225 12L8.325 9.9L6.675 8.25H12Z"
|
||||
d="M10.5 1.5H4.5C3.675 1.5 3 2.175 3 3V15C3 15.825 3.675 16.5 4.5 16.5H13.5C14.325 16.5
|
||||
15 15.825 15 15V6L10.5 1.5ZM13.5 15H4.5V3H9.75V6.75H13.5V15ZM12 8.25V13.575L10.425
|
||||
12L8.325 14.1L6.225 12L8.325 9.9L6.675 8.25H12Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
62
src/components/svg/exportToCsv.vue
Normal file
62
src/components/svg/exportToCsv.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<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
|
||||
1.93115 15.5561 1.44775 15.0082C0.964355 14.4604 0.722656 13.687 0.722656
|
||||
12.6879V12.3715C0.722656 11.7446 0.831055 11.1967 1.04785 10.728C1.26758 10.2563 1.58252
|
||||
9.89301 1.99268 9.63812C2.40283 9.38031 2.87744 9.2514 3.4165 9.2514C4.19287 9.2514
|
||||
4.81689 9.45648 5.28857 9.86664C5.76025 10.2739 6.02832 10.8364 6.09277
|
||||
11.5541H4.55469C4.54297 11.1645 4.44482 10.8847 4.26025 10.7148C4.07568 10.5449 3.79443
|
||||
10.4599 3.4165 10.4599C3.03271 10.4599 2.75146 10.6035 2.57275 10.8906C2.39404 11.1777
|
||||
2.30029 11.6362 2.2915 12.2661V12.7187C2.2915 13.4013 2.37646 13.8891 2.54639
|
||||
14.1821C2.71924 14.475 3.01514 14.6215 3.43408 14.6215C3.78857 14.6215 4.05957 14.538
|
||||
4.24707 14.371C4.43457 14.204 4.53418 13.9389 4.5459 13.5756H6.07959ZM10.1094
|
||||
14.0414C10.1094 13.8159 10.0288 13.6401 9.86768 13.5141C9.70947 13.3881 9.42969 13.2578
|
||||
9.02832 13.123C8.62695 12.9882 8.29883 12.8578 8.04395 12.7319C7.21484 12.3246 6.80029
|
||||
11.7651 6.80029 11.0532C6.80029 10.6987 6.90283 10.3867 7.10791 10.1171C7.31592 9.84467
|
||||
7.60889 9.63373 7.98682 9.48431C8.36475 9.33197 8.78955 9.2558 9.26123 9.2558C9.72119
|
||||
9.2558 10.1328 9.33783 10.4961 9.50189C10.8623 9.66595 11.1465 9.90033 11.3486
|
||||
10.205C11.5508 10.5068 11.6519 10.8525 11.6519 11.2421H10.1138C10.1138 10.9814 10.0332
|
||||
10.7792 9.87207 10.6357C9.71387 10.4921 9.49854 10.4203 9.22607 10.4203C8.95068 10.4203
|
||||
8.73242 10.4819 8.57129 10.6049C8.41309 10.725 8.33398 10.8788 8.33398 11.0663C8.33398
|
||||
11.2304 8.42188 11.3798 8.59766 11.5146C8.77344 11.6464 9.08252 11.7841 9.5249
|
||||
11.9277C9.96729 12.0683 10.3306 12.2206 10.6147 12.3847C11.3062 12.7831 11.6519 13.3325
|
||||
11.6519 14.0327C11.6519 14.5922 11.4409 15.0317 11.019 15.351C10.5972 15.6703 10.0186
|
||||
15.83 9.2832 15.83C8.76465 15.83 8.29443 15.7377 7.87256 15.5532C7.45361 15.3657 7.13721
|
||||
15.1108 6.92334 14.7885C6.7124 14.4633 6.60693 14.0898 6.60693 13.6679H8.15381C8.15381
|
||||
14.0107 8.2417 14.2641 8.41748 14.4282C8.59619 14.5893 8.88477 14.6699 9.2832
|
||||
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"
|
||||
/>
|
||||
<path
|
||||
d="M3.03345 0.991333H4.89869V2.49133H3.03345V7.93074H1.53345V2.49133C1.53345 1.66633
|
||||
2.20845 0.991333 3.03345 0.991333Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
d="M15.917 0.991333H14.0517V2.49133H15.917V7.93074H17.417V2.49133C17.417 1.66633 16.742
|
||||
0.991333 15.917 0.991333Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
d="M9.53043 1.28245e-05L13.2199 3.83975L10.9929 3.7953L10.9337 6.76455L7.96442
|
||||
6.70529L8.02368 3.73603L5.6907 3.68947L9.53043 1.28245e-05Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ExportToCsvIcon'
|
||||
}
|
||||
</script>
|
||||
61
src/components/svg/exportToSvg.vue
Normal file
61
src/components/svg/exportToSvg.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<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
|
||||
0.974609 11.7203 0.974609 11.0084C0.974609 10.6539 1.07715 10.3419 1.28223
|
||||
10.0723C1.49023 9.79987 1.7832 9.58893 2.16113 9.43951C2.53906 9.28717 2.96387 9.211
|
||||
3.43555 9.211C3.89551 9.211 4.30713 9.29303 4.67041 9.45709C5.03662 9.62115 5.3208
|
||||
9.85553 5.52295 10.1602C5.7251 10.462 5.82617 10.8077 5.82617 11.1973H4.28809C4.28809
|
||||
10.9366 4.20752 10.7344 4.04639 10.5909C3.88818 10.4473 3.67285 10.3755 3.40039
|
||||
10.3755C3.125 10.3755 2.90674 10.4371 2.74561 10.5601C2.5874 10.6802 2.5083 10.834
|
||||
2.5083 11.0215C2.5083 11.1856 2.59619 11.335 2.77197 11.4698C2.94775 11.6016 3.25684
|
||||
11.7393 3.69922 11.8829C4.1416 12.0235 4.50488 12.1758 4.78906 12.3399C5.48047 12.7383
|
||||
5.82617 13.2877 5.82617 13.9879C5.82617 14.5474 5.61523 14.9869 5.19336 15.3062C4.77148
|
||||
15.6255 4.19287 15.7852 3.45752 15.7852C2.93896 15.7852 2.46875 15.6929 2.04688
|
||||
15.5084C1.62793 15.3209 1.31152 15.066 1.09766 14.7437C0.886719 14.4185 0.78125 14.045
|
||||
0.78125 13.6231H2.32812C2.32812 13.9659 2.41602 14.2193 2.5918 14.3834C2.77051 14.5445
|
||||
3.05908 14.6251 3.45752 14.6251C3.7124 14.6251 3.91309 14.5709 4.05957 14.4625C4.20898
|
||||
14.3511 4.28369 14.1959 4.28369 13.9966ZM9.1001 13.9571L10.3745 9.29889H12.0972L9.94385
|
||||
15.6973H8.25635L6.12061 9.29889H7.83008L9.1001 13.9571ZM17.7441 14.9063C17.5068 15.17
|
||||
17.1597 15.3824 16.7026 15.5435C16.2456 15.7047 15.7446 15.7852 15.1997 15.7852C14.3618
|
||||
15.7852 13.6924 15.5289 13.1914 15.0162C12.6904 14.5035 12.4224 13.7901 12.3872
|
||||
12.876L12.3828 12.3223C12.3828 11.6924 12.4941 11.1431 12.7168 10.6744C12.9395 10.2027
|
||||
13.2573 9.84088 13.6704 9.58893C14.0864 9.33405 14.5669 9.2066 15.1118 9.2066C15.9087
|
||||
9.2066 16.5269 9.38971 16.9663 9.75592C17.4087 10.1192 17.6665 10.6627 17.7397
|
||||
11.3863H16.2544C16.2017 11.0289 16.0874 10.774 15.9116 10.6216C15.7358 10.4693 15.4868
|
||||
10.3931 15.1646 10.3931C14.7778 10.3931 14.479 10.5572 14.2681 10.8853C14.0571 11.2134
|
||||
13.9502 11.6822 13.9473 12.2916V12.6783C13.9473 13.317 14.0557 13.7974 14.2725
|
||||
14.1197C14.4922 14.439 14.8364 14.5987 15.3052 14.5987C15.7065 14.5987 16.0054 14.5093
|
||||
16.2017 14.3306V13.3375H15.1294V12.2784H17.7441V14.9063Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
d="M2.86768 0.991333H4.73292V2.49133H2.86768V7.93074H1.36768V2.49133C1.36768 1.66633
|
||||
2.04268 0.991333 2.86768 0.991333Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
d="M15.7512 0.991333H13.886V2.49133H15.7512V7.93074H17.2512V2.49133C17.2512 1.66633 16.5762
|
||||
0.991333 15.7512 0.991333Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
d="M9.36466 -1.76931e-05L13.0541 3.83972L10.8272 3.79527L10.7679 6.76452L7.79865
|
||||
6.70526L7.85791 3.736L5.52492 3.68944L9.36466 -1.76931e-05Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
name: 'ExportToSvgIcon'
|
||||
}
|
||||
</script>
|
||||
@@ -10,8 +10,28 @@
|
||||
@mouseenter="showTooltip"
|
||||
@mouseleave="hideTooltip"
|
||||
>
|
||||
<path d="M8.75 14.1666H10.4167V12.5H8.75V14.1666ZM9.58333 16.25C5.90833 16.25 2.91667 13.2583 2.91667 9.58333C2.91667 5.90833 5.90833 2.91667 9.58333 2.91667C13.2583 2.91667 16.25 5.90833 16.25 9.58333C16.25 13.2583 13.2583 16.25 9.58333 16.25ZM9.58333 1.25C8.48898 1.25 7.40535 1.46555 6.3943 1.88434C5.38326 2.30313 4.4646 2.91696 3.69078 3.69078C2.12797 5.25358 1.25 7.3732 1.25 9.58333C1.25 11.7935 2.12797 13.9131 3.69078 15.4759C4.4646 16.2497 5.38326 16.8635 6.3943 17.2823C7.40535 17.7011 8.48898 17.9167 9.58333 17.9167C11.7935 17.9167 13.9131 17.0387 15.4759 15.4759C17.0387 13.9131 17.9167 11.7935 17.9167 9.58333C17.9167 8.48898 17.7011 7.40535 17.2823 6.3943C16.8635 5.38326 16.2497 4.4646 15.4759 3.69078C14.7021 2.91696 13.7834 2.30313 12.7724 1.88434C11.7613 1.46555 10.6777 1.25 9.58333 1.25Z" fill="#A2B1C6"/>
|
||||
<path d="M9.91601 4.51787C8.98167 4.42606 8.05144 4.69097 7.36309 5.24472C6.68735 5.78828 6.2998 6.56661 6.2998 7.38012H7.92488C7.92488 6.97463 8.11059 6.60187 8.44779 6.33061C8.79784 6.049 9.25647 5.92005 9.73896 5.96755C10.4832 6.04076 11.0828 6.57277 11.1647 7.23265C11.2306 7.764 10.9661 8.28194 10.4744 8.58426C9.38676 9.25303 8.73742 10.343 8.73742 11.5H10.3625C10.3625 10.8243 10.7477 10.184 11.3929 9.78733C12.3808 9.17985 12.9122 8.13913 12.7798 7.07124C12.6144 5.73863 11.41 4.66476 9.91601 4.51787Z" fill="#A2B1C6"/>
|
||||
<path
|
||||
d="M8.75 14.1666H10.4167V12.5H8.75V14.1666ZM9.58333 16.25C5.90833 16.25 2.91667 13.2583
|
||||
2.91667 9.58333C2.91667 5.90833 5.90833 2.91667 9.58333 2.91667C13.2583 2.91667 16.25
|
||||
5.90833 16.25 9.58333C16.25 13.2583 13.2583 16.25 9.58333 16.25ZM9.58333 1.25C8.48898
|
||||
1.25 7.40535 1.46555 6.3943 1.88434C5.38326 2.30313 4.4646 2.91696 3.69078
|
||||
3.69078C2.12797 5.25358 1.25 7.3732 1.25 9.58333C1.25 11.7935 2.12797 13.9131 3.69078
|
||||
15.4759C4.4646 16.2497 5.38326 16.8635 6.3943 17.2823C7.40535 17.7011 8.48898 17.9167
|
||||
9.58333 17.9167C11.7935 17.9167 13.9131 17.0387 15.4759 15.4759C17.0387 13.9131 17.9167
|
||||
11.7935 17.9167 9.58333C17.9167 8.48898 17.7011 7.40535 17.2823 6.3943C16.8635 5.38326
|
||||
16.2497 4.4646 15.4759 3.69078C14.7021 2.91696 13.7834 2.30313 12.7724 1.88434C11.7613
|
||||
1.46555 10.6777 1.25 9.58333 1.25Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
d="M9.91601 4.51787C8.98167 4.42606 8.05144 4.69097 7.36309 5.24472C6.68735 5.78828
|
||||
6.2998 6.56661 6.2998 7.38012H7.92488C7.92488 6.97463 8.11059 6.60187 8.44779
|
||||
6.33061C8.79784 6.049 9.25647 5.92005 9.73896 5.96755C10.4832 6.04076 11.0828 6.57277
|
||||
11.1647 7.23265C11.2306 7.764 10.9661 8.28194 10.4744 8.58426C9.38676 9.25303 8.73742
|
||||
10.343 8.73742 11.5H10.3625C10.3625 10.8243 10.7477 10.184 11.3929 9.78733C12.3808
|
||||
9.17985 12.9122 8.13913 12.7798 7.07124C12.6144 5.73863 11.41 4.66476 9.91601 4.51787Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
<span class="icon-tooltip" :style="{...tooltipStyle, maxWidth: maxWidth }" ref="tooltip">
|
||||
{{ hint }}
|
||||
|
||||
@@ -5,7 +5,15 @@
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.7661 5.13901L18.3407 9.43008H16.5161V12.8467C16.5161 13.7957 16.2783 14.6451 15.6714 15.2521C15.0645 15.859 14.215 16.0967 13.2661 16.0967H9.84942V17.9214L5.55835 15.3467L9.84942 12.7721V14.5967H13.2661C13.9838 14.5967 14.3844 14.4178 14.6108 14.1914C14.8372 13.965 15.0161 13.5645 15.0161 12.8467V9.43008H13.1914L15.7661 5.13901Z" fill="#A2B1C6"/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M15.7661 5.13901L18.3407 9.43008H16.5161V12.8467C16.5161 13.7957 16.2783 14.6451 15.6714
|
||||
15.2521C15.0645 15.859 14.215 16.0967 13.2661 16.0967H9.84942V17.9214L5.55835
|
||||
15.3467L9.84942 12.7721V14.5967H13.2661C13.9838 14.5967 14.3844 14.4178 14.6108
|
||||
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"/>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<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 5.51953 9 5.51953ZM9 12.9023C7.35226 12.9023 6.01172 11.5618 6.01172 9.91406C6.01172 8.26632 7.35226 6.92578 9 6.92578C10.6477 6.92578 11.9883 8.26632 11.9883 9.91406C11.9883 11.5618 10.6477 12.9023 9 12.9023Z" fill="#A2B1C6"/>
|
||||
<path d="M15.8906 3.41016H13.304C13.2221 3.41016 13.1483 3.36547 13.1104 3.29319L12.3948 1.78945C12.3928 1.78534 12.3908 1.78126 12.3887 1.77718C12.1117 1.22312 11.5548 0.878906 10.9353 0.878906H7.11478C6.49529 0.878906 5.93835 1.22312 5.66135 1.77722C5.65928 1.7813 5.65731 1.78538 5.65534 1.78949L4.9397 3.2933C4.90173 3.36547 4.82797 3.41016 4.74609 3.41016H2.10938C0.946266 3.41016 0 4.35642 0 5.51953V15.0117C0 16.1748 0.946266 17.1211 2.10938 17.1211H15.8906C17.0537 17.1211 18 16.1748 18 15.0117V5.51953C18 4.35642 17.0537 3.41016 15.8906 3.41016ZM16.5938 15.0117C16.5938 15.3994 16.2783 15.7148 15.8906 15.7148H2.10938C1.72167 15.7148 1.40625 15.3994 1.40625 15.0117V5.51953C1.40625 5.13183 1.72167 4.81641 2.10938 4.81641H4.74609C5.36555 4.81641 5.92249 4.47223 6.19952 3.91816C6.2016 3.91409 6.20357 3.90997 6.20557 3.90586L6.92121 2.40205C6.95914 2.32984 7.0329 2.28516 7.11478 2.28516H10.9353C11.0172 2.28516 11.091 2.32984 11.1289 2.40202L11.8445 3.90582C11.8465 3.90994 11.8485 3.91405 11.8506 3.91813C12.1276 4.47219 12.6846 4.81637 13.304 4.81637H15.8906C16.2783 4.81637 16.5938 5.13179 16.5938 5.5195V15.0117Z" fill="#A2B1C6"/>
|
||||
<path d="M15.1875 6.22266H13.7812V7.62891H15.1875V6.22266Z" fill="#A2B1C6"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -5,8 +5,31 @@
|
||||
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 5.51953 9 5.51953ZM9 12.9023C7.35226 12.9023 6.01172 11.5618 6.01172 9.91406C6.01172 8.26632 7.35226 6.92578 9 6.92578C10.6477 6.92578 11.9883 8.26632 11.9883 9.91406C11.9883 11.5618 10.6477 12.9023 9 12.9023Z" fill="#A2B1C6"/>
|
||||
<path d="M15.8906 3.41016H13.304C13.2221 3.41016 13.1483 3.36547 13.1104 3.29319L12.3948 1.78945C12.3928 1.78534 12.3908 1.78126 12.3887 1.77718C12.1117 1.22312 11.5548 0.878906 10.9353 0.878906H7.11478C6.49529 0.878906 5.93835 1.22312 5.66135 1.77722C5.65928 1.7813 5.65731 1.78538 5.65534 1.78949L4.9397 3.2933C4.90173 3.36547 4.82797 3.41016 4.74609 3.41016H2.10938C0.946266 3.41016 0 4.35642 0 5.51953V15.0117C0 16.1748 0.946266 17.1211 2.10938 17.1211H15.8906C17.0537 17.1211 18 16.1748 18 15.0117V5.51953C18 4.35642 17.0537 3.41016 15.8906 3.41016ZM16.5938 15.0117C16.5938 15.3994 16.2783 15.7148 15.8906 15.7148H2.10938C1.72167 15.7148 1.40625 15.3994 1.40625 15.0117V5.51953C1.40625 5.13183 1.72167 4.81641 2.10938 4.81641H4.74609C5.36555 4.81641 5.92249 4.47223 6.19952 3.91816C6.2016 3.91409 6.20357 3.90997 6.20557 3.90586L6.92121 2.40205C6.95914 2.32984 7.0329 2.28516 7.11478 2.28516H10.9353C11.0172 2.28516 11.091 2.32984 11.1289 2.40202L11.8445 3.90582C11.8465 3.90994 11.8485 3.91405 11.8506 3.91813C12.1276 4.47219 12.6846 4.81637 13.304 4.81637H15.8906C16.2783 4.81637 16.5938 5.13179 16.5938 5.5195V15.0117Z" fill="#A2B1C6"/>
|
||||
<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
|
||||
5.51953 9 5.51953ZM9 12.9023C7.35226 12.9023 6.01172 11.5618 6.01172 9.91406C6.01172
|
||||
8.26632 7.35226 6.92578 9 6.92578C10.6477 6.92578 11.9883 8.26632 11.9883
|
||||
9.91406C11.9883 11.5618 10.6477 12.9023 9 12.9023Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
d="M15.8906 3.41016H13.304C13.2221 3.41016 13.1483 3.36547 13.1104 3.29319L12.3948
|
||||
1.78945C12.3928 1.78534 12.3908 1.78126 12.3887 1.77718C12.1117 1.22312 11.5548 0.878906
|
||||
10.9353 0.878906H7.11478C6.49529 0.878906 5.93835 1.22312 5.66135 1.77722C5.65928 1.7813
|
||||
5.65731 1.78538 5.65534 1.78949L4.9397 3.2933C4.90173 3.36547 4.82797 3.41016 4.74609
|
||||
3.41016H2.10938C0.946266 3.41016 0 4.35642 0 5.51953V15.0117C0 16.1748 0.946266 17.1211
|
||||
2.10938 17.1211H15.8906C17.0537 17.1211 18 16.1748 18 15.0117V5.51953C18 4.35642 17.0537
|
||||
3.41016 15.8906 3.41016ZM16.5938 15.0117C16.5938 15.3994 16.2783 15.7148 15.8906
|
||||
15.7148H2.10938C1.72167 15.7148 1.40625 15.3994 1.40625 15.0117V5.51953C1.40625 5.13183
|
||||
1.72167 4.81641 2.10938 4.81641H4.74609C5.36555 4.81641 5.92249 4.47223 6.19952
|
||||
3.91816C6.2016 3.91409 6.20357 3.90997 6.20557 3.90586L6.92121 2.40205C6.95914 2.32984
|
||||
7.0329 2.28516 7.11478 2.28516H10.9353C11.0172 2.28516 11.091 2.32984 11.1289
|
||||
2.40202L11.8445 3.90582C11.8465 3.90994 11.8485 3.91405 11.8506 3.91813C12.1276 4.47219
|
||||
12.6846 4.81637 13.304 4.81637H15.8906C16.2783 4.81637 16.5938 5.13179 16.5938
|
||||
5.5195V15.0117Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path d="M15.1875 6.22266H13.7812V7.62891H15.1875V6.22266Z" fill="#A2B1C6"/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
@@ -6,8 +6,20 @@
|
||||
viewBox="0 0 6 12"
|
||||
fill="none"
|
||||
>
|
||||
<path d="M2.99932 -3.63032e-05C2.75092 -3.63032e-05 2.54932 0.201563 2.54932 0.449964L2.54932 11.55C2.54932 11.7984 2.75092 12 2.99932 12C3.24772 12 3.44932 11.7984 3.44932 11.55L3.44932 0.449964C3.44932 0.201563 3.24772 -3.63032e-05 2.99932 -3.63032e-05Z" fill="#506784"/>
|
||||
<path d="M2.99915 1.80492e-05C2.8839 1.80492e-05 2.76865 0.0438534 2.68109 0.132073L0.581055 2.232C0.405273 2.40789 0.405273 2.69287 0.581055 2.86865C0.756946 3.04443 1.04193 3.04443 1.21771 2.86865L2.99969 1.08667L4.78168 2.86865C4.95746 3.04443 5.24255 3.04443 5.41833 2.86865C5.59412 2.69287 5.59412 2.40789 5.41833 2.232L3.3183 0.132073C3.22953 0.0438534 3.11428 1.80492e-05 2.99915 1.80492e-05V1.80492e-05Z" fill="#506784"/>
|
||||
<path
|
||||
d="M2.99932 -3.63032e-05C2.75092 -3.63032e-05 2.54932 0.201563 2.54932 0.449964L2.54932
|
||||
11.55C2.54932 11.7984 2.75092 12 2.99932 12C3.24772 12 3.44932 11.7984 3.44932
|
||||
11.55L3.44932 0.449964C3.44932 0.201563 3.24772 -3.63032e-05 2.99932 -3.63032e-05Z"
|
||||
fill="#506784"
|
||||
/>
|
||||
<path
|
||||
d="M2.99915 1.80492e-05C2.8839 1.80492e-05 2.76865 0.0438534 2.68109 0.132073L0.581055
|
||||
2.232C0.405273 2.40789 0.405273 2.69287 0.581055 2.86865C0.756946 3.04443 1.04193 3.04443
|
||||
1.21771 2.86865L2.99969 1.08667L4.78168 2.86865C4.95746 3.04443 5.24255 3.04443 5.41833
|
||||
2.86865C5.59412 2.69287 5.59412 2.40789 5.41833 2.232L3.3183 0.132073C3.22953 0.0438534
|
||||
3.11428 1.80492e-05 2.99915 1.80492e-05V1.80492e-05Z"
|
||||
fill="#506784"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -6,8 +6,44 @@
|
||||
fill="none"
|
||||
>
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M4.5 1.51343H10.5L15 6.01343V8.45284H13.5V6.76343H9.75V3.01343H4.5V8.45284H3V3.01343C3 2.18843 3.675 1.51343 4.5 1.51343Z" fill="#A2B1C6"/>
|
||||
<path d="M4.28369 14.8127C4.28369 14.5872 4.20312 14.4114 4.04199 14.2854C3.88379 14.1594 3.604 14.0291 3.20264 13.8943C2.80127 13.7595 2.47314 13.6292 2.21826 13.5032C1.38916 13.0959 0.974609 12.5364 0.974609 11.8245C0.974609 11.47 1.07715 11.158 1.28223 10.8884C1.49023 10.616 1.7832 10.405 2.16113 10.2556C2.53906 10.1033 2.96387 10.0271 3.43555 10.0271C3.89551 10.0271 4.30713 10.1091 4.67041 10.2732C5.03662 10.4373 5.3208 10.6716 5.52295 10.9763C5.7251 11.2781 5.82617 11.6238 5.82617 12.0134H4.28809C4.28809 11.7527 4.20752 11.5505 4.04639 11.407C3.88818 11.2634 3.67285 11.1917 3.40039 11.1917C3.125 11.1917 2.90674 11.2532 2.74561 11.3762C2.5874 11.4963 2.5083 11.6501 2.5083 11.8376C2.5083 12.0017 2.59619 12.1511 2.77197 12.2859C2.94775 12.4177 3.25684 12.5554 3.69922 12.699C4.1416 12.8396 4.50488 12.9919 4.78906 13.156C5.48047 13.5544 5.82617 14.1038 5.82617 14.804C5.82617 15.3635 5.61523 15.803 5.19336 16.1223C4.77148 16.4417 4.19287 16.6013 3.45752 16.6013C2.93896 16.6013 2.46875 16.509 2.04688 16.3245C1.62793 16.137 1.31152 15.8821 1.09766 15.5598C0.886719 15.2346 0.78125 14.8611 0.78125 14.4392H2.32812C2.32812 14.782 2.41602 15.0354 2.5918 15.1995C2.77051 15.3606 3.05908 15.4412 3.45752 15.4412C3.7124 15.4412 3.91309 15.387 4.05957 15.2786C4.20898 15.1672 4.28369 15.012 4.28369 14.8127ZM12.0444 13.446C12.0444 14.0378 11.9463 14.5549 11.75 14.9973C11.5537 15.4368 11.2827 15.7898 10.937 16.0564L11.9697 16.8738L11.0161 17.6824L9.64062 16.575C9.51172 16.5925 9.38281 16.6013 9.25391 16.6013C8.70898 16.6013 8.22559 16.4753 7.80371 16.2234C7.38184 15.9714 7.05225 15.6111 6.81494 15.1423C6.58057 14.6736 6.45898 14.1345 6.4502 13.5251V13.1868C6.4502 12.5569 6.56445 12.0032 6.79297 11.5256C7.02441 11.0481 7.35254 10.679 7.77734 10.4182C8.20508 10.1575 8.69434 10.0271 9.24512 10.0271C9.78711 10.0271 10.2705 10.156 10.6953 10.4138C11.1201 10.6716 11.4497 11.0393 11.6841 11.5168C11.9214 11.9915 12.0415 12.5364 12.0444 13.1516V13.446ZM10.4756 13.178C10.4756 12.5422 10.3687 12.0603 10.1548 11.7322C9.94385 11.4011 9.64062 11.2356 9.24512 11.2356C8.83789 11.2356 8.53174 11.3982 8.32666 11.7234C8.12158 12.0486 8.01758 12.5247 8.01465 13.1516V13.446C8.01465 14.0759 8.11865 14.5593 8.32666 14.8962C8.53467 15.2302 8.84375 15.3972 9.25391 15.3972C9.64648 15.3972 9.94678 15.2317 10.1548 14.9006C10.3657 14.5696 10.4727 14.0935 10.4756 13.4724V13.178ZM14.3735 15.3269H17.0586V16.5134H12.8311V10.115H14.3735V15.3269Z" fill="#A2B1C6"/>
|
||||
<path
|
||||
d="M4.5 1.51343H10.5L15 6.01343V8.45284H13.5V6.76343H9.75V3.01343H4.5V8.45284H3V3.01343C3
|
||||
2.18843 3.675 1.51343 4.5 1.51343Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
d="M4.28369 14.8127C4.28369 14.5872 4.20312 14.4114 4.04199 14.2854C3.88379 14.1594
|
||||
3.604 14.0291 3.20264 13.8943C2.80127 13.7595 2.47314 13.6292 2.21826 13.5032C1.38916
|
||||
13.0959 0.974609 12.5364 0.974609 11.8245C0.974609 11.47 1.07715 11.158 1.28223
|
||||
10.8884C1.49023 10.616 1.7832 10.405 2.16113 10.2556C2.53906 10.1033 2.96387 10.0271
|
||||
3.43555 10.0271C3.89551 10.0271 4.30713 10.1091 4.67041 10.2732C5.03662 10.4373 5.3208
|
||||
10.6716 5.52295 10.9763C5.7251 11.2781 5.82617 11.6238 5.82617 12.0134H4.28809C4.28809
|
||||
11.7527 4.20752 11.5505 4.04639 11.407C3.88818 11.2634 3.67285 11.1917 3.40039
|
||||
11.1917C3.125 11.1917 2.90674 11.2532 2.74561 11.3762C2.5874 11.4963 2.5083
|
||||
11.6501 2.5083 11.8376C2.5083 12.0017 2.59619 12.1511 2.77197 12.2859C2.94775 12.4177
|
||||
3.25684 12.5554 3.69922 12.699C4.1416 12.8396 4.50488 12.9919 4.78906 13.156C5.48047
|
||||
13.5544 5.82617 14.1038 5.82617 14.804C5.82617 15.3635 5.61523 15.803 5.19336
|
||||
16.1223C4.77148 16.4417 4.19287 16.6013 3.45752 16.6013C2.93896 16.6013 2.46875 16.509
|
||||
2.04688 16.3245C1.62793 16.137 1.31152 15.8821 1.09766 15.5598C0.886719 15.2346
|
||||
0.78125 14.8611 0.78125 14.4392H2.32812C2.32812 14.782 2.41602 15.0354 2.5918
|
||||
15.1995C2.77051 15.3606 3.05908 15.4412 3.45752 15.4412C3.7124 15.4412 3.91309
|
||||
15.387 4.05957 15.2786C4.20898 15.1672 4.28369 15.012 4.28369 14.8127ZM12.0444
|
||||
13.446C12.0444 14.0378 11.9463 14.5549 11.75 14.9973C11.5537 15.4368 11.2827 15.7898
|
||||
10.937 16.0564L11.9697 16.8738L11.0161 17.6824L9.64062 16.575C9.51172 16.5925 9.38281
|
||||
16.6013 9.25391 16.6013C8.70898 16.6013 8.22559 16.4753 7.80371 16.2234C7.38184 15.9714
|
||||
7.05225 15.6111 6.81494 15.1423C6.58057 14.6736 6.45898 14.1345 6.4502
|
||||
13.5251V13.1868C6.4502 12.5569 6.56445 12.0032 6.79297 11.5256C7.02441 11.0481 7.35254
|
||||
10.679 7.77734 10.4182C8.20508 10.1575 8.69434 10.0271 9.24512 10.0271C9.78711 10.0271
|
||||
10.2705 10.156 10.6953 10.4138C11.1201 10.6716 11.4497 11.0393 11.6841 11.5168C11.9214
|
||||
11.9915 12.0415 12.5364 12.0444 13.1516V13.446ZM10.4756 13.178C10.4756 12.5422 10.3687
|
||||
12.0603 10.1548 11.7322C9.94385 11.4011 9.64062 11.2356 9.24512 11.2356C8.83789 11.2356
|
||||
8.53174 11.3982 8.32666 11.7234C8.12158 12.0486 8.01758 12.5247 8.01465
|
||||
13.1516V13.446C8.01465 14.0759 8.11865 14.5593 8.32666 14.8962C8.53467 15.2302 8.84375
|
||||
15.3972 9.25391 15.3972C9.64648 15.3972 9.94678 15.2317 10.1548 14.9006C10.3657
|
||||
14.5696 10.4727 14.0935 10.4756 13.4724V13.178ZM14.3735
|
||||
15.3269H17.0586V16.5134H12.8311V10.115H14.3735V15.3269Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
|
||||
@@ -5,11 +5,38 @@
|
||||
viewBox="0 0 18 18"
|
||||
fill="none"
|
||||
>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.5 2.16512L3.5 2.16999C2.675 2.16999 2 2.84499 2 3.66999V14.3349C2 15.1599 2.675 15.8349 3.5 15.8349L14.5 15.83C15.3327 15.83 16 15.1551 16 14.33V3.66512C16 2.83511 15.33 2.16512 14.5 2.16512ZM14.5014 3.66512L3.49878 3.66999V14.33L14.5014 14.3251V3.66512Z" fill="#A2B1C6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.27319 14.7069L6.27319 3.32135L7.77319 3.32135L7.77319 14.7069L6.27319 14.7069Z" fill="#A2B1C6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.2639 14.6932L10.2639 3.30772L11.7639 3.30772L11.7639 14.6932L10.2639 14.6932Z" fill="#A2B1C6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.5108 7.48325H2.4895V5.98325H15.5108V7.48325Z" fill="#A2B1C6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15.5108 11.6056H2.4895V10.1056H15.5108V11.6056Z" fill="#A2B1C6"/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M14.5 2.16512L3.5 2.16999C2.675 2.16999 2 2.84499 2 3.66999V14.3349C2 15.1599 2.675
|
||||
15.8349 3.5 15.8349L14.5 15.83C15.3327 15.83 16 15.1551 16 14.33V3.66512C16 2.83511 15.33
|
||||
2.16512 14.5 2.16512ZM14.5014 3.66512L3.49878 3.66999V14.33L14.5014 14.3251V3.66512Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M6.27319 14.7069L6.27319 3.32135L7.77319 3.32135L7.77319 14.7069L6.27319 14.7069Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M10.2639 14.6932L10.2639 3.30772L11.7639 3.30772L11.7639 14.6932L10.2639 14.6932Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M15.5108 7.48325H2.4895V5.98325H15.5108V7.48325Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M15.5108 11.6056H2.4895V10.1056H15.5108V11.6056Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M0.721924 9.93097L4.85292 5.79997L0.721924 1.66897L1.99992 0.399973L7.39992 5.79997L1.99992 11.2L0.721924 9.93097Z"
|
||||
d="M0.721924 9.93097L4.85292 5.79997L0.721924 1.66897L1.99992 0.399973L7.39992
|
||||
5.79997L1.99992 11.2L0.721924 9.93097Z"
|
||||
:fill="expanded ? '#506784' : 'rgba(80, 103, 132, 0.5)'"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import dereference from 'react-chart-editor/lib/lib/dereference'
|
||||
import plotly from 'plotly.js'
|
||||
|
||||
export function getOptionsFromDataSources (dataSources) {
|
||||
if (!dataSources) {
|
||||
@@ -23,7 +24,17 @@ export function getOptionsForSave (state, dataSources) {
|
||||
return stateCopy
|
||||
}
|
||||
|
||||
export async function getImageDataUrl (element, type) {
|
||||
const chartElement = element.querySelector('.js-plotly-plot')
|
||||
return await plotly.toImage(chartElement, {
|
||||
format: type,
|
||||
width: null,
|
||||
height: null
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
getOptionsFromDataSources,
|
||||
getOptionsForSave
|
||||
getOptionsForSave,
|
||||
getImageDataUrl
|
||||
}
|
||||
@@ -8,10 +8,15 @@ const hintsByCode = {
|
||||
|
||||
export default {
|
||||
getResult (source) {
|
||||
const result = {}
|
||||
const result = {
|
||||
columns: []
|
||||
}
|
||||
const values = {}
|
||||
if (source.meta.fields) {
|
||||
source.meta.fields.forEach(col => {
|
||||
result[col.trim()] = source.data.map(row => {
|
||||
const colName = col.trim()
|
||||
result.columns.push(colName)
|
||||
values[colName] = source.data.map(row => {
|
||||
let value = row[col]
|
||||
if (value instanceof Date) {
|
||||
value = value.toISOString()
|
||||
@@ -21,7 +26,9 @@ export default {
|
||||
})
|
||||
} else {
|
||||
for (let i = 0; i <= source.data[0].length - 1; i++) {
|
||||
result[`col${i + 1}`] = source.data.map(row => {
|
||||
const colName = `col${i + 1}`
|
||||
result.columns.push(colName)
|
||||
values[colName] = source.data.map(row => {
|
||||
let value = row[i]
|
||||
if (value instanceof Date) {
|
||||
value = value.toISOString()
|
||||
@@ -30,6 +37,23 @@ export default {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
result.values = values
|
||||
return result
|
||||
},
|
||||
|
||||
prepareForExport (resultSet) {
|
||||
const columns = resultSet.columns
|
||||
const rowCount = resultSet.values[columns[0]].length
|
||||
const result = {
|
||||
fields: columns,
|
||||
data: []
|
||||
}
|
||||
|
||||
for (let rowNumber = 0; rowNumber < rowCount; rowNumber++) {
|
||||
result.data.push(columns.map(column => resultSet.values[column][rowNumber]))
|
||||
}
|
||||
|
||||
return result
|
||||
},
|
||||
|
||||
@@ -81,5 +105,9 @@ export default {
|
||||
|
||||
Papa.parse(file, { ...defaultConfig, ...config })
|
||||
})
|
||||
},
|
||||
|
||||
serialize (resultSet) {
|
||||
return Papa.unparse(this.prepareForExport(resultSet), { delimiter: '\t' })
|
||||
}
|
||||
}
|
||||
@@ -48,17 +48,22 @@ export default class Sql {
|
||||
throw new Error('exec: Missing query string')
|
||||
}
|
||||
const sqlResults = this.db.exec(sql, params)
|
||||
return sqlResults.map(result => _getDataSourcesFromSqlResult(result))
|
||||
return sqlResults.map(result => {
|
||||
return {
|
||||
columns: result.columns,
|
||||
values: _getDataSourcesFromSqlResult(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
import (tabName, data, progressCounterId, progressCallback, chunkSize = 1500) {
|
||||
if (this.db === null) {
|
||||
this.createDb()
|
||||
}
|
||||
const columns = Object.keys(data)
|
||||
const rowCount = data[columns[0]].length
|
||||
this.db.exec(dbUtils.getCreateStatement(tabName, data))
|
||||
const chunks = dbUtils.generateChunks(data, chunkSize)
|
||||
const columns = data.columns
|
||||
const rowCount = data.values[columns[0]].length
|
||||
this.db.exec(dbUtils.getCreateStatement(tabName, data.values))
|
||||
const chunks = dbUtils.generateChunks(data.values, chunkSize)
|
||||
const chunksAmount = Math.ceil(rowCount / chunkSize)
|
||||
let count = 0
|
||||
const insertStr = dbUtils.getInsertStmt(tabName, columns)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import sqliteParser from 'sqlite-parser'
|
||||
|
||||
export default {
|
||||
* generateChunks (data, size) {
|
||||
const matrix = Object.keys(data).map(col => data[col])
|
||||
@@ -47,49 +45,5 @@ export default {
|
||||
|
||||
result = result.replace(/,\s$/, ');')
|
||||
return result
|
||||
},
|
||||
|
||||
getAst (sql) {
|
||||
// There is a bug is sqlite-parser
|
||||
// It throws an error if tokenizer has an arguments:
|
||||
// https://github.com/codeschool/sqlite-parser/issues/59
|
||||
const fixedSql = sql
|
||||
.replace(/(tokenize=[^,]+)"tokenchars=.+?"/, '$1')
|
||||
.replace(/(tokenize=[^,]+)"remove_diacritics=.+?"/, '$1')
|
||||
.replace(/(tokenize=[^,]+)"separators=.+?"/, '$1')
|
||||
.replace(/tokenize=.+?(,|\))/, 'tokenize=unicode61$1')
|
||||
|
||||
return sqliteParser(fixedSql)
|
||||
},
|
||||
|
||||
/*
|
||||
* Return an array of columns with name and type. E.g.:
|
||||
* [
|
||||
* { name: 'id', type: 'INTEGER' },
|
||||
* { name: 'title', type: 'NVARCHAR(30)' },
|
||||
* ]
|
||||
*/
|
||||
getColumns (sql) {
|
||||
const columns = []
|
||||
const ast = this.getAst(sql)
|
||||
|
||||
const columnDefinition = ast.statement[0].format === 'table'
|
||||
? ast.statement[0].definition
|
||||
: ast.statement[0].result.args.expression // virtual table
|
||||
|
||||
columnDefinition.forEach(item => {
|
||||
if (item.variant === 'column' && ['identifier', 'definition'].includes(item.type)) {
|
||||
let type = item.datatype ? item.datatype.variant : 'N/A'
|
||||
if (item.datatype && item.datatype.args) {
|
||||
type = type + '(' + item.datatype.args.expression[0].value
|
||||
if (item.datatype.args.expression.length === 2) {
|
||||
type = type + ', ' + item.datatype.args.expression[1].value
|
||||
}
|
||||
type = type + ')'
|
||||
}
|
||||
columns.push({ name: item.name, type: type })
|
||||
}
|
||||
})
|
||||
return columns
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import stms from './_statements'
|
||||
import fu from '@/lib/utils/fileIo'
|
||||
// We can import workers like so because of worker-loader:
|
||||
// https://webpack.js.org/loaders/worker-loader/
|
||||
@@ -81,24 +80,20 @@ class Database {
|
||||
|
||||
async refreshSchema () {
|
||||
const getSchemaSql = `
|
||||
SELECT name, sql
|
||||
FROM sqlite_master
|
||||
WHERE type='table' AND name NOT LIKE 'sqlite_%';
|
||||
WITH columns as (
|
||||
SELECT
|
||||
a.tbl_name,
|
||||
json_group_array(
|
||||
json_object('name', b.name,'type', IIF(b.type = '', 'N/A', b.type))
|
||||
) as column_json
|
||||
FROM sqlite_master a, pragma_table_info(a.name) b
|
||||
WHERE a.type in ('table','view') AND a.name NOT LIKE 'sqlite_%' group by tbl_name
|
||||
)
|
||||
SELECT json_group_array(json_object('name',tbl_name, 'columns', json(column_json))) objects
|
||||
FROM columns;
|
||||
`
|
||||
const result = await this.execute(getSchemaSql)
|
||||
// Parse DDL statements to get column names and types
|
||||
const parsedSchema = []
|
||||
if (result && result.name) {
|
||||
result.name.forEach((table, index) => {
|
||||
parsedSchema.push({
|
||||
name: table,
|
||||
columns: stms.getColumns(result.sql[index])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Refresh schema
|
||||
this.schema = parsedSchema
|
||||
this.schema = JSON.parse(result.values.objects[0])
|
||||
}
|
||||
|
||||
async execute (commands) {
|
||||
|
||||
38
src/lib/utils/clipboardIo.js
Normal file
38
src/lib/utils/clipboardIo.js
Normal file
@@ -0,0 +1,38 @@
|
||||
import Lib from 'plotly.js/src/lib'
|
||||
import dataUrlToBlob from 'dataurl-to-blob'
|
||||
|
||||
export default {
|
||||
async copyCsv (str) {
|
||||
await navigator.clipboard.writeText(str)
|
||||
Lib.notifier('CSV copied to clipboard successfully', 'long')
|
||||
},
|
||||
|
||||
async copyImage (source) {
|
||||
if (source instanceof HTMLCanvasElement) {
|
||||
return this._copyCanvas(source)
|
||||
} else {
|
||||
return this._copyFromDataUrl(source)
|
||||
}
|
||||
},
|
||||
|
||||
async _copyBlob (blob) {
|
||||
await navigator.clipboard.write([
|
||||
new ClipboardItem({ // eslint-disable-line no-undef
|
||||
[blob.type]: blob
|
||||
})
|
||||
])
|
||||
},
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -11,5 +11,11 @@ export default {
|
||||
clearTimeout(timeout)
|
||||
timeout = setTimeout(() => func.apply(this, arguments), ms)
|
||||
}
|
||||
},
|
||||
|
||||
sleep (ms) {
|
||||
return new Promise(resolve => {
|
||||
setTimeout(() => { resolve() }, ms)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,13 +44,13 @@ export default {
|
||||
|
||||
async created () {
|
||||
const state = this.$store.state
|
||||
let result = await state.db.execute('select sqlite_version()')
|
||||
let result = (await state.db.execute('select sqlite_version()')).values
|
||||
this.info.push({
|
||||
name: 'SQLite version',
|
||||
info: result['sqlite_version()']
|
||||
})
|
||||
|
||||
result = await state.db.execute('PRAGMA compile_options')
|
||||
result = (await state.db.execute('PRAGMA compile_options')).values
|
||||
this.info.push({
|
||||
name: 'SQLite compile options',
|
||||
info: result.compile_options
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
<div id="start-guide" v-if="showedInquiries.length === 0">
|
||||
<div id="start-guide" v-if="allInquiries.length === 0">
|
||||
You don't have saved inquiries so far.
|
||||
<span class="link" @click="$root.$emit('createNewInquiry')">Create</span>
|
||||
the one from scratch or
|
||||
<span @click="importInquiries" class="link">import</span> from a file.
|
||||
</div>
|
||||
<div id="my-inquiries-content" ref="my-inquiries-content" v-show="showedInquiries.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">
|
||||
@@ -33,7 +33,12 @@
|
||||
<text-field placeholder="Search inquiry by name" width="300px" v-model="filter"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-bg">
|
||||
|
||||
<div v-show="showedInquiries.length === 0" id="inquiries-not-found">
|
||||
No inquiries found
|
||||
</div>
|
||||
|
||||
<div v-show="showedInquiries.length > 0" class="rounded-bg">
|
||||
<div class="header-container">
|
||||
<div>
|
||||
<div class="fixed-header" ref="name-th">
|
||||
@@ -79,7 +84,10 @@
|
||||
<div class="second-column">
|
||||
<div class="date-container">{{ inquiry.createdAt | date }}</div>
|
||||
<div class="icons-container">
|
||||
<rename-icon v-if="!inquiry.isPredefined" @click="showRenameDialog(inquiry.id)" />
|
||||
<rename-icon
|
||||
v-if="!inquiry.isPredefined"
|
||||
@click="showRenameDialog(inquiry.id)"
|
||||
/>
|
||||
<copy-icon @click="duplicateInquiry(index)"/>
|
||||
<export-icon
|
||||
@click="exportToFile([inquiry], `${inquiry.name}.json`)"
|
||||
@@ -200,6 +208,7 @@ export default {
|
||||
}
|
||||
return showedInquiries
|
||||
},
|
||||
|
||||
allInquiries () {
|
||||
return this.predefinedInquiries.concat(this.inquiries)
|
||||
},
|
||||
@@ -224,6 +233,22 @@ export default {
|
||||
return `Are you sure you want to delete ${deleteItem}?`
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
showedInquiries () {
|
||||
this.selectedInquiriesIds = new Set(this.showedInquiries
|
||||
.filter(inquiry => this.selectedInquiriesIds.has(inquiry.id))
|
||||
.map(inquiry => inquiry.id)
|
||||
)
|
||||
this.selectedInquiriesCount = this.selectedInquiriesIds.size
|
||||
this.selectedNotPredefinedCount = ([...this.selectedInquiriesIds]
|
||||
.filter(id => !this.predefinedInquiriesIds.has(id))).length
|
||||
|
||||
if (this.selectedInquiriesIds.size < this.showedInquiries.length) {
|
||||
this.$refs.mainCheckBox.checked = false
|
||||
this.selectAll = false
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
storedInquiries.readPredefinedInquiries()
|
||||
.then(inquiries => {
|
||||
@@ -264,7 +289,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
calcNameWidth () {
|
||||
const nameWidth = this.$refs['name-td']
|
||||
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`
|
||||
@@ -312,10 +337,6 @@ export default {
|
||||
},
|
||||
duplicateInquiry (index) {
|
||||
const newInquiry = storedInquiries.duplicateInquiry(this.showedInquiries[index])
|
||||
if (this.selectAll) {
|
||||
this.selectedInquiriesIds.add(newInquiry.id)
|
||||
this.selectedInquiriesCount = this.selectedInquiriesIds.size
|
||||
}
|
||||
this.inquiries.push(newInquiry)
|
||||
storedInquiries.updateStorage(this.inquiries)
|
||||
},
|
||||
@@ -337,14 +358,14 @@ export default {
|
||||
this.$store.commit('deleteTab', tabIndex)
|
||||
}
|
||||
|
||||
// Clear checkboxes
|
||||
// Clear checkbox
|
||||
if (this.selectedInquiriesIds.has(this.processedInquiryId)) {
|
||||
this.selectedInquiriesIds.delete(this.processedInquiryId)
|
||||
}
|
||||
} else {
|
||||
this.inquiries = this.selectAll
|
||||
? []
|
||||
: this.inquiries.filter(inquiry => !this.selectedInquiriesIds.has(inquiry.id))
|
||||
this.inquiries = this.inquiries.filter(
|
||||
inquiry => !this.selectedInquiriesIds.has(inquiry.id)
|
||||
)
|
||||
|
||||
// Close deleted inquiries if it was opened
|
||||
const tabs = this.$store.state.tabs
|
||||
@@ -368,37 +389,36 @@ export default {
|
||||
fu.exportToFile(jsonStr, fileName)
|
||||
},
|
||||
exportSelectedInquiries () {
|
||||
const inquiryList = this.selectAll
|
||||
? this.allInquiries
|
||||
: this.allInquiries.filter(inquiry => this.selectedInquiriesIds.has(inquiry.id))
|
||||
const inquiryList = this.allInquiries.filter(
|
||||
inquiry => this.selectedInquiriesIds.has(inquiry.id)
|
||||
)
|
||||
|
||||
this.exportToFile(inquiryList, 'My sqliteviz inquiries.json')
|
||||
},
|
||||
|
||||
importInquiries () {
|
||||
storedInquiries.importInquiries()
|
||||
.then(importedInquiries => {
|
||||
if (this.selectAll) {
|
||||
importedInquiries.forEach(inquiry => {
|
||||
this.selectedInquiriesIds.add(inquiry.id)
|
||||
})
|
||||
this.selectedInquiriesCount = this.selectedInquiriesIds.size
|
||||
}
|
||||
|
||||
this.inquiries = this.inquiries.concat(importedInquiries)
|
||||
storedInquiries.updateStorage(this.inquiries)
|
||||
})
|
||||
},
|
||||
|
||||
toggleSelectAll (checked) {
|
||||
this.selectAll = checked
|
||||
this.$refs.rowCheckBox.forEach(item => { item.checked = checked })
|
||||
|
||||
this.selectedInquiriesIds = checked
|
||||
? new Set(this.allInquiries.map(inquiry => inquiry.id))
|
||||
? new Set(this.showedInquiries.map(inquiry => inquiry.id))
|
||||
: new Set()
|
||||
|
||||
this.selectedInquiriesCount = this.selectedInquiriesIds.size
|
||||
this.selectedNotPredefinedCount = checked ? this.inquiries.length : 0
|
||||
this.selectedNotPredefinedCount = checked
|
||||
? ([...this.selectedInquiriesIds].filter(id => !this.predefinedInquiriesIds.has(id)))
|
||||
.length
|
||||
: 0
|
||||
},
|
||||
|
||||
toggleRow (checked, id) {
|
||||
const isPredefined = this.predefinedInquiriesIds.has(id)
|
||||
if (checked) {
|
||||
@@ -407,7 +427,7 @@ export default {
|
||||
this.selectedNotPredefinedCount += 1
|
||||
}
|
||||
} else {
|
||||
if (this.selectedInquiriesIds.size === this.allInquiries.length) {
|
||||
if (this.selectedInquiriesIds.size === this.showedInquiries.length) {
|
||||
this.$refs.mainCheckBox.checked = false
|
||||
this.selectAll = false
|
||||
}
|
||||
@@ -433,6 +453,15 @@ export default {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#inquiries-not-found {
|
||||
padding: 35px 5px;
|
||||
border-radius: 5px;
|
||||
border: 1px solid var(--color-border-light);
|
||||
color: var(--color-text-base);
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#my-inquiries-content {
|
||||
padding: 52px;
|
||||
height: 100%;
|
||||
|
||||
@@ -11,7 +11,12 @@
|
||||
@mouseleave="hideTooltip"
|
||||
>
|
||||
<path
|
||||
d="M14.25 15.75H6V5.25H14.25V15.75ZM14.25 3.75H6C5.60218 3.75 5.22064 3.90804 4.93934 4.18934C4.65804 4.47064 4.5 4.85218 4.5 5.25V15.75C4.5 16.1478 4.65804 16.5294 4.93934 16.8107C5.22064 17.092 5.60218 17.25 6 17.25H14.25C14.6478 17.25 15.0294 17.092 15.3107 16.8107C15.592 16.5294 15.75 16.1478 15.75 15.75V5.25C15.75 4.85218 15.592 4.47064 15.3107 4.18934C15.0294 3.90804 14.6478 3.75 14.25 3.75ZM12 0.75H3C2.60218 0.75 2.22064 0.908035 1.93934 1.18934C1.65804 1.47064 1.5 1.85218 1.5 2.25V12.75H3V2.25H12V0.75Z"
|
||||
d="M14.25 15.75H6V5.25H14.25V15.75ZM14.25 3.75H6C5.60218 3.75 5.22064 3.90804 4.93934
|
||||
4.18934C4.65804 4.47064 4.5 4.85218 4.5 5.25V15.75C4.5 16.1478 4.65804 16.5294 4.93934
|
||||
16.8107C5.22064 17.092 5.60218 17.25 6 17.25H14.25C14.6478 17.25 15.0294 17.092 15.3107
|
||||
16.8107C15.592 16.5294 15.75 16.1478 15.75 15.75V5.25C15.75 4.85218 15.592 4.47064
|
||||
15.3107 4.18934C15.0294 3.90804 14.6478 3.75 14.25 3.75ZM12 0.75H3C2.60218 0.75 2.22064
|
||||
0.908035 1.93934 1.18934C1.65804 1.47064 1.5 1.85218 1.5 2.25V12.75H3V2.25H12V0.75Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
@mouseleave="hideTooltip"
|
||||
>
|
||||
<path
|
||||
d="M6.75 2.25V3H3V4.5H3.75V14.25C3.75 14.6478 3.90804 15.0294 4.18934 15.3107C4.47064 15.592 4.85218 15.75 5.25 15.75H12.75C13.1478 15.75 13.5294 15.592 13.8107 15.3107C14.092 15.0294 14.25 14.6478 14.25 14.25V4.5H15V3H11.25V2.25H6.75ZM5.25 4.5H12.75V14.25H5.25V4.5ZM6.75 6V12.75H8.25V6H6.75ZM9.75 6V12.75H11.25V6H9.75Z"
|
||||
d="M6.75 2.25V3H3V4.5H3.75V14.25C3.75 14.6478 3.90804 15.0294 4.18934 15.3107C4.47064
|
||||
15.592 4.85218 15.75 5.25 15.75H12.75C13.1478 15.75 13.5294 15.592 13.8107
|
||||
15.3107C14.092 15.0294 14.25 14.6478 14.25 14.25V4.5H15V3H11.25V2.25H6.75ZM5.25
|
||||
4.5H12.75V14.25H5.25V4.5ZM6.75 6V12.75H8.25V6H6.75ZM9.75 6V12.75H11.25V6H9.75Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
@mouseleave="hideTooltip"
|
||||
>
|
||||
<path
|
||||
d="M10.545 6.75L11.25 7.455L4.44 14.25H3.75V13.56L10.545 6.75ZM13.245 2.25C13.0575 2.25 12.8625 2.325 12.72 2.4675L11.3475 3.84L14.16 6.6525L15.5325 5.28C15.825 4.9875 15.825 4.5 15.5325 4.2225L13.7775 2.4675C13.6275 2.3175 13.44 2.25 13.245 2.25ZM10.545 4.6425L2.25 12.9375V15.75H5.0625L13.3575 7.455L10.545 4.6425Z"
|
||||
d="M10.545 6.75L11.25 7.455L4.44 14.25H3.75V13.56L10.545 6.75ZM13.245 2.25C13.0575 2.25
|
||||
12.8625 2.325 12.72 2.4675L11.3475 3.84L14.16 6.6525L15.5325 5.28C15.825 4.9875 15.825
|
||||
4.5 15.5325 4.2225L13.7775 2.4675C13.6275 2.3175 13.44 2.25 13.245 2.25ZM10.545
|
||||
4.6425L2.25 12.9375V15.75H5.0625L13.3575 7.455L10.545 4.6425Z"
|
||||
fill="#A2B1C6"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@@ -28,13 +28,13 @@ import plotly from 'plotly.js'
|
||||
import 'react-chart-editor/lib/react-chart-editor.min.css'
|
||||
|
||||
import PlotlyEditor from 'react-chart-editor'
|
||||
import chartHelper from './chartHelper'
|
||||
import chartHelper from '@/lib/chartHelper'
|
||||
import dereference from 'react-chart-editor/lib/lib/dereference'
|
||||
import fIo from '@/lib/utils/fileIo'
|
||||
|
||||
export default {
|
||||
name: 'Chart',
|
||||
props: ['dataSources', 'initOptions', 'importToPngEnabled'],
|
||||
props: ['dataSources', 'initOptions', 'importToPngEnabled', 'importToSvgEnabled'],
|
||||
components: {
|
||||
PlotlyEditor
|
||||
},
|
||||
@@ -55,6 +55,12 @@ export default {
|
||||
return chartHelper.getOptionsFromDataSources(this.dataSources)
|
||||
}
|
||||
},
|
||||
created () {
|
||||
// https://github.com/plotly/plotly.js/issues/4555
|
||||
plotly.setPlotConfig ({
|
||||
notifyOnLogging: 1
|
||||
})
|
||||
},
|
||||
mounted () {
|
||||
this.resizeObserver = new ResizeObserver(this.handleResize)
|
||||
this.resizeObserver.observe(this.$refs.chartContainer)
|
||||
@@ -66,8 +72,10 @@ export default {
|
||||
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) {
|
||||
dereference(this.state.data, this.dataSources)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleResize () {
|
||||
@@ -87,10 +95,18 @@ export default {
|
||||
return chartHelper.getOptionsForSave(this.state, this.dataSources)
|
||||
},
|
||||
async saveAsPng () {
|
||||
const chartElement = this.$refs.plotlyEditor.$el.querySelector('.js-plotly-plot')
|
||||
const url = await plotly.toImage(chartElement, { format: 'png', width: null, height: null })
|
||||
const url = await this.prepareCopy()
|
||||
this.$emit('loadingImageCompleted')
|
||||
fIo.downloadFromUrl(url, 'chart')
|
||||
},
|
||||
|
||||
async saveAsSvg () {
|
||||
const url = await this.prepareCopy('svg')
|
||||
fIo.downloadFromUrl(url, 'chart')
|
||||
},
|
||||
|
||||
async prepareCopy (type = 'png') {
|
||||
return await chartHelper.getImageDataUrl(this.$refs.plotlyEditor.$el, type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@
|
||||
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'
|
||||
import Chart from '@/views/Main/Workspace/Tabs/Tab/DataView/Chart'
|
||||
import Vue from 'vue'
|
||||
const ChartClass = Vue.extend(Chart)
|
||||
@@ -165,8 +165,11 @@ export default {
|
||||
colOrder: (this.value && this.value.colOrder) || 'key_a_to_z',
|
||||
rowOrder: (this.value && this.value.rowOrder) || 'key_a_to_z',
|
||||
customChartComponent:
|
||||
(this.value && this.value.rendererOptions && this.value.rendererOptions.customChartComponent) ||
|
||||
new ChartClass()
|
||||
(
|
||||
this.value &&
|
||||
this.value.rendererOptions &&
|
||||
this.value.rendererOptions.customChartComponent
|
||||
) || new ChartClass()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -222,7 +225,10 @@ export default {
|
||||
},
|
||||
created () {
|
||||
this.customChartComponent.$on('update', () => { this.$emit('update') })
|
||||
this.customChartComponent.$on('loadingImageCompleted', value => { this.$emit('loadingCustomChartImageCompleted') })
|
||||
this.customChartComponent.$on(
|
||||
'loadingImageCompleted',
|
||||
value => { this.$emit('loadingCustomChartImageCompleted') }
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
returnValue () {
|
||||
|
||||
@@ -14,20 +14,20 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import html2canvas from 'html2canvas'
|
||||
import plotly from 'plotly.js'
|
||||
import fIo from '@/lib/utils/fileIo'
|
||||
import $ from 'jquery'
|
||||
import 'pivottable'
|
||||
import 'pivottable/dist/pivot.css'
|
||||
import PivotUi from './PivotUi'
|
||||
import { getPivotCanvas } from './pivotHelper'
|
||||
import Chart from '@/views/Main/Workspace/Tabs/Tab/DataView/Chart'
|
||||
import chartHelper from '@/lib/chartHelper'
|
||||
import Vue from 'vue'
|
||||
const ChartClass = Vue.extend(Chart)
|
||||
|
||||
export default {
|
||||
name: 'pivot',
|
||||
props: ['dataSources', 'initOptions', 'importToPngEnabled'],
|
||||
props: ['dataSources', 'initOptions', 'importToPngEnabled', 'importToSvgEnabled'],
|
||||
components: {
|
||||
PivotUi
|
||||
},
|
||||
@@ -53,7 +53,9 @@ export default {
|
||||
colOrder: this.initOptions.colOrder,
|
||||
rowOrder: this.initOptions.rowOrder,
|
||||
aggregatorName: this.initOptions.aggregatorName,
|
||||
aggregator: $.pivotUtilities.aggregators[this.initOptions.aggregatorName](this.initOptions.vals),
|
||||
aggregator: $.pivotUtilities.aggregators[
|
||||
this.initOptions.aggregatorName
|
||||
](this.initOptions.vals),
|
||||
vals: this.initOptions.vals,
|
||||
rendererName: this.initOptions.rendererName,
|
||||
renderer: $.pivotUtilities.renderers[this.initOptions.rendererName],
|
||||
@@ -68,6 +70,14 @@ export default {
|
||||
computed: {
|
||||
columns () {
|
||||
return Object.keys(this.dataSources || {})
|
||||
},
|
||||
|
||||
viewStandartChart () {
|
||||
return this.pivotOptions.rendererName in $.pivotUtilities.plotly_renderers
|
||||
},
|
||||
|
||||
viewCustomChart () {
|
||||
return this.pivotOptions.rendererName === 'Custom chart'
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -78,6 +88,7 @@ export default {
|
||||
immediate: true,
|
||||
handler () {
|
||||
this.$emit('update:importToPngEnabled', this.pivotOptions.rendererName !== 'TSV Export')
|
||||
this.$emit('update:importToSvgEnabled', this.viewStandartChart || this.viewCustomChart)
|
||||
}
|
||||
},
|
||||
pivotOptions () {
|
||||
@@ -98,14 +109,14 @@ export default {
|
||||
handleResize () {
|
||||
// hack: plotly changes size only on window.resize event,
|
||||
// so, we trigger it when container resizes (e.g. when move splitter)
|
||||
if (this.pivotOptions.rendererName in $.pivotUtilities.plotly_renderers) {
|
||||
if (this.viewStandartChart) {
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
}
|
||||
},
|
||||
|
||||
show () {
|
||||
const options = { ...this.pivotOptions }
|
||||
if (this.pivotOptions.rendererName in $.pivotUtilities.plotly_renderers) {
|
||||
if (this.viewStandartChart) {
|
||||
options.rendererOptions = {
|
||||
plotly: {
|
||||
autosize: true,
|
||||
@@ -135,7 +146,7 @@ export default {
|
||||
)
|
||||
|
||||
// fix for Firefox: fit plotly renderers just after choosing it in pivotUi
|
||||
if (this.pivotOptions.rendererName in $.pivotUtilities.plotly_renderers) {
|
||||
if (this.viewStandartChart) {
|
||||
window.dispatchEvent(new Event('resize'))
|
||||
}
|
||||
},
|
||||
@@ -153,22 +164,34 @@ export default {
|
||||
},
|
||||
|
||||
async saveAsPng () {
|
||||
if (this.pivotOptions.rendererName === 'Custom chart') {
|
||||
if (this.viewCustomChart) {
|
||||
this.pivotOptions.rendererOptions.customChartComponent.saveAsPng()
|
||||
} else if (this.pivotOptions.rendererName in $.pivotUtilities.plotly_renderers) {
|
||||
const chartElement = this.$refs.pivotOutput.querySelector('.js-plotly-plot')
|
||||
const url = await plotly.toImage(chartElement, {
|
||||
format: 'png',
|
||||
width: null,
|
||||
height: null
|
||||
})
|
||||
this.$emit('loadingImageCompleted')
|
||||
fIo.downloadFromUrl(url, 'pivot')
|
||||
} else {
|
||||
const tableElement = this.$refs.pivotOutput.querySelector('.pvtTable')
|
||||
const canvas = await html2canvas(tableElement)
|
||||
const source = this.viewStandartChart
|
||||
? await chartHelper.getImageDataUrl(this.$refs.pivotOutput, 'png')
|
||||
: (await getPivotCanvas(this.$refs.pivotOutput)).toDataURL('image/png')
|
||||
|
||||
this.$emit('loadingImageCompleted')
|
||||
fIo.downloadFromUrl(canvas.toDataURL('image/png'), 'pivot', 'image/png')
|
||||
fIo.downloadFromUrl(source, 'pivot')
|
||||
}
|
||||
},
|
||||
|
||||
async prepareCopy () {
|
||||
if (this.viewCustomChart) {
|
||||
return await this.pivotOptions.rendererOptions.customChartComponent.prepareCopy()
|
||||
} else if (this.viewStandartChart) {
|
||||
return await chartHelper.getImageDataUrl(this.$refs.pivotOutput, 'png')
|
||||
} else {
|
||||
return await getPivotCanvas(this.$refs.pivotOutput)
|
||||
}
|
||||
},
|
||||
|
||||
async saveAsSvg () {
|
||||
if (this.viewCustomChart) {
|
||||
this.pivotOptions.rendererOptions.customChartComponent.saveAsSvg()
|
||||
} else if (this.viewStandartChart) {
|
||||
const url = await chartHelper.getImageDataUrl(this.$refs.pivotOutput, 'svg')
|
||||
fIo.downloadFromUrl(url, 'pivot')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import $ from 'jquery'
|
||||
import 'pivottable'
|
||||
import 'pivottable/dist/export_renderers.js'
|
||||
import 'pivottable/dist/plotly_renderers.js'
|
||||
import html2canvas from 'html2canvas'
|
||||
|
||||
export const zeroValAggregators = [
|
||||
'Count',
|
||||
@@ -75,3 +76,8 @@ export const aggregators = Object.keys($.pivotUtilities.aggregators).map(key =>
|
||||
fun: $.pivotUtilities.aggregators[key]
|
||||
}
|
||||
})
|
||||
|
||||
export async function getPivotCanvas (pivotOutput) {
|
||||
const tableElement = pivotOutput.querySelector('.pvtTable')
|
||||
return await html2canvas(tableElement, { logging: false })
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
:init-options="mode === initMode ? initOptions : undefined"
|
||||
:data-sources="dataSource"
|
||||
:import-to-png-enabled.sync="importToPngEnabled"
|
||||
:import-to-svg-enabled.sync="importToSvgEnabled"
|
||||
@loadingImageCompleted="loadingImage = false"
|
||||
ref="viewComponent"
|
||||
@update="$emit('update')"
|
||||
@@ -40,7 +41,36 @@
|
||||
>
|
||||
<png-icon />
|
||||
</icon-button>
|
||||
|
||||
<icon-button
|
||||
:disabled="!importToSvgEnabled"
|
||||
tooltip="Save as SVG"
|
||||
tooltip-position="top-left"
|
||||
@click="saveAsSvg"
|
||||
>
|
||||
<export-to-svg-icon />
|
||||
</icon-button>
|
||||
|
||||
<icon-button
|
||||
:loading="copyingImage"
|
||||
tooltip="Copy visualisation to clipboard"
|
||||
tooltip-position="top-left"
|
||||
@click="prepareCopy"
|
||||
>
|
||||
<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"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -51,7 +81,12 @@ import SideToolBar from '../SideToolBar'
|
||||
import IconButton from '@/components/IconButton'
|
||||
import ChartIcon from '@/components/svg/chart'
|
||||
import PivotIcon from '@/components/svg/pivot'
|
||||
import ExportToSvgIcon from '@/components/svg/exportToSvg'
|
||||
import PngIcon from '@/components/svg/png'
|
||||
import ClipboardIcon from '@/components/svg/clipboard'
|
||||
import cIo from '@/lib/utils/clipboardIo'
|
||||
import loadingDialog from '@/components/LoadingDialog'
|
||||
import time from '@/lib/utils/time'
|
||||
|
||||
export default {
|
||||
name: 'DataView',
|
||||
@@ -63,13 +98,20 @@ export default {
|
||||
IconButton,
|
||||
ChartIcon,
|
||||
PivotIcon,
|
||||
PngIcon
|
||||
ExportToSvgIcon,
|
||||
PngIcon,
|
||||
ClipboardIcon,
|
||||
loadingDialog
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
mode: this.initMode || 'chart',
|
||||
importToPngEnabled: true,
|
||||
loadingImage: false
|
||||
importToSvgEnabled: true,
|
||||
loadingImage: false,
|
||||
copyingImage: false,
|
||||
preparingCopy: false,
|
||||
dataToCopy: null
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -82,19 +124,59 @@ export default {
|
||||
async saveAsPng () {
|
||||
this.loadingImage = true
|
||||
/*
|
||||
setTimeout does its thing by putting its callback on the callback queue. The callback queue is only called by the browser after both the call stack and the render queue are done. So our animation (which is on the call stack) gets done, the render queue renders it, and then the browser is ready for the callback queue and calls the long-calculation.
|
||||
setTimeout does its thing by putting its callback on the callback queue.
|
||||
The callback queue is only called by the browser after both the call stack
|
||||
and the render queue are done. So our animation (which is on the call stack) gets done,
|
||||
the render queue renders it, and then the browser is ready for the callback queue
|
||||
and calls the long-calculation.
|
||||
|
||||
nextTick allows you to do something after you have changed the data and VueJS has updated the DOM based on your data change, but before the browser has rendered those changed on the page.
|
||||
nextTick allows you to do something after you have changed the data
|
||||
and VueJS has updated the DOM based on your data change,
|
||||
but before the browser has rendered those changed on the page.
|
||||
|
||||
Lees meer van Katinka Hesselink: http://www.hesselinkwebdesign.nl/2019/nexttick-vs-settimeout-in-vue/
|
||||
http://www.hesselinkwebdesign.nl/2019/nexttick-vs-settimeout-in-vue/
|
||||
|
||||
*/
|
||||
setTimeout(() => {
|
||||
await time.sleep(0)
|
||||
this.$refs.viewComponent.saveAsPng()
|
||||
}, 0)
|
||||
},
|
||||
getOptionsForSave () {
|
||||
return this.$refs.viewComponent.getOptionsForSave()
|
||||
},
|
||||
async prepareCopy () {
|
||||
if ('ClipboardItem' in window) {
|
||||
this.preparingCopy = true
|
||||
this.$modal.show('prepareCopy')
|
||||
const t0 = performance.now()
|
||||
|
||||
await time.sleep(0)
|
||||
this.dataToCopy = await this.$refs.viewComponent.prepareCopy()
|
||||
const t1 = performance.now()
|
||||
if ((t1 - t0) < 950) {
|
||||
this.$modal.hide('prepareCopy')
|
||||
this.copyToClipboard()
|
||||
} else {
|
||||
this.preparingCopy = false
|
||||
}
|
||||
} 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.'
|
||||
)
|
||||
}
|
||||
},
|
||||
async copyToClipboard () {
|
||||
cIo.copyImage(this.dataToCopy)
|
||||
this.$modal.hide('prepareCopy')
|
||||
},
|
||||
cancelCopy () {
|
||||
this.dataToCopy = null
|
||||
this.$modal.hide('prepareCopy')
|
||||
},
|
||||
|
||||
saveAsSvg () {
|
||||
this.$refs.viewComponent.saveAsSvg()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,4 +197,19 @@ export default {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
>>>.vm--container {
|
||||
animation: show-modal 1s linear 0s 1;
|
||||
}
|
||||
|
||||
@keyframes show-modal {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
99% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -26,7 +26,36 @@
|
||||
class="straight"
|
||||
/>
|
||||
</div>
|
||||
<side-tool-bar @switchTo="$emit('switchTo', $event)" panel="table"/>
|
||||
<side-tool-bar @switchTo="$emit('switchTo', $event)" panel="table">
|
||||
<icon-button
|
||||
:disabled="!result"
|
||||
tooltip="Export result set to CSV file"
|
||||
tooltip-position="top-left"
|
||||
@click="exportToCsv"
|
||||
>
|
||||
<export-to-csv-icon/>
|
||||
</icon-button>
|
||||
|
||||
<icon-button
|
||||
:disabled="!result"
|
||||
tooltip="Copy result set to clipboard"
|
||||
tooltip-position="top-left"
|
||||
@click="prepareCopy"
|
||||
>
|
||||
<clipboard-icon/>
|
||||
</icon-button>
|
||||
</side-tool-bar>
|
||||
|
||||
<loading-dialog
|
||||
loadingMsg="Building CSV..."
|
||||
successMsg="CSV is ready"
|
||||
actionBtnName="Copy"
|
||||
name="prepareCSVCopy"
|
||||
title="Copy to clipboard"
|
||||
:loading="preparingCopy"
|
||||
@action="copyToClipboard"
|
||||
@cancel="cancelCopy"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -35,6 +64,14 @@ import Logs from '@/components/Logs'
|
||||
import SqlTable from '@/components/SqlTable'
|
||||
import LoadingIndicator from '@/components/LoadingIndicator'
|
||||
import SideToolBar from './SideToolBar'
|
||||
import ExportToCsvIcon from '@/components/svg/exportToCsv'
|
||||
import ClipboardIcon from '@/components/svg/clipboard'
|
||||
import IconButton from '@/components/IconButton'
|
||||
import csv from '@/lib/csv'
|
||||
import fIo from '@/lib/utils/fileIo'
|
||||
import cIo from '@/lib/utils/clipboardIo'
|
||||
import time from '@/lib/utils/time'
|
||||
import loadingDialog from '@/components/LoadingDialog'
|
||||
|
||||
export default {
|
||||
name: 'RunResult',
|
||||
@@ -42,14 +79,20 @@ export default {
|
||||
data () {
|
||||
return {
|
||||
resizeObserver: null,
|
||||
pageSize: 20
|
||||
pageSize: 20,
|
||||
preparingCopy: false,
|
||||
dataToCopy: null
|
||||
}
|
||||
},
|
||||
components: {
|
||||
SqlTable,
|
||||
LoadingIndicator,
|
||||
Logs,
|
||||
SideToolBar
|
||||
SideToolBar,
|
||||
ExportToCsvIcon,
|
||||
IconButton,
|
||||
ClipboardIcon,
|
||||
loadingDialog
|
||||
},
|
||||
mounted () {
|
||||
this.resizeObserver = new ResizeObserver(this.handleResize)
|
||||
@@ -63,6 +106,7 @@ export default {
|
||||
handleResize () {
|
||||
this.calculatePageSize()
|
||||
},
|
||||
|
||||
calculatePageSize () {
|
||||
const runResultPanel = this.$refs.runResultPanel
|
||||
// 27 - table footer hight
|
||||
@@ -70,6 +114,44 @@ export default {
|
||||
// 35 - height of table header
|
||||
const freeSpace = runResultPanel.offsetHeight - 27 - 5 - 35
|
||||
this.pageSize = Math.max(Math.floor(freeSpace / 35), 20)
|
||||
},
|
||||
|
||||
exportToCsv () {
|
||||
fIo.exportToFile(csv.serialize(this.result), 'result_set.csv', 'text/csv')
|
||||
},
|
||||
|
||||
async prepareCopy () {
|
||||
if ('ClipboardItem' in window) {
|
||||
this.preparingCopy = true
|
||||
this.$modal.show('prepareCSVCopy')
|
||||
const t0 = performance.now()
|
||||
|
||||
await time.sleep(0)
|
||||
this.dataToCopy = csv.serialize(this.result)
|
||||
const t1 = performance.now()
|
||||
if ((t1 - t0) < 950) {
|
||||
this.$modal.hide('prepareCSVCopy')
|
||||
this.copyToClipboard()
|
||||
} else {
|
||||
this.preparingCopy = false
|
||||
}
|
||||
} 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.'
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
copyToClipboard () {
|
||||
cIo.copyCsv(this.dataToCopy)
|
||||
this.$modal.hide('prepareCSVCopy')
|
||||
},
|
||||
|
||||
cancelCopy () {
|
||||
this.dataToCopy = null
|
||||
this.$modal.hide('prepareCSVCopy')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,4 +209,20 @@ export default {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
>>>.vm--container {
|
||||
animation: show-modal 1s linear 0s 1;
|
||||
}
|
||||
|
||||
@keyframes show-modal {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
99% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
<teleport :to="`#${layout.dataView}-${tabIndex}`">
|
||||
<data-view
|
||||
:data-source="result"
|
||||
:data-source="(result && result.values) || null"
|
||||
:init-options="initViewOptions"
|
||||
:init-mode="initViewType"
|
||||
ref="dataView"
|
||||
@@ -59,7 +59,9 @@ import Teleport from 'vue2-teleport'
|
||||
|
||||
export default {
|
||||
name: 'Tab',
|
||||
props: ['id', 'initName', 'initQuery', 'initViewOptions', 'tabIndex', 'isPredefined', 'initViewType'],
|
||||
props: [
|
||||
'id', 'initName', 'initQuery', 'initViewOptions', 'tabIndex', 'isPredefined', 'initViewType'
|
||||
],
|
||||
components: {
|
||||
SqlEditor,
|
||||
DataView,
|
||||
|
||||
@@ -3,7 +3,7 @@ import sinon from 'sinon'
|
||||
import Vuex from 'vuex'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import CsvImport from '@/components/CsvImport'
|
||||
import csv from '@/components/CsvImport/csv'
|
||||
import csv from '@/lib/csv'
|
||||
|
||||
describe('CsvImport.vue', () => {
|
||||
let state = {}
|
||||
@@ -58,8 +58,11 @@ describe('CsvImport.vue', () => {
|
||||
sinon.stub(csv, 'parse').resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col2', 'col1'],
|
||||
values: {
|
||||
col1: [1, 2],
|
||||
col2: ['foo', 'bar']
|
||||
}
|
||||
},
|
||||
rowCount: 2,
|
||||
messages: [{
|
||||
@@ -82,10 +85,10 @@ describe('CsvImport.vue', () => {
|
||||
expect(wrapper.findComponent({ name: 'check-box' }).vm.checked).to.equal(true)
|
||||
const rows = wrapper.findAll('tbody tr')
|
||||
expect(rows).to.have.lengthOf(2)
|
||||
expect(rows.at(0).findAll('td').at(0).text()).to.equal('1')
|
||||
expect(rows.at(0).findAll('td').at(1).text()).to.equal('foo')
|
||||
expect(rows.at(1).findAll('td').at(0).text()).to.equal('2')
|
||||
expect(rows.at(1).findAll('td').at(1).text()).to.equal('bar')
|
||||
expect(rows.at(0).findAll('td').at(0).text()).to.equal('foo')
|
||||
expect(rows.at(0).findAll('td').at(1).text()).to.equal('1')
|
||||
expect(rows.at(1).findAll('td').at(0).text()).to.equal('bar')
|
||||
expect(rows.at(1).findAll('td').at(1).text()).to.equal('2')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.include('Information about row 0. Comma was used as a standart delimiter.')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
@@ -99,8 +102,11 @@ describe('CsvImport.vue', () => {
|
||||
parse.onCall(0).resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col2', 'col1'],
|
||||
values: {
|
||||
col1: [1],
|
||||
col2: ['foo']
|
||||
}
|
||||
},
|
||||
rowCount: 1
|
||||
})
|
||||
@@ -113,8 +119,11 @@ describe('CsvImport.vue', () => {
|
||||
parse.onCall(1).resolves({
|
||||
delimiter: ',',
|
||||
data: {
|
||||
columns: ['col2', 'col1'],
|
||||
values: {
|
||||
col1: [2],
|
||||
col2: ['bar']
|
||||
}
|
||||
},
|
||||
rowCount: 1,
|
||||
hasErrors: false
|
||||
@@ -125,16 +134,19 @@ describe('CsvImport.vue', () => {
|
||||
|
||||
let rows = wrapper.findAll('tbody tr')
|
||||
expect(rows).to.have.lengthOf(1)
|
||||
expect(rows.at(0).findAll('td').at(0).text()).to.equal('2')
|
||||
expect(rows.at(0).findAll('td').at(1).text()).to.equal('bar')
|
||||
expect(rows.at(0).findAll('td').at(0).text()).to.equal('bar')
|
||||
expect(rows.at(0).findAll('td').at(1).text()).to.equal('2')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.include('Preview parsing is completed in')
|
||||
|
||||
parse.onCall(2).resolves({
|
||||
delimiter: ',',
|
||||
data: {
|
||||
columns: ['col2', 'col1'],
|
||||
values: {
|
||||
col1: [3],
|
||||
col2: ['baz']
|
||||
}
|
||||
},
|
||||
rowCount: 1,
|
||||
hasErrors: true,
|
||||
@@ -152,18 +164,24 @@ describe('CsvImport.vue', () => {
|
||||
await csv.parse.returnValues[2]
|
||||
rows = wrapper.findAll('tbody tr')
|
||||
expect(rows).to.have.lengthOf(1)
|
||||
expect(rows.at(0).findAll('td').at(0).text()).to.equal('3')
|
||||
expect(rows.at(0).findAll('td').at(1).text()).to.equal('baz')
|
||||
expect(rows.at(0).findAll('td').at(0).text()).to.equal('baz')
|
||||
expect(rows.at(0).findAll('td').at(1).text()).to.equal('3')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.contain('Error in row 0. Quote is missed. Edit your CSV so that the field has a closing quote char.')
|
||||
.to.contain(
|
||||
'Error in row 0. Quote is missed. ' +
|
||||
'Edit your CSV so that the field has a closing quote char.'
|
||||
)
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.not.contain('Preview parsing is completed in')
|
||||
|
||||
parse.onCall(3).resolves({
|
||||
delimiter: ',',
|
||||
data: {
|
||||
columns: ['col2', 'col1'],
|
||||
values: {
|
||||
col1: [4],
|
||||
col2: ['qux']
|
||||
}
|
||||
},
|
||||
rowCount: 1,
|
||||
hasErrors: false
|
||||
@@ -173,16 +191,19 @@ describe('CsvImport.vue', () => {
|
||||
await csv.parse.returnValues[3]
|
||||
rows = wrapper.findAll('tbody tr')
|
||||
expect(rows).to.have.lengthOf(1)
|
||||
expect(rows.at(0).findAll('td').at(0).text()).to.equal('4')
|
||||
expect(rows.at(0).findAll('td').at(1).text()).to.equal('qux')
|
||||
expect(rows.at(0).findAll('td').at(0).text()).to.equal('qux')
|
||||
expect(rows.at(0).findAll('td').at(1).text()).to.equal('4')
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.contain('Preview parsing is completed in')
|
||||
|
||||
parse.onCall(4).resolves({
|
||||
delimiter: ',',
|
||||
data: {
|
||||
columns: ['col2', 'col1'],
|
||||
values: {
|
||||
col1: [5],
|
||||
col2: ['corge']
|
||||
}
|
||||
},
|
||||
rowCount: 1,
|
||||
hasErrors: false
|
||||
@@ -192,8 +213,9 @@ describe('CsvImport.vue', () => {
|
||||
await csv.parse.returnValues[4]
|
||||
rows = wrapper.findAll('tbody tr')
|
||||
expect(rows).to.have.lengthOf(1)
|
||||
expect(rows.at(0).findAll('td').at(0).text()).to.equal('5')
|
||||
expect(rows.at(0).findAll('td').at(1).text()).to.equal('corge')
|
||||
expect(rows.at(0).findAll('td').at(0).text()).to.equal('corge')
|
||||
expect(rows.at(0).findAll('td').at(1).text()).to.equal('5')
|
||||
|
||||
expect(wrapper.findComponent({ name: 'logs' }).text())
|
||||
.to.include('Preview parsing is completed in')
|
||||
})
|
||||
@@ -203,8 +225,11 @@ describe('CsvImport.vue', () => {
|
||||
parse.onCall(0).resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1],
|
||||
col2: ['foo']
|
||||
}
|
||||
},
|
||||
rowCount: 1
|
||||
})
|
||||
@@ -256,8 +281,11 @@ describe('CsvImport.vue', () => {
|
||||
parse.onCall(0).resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1],
|
||||
col2: ['foo']
|
||||
}
|
||||
},
|
||||
rowCount: 1,
|
||||
hasErrors: false,
|
||||
@@ -267,8 +295,11 @@ describe('CsvImport.vue', () => {
|
||||
parse.onCall(1).resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1, 2],
|
||||
col2: ['foo', 'bar']
|
||||
}
|
||||
},
|
||||
rowCount: 2,
|
||||
hasErrors: false,
|
||||
@@ -311,8 +342,11 @@ describe('CsvImport.vue', () => {
|
||||
parse.onCall(0).resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1],
|
||||
col2: ['foo']
|
||||
}
|
||||
},
|
||||
rowCount: 1,
|
||||
hasErrors: false,
|
||||
@@ -322,8 +356,11 @@ describe('CsvImport.vue', () => {
|
||||
parse.onCall(1).resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1, 2],
|
||||
col2: ['foo', 'bar']
|
||||
}
|
||||
},
|
||||
rowCount: 2,
|
||||
hasErrors: false,
|
||||
@@ -373,8 +410,11 @@ describe('CsvImport.vue', () => {
|
||||
parse.onCall(0).resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1],
|
||||
col2: ['foo']
|
||||
}
|
||||
},
|
||||
rowCount: 1,
|
||||
hasErrors: false,
|
||||
@@ -384,8 +424,11 @@ describe('CsvImport.vue', () => {
|
||||
parse.onCall(1).resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1, 2],
|
||||
col2: ['foo', 'bar']
|
||||
}
|
||||
},
|
||||
rowCount: 2,
|
||||
hasErrors: true,
|
||||
@@ -429,8 +472,11 @@ describe('CsvImport.vue', () => {
|
||||
parse.onCall(0).resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1],
|
||||
col2: ['foo']
|
||||
}
|
||||
},
|
||||
rowCount: 1,
|
||||
hasErrors: false,
|
||||
@@ -440,8 +486,11 @@ describe('CsvImport.vue', () => {
|
||||
parse.onCall(1).resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1, 2],
|
||||
col2: ['foo', 'bar']
|
||||
}
|
||||
},
|
||||
rowCount: 2,
|
||||
hasErrors: false,
|
||||
@@ -496,8 +545,11 @@ describe('CsvImport.vue', () => {
|
||||
parse.onCall(0).resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1],
|
||||
col2: ['foo']
|
||||
}
|
||||
},
|
||||
rowCount: 1,
|
||||
hasErrors: false,
|
||||
@@ -507,8 +559,11 @@ describe('CsvImport.vue', () => {
|
||||
parse.onCall(1).resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1, 2],
|
||||
col2: ['foo', 'bar']
|
||||
}
|
||||
},
|
||||
rowCount: 2,
|
||||
hasErrors: false,
|
||||
@@ -545,8 +600,11 @@ describe('CsvImport.vue', () => {
|
||||
parse.onCall(0).resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1],
|
||||
col2: ['foo']
|
||||
}
|
||||
},
|
||||
rowCount: 1,
|
||||
hasErrors: false,
|
||||
@@ -556,8 +614,11 @@ describe('CsvImport.vue', () => {
|
||||
parse.onCall(1).resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1, 2],
|
||||
col2: ['foo', 'bar']
|
||||
}
|
||||
},
|
||||
rowCount: 2,
|
||||
hasErrors: false,
|
||||
@@ -596,8 +657,11 @@ describe('CsvImport.vue', () => {
|
||||
sinon.stub(csv, 'parse').resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1],
|
||||
col2: ['foo']
|
||||
}
|
||||
},
|
||||
rowCount: 1,
|
||||
hasErrors: false,
|
||||
@@ -624,8 +688,11 @@ describe('CsvImport.vue', () => {
|
||||
sinon.stub(csv, 'parse').resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1],
|
||||
col2: ['foo']
|
||||
}
|
||||
},
|
||||
rowCount: 1,
|
||||
hasErrors: false,
|
||||
|
||||
@@ -19,6 +19,7 @@ describe('LoadingIndicator.vue', () => {
|
||||
})
|
||||
// The lendth of circle in the component is 50.24. If progress is 50% then resulting arc
|
||||
// should be 25.12
|
||||
expect(wrapper.find('.loader-svg.front').element.style.strokeDasharray).to.equal('25.12px, 25.12px')
|
||||
expect(wrapper.find('.loader-svg.front').element.style.strokeDasharray)
|
||||
.to.equal('25.12px, 25.12px')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import * as chartHelper from '@/views/Main/Workspace/Tabs/Tab/DataView/Chart/chartHelper'
|
||||
import * as chartHelper from '@/lib/chartHelper'
|
||||
import * as dereference from 'react-chart-editor/lib/lib/dereference'
|
||||
|
||||
describe('chartHelper.js', () => {
|
||||
@@ -53,4 +53,18 @@ describe('chartHelper.js', () => {
|
||||
|
||||
expect(ds).to.equal(JSON.parse.returnValues[0])
|
||||
})
|
||||
|
||||
it('getImageDataUrl returns dataUrl', async () => {
|
||||
const element = document.createElement('div')
|
||||
const child = document.createElement('div')
|
||||
element.append(child)
|
||||
child.classList.add('js-plotly-plot')
|
||||
|
||||
let url = await chartHelper.getImageDataUrl(element, 'png')
|
||||
expect(/^data:image\/png/.test(url)).to.equal(true)
|
||||
|
||||
url = await chartHelper.getImageDataUrl(element, 'svg')
|
||||
console.log()
|
||||
expect(/^data:image\/svg\+xml/.test(url)).to.equal(true)
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import csv from '@/components/CsvImport/csv'
|
||||
import csv from '@/lib/csv'
|
||||
import Papa from 'papaparse'
|
||||
|
||||
describe('csv.js', () => {
|
||||
@@ -19,9 +19,12 @@ describe('csv.js', () => {
|
||||
}
|
||||
}
|
||||
expect(csv.getResult(source)).to.eql({
|
||||
columns: ['id', 'name', 'date'],
|
||||
values: {
|
||||
id: [1, 2],
|
||||
name: ['foo', 'bar'],
|
||||
date: ['2021-06-30T14:10:24.717Z', '2021-07-30T14:10:15.717Z']
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -34,9 +37,12 @@ describe('csv.js', () => {
|
||||
meta: {}
|
||||
}
|
||||
expect(csv.getResult(source)).to.eql({
|
||||
columns: ['col1', 'col2', 'col3'],
|
||||
values: {
|
||||
col1: [1, 2],
|
||||
col2: ['foo', 'bar'],
|
||||
col3: ['2021-06-30T14:10:24.717Z', '2021-07-30T14:10:15.717Z']
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -73,8 +79,11 @@ describe('csv.js', () => {
|
||||
const result = await csv.parse(file)
|
||||
expect(result).to.eql({
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1, 2],
|
||||
col2: ['foo', 'bar']
|
||||
}
|
||||
},
|
||||
delimiter: ',',
|
||||
rowCount: 2,
|
||||
@@ -106,4 +115,32 @@ describe('csv.js', () => {
|
||||
const file = {}
|
||||
await expect(csv.parse(file)).to.be.rejectedWith(err)
|
||||
})
|
||||
|
||||
it('prepareForExport', () => {
|
||||
const resultSet = {
|
||||
columns: ['id', 'name'],
|
||||
values: {
|
||||
id: [1, 2],
|
||||
name: ['foo', 'bar']
|
||||
}
|
||||
}
|
||||
expect(csv.prepareForExport(resultSet)).to.eql({
|
||||
fields: ['id', 'name'],
|
||||
data: [
|
||||
[1, 'foo'],
|
||||
[2, 'bar']
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
it('serialize returns tsv text', () => {
|
||||
const resultSet = {
|
||||
columns: ['id', 'name'],
|
||||
values: {
|
||||
id: [1, 2],
|
||||
name: ['foo', 'bar']
|
||||
}
|
||||
}
|
||||
expect(csv.serialize(resultSet)).to.eql('id\tname\r\n1\tfoo\r\n2\tbar')
|
||||
})
|
||||
})
|
||||
@@ -35,9 +35,12 @@ describe('_sql.js', () => {
|
||||
const result = sql.exec('SELECT * from test')
|
||||
expect(result).to.have.lengthOf(1)
|
||||
expect(result[0]).to.eql({
|
||||
columns: ['id', 'name', 'faculty'],
|
||||
values: {
|
||||
id: [1, 2],
|
||||
name: ['Harry Potter', 'Draco Malfoy'],
|
||||
faculty: ['Griffindor', 'Slytherin']
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -64,6 +67,8 @@ describe('_sql.js', () => {
|
||||
|
||||
it('imports', async () => {
|
||||
const data = {
|
||||
columns: ['id', 'name'],
|
||||
values: {
|
||||
id: [1, 2, 3, 4],
|
||||
name: [
|
||||
'Harry Potter',
|
||||
@@ -72,6 +77,7 @@ describe('_sql.js', () => {
|
||||
'Ron Weasley'
|
||||
]
|
||||
}
|
||||
}
|
||||
const progressCallback = sinon.stub()
|
||||
const progressCounterId = 1
|
||||
const sql = await Sql.build()
|
||||
@@ -104,7 +110,7 @@ describe('_sql.js', () => {
|
||||
anotherSql.open(data)
|
||||
const result = anotherSql.exec('SELECT * from test')
|
||||
expect(result).to.have.lengthOf(1)
|
||||
expect(result[0]).to.eql({
|
||||
expect(result[0].values).to.eql({
|
||||
id: [1, 2],
|
||||
name: ['Harry Potter', 'Draco Malfoy'],
|
||||
faculty: ['Griffindor', 'Slytherin']
|
||||
@@ -146,12 +152,14 @@ describe('_sql.js', () => {
|
||||
`)
|
||||
|
||||
let result = sql.exec('SELECT * from test')
|
||||
expect(result[0]).to.eql({
|
||||
expect(result[0].values).to.eql({
|
||||
id: [1, 2],
|
||||
name: ['foo', 'bar']
|
||||
})
|
||||
|
||||
const data = {
|
||||
columns: ['id', 'name'],
|
||||
values: {
|
||||
id: [1, 2, 3, 4],
|
||||
name: [
|
||||
'Harry Potter',
|
||||
@@ -160,12 +168,13 @@ describe('_sql.js', () => {
|
||||
'Ron Weasley'
|
||||
]
|
||||
}
|
||||
}
|
||||
// import adds table
|
||||
sql.import('foo', data, 1, sinon.stub(), 2)
|
||||
result = sql.exec('SELECT * from foo')
|
||||
expect(result[0]).to.eql(data)
|
||||
result = sql.exec('SELECT * from test')
|
||||
expect(result[0]).to.eql({
|
||||
expect(result[0].values).to.eql({
|
||||
id: [1, 2],
|
||||
name: ['foo', 'bar']
|
||||
})
|
||||
|
||||
@@ -35,32 +35,4 @@ describe('_statements.js', () => {
|
||||
'CREATE table "foo"("id" REAL, "name" TEXT, "isAdmin" INTEGER, "startDate" TEXT);'
|
||||
)
|
||||
})
|
||||
|
||||
it('getColumns', () => {
|
||||
const sql = `CREATE TABLE test (
|
||||
col1,
|
||||
col2 integer,
|
||||
col3 decimal(5,2),
|
||||
col4 varchar(30)
|
||||
)`
|
||||
expect(stmts.getColumns(sql)).to.eql([
|
||||
{ name: 'col1', type: 'N/A' },
|
||||
{ name: 'col2', type: 'integer' },
|
||||
{ name: 'col3', type: 'decimal(5, 2)' },
|
||||
{ name: 'col4', type: 'varchar(30)' }
|
||||
])
|
||||
})
|
||||
|
||||
it('getColumns with virtual table', async () => {
|
||||
const sql = `
|
||||
CREATE VIRTUAL TABLE test_virtual USING fts4(
|
||||
col1, col2,
|
||||
notindexed=col1, notindexed=col2,
|
||||
tokenize=unicode61 "tokenchars=.+#")
|
||||
`
|
||||
expect(stmts.getColumns(sql)).to.eql([
|
||||
{ name: 'col1', type: 'N/A' },
|
||||
{ name: 'col2', type: 'N/A' }
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -47,6 +47,36 @@ describe('database.js', () => {
|
||||
expect(schema[0].columns[1].type).to.equal('integer')
|
||||
})
|
||||
|
||||
it('creates schema with view', async () => {
|
||||
await db.loadDb()
|
||||
await db.execute(`
|
||||
CREATE TABLE test (col1, col2 integer);
|
||||
CREATE VIEW test_view AS SELECT col2 as amount FROM test;
|
||||
`)
|
||||
|
||||
await db.refreshSchema()
|
||||
const schema = db.schema
|
||||
expect(db.dbName).to.equal('database')
|
||||
expect(schema).to.have.lengthOf(2)
|
||||
expect(schema[0].name).to.equal('test')
|
||||
expect(schema[1].name).to.equal('test_view')
|
||||
|
||||
expect(schema[0].columns[0]).to.eql({
|
||||
name: 'col1',
|
||||
type: 'N/A'
|
||||
})
|
||||
|
||||
expect(schema[0].columns[1]).to.eql({
|
||||
name: 'col2',
|
||||
type: 'integer'
|
||||
})
|
||||
|
||||
expect(schema[1].columns).to.eql([{
|
||||
name: 'amount',
|
||||
type: 'integer'
|
||||
}])
|
||||
})
|
||||
|
||||
it('creates empty db with name database', async () => {
|
||||
sinon.spy(db, 'refreshSchema')
|
||||
|
||||
@@ -85,7 +115,7 @@ describe('database.js', () => {
|
||||
|
||||
await db.loadDb(buffer)
|
||||
const result = await db.execute('SELECT * from test limit 1; SELECT * from test;')
|
||||
expect(result).to.eql({
|
||||
expect(result.values).to.eql({
|
||||
id: [1, 2],
|
||||
name: ['Harry Potter', 'Draco Malfoy'],
|
||||
faculty: ['Griffindor', 'Slytherin']
|
||||
@@ -116,10 +146,13 @@ describe('database.js', () => {
|
||||
|
||||
it('adds table from csv', async () => {
|
||||
const data = {
|
||||
columns: ['id', 'name', 'faculty'],
|
||||
values: {
|
||||
id: [1, 2],
|
||||
name: ['Harry Potter', 'Draco Malfoy'],
|
||||
faculty: ['Griffindor', 'Slytherin']
|
||||
}
|
||||
}
|
||||
const progressHandler = sinon.spy()
|
||||
const progressCounterId = db.createProgressCounter(progressHandler)
|
||||
sinon.spy(db, 'refreshSchema')
|
||||
@@ -130,9 +163,9 @@ describe('database.js', () => {
|
||||
expect(db.schema).to.have.lengthOf(1)
|
||||
expect(db.schema[0].name).to.equal('foo')
|
||||
expect(db.schema[0].columns).to.have.lengthOf(3)
|
||||
expect(db.schema[0].columns[0]).to.eql({ name: 'id', type: 'real' })
|
||||
expect(db.schema[0].columns[1]).to.eql({ name: 'name', type: 'text' })
|
||||
expect(db.schema[0].columns[2]).to.eql({ name: 'faculty', type: 'text' })
|
||||
expect(db.schema[0].columns[0]).to.eql({ name: 'id', type: 'REAL' })
|
||||
expect(db.schema[0].columns[1]).to.eql({ name: 'name', type: 'TEXT' })
|
||||
expect(db.schema[0].columns[2]).to.eql({ name: 'faculty', type: 'TEXT' })
|
||||
|
||||
const result = await db.execute('SELECT * from foo')
|
||||
expect(result).to.eql(data)
|
||||
@@ -144,10 +177,13 @@ describe('database.js', () => {
|
||||
|
||||
it('addTableFromCsv throws errors', async () => {
|
||||
const data = {
|
||||
columns: [],
|
||||
values: {
|
||||
id: [1, 2],
|
||||
name: ['Harry Potter', 'Draco Malfoy'],
|
||||
faculty: null
|
||||
}
|
||||
}
|
||||
const progressHandler = sinon.stub()
|
||||
const progressCounterId = db.createProgressCounter(progressHandler)
|
||||
await expect(db.addTableFromCsv('foo', data, progressCounterId)).to.be.rejected
|
||||
@@ -214,8 +250,11 @@ describe('database.js', () => {
|
||||
// check that new db works and has the same table and data
|
||||
result = await anotherDb.execute('SELECT * from foo')
|
||||
expect(result).to.eql({
|
||||
columns: ['id', 'name'],
|
||||
values: {
|
||||
id: [1],
|
||||
name: ['Harry Potter']
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ describe('SQLite extensions', function () {
|
||||
abs(pi() / 2 - atan2(1, 0)) < 0.000001
|
||||
`)
|
||||
|
||||
expect(actual).to.eql({
|
||||
expect(actual.values).to.eql({
|
||||
'abs(3.1415926 - pi()) < 0.000001': [1],
|
||||
'abs(1 - cos(2 * pi())) < 0.000001': [1],
|
||||
'abs(0 - sin(pi())) < 0.000001': [1],
|
||||
@@ -71,7 +71,7 @@ describe('SQLite extensions', function () {
|
||||
ceil(-1.95) + ceil(1.95),
|
||||
floor(-1.95) + floor(1.95)
|
||||
`)
|
||||
expect(actual).to.eql({
|
||||
expect(actual.values).to.eql({
|
||||
'exp(0)': [1],
|
||||
'log(exp(1))': [1],
|
||||
'log10(10000)': [4],
|
||||
@@ -99,7 +99,7 @@ describe('SQLite extensions', function () {
|
||||
padc('foo', 5),
|
||||
strfilter('abcba', 'bc')
|
||||
`)
|
||||
expect(actual).to.eql({
|
||||
expect(actual.values).to.eql({
|
||||
"replicate('ab', 4)": ['abababab'],
|
||||
"charindex('ab', 'foobarabbarfoo')": [7],
|
||||
"charindex('ab', 'foobarabbarfoo', 8)": [0],
|
||||
@@ -137,7 +137,7 @@ describe('SQLite extensions', function () {
|
||||
VALUES (1)
|
||||
)
|
||||
`)
|
||||
expect(actual).to.eql({
|
||||
expect(actual.values).to.eql({
|
||||
'abs( 3.77406806 - stdev(x)) < 0.000001': [1],
|
||||
'abs(14.24358974 - variance(x)) < 0.000001': [1],
|
||||
'mode(x)': [1],
|
||||
@@ -152,7 +152,7 @@ describe('SQLite extensions', function () {
|
||||
SELECT value
|
||||
FROM generate_series(5, 20, 5)
|
||||
`)
|
||||
expect(actual).to.eql({
|
||||
expect(actual.values).to.eql({
|
||||
value: [5, 10, 15, 20]
|
||||
})
|
||||
})
|
||||
@@ -194,7 +194,7 @@ describe('SQLite extensions', function () {
|
||||
WHERE nc.root = 2 AND nc.depth = 2
|
||||
);
|
||||
`)
|
||||
expect(actual).to.eql({
|
||||
expect(actual.values).to.eql({
|
||||
name: [
|
||||
'_sql.spec.js',
|
||||
'_statements.spec.js',
|
||||
@@ -212,7 +212,7 @@ describe('SQLite extensions', function () {
|
||||
length(uuid()) as length,
|
||||
uuid_str(uuid_blob('26a8349c8a7f4cbeb519bf792c3d7ac6')) as uid
|
||||
`)
|
||||
expect(actual).to.eql({
|
||||
expect(actual.values).to.eql({
|
||||
length: [36],
|
||||
uid: ['26a8349c-8a7f-4cbe-b519-bf792c3d7ac6']
|
||||
})
|
||||
@@ -225,7 +225,7 @@ describe('SQLite extensions', function () {
|
||||
regexpi('=\\s?\\d+', 'const foo = 123; const bar = "bar"') as two,
|
||||
'const foo = 123; const bar = "bar"' REGEXP '=\\s?\\d+' as three
|
||||
`)
|
||||
expect(actual).to.eql({
|
||||
expect(actual.values).to.eql({
|
||||
one: [1],
|
||||
two: [1],
|
||||
three: [1]
|
||||
@@ -260,7 +260,7 @@ describe('SQLite extensions', function () {
|
||||
ALTER TABLE surface DROP COLUMN rownum;
|
||||
SELECT * FROM surface;
|
||||
`)
|
||||
expect(actual).to.eql({
|
||||
expect(actual.values).to.eql({
|
||||
x: [5, 10, 15],
|
||||
y: [3, 6, 9],
|
||||
'5.0': [3.2, 4.3, 5.4],
|
||||
@@ -269,6 +269,48 @@ describe('SQLite extensions', function () {
|
||||
})
|
||||
})
|
||||
|
||||
it('supports percentile', async function () {
|
||||
const actual = await db.execute(`
|
||||
CREATE TABLE s(x INTEGER);
|
||||
INSERT INTO s VALUES (15), (20), (35), (40), (50);
|
||||
|
||||
SELECT
|
||||
percentile(x, 5) p5,
|
||||
percentile(x, 30) p30,
|
||||
percentile(x, 40) p40,
|
||||
percentile(x, 50) p50,
|
||||
percentile(x, 100) p100
|
||||
FROM s;
|
||||
`)
|
||||
expect(actual.values).to.eql({
|
||||
p5: [16],
|
||||
p30: [23],
|
||||
p40: [29],
|
||||
p50: [35],
|
||||
p100: [50]
|
||||
})
|
||||
})
|
||||
|
||||
it('supports decimal', async function () {
|
||||
const actual = await db.execute(`
|
||||
select
|
||||
decimal_add(decimal('0.1'), decimal('0.2')) "add",
|
||||
decimal_sub(0.2, 0.1) sub,
|
||||
decimal_mul(power(2, 69), 2) mul,
|
||||
decimal_cmp(decimal('0.1'), 0.1) cmp_e,
|
||||
decimal_cmp(decimal('0.1'), decimal('0.099999')) cmp_g,
|
||||
decimal_cmp(decimal('0.199999'), decimal('0.2')) cmp_l
|
||||
`)
|
||||
expect(actual.values).to.eql({
|
||||
add: ['0.3'],
|
||||
sub: ['0.1'],
|
||||
mul: ['1180591620717412000000'],
|
||||
cmp_e: [0],
|
||||
cmp_g: [1],
|
||||
cmp_l: [-1]
|
||||
})
|
||||
})
|
||||
|
||||
it('supports FTS5', async function () {
|
||||
const actual = await db.execute(`
|
||||
CREATE VIRTUAL TABLE email USING fts5(sender, title, body, tokenize = 'porter ascii');
|
||||
@@ -292,8 +334,100 @@ describe('SQLite extensions', function () {
|
||||
WHERE body MATCH '"full-text" NOT document'
|
||||
ORDER BY rank;
|
||||
`)
|
||||
expect(actual).to.eql({
|
||||
expect(actual.values).to.eql({
|
||||
sender: ['bar@localhost']
|
||||
})
|
||||
})
|
||||
|
||||
it('supports FTS3', async function () {
|
||||
const actual = await db.execute(`
|
||||
CREATE VIRTUAL TABLE email USING fts3(sender, title, body, tokenize = 'porter');
|
||||
|
||||
INSERT INTO email VALUES
|
||||
(
|
||||
'foo@localhost',
|
||||
'fts3/4',
|
||||
'FTS3 and FTS4 are SQLite virtual table modules that allows users to perform '
|
||||
|| 'full-text searches on a set of documents.'
|
||||
),
|
||||
(
|
||||
'bar@localhost',
|
||||
'fts4',
|
||||
'FTS5 is an SQLite virtual table module that provides full-text search '
|
||||
|| 'functionality to database applications.'
|
||||
);
|
||||
|
||||
SELECT sender
|
||||
FROM email
|
||||
WHERE body MATCH '("full-text" NOT document AND (functionality OR table))';
|
||||
`)
|
||||
expect(actual.values).to.eql({
|
||||
sender: ['bar@localhost']
|
||||
})
|
||||
})
|
||||
|
||||
it('supports FTS4', async function () {
|
||||
const actual = await db.execute(`
|
||||
CREATE VIRTUAL TABLE email USING fts4(
|
||||
sender, title, body, notindexed=sender, tokenize='simple'
|
||||
);
|
||||
|
||||
INSERT INTO email VALUES
|
||||
(
|
||||
'foo@localhost',
|
||||
'fts3/4',
|
||||
'FTS3 and FTS4 are SQLite virtual table modules that allows users to perform '
|
||||
|| 'full-text searches on a set of documents.'
|
||||
),
|
||||
(
|
||||
'bar@localhost',
|
||||
'fts4',
|
||||
'FTS5 is an SQLite virtual table module that provides full-text search '
|
||||
|| 'functionality to database applications.'
|
||||
);
|
||||
|
||||
SELECT sender
|
||||
FROM email
|
||||
WHERE body MATCH '("full-text" NOT document AND (functionality OR table NOT modules))';
|
||||
`)
|
||||
expect(actual.values).to.eql({
|
||||
sender: ['bar@localhost']
|
||||
})
|
||||
})
|
||||
|
||||
it('supports JSON1', async function () {
|
||||
const actual = await db.execute(`
|
||||
WITH input(filename) AS (
|
||||
VALUES
|
||||
('/etc/redis/redis.conf'),
|
||||
('/run/redis/redis-server.pid'),
|
||||
('/var/log/redis-server.log')
|
||||
), tmp AS (
|
||||
SELECT
|
||||
filename,
|
||||
'["' || replace(filename, '/', '", "') || '"]' as filename_array
|
||||
FROM input
|
||||
)
|
||||
SELECT (
|
||||
SELECT group_concat(ip.value, '/')
|
||||
FROM json_each(filename_array) ip
|
||||
WHERE ip.id <= p.id
|
||||
) AS path
|
||||
FROM tmp, json_each(filename_array) AS p
|
||||
WHERE p.id > 1 -- because the filenames start with the separator
|
||||
`)
|
||||
expect(actual.values).to.eql({
|
||||
path: [
|
||||
'/etc',
|
||||
'/etc/redis',
|
||||
'/etc/redis/redis.conf',
|
||||
'/run',
|
||||
'/run/redis',
|
||||
'/run/redis/redis-server.pid',
|
||||
'/var',
|
||||
'/var/log',
|
||||
'/var/log/redis-server.log'
|
||||
]
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
30
tests/lib/utils/clipboardIo.spec.js
Normal file
30
tests/lib/utils/clipboardIo.spec.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { expect } from 'chai'
|
||||
import cIo from '@/lib/utils/clipboardIo'
|
||||
import sinon from 'sinon'
|
||||
|
||||
describe('clipboardIo.js', async () => {
|
||||
afterEach(() => {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
it('copyCsv', async () => {
|
||||
sinon.stub(navigator.clipboard, 'writeText').resolves(true)
|
||||
await cIo.copyCsv('id\tname\r\n1\t2')
|
||||
expect(navigator.clipboard.writeText.calledOnceWith('id\tname\r\n1\t2'))
|
||||
})
|
||||
|
||||
it('copyImage for canvas calls _copyCanvas', async () => {
|
||||
sinon.stub(cIo, '_copyCanvas').resolves(true)
|
||||
const canvas = document.createElement('canvas')
|
||||
|
||||
await cIo.copyImage(canvas)
|
||||
expect(cIo._copyCanvas.calledOnceWith(canvas))
|
||||
})
|
||||
|
||||
it('copyImage for dataUrl calls _copyFromDataUrl', async () => {
|
||||
sinon.stub(cIo, '_copyFromDataUrl').resolves(true)
|
||||
const url = document.createElement('canvas').toDataURL()
|
||||
await cIo.copyImage(url)
|
||||
expect(cIo._copyFromDataUrl.calledOnceWith(url))
|
||||
})
|
||||
})
|
||||
@@ -27,4 +27,14 @@ describe('time.js', () => {
|
||||
|
||||
expect(time.getPeriod(start, end)).to.equal('0.045s')
|
||||
})
|
||||
|
||||
it('sleep resolves after n ms', async () => {
|
||||
let before = performance.now()
|
||||
await time.sleep(10)
|
||||
expect(performance.now() - before).to.be.least(10)
|
||||
|
||||
before = performance.now()
|
||||
await time.sleep(30)
|
||||
expect(performance.now() - before).to.be.least(30)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ describe('Inquiries.vue', () => {
|
||||
expect(wrapper.find('#start-guide').exists()).to.equal(true)
|
||||
})
|
||||
|
||||
it('Renders the list on saved or predefined inquiries', async () => {
|
||||
it('Renders the list of saved and predefined inquiries', async () => {
|
||||
sinon.stub(storedInquiries, 'readPredefinedInquiries').resolves([
|
||||
{
|
||||
id: 0,
|
||||
@@ -125,6 +125,49 @@ describe('Inquiries.vue', () => {
|
||||
expect(rows.at(0).findAll('td').at(1).text()).to.contains('3 November 2020 20:57')
|
||||
})
|
||||
|
||||
it('Shows No found message when filter returns nothing', async () => {
|
||||
sinon.stub(storedInquiries, 'readPredefinedInquiries').resolves([
|
||||
{
|
||||
id: 0,
|
||||
name: 'hello_world',
|
||||
query: '',
|
||||
viewType: 'chart',
|
||||
viewOptions: [],
|
||||
createdAt: '2020-03-08T19:57:56.299Z'
|
||||
}
|
||||
])
|
||||
sinon.stub(storedInquiries, 'getStoredInquiries').returns([
|
||||
{
|
||||
id: 1,
|
||||
name: 'foo',
|
||||
query: '',
|
||||
viewType: 'chart',
|
||||
viewOptions: [],
|
||||
createdAt: '2020-11-03T19:57:56.299Z'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'bar',
|
||||
query: '',
|
||||
viewType: 'chart',
|
||||
viewOptions: [],
|
||||
createdAt: '2020-12-04T18:53:56.299Z'
|
||||
}
|
||||
])
|
||||
const state = {
|
||||
predefinedInquiries: []
|
||||
}
|
||||
|
||||
const store = new Vuex.Store({ state, mutations })
|
||||
const wrapper = mount(Inquiries, { store })
|
||||
await wrapper.find('#toolbar-search input').setValue('baz')
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(wrapper.find('#inquiries-not-found').text()).to.equal('No inquiries found')
|
||||
expect(wrapper.find('#start-guide').exists()).to.equal(false)
|
||||
expect(wrapper.find('tbody').isVisible()).to.equal(false)
|
||||
})
|
||||
|
||||
it('Predefined inquiry has a badge', async () => {
|
||||
sinon.stub(storedInquiries, 'readPredefinedInquiries').resolves([
|
||||
{
|
||||
@@ -231,7 +274,7 @@ describe('Inquiries.vue', () => {
|
||||
).to.equals(true)
|
||||
})
|
||||
|
||||
it('Makes the copy of the inquiry selected if all inquiries were selected before duplication',
|
||||
it('The copy of the inquiry is not selected if all inquiries were selected before duplication',
|
||||
async () => {
|
||||
sinon.stub(storedInquiries, 'readPredefinedInquiries').resolves([])
|
||||
const inquiryInStorage = {
|
||||
@@ -262,12 +305,13 @@ describe('Inquiries.vue', () => {
|
||||
await storedInquiries.readPredefinedInquiries.returnValues[0]
|
||||
await storedInquiries.getStoredInquiries.returnValues[0]
|
||||
await wrapper.vm.$nextTick()
|
||||
await wrapper.findComponent({ ref: 'mainCheckBox' }).find('.checkbox-container').trigger('click')
|
||||
await wrapper.findComponent({ ref: 'mainCheckBox' }).find('.checkbox-container')
|
||||
.trigger('click')
|
||||
await wrapper.findComponent({ name: 'CopyIcon' }).find('svg').trigger('click')
|
||||
|
||||
const checkboxes = wrapper.findAllComponents({ ref: 'rowCheckBox' })
|
||||
expect(checkboxes.at(0).vm.checked).to.equals(true)
|
||||
expect(checkboxes.at(1).vm.checked).to.equals(true)
|
||||
expect(checkboxes.at(1).vm.checked).to.equals(false)
|
||||
})
|
||||
|
||||
it('Opens an inquiry', async () => {
|
||||
@@ -478,7 +522,7 @@ describe('Inquiries.vue', () => {
|
||||
)).to.equals(true)
|
||||
})
|
||||
|
||||
it('Imported inquiries are selected if master check box is checked', async () => {
|
||||
it('Imported inquiries are not selected if master check box was checked', async () => {
|
||||
sinon.stub(storedInquiries, 'readPredefinedInquiries').resolves([])
|
||||
const inquiryInStorage = {
|
||||
id: 1,
|
||||
@@ -510,14 +554,16 @@ describe('Inquiries.vue', () => {
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
// click on master checkbox
|
||||
await wrapper.findComponent({ ref: 'mainCheckBox' }).find('.checkbox-container').trigger('click')
|
||||
await wrapper.findComponent({ ref: 'mainCheckBox' }).find('.checkbox-container')
|
||||
.trigger('click')
|
||||
|
||||
// click Import
|
||||
await wrapper.find('#toolbar-btns-import').trigger('click')
|
||||
|
||||
const checkboxes = wrapper.findAllComponents({ ref: 'rowCheckBox' })
|
||||
expect(wrapper.findComponent({ ref: 'mainCheckBox' }).vm.checked).to.equals(false)
|
||||
expect(checkboxes.at(0).vm.checked).to.equals(true)
|
||||
expect(checkboxes.at(1).vm.checked).to.equals(true)
|
||||
expect(checkboxes.at(1).vm.checked).to.equals(false)
|
||||
})
|
||||
|
||||
it('Deletion is not available for predefined inquiries', async () => {
|
||||
@@ -1045,4 +1091,93 @@ describe('Inquiries.vue', () => {
|
||||
expect(checkboxes.at(0).vm.checked).to.equals(false)
|
||||
expect(checkboxes.at(0).vm.checked).to.equals(false)
|
||||
})
|
||||
|
||||
it('Selection and filter', async () => {
|
||||
sinon.stub(storedInquiries, 'readPredefinedInquiries').resolves([
|
||||
{
|
||||
id: 0,
|
||||
name: 'hello_world',
|
||||
query: '',
|
||||
viewType: 'chart',
|
||||
viewOptions: [],
|
||||
createdAt: '2020-03-08T19:57:56.299Z'
|
||||
}
|
||||
])
|
||||
const foo = {
|
||||
id: 1,
|
||||
name: 'foo',
|
||||
query: '',
|
||||
viewType: 'chart',
|
||||
viewOptions: [],
|
||||
createdAt: '2020-03-08T19:57:56.299Z'
|
||||
}
|
||||
const bar = {
|
||||
id: 2,
|
||||
name: 'bar',
|
||||
query: '',
|
||||
viewType: 'chart',
|
||||
viewOptions: [],
|
||||
createdAt: '2020-03-08T19:57:56.299Z'
|
||||
}
|
||||
sinon.stub(storedInquiries, 'getStoredInquiries').returns([foo, bar])
|
||||
|
||||
const state = {
|
||||
predefinedInquiries: []
|
||||
}
|
||||
const store = new Vuex.Store({ state, mutations })
|
||||
|
||||
const wrapper = mount(Inquiries, { store })
|
||||
await storedInquiries.readPredefinedInquiries.returnValues[0]
|
||||
await storedInquiries.getStoredInquiries.returnValues[0]
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
const mainCheckBox = wrapper.findComponent({ ref: 'mainCheckBox' })
|
||||
// Select all with main checkbox
|
||||
await mainCheckBox.find('.checkbox-container').trigger('click')
|
||||
expect([...wrapper.vm.selectedInquiriesIds]).to.eql([0, 1, 2])
|
||||
expect(wrapper.vm.selectedNotPredefinedCount).to.eql(2)
|
||||
let checkboxes = wrapper.findAllComponents({ ref: 'rowCheckBox' })
|
||||
expect(checkboxes.at(0).vm.checked).to.equals(true)
|
||||
expect(checkboxes.at(1).vm.checked).to.equals(true)
|
||||
expect(checkboxes.at(2).vm.checked).to.equals(true)
|
||||
|
||||
// Filter
|
||||
await wrapper.find('#toolbar-search input').setValue('foo')
|
||||
await wrapper.vm.$nextTick()
|
||||
expect([...wrapper.vm.selectedInquiriesIds]).to.eql([1])
|
||||
expect(wrapper.vm.selectedNotPredefinedCount).to.eql(1)
|
||||
checkboxes = wrapper.findAllComponents({ ref: 'rowCheckBox' })
|
||||
expect(checkboxes.at(0).vm.checked).to.equals(true)
|
||||
|
||||
// Clear filter
|
||||
await wrapper.find('#toolbar-search input').setValue('')
|
||||
await wrapper.vm.$nextTick()
|
||||
expect([...wrapper.vm.selectedInquiriesIds]).to.eql([1])
|
||||
expect(wrapper.vm.selectedNotPredefinedCount).to.eql(1)
|
||||
checkboxes = wrapper.findAll('tr .checkbox-container')
|
||||
expect(checkboxes.at(0).classes()).to.not.include('checked')
|
||||
expect(checkboxes.at(1).classes()).to.include('checked')
|
||||
expect(checkboxes.at(2).classes()).to.not.include('checked')
|
||||
|
||||
// Select also first inquiry
|
||||
wrapper.find('tbody tr .checkbox-container').trigger('click')
|
||||
expect([...wrapper.vm.selectedInquiriesIds]).to.eql([1, 0])
|
||||
expect(wrapper.vm.selectedNotPredefinedCount).to.eql(1)
|
||||
|
||||
// Filter
|
||||
await wrapper.find('#toolbar-search input').setValue('hello')
|
||||
await wrapper.vm.$nextTick()
|
||||
expect([...wrapper.vm.selectedInquiriesIds]).to.eql([0])
|
||||
expect(wrapper.vm.selectedNotPredefinedCount).to.eql(0)
|
||||
checkboxes = wrapper.findAllComponents({ ref: 'rowCheckBox' })
|
||||
expect(checkboxes.at(0).vm.checked).to.equals(true)
|
||||
|
||||
// Select all with main checkbox
|
||||
await mainCheckBox.find('.checkbox-container').trigger('click')
|
||||
|
||||
// Clear filter - main checkbox bocomes not checked
|
||||
await wrapper.find('#toolbar-search input').setValue('')
|
||||
await wrapper.vm.$nextTick()
|
||||
expect(mainCheckBox.vm.checked).to.equals(false)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,11 +2,13 @@ import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import { mount, createLocalVue } from '@vue/test-utils'
|
||||
import Vuex from 'vuex'
|
||||
import actions from '@/store/actions'
|
||||
import mutations from '@/store/mutations'
|
||||
import Schema from '@/views/Main/Workspace/Schema'
|
||||
import TableDescription from '@/views/Main/Workspace/Schema/TableDescription'
|
||||
import database from '@/lib/database'
|
||||
import fIo from '@/lib/utils/fileIo'
|
||||
import csv from '@/components/CsvImport/csv'
|
||||
import csv from '@/lib/csv'
|
||||
|
||||
const localVue = createLocalVue()
|
||||
localVue.use(Vuex)
|
||||
@@ -129,22 +131,26 @@ describe('Schema.vue', () => {
|
||||
sinon.stub(csv, 'parse').resolves({
|
||||
delimiter: '|',
|
||||
data: {
|
||||
columns: ['col1', 'col2'],
|
||||
values: {
|
||||
col1: [1],
|
||||
col2: ['foo']
|
||||
}
|
||||
},
|
||||
hasErrors: false,
|
||||
messages: []
|
||||
})
|
||||
|
||||
const state = {
|
||||
db: database.getNewDatabase()
|
||||
db: database.getNewDatabase(),
|
||||
tabs: []
|
||||
}
|
||||
state.db.dbName = 'db'
|
||||
state.db.execute('CREATE TABLE foo(id)')
|
||||
state.db.refreshSchema()
|
||||
sinon.spy(state.db, 'refreshSchema')
|
||||
|
||||
const store = new Vuex.Store({ state })
|
||||
const store = new Vuex.Store({ state, actions, mutations })
|
||||
const wrapper = mount(Schema, { store, localVue })
|
||||
sinon.spy(wrapper.vm.$refs.addCsv, 'previewCsv')
|
||||
sinon.spy(wrapper.vm, 'addCsv')
|
||||
@@ -163,12 +169,12 @@ describe('Schema.vue', () => {
|
||||
await state.db.refreshSchema.returnValues[0]
|
||||
|
||||
expect(wrapper.vm.$store.state.db.schema).to.eql([
|
||||
{ name: 'test', columns: [{ name: 'col1', type: 'real' }, { name: 'col2', type: 'text' }] },
|
||||
{ name: 'foo', columns: [{ name: 'id', type: 'N/A' }] }
|
||||
{ name: 'foo', columns: [{ name: 'id', type: 'N/A' }] },
|
||||
{ name: 'test', columns: [{ name: 'col1', type: 'REAL' }, { name: 'col2', type: 'TEXT' }] }
|
||||
])
|
||||
|
||||
const res = await wrapper.vm.$store.state.db.execute('select * from test')
|
||||
expect(res).to.eql({
|
||||
expect(res.values).to.eql({
|
||||
col1: [1],
|
||||
col2: ['foo']
|
||||
})
|
||||
|
||||
@@ -2,8 +2,9 @@ import { expect } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import { mount, shallowMount } from '@vue/test-utils'
|
||||
import Chart from '@/views/Main/Workspace/Tabs/Tab/DataView/Chart'
|
||||
import chartHelper from '@/views/Main/Workspace/Tabs/Tab/DataView/Chart/chartHelper'
|
||||
import chartHelper from '@/lib/chartHelper'
|
||||
import * as dereference from 'react-chart-editor/lib/lib/dereference'
|
||||
import fIo from '@/lib/utils/fileIo'
|
||||
|
||||
describe('Chart.vue', () => {
|
||||
afterEach(() => {
|
||||
@@ -27,7 +28,7 @@ describe('Chart.vue', () => {
|
||||
expect(wrapper.emitted('update')).to.have.lengthOf(1)
|
||||
})
|
||||
|
||||
it('calls dereference when sqlResult is changed', async () => {
|
||||
it('calls dereference when dataSources is changed', async () => {
|
||||
sinon.stub(dereference, 'default')
|
||||
const dataSources = {
|
||||
id: [1],
|
||||
@@ -47,4 +48,40 @@ describe('Chart.vue', () => {
|
||||
await wrapper.setProps({ dataSources: newDataSources })
|
||||
expect(dereference.default.called).to.equal(true)
|
||||
})
|
||||
|
||||
it("doesn't calls dereference when dataSources is null", async () => {
|
||||
sinon.stub(dereference, 'default')
|
||||
const dataSources = {
|
||||
id: [1],
|
||||
name: ['foo']
|
||||
}
|
||||
|
||||
// mount the component
|
||||
const wrapper = shallowMount(Chart, {
|
||||
propsData: { dataSources }
|
||||
})
|
||||
|
||||
await wrapper.setProps({ dataSources: null })
|
||||
expect(dereference.default.called).to.equal(false)
|
||||
})
|
||||
|
||||
it('saveAsPng', async () => {
|
||||
sinon.spy(fIo, 'downloadFromUrl')
|
||||
const dataSources = {
|
||||
id: [1],
|
||||
name: ['foo']
|
||||
}
|
||||
|
||||
const wrapper = mount(Chart, {
|
||||
propsData: { dataSources }
|
||||
})
|
||||
sinon.spy(wrapper.vm, 'prepareCopy')
|
||||
|
||||
await wrapper.vm.$nextTick() // chart is rendered
|
||||
await wrapper.vm.saveAsPng()
|
||||
|
||||
const url = await wrapper.vm.prepareCopy.returnValues[0]
|
||||
expect(wrapper.emitted().loadingImageCompleted.length).to.equal(1)
|
||||
expect(fIo.downloadFromUrl.calledOnceWith(url, 'chart'))
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
import { expect } from 'chai'
|
||||
import { mount, createWrapper } from '@vue/test-utils'
|
||||
import DataView from '@/views/Main/Workspace/Tabs/Tab/DataView'
|
||||
import sinon from 'sinon'
|
||||
|
||||
describe('DataView.vue', () => {
|
||||
it('emits update on mode changing', async () => {
|
||||
const wrapper = mount(DataView)
|
||||
|
||||
const pivotBtn = createWrapper(wrapper.findComponent({ name: 'pivotIcon' }).vm.$parent)
|
||||
await pivotBtn.trigger('click')
|
||||
|
||||
expect(wrapper.emitted('update')).to.have.lengthOf(1)
|
||||
})
|
||||
|
||||
it('method getOptionsForSave call the same method of the current view component', async () => {
|
||||
const wrapper = mount(DataView)
|
||||
|
||||
const chart = wrapper.findComponent({ name: 'Chart' }).vm
|
||||
sinon.stub(chart, 'getOptionsForSave').returns({ here_are: 'chart_settings' })
|
||||
|
||||
expect(wrapper.vm.getOptionsForSave()).to.eql({ here_are: 'chart_settings' })
|
||||
|
||||
const pivotBtn = createWrapper(wrapper.findComponent({ name: 'pivotIcon' }).vm.$parent)
|
||||
await pivotBtn.trigger('click')
|
||||
|
||||
const pivot = wrapper.findComponent({ name: 'pivot' }).vm
|
||||
sinon.stub(pivot, 'getOptionsForSave').returns({ here_are: 'pivot_settings' })
|
||||
|
||||
expect(wrapper.vm.getOptionsForSave()).to.eql({ here_are: 'pivot_settings' })
|
||||
})
|
||||
})
|
||||
173
tests/views/Main/Workspace/Tabs/Tab/DataView/DataView.spec.js
Normal file
173
tests/views/Main/Workspace/Tabs/Tab/DataView/DataView.spec.js
Normal file
@@ -0,0 +1,173 @@
|
||||
import { expect } from 'chai'
|
||||
import { mount, createWrapper } from '@vue/test-utils'
|
||||
import DataView from '@/views/Main/Workspace/Tabs/Tab/DataView'
|
||||
import sinon from 'sinon'
|
||||
|
||||
describe('DataView.vue', () => {
|
||||
afterEach(() => {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
it('emits update on mode changing', async () => {
|
||||
const wrapper = mount(DataView)
|
||||
|
||||
const pivotBtn = createWrapper(wrapper.findComponent({ name: 'pivotIcon' }).vm.$parent)
|
||||
await pivotBtn.trigger('click')
|
||||
|
||||
expect(wrapper.emitted('update')).to.have.lengthOf(1)
|
||||
})
|
||||
|
||||
it('method getOptionsForSave calls the same method of the current view component', async () => {
|
||||
const wrapper = mount(DataView)
|
||||
|
||||
const chart = wrapper.findComponent({ name: 'Chart' }).vm
|
||||
sinon.stub(chart, 'getOptionsForSave').returns({ here_are: 'chart_settings' })
|
||||
|
||||
expect(wrapper.vm.getOptionsForSave()).to.eql({ here_are: 'chart_settings' })
|
||||
|
||||
const pivotBtn = createWrapper(wrapper.findComponent({ name: 'pivotIcon' }).vm.$parent)
|
||||
await pivotBtn.trigger('click')
|
||||
|
||||
const pivot = wrapper.findComponent({ name: 'pivot' }).vm
|
||||
sinon.stub(pivot, 'getOptionsForSave').returns({ here_are: 'pivot_settings' })
|
||||
|
||||
expect(wrapper.vm.getOptionsForSave()).to.eql({ here_are: 'pivot_settings' })
|
||||
})
|
||||
|
||||
it('method saveAsSvg calls the same method of the current view component', async () => {
|
||||
const wrapper = mount(DataView)
|
||||
|
||||
// Find chart and spy the method
|
||||
const chart = wrapper.findComponent({ name: 'Chart' }).vm
|
||||
sinon.spy(chart, 'saveAsSvg')
|
||||
|
||||
// Export to svg
|
||||
const svgBtn = createWrapper(wrapper.findComponent({ name: 'exportToSvgIcon' }).vm.$parent)
|
||||
await svgBtn.trigger('click')
|
||||
expect(chart.saveAsSvg.calledOnce).to.equal(true)
|
||||
|
||||
// Switch to pivot
|
||||
const pivotBtn = createWrapper(wrapper.findComponent({ name: 'pivotIcon' }).vm.$parent)
|
||||
await pivotBtn.trigger('click')
|
||||
|
||||
// Find pivot and spy the method
|
||||
const pivot = wrapper.findComponent({ name: 'pivot' }).vm
|
||||
sinon.spy(pivot, 'saveAsSvg')
|
||||
|
||||
// Export to svg
|
||||
await svgBtn.trigger('click')
|
||||
expect(pivot.saveAsSvg.calledOnce).to.equal(true)
|
||||
})
|
||||
|
||||
it('shows alert when ClipboardItem is not supported', async () => {
|
||||
const ClipboardItem = window.ClipboardItem
|
||||
delete window.ClipboardItem
|
||||
sinon.spy(window, 'alert')
|
||||
const wrapper = mount(DataView)
|
||||
|
||||
const copyBtn = createWrapper(wrapper.findComponent({ name: 'clipboardIcon' }).vm.$parent)
|
||||
await copyBtn.trigger('click')
|
||||
|
||||
expect(
|
||||
window.alert.calledOnceWith(
|
||||
"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.'
|
||||
)
|
||||
).to.equal(true)
|
||||
|
||||
window.ClipboardItem = ClipboardItem
|
||||
})
|
||||
|
||||
it('copy to clipboard more than 1 sec', async () => {
|
||||
sinon.stub(window.navigator.clipboard, 'write').resolves()
|
||||
const clock = sinon.useFakeTimers()
|
||||
const wrapper = mount(DataView)
|
||||
sinon.stub(wrapper.vm.$refs.viewComponent, 'prepareCopy').callsFake(() => {
|
||||
clock.tick(5000)
|
||||
})
|
||||
|
||||
// Click copy to clipboard
|
||||
const copyBtn = createWrapper(wrapper.findComponent({ name: 'clipboardIcon' }).vm.$parent)
|
||||
await copyBtn.trigger('click')
|
||||
|
||||
// The dialog is shown...
|
||||
expect(wrapper.find('[data-modal="prepareCopy"]').exists()).to.equal(true)
|
||||
|
||||
// ... with Rendering message...
|
||||
expect(wrapper.find('.dialog-body').text()).to.equal('Rendering the visualisation...')
|
||||
|
||||
// Switch to microtasks (let prepareCopy run)
|
||||
clock.tick(0)
|
||||
// Wait untill prepareCopy is finished
|
||||
await wrapper.vm.$refs.viewComponent.prepareCopy.returnValues[0]
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
// The dialog is shown...
|
||||
expect(wrapper.find('[data-modal="prepareCopy"]').exists()).to.equal(true)
|
||||
|
||||
// ... with Ready message...
|
||||
expect(wrapper.find('.dialog-body').text()).to.equal('Image is ready')
|
||||
|
||||
// Click copy
|
||||
await wrapper.find('.dialog-buttons-container button.primary').trigger('click')
|
||||
|
||||
// The dialog is not shown...
|
||||
expect(wrapper.find('[data-modal="prepareCopy"]').exists()).to.equal(false)
|
||||
})
|
||||
|
||||
it('copy to clipboard less than 1 sec', async () => {
|
||||
sinon.stub(window.navigator.clipboard, 'write').resolves()
|
||||
const clock = sinon.useFakeTimers()
|
||||
const wrapper = mount(DataView)
|
||||
sinon.spy(wrapper.vm, 'copyToClipboard')
|
||||
sinon.stub(wrapper.vm.$refs.viewComponent, 'prepareCopy').callsFake(() => {
|
||||
clock.tick(500)
|
||||
})
|
||||
|
||||
// Click copy to clipboard
|
||||
const copyBtn = createWrapper(wrapper.findComponent({ name: 'clipboardIcon' }).vm.$parent)
|
||||
await copyBtn.trigger('click')
|
||||
|
||||
// Switch to microtasks (let prepareCopy run)
|
||||
clock.tick(0)
|
||||
// Wait untill prepareCopy is finished
|
||||
await wrapper.vm.$refs.viewComponent.prepareCopy.returnValues[0]
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
// The dialog is not shown...
|
||||
expect(wrapper.find('[data-modal="prepareCopy"]').exists()).to.equal(false)
|
||||
// copyToClipboard is called
|
||||
expect(wrapper.vm.copyToClipboard.calledOnce).to.equal(true)
|
||||
})
|
||||
|
||||
it('cancel long copy', async () => {
|
||||
sinon.stub(window.navigator.clipboard, 'write').resolves()
|
||||
const clock = sinon.useFakeTimers()
|
||||
const wrapper = mount(DataView)
|
||||
sinon.spy(wrapper.vm, 'copyToClipboard')
|
||||
sinon.stub(wrapper.vm.$refs.viewComponent, 'prepareCopy').callsFake(() => {
|
||||
clock.tick(5000)
|
||||
})
|
||||
|
||||
// Click copy to clipboard
|
||||
const copyBtn = createWrapper(wrapper.findComponent({ name: 'clipboardIcon' }).vm.$parent)
|
||||
await copyBtn.trigger('click')
|
||||
|
||||
// Switch to microtasks (let prepareCopy run)
|
||||
clock.tick(0)
|
||||
// Wait untill prepareCopy is finished
|
||||
await wrapper.vm.$refs.viewComponent.prepareCopy.returnValues[0]
|
||||
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
// Click cancel
|
||||
await wrapper.find('.dialog-buttons-container button.secondary').trigger('click')
|
||||
|
||||
// The dialog is not shown...
|
||||
expect(wrapper.find('[data-modal="prepareCopy"]').exists()).to.equal(false)
|
||||
// copyToClipboard is not called
|
||||
expect(wrapper.vm.copyToClipboard.calledOnce).to.equal(false)
|
||||
})
|
||||
})
|
||||
@@ -1,9 +1,24 @@
|
||||
import { expect } from 'chai'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Pivot from '@/views/Main/Workspace/Tabs/Tab/DataView/Pivot'
|
||||
import chartHelper from '@/lib/chartHelper'
|
||||
import fIo from '@/lib/utils/fileIo'
|
||||
import $ from 'jquery'
|
||||
import sinon from 'sinon'
|
||||
|
||||
describe('Pivot.vue', () => {
|
||||
let container
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
container.remove()
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
it('renders pivot table', () => {
|
||||
const wrapper = mount(Pivot, {
|
||||
propsData: {
|
||||
@@ -88,55 +103,6 @@ describe('Pivot.vue', () => {
|
||||
expect(rows.at(2).find('td.rowTotal').text()).to.equal('2')
|
||||
})
|
||||
|
||||
it('updates when pivot settings changes', async () => {
|
||||
const wrapper = mount(Pivot, {
|
||||
propsData: {
|
||||
dataSources: {
|
||||
item: ['foo', 'bar', 'bar', 'bar'],
|
||||
year: [2021, 2021, 2020, 2020]
|
||||
},
|
||||
initOptions: {
|
||||
rows: ['item'],
|
||||
cols: ['year'],
|
||||
colOrder: 'key_a_to_z',
|
||||
rowOrder: 'key_a_to_z',
|
||||
aggregatorName: 'Count',
|
||||
vals: [],
|
||||
rendererName: 'Table'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await wrapper.findComponent({ name: 'pivotUi' }).vm.$emit('input', {
|
||||
rows: ['year'],
|
||||
cols: ['item'],
|
||||
colOrder: 'key_a_to_z',
|
||||
rowOrder: 'key_a_to_z',
|
||||
aggregator: $.pivotUtilities.aggregators.Count(),
|
||||
aggregatorName: 'Count',
|
||||
renderer: $.pivotUtilities.renderers.Table,
|
||||
rendererName: 'Table',
|
||||
rendererOptions: undefined,
|
||||
vals: []
|
||||
})
|
||||
|
||||
const colLabels = wrapper.findAll('.pivot-output thead th.pvtColLabel')
|
||||
expect(colLabels.at(0).text()).to.equal('bar')
|
||||
expect(colLabels.at(1).text()).to.equal('foo')
|
||||
const rows = wrapper.findAll('.pivot-output tbody tr')
|
||||
// row0: 2020 - 2 -
|
||||
expect(rows.at(0).find('th').text()).to.equal('2020')
|
||||
expect(rows.at(0).find('td.col0').text()).to.equal('2')
|
||||
expect(rows.at(0).find('td.col1').text()).to.equal('')
|
||||
expect(rows.at(0).find('td.rowTotal').text()).to.equal('2')
|
||||
|
||||
// row1: 2021 - 1 - 1
|
||||
expect(rows.at(1).find('th').text()).to.equal('2021')
|
||||
expect(rows.at(1).find('td.col0').text()).to.equal('1')
|
||||
expect(rows.at(1).find('td.col1').text()).to.equal('1')
|
||||
expect(rows.at(1).find('td.rowTotal').text()).to.equal('2')
|
||||
})
|
||||
|
||||
it('returns options for save', async () => {
|
||||
const wrapper = mount(Pivot, {
|
||||
propsData: {
|
||||
@@ -191,6 +157,7 @@ describe('Pivot.vue', () => {
|
||||
rendererName: 'Custom chart',
|
||||
rendererOptions: {
|
||||
customChartComponent: {
|
||||
$mount: sinon.stub(),
|
||||
getOptionsForSave () {
|
||||
return { here_are: 'custom chart settings' }
|
||||
}
|
||||
@@ -211,4 +178,220 @@ describe('Pivot.vue', () => {
|
||||
})
|
||||
expect(optionsForSave.vals).to.eql([])
|
||||
})
|
||||
|
||||
it('prepareCopy returns canvas for tables and url for plotly charts', async () => {
|
||||
const wrapper = mount(Pivot, {
|
||||
propsData: {
|
||||
dataSources: {
|
||||
item: ['foo', 'bar', 'bar', 'bar'],
|
||||
year: [2021, 2021, 2020, 2020]
|
||||
},
|
||||
initOptions: {
|
||||
rows: ['item'],
|
||||
cols: ['year'],
|
||||
colOrder: 'key_a_to_z',
|
||||
rowOrder: 'key_a_to_z',
|
||||
aggregatorName: 'Count',
|
||||
vals: [],
|
||||
rendererName: 'Table'
|
||||
}
|
||||
},
|
||||
attachTo: container
|
||||
})
|
||||
|
||||
expect(await wrapper.vm.prepareCopy()).to.be.instanceof(HTMLCanvasElement)
|
||||
|
||||
await wrapper.findComponent({ name: 'pivotUi' }).vm.$emit('input', {
|
||||
rows: ['item'],
|
||||
cols: ['year'],
|
||||
colOrder: 'value_a_to_z',
|
||||
rowOrder: 'value_z_to_a',
|
||||
aggregator: $.pivotUtilities.aggregators.Count(),
|
||||
aggregatorName: 'Count',
|
||||
renderer: $.pivotUtilities.renderers['Custom chart'],
|
||||
rendererName: 'Custom chart',
|
||||
rendererOptions: {
|
||||
customChartComponent: {
|
||||
$mount: sinon.stub(),
|
||||
prepareCopy: sinon.stub().returns(URL.createObjectURL(new Blob()))
|
||||
}
|
||||
},
|
||||
vals: []
|
||||
})
|
||||
|
||||
expect(await wrapper.vm.prepareCopy()).to.be.a('string')
|
||||
|
||||
await wrapper.findComponent({ name: 'pivotUi' }).vm.$emit('input', {
|
||||
rows: ['item'],
|
||||
cols: ['year'],
|
||||
colOrder: 'value_a_to_z',
|
||||
rowOrder: 'value_z_to_a',
|
||||
aggregator: $.pivotUtilities.aggregators.Count(),
|
||||
aggregatorName: 'Count',
|
||||
renderer: $.pivotUtilities.renderers['Bar Chart'],
|
||||
rendererName: 'Bar Chart',
|
||||
vals: []
|
||||
})
|
||||
|
||||
expect(await wrapper.vm.prepareCopy()).to.be.a('string')
|
||||
})
|
||||
|
||||
it('saveAsSvg calls chart method if renderer is Custom Chart', async () => {
|
||||
const wrapper = mount(Pivot, {
|
||||
propsData: {
|
||||
dataSources: {
|
||||
item: ['foo', 'bar', 'bar', 'bar'],
|
||||
year: [2021, 2021, 2020, 2020]
|
||||
},
|
||||
initOptions: {
|
||||
rows: ['item'],
|
||||
cols: ['year'],
|
||||
colOrder: 'key_a_to_z',
|
||||
rowOrder: 'key_a_to_z',
|
||||
aggregatorName: 'Count',
|
||||
vals: [],
|
||||
renderer: $.pivotUtilities.renderers['Custom chart'],
|
||||
rendererName: 'Custom chart',
|
||||
rendererOptions: {
|
||||
customChartOptions: {
|
||||
data: [],
|
||||
layout: {},
|
||||
frames: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
attachTo: container
|
||||
})
|
||||
|
||||
const chartComponent = wrapper.vm.pivotOptions.rendererOptions.customChartComponent
|
||||
sinon.stub(chartComponent, 'saveAsSvg')
|
||||
|
||||
await wrapper.vm.saveAsSvg()
|
||||
expect(chartComponent.saveAsSvg.called).to.equal(true)
|
||||
})
|
||||
|
||||
it('saveAsPng calls chart method if renderer is Custom Chart', async () => {
|
||||
const wrapper = mount(Pivot, {
|
||||
propsData: {
|
||||
dataSources: {
|
||||
item: ['foo', 'bar', 'bar', 'bar'],
|
||||
year: [2021, 2021, 2020, 2020]
|
||||
},
|
||||
initOptions: {
|
||||
rows: ['item'],
|
||||
cols: ['year'],
|
||||
colOrder: 'key_a_to_z',
|
||||
rowOrder: 'key_a_to_z',
|
||||
aggregatorName: 'Count',
|
||||
vals: [],
|
||||
renderer: $.pivotUtilities.renderers['Custom chart'],
|
||||
rendererName: 'Custom chart',
|
||||
rendererOptions: {
|
||||
customChartOptions: {
|
||||
data: [],
|
||||
layout: {},
|
||||
frames: []
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
attachTo: container
|
||||
})
|
||||
|
||||
const chartComponent = wrapper.vm.pivotOptions.rendererOptions.customChartComponent
|
||||
sinon.stub(chartComponent, 'saveAsPng')
|
||||
|
||||
await wrapper.vm.saveAsPng()
|
||||
expect(chartComponent.saveAsPng.called).to.equal(true)
|
||||
})
|
||||
|
||||
it('saveAsSvg - standart chart', async () => {
|
||||
sinon.spy(chartHelper, 'getImageDataUrl')
|
||||
|
||||
const wrapper = mount(Pivot, {
|
||||
propsData: {
|
||||
dataSources: {
|
||||
item: ['foo', 'bar', 'bar', 'bar'],
|
||||
year: [2021, 2021, 2020, 2020]
|
||||
},
|
||||
initOptions: {
|
||||
rows: ['item'],
|
||||
cols: ['year'],
|
||||
colOrder: 'key_a_to_z',
|
||||
rowOrder: 'key_a_to_z',
|
||||
aggregatorName: 'Count',
|
||||
vals: [],
|
||||
renderer: $.pivotUtilities.renderers['Bar Chart'],
|
||||
rendererName: 'Bar Chart'
|
||||
}
|
||||
},
|
||||
attachTo: container
|
||||
})
|
||||
|
||||
await wrapper.vm.saveAsSvg()
|
||||
expect(chartHelper.getImageDataUrl.calledOnce).to.equal(true)
|
||||
})
|
||||
|
||||
it('saveAsPng - standart chart', async () => {
|
||||
sinon.stub(chartHelper, 'getImageDataUrl').returns('standat chart data url')
|
||||
sinon.stub(fIo, 'downloadFromUrl')
|
||||
|
||||
const wrapper = mount(Pivot, {
|
||||
propsData: {
|
||||
dataSources: {
|
||||
item: ['foo', 'bar', 'bar', 'bar'],
|
||||
year: [2021, 2021, 2020, 2020]
|
||||
},
|
||||
initOptions: {
|
||||
rows: ['item'],
|
||||
cols: ['year'],
|
||||
colOrder: 'key_a_to_z',
|
||||
rowOrder: 'key_a_to_z',
|
||||
aggregatorName: 'Count',
|
||||
vals: [],
|
||||
renderer: $.pivotUtilities.renderers['Bar Chart'],
|
||||
rendererName: 'Bar Chart'
|
||||
}
|
||||
},
|
||||
attachTo: container
|
||||
})
|
||||
|
||||
await wrapper.vm.saveAsPng()
|
||||
expect(chartHelper.getImageDataUrl.calledOnce).to.equal(true)
|
||||
await chartHelper.getImageDataUrl.returnValues[0]
|
||||
expect(wrapper.emitted().loadingImageCompleted.length).to.equal(1)
|
||||
expect(fIo.downloadFromUrl.calledOnceWith('standat chart data url', 'pivot')).to.equal(true)
|
||||
})
|
||||
|
||||
it('saveAsPng - table', async () => {
|
||||
sinon.stub(HTMLCanvasElement.prototype, 'toDataURL').returns('canvas data url')
|
||||
sinon.stub(fIo, 'downloadFromUrl')
|
||||
|
||||
const wrapper = mount(Pivot, {
|
||||
propsData: {
|
||||
dataSources: {
|
||||
item: ['foo', 'bar', 'bar', 'bar'],
|
||||
year: [2021, 2021, 2020, 2020]
|
||||
},
|
||||
initOptions: {
|
||||
rows: ['item'],
|
||||
cols: ['year'],
|
||||
colOrder: 'key_a_to_z',
|
||||
rowOrder: 'key_a_to_z',
|
||||
aggregatorName: 'Count',
|
||||
vals: [],
|
||||
renderer: $.pivotUtilities.renderers.Table,
|
||||
rendererName: 'Table'
|
||||
}
|
||||
},
|
||||
attachTo: container
|
||||
})
|
||||
|
||||
await wrapper.vm.saveAsPng()
|
||||
expect(HTMLCanvasElement.prototype.toDataURL.calledOnce).to.equal(true)
|
||||
await HTMLCanvasElement.prototype.toDataURL.returnValues[0]
|
||||
expect(wrapper.emitted().loadingImageCompleted.length).to.equal(1)
|
||||
expect(fIo.downloadFromUrl.calledOnceWith('canvas data url', 'pivot')).to.equal(true)
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
import { expect } from 'chai'
|
||||
import { _getDataSources } from '@/views/Main/Workspace/Tabs/Tab/DataView/Pivot/PivotUi/pivotHelper'
|
||||
import { _getDataSources, getPivotCanvas }
|
||||
from '@/views/Main/Workspace/Tabs/Tab/DataView/Pivot/pivotHelper'
|
||||
|
||||
describe('pivotHelper.js', () => {
|
||||
it('_getDataSources returns data sources', () => {
|
||||
@@ -53,4 +54,13 @@ describe('pivotHelper.js', () => {
|
||||
'y:9': [11, 12, 10, 15]
|
||||
})
|
||||
})
|
||||
|
||||
it('getPivotCanvas returns canvas', async () => {
|
||||
const pivotOutput = document.body
|
||||
const child = document.createElement('div')
|
||||
child.classList.add('pvtTable')
|
||||
pivotOutput.append(child)
|
||||
|
||||
expect(await getPivotCanvas(pivotOutput)).to.be.instanceof(HTMLCanvasElement)
|
||||
})
|
||||
})
|
||||
155
tests/views/Main/Workspace/Tabs/Tab/RunResult.spec.js
Normal file
155
tests/views/Main/Workspace/Tabs/Tab/RunResult.spec.js
Normal file
@@ -0,0 +1,155 @@
|
||||
import { expect } from 'chai'
|
||||
import { mount, createWrapper } from '@vue/test-utils'
|
||||
import RunResult from '@/views/Main/Workspace/Tabs/Tab/RunResult'
|
||||
import csv from '@/lib/csv'
|
||||
import sinon from 'sinon'
|
||||
|
||||
describe('RunResult.vue', () => {
|
||||
afterEach(() => {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
it('shows alert when ClipboardItem is not supported', async () => {
|
||||
const ClipboardItem = window.ClipboardItem
|
||||
delete window.ClipboardItem
|
||||
sinon.spy(window, 'alert')
|
||||
const wrapper = mount(RunResult, {
|
||||
propsData: {
|
||||
result: {
|
||||
columns: ['id', 'name'],
|
||||
values: {
|
||||
id: [1],
|
||||
name: ['foo']
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const copyBtn = createWrapper(wrapper.findComponent({ name: 'clipboardIcon' }).vm.$parent)
|
||||
await copyBtn.trigger('click')
|
||||
|
||||
expect(
|
||||
window.alert.calledOnceWith(
|
||||
"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.'
|
||||
)
|
||||
).to.equal(true)
|
||||
|
||||
window.ClipboardItem = ClipboardItem
|
||||
})
|
||||
|
||||
it('copy to clipboard more than 1 sec', async () => {
|
||||
sinon.stub(window.navigator.clipboard, 'writeText').resolves()
|
||||
const clock = sinon.useFakeTimers()
|
||||
const wrapper = mount(RunResult, {
|
||||
propsData: {
|
||||
result: {
|
||||
columns: ['id', 'name'],
|
||||
values: {
|
||||
id: [1],
|
||||
name: ['foo']
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
sinon.stub(csv, 'serialize').callsFake(() => {
|
||||
clock.tick(5000)
|
||||
})
|
||||
|
||||
// Click copy to clipboard
|
||||
const copyBtn = createWrapper(wrapper.findComponent({ name: 'clipboardIcon' }).vm.$parent)
|
||||
await copyBtn.trigger('click')
|
||||
|
||||
// The dialog is shown...
|
||||
expect(wrapper.find('[data-modal="prepareCSVCopy"]').exists()).to.equal(true)
|
||||
|
||||
// ... with Building message...
|
||||
expect(wrapper.find('.dialog-body').text()).to.equal('Building CSV...')
|
||||
|
||||
// Switch to microtasks (let serialize run)
|
||||
clock.tick(0)
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
// The dialog is shown...
|
||||
expect(wrapper.find('[data-modal="prepareCSVCopy"]').exists()).to.equal(true)
|
||||
|
||||
// ... with Ready message...
|
||||
expect(wrapper.find('.dialog-body').text()).to.equal('CSV is ready')
|
||||
|
||||
// Click copy
|
||||
await wrapper.find('.dialog-buttons-container button.primary').trigger('click')
|
||||
|
||||
// The dialog is not shown...
|
||||
expect(wrapper.find('[data-modal="prepareCSVCopy"]').exists()).to.equal(false)
|
||||
})
|
||||
|
||||
it('copy to clipboard less than 1 sec', async () => {
|
||||
sinon.stub(window.navigator.clipboard, 'writeText').resolves()
|
||||
const clock = sinon.useFakeTimers()
|
||||
const wrapper = mount(RunResult, {
|
||||
propsData: {
|
||||
result: {
|
||||
columns: ['id', 'name'],
|
||||
values: {
|
||||
id: [1],
|
||||
name: ['foo']
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
sinon.spy(wrapper.vm, 'copyToClipboard')
|
||||
sinon.stub(csv, 'serialize').callsFake(() => {
|
||||
clock.tick(500)
|
||||
})
|
||||
|
||||
// Click copy to clipboard
|
||||
const copyBtn = createWrapper(wrapper.findComponent({ name: 'clipboardIcon' }).vm.$parent)
|
||||
await copyBtn.trigger('click')
|
||||
|
||||
// Switch to microtasks (let serialize run)
|
||||
clock.tick(0)
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
// The dialog is not shown...
|
||||
expect(wrapper.find('[data-modal="prepareCSVCopy"]').exists()).to.equal(false)
|
||||
// copyToClipboard is called
|
||||
expect(wrapper.vm.copyToClipboard.calledOnce).to.equal(true)
|
||||
})
|
||||
|
||||
it('cancel long copy', async () => {
|
||||
sinon.stub(window.navigator.clipboard, 'writeText').resolves()
|
||||
const clock = sinon.useFakeTimers()
|
||||
const wrapper = mount(RunResult, {
|
||||
propsData: {
|
||||
result: {
|
||||
columns: ['id', 'name'],
|
||||
values: {
|
||||
id: [1],
|
||||
name: ['foo']
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
sinon.spy(wrapper.vm, 'copyToClipboard')
|
||||
sinon.stub(csv, 'serialize').callsFake(() => {
|
||||
clock.tick(5000)
|
||||
})
|
||||
|
||||
// Click copy to clipboard
|
||||
const copyBtn = createWrapper(wrapper.findComponent({ name: 'clipboardIcon' }).vm.$parent)
|
||||
await copyBtn.trigger('click')
|
||||
|
||||
// Switch to microtasks (let serialize run)
|
||||
clock.tick(0)
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
// Click cancel
|
||||
await wrapper.find('.dialog-buttons-container button.secondary').trigger('click')
|
||||
|
||||
// The dialog is not shown...
|
||||
expect(wrapper.find('[data-modal="prepareCSVCopy"]').exists()).to.equal(false)
|
||||
// copyToClipboard is not called
|
||||
expect(wrapper.vm.copyToClipboard.calledOnce).to.equal(false)
|
||||
})
|
||||
})
|
||||
@@ -134,7 +134,9 @@ describe('hint.js', () => {
|
||||
expect(CM.showHint.called).to.equal(false)
|
||||
})
|
||||
|
||||
it('getHints returns [ ] if there is only one option and the token is already completed with this option', () => {
|
||||
it(
|
||||
'getHints returns [ ] if there is only one option and token is completed with this option',
|
||||
() => {
|
||||
// mock CM.hint.sql and editor
|
||||
sinon.stub(CM.hint, 'sql').returns({ list: [{ text: 'SELECT' }] })
|
||||
const editor = {
|
||||
@@ -149,7 +151,8 @@ describe('hint.js', () => {
|
||||
|
||||
const hints = getHints(editor, {})
|
||||
expect(hints.list).to.eql([])
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
it('getHints returns hints as is when there are more than one option', () => {
|
||||
// mock CM.hint.sql and editor
|
||||
@@ -174,7 +177,9 @@ describe('hint.js', () => {
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
it('getHints returns hints as is when there only one option but the token is not cpmpleted', () => {
|
||||
it(
|
||||
'getHints returns hints as is when there only one option but the token is not completed',
|
||||
() => {
|
||||
// mock CM.hint.sql and editor
|
||||
const list = [{ text: 'SELECT' }]
|
||||
sinon.stub(CM.hint, 'sql').returns({ list })
|
||||
@@ -190,7 +195,8 @@ describe('hint.js', () => {
|
||||
|
||||
const hints = getHints(editor, {})
|
||||
expect(hints.list).to.eql(list)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
it('tables is empty object when schema is null', () => {
|
||||
// mock store state
|
||||
|
||||
@@ -44,7 +44,8 @@ describe('Tab.vue', () => {
|
||||
expect(wrapper.find('.tab-content-container').isVisible()).to.equal(true)
|
||||
expect(wrapper.find('.bottomPane .run-result-panel').exists()).to.equal(true)
|
||||
expect(wrapper.find('.run-result-panel .result-before').isVisible()).to.equal(true)
|
||||
expect(wrapper.find('.above .sql-editor-panel .codemirror-container').text()).to.equal('SELECT * FROM foo')
|
||||
expect(wrapper.find('.above .sql-editor-panel .codemirror-container').text())
|
||||
.to.equal('SELECT * FROM foo')
|
||||
})
|
||||
|
||||
it("Doesn't render tab when it's not active", () => {
|
||||
@@ -136,6 +137,35 @@ describe('Tab.vue', () => {
|
||||
expect(state.tabs[0].isSaved).to.equal(false)
|
||||
})
|
||||
|
||||
it('Update tab state when data view settings are changed', async () => {
|
||||
// mock store state
|
||||
const state = {
|
||||
tabs: [
|
||||
{ id: 1, name: 'foo', query: 'SELECT * FROM foo', chart: [], isSaved: true }
|
||||
],
|
||||
currentTabId: 1
|
||||
}
|
||||
|
||||
const store = new Vuex.Store({ state, mutations })
|
||||
|
||||
// mount the component
|
||||
const wrapper = mount(Tab, {
|
||||
store,
|
||||
stubs: ['chart'],
|
||||
propsData: {
|
||||
id: 1,
|
||||
initName: 'foo',
|
||||
initQuery: 'SELECT * FROM foo',
|
||||
initViewOptions: [],
|
||||
initViewType: 'chart',
|
||||
tabIndex: 0,
|
||||
isPredefined: false
|
||||
}
|
||||
})
|
||||
await wrapper.findComponent({ name: 'DataView' }).vm.$emit('update')
|
||||
expect(state.tabs[0].isSaved).to.equal(false)
|
||||
})
|
||||
|
||||
it('Shows .result-in-progress message when executing query', async () => {
|
||||
// mock store state
|
||||
const state = {
|
||||
@@ -201,9 +231,12 @@ describe('Tab.vue', () => {
|
||||
|
||||
it('Passes result to sql-table component', async () => {
|
||||
const result = {
|
||||
columns: ['id', 'name'],
|
||||
values: {
|
||||
id: [1, 2],
|
||||
name: ['foo', 'bar']
|
||||
}
|
||||
}
|
||||
// mock store state
|
||||
const state = {
|
||||
currentTabId: 1,
|
||||
@@ -239,9 +272,12 @@ describe('Tab.vue', () => {
|
||||
|
||||
it('Updates schema after query execution', async () => {
|
||||
const result = {
|
||||
columns: ['id', 'name'],
|
||||
values: {
|
||||
id: [],
|
||||
name: []
|
||||
}
|
||||
}
|
||||
|
||||
// mock store state
|
||||
const state = {
|
||||
@@ -297,25 +333,29 @@ describe('Tab.vue', () => {
|
||||
}
|
||||
})
|
||||
|
||||
let tableBtn = createWrapper(wrapper.find('.above .side-tool-bar').findComponent({ name: 'tableIcon' }).vm.$parent)
|
||||
let tableBtn = createWrapper(wrapper.find('.above .side-tool-bar')
|
||||
.findComponent({ name: 'tableIcon' }).vm.$parent)
|
||||
await tableBtn.trigger('click')
|
||||
|
||||
expect(wrapper.find('.bottomPane .sql-editor-panel').exists()).to.equal(true)
|
||||
expect(wrapper.find('.above .run-result-panel').exists()).to.equal(true)
|
||||
|
||||
const dataViewBtn = createWrapper(wrapper.find('.above .side-tool-bar').findComponent({ name: 'dataViewIcon' }).vm.$parent)
|
||||
const dataViewBtn = createWrapper(wrapper.find('.above .side-tool-bar')
|
||||
.findComponent({ name: 'dataViewIcon' }).vm.$parent)
|
||||
await dataViewBtn.trigger('click')
|
||||
|
||||
expect(wrapper.find('.bottomPane .sql-editor-panel').exists()).to.equal(true)
|
||||
expect(wrapper.find('.above .data-view-panel').exists()).to.equal(true)
|
||||
|
||||
const sqlEditorBtn = createWrapper(wrapper.find('.above .side-tool-bar').findComponent({ name: 'sqlEditorIcon' }).vm.$parent)
|
||||
const sqlEditorBtn = createWrapper(wrapper.find('.above .side-tool-bar')
|
||||
.findComponent({ name: 'sqlEditorIcon' }).vm.$parent)
|
||||
await sqlEditorBtn.trigger('click')
|
||||
|
||||
expect(wrapper.find('.above .sql-editor-panel').exists()).to.equal(true)
|
||||
expect(wrapper.find('.bottomPane .data-view-panel').exists()).to.equal(true)
|
||||
|
||||
tableBtn = createWrapper(wrapper.find('.bottomPane .side-tool-bar').findComponent({ name: 'tableIcon' }).vm.$parent)
|
||||
tableBtn = createWrapper(wrapper.find('.bottomPane .side-tool-bar')
|
||||
.findComponent({ name: 'tableIcon' }).vm.$parent)
|
||||
await tableBtn.trigger('click')
|
||||
|
||||
expect(wrapper.find('.above .sql-editor-panel').exists()).to.equal(true)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { expect } from 'chai'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import actions from '@/store/actions'
|
||||
import mutations from '@/store/mutations'
|
||||
import Vuex from 'vuex'
|
||||
import Workspace from '@/views/Main/Workspace'
|
||||
|
||||
@@ -10,8 +11,11 @@ describe('Workspace.vue', () => {
|
||||
db: {},
|
||||
tabs: []
|
||||
}
|
||||
const store = new Vuex.Store({ state, actions })
|
||||
mount(Workspace, { store })
|
||||
const store = new Vuex.Store({ state, actions, mutations })
|
||||
mount(Workspace, {
|
||||
store,
|
||||
stubs: ['router-link']
|
||||
})
|
||||
|
||||
expect(state.tabs[0].query).to.include('Your database is empty.')
|
||||
expect(state.tabs[0].tempName).to.equal('Untitled')
|
||||
@@ -10,7 +10,8 @@ module.exports = {
|
||||
plugins: [
|
||||
new CopyPlugin([
|
||||
// This wasm file will be fetched dynamically when we initialize sql.js
|
||||
// It is important that we do not change its name, and that it is in the same folder as the js
|
||||
// It is important that we do not change its name,
|
||||
// and that it is in the same folder as the js
|
||||
{ from: 'node_modules/sql.js/dist/sql-wasm.wasm', to: 'js/' },
|
||||
{ from: 'LICENSE', to: './' }
|
||||
]),
|
||||
|
||||
Reference in New Issue
Block a user