mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Compare commits
119 Commits
v0.0.1-alp
...
v0.0.6
Author | SHA1 | Date | |
---|---|---|---|
51ccce3da4 | |||
a1a6f51f2f | |||
801a0de186 | |||
264de9c568 | |||
8390f8aa55 | |||
af7c0e90b8 | |||
|
da33e77361 | ||
|
a4841ab63b | ||
de3f36a3fe | |||
8dc74ef2c3 | |||
256ec76588 | |||
196a3e0185 | |||
bc54fef0aa | |||
a5b478e53d | |||
|
2e235ad2fe | ||
|
950bb17b1e | ||
3a6ea76b93 | |||
d7ed00f4a3 | |||
fd6d5177ef | |||
9599b43f78 | |||
2cfb223ff6 | |||
69def94c88 | |||
e8141b6321 | |||
0b6a188d19 | |||
dca625fe5a | |||
|
a4b94bc19c | ||
744728a14f | |||
6d0724dc90 | |||
59e4a79f42 | |||
7bc10092fe | |||
eb348b3095 | |||
3c6e818ba0 | |||
2f1dfdc654 | |||
128a6cd9e8 | |||
5473858323 | |||
7651d05b37 | |||
c89c1ce83c | |||
771f8a2d68 | |||
13b0816837 | |||
a15e6249e1 | |||
bbde2bd994 | |||
949f7add8f | |||
968ec1edf7 | |||
a9d3a57281 | |||
|
f787439009 | ||
b03d461e21 | |||
5c05e3e9e9 | |||
0089c0cbac | |||
4fd72ec9e7 | |||
712fe9f00d | |||
092e8a0732 | |||
70908eb076 | |||
f8a0783769 | |||
|
cdf964ef07 | ||
413b56916c | |||
acd3310228 | |||
|
e014f81a9c | ||
530361144e | |||
d69e411581 | |||
9a3a0513e2 | |||
|
be8fa96c93 | ||
587116bd20 | |||
|
145d1dd1ad | ||
457410b65a | |||
|
b183eacae1 | ||
76cafdb69a | |||
|
2d63ddb9c7 | ||
f12f00dbb7 | |||
1ecb6d892c | |||
fcd83f35d8 | |||
|
7a4d8286a6 | ||
|
dd5ec2c661 | ||
3f0e5d3512 | |||
ac01511c10 | |||
4a83ae7e75 | |||
60132c94a1 | |||
fdf5bef5ad | |||
67f55fbeb9 | |||
|
fd5a8548c7 | ||
425ecf838d | |||
1a8a49eceb | |||
|
e9fffcc37e | ||
bba7c4af6f | |||
|
376d74c7dc | ||
307a32aff6 | |||
d1aaad276b | |||
|
b23b4f18b9 | ||
334cfa9047 | |||
|
2f6d16c730 | ||
b8e09e7003 | |||
9caf424331 | |||
|
9e5e545478 | ||
|
c9ab731bb4 | ||
57832c43aa | |||
|
184363369b | ||
b221dd12ff | |||
|
1324464aa3 | ||
187b4f50f9 | |||
262a476f50 | |||
|
f00c2600e5 | ||
75a7db9c05 | |||
50cd852d01 | |||
bb8cfa533b | |||
|
1ddc8b5dca | ||
8a4c628128 | |||
0076f146fa | |||
098d12f462 | |||
b619285d94 | |||
|
a5fed1bb64 | ||
55ec03bd8e | |||
|
d1977fbd75 | ||
3c20c0733c | |||
1d4a353d5c | |||
db71777cbc | |||
f350fe8203 | |||
28c3f87dd8 | |||
cc8dbb8df7 | |||
85ac1bc85f | |||
baea5c26e2 |
12
.github/FUNDING.yml
vendored
Normal file
12
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [fabio286]
|
||||||
|
patreon: fabio286
|
||||||
|
open_collective: # Replace with a single Open Collective username
|
||||||
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
liberapay: # Replace with a single Liberapay username
|
||||||
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
|
otechie: # Replace with a single Otechie username
|
||||||
|
custom: ['https://paypal.me/fabiodistasio']
|
11
.github/dependabot.yml
vendored
Normal file
11
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "npm"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,4 +6,4 @@ thumbs.db
|
|||||||
.vscode
|
.vscode
|
||||||
TODO.md
|
TODO.md
|
||||||
*.txt
|
*.txt
|
||||||
dev-app-update.yml
|
package-lock.json
|
14
.stylelintrc
Normal file
14
.stylelintrc
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"stylelint-config-standard"
|
||||||
|
],
|
||||||
|
"fix": true,
|
||||||
|
"formatter": "verbose",
|
||||||
|
"plugins": [
|
||||||
|
"stylelint-scss"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"at-rule-no-unknown": null
|
||||||
|
},
|
||||||
|
"syntax": "scss"
|
||||||
|
}
|
38
.travis.yml
Normal file
38
.travis.yml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
language: node_js
|
||||||
|
node_js: 12
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- npm install
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- node_modules
|
||||||
|
- app/node_modules
|
||||||
|
- $HOME/.cache/electron
|
||||||
|
- $HOME/.cache/electron-builder
|
||||||
|
- $HOME/.npm/_prebuilds
|
||||||
|
|
||||||
|
env:
|
||||||
|
global:
|
||||||
|
- ELECTRON_CACHE=$HOME/.cache/electron
|
||||||
|
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
include:
|
||||||
|
- stage: Test
|
||||||
|
script:
|
||||||
|
- npm test
|
||||||
|
- stage: Deploy Linux & Windows
|
||||||
|
if: tag IS present
|
||||||
|
os: linux
|
||||||
|
services: docker
|
||||||
|
script:
|
||||||
|
- docker run --rm --env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_') -v ${PWD}:/project -v ~/.cache/electron:/root/.cache/electron -v ~/.cache/electron-builder:/root/.cache/electron-builder electronuserland/builder:wine /bin/bash -c "npm run build -- --linux --win -p always"
|
||||||
|
before_cache:
|
||||||
|
- rm -rf $HOME/.cache/electron-builder/wine
|
||||||
|
- stage: Deploy Mac
|
||||||
|
if: tag IS present
|
||||||
|
os: osx
|
||||||
|
osx_image: xcode10.2
|
||||||
|
script:
|
||||||
|
- npm run build -- -p always
|
45
CHANGELOG.md
45
CHANGELOG.md
@@ -1,7 +1,48 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## [0.0.1-alpha]() - Coming Soon
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||||
|
|
||||||
|
### [0.0.6](https://github.com/EStarium/antares/compare/v0.0.5...v0.0.6) (2020-09-03)
|
||||||
|
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
|
||||||
- **Initial release:**
|
* aliases support ([264de9c](https://github.com/EStarium/antares/commit/264de9c5686fb3a2ef22d96171f45b915ba1b34b))
|
||||||
|
* middle click to close tabs ([256ec76](https://github.com/EStarium/antares/commit/256ec765883fcf247355190827e943c76e95f13b))
|
||||||
|
* monaco-editor as query editor ([196a3e0](https://github.com/EStarium/antares/commit/196a3e0185a3d68b7c4ade8dbf187d2b216cc00b))
|
||||||
|
* sql suggestions in query editor ([8dc74ef](https://github.com/EStarium/antares/commit/8dc74ef2c335e8ae4a69f5d2651df65939139b1b))
|
||||||
|
* support to multiple query tabs ([d7ed00f](https://github.com/EStarium/antares/commit/d7ed00f4a3613da9015c9fc48c4d8062d292e416))
|
||||||
|
* tabs horizontal scroll with mouse wheel ([3a6ea76](https://github.com/EStarium/antares/commit/3a6ea76b93682ebd50908df7368c62c2c1e27958))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* error when launching queries without a result from query tabs ([a1a6f51](https://github.com/EStarium/antares/commit/a1a6f51f2fba5140f5e3bd9cd6557c8a13dfaa2c))
|
||||||
|
* field name displayed instead of alias ([801a0de](https://github.com/EStarium/antares/commit/801a0de1865dea2a59ff057b7c2cc988cc9c87ed))
|
||||||
|
* wrong table height calc in some cases ([fd6d517](https://github.com/EStarium/antares/commit/fd6d5177efb6161aab01f9e108eda60df6c7d8c4))
|
||||||
|
|
||||||
|
### [0.0.5](https://github.com/EStarium/antares/compare/v0.0.4...v0.0.5) (2020-08-17)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Badge on setting icon and update tab when update is available ([e8141b6](https://github.com/EStarium/antares/commit/e8141b632154f765ca73fa50b9b7120dc592ead0))
|
||||||
|
* Foreign key support in add/edit row ([0b6a188](https://github.com/EStarium/antares/commit/0b6a188d1959b80b4a66946cc79d2dd3853a428b))
|
||||||
|
* Option to insert table rows ([2f1dfdc](https://github.com/EStarium/antares/commit/2f1dfdc6543b4a6c1d595f0daa00c0832be49c77))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Insert files via add row option ([3c6e818](https://github.com/EStarium/antares/commit/3c6e818ba06f1b8b5db0ecf80c3b7498d6d2a841))
|
||||||
|
* Newline replaced with undefined inside queries ([59e4a79](https://github.com/EStarium/antares/commit/59e4a79f42076b3fce98a764e9ad6a01c674555b))
|
||||||
|
* Query result header didn't show just selected fields ([7bc1009](https://github.com/EStarium/antares/commit/7bc10092fe4823e03133e69e0a7bf86e44fde43b))
|
||||||
|
* Table header not fixed on top when fast scrolling ([13b0816](https://github.com/EStarium/antares/commit/13b0816837461119eaab79fdb7e92223e0950630))
|
||||||
|
* Time and datetime precision ([771f8a2](https://github.com/EStarium/antares/commit/771f8a2d682c64105231e3fef199f05150596298))
|
||||||
|
* Update a row with a string key value ([eb348b3](https://github.com/EStarium/antares/commit/eb348b3095b6905321b62eed6cea228374ebc3d1))
|
||||||
|
* Window title not perfectly centered ([7651d05](https://github.com/EStarium/antares/commit/7651d05b37970574d6ae4bdf75c20c69d59c1e6d))
|
||||||
|
* Wrong schema passed in query tab when a different database was selected ([6d0724d](https://github.com/EStarium/antares/commit/6d0724dc90cdebb10e0342d2c472bdd07aa345f8))
|
||||||
|
|
||||||
|
### [0.0.4](https://github.com/EStarium/antares/compare/v0.0.3-alpha...v0.0.4) (2020-08-06)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Blob fields edit/view/download ([712fe9f](https://github.com/EStarium/antares/commit/712fe9f00d210db0f2317eca61e7fb648383e3fe))
|
||||||
|
* Window title in app title bar ([0089c0c](https://github.com/EStarium/antares/commit/0089c0cbac6caf0a6fd195849099f18713580228))
|
||||||
|
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015-2017 Jakub Szwacz
|
Copyright (c) 2020 Fabio Di Stasio
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
70
README.md
70
README.md
@@ -1,7 +1,71 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<img width="256" src="https://raw.githubusercontent.com/Fabio286/antares/master/docs/logo.png">
|
<img width="800" src="https://raw.githubusercontent.com/Fabio286/antares/master/docs/screen-alpha.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# Antares
|
# Antares SQL Client
|
||||||
|
|
||||||
🚧 Work in progress! 🚧
|
 [](https://travis-ci.com/EStarium/antares)  
|
||||||
|
|
||||||
|
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
|
||||||
|
My target is to support as many databases as possible, and all major operating systems, including the ARM versions.
|
||||||
|
|
||||||
|
**At the moment this application is an alpha, it lacks many features, and isn't ready as a main SQL client**. However i'm actively working on it, hoping to provide all essential features as soon as possible.
|
||||||
|
|
||||||
|
If you are curious to try this early state of Antares you can download and install the [latest release](https://github.com/EStarium/antares/releases), and stay tuned for updates.
|
||||||
|
|
||||||
|
## Philosophy
|
||||||
|
|
||||||
|
Why am I developing an SQL client when there are a lot of them on the market?
|
||||||
|
The main goal is to develop a totally free, cross platform and open source alternative, empowered by JavaScript's ecosystem.
|
||||||
|
An application created with minimalism and semplicity in mind, with features in the righ places, not hundreds of tiny buttons or submenu.
|
||||||
|
|
||||||
|
## How to contribute
|
||||||
|
|
||||||
|
- [Translate Antares](https://github.com/EStarium/antares/wiki/Translate-Antares)
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
|
||||||
|
This is a roadmap with major features will come in near future.
|
||||||
|
|
||||||
|
- Improvements of query editor area.
|
||||||
|
- Multiple query tabs.
|
||||||
|
- Tables management (add/edit/delete).
|
||||||
|
- Stored procedures, views, schedulers and trigger support.
|
||||||
|
- Database tools.
|
||||||
|
- Context menu shortcuts.
|
||||||
|
- Keyboard shortcuts.
|
||||||
|
- More secure password storage.
|
||||||
|
- Query logs console.
|
||||||
|
- Fake data filler.
|
||||||
|
- Import/export and migration.
|
||||||
|
- Themes.
|
||||||
|
|
||||||
|
## Currently supported
|
||||||
|
|
||||||
|
### Databases
|
||||||
|
|
||||||
|
- [x] MySQL/MariaDB
|
||||||
|
- [ ] PostrgreSQL
|
||||||
|
- [ ] MSSQL
|
||||||
|
- [ ] SQLite
|
||||||
|
- [ ] OracleDB
|
||||||
|
- [ ] More...
|
||||||
|
|
||||||
|
### Operating Systems
|
||||||
|
|
||||||
|
#### • x86
|
||||||
|
|
||||||
|
- [x] Windows
|
||||||
|
- [x] Linux
|
||||||
|
- [x] MacOS (needs tests)
|
||||||
|
|
||||||
|
#### • ARM
|
||||||
|
|
||||||
|
- [ ] Windows
|
||||||
|
- [ ] Linux
|
||||||
|
- [ ] MacOS
|
||||||
|
|
||||||
|
## Translations
|
||||||
|
|
||||||
|
[Giuseppe Gigliotti](https://github.com/ReverbOD) / [Italian Translation](https://github.com/EStarium/antares/pull/20)
|
||||||
|
[Mohd-PH](https://github.com/Mohd-PH) / [Arabic Translation](https://github.com/EStarium/antares/pull/29)
|
||||||
|
BIN
build/icon.png
BIN
build/icon.png
Binary file not shown.
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 25 KiB |
BIN
docs/screen-alpha.png
Normal file
BIN
docs/screen-alpha.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 224 KiB |
13749
package-lock.json
generated
13749
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
82
package.json
82
package.json
@@ -1,69 +1,91 @@
|
|||||||
{
|
{
|
||||||
"name": "antares",
|
"name": "antares",
|
||||||
"productName": "Antares",
|
"productName": "Antares",
|
||||||
"version": "0.0.1-alpha",
|
"version": "0.0.6",
|
||||||
"description": "A cross-platform easy to use SQL client.",
|
"description": "A cross-platform easy to use SQL client.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "https://github.com/Fabio286/antares.git",
|
"repository": "https://github.com/EStarium/antares.git",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env NODE_ENV=development electron-webpack dev",
|
"dev": "cross-env NODE_ENV=development electron-webpack dev",
|
||||||
"compile": "electron-webpack",
|
"compile": "electron-webpack",
|
||||||
"dist": "cross-env NODE_ENV=production npm run compile && electron-builder",
|
"build": "cross-env NODE_ENV=production npm run compile && electron-builder",
|
||||||
"dist:dir": "cross-env NODE_ENV=production npm run dist --dir -c.compression=store -c.mac.identity=null",
|
"release": "standard-version",
|
||||||
"publish": "build -p always"
|
"release:pre": "npm run release -- --prerelease alpha",
|
||||||
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"author": "Fabio Di Stasio <fabio286@gmail.com>",
|
"author": "Fabio Di Stasio <fabio286@gmail.com>",
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.estarium.antares",
|
"appId": "com.estarium.antares",
|
||||||
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
|
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
|
||||||
"files": [
|
"dmg": {
|
||||||
"static/*"
|
"contents": [
|
||||||
]
|
{
|
||||||
|
"x": 130,
|
||||||
|
"y": 220
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": 410,
|
||||||
|
"y": 220,
|
||||||
|
"type": "link",
|
||||||
|
"path": "/Applications"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"target": [
|
||||||
|
"deb",
|
||||||
|
"AppImage"
|
||||||
|
],
|
||||||
|
"category": "Development"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"electronWebpack": {
|
"electronWebpack": {
|
||||||
"whiteListedModules": [
|
|
||||||
"codemirror"
|
|
||||||
],
|
|
||||||
"renderer": {
|
"renderer": {
|
||||||
"webpackConfig": "webpack.config.js"
|
"webpackConfig": "webpack.config.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"codemirror": "^5.54.0",
|
"@mdi/font": "^5.5.55",
|
||||||
"electron-log": "^4.2.1",
|
"electron-log": "^4.2.4",
|
||||||
"electron-updater": "^4.3.1",
|
"electron-updater": "^4.3.4",
|
||||||
"lodash": "^4.17.15",
|
"lodash": "^4.17.20",
|
||||||
"material-design-icons": "^3.0.1",
|
"moment": "^2.27.0",
|
||||||
"moment": "^2.26.0",
|
"monaco-editor": "^0.20.0",
|
||||||
"mssql": "^6.2.0",
|
"mssql": "^6.2.1",
|
||||||
"mysql": "^2.18.1",
|
"mysql": "^2.18.1",
|
||||||
"pg": "^8.2.1",
|
"pg": "^8.3.2",
|
||||||
"source-map-support": "^0.5.16",
|
"source-map-support": "^0.5.16",
|
||||||
"spectre.css": "^0.5.8",
|
"spectre.css": "^0.5.9",
|
||||||
"vue-click-outside": "^1.1.0",
|
"vue-click-outside": "^1.1.0",
|
||||||
"vue-i18n": "^8.18.2",
|
"vue-i18n": "^8.21.0",
|
||||||
"vuedraggable": "^2.23.2",
|
"vue-the-mask": "^0.11.1",
|
||||||
"vuex": "^3.4.0",
|
"vuedraggable": "^2.24.0",
|
||||||
|
"vuex": "^3.5.1",
|
||||||
"vuex-persist": "^2.2.0"
|
"vuex-persist": "^2.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
"electron": "^8.3.0",
|
"electron": "^10.1.0",
|
||||||
"electron-builder": "^22.7.0",
|
"electron-builder": "^22.8.0",
|
||||||
"electron-devtools-installer": "^3.0.0",
|
"electron-devtools-installer": "^3.1.1",
|
||||||
"electron-webpack": "^2.8.2",
|
"electron-webpack": "^2.8.2",
|
||||||
"electron-webpack-vue": "^2.4.0",
|
"electron-webpack-vue": "^2.4.0",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^7.7.0",
|
||||||
"eslint-config-standard": "^14.1.1",
|
"eslint-config-standard": "^14.1.1",
|
||||||
"eslint-plugin-import": "^2.21.1",
|
"eslint-plugin-import": "^2.22.0",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"eslint-plugin-standard": "^4.0.1",
|
"eslint-plugin-standard": "^4.0.1",
|
||||||
"eslint-plugin-vue": "^6.2.2",
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
|
"monaco-editor-webpack-plugin": "^1.9.0",
|
||||||
"node-sass": "^4.14.1",
|
"node-sass": "^4.14.1",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^10.0.1",
|
||||||
|
"standard-version": "^9.0.0",
|
||||||
|
"stylelint": "^13.6.1",
|
||||||
|
"stylelint-config-standard": "^20.0.0",
|
||||||
|
"stylelint-scss": "^3.18.0",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"webpack": "^4.43.0"
|
"webpack": "^4.44.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
12
src/common/fieldTypes.js
Normal file
12
src/common/fieldTypes.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
export const TEXT = ['char', 'varchar'];
|
||||||
|
export const LONG_TEXT = ['text', 'mediumtext', 'longtext'];
|
||||||
|
|
||||||
|
export const NUMBER = ['int', 'tinyint', 'smallint', 'mediumint', 'bigint', 'float', 'double', 'decimal'];
|
||||||
|
|
||||||
|
export const DATE = ['date'];
|
||||||
|
export const TIME = ['time'];
|
||||||
|
export const DATETIME = ['datetime', 'timestamp'];
|
||||||
|
|
||||||
|
export const BLOB = ['blob', 'mediumblob', 'longblob'];
|
||||||
|
|
||||||
|
export const BIT = ['bit'];
|
7
src/common/libs/bufferToBase64.js
Normal file
7
src/common/libs/bufferToBase64.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
'use strict';
|
||||||
|
export function bufferToBase64 (buf) {
|
||||||
|
const binstr = Array.prototype.map.call(buf, ch => {
|
||||||
|
return String.fromCharCode(ch);
|
||||||
|
}).join('');
|
||||||
|
return btoa(binstr);
|
||||||
|
}
|
12
src/common/libs/formatBytes.js
Normal file
12
src/common/libs/formatBytes.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
'use strict';
|
||||||
|
export function formatBytes (bytes, decimals = 2) {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
|
||||||
|
const k = 1024;
|
||||||
|
const dm = decimals < 0 ? 0 : decimals;
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
|
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||||
|
}
|
@@ -1,13 +1,10 @@
|
|||||||
export function uidGen () {
|
'use strict';
|
||||||
return Math.random().toString(36).substr(2, 9).toUpperCase();
|
|
||||||
};
|
|
||||||
|
|
||||||
export function mimeFromHex (hex) {
|
export function mimeFromHex (hex) {
|
||||||
switch (hex.substring(0, 4)) { // 2 bytes
|
switch (hex.substring(0, 4)) { // 2 bytes
|
||||||
case '424D':
|
case '424D':
|
||||||
return { ext: 'bmp', mime: 'image/bmp' };
|
return { ext: 'bmp', mime: 'image/bmp' };
|
||||||
case '1F8B':
|
case '1F8B':
|
||||||
return { ext: 'gz', mime: 'application/gzip' };
|
return { ext: 'tar.gz', mime: 'application/gzip' };
|
||||||
case '0B77':
|
case '0B77':
|
||||||
return { ext: 'ac3', mime: 'audio/vnd.dolby.dd-raw' };
|
return { ext: 'ac3', mime: 'audio/vnd.dolby.dd-raw' };
|
||||||
case '7801':
|
case '7801':
|
||||||
@@ -20,7 +17,7 @@ export function mimeFromHex (hex) {
|
|||||||
default:
|
default:
|
||||||
switch (hex.substring(0, 6)) { // 3 bytes
|
switch (hex.substring(0, 6)) { // 3 bytes
|
||||||
case 'FFD8FF':
|
case 'FFD8FF':
|
||||||
return { ext: 'jpj', mime: 'image/jpeg' };
|
return { ext: 'jpg', mime: 'image/jpeg' };
|
||||||
case '4949BC':
|
case '4949BC':
|
||||||
return { ext: 'jxr', mime: 'image/vnd.ms-photo' };
|
return { ext: 'jxr', mime: 'image/vnd.ms-photo' };
|
||||||
case '425A68':
|
case '425A68':
|
||||||
@@ -39,21 +36,11 @@ export function mimeFromHex (hex) {
|
|||||||
return { ext: 'bpg', mime: 'image/bpg' };
|
return { ext: 'bpg', mime: 'image/bpg' };
|
||||||
case '4D4D002A':
|
case '4D4D002A':
|
||||||
return { ext: 'tif', mime: 'image/tiff' };
|
return { ext: 'tif', mime: 'image/tiff' };
|
||||||
|
case '00000100':
|
||||||
|
return { ext: 'ico', mime: 'image/x-icon' };
|
||||||
default:
|
default:
|
||||||
return { ext: '???', mime: 'unknown ' + hex };
|
return { ext: '', mime: 'unknown ' + hex };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export function formatBytes (bytes, decimals = 2) {
|
|
||||||
if (bytes === 0) return '0 Bytes';
|
|
||||||
|
|
||||||
const k = 1024;
|
|
||||||
const dm = decimals < 0 ? 0 : decimals;
|
|
||||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
|
||||||
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
||||||
|
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
|
||||||
}
|
|
19
src/common/libs/sqlEscaper.js
Normal file
19
src/common/libs/sqlEscaper.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/* eslint-disable no-useless-escape */
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
const regex = new RegExp(/[\0\x08\x09\x1a\n\r"'\\\%]/gm);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escapes a string
|
||||||
|
*
|
||||||
|
* @param {String} string
|
||||||
|
* @returns {String}
|
||||||
|
*/
|
||||||
|
function sqlEscaper (string) {
|
||||||
|
return string.replace(regex, char => {
|
||||||
|
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '"', '\\', '\\\\', '%'];
|
||||||
|
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\'\'', '""', '\\\\', '\\\\\\\\', '\\%'];
|
||||||
|
return r[m.indexOf(char)] || char;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { sqlEscaper };
|
8
src/common/libs/uidGen.js
Normal file
8
src/common/libs/uidGen.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* @export
|
||||||
|
* @param {String} [prefix]
|
||||||
|
* @returns {String} Unique ID
|
||||||
|
*/
|
||||||
|
export function uidGen (prefix) {
|
||||||
|
return (prefix ? `${prefix}:` : '') + Math.random().toString(36).substr(2, 9).toUpperCase();
|
||||||
|
};
|
@@ -3,7 +3,6 @@
|
|||||||
import { app, BrowserWindow, nativeImage } from 'electron';
|
import { app, BrowserWindow, nativeImage } from 'electron';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { format as formatUrl } from 'url';
|
import { format as formatUrl } from 'url';
|
||||||
|
|
||||||
import ipcHandlers from './ipc-handlers';
|
import ipcHandlers from './ipc-handlers';
|
||||||
|
|
||||||
const isDevelopment = process.env.NODE_ENV !== 'production';
|
const isDevelopment = process.env.NODE_ENV !== 'production';
|
||||||
@@ -12,35 +11,29 @@ process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
|
|||||||
// global reference to mainWindow (necessary to prevent window from being garbage collected)
|
// global reference to mainWindow (necessary to prevent window from being garbage collected)
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
|
|
||||||
function createMainWindow () {
|
async function createMainWindow () {
|
||||||
const icon = require('../renderer/images/logo-32.png');
|
const icon = require('../renderer/images/logo-32.png');
|
||||||
const window = new BrowserWindow({
|
const window = new BrowserWindow({
|
||||||
width: 1600,
|
width: 1024,
|
||||||
height: 1000,
|
height: 800,
|
||||||
minHeight: 550,
|
|
||||||
minWidth: 900,
|
minWidth: 900,
|
||||||
|
minHeight: 550,
|
||||||
title: 'Antares',
|
title: 'Antares',
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
icon: nativeImage.createFromDataURL(icon.default),
|
icon: nativeImage.createFromDataURL(icon.default),
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
'web-security': false
|
'web-security': false,
|
||||||
|
enableRemoteModule: true,
|
||||||
|
spellcheck: false
|
||||||
},
|
},
|
||||||
frame: false,
|
frame: false,
|
||||||
backgroundColor: '#1d1d1d'
|
backgroundColor: '#1d1d1d'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isDevelopment)
|
|
||||||
window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`);
|
|
||||||
else {
|
|
||||||
window.loadURL(formatUrl({
|
|
||||||
pathname: path.join(__dirname, 'index.html'),
|
|
||||||
protocol: 'file',
|
|
||||||
slashes: true
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isDevelopment) {
|
if (isDevelopment) {
|
||||||
|
await window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`);
|
||||||
|
|
||||||
const { default: installExtension, VUEJS_DEVTOOLS } = require('electron-devtools-installer');
|
const { default: installExtension, VUEJS_DEVTOOLS } = require('electron-devtools-installer');
|
||||||
window.webContents.openDevTools();
|
window.webContents.openDevTools();
|
||||||
|
|
||||||
@@ -52,6 +45,13 @@ function createMainWindow () {
|
|||||||
console.log(err);
|
console.log(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
await window.loadURL(formatUrl({
|
||||||
|
pathname: path.join(__dirname, 'index.html'),
|
||||||
|
protocol: 'file',
|
||||||
|
slashes: true
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
window.on('closed', () => {
|
window.on('closed', () => {
|
||||||
mainWindow = null;
|
mainWindow = null;
|
||||||
@@ -64,12 +64,12 @@ function createMainWindow () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize ipcHandlers
|
|
||||||
ipcHandlers();
|
|
||||||
|
|
||||||
return window;
|
return window;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Initialize ipcHandlers
|
||||||
|
ipcHandlers();
|
||||||
|
|
||||||
// quit application when all windows are closed
|
// quit application when all windows are closed
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
// on macOS it is common for applications to stay open until the user explicitly quits
|
// on macOS it is common for applications to stay open until the user explicitly quits
|
||||||
|
7
src/main/ipc-handlers/application.js
Normal file
7
src/main/ipc-handlers/application.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { app, ipcMain } from 'electron';
|
||||||
|
|
||||||
|
export default () => {
|
||||||
|
ipcMain.on('close-app', () => {
|
||||||
|
app.exit();
|
||||||
|
});
|
||||||
|
};
|
@@ -4,8 +4,8 @@ import { AntaresConnector } from '../libs/AntaresConnector';
|
|||||||
import InformationSchema from '../models/InformationSchema';
|
import InformationSchema from '../models/InformationSchema';
|
||||||
import Generic from '../models/Generic';
|
import Generic from '../models/Generic';
|
||||||
|
|
||||||
export default (connections) => {
|
export default connections => {
|
||||||
ipcMain.handle('testConnection', async (event, conn) => {
|
ipcMain.handle('test-connection', async (event, conn) => {
|
||||||
const Connection = new AntaresConnector({
|
const Connection = new AntaresConnector({
|
||||||
client: conn.client,
|
client: conn.client,
|
||||||
params: {
|
params: {
|
||||||
@@ -28,7 +28,7 @@ export default (connections) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('checkConnection', async (event, uid) => {
|
ipcMain.handle('check-connection', async (event, uid) => {
|
||||||
return uid in connections;
|
return uid in connections;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ export default (connections) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('rawQuery', async (event, { uid, query, schema }) => {
|
ipcMain.handle('raw-query', async (event, { uid, query, schema }) => {
|
||||||
if (!query) return;
|
if (!query) return;
|
||||||
try {
|
try {
|
||||||
const result = await Generic.raw(connections[uid], query, schema);
|
const result = await Generic.raw(connections[uid], query, schema);
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
import connection from './connection';
|
import connection from './connection';
|
||||||
import structure from './structure';
|
import tables from './tables';
|
||||||
import updates from './updates';
|
import updates from './updates';
|
||||||
|
import application from './application';
|
||||||
|
|
||||||
const connections = {};
|
const connections = {};
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
connection(connections);
|
connection(connections);
|
||||||
structure(connections);
|
tables(connections);
|
||||||
updates();
|
updates();
|
||||||
|
application();
|
||||||
};
|
};
|
||||||
|
@@ -1,27 +0,0 @@
|
|||||||
import { ipcMain } from 'electron';
|
|
||||||
import InformationSchema from '../models/InformationSchema';
|
|
||||||
import Generic from '../models/Generic';
|
|
||||||
|
|
||||||
// TODO: remap objects based on client
|
|
||||||
|
|
||||||
export default (connections) => {
|
|
||||||
ipcMain.handle('getTableColumns', async (event, { uid, schema, table }) => {
|
|
||||||
try {
|
|
||||||
const result = await InformationSchema.getTableColumns(connections[uid], schema, table);
|
|
||||||
return { status: 'success', response: result };
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
return { status: 'error', response: err.toString() };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle('getTableData', async (event, { uid, schema, table }) => {
|
|
||||||
try {
|
|
||||||
const result = await Generic.getTableData(connections[uid], schema, table);
|
|
||||||
return { status: 'success', response: result };
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
return { status: 'error', response: err.toString() };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
77
src/main/ipc-handlers/tables.js
Normal file
77
src/main/ipc-handlers/tables.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { ipcMain } from 'electron';
|
||||||
|
import InformationSchema from '../models/InformationSchema';
|
||||||
|
import Tables from '../models/Tables';
|
||||||
|
|
||||||
|
// TODO: remap objects based on client
|
||||||
|
|
||||||
|
export default (connections) => {
|
||||||
|
ipcMain.handle('get-table-columns', async (event, { uid, schema, table }) => {
|
||||||
|
try {
|
||||||
|
const result = await InformationSchema.getTableColumns(connections[uid], schema, table);// TODO: uniform column properties
|
||||||
|
return { status: 'success', response: result };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-table-data', async (event, { uid, schema, table }) => {
|
||||||
|
try {
|
||||||
|
const result = await Tables.getTableData(connections[uid], schema, table);
|
||||||
|
return { status: 'success', response: result };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-key-usage', async (event, { uid, schema, table }) => {
|
||||||
|
try {
|
||||||
|
const result = await InformationSchema.getKeyUsage(connections[uid], schema, table);
|
||||||
|
return { status: 'success', response: result };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('update-table-cell', async (event, params) => {
|
||||||
|
try {
|
||||||
|
const result = await Tables.updateTableCell(connections[params.uid], params);
|
||||||
|
return { status: 'success', response: result };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('delete-table-rows', async (event, params) => {
|
||||||
|
try {
|
||||||
|
const result = await Tables.deleteTableRows(connections[params.uid], params);
|
||||||
|
return { status: 'success', response: result };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('insert-table-rows', async (event, params) => {
|
||||||
|
try {
|
||||||
|
await Tables.insertTableRows(connections[params.uid], params);
|
||||||
|
return { status: 'success' };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-foreign-list', async (event, params) => {
|
||||||
|
try {
|
||||||
|
const results = await Tables.getForeignList(connections[params.uid], params);
|
||||||
|
return { status: 'success', response: results };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
@@ -2,39 +2,40 @@ import { ipcMain } from 'electron';
|
|||||||
import { autoUpdater } from 'electron-updater';
|
import { autoUpdater } from 'electron-updater';
|
||||||
|
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
|
autoUpdater.allowPrerelease = true;
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
ipcMain.on('checkForUpdates', event => {
|
ipcMain.on('check-for-updates', event => {
|
||||||
mainWindow = event;
|
mainWindow = event;
|
||||||
|
|
||||||
autoUpdater.checkForUpdatesAndNotify().catch(() => {
|
autoUpdater.checkForUpdatesAndNotify().catch(() => {
|
||||||
mainWindow.reply('checkFailed');
|
mainWindow.reply('check-failed');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.on('restartToUpdate', () => {
|
ipcMain.on('restart-to-update', () => {
|
||||||
autoUpdater.quitAndInstall();
|
autoUpdater.quitAndInstall();
|
||||||
});
|
});
|
||||||
|
|
||||||
// auto-updater events
|
// auto-updater events
|
||||||
autoUpdater.on('checking-for-update', () => {
|
autoUpdater.on('checking-for-update', () => {
|
||||||
mainWindow.reply('checkingForUpdate');
|
mainWindow.reply('checking-for-update');
|
||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.on('update-available', () => {
|
autoUpdater.on('update-available', () => {
|
||||||
mainWindow.reply('updateAvailable');
|
mainWindow.reply('update-available');
|
||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.on('update-not-available', () => {
|
autoUpdater.on('update-not-available', () => {
|
||||||
mainWindow.reply('updateNotAvailable');
|
mainWindow.reply('update-not-available');
|
||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.on('download-progress', (data) => {
|
autoUpdater.on('download-progress', data => {
|
||||||
mainWindow.reply('downloadProgress', data);
|
mainWindow.reply('download-progress', data);
|
||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.on('update-downloaded', () => {
|
autoUpdater.on('update-downloaded', () => {
|
||||||
mainWindow.reply('updateDownloaded');
|
mainWindow.reply('update-downloaded');
|
||||||
});
|
});
|
||||||
|
|
||||||
autoUpdater.logger = require('electron-log');
|
autoUpdater.logger = require('electron-log');
|
||||||
|
@@ -20,6 +20,7 @@ export class AntaresConnector {
|
|||||||
this._params = args.params;
|
this._params = args.params;
|
||||||
this._poolSize = args.poolSize || false;
|
this._poolSize = args.poolSize || false;
|
||||||
this._connection = null;
|
this._connection = null;
|
||||||
|
this._logger = args.logger || console.log;
|
||||||
|
|
||||||
this._queryDefaults = {
|
this._queryDefaults = {
|
||||||
schema: '',
|
schema: '',
|
||||||
@@ -31,8 +32,8 @@ export class AntaresConnector {
|
|||||||
limit: [],
|
limit: [],
|
||||||
join: [],
|
join: [],
|
||||||
update: [],
|
update: [],
|
||||||
insert: [],
|
insert: {},
|
||||||
delete: []
|
delete: false
|
||||||
};
|
};
|
||||||
this._query = Object.assign({}, this._queryDefaults);
|
this._query = Object.assign({}, this._queryDefaults);
|
||||||
}
|
}
|
||||||
@@ -73,14 +74,10 @@ export class AntaresConnector {
|
|||||||
switch (this._client) {
|
switch (this._client) {
|
||||||
case 'maria':
|
case 'maria':
|
||||||
case 'mysql':
|
case 'mysql':
|
||||||
if (!this._poolSize) {
|
if (!this._poolSize)
|
||||||
const connection = mysql.createConnection(this._params);
|
this._connection = mysql.createConnection(this._params);
|
||||||
this._connection = connection.promise();
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
this._connection = mysql.createPool({ ...this._params, connectionLimit: this._poolSize });
|
this._connection = mysql.createPool({ ...this._params, connectionLimit: this._poolSize });
|
||||||
// this._connection = pool.promise();
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'mssql': {
|
case 'mssql': {
|
||||||
const mssqlParams = {
|
const mssqlParams = {
|
||||||
@@ -111,6 +108,17 @@ export class AntaresConnector {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
into (table) {
|
||||||
|
this._query.from = table;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
delete (table) {
|
||||||
|
this._query.delete = true;
|
||||||
|
this.from(table);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
where (...args) {
|
where (...args) {
|
||||||
this._query.where = [...this._query.where, ...args];
|
this._query.where = [...this._query.where, ...args];
|
||||||
return this;
|
return this;
|
||||||
@@ -149,6 +157,26 @@ export class AntaresConnector {
|
|||||||
return this.raw(sql);
|
return this.raw(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String | Array} args field = value
|
||||||
|
* @returns
|
||||||
|
* @memberof AntaresConnector
|
||||||
|
*/
|
||||||
|
update (...args) {
|
||||||
|
this._query.update = [...this._query.update, ...args];
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} obj field: value
|
||||||
|
* @returns
|
||||||
|
* @memberof AntaresConnector
|
||||||
|
*/
|
||||||
|
insert (obj) {
|
||||||
|
this._query.insert = { ...this._query.insert, ...obj };
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {string} SQL string
|
* @returns {string} SQL string
|
||||||
* @memberof AntaresConnector
|
* @memberof AntaresConnector
|
||||||
@@ -156,30 +184,37 @@ export class AntaresConnector {
|
|||||||
getSQL () {
|
getSQL () {
|
||||||
// SELECT
|
// SELECT
|
||||||
const selectArray = this._query.select.reduce(this._reducer, []);
|
const selectArray = this._query.select.reduce(this._reducer, []);
|
||||||
let selectRaw;
|
let selectRaw = '';
|
||||||
switch (this._client) {
|
if (selectArray.length) {
|
||||||
case 'maria':
|
switch (this._client) {
|
||||||
case 'mysql':
|
case 'maria':
|
||||||
selectRaw = selectArray.length ? `SELECT ${selectArray.join(', ')} ` : 'SELECT * ';
|
case 'mysql':
|
||||||
break;
|
selectRaw = selectArray.length ? `SELECT ${selectArray.join(', ')} ` : 'SELECT * ';
|
||||||
case 'mssql': {
|
break;
|
||||||
const topRaw = this._query.limit.length ? ` TOP (${this._query.limit[0]}) ` : '';
|
case 'mssql': {
|
||||||
selectRaw = selectArray.length ? `SELECT${topRaw} ${selectArray.join(', ')} ` : 'SELECT * ';
|
const topRaw = this._query.limit.length ? ` TOP (${this._query.limit[0]}) ` : '';
|
||||||
|
selectRaw = selectArray.length ? `SELECT${topRaw} ${selectArray.join(', ')} ` : 'SELECT * ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FROM
|
// FROM
|
||||||
let fromRaw;
|
let fromRaw = '';
|
||||||
|
if (!this._query.update.length && !Object.keys(this._query.insert).length && !!this._query.from)
|
||||||
|
fromRaw = 'FROM';
|
||||||
|
else if (Object.keys(this._query.insert).length)
|
||||||
|
fromRaw = 'INTO';
|
||||||
|
|
||||||
switch (this._client) {
|
switch (this._client) {
|
||||||
case 'maria':
|
case 'maria':
|
||||||
case 'mysql':
|
case 'mysql':
|
||||||
fromRaw = this._query.from ? `FROM ${this._query.schema ? `\`${this._query.schema}\`.` : ''}\`${this._query.from}\` ` : '';
|
fromRaw += this._query.from ? ` ${this._query.schema ? `\`${this._query.schema}\`.` : ''}\`${this._query.from}\` ` : '';
|
||||||
break;
|
break;
|
||||||
case 'mssql':
|
case 'mssql':
|
||||||
fromRaw = this._query.from ? `FROM ${this._query.schema ? `${this._query.schema}.` : ''}${this._query.from} ` : '';
|
fromRaw += this._query.from ? ` ${this._query.schema ? `${this._query.schema}.` : ''}${this._query.from} ` : '';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@@ -187,8 +222,28 @@ export class AntaresConnector {
|
|||||||
|
|
||||||
const whereArray = this._query.where.reduce(this._reducer, []);
|
const whereArray = this._query.where.reduce(this._reducer, []);
|
||||||
const whereRaw = whereArray.length ? `WHERE ${whereArray.join(' AND ')} ` : '';
|
const whereRaw = whereArray.length ? `WHERE ${whereArray.join(' AND ')} ` : '';
|
||||||
|
|
||||||
|
const updateArray = this._query.update.reduce(this._reducer, []);
|
||||||
|
const updateRaw = updateArray.length ? `SET ${updateArray.join(', ')} ` : '';
|
||||||
|
|
||||||
|
let insertRaw = '';
|
||||||
|
if (Object.keys(this._query.insert).length) {
|
||||||
|
const fieldsList = [];
|
||||||
|
const valueList = [];
|
||||||
|
const fields = this._query.insert;
|
||||||
|
|
||||||
|
for (const key in fields) {
|
||||||
|
if (fields[key] === null) continue;
|
||||||
|
fieldsList.push(key);
|
||||||
|
valueList.push(fields[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
insertRaw = `(${fieldsList.join(', ')}) VALUES (${valueList.join(', ')}) `;
|
||||||
|
}
|
||||||
|
|
||||||
const groupByArray = this._query.groupBy.reduce(this._reducer, []);
|
const groupByArray = this._query.groupBy.reduce(this._reducer, []);
|
||||||
const groupByRaw = groupByArray.length ? `GROUP BY ${groupByArray.join(', ')} ` : '';
|
const groupByRaw = groupByArray.length ? `GROUP BY ${groupByArray.join(', ')} ` : '';
|
||||||
|
|
||||||
const orderByArray = this._query.orderBy.reduce(this._reducer, []);
|
const orderByArray = this._query.orderBy.reduce(this._reducer, []);
|
||||||
const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : '';
|
const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : '';
|
||||||
|
|
||||||
@@ -206,7 +261,7 @@ export class AntaresConnector {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `${selectRaw}${fromRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}`;
|
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${insertRaw}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -225,20 +280,25 @@ export class AntaresConnector {
|
|||||||
* @memberof AntaresConnector
|
* @memberof AntaresConnector
|
||||||
*/
|
*/
|
||||||
async raw (sql) {
|
async raw (sql) {
|
||||||
if (process.env.NODE_ENV === 'development') console.log(sql);
|
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
|
||||||
|
|
||||||
switch (this._client) {
|
switch (this._client) { // TODO: uniform fields with every client type, needed table name and fields array
|
||||||
case 'maria':
|
case 'maria':
|
||||||
case 'mysql': {
|
case 'mysql': {
|
||||||
const { rows, fields } = await new Promise((resolve, reject) => {
|
const { rows, report, fields } = await new Promise((resolve, reject) => {
|
||||||
this._connection.query(sql, (err, rows, fields) => {
|
this._connection.query(sql, (err, response, fields) => {
|
||||||
if (err)
|
if (err)
|
||||||
reject(err);
|
reject(err);
|
||||||
else
|
else {
|
||||||
resolve({ rows, fields });
|
resolve({
|
||||||
|
rows: Array.isArray(response) ? response : false,
|
||||||
|
report: !Array.isArray(response) ? response : false,
|
||||||
|
fields
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
return { rows, fields };
|
return { rows, report, fields };
|
||||||
}
|
}
|
||||||
case 'mssql': {
|
case 'mssql': {
|
||||||
const results = await this._connection.request().query(sql);
|
const results = await this._connection.request().query(sql);
|
||||||
|
@@ -11,13 +11,4 @@ export default class {
|
|||||||
}
|
}
|
||||||
return connection.raw(query);
|
return connection.raw(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getTableData (connection, schema, table) {
|
|
||||||
return connection
|
|
||||||
.select('*')
|
|
||||||
.schema(schema)
|
|
||||||
.from(table)
|
|
||||||
.limit(1000)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -13,13 +13,52 @@ export default class {
|
|||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
static getTableColumns (connection, schema, table) {
|
static async getTableColumns (connection, schema, table) {
|
||||||
return connection
|
const { rows } = await connection
|
||||||
.select('*')
|
.select('*')
|
||||||
.schema('information_schema')
|
.schema('information_schema')
|
||||||
.from('COLUMNS')
|
.from('COLUMNS')
|
||||||
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` })
|
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` })
|
||||||
.orderBy({ ORDINAL_POSITION: 'ASC' })
|
.orderBy({ ORDINAL_POSITION: 'ASC' })
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
|
return rows.map(field => {
|
||||||
|
return {
|
||||||
|
name: field.COLUMN_NAME,
|
||||||
|
key: field.COLUMN_KEY.toLowerCase(),
|
||||||
|
type: field.DATA_TYPE,
|
||||||
|
numPrecision: field.NUMERIC_PRECISION,
|
||||||
|
datePrecision: field.DATETIME_PRECISION,
|
||||||
|
charLength: field.CHARACTER_MAXIMUM_LENGTH,
|
||||||
|
isNullable: field.IS_NULLABLE,
|
||||||
|
default: field.COLUMN_DEFAULT,
|
||||||
|
charset: field.CHARACTER_SET_NAME,
|
||||||
|
collation: field.COLLATION_NAME,
|
||||||
|
autoIncrement: field.EXTRA.includes('auto_increment')
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getKeyUsage (connection, schema, table) {
|
||||||
|
const { rows } = await connection
|
||||||
|
.select('*')
|
||||||
|
.schema('information_schema')
|
||||||
|
.from('KEY_COLUMN_USAGE')
|
||||||
|
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' })
|
||||||
|
.run();
|
||||||
|
|
||||||
|
return rows.map(field => {
|
||||||
|
return {
|
||||||
|
schema: field.TABLE_SCHEMA,
|
||||||
|
table: field.TABLE_NAME,
|
||||||
|
column: field.COLUMN_NAME,
|
||||||
|
position: field.ORDINAL_POSITION,
|
||||||
|
constraintPosition: field.POSITION_IN_UNIQUE_CONSTRAINT,
|
||||||
|
constraintName: field.CONSTRAINT_NAME,
|
||||||
|
refSchema: field.REFERENCED_TABLE_SCHEMA,
|
||||||
|
refTable: field.REFERENCED_TABLE_NAME,
|
||||||
|
refColumn: field.REFERENCED_COLUMN_NAME
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
102
src/main/models/Tables.js
Normal file
102
src/main/models/Tables.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
'use strict';
|
||||||
|
import { sqlEscaper } from 'common/libs/sqlEscaper';
|
||||||
|
import { TEXT, LONG_TEXT, NUMBER, BLOB } from 'common/fieldTypes';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
export default class {
|
||||||
|
static async getTableData (connection, schema, table) {
|
||||||
|
return connection
|
||||||
|
.select('*')
|
||||||
|
.schema(schema)
|
||||||
|
.from(table)
|
||||||
|
.limit(1000)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async updateTableCell (connection, params) {
|
||||||
|
let escapedParam;
|
||||||
|
let reload = false;
|
||||||
|
const id = typeof params.id === 'number' ? params.id : `"${params.id}"`;
|
||||||
|
|
||||||
|
if (NUMBER.includes(params.type))
|
||||||
|
escapedParam = params.content;
|
||||||
|
else if ([...TEXT, ...LONG_TEXT].includes(params.type))
|
||||||
|
escapedParam = `"${sqlEscaper(params.content)}"`;
|
||||||
|
else if (BLOB.includes(params.type)) {
|
||||||
|
if (params.content) {
|
||||||
|
const fileBlob = fs.readFileSync(params.content);
|
||||||
|
escapedParam = `0x${fileBlob.toString('hex')}`;
|
||||||
|
reload = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
escapedParam = '""';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
escapedParam = `"${sqlEscaper(params.content)}"`;
|
||||||
|
|
||||||
|
await connection
|
||||||
|
.update({ [params.field]: `= ${escapedParam}` })
|
||||||
|
.schema(params.schema)
|
||||||
|
.from(params.table)
|
||||||
|
.where({ [params.primary]: `= ${id}` })
|
||||||
|
.run();
|
||||||
|
|
||||||
|
return { reload };
|
||||||
|
}
|
||||||
|
|
||||||
|
static async deleteTableRows (connection, params) {
|
||||||
|
return connection
|
||||||
|
.schema(params.schema)
|
||||||
|
.delete(params.table)
|
||||||
|
.where({ [params.primary]: `IN (${params.rows.join(',')})` })
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
static async insertTableRows (connection, params) {
|
||||||
|
const insertObj = {};
|
||||||
|
for (const key in params.row) {
|
||||||
|
const type = params.fields[key];
|
||||||
|
let escapedParam;
|
||||||
|
|
||||||
|
if (params.row[key] === null)
|
||||||
|
escapedParam = 'NULL';
|
||||||
|
else if (NUMBER.includes(type))
|
||||||
|
escapedParam = params.row[key];
|
||||||
|
else if ([...TEXT, ...LONG_TEXT].includes(type))
|
||||||
|
escapedParam = `"${sqlEscaper(params.row[key])}"`;
|
||||||
|
else if (BLOB.includes(type)) {
|
||||||
|
if (params.row[key]) {
|
||||||
|
const fileBlob = fs.readFileSync(params.row[key]);
|
||||||
|
escapedParam = `0x${fileBlob.toString('hex')}`;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
escapedParam = '""';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
escapedParam = `"${sqlEscaper(params.row[key])}"`;
|
||||||
|
|
||||||
|
insertObj[key] = escapedParam;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < params.repeat; i++) {
|
||||||
|
await connection
|
||||||
|
.schema(params.schema)
|
||||||
|
.into(params.table)
|
||||||
|
.insert(insertObj)
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getForeignList (connection, params) {
|
||||||
|
const query = connection
|
||||||
|
.select(`${params.column} AS foreignColumn`)
|
||||||
|
.schema(params.schema)
|
||||||
|
.from(params.table)
|
||||||
|
.orderBy('foreignColumn ASC');
|
||||||
|
|
||||||
|
if (params.description)
|
||||||
|
query.select(`LEFT(${params.description}, 20) AS foreignDescription`);
|
||||||
|
|
||||||
|
return query.run();
|
||||||
|
}
|
||||||
|
}
|
@@ -4,7 +4,7 @@
|
|||||||
<div id="window-content">
|
<div id="window-content">
|
||||||
<TheSettingBar />
|
<TheSettingBar />
|
||||||
<div id="main-content" class="container">
|
<div id="main-content" class="container">
|
||||||
<TheAppWelcome v-if="!connections.length" @newConn="showNewConnModal" />
|
<TheAppWelcome v-if="!connections.length" @new-conn="showNewConnModal" />
|
||||||
<div v-else class="columns col-gapless">
|
<div v-else class="columns col-gapless">
|
||||||
<Workspace
|
<Workspace
|
||||||
v-for="connection in connections"
|
v-for="connection in connections"
|
||||||
@@ -40,8 +40,7 @@ export default {
|
|||||||
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings')
|
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings')
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {};
|
||||||
};
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
@@ -53,7 +52,7 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
ipcRenderer.send('checkForUpdates');
|
ipcRenderer.send('check-for-updates');
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
@@ -64,30 +63,30 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
html,
|
html,
|
||||||
body{
|
body {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#wrapper{
|
#wrapper {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
#window-content{
|
#window-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
#main-content {
|
#main-content {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
height: calc(100vh - #{$excluding-size});
|
height: calc(100vh - #{$excluding-size});
|
||||||
width: calc(100% - #{$settingbar-width});
|
width: calc(100% - #{$settingbar-width});
|
||||||
|
|
||||||
> .columns{
|
> .columns {
|
||||||
height: calc(100vh - #{$footer-height});
|
height: calc(100vh - #{$footer-height});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="modal modal-sm active">
|
<div class="modal active" :class="modalSizeClass">
|
||||||
<a class="modal-overlay" @click="hideModal" />
|
<a class="modal-overlay" @click="hideModal" />
|
||||||
<div class="modal-container">
|
<div class="modal-container">
|
||||||
<div v-if="hasHeader" class="modal-header">
|
<div v-if="hasHeader" class="modal-header pl-2">
|
||||||
<div class="modal-title h6">
|
<div class="modal-title h6">
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
</div>
|
</div>
|
||||||
@@ -29,13 +29,13 @@
|
|||||||
class="btn btn-primary mr-2"
|
class="btn btn-primary mr-2"
|
||||||
@click="confirmModal"
|
@click="confirmModal"
|
||||||
>
|
>
|
||||||
{{ $t('word.confirm') }}
|
{{ confirmText || $t('word.confirm') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-link"
|
class="btn btn-link"
|
||||||
@click="hideModal"
|
@click="hideModal"
|
||||||
>
|
>
|
||||||
{{ $t('word.cancel') }}
|
{{ cancelText || $t('word.cancel') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,6 +45,15 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'BaseConfirmModal',
|
name: 'BaseConfirmModal',
|
||||||
|
props: {
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
validator: prop => ['small', 'medium', 'large'].includes(prop),
|
||||||
|
default: 'small'
|
||||||
|
},
|
||||||
|
confirmText: String,
|
||||||
|
cancelText: String
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
hasHeader () {
|
hasHeader () {
|
||||||
return !!this.$slots.header;
|
return !!this.$slots.header;
|
||||||
@@ -54,6 +63,13 @@ export default {
|
|||||||
},
|
},
|
||||||
hasDefault () {
|
hasDefault () {
|
||||||
return !!this.$slots.default;
|
return !!this.$slots.default;
|
||||||
|
},
|
||||||
|
modalSizeClass () {
|
||||||
|
if (this.size === 'small')
|
||||||
|
return 'modal-sm';
|
||||||
|
else if (this.size === 'large')
|
||||||
|
return 'modal-lg';
|
||||||
|
else return '';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -70,7 +86,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.modal.modal-sm .modal-container{
|
.modal.modal-sm .modal-container {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -31,63 +31,64 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
close () {
|
close () {
|
||||||
this.$emit('closeContext');
|
this.$emit('close-context');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.context{
|
.context {
|
||||||
|
display: flex;
|
||||||
|
color: $body-font-color;
|
||||||
|
font-size: 16px;
|
||||||
|
z-index: 400;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
position: fixed;
|
||||||
|
height: 100vh;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
.context-container {
|
||||||
|
min-width: 100px;
|
||||||
|
max-width: 150px;
|
||||||
|
z-index: 10;
|
||||||
|
box-shadow: 0 0 1px 0 #000;
|
||||||
|
padding: 0;
|
||||||
|
background: #1d1d1d;
|
||||||
|
border-radius: 0.1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: initial;
|
||||||
|
|
||||||
|
.context-element {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.1rem 0.3rem;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-overlay {
|
||||||
|
background: transparent;
|
||||||
|
bottom: 0;
|
||||||
|
cursor: default;
|
||||||
|
display: block;
|
||||||
|
left: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 400;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 0.4rem;
|
|
||||||
position: fixed;
|
|
||||||
right: 0;
|
right: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
}
|
||||||
bottom: 0;
|
}
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
.context-container{
|
|
||||||
min-width: 100px;
|
|
||||||
max-width: 150px;
|
|
||||||
z-index: 1;
|
|
||||||
box-shadow: 0px 0px 1px 0px #000;
|
|
||||||
padding: 0;
|
|
||||||
background: #1d1d1d;
|
|
||||||
border-radius: 0.1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
position: absolute;
|
|
||||||
pointer-events: initial;
|
|
||||||
|
|
||||||
.context-element{
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: .1rem .3rem;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover{
|
|
||||||
background: $primary-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.context-overlay{
|
|
||||||
background: transparent;
|
|
||||||
bottom: 0;
|
|
||||||
cursor: default;
|
|
||||||
display: block;
|
|
||||||
left: 0;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,14 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="toast mt-2" :class="notificationStatus.className">
|
<div class="toast mt-2" :class="notificationStatus.className">
|
||||||
<span class="p-vcentered text-left" :class="{'expanded': isExpanded}">
|
<span class="p-vcentered text-left" :class="{'expanded': isExpanded}">
|
||||||
<i class="material-icons mr-1">{{ notificationStatus.iconName }}</i>
|
<i class="mdi mdi-24px mr-2" :class="notificationStatus.iconName" />
|
||||||
<span class="notification-message">{{ message }}</span>
|
<span class="notification-message">{{ message }}</span>
|
||||||
</span>
|
</span>
|
||||||
<i
|
<i
|
||||||
v-if="isExpandable"
|
v-if="isExpandable"
|
||||||
class="material-icons c-hand"
|
class="mdi mdi-24px c-hand expand-btn"
|
||||||
|
:class="isExpanded ? 'mdi-chevron-up' : 'mdi-chevron-down'"
|
||||||
@click="toggleExpand"
|
@click="toggleExpand"
|
||||||
>{{ isExpanded ? 'expand_less' : 'expand_more' }}</i>
|
/>
|
||||||
<button class="btn btn-clear ml-2" @click="hideToast" />
|
<button class="btn btn-clear ml-2" @click="hideToast" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -38,19 +39,19 @@ export default {
|
|||||||
switch (this.status) {
|
switch (this.status) {
|
||||||
case 'success':
|
case 'success':
|
||||||
className = 'toast-success';
|
className = 'toast-success';
|
||||||
iconName = 'done';
|
iconName = 'mdi-check';
|
||||||
break;
|
break;
|
||||||
case 'error':
|
case 'error':
|
||||||
className = 'toast-error';
|
className = 'toast-error';
|
||||||
iconName = 'error';
|
iconName = 'mdi-alert-rhombus';
|
||||||
break;
|
break;
|
||||||
case 'warning':
|
case 'warning':
|
||||||
className = 'toast-warning';
|
className = 'toast-warning';
|
||||||
iconName = 'warning';
|
iconName = 'mdi-alert';
|
||||||
break;
|
break;
|
||||||
case 'primary':
|
case 'primary':
|
||||||
className = 'toast-primary';
|
className = 'toast-primary';
|
||||||
iconName = 'info_outline';
|
iconName = 'mdi-information-outline';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,25 +72,28 @@ export default {
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.toast{
|
.toast {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.notification-message{
|
.notification-message {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
max-width: 30rem;
|
max-width: 30rem;
|
||||||
user-select: none;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.expanded .notification-message{
|
.expand-btn {
|
||||||
white-space: initial;
|
align-items: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.expanded .notification-message {
|
||||||
|
white-space: initial;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -34,19 +34,19 @@ export default {
|
|||||||
switch (this.status) {
|
switch (this.status) {
|
||||||
case 'success':
|
case 'success':
|
||||||
className = 'toast-success';
|
className = 'toast-success';
|
||||||
iconTag = '<i class="material-icons mr-1">done</i>';
|
iconTag = '<i class="mdi mdi-24px mdi-check mr-1"></i>';
|
||||||
break;
|
break;
|
||||||
case 'error':
|
case 'error':
|
||||||
className = 'toast-error';
|
className = 'toast-error';
|
||||||
iconTag = '<i class="material-icons mr-1">error</i>';
|
iconTag = '<i class="mdi mdi-24px mdi-alert-rhombus mr-1"></i>';
|
||||||
break;
|
break;
|
||||||
case 'warning':
|
case 'warning':
|
||||||
className = 'toast-warning';
|
className = 'toast-warning';
|
||||||
iconTag = '<i class="material-icons mr-1">warning</i>';
|
iconTag = '<i class="mdi mdi-24px mdi-alert mr-1"></i>';
|
||||||
break;
|
break;
|
||||||
case 'primary':
|
case 'primary':
|
||||||
className = 'toast-primary';
|
className = 'toast-primary';
|
||||||
iconTag = '<i class="material-icons mr-1">info_outline</i>';
|
iconTag = '<i class="mdi mdi-24px mdi-information-outline mr-1"></i>';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,10 +70,10 @@ export default {
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.toast{
|
.toast {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
user-select: text;
|
user-select: text;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -21,47 +21,50 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// credits: https://github.com/xrado 👼
|
|
||||||
export default {
|
export default {
|
||||||
name: 'BaseVirtualScroll',
|
name: 'BaseVirtualScroll',
|
||||||
props: {
|
props: {
|
||||||
items: Array,
|
items: Array,
|
||||||
itemHeight: Number
|
itemHeight: Number,
|
||||||
|
visibleHeight: Number,
|
||||||
|
scrollElement: {
|
||||||
|
type: HTMLDivElement,
|
||||||
|
default: null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
topHeight: 0,
|
topHeight: 0,
|
||||||
bottomHeight: 0,
|
bottomHeight: 0,
|
||||||
visibleItems: []
|
visibleItems: [],
|
||||||
|
renderTimeout: null,
|
||||||
|
localScrollElement: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this._checkScrollPosition = this.checkScrollPosition.bind(this);
|
this._checkScrollPosition = this.checkScrollPosition.bind(this);
|
||||||
this.checkScrollPosition();
|
this.localScrollElement = this.scrollElement ? this.scrollElement : this.$el;
|
||||||
this.$el.addEventListener('scroll', this._checkScrollPosition);
|
this.updateWindow();
|
||||||
this.$el.addEventListener('wheel', this._checkScrollPosition);
|
this.localScrollElement.addEventListener('scroll', this._checkScrollPosition);
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
this.$el.removeEventListener('scroll', this._checkScrollPosition);
|
this.localScrollElement.removeEventListener('scroll', this._checkScrollPosition);
|
||||||
this.$el.removeEventListener('wheel', this._checkScrollPosition);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
checkScrollPosition (e = {}) {
|
checkScrollPosition (e) {
|
||||||
const el = this.$el;
|
clearTimeout(this.renderTimeout);
|
||||||
|
|
||||||
// prevent parent scroll
|
this.renderTimeout = setTimeout(() => {
|
||||||
if ((el.scrollTop === 0 && e.deltaY < 0) || (Math.abs(el.scrollTop - (el.scrollHeight - el.clientHeight)) <= 1 && e.deltaY > 0))
|
this.updateWindow(e);
|
||||||
e.preventDefault();
|
}, 200);
|
||||||
|
|
||||||
this.updateWindow(e);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateWindow (e) {
|
updateWindow (e) {
|
||||||
const visibleItemsCount = Math.ceil(this.$el.clientHeight / this.itemHeight);
|
const visibleItemsCount = Math.ceil(this.visibleHeight / this.itemHeight);
|
||||||
const totalScrollHeight = this.items.length * this.itemHeight;
|
const totalScrollHeight = this.items.length * this.itemHeight;
|
||||||
|
const offset = 50;
|
||||||
|
|
||||||
|
const scrollTop = this.localScrollElement.scrollTop;
|
||||||
|
|
||||||
const scrollTop = this.$el.scrollTop;
|
|
||||||
const offset = 5;
|
|
||||||
const firstVisibleIndex = Math.floor(scrollTop / this.itemHeight);
|
const firstVisibleIndex = Math.floor(scrollTop / this.itemHeight);
|
||||||
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
|
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
|
||||||
const firstCutIndex = Math.max(firstVisibleIndex - offset, 0);
|
const firstCutIndex = Math.max(firstVisibleIndex - offset, 0);
|
||||||
|
89
src/renderer/components/ForeignKeySelect.vue
Normal file
89
src/renderer/components/ForeignKeySelect.vue
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<template>
|
||||||
|
<select
|
||||||
|
ref="editField"
|
||||||
|
class="px-1"
|
||||||
|
@change="onChange"
|
||||||
|
@blur="$emit('blur')"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="row in foreignList"
|
||||||
|
:key="row.foreignColumn"
|
||||||
|
:value="row.foreignColumn"
|
||||||
|
:selected="row.foreignColumn === value"
|
||||||
|
>
|
||||||
|
{{ row.foreignColumn }} {{ 'foreignDescription' in row ? ` - ${row.foreignDescription}` : '' | cutText }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Tables from '@/ipc-api/Tables';
|
||||||
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
|
import { TEXT, LONG_TEXT } from 'common/fieldTypes';
|
||||||
|
export default {
|
||||||
|
name: 'ForeignKeySelect',
|
||||||
|
filters: {
|
||||||
|
cutText (val) {
|
||||||
|
if (typeof val !== 'string') return val;
|
||||||
|
return val.length > 15 ? `${val.substring(0, 15)}...` : val;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
value: [String, Number],
|
||||||
|
keyUsage: Object
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
foreignList: []
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
selectedWorkspace: 'workspaces/getSelected'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async created () {
|
||||||
|
let firstTextField;
|
||||||
|
const params = {
|
||||||
|
uid: this.selectedWorkspace,
|
||||||
|
schema: this.keyUsage.refSchema,
|
||||||
|
table: this.keyUsage.refTable
|
||||||
|
};
|
||||||
|
|
||||||
|
try { // Field data
|
||||||
|
const { status, response } = await Tables.getTableColumns(params);
|
||||||
|
if (status === 'success')
|
||||||
|
firstTextField = response.find(field => [...TEXT, ...LONG_TEXT].includes(field.type)).name || false;
|
||||||
|
else
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
try { // Foregn list
|
||||||
|
const { status, response } = await Tables.getForeignList({
|
||||||
|
...params,
|
||||||
|
column: this.keyUsage.refColumn,
|
||||||
|
description: firstTextField
|
||||||
|
});
|
||||||
|
|
||||||
|
if (status === 'success')
|
||||||
|
this.foreignList = response.rows;
|
||||||
|
else
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions({
|
||||||
|
addNotification: 'notifications/addNotification'
|
||||||
|
}),
|
||||||
|
onChange () {
|
||||||
|
this.$emit('update:value', this.$refs.editField.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@@ -2,9 +2,11 @@
|
|||||||
<div class="modal active modal-sm">
|
<div class="modal active modal-sm">
|
||||||
<a class="modal-overlay" />
|
<a class="modal-overlay" />
|
||||||
<div class="modal-container p-0">
|
<div class="modal-container p-0">
|
||||||
<div class="modal-header">
|
<div class="modal-header pl-2">
|
||||||
<div class="modal-title h6">
|
<div class="modal-title h6">
|
||||||
{{ $t('word.credentials') }}
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-key-variant mr-1" /> {{ $t('word.credentials') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||||
</div>
|
</div>
|
||||||
@@ -63,7 +65,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
closeModal () {
|
closeModal () {
|
||||||
this.$emit('closeAsking');
|
this.$emit('close-asking');
|
||||||
},
|
},
|
||||||
sendCredentials () {
|
sendCredentials () {
|
||||||
this.$emit('credentials', this.credentials);
|
this.$emit('credentials', this.credentials);
|
||||||
@@ -71,7 +73,3 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
@@ -2,9 +2,11 @@
|
|||||||
<div class="modal active">
|
<div class="modal active">
|
||||||
<a class="modal-overlay c-hand" @click="closeModal" />
|
<a class="modal-overlay c-hand" @click="closeModal" />
|
||||||
<div class="modal-container">
|
<div class="modal-container">
|
||||||
<div class="modal-header">
|
<div class="modal-header pl-2">
|
||||||
<div class="modal-title h6">
|
<div class="modal-title h6">
|
||||||
{{ $t('message.editConnection') }}
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-server mr-1" /> {{ $t('message.editConnection') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||||
</div>
|
</div>
|
||||||
@@ -36,7 +38,7 @@
|
|||||||
<option value="maria">
|
<option value="maria">
|
||||||
MariaDB
|
MariaDB
|
||||||
</option>
|
</option>
|
||||||
<option value="mssql">
|
<!-- <option value="mssql">
|
||||||
Microsoft SQL
|
Microsoft SQL
|
||||||
</option>
|
</option>
|
||||||
<option value="pg">
|
<option value="pg">
|
||||||
@@ -44,7 +46,7 @@
|
|||||||
</option>
|
</option>
|
||||||
<option value="oracledb">
|
<option value="oracledb">
|
||||||
Oracle DB
|
Oracle DB
|
||||||
</option>
|
</option> -->
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -135,7 +137,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ModalAskCredentials
|
<ModalAskCredentials
|
||||||
v-if="isAsking"
|
v-if="isAsking"
|
||||||
@closeAsking="closeAsking"
|
@close-asking="closeAsking"
|
||||||
@credentials="continueTest"
|
@credentials="continueTest"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -230,7 +232,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.modal-container{
|
.modal-container {
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -2,9 +2,11 @@
|
|||||||
<div class="modal active">
|
<div class="modal active">
|
||||||
<a class="modal-overlay c-hand" @click="closeModal" />
|
<a class="modal-overlay c-hand" @click="closeModal" />
|
||||||
<div class="modal-container">
|
<div class="modal-container">
|
||||||
<div class="modal-header">
|
<div class="modal-header pl-2">
|
||||||
<div class="modal-title h6">
|
<div class="modal-title h6">
|
||||||
{{ $t('message.createNewConnection') }}
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-server-plus mr-1" /> {{ $t('message.createNewConnection') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||||
</div>
|
</div>
|
||||||
@@ -40,7 +42,7 @@
|
|||||||
<option value="maria">
|
<option value="maria">
|
||||||
MariaDB
|
MariaDB
|
||||||
</option>
|
</option>
|
||||||
<option value="mssql">
|
<!-- <option value="mssql">
|
||||||
Microsoft SQL
|
Microsoft SQL
|
||||||
</option>
|
</option>
|
||||||
<option value="pg">
|
<option value="pg">
|
||||||
@@ -48,7 +50,7 @@
|
|||||||
</option>
|
</option>
|
||||||
<option value="oracledb">
|
<option value="oracledb">
|
||||||
Oracle DB
|
Oracle DB
|
||||||
</option>
|
</option> -->
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -139,7 +141,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ModalAskCredentials
|
<ModalAskCredentials
|
||||||
v-if="isAsking"
|
v-if="isAsking"
|
||||||
@closeAsking="closeAsking"
|
@close-asking="closeAsking"
|
||||||
@credentials="continueTest"
|
@credentials="continueTest"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -148,7 +150,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapActions } from 'vuex';
|
import { mapActions } from 'vuex';
|
||||||
import Connection from '@/ipc-api/Connection';
|
import Connection from '@/ipc-api/Connection';
|
||||||
import { uidGen } from 'common/libs/utilities';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
import ModalAskCredentials from '@/components/ModalAskCredentials';
|
import ModalAskCredentials from '@/components/ModalAskCredentials';
|
||||||
import BaseToast from '@/components/BaseToast';
|
import BaseToast from '@/components/BaseToast';
|
||||||
|
|
||||||
@@ -168,7 +170,7 @@ export default {
|
|||||||
user: 'root',
|
user: 'root',
|
||||||
password: '',
|
password: '',
|
||||||
ask: false,
|
ask: false,
|
||||||
uid: uidGen()
|
uid: uidGen('C')
|
||||||
},
|
},
|
||||||
toast: {
|
toast: {
|
||||||
status: '',
|
status: '',
|
||||||
@@ -253,7 +255,7 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.modal-container{
|
.modal-container {
|
||||||
max-width: 450px;
|
max-width: 450px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
326
src/renderer/components/ModalNewTableRow.vue
Normal file
326
src/renderer/components/ModalNewTableRow.vue
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
<template>
|
||||||
|
<div class="modal active">
|
||||||
|
<a class="modal-overlay" @click.stop="closeModal" />
|
||||||
|
<div class="modal-container p-0">
|
||||||
|
<div class="modal-header pl-2">
|
||||||
|
<div class="modal-title h6">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-playlist-plus mr-1" /> {{ $t('message.addNewRow') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="content">
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<fieldset :disabled="isInserting">
|
||||||
|
<div
|
||||||
|
v-for="(field, key) in fields"
|
||||||
|
:key="field.name"
|
||||||
|
class="form-group"
|
||||||
|
>
|
||||||
|
<div class="col-4 col-sm-12">
|
||||||
|
<label class="form-label" :title="field.name">{{ field.name }}</label>
|
||||||
|
</div>
|
||||||
|
<div class="input-group col-8 col-sm-12">
|
||||||
|
<ForeignKeySelect
|
||||||
|
v-if="foreignKeys.includes(field.name)"
|
||||||
|
class="form-select"
|
||||||
|
:value.sync="localRow[field.name]"
|
||||||
|
:key-usage="getKeyUsage(field.name)"
|
||||||
|
:disabled="fieldsToExclude.includes(field.name)"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
v-else-if="inputProps(field).mask"
|
||||||
|
v-model="localRow[field.name]"
|
||||||
|
v-mask="inputProps(field).mask"
|
||||||
|
class="form-input"
|
||||||
|
:type="inputProps(field).type"
|
||||||
|
:disabled="fieldsToExclude.includes(field.name)"
|
||||||
|
:tabindex="key+1"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-else-if="inputProps(field).type === 'file'"
|
||||||
|
class="form-input"
|
||||||
|
type="file"
|
||||||
|
:disabled="fieldsToExclude.includes(field.name)"
|
||||||
|
:tabindex="key+1"
|
||||||
|
@change="filesChange($event,field.name)"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-else
|
||||||
|
v-model="localRow[field.name]"
|
||||||
|
class="form-input"
|
||||||
|
:type="inputProps(field).type"
|
||||||
|
:disabled="fieldsToExclude.includes(field.name)"
|
||||||
|
:tabindex="key+1"
|
||||||
|
>
|
||||||
|
<span class="input-group-addon" :class="`type-${field.type}`">
|
||||||
|
{{ field.type }} {{ fieldLength(field) | wrapNumber }}
|
||||||
|
</span>
|
||||||
|
<label class="form-checkbox ml-3" :title="$t('word.insert')">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
:checked="!field.autoIncrement"
|
||||||
|
@change.prevent="toggleFields($event, field)"
|
||||||
|
><i class="form-icon" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer text-light">
|
||||||
|
<div class="input-group col-3 tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')">
|
||||||
|
<input
|
||||||
|
v-model="nInserts"
|
||||||
|
type="number"
|
||||||
|
class="form-input"
|
||||||
|
min="1"
|
||||||
|
:disabled="isInserting"
|
||||||
|
>
|
||||||
|
<span class="input-group-addon">
|
||||||
|
<i class="mdi mdi-24px mdi-repeat" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button
|
||||||
|
class="btn btn-primary mr-2"
|
||||||
|
:class="{'loading': isInserting}"
|
||||||
|
@click.stop="insertRows"
|
||||||
|
>
|
||||||
|
{{ $t('word.insert') }}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-link" @click.stop="closeModal">
|
||||||
|
{{ $t('word.close') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment';
|
||||||
|
import { TEXT, LONG_TEXT, NUMBER, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
||||||
|
import { mask } from 'vue-the-mask';
|
||||||
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
|
import Tables from '@/ipc-api/Tables';
|
||||||
|
import ForeignKeySelect from '@/components/ForeignKeySelect';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ModalNewTableRow',
|
||||||
|
components: {
|
||||||
|
ForeignKeySelect
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
mask
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
wrapNumber (num) {
|
||||||
|
if (!num) return '';
|
||||||
|
return `(${num})`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
connection: Object,
|
||||||
|
tabUid: [String, Number]
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
localRow: {},
|
||||||
|
fieldsToExclude: [],
|
||||||
|
nInserts: 1,
|
||||||
|
isInserting: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
selectedWorkspace: 'workspaces/getSelected',
|
||||||
|
getWorkspace: 'workspaces/getWorkspace',
|
||||||
|
getWorkspaceTab: 'workspaces/getWorkspaceTab'
|
||||||
|
}),
|
||||||
|
workspace () {
|
||||||
|
return this.getWorkspace(this.selectedWorkspace);
|
||||||
|
},
|
||||||
|
foreignKeys () {
|
||||||
|
return this.keyUsage.map(key => key.column);
|
||||||
|
},
|
||||||
|
fields () {
|
||||||
|
return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).fields : [];
|
||||||
|
},
|
||||||
|
keyUsage () {
|
||||||
|
return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).keyUsage : [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
nInserts (val) {
|
||||||
|
if (!val || val < 1)
|
||||||
|
this.nInserts = 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
const rowObj = {};
|
||||||
|
|
||||||
|
for (const field of this.fields) {
|
||||||
|
let fieldDefault;
|
||||||
|
|
||||||
|
if (field.default === 'NULL') fieldDefault = null;
|
||||||
|
else {
|
||||||
|
if (NUMBER.includes(field.type))
|
||||||
|
fieldDefault = +field.default;
|
||||||
|
|
||||||
|
if ([...TEXT, ...LONG_TEXT].includes(field.type))
|
||||||
|
fieldDefault = field.default ? field.default.substring(1, field.default.length - 1) : '';
|
||||||
|
|
||||||
|
if ([...TIME, ...DATE].includes(field.type))
|
||||||
|
fieldDefault = field.default;
|
||||||
|
|
||||||
|
if (DATETIME.includes(field.type)) {
|
||||||
|
if (field.default && field.default.toLowerCase().includes('current_timestamp')) {
|
||||||
|
let datePrecision = '';
|
||||||
|
for (let i = 0; i < field.datePrecision; i++)
|
||||||
|
datePrecision += i === 0 ? '.S' : 'S';
|
||||||
|
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rowObj[field.name] = fieldDefault;
|
||||||
|
|
||||||
|
if (field.autoIncrement)// Disable by default auto increment fields
|
||||||
|
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.localRow = { ...rowObj };
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions({
|
||||||
|
addNotification: 'notifications/addNotification'
|
||||||
|
}),
|
||||||
|
async insertRows () {
|
||||||
|
this.isInserting = true;
|
||||||
|
const rowToInsert = this.localRow;
|
||||||
|
Object.keys(rowToInsert).forEach(key => {
|
||||||
|
if (this.fieldsToExclude.includes(key))
|
||||||
|
delete rowToInsert[key];
|
||||||
|
if (typeof rowToInsert[key] === 'undefined')
|
||||||
|
delete rowToInsert[key];
|
||||||
|
});
|
||||||
|
|
||||||
|
const fieldTypes = {};
|
||||||
|
this.fields.forEach(field => {
|
||||||
|
fieldTypes[field.name] = field.type;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status, response } = await Tables.insertTableRows({
|
||||||
|
uid: this.selectedWorkspace,
|
||||||
|
schema: this.workspace.breadcrumbs.schema,
|
||||||
|
table: this.workspace.breadcrumbs.table,
|
||||||
|
row: rowToInsert,
|
||||||
|
repeat: this.nInserts,
|
||||||
|
fields: fieldTypes
|
||||||
|
});
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
this.closeModal();
|
||||||
|
this.$emit('reload');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isInserting = false;
|
||||||
|
},
|
||||||
|
closeModal () {
|
||||||
|
this.$emit('hide');
|
||||||
|
},
|
||||||
|
fieldLength (field) {
|
||||||
|
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
|
||||||
|
return field.numPrecision || field.datePrecision || field.charLength || 0;
|
||||||
|
},
|
||||||
|
inputProps (field) {
|
||||||
|
if ([...TEXT, ...LONG_TEXT].includes(field.type))
|
||||||
|
return { type: 'text', mask: false };
|
||||||
|
|
||||||
|
if (NUMBER.includes(field.type))
|
||||||
|
return { type: 'number', mask: false };
|
||||||
|
|
||||||
|
if (TIME.includes(field.type)) {
|
||||||
|
let timeMask = '##:##:##';
|
||||||
|
const precision = this.fieldLength(field);
|
||||||
|
|
||||||
|
for (let i = 0; i < precision; i++)
|
||||||
|
timeMask += i === 0 ? '.#' : '#';
|
||||||
|
|
||||||
|
return { type: 'text', mask: timeMask };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DATE.includes(field.type))
|
||||||
|
return { type: 'text', mask: '####-##-##' };
|
||||||
|
|
||||||
|
if (DATETIME.includes(field.type)) {
|
||||||
|
let datetimeMask = '####-##-## ##:##:##';
|
||||||
|
const precision = this.fieldLength(field);
|
||||||
|
|
||||||
|
for (let i = 0; i < precision; i++)
|
||||||
|
datetimeMask += i === 0 ? '.#' : '#';
|
||||||
|
|
||||||
|
return { type: 'text', mask: datetimeMask };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BLOB.includes(field.type))
|
||||||
|
return { type: 'file', mask: false };
|
||||||
|
|
||||||
|
if (BIT.includes(field.type))
|
||||||
|
return { type: 'text', mask: false };
|
||||||
|
|
||||||
|
return { type: 'text', mask: false };
|
||||||
|
},
|
||||||
|
toggleFields (event, field) {
|
||||||
|
if (event.target.checked)
|
||||||
|
this.fieldsToExclude = this.fieldsToExclude.filter(f => f !== field.name);
|
||||||
|
else
|
||||||
|
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
|
||||||
|
},
|
||||||
|
filesChange (event, field) {
|
||||||
|
const { files } = event.target;
|
||||||
|
if (!files.length) return;
|
||||||
|
|
||||||
|
this.localRow[field] = files[0].path;
|
||||||
|
},
|
||||||
|
|
||||||
|
getKeyUsage (keyName) {
|
||||||
|
return this.keyUsage.find(key => key.column === keyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.modal-container {
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: normal;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-addon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -2,9 +2,12 @@
|
|||||||
<div id="settings" class="modal active">
|
<div id="settings" class="modal active">
|
||||||
<a class="modal-overlay c-hand" @click="closeModal" />
|
<a class="modal-overlay c-hand" @click="closeModal" />
|
||||||
<div class="modal-container">
|
<div class="modal-container">
|
||||||
<div class="modal-header">
|
<div class="modal-header pl-2">
|
||||||
<div class="modal-title h5">
|
<div class="modal-title h6">
|
||||||
{{ $t('word.settings') }}
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-cog mr-1" />
|
||||||
|
{{ $t('word.settings') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||||
</div>
|
</div>
|
||||||
@@ -31,7 +34,7 @@
|
|||||||
:class="{'active': selectedTab === 'update'}"
|
:class="{'active': selectedTab === 'update'}"
|
||||||
@click="selectTab('update')"
|
@click="selectTab('update')"
|
||||||
>
|
>
|
||||||
<a class="c-hand" :class="{'badge': isUpdate}">{{ $t('word.update') }}</a>
|
<a class="c-hand" :class="{'badge badge-update': hasUpdates}">{{ $t('word.update') }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
class="tab-item"
|
class="tab-item"
|
||||||
@@ -45,11 +48,11 @@
|
|||||||
|
|
||||||
<div v-if="selectedTab === 'general'" class="panel-body py-4">
|
<div v-if="selectedTab === 'general'" class="panel-body py-4">
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
<div class="col-6 col-sm-12">
|
<div class="col-8 col-sm-12">
|
||||||
<div class="form-group">
|
<div class="form-group mb-4">
|
||||||
<div class="col-6 col-sm-12">
|
<div class="col-6 col-sm-12">
|
||||||
<label class="form-label">
|
<label class="form-label">
|
||||||
<i class="material-icons md-18 mr-1">translate</i>
|
<i class="mdi mdi-18px mdi-translate mr-1" />
|
||||||
{{ $t('word.language') }}:
|
{{ $t('word.language') }}:
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -69,12 +72,33 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-6 col-sm-12">
|
||||||
|
<label class="form-label">
|
||||||
|
{{ $t('message.notificationsTimeout') }}:
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-sm-12">
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
v-model="localTimeout"
|
||||||
|
class="form-input"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
@focusout="checkNotificationsTimeout"
|
||||||
|
>
|
||||||
|
<span class="input-group-addon">{{ $t('word.seconds') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="selectedTab === 'themes'" class="panel-body py-4">
|
<div v-if="selectedTab === 'themes'" class="panel-body py-4">
|
||||||
<!-- -->
|
<div class="text-center">
|
||||||
|
<p>In future releases</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="selectedTab === 'update'" class="panel-body py-4">
|
<div v-if="selectedTab === 'update'" class="panel-body py-4">
|
||||||
@@ -87,7 +111,7 @@
|
|||||||
<h4>{{ appName }}</h4>
|
<h4>{{ appName }}</h4>
|
||||||
<p>
|
<p>
|
||||||
{{ $t('word.version') }}: {{ appVersion }}<br>
|
{{ $t('word.version') }}: {{ appVersion }}<br>
|
||||||
<a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares')">GitHub</a><br>
|
<a class="c-hand" @click="openOutside('https://github.com/EStarium/antares')">GitHub</a><br>
|
||||||
<small>{{ $t('message.madeWithJS') }}</small>
|
<small>{{ $t('message.madeWithJS') }}</small>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -111,8 +135,8 @@ export default {
|
|||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
isUpdate: false,
|
|
||||||
localLocale: null,
|
localLocale: null,
|
||||||
|
localTimeout: null,
|
||||||
selectedTab: 'general'
|
selectedTab: 'general'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -121,7 +145,9 @@ export default {
|
|||||||
appName: 'application/appName',
|
appName: 'application/appName',
|
||||||
appVersion: 'application/appVersion',
|
appVersion: 'application/appVersion',
|
||||||
selectedSettingTab: 'application/selectedSettingTab',
|
selectedSettingTab: 'application/selectedSettingTab',
|
||||||
selectedLocale: 'settings/getLocale'
|
selectedLocale: 'settings/getLocale',
|
||||||
|
notificationsTimeout: 'settings/getNotificationsTimeout',
|
||||||
|
updateStatus: 'application/getUpdateStatus'
|
||||||
}),
|
}),
|
||||||
locales () {
|
locales () {
|
||||||
const locales = [];
|
const locales = [];
|
||||||
@@ -129,46 +155,61 @@ export default {
|
|||||||
locales.push({ code: locale, name: localesNames[locale] });
|
locales.push({ code: locale, name: localesNames[locale] });
|
||||||
|
|
||||||
return locales;
|
return locales;
|
||||||
|
},
|
||||||
|
hasUpdates () {
|
||||||
|
return ['available', 'downloading', 'downloaded'].includes(this.updateStatus);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
this.localLocale = this.selectedLocale;
|
this.localLocale = this.selectedLocale;
|
||||||
|
this.localTimeout = this.notificationsTimeout;
|
||||||
this.selectedTab = this.selectedSettingTab;
|
this.selectedTab = this.selectedSettingTab;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
closeModal: 'application/hideSettingModal',
|
closeModal: 'application/hideSettingModal',
|
||||||
changeLocale: 'settings/changeLocale'
|
changeLocale: 'settings/changeLocale',
|
||||||
|
updateNotificationsTimeout: 'settings/updateNotificationsTimeout'
|
||||||
}),
|
}),
|
||||||
selectTab (tab) {
|
selectTab (tab) {
|
||||||
this.selectedTab = tab;
|
this.selectedTab = tab;
|
||||||
},
|
},
|
||||||
openOutside (link) {
|
openOutside (link) {
|
||||||
shell.openExternal(link);
|
shell.openExternal(link);
|
||||||
|
},
|
||||||
|
checkNotificationsTimeout () {
|
||||||
|
if (!this.localTimeout)
|
||||||
|
this.localTimeout = 10;
|
||||||
|
|
||||||
|
this.updateNotificationsTimeout(+this.localTimeout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#settings{
|
#settings {
|
||||||
.modal-body{
|
.modal-body {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
.panel-body{
|
.panel-body {
|
||||||
height: calc(70vh - 70px);
|
height: calc(70vh - 70px);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge::after{
|
.badge::after {
|
||||||
background: #32b643;
|
background: #32b643;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-label{
|
.badge-update::after {
|
||||||
display: flex;
|
bottom: initial;
|
||||||
align-items: center;
|
background: $primary-color;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="empty">
|
<div class="empty">
|
||||||
<div class="empty-icon">
|
<div class="empty-icon">
|
||||||
<i class="material-icons md-48">system_update_alt</i>
|
<i class="mdi mdi-48px mdi-cloud-download" />
|
||||||
</div>
|
</div>
|
||||||
<p class="empty-title h5">
|
<p class="empty-title h5">
|
||||||
{{ updateMessage }}
|
{{ updateMessage }}
|
||||||
@@ -68,17 +68,17 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
checkForUpdates () {
|
checkForUpdates () {
|
||||||
ipcRenderer.send('checkForUpdates');
|
ipcRenderer.send('check-for-updates');
|
||||||
},
|
},
|
||||||
restartToUpdate () {
|
restartToUpdate () {
|
||||||
ipcRenderer.send('restartToUpdate');
|
ipcRenderer.send('restart-to-update');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.empty{
|
.empty {
|
||||||
color: $body-font-color;
|
color: $body-font-color;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,25 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="editor-wrapper">
|
<div class="editor-wrapper">
|
||||||
<textarea
|
<div ref="editor" class="editor" />
|
||||||
ref="codemirror"
|
|
||||||
:options="cmOptions"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import CodeMirror from 'codemirror';
|
|
||||||
|
|
||||||
import 'codemirror/lib/codemirror.css';
|
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||||
import 'codemirror/theme/material-darker.css';
|
import { completionItemProvider } from '@/suggestions/sql';
|
||||||
import 'codemirror/mode/sql/sql';
|
|
||||||
import 'codemirror/addon/edit/closebrackets';
|
|
||||||
import 'codemirror/addon/selection/active-line';
|
|
||||||
import 'codemirror/addon/hint/show-hint';
|
|
||||||
import 'codemirror/addon/hint/show-hint.css';
|
|
||||||
import 'codemirror/addon/hint/sql-hint';
|
|
||||||
|
|
||||||
CodeMirror.defineOption('sql-hint');
|
monaco.languages.registerCompletionItemProvider('sql', completionItemProvider(monaco));
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'QueryEditor',
|
name: 'QueryEditor',
|
||||||
@@ -28,62 +18,53 @@ export default {
|
|||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
cminstance: null,
|
editor: null
|
||||||
content: '',
|
|
||||||
cmOptions: {
|
|
||||||
tabSize: 3,
|
|
||||||
smartIndent: true,
|
|
||||||
styleActiveLine: true,
|
|
||||||
lineNumbers: true,
|
|
||||||
line: true,
|
|
||||||
mode: 'text/x-sql',
|
|
||||||
theme: 'material-darker',
|
|
||||||
extraKeys: {
|
|
||||||
'Ctrl-Space': 'autocomplete'
|
|
||||||
},
|
|
||||||
hintOptions: {
|
|
||||||
tables: {
|
|
||||||
users: ['name', 'score', 'birthDate'],
|
|
||||||
countries: ['name', 'population', 'size']
|
|
||||||
}
|
|
||||||
},
|
|
||||||
autoCloseBrackets: true
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.initialize();
|
this.editor = monaco.editor.create(this.$refs.editor, {
|
||||||
},
|
value: this.value,
|
||||||
methods: {
|
language: 'sql',
|
||||||
initialize () {
|
theme: 'vs-dark',
|
||||||
this.cminstance = CodeMirror.fromTextArea(this.$refs.codemirror, this.cmOptions);
|
autoIndent: true,
|
||||||
this.cminstance.setValue(this.value || this.content);
|
minimap: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
contextmenu: false,
|
||||||
|
wordBasedSuggestions: true,
|
||||||
|
acceptSuggestionOnEnter: 'smart',
|
||||||
|
quickSuggestions: true
|
||||||
|
});
|
||||||
|
|
||||||
this.cminstance.on('change', cm => {
|
this.editor.onDidChangeModelContent(e => {
|
||||||
this.content = cm.getValue();
|
const content = this.editor.getValue();
|
||||||
this.$emit('input', this.content);
|
this.$emit('update:value', content);
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
this.editor && this.editor.dispose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.editor-wrapper{
|
.editor-wrapper {
|
||||||
border-bottom: 1px solid #444444;
|
border-bottom: 1px solid #444;
|
||||||
}
|
|
||||||
|
|
||||||
.CodeMirror{
|
.editor {
|
||||||
height: 200px;
|
height: 200px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.CodeMirror-scroll{
|
.CodeMirror {
|
||||||
max-width: 100%;
|
.CodeMirror-scroll {
|
||||||
}
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.CodeMirror-line {
|
.CodeMirror-line {
|
||||||
word-break: break-word!important;
|
word-break: break-word !important;
|
||||||
white-space: pre-wrap!important;
|
white-space: pre-wrap !important;
|
||||||
word-break: normal;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<BaseContextMenu
|
<BaseContextMenu
|
||||||
:context-event="contextEvent"
|
:context-event="contextEvent"
|
||||||
@closeContext="$emit('closeContext')"
|
@close-context="$emit('close-context')"
|
||||||
>
|
>
|
||||||
<div class="context-element" @click="showEditModal(contextConnection)">
|
<div class="context-element" @click="showEditModal(contextConnection)">
|
||||||
<i class="material-icons md-18 text-light pr-1">edit</i> {{ $t('word.edit') }}
|
<i class="mdi mdi-18px mdi-pencil text-light pr-1" /> {{ $t('word.edit') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="context-element" @click="showConfirmModal">
|
<div class="context-element" @click="showConfirmModal">
|
||||||
<i class="material-icons md-18 text-light pr-1">delete</i> {{ $t('word.delete') }}
|
<i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('word.delete') }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
@@ -16,7 +16,9 @@
|
|||||||
@hide="hideConfirmModal"
|
@hide="hideConfirmModal"
|
||||||
>
|
>
|
||||||
<template :slot="'header'">
|
<template :slot="'header'">
|
||||||
{{ $t('message.deleteConnection') }}
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-server-remove mr-1" /> {{ $t('message.deleteConnection') }}
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div :slot="'body'">
|
<div :slot="'body'">
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
@@ -69,7 +71,3 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column col-12 empty text-light">
|
<div class="column col-12 empty text-light">
|
||||||
<div class="empty-icon">
|
<div class="empty-icon">
|
||||||
<i class="material-icons md-48">mood</i>
|
<i class="mdi mdi-48px mdi-emoticon" />
|
||||||
</div>
|
</div>
|
||||||
<p class="empty-title h5">
|
<p class="empty-title h5">
|
||||||
{{ $t('message.appWelcome') }}
|
{{ $t('message.appWelcome') }}
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
{{ $t('message.appFirstStep') }}
|
{{ $t('message.appFirstStep') }}
|
||||||
</p>
|
</p>
|
||||||
<div class="empty-action">
|
<div class="empty-action">
|
||||||
<button class="btn btn-primary" @click="$emit('newConn')">
|
<button class="btn btn-primary" @click="$emit('new-conn')">
|
||||||
{{ $t('message.createConnection') }}
|
{{ $t('message.createConnection') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -26,12 +26,12 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.empty{
|
.empty {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
<div class="footer-left-elements">
|
<div class="footer-left-elements">
|
||||||
<ul class="footer-elements">
|
<ul class="footer-elements">
|
||||||
<li class="footer-element">
|
<li class="footer-element">
|
||||||
<i class="material-icons md-18 mr-1">memory</i>
|
<i class="mdi mdi-18px mdi-memory mr-1" />
|
||||||
<small>{{ appVersion }}</small>
|
<small>{{ appVersion }}</small>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -11,15 +11,15 @@
|
|||||||
|
|
||||||
<div class="footer-right-elements">
|
<div class="footer-right-elements">
|
||||||
<ul class="footer-elements">
|
<ul class="footer-elements">
|
||||||
<li class="footer-element footer-link">
|
<li class="footer-element footer-link" @click="openOutside('https://github.com/sponsors/Fabio286')">
|
||||||
<i class="material-icons md-18 mr-1">favorite</i>
|
<i class="mdi mdi-18px mdi-coffee mr-1" />
|
||||||
<small>{{ $t('word.donate') }}</small>
|
<small>{{ $t('word.donate') }}</small>
|
||||||
</li>
|
</li>
|
||||||
<li class="footer-element footer-link">
|
<li class="footer-element footer-link" @click="openOutside('https://github.com/EStarium/antares/issues')">
|
||||||
<i class="material-icons md-18">bug_report</i>
|
<i class="mdi mdi-18px mdi-bug" />
|
||||||
</li>
|
</li>
|
||||||
<li class="footer-element footer-link" @click="showSettingModal('about')">
|
<li class="footer-element footer-link" @click="showSettingModal('about')">
|
||||||
<i class="material-icons md-18">info_outline</i>
|
<i class="mdi mdi-18px mdi-information-outline" />
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapActions, mapGetters } from 'vuex';
|
import { mapActions, mapGetters } from 'vuex';
|
||||||
|
const { shell } = require('electron');
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TheFooter',
|
name: 'TheFooter',
|
||||||
@@ -40,47 +41,50 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
showSettingModal: 'application/showSettingModal'
|
showSettingModal: 'application/showSettingModal'
|
||||||
})
|
}),
|
||||||
|
openOutside (link) {
|
||||||
|
shell.openExternal(link);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#footer{
|
#footer {
|
||||||
height: $footer-height;
|
height: $footer-height;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: $primary-color;
|
||||||
|
padding: 0 0.2rem;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
box-shadow: 0 0 1px 0 #000;
|
||||||
|
|
||||||
|
.footer-elements {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: $primary-color;
|
|
||||||
padding: 0 .2rem;
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
box-shadow: 0 0 1px 0px #000;
|
|
||||||
|
|
||||||
.footer-elements{
|
.footer-element {
|
||||||
list-style: none;
|
height: $footer-height;
|
||||||
margin: 0;
|
display: flex;
|
||||||
display: flex;
|
align-items: center;
|
||||||
align-items: center;
|
padding: 0 0.4rem;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
.footer-element{
|
&.footer-link {
|
||||||
height: $footer-height;
|
cursor: pointer;
|
||||||
display: flex;
|
transition: background 0.2s;
|
||||||
align-items: center;
|
|
||||||
padding: 0 .4rem;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
&.footer-link{
|
&:hover {
|
||||||
cursor: pointer;
|
background: rgba($color: #fff, $alpha: 0.1);
|
||||||
transition: background .2s;
|
}
|
||||||
|
}
|
||||||
&:hover{
|
|
||||||
background: rgba($color: #fff, $alpha: .1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="notifications-board">
|
<div
|
||||||
|
id="notifications-board"
|
||||||
|
@mouseenter="clearTimeouts"
|
||||||
|
@mouseleave="rearmTimeouts"
|
||||||
|
>
|
||||||
<transition-group name="slide-fade">
|
<transition-group name="slide-fade">
|
||||||
<BaseNotification
|
<BaseNotification
|
||||||
v-for="notification in latestNotifications"
|
v-for="notification in latestNotifications"
|
||||||
@@ -21,27 +25,60 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
BaseNotification
|
BaseNotification
|
||||||
},
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
timeouts: {}
|
||||||
|
};
|
||||||
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
notifications: 'notifications/getNotifications'
|
notifications: 'notifications/getNotifications',
|
||||||
|
notificationsTimeout: 'settings/getNotificationsTimeout'
|
||||||
}),
|
}),
|
||||||
latestNotifications () {
|
latestNotifications () {
|
||||||
return this.notifications.slice(0, 10);
|
return this.notifications.slice(0, 10);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
notifications: {
|
||||||
|
deep: true,
|
||||||
|
handler: function (notification) {
|
||||||
|
if (notification.length) {
|
||||||
|
this.timeouts[notification[0].uid] = setTimeout(() => {
|
||||||
|
this.removeNotification(notification[0].uid);
|
||||||
|
delete this.timeouts[notification.uid];
|
||||||
|
}, this.notificationsTimeout * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
removeNotification: 'notifications/removeNotification'
|
removeNotification: 'notifications/removeNotification'
|
||||||
})
|
}),
|
||||||
|
clearTimeouts () {
|
||||||
|
for (const uid in this.timeouts) {
|
||||||
|
clearTimeout(this.timeouts[uid]);
|
||||||
|
delete this.timeouts[uid];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rearmTimeouts () {
|
||||||
|
for (const notification of this.notifications) {
|
||||||
|
this.timeouts[notification.uid] = setTimeout(() => {
|
||||||
|
this.removeNotification(notification.uid);
|
||||||
|
delete this.timeouts[notification.uid];
|
||||||
|
}, this.notificationsTimeout * 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#notifications-board{
|
#notifications-board {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 9;
|
z-index: 999;
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
bottom: 1rem;
|
bottom: 1rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
v-if="isContext"
|
v-if="isContext"
|
||||||
:context-event="contextEvent"
|
:context-event="contextEvent"
|
||||||
:context-connection="contextConnection"
|
:context-connection="contextConnection"
|
||||||
@closeContext="isContext = false"
|
@close-context="isContext = false"
|
||||||
/>
|
/>
|
||||||
<ul class="settingbar-elements">
|
<ul class="settingbar-elements">
|
||||||
<draggable v-model="connections">
|
<draggable v-model="connections">
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
@click="showNewConnModal"
|
@click="showNewConnModal"
|
||||||
@mouseover.self="tooltipPosition"
|
@mouseover.self="tooltipPosition"
|
||||||
>
|
>
|
||||||
<i class="settingbar-element-icon material-icons text-light">add</i>
|
<i class="settingbar-element-icon mdi mdi-24px mdi-plus text-light" />
|
||||||
<span class="ex-tooltip-content">{{ $t('message.addConnection') }}</span>
|
<span class="ex-tooltip-content">{{ $t('message.addConnection') }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
<div class="settingbar-bottom-elements">
|
<div class="settingbar-bottom-elements">
|
||||||
<ul class="settingbar-elements">
|
<ul class="settingbar-elements">
|
||||||
<li class="settingbar-element btn btn-link ex-tooltip" @click="showSettingModal('general')">
|
<li class="settingbar-element btn btn-link ex-tooltip" @click="showSettingModal('general')">
|
||||||
<i class="settingbar-element-icon material-icons text-light">settings</i>
|
<i class="settingbar-element-icon mdi mdi-24px mdi-cog text-light" :class="{' badge badge-update': hasUpdates}" />
|
||||||
<span class="ex-tooltip-content">{{ $t('word.settings') }}</span>
|
<span class="ex-tooltip-content">{{ $t('word.settings') }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -70,7 +70,8 @@ export default {
|
|||||||
getConnections: 'connections/getConnections',
|
getConnections: 'connections/getConnections',
|
||||||
getConnectionName: 'connections/getConnectionName',
|
getConnectionName: 'connections/getConnectionName',
|
||||||
connected: 'workspaces/getConnected',
|
connected: 'workspaces/getConnected',
|
||||||
selectedWorkspace: 'workspaces/getSelected'
|
selectedWorkspace: 'workspaces/getSelected',
|
||||||
|
updateStatus: 'application/getUpdateStatus'
|
||||||
}),
|
}),
|
||||||
connections: {
|
connections: {
|
||||||
get () {
|
get () {
|
||||||
@@ -79,6 +80,9 @@ export default {
|
|||||||
set (value) {
|
set (value) {
|
||||||
this.updateConnections(value);
|
this.updateConnections(value);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
hasUpdates () {
|
||||||
|
return ['available', 'downloading', 'downloaded'].includes(this.updateStatus);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -106,101 +110,104 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#settingbar{
|
#settingbar {
|
||||||
width: $settingbar-width;
|
width: $settingbar-width;
|
||||||
height: calc(100vh - #{$excluding-size});
|
height: calc(100vh - #{$excluding-size});
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
background: $bg-color-light;
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: 0 0 1px 0 #000;
|
||||||
|
z-index: 9;
|
||||||
|
|
||||||
|
.settingbar-top-elements {
|
||||||
|
overflow-x: hidden;
|
||||||
|
overflow-y: overlay;
|
||||||
|
max-height: calc((100vh - 3.5rem) - #{$excluding-size});
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingbar-bottom-elements {
|
||||||
|
padding-top: 0.5rem;
|
||||||
background: $bg-color-light;
|
background: $bg-color-light;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingbar-elements {
|
||||||
|
list-style: none;
|
||||||
|
text-align: center;
|
||||||
|
width: $settingbar-width;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-shadow: 0 0 1px 0px #000;
|
margin: 0;
|
||||||
z-index: 9;
|
|
||||||
|
|
||||||
.settingbar-top-elements{
|
.settingbar-element {
|
||||||
overflow-x: hidden;
|
height: $settingbar-width;
|
||||||
overflow-y: overlay;
|
width: 100%;
|
||||||
max-height: calc((100vh - 3.5rem) - #{$excluding-size});
|
margin: 0;
|
||||||
|
border-left: 3px solid transparent;
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&:hover {
|
||||||
width: 3px;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
border-left-color: $body-font-color;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingbar-element-icon {
|
||||||
|
&.badge::after {
|
||||||
|
bottom: -10px;
|
||||||
|
right: 0;
|
||||||
|
position: absolute;
|
||||||
|
background: $success-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.badge-update::after {
|
||||||
|
bottom: initial;
|
||||||
|
background: $primary-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.settingbar-bottom-elements{
|
.ex-tooltip {// Because both overflow-x: visible and overflow-y:auto are evil!!!
|
||||||
padding-top: .5rem;
|
.ex-tooltip-content {
|
||||||
background: $bg-color-light;
|
z-index: 999;
|
||||||
z-index: 1;
|
visibility: hidden;
|
||||||
}
|
opacity: 0;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0 0 0 calc(#{$settingbar-width} - 5px);
|
||||||
|
left: 0;
|
||||||
|
padding: 0.2rem 0.4rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
background: rgba(48, 55, 66, 0.95);
|
||||||
|
border-radius: 0.1rem;
|
||||||
|
color: #fff;
|
||||||
|
max-width: 320px;
|
||||||
|
pointer-events: none;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
.settingbar-elements{
|
&:hover .ex-tooltip-content {
|
||||||
list-style: none;
|
visibility: visible;
|
||||||
text-align: center;
|
opacity: 1;
|
||||||
width: $settingbar-width;
|
}
|
||||||
padding: 0;
|
}
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
.settingbar-element{
|
|
||||||
height: $settingbar-width;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
border-left: 3px solid transparent;
|
|
||||||
opacity: .5;
|
|
||||||
transition: opacity .2s;
|
|
||||||
display: flex;
|
|
||||||
align-content: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
&:hover{
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected{
|
|
||||||
border-left-color: $body-font-color;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settingbar-element-icon{
|
|
||||||
|
|
||||||
&.badge::after{
|
|
||||||
bottom: -10px;
|
|
||||||
right: 0;
|
|
||||||
position: absolute;
|
|
||||||
background: $success-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ex-tooltip{// Because both overflow-x: visible and overflow-y:auto are evil!!!
|
|
||||||
.ex-tooltip-content{
|
|
||||||
z-index: 999;
|
|
||||||
visibility: hidden;
|
|
||||||
opacity: 0;
|
|
||||||
display:block;
|
|
||||||
position:absolute;
|
|
||||||
background-color:#feffe1;
|
|
||||||
text-align: center;
|
|
||||||
margin:.0 0 0 calc(#{$settingbar-width} - 5px);
|
|
||||||
left: 0;
|
|
||||||
padding: .2rem .4rem;
|
|
||||||
font-size: .7rem;
|
|
||||||
background: rgba(48,55,66,.95);
|
|
||||||
border-radius: .1rem;
|
|
||||||
color: #fff;
|
|
||||||
max-width: 320px;
|
|
||||||
pointer-events: none;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
transition: opacity .2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .ex-tooltip-content{
|
|
||||||
visibility: visible;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
@@ -4,8 +4,8 @@
|
|||||||
<div class="titlebar-elements">
|
<div class="titlebar-elements">
|
||||||
<img class="titlebar-logo" :src="require('@/images/logo.svg').default">
|
<img class="titlebar-logo" :src="require('@/images/logo.svg').default">
|
||||||
</div>
|
</div>
|
||||||
<div class="titlebar-elements">
|
<div class="titlebar-elements titlebar-title">
|
||||||
<!-- -->
|
{{ windowTitle }}
|
||||||
</div>
|
</div>
|
||||||
<div class="titlebar-elements">
|
<div class="titlebar-elements">
|
||||||
<div
|
<div
|
||||||
@@ -13,31 +13,32 @@
|
|||||||
class="titlebar-element"
|
class="titlebar-element"
|
||||||
@click="openDevTools"
|
@click="openDevTools"
|
||||||
>
|
>
|
||||||
<i class="material-icons">code</i>
|
<i class="mdi mdi-24px mdi-code-tags" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="isDevelopment"
|
v-if="isDevelopment"
|
||||||
class="titlebar-element"
|
class="titlebar-element"
|
||||||
@click="reload"
|
@click="reload"
|
||||||
>
|
>
|
||||||
<i class="material-icons">refresh</i>
|
<i class="mdi mdi-24px mdi-refresh" />
|
||||||
</div>
|
</div>
|
||||||
<div class="titlebar-element" @click="minimizeApp">
|
<div class="titlebar-element" @click="minimizeApp">
|
||||||
<i class="material-icons">remove</i>
|
<i class="mdi mdi-24px mdi-minus" />
|
||||||
</div>
|
</div>
|
||||||
<div class="titlebar-element" @click="toggleFullScreen">
|
<div class="titlebar-element" @click="toggleFullScreen">
|
||||||
<i v-if="isMaximized" class="material-icons">fullscreen_exit</i>
|
<i v-if="isMaximized" class="mdi mdi-24px mdi-fullscreen-exit" />
|
||||||
<i v-else class="material-icons">fullscreen</i>
|
<i v-else class="mdi mdi-24px mdi-fullscreen" />
|
||||||
</div>
|
</div>
|
||||||
<div class="titlebar-element close-button" @click="closeApp">
|
<div class="titlebar-element close-button" @click="closeApp">
|
||||||
<i class="material-icons">close</i>
|
<i class="mdi mdi-24px mdi-close" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { remote } from 'electron';
|
import { remote, ipcRenderer } from 'electron';
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TheTitleBar',
|
name: 'TheTitleBar',
|
||||||
@@ -48,6 +49,22 @@ export default {
|
|||||||
isDevelopment: process.env.NODE_ENV === 'development'
|
isDevelopment: process.env.NODE_ENV === 'development'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
getConnectionName: 'connections/getConnectionName',
|
||||||
|
selectedWorkspace: 'workspaces/getSelected',
|
||||||
|
getWorkspace: 'workspaces/getWorkspace'
|
||||||
|
}),
|
||||||
|
windowTitle () {
|
||||||
|
if (!this.selectedWorkspace) return '';
|
||||||
|
|
||||||
|
const connectionName = this.getConnectionName(this.selectedWorkspace);
|
||||||
|
const workspace = this.getWorkspace(this.selectedWorkspace);
|
||||||
|
const breadcrumbs = Object.values(workspace.breadcrumbs).filter(breadcrumb => breadcrumb);
|
||||||
|
|
||||||
|
return [connectionName, ...breadcrumbs].join(' • ');
|
||||||
|
}
|
||||||
|
},
|
||||||
created () {
|
created () {
|
||||||
window.addEventListener('resize', this.onResize);
|
window.addEventListener('resize', this.onResize);
|
||||||
},
|
},
|
||||||
@@ -56,7 +73,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
closeApp () {
|
closeApp () {
|
||||||
this.w.close();
|
ipcRenderer.send('close-app');
|
||||||
},
|
},
|
||||||
minimizeApp () {
|
minimizeApp () {
|
||||||
this.w.minimize();
|
this.w.minimize();
|
||||||
@@ -81,55 +98,64 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
#titlebar{
|
#titlebar {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
justify-content: space-between;
|
||||||
|
background: $bg-color-light;
|
||||||
|
align-items: center;
|
||||||
|
height: $titlebar-height;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
user-select: none;
|
||||||
|
box-shadow: 0 0 1px 0 #000;
|
||||||
|
z-index: 9999;
|
||||||
|
|
||||||
|
.titlebar-resizer {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
z-index: 999;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titlebar-elements {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
|
||||||
justify-content: space-between;
|
|
||||||
background: $bg-color-light;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: $titlebar-height;
|
|
||||||
-webkit-app-region: drag;
|
|
||||||
user-select: none;
|
|
||||||
box-shadow: 0 0 1px 0px #000;
|
|
||||||
z-index: 9999;
|
|
||||||
|
|
||||||
.titlebar-resizer{
|
&.titlebar-title {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
left: 0;
|
||||||
width: 100%;
|
right: 0;
|
||||||
height: 4px;
|
text-align: center;
|
||||||
z-index: 999;
|
display: block;
|
||||||
-webkit-app-region: no-drag;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.titlebar-elements{
|
.titlebar-logo {
|
||||||
display: flex;
|
height: $titlebar-height;
|
||||||
align-items: center;
|
padding: 0 0.4rem;
|
||||||
|
|
||||||
.titlebar-logo{
|
|
||||||
height: $titlebar-height;
|
|
||||||
padding: 0 .4rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.titlebar-element{
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: $titlebar-height;
|
|
||||||
line-height: 0;
|
|
||||||
padding: 0 .7rem;
|
|
||||||
opacity: .7;
|
|
||||||
transition: opacity .2s;
|
|
||||||
-webkit-app-region: no-drag;
|
|
||||||
|
|
||||||
&:hover{
|
|
||||||
opacity: 1;
|
|
||||||
background: rgba($color: #fff, $alpha: .2);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.close-button:hover{
|
|
||||||
background: red;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.titlebar-element {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: $titlebar-height;
|
||||||
|
line-height: 0;
|
||||||
|
padding: 0 0.7rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
background: rgba($color: #fff, $alpha: 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.close-button:hover {
|
||||||
|
background: red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -2,37 +2,69 @@
|
|||||||
<div v-show="isSelected" class="workspace column columns col-gapless">
|
<div v-show="isSelected" class="workspace column columns col-gapless">
|
||||||
<WorkspaceExploreBar :connection="connection" :is-selected="isSelected" />
|
<WorkspaceExploreBar :connection="connection" :is-selected="isSelected" />
|
||||||
<div v-if="workspace.connected" class="workspace-tabs column columns col-gapless">
|
<div v-if="workspace.connected" class="workspace-tabs column columns col-gapless">
|
||||||
<ul class="tab tab-block column col-12">
|
<ul ref="tabWrap" class="tab tab-block column col-12">
|
||||||
<li
|
<li
|
||||||
v-if="workspace.breadcrumbs.table"
|
v-if="workspace.breadcrumbs.table"
|
||||||
class="tab-item"
|
class="tab-item"
|
||||||
:class="{'active': selectedTab === 1}"
|
:class="{'active': selectedTab === 'prop'}"
|
||||||
@click="selectTab({uid: workspace.uid, tab: 1})"
|
@click="selectTab({uid: workspace.uid, tab: 'prop'})"
|
||||||
>
|
>
|
||||||
<a class="tab-link">
|
<a class="tab-link">
|
||||||
<i class="material-icons md-18 mr-1">grid_on</i>
|
<i class="mdi mdi-18px mdi-tune mr-1" />
|
||||||
<span :title="workspace.breadcrumbs.table">{{ workspace.breadcrumbs.table }}</span>
|
<span :title="workspace.breadcrumbs.table">{{ $t('word.properties').toUpperCase() }}: {{ workspace.breadcrumbs.table }}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-for="(tab, key) of queryTabs"
|
v-if="workspace.breadcrumbs.table"
|
||||||
|
class="tab-item"
|
||||||
|
:class="{'active': selectedTab === 'data'}"
|
||||||
|
@click="selectTab({uid: workspace.uid, tab: 'data'})"
|
||||||
|
>
|
||||||
|
<a class="tab-link">
|
||||||
|
<i class="mdi mdi-18px mdi-table mr-1" />
|
||||||
|
<span :title="workspace.breadcrumbs.table">{{ $t('word.data').toUpperCase() }}: {{ workspace.breadcrumbs.table }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-for="tab of queryTabs"
|
||||||
:key="tab.uid"
|
:key="tab.uid"
|
||||||
class="tab-item"
|
class="tab-item"
|
||||||
:class="{'active': selectedTab === tab.uid}"
|
:class="{'active': selectedTab === tab.uid}"
|
||||||
@click="selectTab({uid: workspace.uid, tab: tab.uid})"
|
@click="selectTab({uid: workspace.uid, tab: tab.uid})"
|
||||||
|
@mousedown.middle="closeTab(tab.uid)"
|
||||||
>
|
>
|
||||||
<a><span>Query #{{ key+1 }} <span v-if="queryTabs.length > 1" class="btn btn-clear" /></span></a>
|
<a>
|
||||||
|
<span>
|
||||||
|
Query #{{ tab.index }}
|
||||||
|
<span
|
||||||
|
v-if="queryTabs.length > 1"
|
||||||
|
class="btn btn-clear"
|
||||||
|
:title="$t('word.close')"
|
||||||
|
@click.stop="closeTab(tab.uid)"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="tab-item">
|
||||||
|
<a
|
||||||
|
class="tab-add"
|
||||||
|
:title="$t('message.openNewTab')"
|
||||||
|
@click="addTab"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-24px mdi-plus" />
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<WorkspaceTableTab
|
<WorkspaceTableTab
|
||||||
v-show="selectedTab === 1"
|
v-show="selectedTab === 'data'"
|
||||||
:connection="connection"
|
:connection="connection"
|
||||||
:table="workspace.breadcrumbs.table"
|
:table="workspace.breadcrumbs.table"
|
||||||
/>
|
/>
|
||||||
<WorkspaceQueryTab
|
<WorkspaceQueryTab
|
||||||
v-for="tab of queryTabs"
|
v-for="tab of queryTabs"
|
||||||
v-show="selectedTab === tab.uid"
|
|
||||||
:key="tab.uid"
|
:key="tab.uid"
|
||||||
|
:tab-uid="tab.uid"
|
||||||
|
:is-selected="selectedTab === tab.uid"
|
||||||
:connection="connection"
|
:connection="connection"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,7 +100,7 @@ export default {
|
|||||||
return this.selectedWorkspace === this.connection.uid;
|
return this.selectedWorkspace === this.connection.uid;
|
||||||
},
|
},
|
||||||
selectedTab () {
|
selectedTab () {
|
||||||
return this.workspace.selected_tab || this.queryTabs[0].uid;
|
return this.queryTabs.find(tab => tab.uid === this.workspace.selected_tab) || ['data', 'prop'].includes(this.workspace.selected_tab) ? this.workspace.selected_tab : this.queryTabs[0].uid;
|
||||||
},
|
},
|
||||||
queryTabs () {
|
queryTabs () {
|
||||||
return this.workspace.tabs.filter(tab => tab.type === 'query');
|
return this.workspace.tabs.filter(tab => tab.type === 'query');
|
||||||
@@ -80,102 +112,141 @@ export default {
|
|||||||
if (isInitiated)
|
if (isInitiated)
|
||||||
this.connectWorkspace(this.connection);
|
this.connectWorkspace(this.connection);
|
||||||
},
|
},
|
||||||
|
mounted () {
|
||||||
|
if (this.$refs.tabWrap) {
|
||||||
|
this.$refs.tabWrap.addEventListener('wheel', e => {
|
||||||
|
if (e.deltaY > 0) this.$refs.tabWrap.scrollLeft += 50;
|
||||||
|
else this.$refs.tabWrap.scrollLeft -= 50;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
addNotification: 'notifications/addNotification',
|
|
||||||
addWorkspace: 'workspaces/addWorkspace',
|
addWorkspace: 'workspaces/addWorkspace',
|
||||||
connectWorkspace: 'workspaces/connectWorkspace',
|
connectWorkspace: 'workspaces/connectWorkspace',
|
||||||
removeConnected: 'workspaces/removeConnected',
|
removeConnected: 'workspaces/removeConnected',
|
||||||
selectTab: 'workspaces/selectTab'
|
selectTab: 'workspaces/selectTab',
|
||||||
})
|
newTab: 'workspaces/newTab',
|
||||||
|
removeTab: 'workspaces/removeTab'
|
||||||
|
}),
|
||||||
|
addTab () {
|
||||||
|
this.newTab(this.connection.uid);
|
||||||
|
},
|
||||||
|
closeTab (tUid) {
|
||||||
|
if (this.queryTabs.length === 1) return;
|
||||||
|
this.removeTab({ uid: this.connection.uid, tab: tUid });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.workspace{
|
.workspace {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
.workspace-tabs{
|
.workspace-tabs {
|
||||||
|
overflow: hidden;
|
||||||
|
height: calc(100vh - #{$excluding-size});
|
||||||
|
|
||||||
|
.tab-block {
|
||||||
|
background: $bg-color-light;
|
||||||
|
margin-top: 0;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: nowrap;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
height: calc(100vh - #{$excluding-size});
|
|
||||||
|
|
||||||
.tab-block{
|
&::-webkit-scrollbar {
|
||||||
background: $bg-color-light;
|
width: 2px;
|
||||||
margin-top: 0;
|
height: 2px;
|
||||||
|
|
||||||
.tab-item{
|
|
||||||
max-width: 12rem;
|
|
||||||
width: fit-content;
|
|
||||||
flex: initial;
|
|
||||||
|
|
||||||
&.active a{
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
> a{
|
|
||||||
padding: .2rem .8rem;
|
|
||||||
color: $body-font-color;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
opacity: .7;
|
|
||||||
transition: opacity .2s;
|
|
||||||
|
|
||||||
&:hover{
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
> span {
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-query-results{
|
.tab-item {
|
||||||
overflow: auto;
|
max-width: 12rem;
|
||||||
white-space: nowrap;
|
width: fit-content;
|
||||||
|
flex: initial;
|
||||||
|
|
||||||
.table{
|
> a {
|
||||||
width: auto;
|
padding: 0.2rem 0.8rem;
|
||||||
border-collapse: separate;
|
color: $body-font-color;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0.7;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
.th{
|
&:hover {
|
||||||
position: sticky;
|
opacity: 1;
|
||||||
top: 0;
|
}
|
||||||
background: $bg-color;
|
|
||||||
border: 1px solid;
|
|
||||||
border-left: none;
|
|
||||||
border-bottom-width: 2px;
|
|
||||||
border-color: $bg-color-light;
|
|
||||||
padding: .1rem .4rem;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: .7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.td{
|
&.tab-add {
|
||||||
border-right: 1px solid;
|
padding: 0.2rem 0.4rem;
|
||||||
border-bottom: 1px solid;
|
margin-top: 2px;
|
||||||
border-color: $bg-color-light;
|
border: 0;
|
||||||
padding: 0 .4rem;
|
}
|
||||||
text-overflow: ellipsis;
|
|
||||||
max-width: 200px;
|
> span {
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: .7rem;
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
padding: 0 0.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:focus{
|
&.active a {
|
||||||
box-shadow:inset 0px 0px 0px 1px $body-font-color;
|
opacity: 1;
|
||||||
background: rgba($color: #000000, $alpha: .3);
|
}
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.workspace-query-results {
|
||||||
|
overflow: auto;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.table {
|
||||||
|
width: auto;
|
||||||
|
border-collapse: separate;
|
||||||
|
|
||||||
|
.th {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: $bg-color;
|
||||||
|
border: 1px solid;
|
||||||
|
border-left: none;
|
||||||
|
border-bottom-width: 2px;
|
||||||
|
border-color: $bg-color-light;
|
||||||
|
padding: 0;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
> div {
|
||||||
|
padding: 0.1rem 0.4rem;
|
||||||
|
min-width: -webkit-fill-available;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.td {
|
||||||
|
border-right: 1px solid;
|
||||||
|
border-bottom: 1px solid;
|
||||||
|
border-color: $bg-color-light;
|
||||||
|
padding: 0 0.4rem;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
max-width: 200px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
box-shadow: inset 0 0 0 1px $body-font-color;
|
||||||
|
background: rgba($color: #000, $alpha: 0.3);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
<div class="columns">
|
<div class="columns">
|
||||||
<div class="column col-12 empty text-light">
|
<div class="column col-12 empty text-light">
|
||||||
<div class="empty-icon">
|
<div class="empty-icon">
|
||||||
<i class="material-icons md-48">cloud_off</i>
|
<i class="mdi mdi-48px mdi-power-plug-off" />
|
||||||
</div>
|
</div>
|
||||||
<p class="empty-title h5">
|
<p class="empty-title h5">
|
||||||
{{ $t('word.disconnected') }}
|
{{ $t('word.disconnected') }}
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ModalAskCredentials
|
<ModalAskCredentials
|
||||||
v-if="isAsking"
|
v-if="isAsking"
|
||||||
@closeAsking="closeAsking"
|
@close-asking="closeAsking"
|
||||||
@credentials="continueTest"
|
@credentials="continueTest"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +45,6 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
addNotification: 'notifications/addNotification',
|
|
||||||
connectWorkspace: 'workspaces/connectWorkspace'
|
connectWorkspace: 'workspaces/connectWorkspace'
|
||||||
}),
|
}),
|
||||||
async startConnection () {
|
async startConnection () {
|
||||||
@@ -73,11 +72,11 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.empty{
|
.empty {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -10,16 +10,16 @@
|
|||||||
<span class="workspace-explorebar-title">{{ connectionName }}</span>
|
<span class="workspace-explorebar-title">{{ connectionName }}</span>
|
||||||
<span v-if="workspace.connected" class="workspace-explorebar-tools">
|
<span v-if="workspace.connected" class="workspace-explorebar-tools">
|
||||||
<i
|
<i
|
||||||
class="material-icons md-18 c-hand"
|
class="mdi mdi-18px mdi-refresh c-hand"
|
||||||
:class="{'rotate':isRefreshing}"
|
:class="{'rotate':isRefreshing}"
|
||||||
:title="$t('word.refresh')"
|
:title="$t('word.refresh')"
|
||||||
@click="refresh"
|
@click="refresh"
|
||||||
>refresh</i>
|
/>
|
||||||
<i
|
<i
|
||||||
class="material-icons md-18 c-hand mr-1 ml-2"
|
class="mdi mdi-18px mdi-power-plug-off c-hand mr-1 ml-2"
|
||||||
:title="$t('word.disconnect')"
|
:title="$t('word.disconnect')"
|
||||||
@click="disconnectWorkspace(connection.uid)"
|
@click="disconnectWorkspace(connection.uid)"
|
||||||
>exit_to_app</i>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<WorkspaceConnectPanel
|
<WorkspaceConnectPanel
|
||||||
@@ -123,70 +123,70 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.workspace-explorebar-resizer{
|
.workspace-explorebar-resizer {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 4px;
|
width: 4px;
|
||||||
right: -2px;
|
right: -2px;
|
||||||
top: 0;
|
top: 0;
|
||||||
height: calc(100vh - #{$excluding-size});
|
height: calc(100vh - #{$excluding-size});
|
||||||
cursor: ew-resize;
|
cursor: ew-resize;
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-explorebar{
|
.workspace-explorebar {
|
||||||
width: $explorebar-width;
|
width: $explorebar-width;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
text-align: left;
|
||||||
|
background: $bg-color-gray;
|
||||||
|
box-shadow: 0 0 1px 0 #000;
|
||||||
|
z-index: 8;
|
||||||
|
flex: initial;
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
.workspace-explorebar-header {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.3rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
justify-content: space-between;
|
||||||
justify-content: flex-start;
|
font-size: 0.6rem;
|
||||||
align-items: center;
|
font-weight: 700;
|
||||||
text-align: left;
|
text-transform: uppercase;
|
||||||
background: $bg-color-gray;
|
|
||||||
box-shadow: 0 0 1px 0px #000;
|
|
||||||
z-index: 8;
|
|
||||||
flex: initial;
|
|
||||||
position: relative;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
.workspace-explorebar-header{
|
.workspace-explorebar-title {
|
||||||
width: 100%;
|
width: 80%;
|
||||||
padding: .3rem;
|
white-space: nowrap;
|
||||||
display: flex;
|
overflow: hidden;
|
||||||
justify-content: space-between;
|
text-overflow: ellipsis;
|
||||||
font-size: .6rem;
|
display: block;
|
||||||
font-weight: 700;
|
align-items: center;
|
||||||
text-transform: uppercase;
|
|
||||||
|
|
||||||
.workspace-explorebar-title{
|
|
||||||
width: 80%;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
display: block;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-explorebar-tools {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
> i{
|
|
||||||
opacity: .6;
|
|
||||||
transition: opacity .2s;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
&:hover{
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.workspace-explorebar-body{
|
.workspace-explorebar-tools {
|
||||||
width: 100%;
|
display: flex;
|
||||||
height: calc((100vh - 30px) - #{$excluding-size});
|
align-items: center;
|
||||||
overflow: overlay;
|
|
||||||
padding: 0 .1rem;
|
> i {
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.workspace-explorebar-body {
|
||||||
|
width: 100%;
|
||||||
|
height: calc((100vh - 30px) - #{$excluding-size});
|
||||||
|
overflow: overlay;
|
||||||
|
padding: 0 0.1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -5,8 +5,8 @@
|
|||||||
:class="{'text-bold': breadcrumbs.schema === database.name}"
|
:class="{'text-bold': breadcrumbs.schema === database.name}"
|
||||||
@click="changeBreadcrumbs({schema: database.name, table:null})"
|
@click="changeBreadcrumbs({schema: database.name, table:null})"
|
||||||
>
|
>
|
||||||
<i class="icon material-icons md-18 mr-1">navigate_next</i>
|
<i class="icon mdi mdi-18px mdi-chevron-right" />
|
||||||
<i class="material-icons md-18 mr-1">view_agenda</i>
|
<i class="database-icon mdi mdi-18px mdi-database mr-1" />
|
||||||
<span>{{ database.name }}</span>
|
<span>{{ database.name }}</span>
|
||||||
</summary>
|
</summary>
|
||||||
<div class="accordion-body">
|
<div class="accordion-body">
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
@click="changeBreadcrumbs({schema: database.name, table: table.TABLE_NAME})"
|
@click="changeBreadcrumbs({schema: database.name, table: table.TABLE_NAME})"
|
||||||
>
|
>
|
||||||
<a class="table-name">
|
<a class="table-name">
|
||||||
<i class="material-icons md-18 mr-1">grid_on</i>
|
<i class="table-icon mdi mdi-18px mdi-table mr-1" />
|
||||||
<span>{{ table.TABLE_NAME }}</span>
|
<span>{{ table.TABLE_NAME }}</span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
@@ -56,35 +56,40 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.workspace-explorebar-database{
|
.workspace-explorebar-database {
|
||||||
.database-name,
|
.database-name,
|
||||||
a.table-name{
|
a.table-name {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: .1rem;
|
padding: 0.1rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: .7rem;
|
font-size: 0.7rem;
|
||||||
|
|
||||||
> span{
|
> span {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
display: block;
|
display: block;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
|
||||||
|
|
||||||
&:hover{
|
|
||||||
color: $body-font-color;
|
|
||||||
background: rgba($color: #FFF, $alpha: .05);
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-item{
|
&:hover {
|
||||||
line-height: 1.2;
|
color: $body-font-color;
|
||||||
|
background: rgba($color: #fff, $alpha: 0.05);
|
||||||
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.database-tables{
|
.database-icon,
|
||||||
margin-left: 1.2rem;
|
.table-icon {
|
||||||
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.database-tables {
|
||||||
|
margin-left: 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,27 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="workspace-query-tab column col-12 columns col-gapless">
|
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
|
||||||
<div class="workspace-query-runner column col-12">
|
<div class="workspace-query-runner column col-12">
|
||||||
<QueryEditor v-model="query" />
|
<QueryEditor v-if="isSelected" :value.sync="query" />
|
||||||
<div class="workspace-query-runner-footer">
|
<div class="workspace-query-runner-footer">
|
||||||
<div class="workspace-query-buttons">
|
<div class="workspace-query-buttons">
|
||||||
<button
|
<button
|
||||||
class="btn btn-link btn-sm"
|
class="btn btn-link btn-sm"
|
||||||
:class="{'loading':isQuering}"
|
:class="{'loading':isQuering}"
|
||||||
:disabled="!query"
|
:disabled="!query"
|
||||||
@click="runQuery"
|
@click="runQuery(query)"
|
||||||
>
|
>
|
||||||
<span>{{ $t('word.run') }}</span>
|
<span>{{ $t('word.run') }}</span>
|
||||||
<i class="material-icons text-success">play_arrow</i>
|
<i class="mdi mdi-24px mdi-play text-success" />
|
||||||
</button>
|
</button>
|
||||||
<!-- <button class="btn btn-link btn-sm">
|
|
||||||
<span>{{ $t('word.save') }}</span>
|
|
||||||
<i class="material-icons ml-1">save</i>
|
|
||||||
</button> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<div class="workspace-query-info">
|
||||||
<div v-if="results.rows">
|
<div v-if="results.rows">
|
||||||
{{ $t('word.results') }}: <b>{{ results.rows.length }}</b>
|
{{ $t('word.results') }}: <b>{{ results.rows.length }}</b>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="results.report">
|
||||||
|
{{ $t('message.affectedRows') }}: <b>{{ results.report.affectedRows }}</b>
|
||||||
|
</div>
|
||||||
<div v-if="workspace.breadcrumbs.schema">
|
<div v-if="workspace.breadcrumbs.schema">
|
||||||
{{ $t('word.schema') }}: <b>{{ workspace.breadcrumbs.schema }}</b>
|
{{ $t('word.schema') }}: <b>{{ workspace.breadcrumbs.schema }}</b>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,8 +30,12 @@
|
|||||||
<div class="workspace-query-results column col-12">
|
<div class="workspace-query-results column col-12">
|
||||||
<WorkspaceQueryTable
|
<WorkspaceQueryTable
|
||||||
v-if="results"
|
v-if="results"
|
||||||
|
v-show="!isQuering"
|
||||||
|
ref="queryTable"
|
||||||
:results="results"
|
:results="results"
|
||||||
:fields="resultsFields"
|
:tab-uid="tabUid"
|
||||||
|
@update-field="updateField"
|
||||||
|
@delete-selected="deleteSelected"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -40,9 +43,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Connection from '@/ipc-api/Connection';
|
import Connection from '@/ipc-api/Connection';
|
||||||
|
import Tables from '@/ipc-api/Tables';
|
||||||
import QueryEditor from '@/components/QueryEditor';
|
import QueryEditor from '@/components/QueryEditor';
|
||||||
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
|
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
|
||||||
import { mapGetters, mapActions } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
|
import tableTabs from '@/mixins/tableTabs';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WorkspaceQueryTab',
|
name: 'WorkspaceQueryTab',
|
||||||
@@ -50,14 +55,19 @@ export default {
|
|||||||
QueryEditor,
|
QueryEditor,
|
||||||
WorkspaceQueryTable
|
WorkspaceQueryTable
|
||||||
},
|
},
|
||||||
|
mixins: [tableTabs],
|
||||||
props: {
|
props: {
|
||||||
connection: Object
|
connection: Object,
|
||||||
|
tabUid: String,
|
||||||
|
isSelected: Boolean
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
query: '',
|
query: '',
|
||||||
|
lastQuery: '',
|
||||||
isQuering: false,
|
isQuering: false,
|
||||||
results: {}
|
results: {},
|
||||||
|
selectedFields: []
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -67,31 +77,87 @@ export default {
|
|||||||
workspace () {
|
workspace () {
|
||||||
return this.getWorkspace(this.connection.uid);
|
return this.getWorkspace(this.connection.uid);
|
||||||
},
|
},
|
||||||
resultsFields () {
|
table () {
|
||||||
return this.results.rows && this.results.rows.length ? Object.keys(this.results.rows[0]).map(field => {
|
if ('fields' in this.results && this.results.fields.length)
|
||||||
return { name: field, key: '', type: '' }; // TODO: extract getting table name from query
|
return this.results.fields[0].orgTable;
|
||||||
}) : [];
|
return '';
|
||||||
|
},
|
||||||
|
schema () {
|
||||||
|
if ('fields' in this.results && this.results.fields.length)
|
||||||
|
return this.results.fields[0].db;
|
||||||
|
return this.workspace.breadcrumbs.schema;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
addNotification: 'notifications/addNotification'
|
addNotification: 'notifications/addNotification',
|
||||||
|
setTabFields: 'workspaces/setTabFields',
|
||||||
|
setTabKeyUsage: 'workspaces/setTabKeyUsage'
|
||||||
}),
|
}),
|
||||||
async runQuery () {
|
async runQuery (query) {
|
||||||
if (!this.query) return;
|
if (!query) return;
|
||||||
this.isQuering = true;
|
this.isQuering = true;
|
||||||
this.results = {};
|
this.clearTabData();
|
||||||
|
|
||||||
try {
|
try { // Query Data
|
||||||
const params = {
|
const params = {
|
||||||
uid: this.connection.uid,
|
uid: this.connection.uid,
|
||||||
query: this.query,
|
schema: this.schema,
|
||||||
schema: this.workspace.breadcrumbs.schema
|
query
|
||||||
};
|
};
|
||||||
|
|
||||||
const { status, response } = await Connection.rawQuery(params);
|
const { status, response } = await Connection.rawQuery(params);
|
||||||
if (status === 'success')
|
if (status === 'success') {
|
||||||
this.results = response;
|
this.results = response;
|
||||||
|
if (response.rows) { // if is a select
|
||||||
|
this.selectedFields = response.fields.map(field => field.orgName);
|
||||||
|
|
||||||
|
try { // Table data
|
||||||
|
const params = {
|
||||||
|
uid: this.connection.uid,
|
||||||
|
schema: this.schema,
|
||||||
|
table: this.table
|
||||||
|
};
|
||||||
|
|
||||||
|
const { status, response } = await Tables.getTableColumns(params);
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
let fields = response.filter(field => this.selectedFields.includes(field.name));
|
||||||
|
if (this.selectedFields.length) {
|
||||||
|
fields = fields.map((field, index) => {
|
||||||
|
return { ...field, alias: this.results.fields[index].name };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
try { // Key usage (foreign keys)
|
||||||
|
const params = {
|
||||||
|
uid: this.connection.uid,
|
||||||
|
schema: this.schema,
|
||||||
|
table: this.table
|
||||||
|
};
|
||||||
|
|
||||||
|
const { status, response } = await Tables.getKeyUsage(params);
|
||||||
|
if (status === 'success')
|
||||||
|
this.setTabKeyUsage({ cUid: this.connection.uid, tUid: this.tabUid, keyUsage: response });
|
||||||
|
else
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // if is a query without results
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
this.addNotification({ status: 'error', message: response });
|
this.addNotification({ status: 'error', message: response });
|
||||||
}
|
}
|
||||||
@@ -100,43 +166,50 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.isQuering = false;
|
this.isQuering = false;
|
||||||
|
this.lastQuery = query;
|
||||||
|
},
|
||||||
|
reloadTable () {
|
||||||
|
this.runQuery(this.lastQuery);
|
||||||
|
},
|
||||||
|
clearTabData () {
|
||||||
|
this.results = {};
|
||||||
|
this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields: [] });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.workspace-tabs{
|
.workspace-tabs {
|
||||||
align-content: baseline;
|
align-content: baseline;
|
||||||
|
|
||||||
.workspace-query-runner{
|
.workspace-query-runner {
|
||||||
|
.workspace-query-runner-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.3rem 0.6rem 0.4rem;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.workspace-query-runner-footer{
|
.workspace-query-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
padding: .3rem .6rem .4rem;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.workspace-query-buttons{
|
.btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-self: center;
|
||||||
.btn{
|
color: $body-font-color;
|
||||||
display: flex;
|
margin-right: 0.4rem;
|
||||||
align-self: center;
|
}
|
||||||
color: $body-font-color;
|
|
||||||
margin-right: .4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-query-info{
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
> div + div{
|
|
||||||
padding-left: .6rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.workspace-query-info {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> div + div {
|
||||||
|
padding-left: 0.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
@@ -1,120 +1,144 @@
|
|||||||
<template>
|
<template>
|
||||||
<BaseVirtualScroll
|
<div
|
||||||
v-if="results.rows"
|
ref="tableWrapper"
|
||||||
ref="resultTable"
|
|
||||||
:items="localResults"
|
|
||||||
:item-height="25"
|
|
||||||
class="vscroll"
|
class="vscroll"
|
||||||
:style="{'height': resultsSize+'px'}"
|
:style="{'height': resultsSize+'px'}"
|
||||||
>
|
>
|
||||||
<template slot-scope="{ items }">
|
<TableContext
|
||||||
<div class="table table-hover">
|
v-if="isContext"
|
||||||
<div class="thead">
|
:context-event="contextEvent"
|
||||||
<div class="tr">
|
:selected-rows="selectedRows"
|
||||||
<div
|
@delete-selected="deleteSelected"
|
||||||
v-for="field in fields"
|
@close-context="isContext = false"
|
||||||
:key="field.name"
|
/>
|
||||||
class="th"
|
<div ref="table" class="table table-hover">
|
||||||
>
|
<div class="thead">
|
||||||
<div class="table-column-title">
|
<div class="tr">
|
||||||
|
<div
|
||||||
|
v-for="field in fields"
|
||||||
|
:key="field.name"
|
||||||
|
class="th c-hand"
|
||||||
|
>
|
||||||
|
<div ref="columnResize" class="column-resizable">
|
||||||
|
<div class="table-column-title" @click="sort(field.name)">
|
||||||
<i
|
<i
|
||||||
v-if="field.key"
|
v-if="field.key"
|
||||||
class="material-icons column-key c-help"
|
class="mdi mdi-key column-key c-help"
|
||||||
:class="`key-${field.key}`"
|
:class="`key-${field.key}`"
|
||||||
:title="keyName(field.key)"
|
:title="keyName(field.key)"
|
||||||
>vpn_key</i>
|
/>
|
||||||
<span>{{ field.name }}</span>
|
<span>{{ field.alias || field.name }}</span>
|
||||||
|
<i
|
||||||
|
v-if="currentSort === field.name"
|
||||||
|
class="mdi sort-icon"
|
||||||
|
:class="currentSortDir === 'asc' ? 'mdi-sort-ascending':'mdi-sort-descending'"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tbody">
|
</div>
|
||||||
<div
|
<BaseVirtualScroll
|
||||||
|
v-if="results.rows"
|
||||||
|
ref="resultTable"
|
||||||
|
:items="sortedResults"
|
||||||
|
:item-height="22"
|
||||||
|
class="tbody"
|
||||||
|
:visible-height="resultsSize"
|
||||||
|
:scroll-element="scrollElement"
|
||||||
|
>
|
||||||
|
<template slot-scope="{ items }">
|
||||||
|
<WorkspaceQueryTableRow
|
||||||
v-for="row in items"
|
v-for="row in items"
|
||||||
:key="row._id"
|
:key="row._id"
|
||||||
|
:row="row"
|
||||||
|
:fields="fields"
|
||||||
|
:key-usage="keyUsage"
|
||||||
class="tr"
|
class="tr"
|
||||||
>
|
:class="{'selected': selectedRows.includes(row._id)}"
|
||||||
<div
|
@select-row="selectRow($event, row._id)"
|
||||||
v-for="(col, cKey) in row"
|
@update-field="updateField($event, row[primaryField.alias || primaryField.name])"
|
||||||
:key="cKey"
|
@contextmenu="contextMenu"
|
||||||
class="td"
|
/>
|
||||||
:class="`type-${fieldType(cKey)}${isNull(col)}`"
|
</template>
|
||||||
:style="{'display': cKey === '_id' ? 'none' : ''}"
|
</basevirtualscroll>
|
||||||
tabindex="0"
|
</div>
|
||||||
>
|
</div>
|
||||||
{{ col | typeFormat(fieldType(cKey)) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</BaseVirtualScroll>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { uidGen, mimeFromHex, formatBytes } from 'common/libs/utilities';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
import hexToBinary from 'common/libs/hexToBinary';
|
|
||||||
import moment from 'moment';
|
|
||||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
||||||
|
import WorkspaceQueryTableRow from '@/components/WorkspaceQueryTableRow';
|
||||||
|
import TableContext from '@/components/WorkspaceQueryTableContext';
|
||||||
|
import { mapActions, mapGetters } from 'vuex';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WorkspaceQueryTable',
|
name: 'WorkspaceQueryTable',
|
||||||
components: {
|
components: {
|
||||||
BaseVirtualScroll
|
BaseVirtualScroll,
|
||||||
},
|
WorkspaceQueryTableRow,
|
||||||
filters: {
|
TableContext
|
||||||
typeFormat (val, type) {
|
|
||||||
if (!val) return val;
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'char':
|
|
||||||
case 'varchar':
|
|
||||||
case 'text':
|
|
||||||
case 'mediumtext':
|
|
||||||
return val.substring(0, 128);
|
|
||||||
case 'date':
|
|
||||||
return moment(val).format('YYYY-MM-DD');
|
|
||||||
case 'datetime':
|
|
||||||
case 'timestamp':
|
|
||||||
return moment(val).format('YYYY-MM-DD HH:mm:ss.SSS');
|
|
||||||
case 'blob':
|
|
||||||
case 'mediumblob':
|
|
||||||
case 'longblob': {
|
|
||||||
const buff = Buffer.from(val);
|
|
||||||
if (!buff.length) return '';
|
|
||||||
|
|
||||||
const hex = buff.toString('hex').substring(0, 8).toUpperCase();
|
|
||||||
return `${mimeFromHex(hex).mime} (${formatBytes(buff.length)})`;
|
|
||||||
}
|
|
||||||
case 'bit': {
|
|
||||||
const hex = Buffer.from(val).toString('hex');
|
|
||||||
return hexToBinary(hex);
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
results: Object,
|
results: Object,
|
||||||
fields: Array
|
tabUid: [String, Number]
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
resultsSize: 1000,
|
resultsSize: 1000,
|
||||||
localResults: []
|
localResults: [],
|
||||||
|
isContext: false,
|
||||||
|
contextEvent: null,
|
||||||
|
selectedCell: null,
|
||||||
|
selectedRows: [],
|
||||||
|
currentSort: '',
|
||||||
|
currentSortDir: 'asc'
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
getWorkspaceTab: 'workspaces/getWorkspaceTab'
|
||||||
|
}),
|
||||||
|
primaryField () {
|
||||||
|
return this.fields.filter(field => ['pri', 'uni'].includes(field.key))[0] || false;
|
||||||
|
},
|
||||||
|
sortedResults () {
|
||||||
|
if (this.currentSort) {
|
||||||
|
return [...this.localResults].sort((a, b) => {
|
||||||
|
let modifier = 1;
|
||||||
|
const valA = typeof a[this.currentSort] === 'string' ? a[this.currentSort].toLowerCase() : a[this.currentSort];
|
||||||
|
const valB = typeof b[this.currentSort] === 'string' ? b[this.currentSort].toLowerCase() : b[this.currentSort];
|
||||||
|
if (this.currentSortDir === 'desc') modifier = -1;
|
||||||
|
if (valA < valB) return -1 * modifier;
|
||||||
|
if (valA > valB) return 1 * modifier;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return this.localResults;
|
||||||
|
},
|
||||||
|
fields () {
|
||||||
|
return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).fields : [];
|
||||||
|
},
|
||||||
|
keyUsage () {
|
||||||
|
return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).keyUsage : [];
|
||||||
|
},
|
||||||
|
scrollElement () {
|
||||||
|
return this.$refs.tableWrapper;
|
||||||
|
}
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
results () {
|
results () {
|
||||||
|
this.resetSort();
|
||||||
this.localResults = this.results.rows ? this.results.rows.map(item => {
|
this.localResults = this.results.rows ? this.results.rows.map(item => {
|
||||||
return { ...item, _id: uidGen() };
|
return { ...item, _id: uidGen() };
|
||||||
}) : [];
|
}) : [];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updated () {
|
updated () {
|
||||||
if (this.$refs.resultTable)
|
if (this.$refs.table)
|
||||||
this.resizeResults();
|
this.refreshScroller();
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
window.addEventListener('resize', this.resizeResults);
|
window.addEventListener('resize', this.resizeResults);
|
||||||
@@ -123,6 +147,9 @@ export default {
|
|||||||
window.removeEventListener('resize', this.resizeResults);
|
window.removeEventListener('resize', this.resizeResults);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
...mapActions({
|
||||||
|
addNotification: 'notifications/addNotification'
|
||||||
|
}),
|
||||||
fieldType (cKey) {
|
fieldType (cKey) {
|
||||||
let type = 'unknown';
|
let type = 'unknown';
|
||||||
const field = this.fields.filter(field => field.name === cKey)[0];
|
const field = this.fields.filter(field => field.name === cKey)[0];
|
||||||
@@ -131,8 +158,13 @@ export default {
|
|||||||
|
|
||||||
return type;
|
return type;
|
||||||
},
|
},
|
||||||
isNull (col) {
|
fieldPrecision (cKey) {
|
||||||
return col === null ? ' is-null' : '';
|
let length = 0;
|
||||||
|
const field = this.fields.filter(field => field.name === cKey)[0];
|
||||||
|
if (field)
|
||||||
|
length = field.datePrecision;
|
||||||
|
|
||||||
|
return length;
|
||||||
},
|
},
|
||||||
keyName (key) {
|
keyName (key) {
|
||||||
switch (key) {
|
switch (key) {
|
||||||
@@ -146,16 +178,103 @@ export default {
|
|||||||
return 'UNKNOWN ' + key;
|
return 'UNKNOWN ' + key;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
resizeResults (e) {
|
resizeResults () {
|
||||||
if (this.$refs.resultTable) {
|
if (this.$refs.resultTable) {
|
||||||
const el = this.$refs.resultTable.$el;
|
const el = this.$refs.tableWrapper;
|
||||||
const footer = document.getElementById('footer');
|
|
||||||
|
|
||||||
if (el) {
|
if (el) {
|
||||||
|
const footer = document.getElementById('footer');
|
||||||
const size = window.innerHeight - el.getBoundingClientRect().top - footer.offsetHeight;
|
const size = window.innerHeight - el.getBoundingClientRect().top - footer.offsetHeight;
|
||||||
this.resultsSize = size;
|
this.resultsSize = size;
|
||||||
}
|
}
|
||||||
|
this.$refs.resultTable.updateWindow();
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
refreshScroller () {
|
||||||
|
this.resizeResults();
|
||||||
|
},
|
||||||
|
updateField (payload, id) {
|
||||||
|
if (!this.primaryField)
|
||||||
|
this.addNotification({ status: 'warning', message: this.$t('message.unableEditFieldWithoutPrimary') });
|
||||||
|
else {
|
||||||
|
const params = {
|
||||||
|
primary: this.primaryField.name,
|
||||||
|
id,
|
||||||
|
...payload
|
||||||
|
};
|
||||||
|
this.$emit('update-field', params);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteSelected () {
|
||||||
|
if (!this.primaryField)
|
||||||
|
this.addNotification({ status: 'warning', message: this.$t('message.unableEditFieldWithoutPrimary') });
|
||||||
|
else {
|
||||||
|
const rowIDs = this.localResults.filter(row => this.selectedRows.includes(row._id)).map(row => row[this.primaryField.name]);
|
||||||
|
const params = {
|
||||||
|
primary: this.primaryField.name,
|
||||||
|
rows: rowIDs
|
||||||
|
};
|
||||||
|
this.$emit('delete-selected', params);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
applyUpdate (params) {
|
||||||
|
const { primary, id, field, content } = params;
|
||||||
|
this.localResults = this.localResults.map(row => {
|
||||||
|
if (row[primary] === id)
|
||||||
|
row[field] = content;
|
||||||
|
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
selectRow (event, row) {
|
||||||
|
if (event.ctrlKey) {
|
||||||
|
if (this.selectedRows.includes(row))
|
||||||
|
this.selectedRows = this.selectedRows.filter(el => el !== row);
|
||||||
|
else
|
||||||
|
this.selectedRows.push(row);
|
||||||
|
}
|
||||||
|
else if (event.shiftKey) {
|
||||||
|
if (!this.selectedRows.length)
|
||||||
|
this.selectedRows.push(row);
|
||||||
|
else {
|
||||||
|
const lastID = this.selectedRows.slice(-1)[0];
|
||||||
|
const lastIndex = this.localResults.findIndex(el => el._id === lastID);
|
||||||
|
const clickedIndex = this.localResults.findIndex(el => el._id === row);
|
||||||
|
if (lastIndex > clickedIndex) {
|
||||||
|
for (let i = clickedIndex; i < lastIndex; i++)
|
||||||
|
this.selectedRows.push(this.localResults[i]._id);
|
||||||
|
}
|
||||||
|
else if (lastIndex < clickedIndex) {
|
||||||
|
for (let i = clickedIndex; i > lastIndex; i--)
|
||||||
|
this.selectedRows.push(this.localResults[i]._id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.selectedRows = [row];
|
||||||
|
},
|
||||||
|
contextMenu (event, cell) {
|
||||||
|
this.selectedCell = cell;
|
||||||
|
if (!this.selectedRows.includes(cell.id))
|
||||||
|
this.selectedRows = [cell.id];
|
||||||
|
this.contextEvent = event;
|
||||||
|
this.isContext = true;
|
||||||
|
},
|
||||||
|
sort (field) {
|
||||||
|
if (field === this.currentSort) {
|
||||||
|
if (this.currentSortDir === 'asc')
|
||||||
|
this.currentSortDir = 'desc';
|
||||||
|
else
|
||||||
|
this.resetSort();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.currentSortDir = 'asc';
|
||||||
|
this.currentSort = field;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resetSort () {
|
||||||
|
this.currentSort = '';
|
||||||
|
this.currentSortDir = 'asc';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -163,32 +282,27 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.vscroll {
|
.vscroll {
|
||||||
height: 1000px;
|
height: 1000px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overflow-anchor: none;
|
overflow-anchor: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-column-title{
|
.column-resizable {
|
||||||
display: flex;
|
&:hover,
|
||||||
align-items: center;
|
&:active {
|
||||||
|
resize: horizontal;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.column-key{
|
.table-column-title {
|
||||||
transform: rotate(90deg);
|
display: flex;
|
||||||
font-size: .7rem;
|
align-items: center;
|
||||||
line-height: 1.5;
|
}
|
||||||
margin-right: .2rem;
|
|
||||||
|
|
||||||
&.key-pri{
|
.sort-icon {
|
||||||
color: goldenrod;
|
font-size: 0.7rem;
|
||||||
}
|
line-height: 1;
|
||||||
|
margin-left: 0.2rem;
|
||||||
&.key-uni{
|
|
||||||
color: deepskyblue;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.key-mul{
|
|
||||||
color: palegreen;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
71
src/renderer/components/WorkspaceQueryTableContext.vue
Normal file
71
src/renderer/components/WorkspaceQueryTableContext.vue
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<BaseContextMenu
|
||||||
|
:context-event="contextEvent"
|
||||||
|
@close-context="closeContext"
|
||||||
|
>
|
||||||
|
<div class="context-element" @click="showConfirmModal">
|
||||||
|
<i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $tc('message.deleteRows', selectedRows.length) }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
v-if="isConfirmModal"
|
||||||
|
@confirm="deleteRows"
|
||||||
|
@hide="hideConfirmModal"
|
||||||
|
>
|
||||||
|
<template :slot="'header'">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-delete mr-1" /> {{ $tc('message.deleteRows', selectedRows.length) }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div :slot="'body'">
|
||||||
|
<div class="mb-2">
|
||||||
|
{{ $tc('message.confirmToDeleteRows', selectedRows.length) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ConfirmModal>
|
||||||
|
</BaseContextMenu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapActions } from 'vuex';
|
||||||
|
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||||
|
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WorkspaceQueryTableContext',
|
||||||
|
components: {
|
||||||
|
BaseContextMenu,
|
||||||
|
ConfirmModal
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
contextEvent: MouseEvent,
|
||||||
|
selectedRows: Array
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isConfirmModal: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions({
|
||||||
|
deleteConnection: 'connections/deleteConnection',
|
||||||
|
showEditModal: 'application/showEditConnModal'
|
||||||
|
}),
|
||||||
|
showConfirmModal () {
|
||||||
|
this.isConfirmModal = true;
|
||||||
|
},
|
||||||
|
hideConfirmModal () {
|
||||||
|
this.isConfirmModal = false;
|
||||||
|
},
|
||||||
|
closeContext () {
|
||||||
|
this.$emit('close-context');
|
||||||
|
},
|
||||||
|
deleteRows () {
|
||||||
|
this.$emit('delete-selected');
|
||||||
|
this.closeContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
442
src/renderer/components/WorkspaceQueryTableRow.vue
Normal file
442
src/renderer/components/WorkspaceQueryTableRow.vue
Normal file
@@ -0,0 +1,442 @@
|
|||||||
|
<template>
|
||||||
|
<div class="tr" @click="selectRow($event, row._id)">
|
||||||
|
<div
|
||||||
|
v-for="(col, cKey) in row"
|
||||||
|
v-show="cKey !== '_id'"
|
||||||
|
:key="cKey"
|
||||||
|
class="td p-0"
|
||||||
|
tabindex="0"
|
||||||
|
@contextmenu.prevent="$emit('contextmenu', $event, {id: row._id, field: cKey})"
|
||||||
|
>
|
||||||
|
<template v-if="cKey !== '_id'">
|
||||||
|
<span
|
||||||
|
v-if="!isInlineEditor[cKey]"
|
||||||
|
class="cell-content px-2"
|
||||||
|
:class="`${isNull(col)} type-${getFieldType(cKey)}`"
|
||||||
|
@dblclick="editON($event, col, cKey)"
|
||||||
|
>{{ col | typeFormat(getFieldType(cKey), getFieldPrecision(cKey)) | cutText }}</span>
|
||||||
|
<ForeignKeySelect
|
||||||
|
v-else-if="foreignKeys.includes(cKey)"
|
||||||
|
class="editable-field"
|
||||||
|
:value.sync="editingContent"
|
||||||
|
:key-usage="getKeyUsage(cKey)"
|
||||||
|
@blur="editOFF"
|
||||||
|
/>
|
||||||
|
<template v-else>
|
||||||
|
<input
|
||||||
|
v-if="inputProps.mask"
|
||||||
|
ref="editField"
|
||||||
|
v-model="editingContent"
|
||||||
|
v-mask="inputProps.mask"
|
||||||
|
:type="inputProps.type"
|
||||||
|
autofocus
|
||||||
|
class="editable-field px-2"
|
||||||
|
@blur="editOFF"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-else
|
||||||
|
ref="editField"
|
||||||
|
v-model="editingContent"
|
||||||
|
:type="inputProps.type"
|
||||||
|
autofocus
|
||||||
|
class="editable-field px-2"
|
||||||
|
@blur="editOFF"
|
||||||
|
>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<ConfirmModal
|
||||||
|
v-if="isTextareaEditor"
|
||||||
|
:confirm-text="$t('word.update')"
|
||||||
|
size="medium"
|
||||||
|
@confirm="editOFF"
|
||||||
|
@hide="hideEditorModal"
|
||||||
|
>
|
||||||
|
<template :slot="'header'">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-playlist-edit mr-1" /> {{ $t('word.edit') }} "{{ editingField }}"
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div :slot="'body'">
|
||||||
|
<div class="mb-2">
|
||||||
|
<div>
|
||||||
|
<textarea
|
||||||
|
v-model="editingContent"
|
||||||
|
class="form-input textarea-editor"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="editor-field-info">
|
||||||
|
<div><b>{{ $t('word.size') }}</b>: {{ editingContent.length }}</div>
|
||||||
|
<div><b>{{ $t('word.type') }}</b>: {{ editingType.toUpperCase() }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ConfirmModal>
|
||||||
|
<ConfirmModal
|
||||||
|
v-if="isBlobEditor"
|
||||||
|
:confirm-text="$t('word.update')"
|
||||||
|
@confirm="editOFF"
|
||||||
|
@hide="hideEditorModal"
|
||||||
|
>
|
||||||
|
<template :slot="'header'">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-playlist-edit mr-1" /> {{ $t('word.edit') }} "{{ editingField }}"
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div :slot="'body'">
|
||||||
|
<div class="mb-2">
|
||||||
|
<transition name="jump-down">
|
||||||
|
<div v-if="contentInfo.size">
|
||||||
|
<img
|
||||||
|
v-if="isImage"
|
||||||
|
:src="`data:${contentInfo.mime};base64, ${bufferToBase64(editingContent)}`"
|
||||||
|
class="img-responsive p-centered bg-checkered"
|
||||||
|
>
|
||||||
|
<div v-else class="text-center">
|
||||||
|
<i class="mdi mdi-36px mdi-file" />
|
||||||
|
</div>
|
||||||
|
<div class="editor-buttons mt-2">
|
||||||
|
<button class="btn btn-link btn-sm" @click="downloadFile">
|
||||||
|
<span>{{ $t('word.download') }}</span>
|
||||||
|
<i class="mdi mdi-24px mdi-download ml-1" />
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-link btn-sm" @click="prepareToDelete">
|
||||||
|
<span>{{ $t('word.delete') }}</span>
|
||||||
|
<i class="mdi mdi-24px mdi-delete-forever ml-1" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
<div class="editor-field-info">
|
||||||
|
<div>
|
||||||
|
<b>{{ $t('word.size') }}</b>: {{ editingContent.length | formatBytes }}<br>
|
||||||
|
<b>{{ $t('word.mimeType') }}</b>: {{ contentInfo.mime }}
|
||||||
|
</div>
|
||||||
|
<div><b>{{ $t('word.type') }}</b>: {{ editingType.toUpperCase() }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3">
|
||||||
|
<label>{{ $t('message.uploadFile') }}</label>
|
||||||
|
<input
|
||||||
|
class="form-input"
|
||||||
|
type="file"
|
||||||
|
@change="filesChange($event)"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ConfirmModal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import moment from 'moment';
|
||||||
|
import { mimeFromHex } from 'common/libs/mimeFromHex';
|
||||||
|
import { formatBytes } from 'common/libs/formatBytes';
|
||||||
|
import { bufferToBase64 } from 'common/libs/bufferToBase64';
|
||||||
|
import hexToBinary from 'common/libs/hexToBinary';
|
||||||
|
import { TEXT, LONG_TEXT, NUMBER, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
||||||
|
import { mask } from 'vue-the-mask';
|
||||||
|
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||||
|
import ForeignKeySelect from '@/components/ForeignKeySelect';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WorkspaceQueryTableRow',
|
||||||
|
components: {
|
||||||
|
ConfirmModal,
|
||||||
|
ForeignKeySelect
|
||||||
|
},
|
||||||
|
directives: {
|
||||||
|
mask
|
||||||
|
},
|
||||||
|
filters: {
|
||||||
|
formatBytes,
|
||||||
|
cutText (val) {
|
||||||
|
if (typeof val !== 'string') return val;
|
||||||
|
return val.length > 128 ? `${val.substring(0, 128)}[...]` : val;
|
||||||
|
},
|
||||||
|
typeFormat (val, type, precision) {
|
||||||
|
if (!val) return val;
|
||||||
|
|
||||||
|
if (DATE.includes(type))
|
||||||
|
return moment(val).isValid() ? moment(val).format('YYYY-MM-DD') : val;
|
||||||
|
|
||||||
|
if (DATETIME.includes(type)) {
|
||||||
|
let datePrecision = '';
|
||||||
|
for (let i = 0; i < precision; i++)
|
||||||
|
datePrecision += i === 0 ? '.S' : 'S';
|
||||||
|
|
||||||
|
return moment(val).isValid() ? moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`) : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BLOB.includes(type)) {
|
||||||
|
const buff = Buffer.from(val);
|
||||||
|
if (!buff.length) return '';
|
||||||
|
|
||||||
|
const hex = buff.toString('hex').substring(0, 8).toUpperCase();
|
||||||
|
return `${mimeFromHex(hex).mime} (${formatBytes(buff.length)})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BIT.includes(type)) {
|
||||||
|
const hex = Buffer.from(val).toString('hex');
|
||||||
|
return hexToBinary(hex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
row: Object,
|
||||||
|
fields: Array,
|
||||||
|
keyUsage: Array
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isInlineEditor: {},
|
||||||
|
isTextareaEditor: false,
|
||||||
|
isBlobEditor: false,
|
||||||
|
willBeDeleted: false,
|
||||||
|
originalContent: null,
|
||||||
|
editingContent: null,
|
||||||
|
editingType: null,
|
||||||
|
editingField: null,
|
||||||
|
contentInfo: {
|
||||||
|
ext: '',
|
||||||
|
mime: '',
|
||||||
|
size: null
|
||||||
|
},
|
||||||
|
fileToUpload: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
inputProps () {
|
||||||
|
if ([...TEXT, ...LONG_TEXT].includes(this.editingType))
|
||||||
|
return { type: 'text', mask: false };
|
||||||
|
|
||||||
|
if (NUMBER.includes(this.editingType))
|
||||||
|
return { type: 'number', mask: false };
|
||||||
|
|
||||||
|
if (TIME.includes(this.editingType)) {
|
||||||
|
let timeMask = '##:##:##';
|
||||||
|
const precision = this.getFieldPrecision(this.editingField);
|
||||||
|
|
||||||
|
for (let i = 0; i < precision; i++)
|
||||||
|
timeMask += i === 0 ? '.#' : '#';
|
||||||
|
|
||||||
|
return { type: 'text', mask: timeMask };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DATE.includes(this.editingType))
|
||||||
|
return { type: 'text', mask: '####-##-##' };
|
||||||
|
|
||||||
|
if (DATETIME.includes(this.editingType)) {
|
||||||
|
let datetimeMask = '####-##-## ##:##:##';
|
||||||
|
const precision = this.getFieldPrecision(this.editingField);
|
||||||
|
|
||||||
|
for (let i = 0; i < precision; i++)
|
||||||
|
datetimeMask += i === 0 ? '.#' : '#';
|
||||||
|
|
||||||
|
return { type: 'text', mask: datetimeMask };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BLOB.includes(this.editingType))
|
||||||
|
return { type: 'file', mask: false };
|
||||||
|
|
||||||
|
if (BIT.includes(this.editingType))
|
||||||
|
return { type: 'text', mask: false };
|
||||||
|
|
||||||
|
return { type: 'text', mask: false };
|
||||||
|
},
|
||||||
|
isImage () {
|
||||||
|
return ['gif', 'jpg', 'png', 'bmp', 'ico', 'tif'].includes(this.contentInfo.ext);
|
||||||
|
},
|
||||||
|
foreignKeys () {
|
||||||
|
return this.keyUsage.map(key => key.column);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.fields.forEach(field => {
|
||||||
|
this.isInlineEditor[field.name] = false;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getFieldType (cKey) {
|
||||||
|
let type = 'unknown';
|
||||||
|
const field = this.getFieldObj(cKey);
|
||||||
|
if (field)
|
||||||
|
type = field.type;
|
||||||
|
|
||||||
|
return type;
|
||||||
|
},
|
||||||
|
getFieldPrecision (cKey) {
|
||||||
|
let length = 0;
|
||||||
|
const field = this.getFieldObj(cKey);
|
||||||
|
if (field)
|
||||||
|
length = field.datePrecision;
|
||||||
|
|
||||||
|
return length;
|
||||||
|
},
|
||||||
|
getFieldObj (cKey) {
|
||||||
|
return this.fields.filter(field => field.name === cKey || field.alias === cKey)[0];
|
||||||
|
},
|
||||||
|
isNull (value) {
|
||||||
|
return value === null ? ' is-null' : '';
|
||||||
|
},
|
||||||
|
bufferToBase64 (val) {
|
||||||
|
return bufferToBase64(val);
|
||||||
|
},
|
||||||
|
editON (event, content, field) {
|
||||||
|
const type = this.getFieldType(field);
|
||||||
|
this.originalContent = content;
|
||||||
|
this.editingType = type;
|
||||||
|
this.editingField = field;
|
||||||
|
|
||||||
|
if (LONG_TEXT.includes(type)) {
|
||||||
|
this.isTextareaEditor = true;
|
||||||
|
this.editingContent = this.$options.filters.typeFormat(this.originalContent, type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BLOB.includes(type)) {
|
||||||
|
this.isBlobEditor = true;
|
||||||
|
this.editingContent = this.originalContent || '';
|
||||||
|
this.fileToUpload = null;
|
||||||
|
this.willBeDeleted = false;
|
||||||
|
|
||||||
|
if (this.originalContent !== null) {
|
||||||
|
const buff = Buffer.from(this.editingContent);
|
||||||
|
if (buff.length) {
|
||||||
|
const hex = buff.toString('hex').substring(0, 8).toUpperCase();
|
||||||
|
const { ext, mime } = mimeFromHex(hex);
|
||||||
|
this.contentInfo = {
|
||||||
|
ext,
|
||||||
|
mime,
|
||||||
|
size: this.editingContent.length
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inline editable fields
|
||||||
|
this.editingContent = this.$options.filters.typeFormat(this.originalContent, type, this.getFieldPrecision(field));
|
||||||
|
this.$nextTick(() => { // Focus on input
|
||||||
|
event.target.blur();
|
||||||
|
|
||||||
|
this.$nextTick(() => document.querySelector('.editable-field').focus());
|
||||||
|
});
|
||||||
|
|
||||||
|
const obj = {
|
||||||
|
[field]: true
|
||||||
|
};
|
||||||
|
this.isInlineEditor = { ...this.isInlineEditor, ...obj };
|
||||||
|
},
|
||||||
|
editOFF () {
|
||||||
|
this.isInlineEditor[this.editingField] = false;
|
||||||
|
let content;
|
||||||
|
if (!BLOB.includes(this.editingType)) {
|
||||||
|
if (this.editingContent === this.$options.filters.typeFormat(this.originalContent, this.editingType)) return;// If not changed
|
||||||
|
content = this.editingContent;
|
||||||
|
}
|
||||||
|
else { // Handle file upload
|
||||||
|
if (this.willBeDeleted) {
|
||||||
|
content = '';
|
||||||
|
this.willBeDeleted = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!this.fileToUpload) return;
|
||||||
|
content = this.fileToUpload.file.path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('update-field', {
|
||||||
|
field: this.getFieldObj(this.editingField).name,
|
||||||
|
type: this.editingType,
|
||||||
|
content
|
||||||
|
});
|
||||||
|
|
||||||
|
this.editingType = null;
|
||||||
|
this.editingField = null;
|
||||||
|
},
|
||||||
|
hideEditorModal () {
|
||||||
|
this.isTextareaEditor = false;
|
||||||
|
this.isBlobEditor = false;
|
||||||
|
},
|
||||||
|
downloadFile () {
|
||||||
|
const downloadLink = document.createElement('a');
|
||||||
|
|
||||||
|
downloadLink.href = `data:${this.contentInfo.mime};base64, ${bufferToBase64(this.editingContent)}`;
|
||||||
|
downloadLink.setAttribute('download', `${this.editingField}.${this.contentInfo.ext}`);
|
||||||
|
document.body.appendChild(downloadLink);
|
||||||
|
|
||||||
|
downloadLink.click();
|
||||||
|
downloadLink.remove();
|
||||||
|
},
|
||||||
|
filesChange (event) {
|
||||||
|
const { files } = event.target;
|
||||||
|
if (!files.length) return;
|
||||||
|
|
||||||
|
this.fileToUpload = { name: files[0].name, file: files[0] };
|
||||||
|
this.willBeDeleted = false;
|
||||||
|
},
|
||||||
|
prepareToDelete () {
|
||||||
|
this.editingContent = '';
|
||||||
|
this.contentInfo = {
|
||||||
|
ext: '',
|
||||||
|
mime: '',
|
||||||
|
size: null
|
||||||
|
};
|
||||||
|
this.willBeDeleted = true;
|
||||||
|
},
|
||||||
|
contextMenu (event, cell) {
|
||||||
|
this.$emit('update-field', event, cell);
|
||||||
|
},
|
||||||
|
selectRow (event, row) {
|
||||||
|
this.$emit('select-row', event, row);
|
||||||
|
},
|
||||||
|
getKeyUsage (keyName) {
|
||||||
|
return this.keyUsage.find(key => key.column === keyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.editable-field {
|
||||||
|
margin: 0;
|
||||||
|
border: none;
|
||||||
|
line-height: 1;
|
||||||
|
width: 100%;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell-content {
|
||||||
|
display: block;
|
||||||
|
min-height: 0.8rem;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea-editor {
|
||||||
|
height: 50vh !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-field-info {
|
||||||
|
margin-top: 0.6rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@@ -6,15 +6,19 @@
|
|||||||
<button
|
<button
|
||||||
class="btn btn-link btn-sm"
|
class="btn btn-link btn-sm"
|
||||||
:class="{'loading':isQuering}"
|
:class="{'loading':isQuering}"
|
||||||
@click="getTableData"
|
@click="reloadTable"
|
||||||
>
|
>
|
||||||
<span>{{ $t('word.refresh') }}</span>
|
<span>{{ $t('word.refresh') }}</span>
|
||||||
<i class="material-icons ml-1">refresh</i>
|
<i class="mdi mdi-24px mdi-refresh ml-1" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-link btn-sm"
|
||||||
|
:class="{'disabled':isQuering}"
|
||||||
|
@click="showAddModal"
|
||||||
|
>
|
||||||
|
<span>{{ $t('word.add') }}</span>
|
||||||
|
<i class="mdi mdi-24px mdi-playlist-plus ml-1" />
|
||||||
</button>
|
</button>
|
||||||
<!-- <button class="btn btn-link btn-sm">
|
|
||||||
<span>{{ $t('word.save') }}</span>
|
|
||||||
<i class="material-icons ml-1">save</i>
|
|
||||||
</button> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<div class="workspace-query-info">
|
||||||
<div v-if="results.rows">
|
<div v-if="results.rows">
|
||||||
@@ -29,33 +33,49 @@
|
|||||||
<div class="workspace-query-results column col-12">
|
<div class="workspace-query-results column col-12">
|
||||||
<WorkspaceQueryTable
|
<WorkspaceQueryTable
|
||||||
v-if="results"
|
v-if="results"
|
||||||
|
ref="queryTable"
|
||||||
:results="results"
|
:results="results"
|
||||||
:fields="resultsFields"
|
:tab-uid="tabUid"
|
||||||
|
@update-field="updateField"
|
||||||
|
@delete-selected="deleteSelected"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<ModalNewTableRow
|
||||||
|
v-if="isAddModal"
|
||||||
|
:tab-uid="tabUid"
|
||||||
|
@hide="hideAddModal"
|
||||||
|
@reload="reloadTable"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Structure from '@/ipc-api/Structure';
|
import Tables from '@/ipc-api/Tables';
|
||||||
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
|
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
|
||||||
|
import ModalNewTableRow from '@/components/ModalNewTableRow';
|
||||||
import { mapGetters, mapActions } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
|
import tableTabs from '@/mixins/tableTabs';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WorkspaceTableTab',
|
name: 'WorkspaceTableTab',
|
||||||
components: {
|
components: {
|
||||||
WorkspaceQueryTable
|
WorkspaceQueryTable,
|
||||||
|
ModalNewTableRow
|
||||||
},
|
},
|
||||||
|
mixins: [tableTabs],
|
||||||
props: {
|
props: {
|
||||||
connection: Object,
|
connection: Object,
|
||||||
table: String
|
table: String
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
tabUid: 'data',
|
||||||
isQuering: false,
|
isQuering: false,
|
||||||
results: {},
|
results: {},
|
||||||
fields: [],
|
fields: [],
|
||||||
lastTable: null
|
keyUsage: [],
|
||||||
|
lastTable: null,
|
||||||
|
isAddModal: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -66,26 +86,17 @@ export default {
|
|||||||
return this.getWorkspace(this.connection.uid);
|
return this.getWorkspace(this.connection.uid);
|
||||||
},
|
},
|
||||||
isSelected () {
|
isSelected () {
|
||||||
return this.workspace.selected_tab === 1;
|
return this.workspace.selected_tab === 'data';
|
||||||
},
|
|
||||||
resultsFields () {
|
|
||||||
return this.fields.map(field => { // TODO: move to main process
|
|
||||||
return {
|
|
||||||
name: field.COLUMN_NAME,
|
|
||||||
key: field.COLUMN_KEY.toLowerCase(),
|
|
||||||
type: field.DATA_TYPE
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
table: function () {
|
table () {
|
||||||
if (this.isSelected) {
|
if (this.isSelected) {
|
||||||
this.getTableData();
|
this.getTableData();
|
||||||
this.lastTable = this.table;
|
this.lastTable = this.table;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isSelected: function (val) {
|
isSelected (val) {
|
||||||
if (val && this.lastTable !== this.table) {
|
if (val && this.lastTable !== this.table) {
|
||||||
this.getTableData();
|
this.getTableData();
|
||||||
this.lastTable = this.table;
|
this.lastTable = this.table;
|
||||||
@@ -97,12 +108,15 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
addNotification: 'notifications/addNotification'
|
addNotification: 'notifications/addNotification',
|
||||||
|
setTabFields: 'workspaces/setTabFields',
|
||||||
|
setTabKeyUsage: 'workspaces/setTabKeyUsage'
|
||||||
}),
|
}),
|
||||||
async getTableData () {
|
async getTableData () {
|
||||||
if (!this.table) return;
|
if (!this.table) return;
|
||||||
this.isQuering = true;
|
this.isQuering = true;
|
||||||
this.results = {};
|
this.results = {};
|
||||||
|
this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields: [] });
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
uid: this.connection.uid,
|
uid: this.connection.uid,
|
||||||
@@ -110,10 +124,12 @@ export default {
|
|||||||
table: this.workspace.breadcrumbs.table
|
table: this.workspace.breadcrumbs.table
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try { // Columns data
|
||||||
const { status, response } = await Structure.getTableColumns(params);
|
const { status, response } = await Tables.getTableColumns(params);
|
||||||
if (status === 'success')
|
if (status === 'success') {
|
||||||
this.fields = response.rows;
|
this.fields = response;// Needed to add new rows
|
||||||
|
this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields: response });
|
||||||
|
}
|
||||||
else
|
else
|
||||||
this.addNotification({ status: 'error', message: response });
|
this.addNotification({ status: 'error', message: response });
|
||||||
}
|
}
|
||||||
@@ -121,8 +137,8 @@ export default {
|
|||||||
this.addNotification({ status: 'error', message: err.stack });
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try { // Table data
|
||||||
const { status, response } = await Structure.getTableData(params);
|
const { status, response } = await Tables.getTableData(params);
|
||||||
|
|
||||||
if (status === 'success')
|
if (status === 'success')
|
||||||
this.results = response;
|
this.results = response;
|
||||||
@@ -133,44 +149,65 @@ export default {
|
|||||||
this.addNotification({ status: 'error', message: err.stack });
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try { // Key usage (foreign keys)
|
||||||
|
const { status, response } = await Tables.getKeyUsage(params);
|
||||||
|
if (status === 'success') {
|
||||||
|
this.keyUsage = response;// Needed to add new rows
|
||||||
|
this.setTabKeyUsage({ cUid: this.connection.uid, tUid: this.tabUid, keyUsage: response });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
this.isQuering = false;
|
this.isQuering = false;
|
||||||
|
},
|
||||||
|
reloadTable () {
|
||||||
|
this.getTableData();
|
||||||
|
},
|
||||||
|
showAddModal () {
|
||||||
|
this.isAddModal = true;
|
||||||
|
},
|
||||||
|
hideAddModal () {
|
||||||
|
this.isAddModal = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.workspace-tabs{
|
.workspace-tabs {
|
||||||
align-content: baseline;
|
align-content: baseline;
|
||||||
|
|
||||||
.workspace-query-runner{
|
.workspace-query-runner {
|
||||||
|
.workspace-query-runner-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.3rem 0.6rem 0.4rem;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.workspace-query-runner-footer{
|
.workspace-query-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
padding: .3rem .6rem .4rem;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.workspace-query-buttons{
|
.btn {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-self: center;
|
||||||
.btn{
|
color: $body-font-color;
|
||||||
display: flex;
|
margin-right: 0.4rem;
|
||||||
align-self: center;
|
}
|
||||||
color: $body-font-color;
|
|
||||||
margin-right: .4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.workspace-query-info{
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
> div + div{
|
|
||||||
padding-left: .6rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.workspace-query-info {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
> div + div {
|
||||||
|
padding-left: 0.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
87
src/renderer/i18n/ar-SA.js
Normal file
87
src/renderer/i18n/ar-SA.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
module.exports = {
|
||||||
|
word: {
|
||||||
|
edit: 'تعديل',
|
||||||
|
save: 'حفظ',
|
||||||
|
close: 'إغلاق',
|
||||||
|
delete: 'حفظ',
|
||||||
|
confirm: 'تأكيد',
|
||||||
|
cancel: 'إلغاء',
|
||||||
|
send: 'إرسال',
|
||||||
|
connectionName: 'إسم الإتصال',
|
||||||
|
client: 'العميل',
|
||||||
|
hostName: 'إسم المستضيف',
|
||||||
|
port: 'المنفذ',
|
||||||
|
user: 'المستخدم',
|
||||||
|
password: 'الرقم السري',
|
||||||
|
credentials: 'بيانات الدخول',
|
||||||
|
connect: 'إتصال',
|
||||||
|
connected: 'متصل',
|
||||||
|
disconnect: 'إلغاء الإتصال',
|
||||||
|
disconnected: 'غير متصل',
|
||||||
|
refresh: 'تحديث',
|
||||||
|
settings: 'الإعدادات',
|
||||||
|
general: 'عام',
|
||||||
|
themes: 'الأنماط',
|
||||||
|
update: 'تحديث',
|
||||||
|
about: 'حول',
|
||||||
|
language: 'اللغة',
|
||||||
|
version: 'النسخة',
|
||||||
|
donate: 'إدعم',
|
||||||
|
run: 'شغل',
|
||||||
|
schema: 'Schema',
|
||||||
|
results: 'النتائج',
|
||||||
|
size: 'الحجم',
|
||||||
|
seconds: 'ثواني',
|
||||||
|
type: 'نوع',
|
||||||
|
mimeType: 'نوع الميديا',
|
||||||
|
download: 'تحميل',
|
||||||
|
add: 'أضف',
|
||||||
|
data: 'بيانات',
|
||||||
|
properties: 'خصائص',
|
||||||
|
insert: 'أدرج'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
appWelcome: 'مرحبا بك في عميل الSQL انتاريس!',
|
||||||
|
appFirstStep: 'خطوتك الأولى قم بإنشاء إتصال جديد بقاعدة بيانات.',
|
||||||
|
addConnection: 'إضافة إتصال',
|
||||||
|
createConnection: 'إنشاء إتصال',
|
||||||
|
createNewConnection: 'إنشاء إتصال جديد',
|
||||||
|
askCredentials: 'إطلب بيانات الدخول',
|
||||||
|
testConnection: 'إختبر الإتصال',
|
||||||
|
editConnection: 'عدل الإتصال',
|
||||||
|
deleteConnection: 'إحذف الإتصال',
|
||||||
|
deleteConnectionCorfirm: 'هل أنت متأكد من حذف الإتصال؟',
|
||||||
|
connectionSuccessfullyMade: 'تم الإتصال بنجاح!',
|
||||||
|
madeWithJS: 'بني بـ 💛 و جافاسكربت!',
|
||||||
|
checkForUpdates: 'تأكد من التحديثات',
|
||||||
|
noUpdatesAvailable: 'لا توجد تحديثات',
|
||||||
|
checkingForUpdate: 'البحث عن تحديثات',
|
||||||
|
checkFailure: 'فشل البحث, نرجوا المحاولة في وقت لاحق',
|
||||||
|
updateAvailable: 'تحديث جديد متوفر',
|
||||||
|
downloadingUpdate: 'جاري تحميل التحديث',
|
||||||
|
updateDownloaded: 'تم تحميل التحديث',
|
||||||
|
restartToInstall: 'قم بإعادة تشغيل انتاريس للتحديث',
|
||||||
|
unableEditFieldWithoutPrimary: 'لا يمكن تعديل الخانة بدون وجود مفتاح رئيسي في النتائج',
|
||||||
|
editCell: 'تعديل الخلية',
|
||||||
|
deleteRows: 'حذف صف | حذف {count} صفوف',
|
||||||
|
confirmToDeleteRows: 'هل أنت متأكد من حذف صف واحد؟? | هل أنت متأكد من حذف {count} صف?',
|
||||||
|
notificationsTimeout: 'إنتهاء التنبيهات',
|
||||||
|
uploadFile: 'رفع ملف',
|
||||||
|
addNewRow: 'إضافة صف جديد',
|
||||||
|
numberOfInserts: 'عدد الإدراجات'
|
||||||
|
},
|
||||||
|
// Date and Time
|
||||||
|
short: {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric'
|
||||||
|
},
|
||||||
|
long: {
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
weekday: 'short',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: 'numeric'
|
||||||
|
}
|
||||||
|
};
|
@@ -29,7 +29,16 @@ module.exports = {
|
|||||||
donate: 'Donate',
|
donate: 'Donate',
|
||||||
run: 'Run',
|
run: 'Run',
|
||||||
schema: 'Schema',
|
schema: 'Schema',
|
||||||
results: 'Results'
|
results: 'Results',
|
||||||
|
size: 'Size',
|
||||||
|
seconds: 'Seconds',
|
||||||
|
type: 'Type',
|
||||||
|
mimeType: 'Mime-Type',
|
||||||
|
download: 'Download',
|
||||||
|
add: 'Add',
|
||||||
|
data: 'Data',
|
||||||
|
properties: 'Properties',
|
||||||
|
insert: 'Insert'
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
appWelcome: 'Welcome to Antares SQL Client!',
|
appWelcome: 'Welcome to Antares SQL Client!',
|
||||||
@@ -51,7 +60,17 @@ module.exports = {
|
|||||||
updateAvailable: 'Update available',
|
updateAvailable: 'Update available',
|
||||||
downloadingUpdate: 'Downloading update',
|
downloadingUpdate: 'Downloading update',
|
||||||
updateDownloaded: 'Update downloaded',
|
updateDownloaded: 'Update downloaded',
|
||||||
restartToInstall: 'Restart Antares to install'
|
restartToInstall: 'Restart Antares to install',
|
||||||
|
unableEditFieldWithoutPrimary: 'Unable to edit a field without a primary key in resultset',
|
||||||
|
editCell: 'Edit cell',
|
||||||
|
deleteRows: 'Delete row | Delete {count} rows',
|
||||||
|
confirmToDeleteRows: 'Do you confirm to delete one row? | Do you confirm to delete {count} rows?',
|
||||||
|
notificationsTimeout: 'Notifications timeout',
|
||||||
|
uploadFile: 'Upload file',
|
||||||
|
addNewRow: 'Add new row',
|
||||||
|
numberOfInserts: 'Number of inserts',
|
||||||
|
openNewTab: 'Open a new tab',
|
||||||
|
affectedRows: 'Affected rows'
|
||||||
},
|
},
|
||||||
// Date and Time
|
// Date and Time
|
||||||
short: {
|
short: {
|
||||||
|
@@ -6,7 +6,8 @@ Vue.use(VueI18n);
|
|||||||
const i18n = new VueI18n({
|
const i18n = new VueI18n({
|
||||||
messages: {
|
messages: {
|
||||||
'en-US': require('./en-US'),
|
'en-US': require('./en-US'),
|
||||||
'it-IT': require('./it-IT')
|
'it-IT': require('./it-IT'),
|
||||||
|
'ar-SA': require('./ar-SA')
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
@@ -27,7 +27,9 @@ module.exports = {
|
|||||||
language: 'Lingua',
|
language: 'Lingua',
|
||||||
version: 'Versione',
|
version: 'Versione',
|
||||||
donate: 'Dona',
|
donate: 'Dona',
|
||||||
run: 'Esegui'
|
run: 'Esegui',
|
||||||
|
schema: 'Schema',
|
||||||
|
results: 'Results'
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
appWelcome: 'Benvenuto in Antares SQL Client!',
|
appWelcome: 'Benvenuto in Antares SQL Client!',
|
||||||
@@ -41,7 +43,19 @@ module.exports = {
|
|||||||
deleteConnection: 'Elimina connessione',
|
deleteConnection: 'Elimina connessione',
|
||||||
deleteConnectionCorfirm: 'Confermi l\'eliminazione di',
|
deleteConnectionCorfirm: 'Confermi l\'eliminazione di',
|
||||||
connectionSuccessfullyMade: 'Connessione avvenuta con successo!',
|
connectionSuccessfullyMade: 'Connessione avvenuta con successo!',
|
||||||
madeWithJS: 'Fatto con 💛 e JavaScript!'
|
madeWithJS: 'Fatto con 💛 e JavaScript!',
|
||||||
|
checkForUpdates: 'Cerca aggiornamenti',
|
||||||
|
noUpdatesAvailable: 'Nessun aggiornamento disponibile',
|
||||||
|
checkingForUpdate: 'Controllo aggiornamenti in corso',
|
||||||
|
checkFailure: 'Controllo fallito, riprova più tardi',
|
||||||
|
updateAvailable: 'Aggiornamento disponibile',
|
||||||
|
downloadingUpdate: 'Download dell\'aggiornamento',
|
||||||
|
updateDownloaded: 'Aggiornamento scaricato',
|
||||||
|
restartToInstall: 'Riavvia Antares per installare l\'aggiornamento',
|
||||||
|
unableEditFieldWithoutPrimary: 'Impossibile modificare il campo senza una primary key nel resultset',
|
||||||
|
editCell: 'Modifica cella',
|
||||||
|
deleteRows: 'Elimina riga | Elimina {count} righe',
|
||||||
|
confirmToDeleteRows: 'Confermi di voler cancellare una riga? | Confermi di voler cancellare {count} righe?'
|
||||||
},
|
},
|
||||||
// Date and Time
|
// Date and Time
|
||||||
short: {
|
short: {
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
export default {
|
export default {
|
||||||
'en-US': 'English',
|
'en-US': 'English',
|
||||||
'it-IT': 'Italiano'
|
'it-IT': 'Italiano',
|
||||||
|
'ar-SA': 'العربية'
|
||||||
};
|
};
|
||||||
|
@@ -3,11 +3,11 @@ import { ipcRenderer } from 'electron';
|
|||||||
|
|
||||||
export default class {
|
export default class {
|
||||||
static makeTest (params) {
|
static makeTest (params) {
|
||||||
return ipcRenderer.invoke('testConnection', params);
|
return ipcRenderer.invoke('test-connection', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
static checkConnection (params) {
|
static checkConnection (params) {
|
||||||
return ipcRenderer.invoke('checkConnection', params);
|
return ipcRenderer.invoke('check-connection', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
static connect (params) {
|
static connect (params) {
|
||||||
@@ -23,6 +23,6 @@ export default class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static rawQuery (params) {
|
static rawQuery (params) {
|
||||||
return ipcRenderer.invoke('rawQuery', params);
|
return ipcRenderer.invoke('raw-query', params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,12 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
import { ipcRenderer } from 'electron';
|
|
||||||
|
|
||||||
export default class {
|
|
||||||
static getTableColumns (params) {
|
|
||||||
return ipcRenderer.invoke('getTableColumns', params);
|
|
||||||
}
|
|
||||||
|
|
||||||
static getTableData (params) {
|
|
||||||
return ipcRenderer.invoke('getTableData', params);
|
|
||||||
}
|
|
||||||
}
|
|
32
src/renderer/ipc-api/Tables.js
Normal file
32
src/renderer/ipc-api/Tables.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
'use strict';
|
||||||
|
import { ipcRenderer } from 'electron';
|
||||||
|
|
||||||
|
export default class {
|
||||||
|
static getTableColumns (params) {
|
||||||
|
return ipcRenderer.invoke('get-table-columns', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getTableData (params) {
|
||||||
|
return ipcRenderer.invoke('get-table-data', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getKeyUsage (params) {
|
||||||
|
return ipcRenderer.invoke('get-key-usage', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static updateTableCell (params) {
|
||||||
|
return ipcRenderer.invoke('update-table-cell', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static deleteTableRows (params) {
|
||||||
|
return ipcRenderer.invoke('delete-table-rows', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static insertTableRows (params) {
|
||||||
|
return ipcRenderer.invoke('insert-table-rows', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getForeignList (params) {
|
||||||
|
return ipcRenderer.invoke('get-foreign-list', params);
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
|
|
||||||
import 'material-design-icons/iconfont/material-icons.css';
|
import '@mdi/font/css/materialdesignicons.css';
|
||||||
import '@/scss/main.scss';
|
import '@/scss/main.scss';
|
||||||
|
|
||||||
import App from '@/App.vue';
|
import App from '@/App.vue';
|
||||||
|
59
src/renderer/mixins/tableTabs.js
Normal file
59
src/renderer/mixins/tableTabs.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import Tables from '@/ipc-api/Tables';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
async updateField (payload) {
|
||||||
|
this.isQuering = true;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
uid: this.connection.uid,
|
||||||
|
schema: this.workspace.breadcrumbs.schema,
|
||||||
|
table: this.table,
|
||||||
|
...payload
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status, response } = await Tables.updateTableCell(params);
|
||||||
|
if (status === 'success') {
|
||||||
|
if (response.reload)// Needed for blob fields
|
||||||
|
this.reloadTable();
|
||||||
|
else
|
||||||
|
this.$refs.queryTable.applyUpdate(payload);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isQuering = false;
|
||||||
|
},
|
||||||
|
async deleteSelected (payload) {
|
||||||
|
this.isQuering = true;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
uid: this.connection.uid,
|
||||||
|
schema: this.workspace.breadcrumbs.schema,
|
||||||
|
table: this.workspace.breadcrumbs.table,
|
||||||
|
...payload
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status, response } = await Tables.deleteTableRows(params);
|
||||||
|
if (status === 'success') {
|
||||||
|
const { primary, rows } = params;
|
||||||
|
this.results = { ...this.results, rows: this.results.rows.filter(row => !rows.includes(row[primary])) };
|
||||||
|
this.$refs.queryTable.refreshScroller();// Necessary to re-render virtual scroller
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isQuering = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
@@ -1,44 +1,48 @@
|
|||||||
@mixin type-colors($types) {
|
@mixin type-colors($types) {
|
||||||
@each $type, $color in $types {
|
@each $type, $color in $types {
|
||||||
.type-#{$type} {
|
.type-#{$type} {
|
||||||
color: $color;
|
color: $color;
|
||||||
|
|
||||||
@if $type == 'number'{
|
@if $type == "number" {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include type-colors((
|
@include type-colors(
|
||||||
"char": seagreen,
|
(
|
||||||
"varchar": seagreen,
|
"char": seagreen,
|
||||||
"text": seagreen,
|
"varchar": seagreen,
|
||||||
"mediumtext": seagreen,
|
"text": seagreen,
|
||||||
|
"mediumtext": seagreen,
|
||||||
|
"longtext": seagreen,
|
||||||
|
"int": cornflowerblue,
|
||||||
|
"tinyint": cornflowerblue,
|
||||||
|
"smallint": cornflowerblue,
|
||||||
|
"mediumint": cornflowerblue,
|
||||||
|
"float": cornflowerblue,
|
||||||
|
"double": cornflowerblue,
|
||||||
|
"decimal": cornflowerblue,
|
||||||
|
"bigint": cornflowerblue,
|
||||||
|
"datetime": coral,
|
||||||
|
"date": coral,
|
||||||
|
"time": coral,
|
||||||
|
"timestamp": coral,
|
||||||
|
"bit": lightskyblue,
|
||||||
|
"blob": darkorchid,
|
||||||
|
"mediumblob": darkorchid,
|
||||||
|
"longblob": darkorchid,
|
||||||
|
"enum": gold,
|
||||||
|
"set": gold,
|
||||||
|
"unknown": gray,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
"int": cornflowerblue,
|
.is-null {
|
||||||
"tinyint": cornflowerblue,
|
color: gray;
|
||||||
"smallint": cornflowerblue,
|
|
||||||
"mediumint": cornflowerblue,
|
|
||||||
|
|
||||||
"datetime": coral,
|
|
||||||
"date": coral,
|
|
||||||
"time": coral,
|
|
||||||
"timestamp": coral,
|
|
||||||
|
|
||||||
"bit": lightskyblue,
|
&::after {
|
||||||
|
content: "NULL";
|
||||||
"blob": darkorchid,
|
}
|
||||||
"mediumblob": darkorchid,
|
}
|
||||||
"longblob": darkorchid,
|
|
||||||
|
|
||||||
"unknown": gray,
|
|
||||||
));
|
|
||||||
|
|
||||||
.is-null{
|
|
||||||
color: gray;
|
|
||||||
|
|
||||||
&::after{
|
|
||||||
content: 'NULL';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -1,26 +1,26 @@
|
|||||||
.dbi{
|
.dbi {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 42px;
|
width: 42px;
|
||||||
height: 42px;
|
height: 42px;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
|
|
||||||
&.dbi-mysql{
|
&.dbi-mysql {
|
||||||
background-image: url('../images/svg/mysql.svg');
|
background-image: url("../images/svg/mysql.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dbi-maria{
|
&.dbi-maria {
|
||||||
background-image: url('../images/svg/mariadb.svg');
|
background-image: url("../images/svg/mariadb.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dbi-mssql{
|
&.dbi-mssql {
|
||||||
background-image: url('../images/svg/mssql.svg');
|
background-image: url("../images/svg/mssql.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dbi-pg{
|
&.dbi-pg {
|
||||||
background-image: url('../images/svg/pg.svg');
|
background-image: url("../images/svg/pg.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
&.dbi-oracledb{
|
&.dbi-oracledb {
|
||||||
background-image: url('../images/svg/oracledb.svg');
|
background-image: url("../images/svg/oracledb.svg");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,65 +1,71 @@
|
|||||||
.table {
|
.table {
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: table;
|
display: table;
|
||||||
|
table-layout: fixed;
|
||||||
&.table-striped {
|
|
||||||
.tbody {
|
|
||||||
.tr:nth-of-type(odd) {
|
|
||||||
background: $bg-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&,
|
|
||||||
&.table-striped {
|
|
||||||
.tbody {
|
|
||||||
.tr {
|
|
||||||
&.active {
|
|
||||||
background: $bg-color-dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.table-hover {
|
|
||||||
.tbody {
|
|
||||||
.tr {
|
|
||||||
&:hover {
|
|
||||||
background: $bg-color-dark;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scollable tables
|
|
||||||
&.table-scroll {
|
|
||||||
display: block;
|
|
||||||
overflow-x: auto;
|
|
||||||
padding-bottom: .75rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.thead{
|
.tbody {
|
||||||
display: table-header-group;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tbody{
|
|
||||||
display: table-row-group;
|
display: table-row-group;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tr{
|
.tr {
|
||||||
display: table-row;
|
display: table-row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.td,
|
// Scollable tables
|
||||||
.th {
|
&.table-scroll {
|
||||||
border-bottom: $border-width solid $border-color;
|
display: block;
|
||||||
padding: $unit-3 $unit-2;
|
overflow-x: auto;
|
||||||
display: table-cell;
|
padding-bottom: 0.75rem;
|
||||||
}
|
white-space: nowrap;
|
||||||
.th {
|
}
|
||||||
border-bottom-width: $border-width-lg;
|
|
||||||
}
|
.thead {
|
||||||
}
|
display: table-header-group;
|
||||||
|
}
|
||||||
|
|
||||||
|
.td,
|
||||||
|
.th {
|
||||||
|
border-bottom: $border-width solid $border-color;
|
||||||
|
padding: $unit-3 $unit-2;
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
.th {
|
||||||
|
border-bottom-width: $border-width-lg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&,
|
||||||
|
&.table-striped {
|
||||||
|
.tbody {
|
||||||
|
.tr {
|
||||||
|
&.selected {
|
||||||
|
background: #333 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: $bg-color-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.table-hover {
|
||||||
|
.tbody {
|
||||||
|
.tr {
|
||||||
|
&:hover {
|
||||||
|
background: $bg-color-dark;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.table-striped {
|
||||||
|
.tbody {
|
||||||
|
.tr:nth-of-type(odd) {
|
||||||
|
background: $bg-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,16 +1,9 @@
|
|||||||
|
.mdi {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
.material-icons{// TODO: rewrite with rem
|
&::before {
|
||||||
/* Rules for sizing the icon. */
|
line-height: 1;
|
||||||
&.md-18 { font-size: 18px; }
|
}
|
||||||
&.md-24 { font-size: 24px; }
|
}
|
||||||
&.md-36 { font-size: 36px; }
|
|
||||||
&.md-48 { font-size: 48px; }
|
|
||||||
|
|
||||||
/* Rules for using icons as black on a light background. */
|
|
||||||
&.md-dark { color: rgba(0, 0, 0, 0.54); }
|
|
||||||
&.md-dark.md-inactive { color: rgba(0, 0, 0, 0.26); }
|
|
||||||
|
|
||||||
/* Rules for using icons as white on a dark background. */
|
|
||||||
&.md-light { color: rgba(255, 255, 255, 1); }
|
|
||||||
&.md-light.md-inactive { color: rgba(255, 255, 255, 0.3); }
|
|
||||||
}
|
|
||||||
|
18
src/renderer/scss/_table-keys.scss
Normal file
18
src/renderer/scss/_table-keys.scss
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
.column-key {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-right: 0.2rem;
|
||||||
|
|
||||||
|
&.key-pri {
|
||||||
|
color: goldenrod;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.key-uni {
|
||||||
|
color: deepskyblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.key-mul {
|
||||||
|
color: palegreen;
|
||||||
|
}
|
||||||
|
}
|
@@ -1,10 +1,41 @@
|
|||||||
.slide-fade-enter-active {
|
.slide-fade-enter-active {
|
||||||
transition: all .3s ease;
|
transition: all 0.3s ease;
|
||||||
}
|
}
|
||||||
.slide-fade-leave-active {
|
|
||||||
transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
|
.slide-fade-leave-active {
|
||||||
}
|
transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
|
||||||
.slide-fade-enter, .slide-fade-leave-to {
|
}
|
||||||
transform: translateX(10px);
|
|
||||||
opacity: 0;
|
.slide-fade-enter,
|
||||||
}
|
.slide-fade-leave-to {
|
||||||
|
transform: translateX(10px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jump-down-enter-active {
|
||||||
|
animation: jump-down-in 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.jump-down-leave-active {
|
||||||
|
animation: jump-down-in 0.2s reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes jump-down-in {
|
||||||
|
0% {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
/*Colors*/
|
/* Colors */
|
||||||
$body-bg: #1d1d1d;
|
$body-bg: #1d1d1d;
|
||||||
$body-font-color: #fff;
|
$body-font-color: #fff;
|
||||||
$bg-color: #1d1d1d;
|
$bg-color: #1d1d1d;
|
||||||
@@ -7,10 +7,11 @@ $bg-color-gray: #272727;
|
|||||||
$primary-color: #e36929;
|
$primary-color: #e36929;
|
||||||
$success-color: #32b643;
|
$success-color: #32b643;
|
||||||
$error-color: #de3b28;
|
$error-color: #de3b28;
|
||||||
|
$warning-color: #e0a40c;
|
||||||
|
|
||||||
/*Sizes*/
|
/* Sizes */
|
||||||
$titlebar-height: 1.5rem;
|
$titlebar-height: 1.5rem;
|
||||||
$settingbar-width: 3rem;
|
$settingbar-width: 3rem;
|
||||||
$explorebar-width: 14rem;
|
$explorebar-width: 14rem;
|
||||||
$footer-height: 1.5rem;
|
$footer-height: 1.5rem;
|
||||||
$excluding-size: $footer-height + $titlebar-height;
|
$excluding-size: $footer-height + $titlebar-height;
|
||||||
|
@@ -1,150 +1,175 @@
|
|||||||
|
|
||||||
@import "~spectre.css/src/variables";
|
@import "~spectre.css/src/variables";
|
||||||
@import "variables";
|
@import "variables";
|
||||||
@import "transitions";
|
@import "transitions";
|
||||||
@import "data-types";
|
@import "data-types";
|
||||||
|
@import "table-keys";
|
||||||
@import "fake-tables";
|
@import "fake-tables";
|
||||||
@import "mdi-additions";
|
@import "mdi-additions";
|
||||||
@import "db-icons";
|
@import "db-icons";
|
||||||
@import "~spectre.css/src/spectre";
|
@import "~spectre.css/src/spectre";
|
||||||
@import "~spectre.css/src/spectre-exp";
|
@import "~spectre.css/src/spectre-exp";
|
||||||
|
|
||||||
body{
|
body {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*Additions*/
|
/* Additions */
|
||||||
@include margin-variant(3, $unit-3);
|
@include margin-variant(3, $unit-3);
|
||||||
@include margin-variant(4, $unit-4);
|
@include margin-variant(4, $unit-4);
|
||||||
@include padding-variant(3, $unit-3);
|
@include padding-variant(3, $unit-3);
|
||||||
@include padding-variant(4, $unit-4);
|
@include padding-variant(4, $unit-4);
|
||||||
|
|
||||||
.btn.btn-gray{
|
.btn.btn-gray {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: $bg-color-gray;
|
background: $bg-color-gray;
|
||||||
|
|
||||||
&:hover{
|
&:hover {
|
||||||
background: $bg-color;
|
background: $bg-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-vcentered{
|
.p-vcentered {
|
||||||
display: flex!important;
|
display: flex !important;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.c-help{
|
.c-help {
|
||||||
cursor: help;
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-checkered {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(to right, rgba(192, 192, 192, 0.75), rgba(192, 192, 192, 0.75)),
|
||||||
|
linear-gradient(to right, black 50%, white 50%),
|
||||||
|
linear-gradient(to bottom, black 50%, white 50%);
|
||||||
|
background-blend-mode: normal, difference, normal;
|
||||||
|
background-size: 2em 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scrollbars
|
// Scrollbars
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background: $bg-color-light;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background: rgba($color: #FFF, $alpha: .5);
|
|
||||||
|
|
||||||
&:hover {
|
::-webkit-scrollbar-track {
|
||||||
background: rgba($color: #FFF, $alpha: 1);
|
background: $bg-color-light;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba($color: #fff, $alpha: 0.5);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba($color: #fff, $alpha: 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Animations
|
// Animations
|
||||||
@keyframes rotation {
|
@keyframes rotation {
|
||||||
from {
|
from {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
}
|
}
|
||||||
to {
|
|
||||||
transform: rotate(359deg);
|
to {
|
||||||
}
|
transform: rotate(359deg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.rotate {
|
.rotate {
|
||||||
animation: rotation .8s infinite linear;
|
animation: rotation 0.8s infinite linear;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*Override*/
|
/* Override */
|
||||||
.modal{
|
.modal {
|
||||||
.modal-overlay,
|
.modal-overlay,
|
||||||
&.active .modal-overlay{
|
&.active .modal-overlay {
|
||||||
background: rgba(255, 255, 255, 0.15);
|
background: rgba(255, 255, 255, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-sm .modal-container,
|
.modal-container,
|
||||||
.modal-container{
|
.modal-sm .modal-container {
|
||||||
box-shadow: 0 0 1px 0px #000;
|
box-shadow: 0 0 1px 0 #000;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: $bg-color;
|
background: $bg-color;
|
||||||
|
|
||||||
.modal-header{
|
.modal-header {
|
||||||
padding: .4rem .8rem;
|
padding: 0.4rem 0.8rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
background: $bg-color-gray;
|
background: $bg-color-gray;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: #FFF;
|
color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tab{
|
.tab {
|
||||||
border-color: #272727;
|
border-color: #272727;
|
||||||
|
|
||||||
|
.tab-item {
|
||||||
|
.btn-clear {
|
||||||
|
margin-top: -0.1rem;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel{
|
.panel {
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.badge{
|
.badge {
|
||||||
&[data-badge],
|
&[data-badge],
|
||||||
&:not([data-badge]){
|
&:not([data-badge]) {
|
||||||
&::after {
|
&::after {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-select{
|
.form-select {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-select,
|
.form-select,
|
||||||
.form-select:not([multiple]):not([size]),
|
|
||||||
.form-input,
|
.form-input,
|
||||||
.form-checkbox .form-icon,
|
.form-select:not([multiple]):not([size]),
|
||||||
.form-radio .form-icon{
|
.form-checkbox .form-icon,
|
||||||
border-color: $bg-color-light;
|
.form-radio .form-icon {
|
||||||
background: $bg-color-gray;
|
border-color: $bg-color-light;
|
||||||
|
background-color: $bg-color-gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-select:not([multiple]):not([size]):focus{
|
.form-input:not(:placeholder-shown):invalid:focus {
|
||||||
border-color: $primary-color;
|
background: $bg-color-gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu{
|
.form-select:not([multiple]):not([size]):focus {
|
||||||
font-size: .7rem;
|
border-color: $primary-color;
|
||||||
.menu-item {
|
}
|
||||||
+ .menu-item{
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
.input-group .input-group-addon {
|
||||||
|
border-color: #3f3f3f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
|
||||||
|
.menu-item {
|
||||||
|
+ .menu-item {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.accordion-body {
|
.accordion-body {
|
||||||
max-height: 500rem!important;
|
max-height: 500rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn.loading {
|
.btn.loading {
|
||||||
> .material-icons,
|
> .mdi,
|
||||||
> span{
|
> span {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -24,7 +24,7 @@ export default {
|
|||||||
isSettingModal: state => state.is_setting_modal,
|
isSettingModal: state => state.is_setting_modal,
|
||||||
selectedSettingTab: state => state.selected_setting_tab,
|
selectedSettingTab: state => state.selected_setting_tab,
|
||||||
getUpdateStatus: state => state.update_status,
|
getUpdateStatus: state => state.update_status,
|
||||||
getDownloadProgress: state => state.download_progress
|
getDownloadProgress: state => Number(state.download_progress.toFixed(1))
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
SET_LOADING_STATUS (state, payload) {
|
SET_LOADING_STATUS (state, payload) {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import { uidGen } from 'common/libs/utilities';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
@@ -20,7 +20,7 @@ export default {
|
|||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
addNotification ({ commit }, payload) {
|
addNotification ({ commit }, payload) {
|
||||||
payload.uid = uidGen();
|
payload.uid = uidGen('N');
|
||||||
commit('ADD_NOTIFICATION', payload);
|
commit('ADD_NOTIFICATION', payload);
|
||||||
},
|
},
|
||||||
removeNotification ({ commit }, uid) {
|
removeNotification ({ commit }, uid) {
|
||||||
|
@@ -6,17 +6,22 @@ export default {
|
|||||||
strict: true,
|
strict: true,
|
||||||
state: {
|
state: {
|
||||||
locale: 'en-US',
|
locale: 'en-US',
|
||||||
explorebar_size: null
|
explorebar_size: null,
|
||||||
|
notifications_timeout: 10
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
getLocale: state => state.locale,
|
getLocale: state => state.locale,
|
||||||
getExplorebarSize: state => state.explorebar_size
|
getExplorebarSize: state => state.explorebar_size,
|
||||||
|
getNotificationsTimeout: state => state.notifications_timeout
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
SET_LOCALE (state, locale) {
|
SET_LOCALE (state, locale) {
|
||||||
state.locale = locale;
|
state.locale = locale;
|
||||||
i18n.locale = locale;
|
i18n.locale = locale;
|
||||||
},
|
},
|
||||||
|
SET_NOTIFICATIONS_TIMEOUT (state, timeout) {
|
||||||
|
state.notifications_timeout = timeout;
|
||||||
|
},
|
||||||
SET_EXPLOREBAR_SIZE (state, size) {
|
SET_EXPLOREBAR_SIZE (state, size) {
|
||||||
state.explorebar_size = size;
|
state.explorebar_size = size;
|
||||||
}
|
}
|
||||||
@@ -25,6 +30,9 @@ export default {
|
|||||||
changeLocale ({ commit }, locale) {
|
changeLocale ({ commit }, locale) {
|
||||||
commit('SET_LOCALE', locale);
|
commit('SET_LOCALE', locale);
|
||||||
},
|
},
|
||||||
|
updateNotificationsTimeout ({ commit }, timeout) {
|
||||||
|
commit('SET_NOTIFICATIONS_TIMEOUT', timeout);
|
||||||
|
},
|
||||||
changeExplorebarSize ({ commit }, size) {
|
changeExplorebarSize ({ commit }, size) {
|
||||||
commit('SET_EXPLOREBAR_SIZE', size);
|
commit('SET_EXPLOREBAR_SIZE', size);
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
import Connection from '@/ipc-api/Connection';
|
import Connection from '@/ipc-api/Connection';
|
||||||
import { uidGen } from 'common/libs/utilities';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
|
const tabIndex = [];
|
||||||
|
|
||||||
function remapStructure (structure) {
|
function remapStructure (structure) { // TODO: move to main process and add fields (for autocomplete purpose)
|
||||||
const databases = structure.map(table => table.TABLE_SCHEMA)
|
const databases = structure.map(table => table.TABLE_SCHEMA)
|
||||||
.filter((value, index, self) => self.indexOf(value) === index);
|
.filter((value, index, self) => self.indexOf(value) === index);
|
||||||
|
|
||||||
@@ -28,8 +29,14 @@ export default {
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
getWorkspace: state => uid => {
|
getWorkspace: state => uid => {
|
||||||
const workspace = state.workspaces.filter(workspace => workspace.uid === uid);
|
return state.workspaces.find(workspace => workspace.uid === uid);
|
||||||
return workspace.length ? workspace[0] : {};
|
},
|
||||||
|
getWorkspaceTab: (state, getters) => tUid => {
|
||||||
|
if (!getters.getSelected) return;
|
||||||
|
const workspace = state.workspaces.find(workspace => workspace.uid === getters.getSelected);
|
||||||
|
if ('tabs' in workspace)
|
||||||
|
return workspace.tabs.find(tab => tab.uid === tUid);
|
||||||
|
return {};
|
||||||
},
|
},
|
||||||
getConnected: state => {
|
getConnected: state => {
|
||||||
return state.workspaces
|
return state.workspaces
|
||||||
@@ -57,10 +64,15 @@ export default {
|
|||||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, breadcrumbs } : workspace);
|
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, breadcrumbs } : workspace);
|
||||||
},
|
},
|
||||||
NEW_TAB (state, uid) {
|
NEW_TAB (state, uid) {
|
||||||
|
tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1;
|
||||||
|
|
||||||
const newTab = {
|
const newTab = {
|
||||||
uid: uidGen(),
|
uid: uidGen('T'),
|
||||||
|
index: tabIndex[uid],
|
||||||
selected: false,
|
selected: false,
|
||||||
type: 'query'
|
type: 'query',
|
||||||
|
fields: [],
|
||||||
|
keyUsage: []
|
||||||
};
|
};
|
||||||
state.workspaces = state.workspaces.map(workspace => {
|
state.workspaces = state.workspaces.map(workspace => {
|
||||||
if (workspace.uid === uid) {
|
if (workspace.uid === uid) {
|
||||||
@@ -73,8 +85,54 @@ export default {
|
|||||||
return workspace;
|
return workspace;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
REMOVE_TAB (state, { uid, tab: tUid }) {
|
||||||
|
state.workspaces = state.workspaces.map(workspace => {
|
||||||
|
if (workspace.uid === uid) {
|
||||||
|
return {
|
||||||
|
...workspace,
|
||||||
|
tabs: workspace.tabs.filter(tab => tab.uid !== tUid)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return workspace;
|
||||||
|
});
|
||||||
|
},
|
||||||
SELECT_TAB (state, { uid, tab }) {
|
SELECT_TAB (state, { uid, tab }) {
|
||||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, selected_tab: tab } : workspace);
|
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, selected_tab: tab } : workspace);
|
||||||
|
},
|
||||||
|
SET_TAB_FIELDS (state, { cUid, tUid, fields }) {
|
||||||
|
state.workspaces = state.workspaces.map(workspace => {
|
||||||
|
if (workspace.uid === cUid) {
|
||||||
|
return {
|
||||||
|
...workspace,
|
||||||
|
tabs: workspace.tabs.map(tab => {
|
||||||
|
if (tab.uid === tUid)
|
||||||
|
return { ...tab, fields };
|
||||||
|
else
|
||||||
|
return tab;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return workspace;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
SET_TAB_KEY_USAGE (state, { cUid, tUid, keyUsage }) {
|
||||||
|
state.workspaces = state.workspaces.map(workspace => {
|
||||||
|
if (workspace.uid === cUid) {
|
||||||
|
return {
|
||||||
|
...workspace,
|
||||||
|
tabs: workspace.tabs.map(tab => {
|
||||||
|
if (tab.uid === tUid)
|
||||||
|
return { ...tab, keyUsage };
|
||||||
|
else
|
||||||
|
return tab;
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return workspace;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
@@ -115,14 +173,25 @@ export default {
|
|||||||
uid,
|
uid,
|
||||||
connected: false,
|
connected: false,
|
||||||
selected_tab: 0,
|
selected_tab: 0,
|
||||||
tabs: [{ uid: 1, type: 'table' }],
|
tabs: [{
|
||||||
|
uid: 'data',
|
||||||
|
type: 'table',
|
||||||
|
fields: [],
|
||||||
|
keyUsage: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uid: 'prop',
|
||||||
|
type: 'table',
|
||||||
|
fields: [],
|
||||||
|
keyUsage: []
|
||||||
|
}],
|
||||||
structure: {},
|
structure: {},
|
||||||
breadcrumbs: {}
|
breadcrumbs: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
commit('ADD_WORKSPACE', workspace);
|
commit('ADD_WORKSPACE', workspace);
|
||||||
|
|
||||||
if (getters.getWorkspace(uid).tabs.length < 2)
|
if (getters.getWorkspace(uid).tabs.length < 3)
|
||||||
dispatch('newTab', uid);
|
dispatch('newTab', uid);
|
||||||
},
|
},
|
||||||
changeBreadcrumbs ({ commit, getters }, payload) {
|
changeBreadcrumbs ({ commit, getters }, payload) {
|
||||||
@@ -131,8 +200,17 @@ export default {
|
|||||||
newTab ({ commit }, uid) {
|
newTab ({ commit }, uid) {
|
||||||
commit('NEW_TAB', uid);
|
commit('NEW_TAB', uid);
|
||||||
},
|
},
|
||||||
|
removeTab ({ commit }, payload) {
|
||||||
|
commit('REMOVE_TAB', payload);
|
||||||
|
},
|
||||||
selectTab ({ commit }, payload) {
|
selectTab ({ commit }, payload) {
|
||||||
commit('SELECT_TAB', payload);
|
commit('SELECT_TAB', payload);
|
||||||
|
},
|
||||||
|
setTabFields ({ commit }, payload) {
|
||||||
|
commit('SET_TAB_FIELDS', payload);
|
||||||
|
},
|
||||||
|
setTabKeyUsage ({ commit }, payload) {
|
||||||
|
commit('SET_TAB_KEY_USAGE', payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -1,23 +1,23 @@
|
|||||||
import { ipcRenderer } from 'electron';
|
import { ipcRenderer } from 'electron';
|
||||||
|
|
||||||
export default store => {
|
export default store => {
|
||||||
ipcRenderer.on('checkingForUpdate', () => {
|
ipcRenderer.on('checking-for-update', () => {
|
||||||
store.commit('application/CHANGE_UPDATE_STATUS', 'checking');
|
store.commit('application/CHANGE_UPDATE_STATUS', 'checking');
|
||||||
});
|
});
|
||||||
ipcRenderer.on('updateAvailable', () => {
|
ipcRenderer.on('update-available', () => {
|
||||||
store.commit('application/CHANGE_UPDATE_STATUS', 'available');
|
store.commit('application/CHANGE_UPDATE_STATUS', 'available');
|
||||||
});
|
});
|
||||||
ipcRenderer.on('updateNotAvailable', () => {
|
ipcRenderer.on('update-not-available', () => {
|
||||||
store.commit('application/CHANGE_UPDATE_STATUS', 'noupdate');
|
store.commit('application/CHANGE_UPDATE_STATUS', 'noupdate');
|
||||||
});
|
});
|
||||||
ipcRenderer.on('checkFailed', () => {
|
ipcRenderer.on('check-failed', () => {
|
||||||
store.commit('application/CHANGE_UPDATE_STATUS', 'nocheck');
|
store.commit('application/CHANGE_UPDATE_STATUS', 'nocheck');
|
||||||
});
|
});
|
||||||
ipcRenderer.on('downloadProgress', (event, data) => {
|
ipcRenderer.on('download-progress', (event, data) => {
|
||||||
store.commit('application/CHANGE_UPDATE_STATUS', 'downloading');
|
store.commit('application/CHANGE_UPDATE_STATUS', 'downloading');
|
||||||
store.commit('application/CHANGE_PROGRESS_PERCENTAGE', data.percent);
|
store.commit('application/CHANGE_PROGRESS_PERCENTAGE', data.percent);
|
||||||
});
|
});
|
||||||
ipcRenderer.on('updateDownloaded', () => {
|
ipcRenderer.on('update-downloaded', () => {
|
||||||
store.commit('application/CHANGE_UPDATE_STATUS', 'downloaded');
|
store.commit('application/CHANGE_UPDATE_STATUS', 'downloaded');
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
12
src/renderer/suggestions/sql.js
Normal file
12
src/renderer/suggestions/sql.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { functions } from '@/suggestions/sql/sql-functions';
|
||||||
|
import { keywords } from '@/suggestions/sql/sql-keywords';
|
||||||
|
import { operators } from '@/suggestions/sql/sql-operators';
|
||||||
|
import { variables } from '@/suggestions/sql/sql-variables';
|
||||||
|
|
||||||
|
export const completionItemProvider = (monaco) => {
|
||||||
|
return {
|
||||||
|
provideCompletionItems () {
|
||||||
|
return { suggestions: [...functions(monaco), ...keywords(monaco), ...operators(monaco), ...variables(monaco)] };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
1267
src/renderer/suggestions/sql/sql-functions.js
Normal file
1267
src/renderer/suggestions/sql/sql-functions.js
Normal file
File diff suppressed because it is too large
Load Diff
4547
src/renderer/suggestions/sql/sql-keywords.js
Normal file
4547
src/renderer/suggestions/sql/sql-keywords.js
Normal file
File diff suppressed because it is too large
Load Diff
142
src/renderer/suggestions/sql/sql-operators.js
Normal file
142
src/renderer/suggestions/sql/sql-operators.js
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
export const operators = (monaco) => {
|
||||||
|
return [{
|
||||||
|
label: 'ALL',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'ALL'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'AND',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'AND'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'ANY',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'ANY'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'BETWEEN',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'BETWEEN'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'EXISTS',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'EXISTS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'IN',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'IN'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'LIKE',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'LIKE'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'NOT',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'NOT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'OR',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'OR'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'SOME',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'SOME'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'EXCEPT',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'EXCEPT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'INTERSECT',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'INTERSECT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'UNION',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'UNION'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'APPLY',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'APPLY'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'CROSS',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'CROSS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'FULL',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'FULL'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'INNER',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'INNER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'JOIN',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'JOIN'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'LEFT',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'LEFT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'OUTER',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'OUTER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'RIGHT',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'RIGHT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'CONTAINS',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'CONTAINS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'FREETEXT',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'FREETEXT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'IS',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'IS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'NULL',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'NULL'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'PIVOT',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'PIVOT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'UNPIVOT',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'UNPIVOT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'MATCHED',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Operator,
|
||||||
|
insertText: 'MATCHED'
|
||||||
|
}];
|
||||||
|
};
|
172
src/renderer/suggestions/sql/sql-variables.js
Normal file
172
src/renderer/suggestions/sql/sql-variables.js
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
export const variables = (monaco) => {
|
||||||
|
return [{
|
||||||
|
label: '@@DATEFIRST',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@DATEFIRST'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@DBTS',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@DBTS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@LANGID',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@LANGID'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@LANGUAGE',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@LANGUAGE'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@LOCK_TIMEOUT',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@LOCK_TIMEOUT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@MAX_CONNECTIONS',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@MAX_CONNECTIONS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@MAX_PRECISION',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@MAX_PRECISION'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@NESTLEVEL',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@NESTLEVEL'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@OPTIONS',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@OPTIONS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@REMSERVER',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@REMSERVER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@SERVERNAME',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@SERVERNAME'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@SERVICENAME',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@SERVICENAME'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@SPID',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@SPID'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@TEXTSIZE',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@TEXTSIZE'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@VERSION',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@VERSION'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@CURSOR_ROWS',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@CURSOR_ROWS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@FETCH_STATUS',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@FETCH_STATUS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@DATEFIRST',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@DATEFIRST'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@PROCID',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@PROCID'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@ERROR',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@ERROR'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@IDENTITY',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@IDENTITY'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@ROWCOUNT',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@ROWCOUNT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@TRANCOUNT',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@TRANCOUNT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@CONNECTIONS',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@CONNECTIONS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@CPU_BUSY',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@CPU_BUSY'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@IDLE',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@IDLE'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@IO_BUSY',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@IO_BUSY'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@PACKET_ERRORS',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@PACKET_ERRORS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@PACK_RECEIVED',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@PACK_RECEIVED'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@PACK_SENT',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@PACK_SENT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@TIMETICKS',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@TIMETICKS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@TOTAL_ERRORS',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@TOTAL_ERRORS'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@TOTAL_READ',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@TOTAL_READ'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '@@TOTAL_WRITE',
|
||||||
|
kind: monaco.languages.CompletionItemKind.Variable,
|
||||||
|
insertText: '@@TOTAL_WRITE'
|
||||||
|
}];
|
||||||
|
};
|
@@ -1,8 +1,11 @@
|
|||||||
|
|
||||||
const webpack = require('webpack');
|
const webpack = require('webpack');
|
||||||
|
const MonacoEditorPlugin = require('monaco-editor-webpack-plugin');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
plugins: [
|
plugins: [
|
||||||
|
new MonacoEditorPlugin({
|
||||||
|
languages: ['sql']
|
||||||
|
}),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
'process.env': {
|
'process.env': {
|
||||||
PACKAGE_VERSION: JSON.stringify(require('./package.json').version)
|
PACKAGE_VERSION: JSON.stringify(require('./package.json').version)
|
||||||
@@ -17,7 +20,7 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
loader: 'sass-loader',
|
loader: 'sass-loader',
|
||||||
options: {
|
options: {
|
||||||
prependData: '@import "@/scss/_variables.scss";'
|
additionalData: '@import "@/scss/_variables.scss";'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
Reference in New Issue
Block a user