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
|
||||
TODO.md
|
||||
*.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
|
||||
|
||||
## [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
|
||||
|
||||
- **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)
|
||||
|
||||
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:
|
||||
|
||||
|
70
README.md
70
README.md
@@ -1,7 +1,71 @@
|
||||
<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>
|
||||
|
||||
# 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",
|
||||
"productName": "Antares",
|
||||
"version": "0.0.1-alpha",
|
||||
"version": "0.0.6",
|
||||
"description": "A cross-platform easy to use SQL client.",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/Fabio286/antares.git",
|
||||
"repository": "https://github.com/EStarium/antares.git",
|
||||
"scripts": {
|
||||
"dev": "cross-env NODE_ENV=development electron-webpack dev",
|
||||
"compile": "electron-webpack",
|
||||
"dist": "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",
|
||||
"publish": "build -p always"
|
||||
"build": "cross-env NODE_ENV=production npm run compile && electron-builder",
|
||||
"release": "standard-version",
|
||||
"release:pre": "npm run release -- --prerelease alpha",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"author": "Fabio Di Stasio <fabio286@gmail.com>",
|
||||
"build": {
|
||||
"appId": "com.estarium.antares",
|
||||
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
|
||||
"files": [
|
||||
"static/*"
|
||||
]
|
||||
"dmg": {
|
||||
"contents": [
|
||||
{
|
||||
"x": 130,
|
||||
"y": 220
|
||||
},
|
||||
{
|
||||
"x": 410,
|
||||
"y": 220,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"deb",
|
||||
"AppImage"
|
||||
],
|
||||
"category": "Development"
|
||||
}
|
||||
},
|
||||
"electronWebpack": {
|
||||
"whiteListedModules": [
|
||||
"codemirror"
|
||||
],
|
||||
"renderer": {
|
||||
"webpackConfig": "webpack.config.js"
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"codemirror": "^5.54.0",
|
||||
"electron-log": "^4.2.1",
|
||||
"electron-updater": "^4.3.1",
|
||||
"lodash": "^4.17.15",
|
||||
"material-design-icons": "^3.0.1",
|
||||
"moment": "^2.26.0",
|
||||
"mssql": "^6.2.0",
|
||||
"@mdi/font": "^5.5.55",
|
||||
"electron-log": "^4.2.4",
|
||||
"electron-updater": "^4.3.4",
|
||||
"lodash": "^4.17.20",
|
||||
"moment": "^2.27.0",
|
||||
"monaco-editor": "^0.20.0",
|
||||
"mssql": "^6.2.1",
|
||||
"mysql": "^2.18.1",
|
||||
"pg": "^8.2.1",
|
||||
"pg": "^8.3.2",
|
||||
"source-map-support": "^0.5.16",
|
||||
"spectre.css": "^0.5.8",
|
||||
"spectre.css": "^0.5.9",
|
||||
"vue-click-outside": "^1.1.0",
|
||||
"vue-i18n": "^8.18.2",
|
||||
"vuedraggable": "^2.23.2",
|
||||
"vuex": "^3.4.0",
|
||||
"vue-i18n": "^8.21.0",
|
||||
"vue-the-mask": "^0.11.1",
|
||||
"vuedraggable": "^2.24.0",
|
||||
"vuex": "^3.5.1",
|
||||
"vuex-persist": "^2.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.1.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"electron": "^8.3.0",
|
||||
"electron-builder": "^22.7.0",
|
||||
"electron-devtools-installer": "^3.0.0",
|
||||
"electron": "^10.1.0",
|
||||
"electron-builder": "^22.8.0",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-webpack": "^2.8.2",
|
||||
"electron-webpack-vue": "^2.4.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint": "^7.7.0",
|
||||
"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-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
"monaco-editor-webpack-plugin": "^1.9.0",
|
||||
"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",
|
||||
"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 () {
|
||||
return Math.random().toString(36).substr(2, 9).toUpperCase();
|
||||
};
|
||||
|
||||
'use strict';
|
||||
export function mimeFromHex (hex) {
|
||||
switch (hex.substring(0, 4)) { // 2 bytes
|
||||
case '424D':
|
||||
return { ext: 'bmp', mime: 'image/bmp' };
|
||||
case '1F8B':
|
||||
return { ext: 'gz', mime: 'application/gzip' };
|
||||
return { ext: 'tar.gz', mime: 'application/gzip' };
|
||||
case '0B77':
|
||||
return { ext: 'ac3', mime: 'audio/vnd.dolby.dd-raw' };
|
||||
case '7801':
|
||||
@@ -20,7 +17,7 @@ export function mimeFromHex (hex) {
|
||||
default:
|
||||
switch (hex.substring(0, 6)) { // 3 bytes
|
||||
case 'FFD8FF':
|
||||
return { ext: 'jpj', mime: 'image/jpeg' };
|
||||
return { ext: 'jpg', mime: 'image/jpeg' };
|
||||
case '4949BC':
|
||||
return { ext: 'jxr', mime: 'image/vnd.ms-photo' };
|
||||
case '425A68':
|
||||
@@ -39,21 +36,11 @@ export function mimeFromHex (hex) {
|
||||
return { ext: 'bpg', mime: 'image/bpg' };
|
||||
case '4D4D002A':
|
||||
return { ext: 'tif', mime: 'image/tiff' };
|
||||
case '00000100':
|
||||
return { ext: 'ico', mime: 'image/x-icon' };
|
||||
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 * as path from 'path';
|
||||
import { format as formatUrl } from 'url';
|
||||
|
||||
import ipcHandlers from './ipc-handlers';
|
||||
|
||||
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)
|
||||
let mainWindow;
|
||||
|
||||
function createMainWindow () {
|
||||
async function createMainWindow () {
|
||||
const icon = require('../renderer/images/logo-32.png');
|
||||
const window = new BrowserWindow({
|
||||
width: 1600,
|
||||
height: 1000,
|
||||
minHeight: 550,
|
||||
width: 1024,
|
||||
height: 800,
|
||||
minWidth: 900,
|
||||
minHeight: 550,
|
||||
title: 'Antares',
|
||||
autoHideMenuBar: true,
|
||||
icon: nativeImage.createFromDataURL(icon.default),
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
'web-security': false
|
||||
'web-security': false,
|
||||
enableRemoteModule: true,
|
||||
spellcheck: false
|
||||
},
|
||||
frame: false,
|
||||
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) {
|
||||
await window.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`);
|
||||
|
||||
const { default: installExtension, VUEJS_DEVTOOLS } = require('electron-devtools-installer');
|
||||
window.webContents.openDevTools();
|
||||
|
||||
@@ -52,6 +45,13 @@ function createMainWindow () {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
else {
|
||||
await window.loadURL(formatUrl({
|
||||
pathname: path.join(__dirname, 'index.html'),
|
||||
protocol: 'file',
|
||||
slashes: true
|
||||
}));
|
||||
}
|
||||
|
||||
window.on('closed', () => {
|
||||
mainWindow = null;
|
||||
@@ -64,12 +64,12 @@ function createMainWindow () {
|
||||
});
|
||||
});
|
||||
|
||||
// Initialize ipcHandlers
|
||||
ipcHandlers();
|
||||
|
||||
return window;
|
||||
};
|
||||
|
||||
// Initialize ipcHandlers
|
||||
ipcHandlers();
|
||||
|
||||
// quit application when all windows are closed
|
||||
app.on('window-all-closed', () => {
|
||||
// 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 Generic from '../models/Generic';
|
||||
|
||||
export default (connections) => {
|
||||
ipcMain.handle('testConnection', async (event, conn) => {
|
||||
export default connections => {
|
||||
ipcMain.handle('test-connection', async (event, conn) => {
|
||||
const Connection = new AntaresConnector({
|
||||
client: conn.client,
|
||||
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;
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
try {
|
||||
const result = await Generic.raw(connections[uid], query, schema);
|
||||
|
@@ -1,11 +1,13 @@
|
||||
import connection from './connection';
|
||||
import structure from './structure';
|
||||
import tables from './tables';
|
||||
import updates from './updates';
|
||||
import application from './application';
|
||||
|
||||
const connections = {};
|
||||
|
||||
export default () => {
|
||||
connection(connections);
|
||||
structure(connections);
|
||||
tables(connections);
|
||||
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';
|
||||
|
||||
let mainWindow;
|
||||
autoUpdater.allowPrerelease = true;
|
||||
|
||||
export default () => {
|
||||
ipcMain.on('checkForUpdates', event => {
|
||||
ipcMain.on('check-for-updates', event => {
|
||||
mainWindow = event;
|
||||
|
||||
autoUpdater.checkForUpdatesAndNotify().catch(() => {
|
||||
mainWindow.reply('checkFailed');
|
||||
mainWindow.reply('check-failed');
|
||||
});
|
||||
});
|
||||
|
||||
ipcMain.on('restartToUpdate', () => {
|
||||
ipcMain.on('restart-to-update', () => {
|
||||
autoUpdater.quitAndInstall();
|
||||
});
|
||||
|
||||
// auto-updater events
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
mainWindow.reply('checkingForUpdate');
|
||||
mainWindow.reply('checking-for-update');
|
||||
});
|
||||
|
||||
autoUpdater.on('update-available', () => {
|
||||
mainWindow.reply('updateAvailable');
|
||||
mainWindow.reply('update-available');
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', () => {
|
||||
mainWindow.reply('updateNotAvailable');
|
||||
mainWindow.reply('update-not-available');
|
||||
});
|
||||
|
||||
autoUpdater.on('download-progress', (data) => {
|
||||
mainWindow.reply('downloadProgress', data);
|
||||
autoUpdater.on('download-progress', data => {
|
||||
mainWindow.reply('download-progress', data);
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', () => {
|
||||
mainWindow.reply('updateDownloaded');
|
||||
mainWindow.reply('update-downloaded');
|
||||
});
|
||||
|
||||
autoUpdater.logger = require('electron-log');
|
||||
|
@@ -20,6 +20,7 @@ export class AntaresConnector {
|
||||
this._params = args.params;
|
||||
this._poolSize = args.poolSize || false;
|
||||
this._connection = null;
|
||||
this._logger = args.logger || console.log;
|
||||
|
||||
this._queryDefaults = {
|
||||
schema: '',
|
||||
@@ -31,8 +32,8 @@ export class AntaresConnector {
|
||||
limit: [],
|
||||
join: [],
|
||||
update: [],
|
||||
insert: [],
|
||||
delete: []
|
||||
insert: {},
|
||||
delete: false
|
||||
};
|
||||
this._query = Object.assign({}, this._queryDefaults);
|
||||
}
|
||||
@@ -73,14 +74,10 @@ export class AntaresConnector {
|
||||
switch (this._client) {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
if (!this._poolSize) {
|
||||
const connection = mysql.createConnection(this._params);
|
||||
this._connection = connection.promise();
|
||||
}
|
||||
if (!this._poolSize)
|
||||
this._connection = mysql.createConnection(this._params);
|
||||
else
|
||||
this._connection = mysql.createPool({ ...this._params, connectionLimit: this._poolSize });
|
||||
// this._connection = pool.promise();
|
||||
|
||||
break;
|
||||
case 'mssql': {
|
||||
const mssqlParams = {
|
||||
@@ -111,6 +108,17 @@ export class AntaresConnector {
|
||||
return this;
|
||||
}
|
||||
|
||||
into (table) {
|
||||
this._query.from = table;
|
||||
return this;
|
||||
}
|
||||
|
||||
delete (table) {
|
||||
this._query.delete = true;
|
||||
this.from(table);
|
||||
return this;
|
||||
}
|
||||
|
||||
where (...args) {
|
||||
this._query.where = [...this._query.where, ...args];
|
||||
return this;
|
||||
@@ -149,6 +157,26 @@ export class AntaresConnector {
|
||||
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
|
||||
* @memberof AntaresConnector
|
||||
@@ -156,30 +184,37 @@ export class AntaresConnector {
|
||||
getSQL () {
|
||||
// SELECT
|
||||
const selectArray = this._query.select.reduce(this._reducer, []);
|
||||
let selectRaw;
|
||||
switch (this._client) {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
selectRaw = selectArray.length ? `SELECT ${selectArray.join(', ')} ` : 'SELECT * ';
|
||||
break;
|
||||
case 'mssql': {
|
||||
const topRaw = this._query.limit.length ? ` TOP (${this._query.limit[0]}) ` : '';
|
||||
selectRaw = selectArray.length ? `SELECT${topRaw} ${selectArray.join(', ')} ` : 'SELECT * ';
|
||||
let selectRaw = '';
|
||||
if (selectArray.length) {
|
||||
switch (this._client) {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
selectRaw = selectArray.length ? `SELECT ${selectArray.join(', ')} ` : 'SELECT * ';
|
||||
break;
|
||||
case 'mssql': {
|
||||
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
|
||||
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) {
|
||||
case 'maria':
|
||||
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;
|
||||
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;
|
||||
default:
|
||||
break;
|
||||
@@ -187,8 +222,28 @@ export class AntaresConnector {
|
||||
|
||||
const whereArray = this._query.where.reduce(this._reducer, []);
|
||||
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 groupByRaw = groupByArray.length ? `GROUP BY ${groupByArray.join(', ')} ` : '';
|
||||
|
||||
const orderByArray = this._query.orderBy.reduce(this._reducer, []);
|
||||
const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : '';
|
||||
|
||||
@@ -206,7 +261,7 @@ export class AntaresConnector {
|
||||
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
|
||||
*/
|
||||
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 'mysql': {
|
||||
const { rows, fields } = await new Promise((resolve, reject) => {
|
||||
this._connection.query(sql, (err, rows, fields) => {
|
||||
const { rows, report, fields } = await new Promise((resolve, reject) => {
|
||||
this._connection.query(sql, (err, response, fields) => {
|
||||
if (err)
|
||||
reject(err);
|
||||
else
|
||||
resolve({ rows, fields });
|
||||
else {
|
||||
resolve({
|
||||
rows: Array.isArray(response) ? response : false,
|
||||
report: !Array.isArray(response) ? response : false,
|
||||
fields
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
return { rows, fields };
|
||||
return { rows, report, fields };
|
||||
}
|
||||
case 'mssql': {
|
||||
const results = await this._connection.request().query(sql);
|
||||
|
@@ -11,13 +11,4 @@ export default class {
|
||||
}
|
||||
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();
|
||||
}
|
||||
|
||||
static getTableColumns (connection, schema, table) {
|
||||
return connection
|
||||
static async getTableColumns (connection, schema, table) {
|
||||
const { rows } = await connection
|
||||
.select('*')
|
||||
.schema('information_schema')
|
||||
.from('COLUMNS')
|
||||
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` })
|
||||
.orderBy({ ORDINAL_POSITION: 'ASC' })
|
||||
.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">
|
||||
<TheSettingBar />
|
||||
<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">
|
||||
<Workspace
|
||||
v-for="connection in connections"
|
||||
@@ -40,8 +40,7 @@ export default {
|
||||
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings')
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
};
|
||||
return {};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
@@ -53,7 +52,7 @@ export default {
|
||||
})
|
||||
},
|
||||
mounted () {
|
||||
ipcRenderer.send('checkForUpdates');
|
||||
ipcRenderer.send('check-for-updates');
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
@@ -64,30 +63,30 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
html,
|
||||
body{
|
||||
height: 100%;
|
||||
}
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#wrapper{
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
#wrapper {
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#window-content{
|
||||
display: flex;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
#window-content {
|
||||
display: flex;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#main-content {
|
||||
padding: 0;
|
||||
justify-content: flex-start;
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
width: calc(100% - #{$settingbar-width});
|
||||
#main-content {
|
||||
padding: 0;
|
||||
justify-content: flex-start;
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
width: calc(100% - #{$settingbar-width});
|
||||
|
||||
> .columns{
|
||||
height: calc(100vh - #{$footer-height});
|
||||
}
|
||||
}
|
||||
> .columns {
|
||||
height: calc(100vh - #{$footer-height});
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="modal modal-sm active">
|
||||
<div class="modal active" :class="modalSizeClass">
|
||||
<a class="modal-overlay" @click="hideModal" />
|
||||
<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">
|
||||
<slot name="header" />
|
||||
</div>
|
||||
@@ -29,13 +29,13 @@
|
||||
class="btn btn-primary mr-2"
|
||||
@click="confirmModal"
|
||||
>
|
||||
{{ $t('word.confirm') }}
|
||||
{{ confirmText || $t('word.confirm') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-link"
|
||||
@click="hideModal"
|
||||
>
|
||||
{{ $t('word.cancel') }}
|
||||
{{ cancelText || $t('word.cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,6 +45,15 @@
|
||||
<script>
|
||||
export default {
|
||||
name: 'BaseConfirmModal',
|
||||
props: {
|
||||
size: {
|
||||
type: String,
|
||||
validator: prop => ['small', 'medium', 'large'].includes(prop),
|
||||
default: 'small'
|
||||
},
|
||||
confirmText: String,
|
||||
cancelText: String
|
||||
},
|
||||
computed: {
|
||||
hasHeader () {
|
||||
return !!this.$slots.header;
|
||||
@@ -54,6 +63,13 @@ export default {
|
||||
},
|
||||
hasDefault () {
|
||||
return !!this.$slots.default;
|
||||
},
|
||||
modalSizeClass () {
|
||||
if (this.size === 'small')
|
||||
return 'modal-sm';
|
||||
else if (this.size === 'large')
|
||||
return 'modal-lg';
|
||||
else return '';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -70,7 +86,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal.modal-sm .modal-container{
|
||||
padding: 0;
|
||||
}
|
||||
.modal.modal-sm .modal-container {
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
|
@@ -31,63 +31,64 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$emit('closeContext');
|
||||
this.$emit('close-context');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<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;
|
||||
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;
|
||||
z-index: 400;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
padding: 0.4rem;
|
||||
position: fixed;
|
||||
right: 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>
|
||||
|
@@ -1,14 +1,15 @@
|
||||
<template>
|
||||
<div class="toast mt-2" :class="notificationStatus.className">
|
||||
<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>
|
||||
<i
|
||||
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"
|
||||
>{{ isExpanded ? 'expand_less' : 'expand_more' }}</i>
|
||||
/>
|
||||
<button class="btn btn-clear ml-2" @click="hideToast" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -38,19 +39,19 @@ export default {
|
||||
switch (this.status) {
|
||||
case 'success':
|
||||
className = 'toast-success';
|
||||
iconName = 'done';
|
||||
iconName = 'mdi-check';
|
||||
break;
|
||||
case 'error':
|
||||
className = 'toast-error';
|
||||
iconName = 'error';
|
||||
iconName = 'mdi-alert-rhombus';
|
||||
break;
|
||||
case 'warning':
|
||||
className = 'toast-warning';
|
||||
iconName = 'warning';
|
||||
iconName = 'mdi-alert';
|
||||
break;
|
||||
case 'primary':
|
||||
className = 'toast-primary';
|
||||
iconName = 'info_outline';
|
||||
iconName = 'mdi-information-outline';
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -71,25 +72,28 @@ export default {
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.toast{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
user-select: text;
|
||||
word-break: break-all;
|
||||
width: fit-content;
|
||||
margin-left: auto;
|
||||
}
|
||||
.toast {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
user-select: text;
|
||||
word-break: break-all;
|
||||
width: fit-content;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.notification-message{
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
max-width: 30rem;
|
||||
user-select: none;
|
||||
}
|
||||
.notification-message {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: inline-block;
|
||||
max-width: 30rem;
|
||||
}
|
||||
|
||||
.expanded .notification-message{
|
||||
white-space: initial;
|
||||
}
|
||||
.expand-btn {
|
||||
align-items: initial;
|
||||
}
|
||||
|
||||
.expanded .notification-message {
|
||||
white-space: initial;
|
||||
}
|
||||
</style>
|
||||
|
@@ -34,19 +34,19 @@ export default {
|
||||
switch (this.status) {
|
||||
case '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;
|
||||
case '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;
|
||||
case '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;
|
||||
case '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;
|
||||
}
|
||||
|
||||
@@ -70,10 +70,10 @@ export default {
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.toast{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
user-select: text;
|
||||
word-break: break-all;
|
||||
}
|
||||
.toast {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
user-select: text;
|
||||
word-break: break-all;
|
||||
}
|
||||
</style>
|
||||
|
@@ -21,47 +21,50 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// credits: https://github.com/xrado 👼
|
||||
export default {
|
||||
name: 'BaseVirtualScroll',
|
||||
props: {
|
||||
items: Array,
|
||||
itemHeight: Number
|
||||
itemHeight: Number,
|
||||
visibleHeight: Number,
|
||||
scrollElement: {
|
||||
type: HTMLDivElement,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
topHeight: 0,
|
||||
bottomHeight: 0,
|
||||
visibleItems: []
|
||||
visibleItems: [],
|
||||
renderTimeout: null,
|
||||
localScrollElement: null
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this._checkScrollPosition = this.checkScrollPosition.bind(this);
|
||||
this.checkScrollPosition();
|
||||
this.$el.addEventListener('scroll', this._checkScrollPosition);
|
||||
this.$el.addEventListener('wheel', this._checkScrollPosition);
|
||||
this.localScrollElement = this.scrollElement ? this.scrollElement : this.$el;
|
||||
this.updateWindow();
|
||||
this.localScrollElement.addEventListener('scroll', this._checkScrollPosition);
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$el.removeEventListener('scroll', this._checkScrollPosition);
|
||||
this.$el.removeEventListener('wheel', this._checkScrollPosition);
|
||||
this.localScrollElement.removeEventListener('scroll', this._checkScrollPosition);
|
||||
},
|
||||
methods: {
|
||||
checkScrollPosition (e = {}) {
|
||||
const el = this.$el;
|
||||
checkScrollPosition (e) {
|
||||
clearTimeout(this.renderTimeout);
|
||||
|
||||
// prevent parent scroll
|
||||
if ((el.scrollTop === 0 && e.deltaY < 0) || (Math.abs(el.scrollTop - (el.scrollHeight - el.clientHeight)) <= 1 && e.deltaY > 0))
|
||||
e.preventDefault();
|
||||
|
||||
this.updateWindow(e);
|
||||
this.renderTimeout = setTimeout(() => {
|
||||
this.updateWindow(e);
|
||||
}, 200);
|
||||
},
|
||||
|
||||
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 offset = 50;
|
||||
|
||||
const scrollTop = this.localScrollElement.scrollTop;
|
||||
|
||||
const scrollTop = this.$el.scrollTop;
|
||||
const offset = 5;
|
||||
const firstVisibleIndex = Math.floor(scrollTop / this.itemHeight);
|
||||
const lastVisibleIndex = firstVisibleIndex + visibleItemsCount;
|
||||
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">
|
||||
<a class="modal-overlay" />
|
||||
<div class="modal-container p-0">
|
||||
<div class="modal-header">
|
||||
<div class="modal-header pl-2">
|
||||
<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>
|
||||
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
|
||||
</div>
|
||||
@@ -63,7 +65,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
closeModal () {
|
||||
this.$emit('closeAsking');
|
||||
this.$emit('close-asking');
|
||||
},
|
||||
sendCredentials () {
|
||||
this.$emit('credentials', this.credentials);
|
||||
@@ -71,7 +73,3 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
@@ -2,9 +2,11 @@
|
||||
<div class="modal active">
|
||||
<a class="modal-overlay c-hand" @click="closeModal" />
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<div class="modal-header pl-2">
|
||||
<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>
|
||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||
</div>
|
||||
@@ -36,7 +38,7 @@
|
||||
<option value="maria">
|
||||
MariaDB
|
||||
</option>
|
||||
<option value="mssql">
|
||||
<!-- <option value="mssql">
|
||||
Microsoft SQL
|
||||
</option>
|
||||
<option value="pg">
|
||||
@@ -44,7 +46,7 @@
|
||||
</option>
|
||||
<option value="oracledb">
|
||||
Oracle DB
|
||||
</option>
|
||||
</option> -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -135,7 +137,7 @@
|
||||
</div>
|
||||
<ModalAskCredentials
|
||||
v-if="isAsking"
|
||||
@closeAsking="closeAsking"
|
||||
@close-asking="closeAsking"
|
||||
@credentials="continueTest"
|
||||
/>
|
||||
</div>
|
||||
@@ -230,7 +232,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container{
|
||||
max-width: 450px;
|
||||
}
|
||||
.modal-container {
|
||||
max-width: 450px;
|
||||
}
|
||||
</style>
|
||||
|
@@ -2,9 +2,11 @@
|
||||
<div class="modal active">
|
||||
<a class="modal-overlay c-hand" @click="closeModal" />
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<div class="modal-header pl-2">
|
||||
<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>
|
||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||
</div>
|
||||
@@ -40,7 +42,7 @@
|
||||
<option value="maria">
|
||||
MariaDB
|
||||
</option>
|
||||
<option value="mssql">
|
||||
<!-- <option value="mssql">
|
||||
Microsoft SQL
|
||||
</option>
|
||||
<option value="pg">
|
||||
@@ -48,7 +50,7 @@
|
||||
</option>
|
||||
<option value="oracledb">
|
||||
Oracle DB
|
||||
</option>
|
||||
</option> -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -139,7 +141,7 @@
|
||||
</div>
|
||||
<ModalAskCredentials
|
||||
v-if="isAsking"
|
||||
@closeAsking="closeAsking"
|
||||
@close-asking="closeAsking"
|
||||
@credentials="continueTest"
|
||||
/>
|
||||
</div>
|
||||
@@ -148,7 +150,7 @@
|
||||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import Connection from '@/ipc-api/Connection';
|
||||
import { uidGen } from 'common/libs/utilities';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import ModalAskCredentials from '@/components/ModalAskCredentials';
|
||||
import BaseToast from '@/components/BaseToast';
|
||||
|
||||
@@ -168,7 +170,7 @@ export default {
|
||||
user: 'root',
|
||||
password: '',
|
||||
ask: false,
|
||||
uid: uidGen()
|
||||
uid: uidGen('C')
|
||||
},
|
||||
toast: {
|
||||
status: '',
|
||||
@@ -253,7 +255,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-container{
|
||||
max-width: 450px;
|
||||
}
|
||||
.modal-container {
|
||||
max-width: 450px;
|
||||
}
|
||||
</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">
|
||||
<a class="modal-overlay c-hand" @click="closeModal" />
|
||||
<div class="modal-container">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title h5">
|
||||
{{ $t('word.settings') }}
|
||||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-cog mr-1" />
|
||||
{{ $t('word.settings') }}
|
||||
</div>
|
||||
</div>
|
||||
<a class="btn btn-clear c-hand" @click="closeModal" />
|
||||
</div>
|
||||
@@ -31,7 +34,7 @@
|
||||
:class="{'active': selectedTab === '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
|
||||
class="tab-item"
|
||||
@@ -45,11 +48,11 @@
|
||||
|
||||
<div v-if="selectedTab === 'general'" class="panel-body py-4">
|
||||
<form class="form-horizontal">
|
||||
<div class="col-6 col-sm-12">
|
||||
<div class="form-group">
|
||||
<div class="col-8 col-sm-12">
|
||||
<div class="form-group mb-4">
|
||||
<div class="col-6 col-sm-12">
|
||||
<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') }}:
|
||||
</label>
|
||||
</div>
|
||||
@@ -69,12 +72,33 @@
|
||||
</select>
|
||||
</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>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedTab === 'themes'" class="panel-body py-4">
|
||||
<!-- -->
|
||||
<div class="text-center">
|
||||
<p>In future releases</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedTab === 'update'" class="panel-body py-4">
|
||||
@@ -87,7 +111,7 @@
|
||||
<h4>{{ appName }}</h4>
|
||||
<p>
|
||||
{{ $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>
|
||||
</p>
|
||||
</div>
|
||||
@@ -111,8 +135,8 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isUpdate: false,
|
||||
localLocale: null,
|
||||
localTimeout: null,
|
||||
selectedTab: 'general'
|
||||
};
|
||||
},
|
||||
@@ -121,7 +145,9 @@ export default {
|
||||
appName: 'application/appName',
|
||||
appVersion: 'application/appVersion',
|
||||
selectedSettingTab: 'application/selectedSettingTab',
|
||||
selectedLocale: 'settings/getLocale'
|
||||
selectedLocale: 'settings/getLocale',
|
||||
notificationsTimeout: 'settings/getNotificationsTimeout',
|
||||
updateStatus: 'application/getUpdateStatus'
|
||||
}),
|
||||
locales () {
|
||||
const locales = [];
|
||||
@@ -129,46 +155,61 @@ export default {
|
||||
locales.push({ code: locale, name: localesNames[locale] });
|
||||
|
||||
return locales;
|
||||
},
|
||||
hasUpdates () {
|
||||
return ['available', 'downloading', 'downloaded'].includes(this.updateStatus);
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.localLocale = this.selectedLocale;
|
||||
this.localTimeout = this.notificationsTimeout;
|
||||
this.selectedTab = this.selectedSettingTab;
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
closeModal: 'application/hideSettingModal',
|
||||
changeLocale: 'settings/changeLocale'
|
||||
changeLocale: 'settings/changeLocale',
|
||||
updateNotificationsTimeout: 'settings/updateNotificationsTimeout'
|
||||
}),
|
||||
selectTab (tab) {
|
||||
this.selectedTab = tab;
|
||||
},
|
||||
openOutside (link) {
|
||||
shell.openExternal(link);
|
||||
},
|
||||
checkNotificationsTimeout () {
|
||||
if (!this.localTimeout)
|
||||
this.localTimeout = 10;
|
||||
|
||||
this.updateNotificationsTimeout(+this.localTimeout);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#settings{
|
||||
.modal-body{
|
||||
overflow: hidden;
|
||||
#settings {
|
||||
.modal-body {
|
||||
overflow: hidden;
|
||||
|
||||
.panel-body{
|
||||
height: calc(70vh - 70px);
|
||||
overflow: auto;
|
||||
}
|
||||
.panel-body {
|
||||
height: calc(70vh - 70px);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.badge::after{
|
||||
background: #32b643;
|
||||
}
|
||||
.badge::after {
|
||||
background: #32b643;
|
||||
}
|
||||
|
||||
.form-label{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.badge-update::after {
|
||||
bottom: initial;
|
||||
background: $primary-color;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="empty">
|
||||
<div class="empty-icon">
|
||||
<i class="material-icons md-48">system_update_alt</i>
|
||||
<i class="mdi mdi-48px mdi-cloud-download" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ updateMessage }}
|
||||
@@ -68,17 +68,17 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
checkForUpdates () {
|
||||
ipcRenderer.send('checkForUpdates');
|
||||
ipcRenderer.send('check-for-updates');
|
||||
},
|
||||
restartToUpdate () {
|
||||
ipcRenderer.send('restartToUpdate');
|
||||
ipcRenderer.send('restart-to-update');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.empty{
|
||||
color: $body-font-color;
|
||||
.empty {
|
||||
color: $body-font-color;
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,25 +1,15 @@
|
||||
<template>
|
||||
<div class="editor-wrapper">
|
||||
<textarea
|
||||
ref="codemirror"
|
||||
:options="cmOptions"
|
||||
/>
|
||||
<div ref="editor" class="editor" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CodeMirror from 'codemirror';
|
||||
|
||||
import 'codemirror/lib/codemirror.css';
|
||||
import 'codemirror/theme/material-darker.css';
|
||||
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';
|
||||
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
|
||||
import { completionItemProvider } from '@/suggestions/sql';
|
||||
|
||||
CodeMirror.defineOption('sql-hint');
|
||||
monaco.languages.registerCompletionItemProvider('sql', completionItemProvider(monaco));
|
||||
|
||||
export default {
|
||||
name: 'QueryEditor',
|
||||
@@ -28,62 +18,53 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
cminstance: 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
|
||||
}
|
||||
editor: null
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.initialize();
|
||||
},
|
||||
methods: {
|
||||
initialize () {
|
||||
this.cminstance = CodeMirror.fromTextArea(this.$refs.codemirror, this.cmOptions);
|
||||
this.cminstance.setValue(this.value || this.content);
|
||||
this.editor = monaco.editor.create(this.$refs.editor, {
|
||||
value: this.value,
|
||||
language: 'sql',
|
||||
theme: 'vs-dark',
|
||||
autoIndent: true,
|
||||
minimap: {
|
||||
enabled: false
|
||||
},
|
||||
contextmenu: false,
|
||||
wordBasedSuggestions: true,
|
||||
acceptSuggestionOnEnter: 'smart',
|
||||
quickSuggestions: true
|
||||
});
|
||||
|
||||
this.cminstance.on('change', cm => {
|
||||
this.content = cm.getValue();
|
||||
this.$emit('input', this.content);
|
||||
});
|
||||
}
|
||||
this.editor.onDidChangeModelContent(e => {
|
||||
const content = this.editor.getValue();
|
||||
this.$emit('update:value', content);
|
||||
});
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.editor && this.editor.dispose();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.editor-wrapper{
|
||||
border-bottom: 1px solid #444444;
|
||||
}
|
||||
.editor-wrapper {
|
||||
border-bottom: 1px solid #444;
|
||||
|
||||
.CodeMirror{
|
||||
.editor {
|
||||
height: 200px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.CodeMirror-scroll{
|
||||
max-width: 100%;
|
||||
}
|
||||
.CodeMirror {
|
||||
.CodeMirror-scroll {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.CodeMirror-line {
|
||||
word-break: break-word!important;
|
||||
white-space: pre-wrap!important;
|
||||
word-break: normal;
|
||||
}
|
||||
}
|
||||
.CodeMirror-line {
|
||||
word-break: break-word !important;
|
||||
white-space: pre-wrap !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,13 +1,13 @@
|
||||
<template>
|
||||
<BaseContextMenu
|
||||
:context-event="contextEvent"
|
||||
@closeContext="$emit('closeContext')"
|
||||
@close-context="$emit('close-context')"
|
||||
>
|
||||
<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 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>
|
||||
|
||||
<ConfirmModal
|
||||
@@ -16,7 +16,9 @@
|
||||
@hide="hideConfirmModal"
|
||||
>
|
||||
<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>
|
||||
<div :slot="'body'">
|
||||
<div class="mb-2">
|
||||
@@ -69,7 +71,3 @@ export default {
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div class="columns">
|
||||
<div class="column col-12 empty text-light">
|
||||
<div class="empty-icon">
|
||||
<i class="material-icons md-48">mood</i>
|
||||
<i class="mdi mdi-48px mdi-emoticon" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('message.appWelcome') }}
|
||||
@@ -11,7 +11,7 @@
|
||||
{{ $t('message.appFirstStep') }}
|
||||
</p>
|
||||
<div class="empty-action">
|
||||
<button class="btn btn-primary" @click="$emit('newConn')">
|
||||
<button class="btn btn-primary" @click="$emit('new-conn')">
|
||||
{{ $t('message.createConnection') }}
|
||||
</button>
|
||||
</div>
|
||||
@@ -26,12 +26,12 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.empty{
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
.empty {
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
</style>
|
||||
|
@@ -3,7 +3,7 @@
|
||||
<div class="footer-left-elements">
|
||||
<ul class="footer-elements">
|
||||
<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>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -11,15 +11,15 @@
|
||||
|
||||
<div class="footer-right-elements">
|
||||
<ul class="footer-elements">
|
||||
<li class="footer-element footer-link">
|
||||
<i class="material-icons md-18 mr-1">favorite</i>
|
||||
<li class="footer-element footer-link" @click="openOutside('https://github.com/sponsors/Fabio286')">
|
||||
<i class="mdi mdi-18px mdi-coffee mr-1" />
|
||||
<small>{{ $t('word.donate') }}</small>
|
||||
</li>
|
||||
<li class="footer-element footer-link">
|
||||
<i class="material-icons md-18">bug_report</i>
|
||||
<li class="footer-element footer-link" @click="openOutside('https://github.com/EStarium/antares/issues')">
|
||||
<i class="mdi mdi-18px mdi-bug" />
|
||||
</li>
|
||||
<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>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
const { shell } = require('electron');
|
||||
|
||||
export default {
|
||||
name: 'TheFooter',
|
||||
@@ -40,47 +41,50 @@ export default {
|
||||
methods: {
|
||||
...mapActions({
|
||||
showSettingModal: 'application/showSettingModal'
|
||||
})
|
||||
}),
|
||||
openOutside (link) {
|
||||
shell.openExternal(link);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#footer{
|
||||
height: $footer-height;
|
||||
#footer {
|
||||
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;
|
||||
justify-content: space-between;
|
||||
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{
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.footer-element {
|
||||
height: $footer-height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 0.4rem;
|
||||
margin: 0;
|
||||
|
||||
.footer-element{
|
||||
height: $footer-height;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 .4rem;
|
||||
margin: 0;
|
||||
&.footer-link {
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
|
||||
&.footer-link{
|
||||
cursor: pointer;
|
||||
transition: background .2s;
|
||||
|
||||
&:hover{
|
||||
background: rgba($color: #fff, $alpha: .1);
|
||||
}
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
background: rgba($color: #fff, $alpha: 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<div id="notifications-board">
|
||||
<div
|
||||
id="notifications-board"
|
||||
@mouseenter="clearTimeouts"
|
||||
@mouseleave="rearmTimeouts"
|
||||
>
|
||||
<transition-group name="slide-fade">
|
||||
<BaseNotification
|
||||
v-for="notification in latestNotifications"
|
||||
@@ -21,27 +25,60 @@ export default {
|
||||
components: {
|
||||
BaseNotification
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
timeouts: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
notifications: 'notifications/getNotifications'
|
||||
notifications: 'notifications/getNotifications',
|
||||
notificationsTimeout: 'settings/getNotificationsTimeout'
|
||||
}),
|
||||
latestNotifications () {
|
||||
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: {
|
||||
...mapActions({
|
||||
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>
|
||||
|
||||
<style lang="scss">
|
||||
#notifications-board{
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
right: 1rem;
|
||||
bottom: 1rem;
|
||||
}
|
||||
#notifications-board {
|
||||
position: absolute;
|
||||
z-index: 999;
|
||||
right: 1rem;
|
||||
bottom: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
@@ -5,7 +5,7 @@
|
||||
v-if="isContext"
|
||||
:context-event="contextEvent"
|
||||
:context-connection="contextConnection"
|
||||
@closeContext="isContext = false"
|
||||
@close-context="isContext = false"
|
||||
/>
|
||||
<ul class="settingbar-elements">
|
||||
<draggable v-model="connections">
|
||||
@@ -28,7 +28,7 @@
|
||||
@click="showNewConnModal"
|
||||
@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>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -37,7 +37,7 @@
|
||||
<div class="settingbar-bottom-elements">
|
||||
<ul class="settingbar-elements">
|
||||
<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>
|
||||
</li>
|
||||
</ul>
|
||||
@@ -70,7 +70,8 @@ export default {
|
||||
getConnections: 'connections/getConnections',
|
||||
getConnectionName: 'connections/getConnectionName',
|
||||
connected: 'workspaces/getConnected',
|
||||
selectedWorkspace: 'workspaces/getSelected'
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
updateStatus: 'application/getUpdateStatus'
|
||||
}),
|
||||
connections: {
|
||||
get () {
|
||||
@@ -79,6 +80,9 @@ export default {
|
||||
set (value) {
|
||||
this.updateConnections(value);
|
||||
}
|
||||
},
|
||||
hasUpdates () {
|
||||
return ['available', 'downloading', 'downloaded'].includes(this.updateStatus);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -106,101 +110,104 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#settingbar{
|
||||
width: $settingbar-width;
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
#settingbar {
|
||||
width: $settingbar-width;
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
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;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.settingbar-elements {
|
||||
list-style: none;
|
||||
text-align: center;
|
||||
width: $settingbar-width;
|
||||
padding: 0;
|
||||
box-shadow: 0 0 1px 0px #000;
|
||||
z-index: 9;
|
||||
margin: 0;
|
||||
|
||||
.settingbar-top-elements{
|
||||
overflow-x: hidden;
|
||||
overflow-y: overlay;
|
||||
max-height: calc((100vh - 3.5rem) - #{$excluding-size});
|
||||
.settingbar-element {
|
||||
height: $settingbar-width;
|
||||
width: 100%;
|
||||
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 {
|
||||
width: 3px;
|
||||
}
|
||||
&: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;
|
||||
}
|
||||
|
||||
&.badge-update::after {
|
||||
bottom: initial;
|
||||
background: $primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.settingbar-bottom-elements{
|
||||
padding-top: .5rem;
|
||||
background: $bg-color-light;
|
||||
z-index: 1;
|
||||
}
|
||||
.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;
|
||||
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{
|
||||
list-style: none;
|
||||
text-align: center;
|
||||
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;
|
||||
}
|
||||
}
|
||||
&:hover .ex-tooltip-content {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -4,8 +4,8 @@
|
||||
<div class="titlebar-elements">
|
||||
<img class="titlebar-logo" :src="require('@/images/logo.svg').default">
|
||||
</div>
|
||||
<div class="titlebar-elements">
|
||||
<!-- -->
|
||||
<div class="titlebar-elements titlebar-title">
|
||||
{{ windowTitle }}
|
||||
</div>
|
||||
<div class="titlebar-elements">
|
||||
<div
|
||||
@@ -13,31 +13,32 @@
|
||||
class="titlebar-element"
|
||||
@click="openDevTools"
|
||||
>
|
||||
<i class="material-icons">code</i>
|
||||
<i class="mdi mdi-24px mdi-code-tags" />
|
||||
</div>
|
||||
<div
|
||||
v-if="isDevelopment"
|
||||
class="titlebar-element"
|
||||
@click="reload"
|
||||
>
|
||||
<i class="material-icons">refresh</i>
|
||||
<i class="mdi mdi-24px mdi-refresh" />
|
||||
</div>
|
||||
<div class="titlebar-element" @click="minimizeApp">
|
||||
<i class="material-icons">remove</i>
|
||||
<i class="mdi mdi-24px mdi-minus" />
|
||||
</div>
|
||||
<div class="titlebar-element" @click="toggleFullScreen">
|
||||
<i v-if="isMaximized" class="material-icons">fullscreen_exit</i>
|
||||
<i v-else class="material-icons">fullscreen</i>
|
||||
<i v-if="isMaximized" class="mdi mdi-24px mdi-fullscreen-exit" />
|
||||
<i v-else class="mdi mdi-24px mdi-fullscreen" />
|
||||
</div>
|
||||
<div class="titlebar-element close-button" @click="closeApp">
|
||||
<i class="material-icons">close</i>
|
||||
<i class="mdi mdi-24px mdi-close" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { remote } from 'electron';
|
||||
import { remote, ipcRenderer } from 'electron';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'TheTitleBar',
|
||||
@@ -48,6 +49,22 @@ export default {
|
||||
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 () {
|
||||
window.addEventListener('resize', this.onResize);
|
||||
},
|
||||
@@ -56,7 +73,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
closeApp () {
|
||||
this.w.close();
|
||||
ipcRenderer.send('close-app');
|
||||
},
|
||||
minimizeApp () {
|
||||
this.w.minimize();
|
||||
@@ -81,55 +98,64 @@ export default {
|
||||
</script>
|
||||
|
||||
<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;
|
||||
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 0px #000;
|
||||
z-index: 9999;
|
||||
|
||||
.titlebar-resizer{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
z-index: 999;
|
||||
-webkit-app-region: no-drag;
|
||||
&.titlebar-title {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.titlebar-elements{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.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-logo {
|
||||
height: $titlebar-height;
|
||||
padding: 0 0.4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
|
@@ -2,37 +2,69 @@
|
||||
<div v-show="isSelected" class="workspace column columns col-gapless">
|
||||
<WorkspaceExploreBar :connection="connection" :is-selected="isSelected" />
|
||||
<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
|
||||
v-if="workspace.breadcrumbs.table"
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 1}"
|
||||
@click="selectTab({uid: workspace.uid, tab: 1})"
|
||||
:class="{'active': selectedTab === 'prop'}"
|
||||
@click="selectTab({uid: workspace.uid, tab: 'prop'})"
|
||||
>
|
||||
<a class="tab-link">
|
||||
<i class="material-icons md-18 mr-1">grid_on</i>
|
||||
<span :title="workspace.breadcrumbs.table">{{ workspace.breadcrumbs.table }}</span>
|
||||
<i class="mdi mdi-18px mdi-tune mr-1" />
|
||||
<span :title="workspace.breadcrumbs.table">{{ $t('word.properties').toUpperCase() }}: {{ workspace.breadcrumbs.table }}</span>
|
||||
</a>
|
||||
</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"
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 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>
|
||||
</ul>
|
||||
<WorkspaceTableTab
|
||||
v-show="selectedTab === 1"
|
||||
v-show="selectedTab === 'data'"
|
||||
:connection="connection"
|
||||
:table="workspace.breadcrumbs.table"
|
||||
/>
|
||||
<WorkspaceQueryTab
|
||||
v-for="tab of queryTabs"
|
||||
v-show="selectedTab === tab.uid"
|
||||
:key="tab.uid"
|
||||
:tab-uid="tab.uid"
|
||||
:is-selected="selectedTab === tab.uid"
|
||||
:connection="connection"
|
||||
/>
|
||||
</div>
|
||||
@@ -68,7 +100,7 @@ export default {
|
||||
return this.selectedWorkspace === this.connection.uid;
|
||||
},
|
||||
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 () {
|
||||
return this.workspace.tabs.filter(tab => tab.type === 'query');
|
||||
@@ -80,102 +112,141 @@ export default {
|
||||
if (isInitiated)
|
||||
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: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
addWorkspace: 'workspaces/addWorkspace',
|
||||
connectWorkspace: 'workspaces/connectWorkspace',
|
||||
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>
|
||||
|
||||
<style lang="scss">
|
||||
.workspace{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
.workspace {
|
||||
padding: 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;
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
|
||||
.tab-block{
|
||||
background: $bg-color-light;
|
||||
margin-top: 0;
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
&::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-query-results{
|
||||
overflow: auto;
|
||||
white-space: nowrap;
|
||||
.tab-item {
|
||||
max-width: 12rem;
|
||||
width: fit-content;
|
||||
flex: initial;
|
||||
|
||||
.table{
|
||||
width: auto;
|
||||
border-collapse: separate;
|
||||
> a {
|
||||
padding: 0.2rem 0.8rem;
|
||||
color: $body-font-color;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
.th{
|
||||
position: sticky;
|
||||
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;
|
||||
}
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.td{
|
||||
border-right: 1px solid;
|
||||
border-bottom: 1px solid;
|
||||
border-color: $bg-color-light;
|
||||
padding: 0 .4rem;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 200px;
|
||||
white-space: nowrap;
|
||||
&.tab-add {
|
||||
padding: 0.2rem 0.4rem;
|
||||
margin-top: 2px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
> span {
|
||||
overflow: hidden;
|
||||
font-size: .7rem;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
padding: 0 0.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:focus{
|
||||
box-shadow:inset 0px 0px 0px 1px $body-font-color;
|
||||
background: rgba($color: #000000, $alpha: .3);
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
&.active a {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div class="columns">
|
||||
<div class="column col-12 empty text-light">
|
||||
<div class="empty-icon">
|
||||
<i class="material-icons md-48">cloud_off</i>
|
||||
<i class="mdi mdi-48px mdi-power-plug-off" />
|
||||
</div>
|
||||
<p class="empty-title h5">
|
||||
{{ $t('word.disconnected') }}
|
||||
@@ -19,7 +19,7 @@
|
||||
</div>
|
||||
<ModalAskCredentials
|
||||
v-if="isAsking"
|
||||
@closeAsking="closeAsking"
|
||||
@close-asking="closeAsking"
|
||||
@credentials="continueTest"
|
||||
/>
|
||||
</div>
|
||||
@@ -45,7 +45,6 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
connectWorkspace: 'workspaces/connectWorkspace'
|
||||
}),
|
||||
async startConnection () {
|
||||
@@ -73,11 +72,11 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.empty{
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.empty {
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
|
@@ -10,16 +10,16 @@
|
||||
<span class="workspace-explorebar-title">{{ connectionName }}</span>
|
||||
<span v-if="workspace.connected" class="workspace-explorebar-tools">
|
||||
<i
|
||||
class="material-icons md-18 c-hand"
|
||||
class="mdi mdi-18px mdi-refresh c-hand"
|
||||
:class="{'rotate':isRefreshing}"
|
||||
:title="$t('word.refresh')"
|
||||
@click="refresh"
|
||||
>refresh</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')"
|
||||
@click="disconnectWorkspace(connection.uid)"
|
||||
>exit_to_app</i>
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
<WorkspaceConnectPanel
|
||||
@@ -123,70 +123,70 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.workspace-explorebar-resizer{
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
right: -2px;
|
||||
top: 0;
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
cursor: ew-resize;
|
||||
z-index: 99;
|
||||
}
|
||||
.workspace-explorebar-resizer {
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
right: -2px;
|
||||
top: 0;
|
||||
height: calc(100vh - #{$excluding-size});
|
||||
cursor: ew-resize;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.workspace-explorebar{
|
||||
width: $explorebar-width;
|
||||
.workspace-explorebar {
|
||||
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;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
background: $bg-color-gray;
|
||||
box-shadow: 0 0 1px 0px #000;
|
||||
z-index: 8;
|
||||
flex: initial;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
justify-content: space-between;
|
||||
font-size: 0.6rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
|
||||
.workspace-explorebar-header{
|
||||
width: 100%;
|
||||
padding: .3rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: .6rem;
|
||||
font-weight: 700;
|
||||
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-title {
|
||||
width: 80%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.workspace-explorebar-body{
|
||||
width: 100%;
|
||||
height: calc((100vh - 30px) - #{$excluding-size});
|
||||
overflow: overlay;
|
||||
padding: 0 .1rem;
|
||||
.workspace-explorebar-tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> 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>
|
||||
|
@@ -5,8 +5,8 @@
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name}"
|
||||
@click="changeBreadcrumbs({schema: database.name, table:null})"
|
||||
>
|
||||
<i class="icon material-icons md-18 mr-1">navigate_next</i>
|
||||
<i class="material-icons md-18 mr-1">view_agenda</i>
|
||||
<i class="icon mdi mdi-18px mdi-chevron-right" />
|
||||
<i class="database-icon mdi mdi-18px mdi-database mr-1" />
|
||||
<span>{{ database.name }}</span>
|
||||
</summary>
|
||||
<div class="accordion-body">
|
||||
@@ -20,7 +20,7 @@
|
||||
@click="changeBreadcrumbs({schema: database.name, table: table.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>
|
||||
</a>
|
||||
</li>
|
||||
@@ -56,35 +56,40 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.workspace-explorebar-database{
|
||||
.database-name,
|
||||
a.table-name{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .1rem;
|
||||
cursor: pointer;
|
||||
font-size: .7rem;
|
||||
.workspace-explorebar-database {
|
||||
.database-name,
|
||||
a.table-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.1rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.7rem;
|
||||
|
||||
> span{
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&:hover{
|
||||
color: $body-font-color;
|
||||
background: rgba($color: #FFF, $alpha: .05);
|
||||
border-radius: 2px;
|
||||
}
|
||||
> span {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.menu-item{
|
||||
line-height: 1.2;
|
||||
&:hover {
|
||||
color: $body-font-color;
|
||||
background: rgba($color: #fff, $alpha: 0.05);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.database-tables{
|
||||
margin-left: 1.2rem;
|
||||
.database-icon,
|
||||
.table-icon {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.database-tables {
|
||||
margin-left: 1.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,27 +1,26 @@
|
||||
<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">
|
||||
<QueryEditor v-model="query" />
|
||||
<QueryEditor v-if="isSelected" :value.sync="query" />
|
||||
<div class="workspace-query-runner-footer">
|
||||
<div class="workspace-query-buttons">
|
||||
<button
|
||||
class="btn btn-link btn-sm"
|
||||
:class="{'loading':isQuering}"
|
||||
:disabled="!query"
|
||||
@click="runQuery"
|
||||
@click="runQuery(query)"
|
||||
>
|
||||
<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 class="btn btn-link btn-sm">
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<i class="material-icons ml-1">save</i>
|
||||
</button> -->
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div v-if="results.rows">
|
||||
{{ $t('word.results') }}: <b>{{ results.rows.length }}</b>
|
||||
</div>
|
||||
<div v-if="results.report">
|
||||
{{ $t('message.affectedRows') }}: <b>{{ results.report.affectedRows }}</b>
|
||||
</div>
|
||||
<div v-if="workspace.breadcrumbs.schema">
|
||||
{{ $t('word.schema') }}: <b>{{ workspace.breadcrumbs.schema }}</b>
|
||||
</div>
|
||||
@@ -31,8 +30,12 @@
|
||||
<div class="workspace-query-results column col-12">
|
||||
<WorkspaceQueryTable
|
||||
v-if="results"
|
||||
v-show="!isQuering"
|
||||
ref="queryTable"
|
||||
:results="results"
|
||||
:fields="resultsFields"
|
||||
:tab-uid="tabUid"
|
||||
@update-field="updateField"
|
||||
@delete-selected="deleteSelected"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -40,9 +43,11 @@
|
||||
|
||||
<script>
|
||||
import Connection from '@/ipc-api/Connection';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import tableTabs from '@/mixins/tableTabs';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceQueryTab',
|
||||
@@ -50,14 +55,19 @@ export default {
|
||||
QueryEditor,
|
||||
WorkspaceQueryTable
|
||||
},
|
||||
mixins: [tableTabs],
|
||||
props: {
|
||||
connection: Object
|
||||
connection: Object,
|
||||
tabUid: String,
|
||||
isSelected: Boolean
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
query: '',
|
||||
lastQuery: '',
|
||||
isQuering: false,
|
||||
results: {}
|
||||
results: {},
|
||||
selectedFields: []
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -67,31 +77,87 @@ export default {
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
resultsFields () {
|
||||
return this.results.rows && this.results.rows.length ? Object.keys(this.results.rows[0]).map(field => {
|
||||
return { name: field, key: '', type: '' }; // TODO: extract getting table name from query
|
||||
}) : [];
|
||||
table () {
|
||||
if ('fields' in this.results && this.results.fields.length)
|
||||
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: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification'
|
||||
addNotification: 'notifications/addNotification',
|
||||
setTabFields: 'workspaces/setTabFields',
|
||||
setTabKeyUsage: 'workspaces/setTabKeyUsage'
|
||||
}),
|
||||
async runQuery () {
|
||||
if (!this.query) return;
|
||||
async runQuery (query) {
|
||||
if (!query) return;
|
||||
this.isQuering = true;
|
||||
this.results = {};
|
||||
this.clearTabData();
|
||||
|
||||
try {
|
||||
try { // Query Data
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
query: this.query,
|
||||
schema: this.workspace.breadcrumbs.schema
|
||||
schema: this.schema,
|
||||
query
|
||||
};
|
||||
|
||||
const { status, response } = await Connection.rawQuery(params);
|
||||
if (status === 'success')
|
||||
if (status === 'success') {
|
||||
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
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
@@ -100,43 +166,50 @@ export default {
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<style lang="scss">
|
||||
.workspace-tabs{
|
||||
align-content: baseline;
|
||||
.workspace-tabs {
|
||||
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{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: .3rem .6rem .4rem;
|
||||
align-items: center;
|
||||
.workspace-query-buttons {
|
||||
display: flex;
|
||||
|
||||
.workspace-query-buttons{
|
||||
display: flex;
|
||||
|
||||
.btn{
|
||||
display: flex;
|
||||
align-self: center;
|
||||
color: $body-font-color;
|
||||
margin-right: .4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-query-info{
|
||||
display: flex;
|
||||
|
||||
> div + div{
|
||||
padding-left: .6rem;
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
color: $body-font-color;
|
||||
margin-right: 0.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-query-info {
|
||||
display: flex;
|
||||
|
||||
> div + div {
|
||||
padding-left: 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
@@ -1,120 +1,144 @@
|
||||
<template>
|
||||
<BaseVirtualScroll
|
||||
v-if="results.rows"
|
||||
ref="resultTable"
|
||||
:items="localResults"
|
||||
:item-height="25"
|
||||
<div
|
||||
ref="tableWrapper"
|
||||
class="vscroll"
|
||||
:style="{'height': resultsSize+'px'}"
|
||||
>
|
||||
<template slot-scope="{ items }">
|
||||
<div class="table table-hover">
|
||||
<div class="thead">
|
||||
<div class="tr">
|
||||
<div
|
||||
v-for="field in fields"
|
||||
:key="field.name"
|
||||
class="th"
|
||||
>
|
||||
<div class="table-column-title">
|
||||
<TableContext
|
||||
v-if="isContext"
|
||||
:context-event="contextEvent"
|
||||
:selected-rows="selectedRows"
|
||||
@delete-selected="deleteSelected"
|
||||
@close-context="isContext = false"
|
||||
/>
|
||||
<div ref="table" class="table table-hover">
|
||||
<div class="thead">
|
||||
<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
|
||||
v-if="field.key"
|
||||
class="material-icons column-key c-help"
|
||||
class="mdi mdi-key column-key c-help"
|
||||
:class="`key-${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 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"
|
||||
:key="row._id"
|
||||
:row="row"
|
||||
:fields="fields"
|
||||
:key-usage="keyUsage"
|
||||
class="tr"
|
||||
>
|
||||
<div
|
||||
v-for="(col, cKey) in row"
|
||||
:key="cKey"
|
||||
class="td"
|
||||
:class="`type-${fieldType(cKey)}${isNull(col)}`"
|
||||
:style="{'display': cKey === '_id' ? 'none' : ''}"
|
||||
tabindex="0"
|
||||
>
|
||||
{{ col | typeFormat(fieldType(cKey)) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</BaseVirtualScroll>
|
||||
:class="{'selected': selectedRows.includes(row._id)}"
|
||||
@select-row="selectRow($event, row._id)"
|
||||
@update-field="updateField($event, row[primaryField.alias || primaryField.name])"
|
||||
@contextmenu="contextMenu"
|
||||
/>
|
||||
</template>
|
||||
</basevirtualscroll>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { uidGen, mimeFromHex, formatBytes } from 'common/libs/utilities';
|
||||
import hexToBinary from 'common/libs/hexToBinary';
|
||||
import moment from 'moment';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
||||
import WorkspaceQueryTableRow from '@/components/WorkspaceQueryTableRow';
|
||||
import TableContext from '@/components/WorkspaceQueryTableContext';
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceQueryTable',
|
||||
components: {
|
||||
BaseVirtualScroll
|
||||
},
|
||||
filters: {
|
||||
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;
|
||||
}
|
||||
}
|
||||
BaseVirtualScroll,
|
||||
WorkspaceQueryTableRow,
|
||||
TableContext
|
||||
},
|
||||
props: {
|
||||
results: Object,
|
||||
fields: Array
|
||||
tabUid: [String, Number]
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
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: {
|
||||
results () {
|
||||
this.resetSort();
|
||||
this.localResults = this.results.rows ? this.results.rows.map(item => {
|
||||
return { ...item, _id: uidGen() };
|
||||
}) : [];
|
||||
}
|
||||
},
|
||||
updated () {
|
||||
if (this.$refs.resultTable)
|
||||
this.resizeResults();
|
||||
if (this.$refs.table)
|
||||
this.refreshScroller();
|
||||
},
|
||||
mounted () {
|
||||
window.addEventListener('resize', this.resizeResults);
|
||||
@@ -123,6 +147,9 @@ export default {
|
||||
window.removeEventListener('resize', this.resizeResults);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification'
|
||||
}),
|
||||
fieldType (cKey) {
|
||||
let type = 'unknown';
|
||||
const field = this.fields.filter(field => field.name === cKey)[0];
|
||||
@@ -131,8 +158,13 @@ export default {
|
||||
|
||||
return type;
|
||||
},
|
||||
isNull (col) {
|
||||
return col === null ? ' is-null' : '';
|
||||
fieldPrecision (cKey) {
|
||||
let length = 0;
|
||||
const field = this.fields.filter(field => field.name === cKey)[0];
|
||||
if (field)
|
||||
length = field.datePrecision;
|
||||
|
||||
return length;
|
||||
},
|
||||
keyName (key) {
|
||||
switch (key) {
|
||||
@@ -146,16 +178,103 @@ export default {
|
||||
return 'UNKNOWN ' + key;
|
||||
}
|
||||
},
|
||||
resizeResults (e) {
|
||||
resizeResults () {
|
||||
if (this.$refs.resultTable) {
|
||||
const el = this.$refs.resultTable.$el;
|
||||
const footer = document.getElementById('footer');
|
||||
const el = this.$refs.tableWrapper;
|
||||
|
||||
if (el) {
|
||||
const footer = document.getElementById('footer');
|
||||
const size = window.innerHeight - el.getBoundingClientRect().top - footer.offsetHeight;
|
||||
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">
|
||||
.vscroll {
|
||||
height: 1000px;
|
||||
overflow: auto;
|
||||
overflow-anchor: none;
|
||||
height: 1000px;
|
||||
overflow: auto;
|
||||
overflow-anchor: none;
|
||||
}
|
||||
|
||||
.table-column-title{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.column-resizable {
|
||||
&:hover,
|
||||
&:active {
|
||||
resize: horizontal;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.column-key{
|
||||
transform: rotate(90deg);
|
||||
font-size: .7rem;
|
||||
line-height: 1.5;
|
||||
margin-right: .2rem;
|
||||
.table-column-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&.key-pri{
|
||||
color: goldenrod;
|
||||
}
|
||||
|
||||
&.key-uni{
|
||||
color: deepskyblue;
|
||||
}
|
||||
|
||||
&.key-mul{
|
||||
color: palegreen;
|
||||
}
|
||||
.sort-icon {
|
||||
font-size: 0.7rem;
|
||||
line-height: 1;
|
||||
margin-left: 0.2rem;
|
||||
}
|
||||
</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
|
||||
class="btn btn-link btn-sm"
|
||||
:class="{'loading':isQuering}"
|
||||
@click="getTableData"
|
||||
@click="reloadTable"
|
||||
>
|
||||
<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 class="btn btn-link btn-sm">
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
<i class="material-icons ml-1">save</i>
|
||||
</button> -->
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div v-if="results.rows">
|
||||
@@ -29,33 +33,49 @@
|
||||
<div class="workspace-query-results column col-12">
|
||||
<WorkspaceQueryTable
|
||||
v-if="results"
|
||||
ref="queryTable"
|
||||
:results="results"
|
||||
:fields="resultsFields"
|
||||
:tab-uid="tabUid"
|
||||
@update-field="updateField"
|
||||
@delete-selected="deleteSelected"
|
||||
/>
|
||||
</div>
|
||||
<ModalNewTableRow
|
||||
v-if="isAddModal"
|
||||
:tab-uid="tabUid"
|
||||
@hide="hideAddModal"
|
||||
@reload="reloadTable"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Structure from '@/ipc-api/Structure';
|
||||
import Tables from '@/ipc-api/Tables';
|
||||
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
|
||||
import ModalNewTableRow from '@/components/ModalNewTableRow';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
import tableTabs from '@/mixins/tableTabs';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceTableTab',
|
||||
components: {
|
||||
WorkspaceQueryTable
|
||||
WorkspaceQueryTable,
|
||||
ModalNewTableRow
|
||||
},
|
||||
mixins: [tableTabs],
|
||||
props: {
|
||||
connection: Object,
|
||||
table: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tabUid: 'data',
|
||||
isQuering: false,
|
||||
results: {},
|
||||
fields: [],
|
||||
lastTable: null
|
||||
keyUsage: [],
|
||||
lastTable: null,
|
||||
isAddModal: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -66,26 +86,17 @@ export default {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 1;
|
||||
},
|
||||
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
|
||||
};
|
||||
});
|
||||
return this.workspace.selected_tab === 'data';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
table: function () {
|
||||
table () {
|
||||
if (this.isSelected) {
|
||||
this.getTableData();
|
||||
this.lastTable = this.table;
|
||||
}
|
||||
},
|
||||
isSelected: function (val) {
|
||||
isSelected (val) {
|
||||
if (val && this.lastTable !== this.table) {
|
||||
this.getTableData();
|
||||
this.lastTable = this.table;
|
||||
@@ -97,12 +108,15 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification'
|
||||
addNotification: 'notifications/addNotification',
|
||||
setTabFields: 'workspaces/setTabFields',
|
||||
setTabKeyUsage: 'workspaces/setTabKeyUsage'
|
||||
}),
|
||||
async getTableData () {
|
||||
if (!this.table) return;
|
||||
this.isQuering = true;
|
||||
this.results = {};
|
||||
this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields: [] });
|
||||
|
||||
const params = {
|
||||
uid: this.connection.uid,
|
||||
@@ -110,10 +124,12 @@ export default {
|
||||
table: this.workspace.breadcrumbs.table
|
||||
};
|
||||
|
||||
try {
|
||||
const { status, response } = await Structure.getTableColumns(params);
|
||||
if (status === 'success')
|
||||
this.fields = response.rows;
|
||||
try { // Columns data
|
||||
const { status, response } = await Tables.getTableColumns(params);
|
||||
if (status === 'success') {
|
||||
this.fields = response;// Needed to add new rows
|
||||
this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields: response });
|
||||
}
|
||||
else
|
||||
this.addNotification({ status: 'error', message: response });
|
||||
}
|
||||
@@ -121,8 +137,8 @@ export default {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
try {
|
||||
const { status, response } = await Structure.getTableData(params);
|
||||
try { // Table data
|
||||
const { status, response } = await Tables.getTableData(params);
|
||||
|
||||
if (status === 'success')
|
||||
this.results = response;
|
||||
@@ -133,44 +149,65 @@ export default {
|
||||
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;
|
||||
},
|
||||
reloadTable () {
|
||||
this.getTableData();
|
||||
},
|
||||
showAddModal () {
|
||||
this.isAddModal = true;
|
||||
},
|
||||
hideAddModal () {
|
||||
this.isAddModal = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.workspace-tabs{
|
||||
align-content: baseline;
|
||||
.workspace-tabs {
|
||||
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{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: .3rem .6rem .4rem;
|
||||
align-items: center;
|
||||
.workspace-query-buttons {
|
||||
display: flex;
|
||||
|
||||
.workspace-query-buttons{
|
||||
display: flex;
|
||||
|
||||
.btn{
|
||||
display: flex;
|
||||
align-self: center;
|
||||
color: $body-font-color;
|
||||
margin-right: .4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-query-info{
|
||||
display: flex;
|
||||
|
||||
> div + div{
|
||||
padding-left: .6rem;
|
||||
}
|
||||
}
|
||||
.btn {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
color: $body-font-color;
|
||||
margin-right: 0.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.workspace-query-info {
|
||||
display: flex;
|
||||
|
||||
> div + div {
|
||||
padding-left: 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</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',
|
||||
run: 'Run',
|
||||
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: {
|
||||
appWelcome: 'Welcome to Antares SQL Client!',
|
||||
@@ -51,7 +60,17 @@ module.exports = {
|
||||
updateAvailable: 'Update available',
|
||||
downloadingUpdate: 'Downloading update',
|
||||
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
|
||||
short: {
|
||||
|
@@ -6,7 +6,8 @@ Vue.use(VueI18n);
|
||||
const i18n = new VueI18n({
|
||||
messages: {
|
||||
'en-US': require('./en-US'),
|
||||
'it-IT': require('./it-IT')
|
||||
'it-IT': require('./it-IT'),
|
||||
'ar-SA': require('./ar-SA')
|
||||
}
|
||||
});
|
||||
export default i18n;
|
||||
|
@@ -27,7 +27,9 @@ module.exports = {
|
||||
language: 'Lingua',
|
||||
version: 'Versione',
|
||||
donate: 'Dona',
|
||||
run: 'Esegui'
|
||||
run: 'Esegui',
|
||||
schema: 'Schema',
|
||||
results: 'Results'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Benvenuto in Antares SQL Client!',
|
||||
@@ -41,7 +43,19 @@ module.exports = {
|
||||
deleteConnection: 'Elimina connessione',
|
||||
deleteConnectionCorfirm: 'Confermi l\'eliminazione di',
|
||||
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
|
||||
short: {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
export default {
|
||||
'en-US': 'English',
|
||||
'it-IT': 'Italiano'
|
||||
'it-IT': 'Italiano',
|
||||
'ar-SA': 'العربية'
|
||||
};
|
||||
|
@@ -3,11 +3,11 @@ import { ipcRenderer } from 'electron';
|
||||
|
||||
export default class {
|
||||
static makeTest (params) {
|
||||
return ipcRenderer.invoke('testConnection', params);
|
||||
return ipcRenderer.invoke('test-connection', params);
|
||||
}
|
||||
|
||||
static checkConnection (params) {
|
||||
return ipcRenderer.invoke('checkConnection', params);
|
||||
return ipcRenderer.invoke('check-connection', params);
|
||||
}
|
||||
|
||||
static connect (params) {
|
||||
@@ -23,6 +23,6 @@ export default class {
|
||||
}
|
||||
|
||||
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 'material-design-icons/iconfont/material-icons.css';
|
||||
import '@mdi/font/css/materialdesignicons.css';
|
||||
import '@/scss/main.scss';
|
||||
|
||||
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) {
|
||||
@each $type, $color in $types {
|
||||
.type-#{$type} {
|
||||
color: $color;
|
||||
@each $type, $color in $types {
|
||||
.type-#{$type} {
|
||||
color: $color;
|
||||
|
||||
@if $type == 'number'{
|
||||
text-align: right;
|
||||
}
|
||||
@if $type == "number" {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include type-colors((
|
||||
"char": seagreen,
|
||||
"varchar": seagreen,
|
||||
"text": seagreen,
|
||||
"mediumtext": seagreen,
|
||||
@include type-colors(
|
||||
(
|
||||
"char": seagreen,
|
||||
"varchar": 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,
|
||||
"tinyint": cornflowerblue,
|
||||
"smallint": cornflowerblue,
|
||||
"mediumint": cornflowerblue,
|
||||
|
||||
"datetime": coral,
|
||||
"date": coral,
|
||||
"time": coral,
|
||||
"timestamp": coral,
|
||||
.is-null {
|
||||
color: gray;
|
||||
|
||||
"bit": lightskyblue,
|
||||
|
||||
"blob": darkorchid,
|
||||
"mediumblob": darkorchid,
|
||||
"longblob": darkorchid,
|
||||
|
||||
"unknown": gray,
|
||||
));
|
||||
|
||||
.is-null{
|
||||
color: gray;
|
||||
|
||||
&::after{
|
||||
content: 'NULL';
|
||||
}
|
||||
}
|
||||
&::after {
|
||||
content: "NULL";
|
||||
}
|
||||
}
|
||||
|
@@ -1,26 +1,26 @@
|
||||
.dbi{
|
||||
display: inline-block;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
background-size: cover;
|
||||
.dbi {
|
||||
display: inline-block;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
background-size: cover;
|
||||
|
||||
&.dbi-mysql{
|
||||
background-image: url('../images/svg/mysql.svg');
|
||||
}
|
||||
&.dbi-mysql {
|
||||
background-image: url("../images/svg/mysql.svg");
|
||||
}
|
||||
|
||||
&.dbi-maria{
|
||||
background-image: url('../images/svg/mariadb.svg');
|
||||
}
|
||||
&.dbi-maria {
|
||||
background-image: url("../images/svg/mariadb.svg");
|
||||
}
|
||||
|
||||
&.dbi-mssql{
|
||||
background-image: url('../images/svg/mssql.svg');
|
||||
}
|
||||
&.dbi-mssql {
|
||||
background-image: url("../images/svg/mssql.svg");
|
||||
}
|
||||
|
||||
&.dbi-pg{
|
||||
background-image: url('../images/svg/pg.svg');
|
||||
}
|
||||
&.dbi-pg {
|
||||
background-image: url("../images/svg/pg.svg");
|
||||
}
|
||||
|
||||
&.dbi-oracledb{
|
||||
background-image: url('../images/svg/oracledb.svg');
|
||||
}
|
||||
}
|
||||
&.dbi-oracledb {
|
||||
background-image: url("../images/svg/oracledb.svg");
|
||||
}
|
||||
}
|
||||
|
@@ -1,65 +1,71 @@
|
||||
.table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
display: table;
|
||||
|
||||
&.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;
|
||||
}
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
display: table;
|
||||
table-layout: fixed;
|
||||
|
||||
.thead{
|
||||
display: table-header-group;
|
||||
}
|
||||
|
||||
.tbody{
|
||||
.tbody {
|
||||
display: table-row-group;
|
||||
}
|
||||
}
|
||||
|
||||
.tr{
|
||||
.tr {
|
||||
display: table-row;
|
||||
}
|
||||
}
|
||||
|
||||
.td,
|
||||
.th {
|
||||
border-bottom: $border-width solid $border-color;
|
||||
padding: $unit-3 $unit-2;
|
||||
display: table-cell;
|
||||
}
|
||||
.th {
|
||||
border-bottom-width: $border-width-lg;
|
||||
}
|
||||
}
|
||||
// Scollable tables
|
||||
&.table-scroll {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
padding-bottom: 0.75rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.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
|
||||
/* Rules for sizing the icon. */
|
||||
&.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); }
|
||||
}
|
||||
&::before {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
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 {
|
||||
transition: all .3s ease;
|
||||
}
|
||||
.slide-fade-leave-active {
|
||||
transition: all .3s cubic-bezier(1.0, 0.5, 0.8, 1.0);
|
||||
}
|
||||
.slide-fade-enter, .slide-fade-leave-to {
|
||||
transform: translateX(10px);
|
||||
opacity: 0;
|
||||
}
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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-font-color: #fff;
|
||||
$bg-color: #1d1d1d;
|
||||
@@ -7,10 +7,11 @@ $bg-color-gray: #272727;
|
||||
$primary-color: #e36929;
|
||||
$success-color: #32b643;
|
||||
$error-color: #de3b28;
|
||||
$warning-color: #e0a40c;
|
||||
|
||||
/*Sizes*/
|
||||
/* Sizes */
|
||||
$titlebar-height: 1.5rem;
|
||||
$settingbar-width: 3rem;
|
||||
$explorebar-width: 14rem;
|
||||
$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 "variables";
|
||||
@import "transitions";
|
||||
@import "data-types";
|
||||
@import "table-keys";
|
||||
@import "fake-tables";
|
||||
@import "mdi-additions";
|
||||
@import "db-icons";
|
||||
@import "~spectre.css/src/spectre";
|
||||
@import "~spectre.css/src/spectre-exp";
|
||||
|
||||
body{
|
||||
user-select: none;
|
||||
body {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/*Additions*/
|
||||
/* Additions */
|
||||
@include margin-variant(3, $unit-3);
|
||||
@include margin-variant(4, $unit-4);
|
||||
@include padding-variant(3, $unit-3);
|
||||
@include padding-variant(4, $unit-4);
|
||||
|
||||
.btn.btn-gray{
|
||||
color: #fff;
|
||||
background: $bg-color-gray;
|
||||
.btn.btn-gray {
|
||||
color: #fff;
|
||||
background: $bg-color-gray;
|
||||
|
||||
&:hover{
|
||||
background: $bg-color;
|
||||
}
|
||||
&:hover {
|
||||
background: $bg-color;
|
||||
}
|
||||
}
|
||||
|
||||
.p-vcentered{
|
||||
display: flex!important;
|
||||
align-items: center;
|
||||
.p-vcentered {
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.c-help{
|
||||
cursor: help;
|
||||
.c-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
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: $bg-color-light;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba($color: #FFF, $alpha: .5);
|
||||
|
||||
&:hover {
|
||||
background: rgba($color: #FFF, $alpha: 1);
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: $bg-color-light;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba($color: #fff, $alpha: 0.5);
|
||||
|
||||
&:hover {
|
||||
background: rgba($color: #fff, $alpha: 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Animations
|
||||
@keyframes rotation {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(359deg);
|
||||
}
|
||||
}
|
||||
|
||||
.rotate {
|
||||
animation: rotation .8s infinite linear;
|
||||
animation: rotation 0.8s infinite linear;
|
||||
}
|
||||
|
||||
/*Override*/
|
||||
.modal{
|
||||
.modal-overlay,
|
||||
&.active .modal-overlay{
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.modal-sm .modal-container,
|
||||
.modal-container{
|
||||
box-shadow: 0 0 1px 0px #000;
|
||||
padding: 0;
|
||||
background: $bg-color;
|
||||
|
||||
.modal-header{
|
||||
padding: .4rem .8rem;
|
||||
text-transform: uppercase;
|
||||
background: $bg-color-gray;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
/* Override */
|
||||
.modal {
|
||||
.modal-overlay,
|
||||
&.active .modal-overlay {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.modal-container,
|
||||
.modal-sm .modal-container {
|
||||
box-shadow: 0 0 1px 0 #000;
|
||||
padding: 0;
|
||||
background: $bg-color;
|
||||
|
||||
.modal-header {
|
||||
padding: 0.4rem 0.8rem;
|
||||
text-transform: uppercase;
|
||||
background: $bg-color-gray;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab{
|
||||
border-color: #272727;
|
||||
.tab {
|
||||
border-color: #272727;
|
||||
|
||||
.tab-item {
|
||||
.btn-clear {
|
||||
margin-top: -0.1rem;
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.panel{
|
||||
border: none;
|
||||
.panel {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.badge{
|
||||
&[data-badge],
|
||||
&:not([data-badge]){
|
||||
&::after {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
.badge {
|
||||
&[data-badge],
|
||||
&:not([data-badge]) {
|
||||
&::after {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-select{
|
||||
cursor: pointer;
|
||||
.form-select {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-select,
|
||||
.form-select:not([multiple]):not([size]),
|
||||
.form-input,
|
||||
.form-checkbox .form-icon,
|
||||
.form-radio .form-icon{
|
||||
border-color: $bg-color-light;
|
||||
background: $bg-color-gray;
|
||||
.form-select:not([multiple]):not([size]),
|
||||
.form-checkbox .form-icon,
|
||||
.form-radio .form-icon {
|
||||
border-color: $bg-color-light;
|
||||
background-color: $bg-color-gray;
|
||||
}
|
||||
|
||||
.form-select:not([multiple]):not([size]):focus{
|
||||
border-color: $primary-color;
|
||||
.form-input:not(:placeholder-shown):invalid:focus {
|
||||
background: $bg-color-gray;
|
||||
}
|
||||
|
||||
.menu{
|
||||
font-size: .7rem;
|
||||
.menu-item {
|
||||
+ .menu-item{
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
.form-select:not([multiple]):not([size]):focus {
|
||||
border-color: $primary-color;
|
||||
}
|
||||
|
||||
}
|
||||
.input-group .input-group-addon {
|
||||
border-color: #3f3f3f;
|
||||
}
|
||||
|
||||
.menu {
|
||||
font-size: 0.7rem;
|
||||
|
||||
.menu-item {
|
||||
+ .menu-item {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.accordion-body {
|
||||
max-height: 500rem!important;
|
||||
max-height: 500rem !important;
|
||||
}
|
||||
|
||||
.btn.loading {
|
||||
> .material-icons,
|
||||
> span{
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
> .mdi,
|
||||
> span {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ export default {
|
||||
isSettingModal: state => state.is_setting_modal,
|
||||
selectedSettingTab: state => state.selected_setting_tab,
|
||||
getUpdateStatus: state => state.update_status,
|
||||
getDownloadProgress: state => state.download_progress
|
||||
getDownloadProgress: state => Number(state.download_progress.toFixed(1))
|
||||
},
|
||||
mutations: {
|
||||
SET_LOADING_STATUS (state, payload) {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
import { uidGen } from 'common/libs/utilities';
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
@@ -20,7 +20,7 @@ export default {
|
||||
},
|
||||
actions: {
|
||||
addNotification ({ commit }, payload) {
|
||||
payload.uid = uidGen();
|
||||
payload.uid = uidGen('N');
|
||||
commit('ADD_NOTIFICATION', payload);
|
||||
},
|
||||
removeNotification ({ commit }, uid) {
|
||||
|
@@ -6,17 +6,22 @@ export default {
|
||||
strict: true,
|
||||
state: {
|
||||
locale: 'en-US',
|
||||
explorebar_size: null
|
||||
explorebar_size: null,
|
||||
notifications_timeout: 10
|
||||
},
|
||||
getters: {
|
||||
getLocale: state => state.locale,
|
||||
getExplorebarSize: state => state.explorebar_size
|
||||
getExplorebarSize: state => state.explorebar_size,
|
||||
getNotificationsTimeout: state => state.notifications_timeout
|
||||
},
|
||||
mutations: {
|
||||
SET_LOCALE (state, locale) {
|
||||
state.locale = locale;
|
||||
i18n.locale = locale;
|
||||
},
|
||||
SET_NOTIFICATIONS_TIMEOUT (state, timeout) {
|
||||
state.notifications_timeout = timeout;
|
||||
},
|
||||
SET_EXPLOREBAR_SIZE (state, size) {
|
||||
state.explorebar_size = size;
|
||||
}
|
||||
@@ -25,6 +30,9 @@ export default {
|
||||
changeLocale ({ commit }, locale) {
|
||||
commit('SET_LOCALE', locale);
|
||||
},
|
||||
updateNotificationsTimeout ({ commit }, timeout) {
|
||||
commit('SET_NOTIFICATIONS_TIMEOUT', timeout);
|
||||
},
|
||||
changeExplorebarSize ({ commit }, size) {
|
||||
commit('SET_EXPLOREBAR_SIZE', size);
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
'use strict';
|
||||
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)
|
||||
.filter((value, index, self) => self.indexOf(value) === index);
|
||||
|
||||
@@ -28,8 +29,14 @@ export default {
|
||||
return null;
|
||||
},
|
||||
getWorkspace: state => uid => {
|
||||
const workspace = state.workspaces.filter(workspace => workspace.uid === uid);
|
||||
return workspace.length ? workspace[0] : {};
|
||||
return state.workspaces.find(workspace => workspace.uid === uid);
|
||||
},
|
||||
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 => {
|
||||
return state.workspaces
|
||||
@@ -57,10 +64,15 @@ export default {
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, breadcrumbs } : workspace);
|
||||
},
|
||||
NEW_TAB (state, uid) {
|
||||
tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1;
|
||||
|
||||
const newTab = {
|
||||
uid: uidGen(),
|
||||
uid: uidGen('T'),
|
||||
index: tabIndex[uid],
|
||||
selected: false,
|
||||
type: 'query'
|
||||
type: 'query',
|
||||
fields: [],
|
||||
keyUsage: []
|
||||
};
|
||||
state.workspaces = state.workspaces.map(workspace => {
|
||||
if (workspace.uid === uid) {
|
||||
@@ -73,8 +85,54 @@ export default {
|
||||
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 }) {
|
||||
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: {
|
||||
@@ -115,14 +173,25 @@ export default {
|
||||
uid,
|
||||
connected: false,
|
||||
selected_tab: 0,
|
||||
tabs: [{ uid: 1, type: 'table' }],
|
||||
tabs: [{
|
||||
uid: 'data',
|
||||
type: 'table',
|
||||
fields: [],
|
||||
keyUsage: []
|
||||
},
|
||||
{
|
||||
uid: 'prop',
|
||||
type: 'table',
|
||||
fields: [],
|
||||
keyUsage: []
|
||||
}],
|
||||
structure: {},
|
||||
breadcrumbs: {}
|
||||
};
|
||||
|
||||
commit('ADD_WORKSPACE', workspace);
|
||||
|
||||
if (getters.getWorkspace(uid).tabs.length < 2)
|
||||
if (getters.getWorkspace(uid).tabs.length < 3)
|
||||
dispatch('newTab', uid);
|
||||
},
|
||||
changeBreadcrumbs ({ commit, getters }, payload) {
|
||||
@@ -131,8 +200,17 @@ export default {
|
||||
newTab ({ commit }, uid) {
|
||||
commit('NEW_TAB', uid);
|
||||
},
|
||||
removeTab ({ commit }, payload) {
|
||||
commit('REMOVE_TAB', payload);
|
||||
},
|
||||
selectTab ({ commit }, 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';
|
||||
|
||||
export default store => {
|
||||
ipcRenderer.on('checkingForUpdate', () => {
|
||||
ipcRenderer.on('checking-for-update', () => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'checking');
|
||||
});
|
||||
ipcRenderer.on('updateAvailable', () => {
|
||||
ipcRenderer.on('update-available', () => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'available');
|
||||
});
|
||||
ipcRenderer.on('updateNotAvailable', () => {
|
||||
ipcRenderer.on('update-not-available', () => {
|
||||
store.commit('application/CHANGE_UPDATE_STATUS', 'noupdate');
|
||||
});
|
||||
ipcRenderer.on('checkFailed', () => {
|
||||
ipcRenderer.on('check-failed', () => {
|
||||
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_PROGRESS_PERCENTAGE', data.percent);
|
||||
});
|
||||
ipcRenderer.on('updateDownloaded', () => {
|
||||
ipcRenderer.on('update-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 MonacoEditorPlugin = require('monaco-editor-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
new MonacoEditorPlugin({
|
||||
languages: ['sql']
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
PACKAGE_VERSION: JSON.stringify(require('./package.json').version)
|
||||
@@ -17,7 +20,7 @@ module.exports = {
|
||||
{
|
||||
loader: 'sass-loader',
|
||||
options: {
|
||||
prependData: '@import "@/scss/_variables.scss";'
|
||||
additionalData: '@import "@/scss/_variables.scss";'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
Reference in New Issue
Block a user