1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-06-05 21:59:22 +02:00

Compare commits

...

78 Commits

Author SHA1 Message Date
949f7add8f chore(release): 0.0.4 2020-08-06 10:22:06 +02:00
968ec1edf7 ci: improvements on travis release config 2020-08-06 10:20:36 +02:00
a9d3a57281 Merge pull request #26 from EStarium/dependabot/npm_and_yarn/sass-loader-9.0.3
Bump sass-loader from 9.0.2 to 9.0.3
2020-08-06 08:54:45 +02:00
dependabot[bot]
f787439009 Bump sass-loader from 9.0.2 to 9.0.3
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 9.0.2 to 9.0.3.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v9.0.2...v9.0.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-06 06:11:22 +00:00
b03d461e21 build: fix to build configuration on Travis 2020-08-05 22:09:23 +02:00
5c05e3e9e9 refactor: moved queri fields mapping to main process 2020-08-05 22:08:20 +02:00
0089c0cbac feat: window title in app title bar 2020-08-05 13:53:30 +02:00
4fd72ec9e7 refactor: improvements to blob editor and code cleanup 2020-08-04 17:54:19 +02:00
712fe9f00d feat: blob fields edit/view/download 2020-08-03 18:07:08 +02:00
092e8a0732 style: 🎨 stylelint implementation 2020-07-31 18:16:28 +02:00
70908eb076 Minor Improvements 2020-07-31 15:45:32 +02:00
f8a0783769 Merge pull request #25 from EStarium/dependabot/npm_and_yarn/webpack-4.44.1
Bump webpack from 4.44.0 to 4.44.1
2020-07-31 08:10:30 +02:00
dependabot[bot]
cdf964ef07 Bump webpack from 4.44.0 to 4.44.1
Bumps [webpack](https://github.com/webpack/webpack) from 4.44.0 to 4.44.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v4.44.0...v4.44.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-31 05:22:32 +00:00
413b56916c Notifications timeout, large text editor 2020-07-30 19:12:29 +02:00
acd3310228 Merge pull request #24 from EStarium/dependabot/npm_and_yarn/vue-i18n-8.20.0
Bump vue-i18n from 8.19.0 to 8.20.0
2020-07-30 08:16:32 +02:00
dependabot[bot]
e014f81a9c Bump vue-i18n from 8.19.0 to 8.20.0
Bumps [vue-i18n](https://github.com/kazupon/vue-i18n) from 8.19.0 to 8.20.0.
- [Release notes](https://github.com/kazupon/vue-i18n/releases)
- [Changelog](https://github.com/kazupon/vue-i18n/blob/v8.x/CHANGELOG.md)
- [Commits](https://github.com/kazupon/vue-i18n/compare/v8.19.0...v8.20.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-30 05:32:52 +00:00
530361144e Merge branch 'master' of https://github.com/EStarium/antares 2020-07-29 15:56:32 +02:00
d69e411581 Moved to electron 9 2020-07-29 15:56:29 +02:00
9a3a0513e2 Merge pull request #23 from EStarium/dependabot/npm_and_yarn/electron-9.1.2
Bump electron from 8.4.0 to 9.1.2
2020-07-29 15:04:58 +02:00
dependabot[bot]
be8fa96c93 Bump electron from 8.4.0 to 9.1.2
Bumps [electron](https://github.com/electron/electron) from 8.4.0 to 9.1.2.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/master/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v8.4.0...v9.1.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-29 05:58:58 +00:00
587116bd20 Merge pull request #22 from EStarium/dependabot/npm_and_yarn/electron-updater-4.3.4
Bump electron-updater from 4.3.1 to 4.3.4
2020-07-28 09:53:45 +02:00
dependabot[bot]
145d1dd1ad Bump electron-updater from 4.3.1 to 4.3.4
Bumps [electron-updater](https://github.com/electron-userland/electron-builder) from 4.3.1 to 4.3.4.
- [Release notes](https://github.com/electron-userland/electron-builder/releases)
- [Changelog](https://github.com/electron-userland/electron-builder/blob/master/CHANGELOG.md)
- [Commits](https://github.com/electron-userland/electron-builder/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 07:50:07 +00:00
457410b65a Merge pull request #21 from EStarium/dependabot/npm_and_yarn/electron-builder-22.8.0
Bump electron-builder from 22.7.0 to 22.8.0
2020-07-28 08:09:09 +02:00
dependabot[bot]
b183eacae1 Bump electron-builder from 22.7.0 to 22.8.0
Bumps [electron-builder](https://github.com/electron-userland/electron-builder) from 22.7.0 to 22.8.0.
- [Release notes](https://github.com/electron-userland/electron-builder/releases)
- [Changelog](https://github.com/electron-userland/electron-builder/blob/master/CHANGELOG.md)
- [Commits](https://github.com/electron-userland/electron-builder/compare/v22.7.0...v22.8.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-28 05:20:18 +00:00
76cafdb69a Merge pull request #20 from ReverbOD/master
Update it-IT translation
2020-07-27 20:55:10 +02:00
Giuseppe Gigliotti
2d63ddb9c7 Update it-IT translation
fixed & updated italian translation
2020-07-27 20:12:48 +02:00
f12f00dbb7 Added .travis.yml 2020-07-27 16:27:33 +02:00
1ecb6d892c Merge pull request #19 from EStarium/dependabot/npm_and_yarn/webpack-4.44.0
Bump webpack from 4.43.0 to 4.44.0
2020-07-27 07:59:02 +02:00
fcd83f35d8 Merge pull request #18 from EStarium/dependabot/npm_and_yarn/vue-i18n-8.19.0
Bump vue-i18n from 8.18.2 to 8.19.0
2020-07-27 07:58:47 +02:00
dependabot[bot]
7a4d8286a6 Bump webpack from 4.43.0 to 4.44.0
Bumps [webpack](https://github.com/webpack/webpack) from 4.43.0 to 4.44.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v4.43.0...v4.44.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-27 05:41:17 +00:00
dependabot[bot]
dd5ec2c661 Bump vue-i18n from 8.18.2 to 8.19.0
Bumps [vue-i18n](https://github.com/kazupon/vue-i18n) from 8.18.2 to 8.19.0.
- [Release notes](https://github.com/kazupon/vue-i18n/releases)
- [Changelog](https://github.com/kazupon/vue-i18n/blob/v8.x/CHANGELOG.md)
- [Commits](https://github.com/kazupon/vue-i18n/compare/v8.18.2...v8.19.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-27 05:39:54 +00:00
3f0e5d3512 Fix typos 2020-07-26 15:10:01 +02:00
ac01511c10 Update version 2020-07-24 17:34:39 +02:00
4a83ae7e75 Update README.md 2020-07-24 14:05:14 +02:00
60132c94a1 Results table improvements 2020-07-24 13:26:56 +02:00
fdf5bef5ad Delete rows 2020-07-23 19:10:14 +02:00
67f55fbeb9 Merge pull request #17 from EStarium/dependabot/npm_and_yarn/mssql-6.2.1
Bump mssql from 6.2.0 to 6.2.1
2020-07-23 08:55:58 +02:00
dependabot[bot]
fd5a8548c7 Bump mssql from 6.2.0 to 6.2.1
Bumps [mssql](https://github.com/tediousjs/node-mssql) from 6.2.0 to 6.2.1.
- [Release notes](https://github.com/tediousjs/node-mssql/releases)
- [Changelog](https://github.com/tediousjs/node-mssql/blob/master/CHANGELOG.txt)
- [Commits](https://github.com/tediousjs/node-mssql/compare/v6.2.0...v6.2.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-23 05:45:56 +00:00
425ecf838d Row multi select 2020-07-22 18:30:52 +02:00
1a8a49eceb Merge pull request #15 from EStarium/dependabot/npm_and_yarn/codemirror-5.56.0
Bump codemirror from 5.55.0 to 5.56.0
2020-07-21 10:52:21 +02:00
dependabot[bot]
e9fffcc37e Bump codemirror from 5.55.0 to 5.56.0
Bumps [codemirror](https://github.com/codemirror/CodeMirror) from 5.55.0 to 5.56.0.
- [Release notes](https://github.com/codemirror/CodeMirror/releases)
- [Changelog](https://github.com/codemirror/CodeMirror/blob/master/CHANGELOG.md)
- [Commits](https://github.com/codemirror/CodeMirror/compare/5.55.0...5.56.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-21 05:56:20 +00:00
bba7c4af6f Merge pull request #14 from EStarium/dependabot/npm_and_yarn/electron-devtools-installer-3.1.1
Bump electron-devtools-installer from 3.1.0 to 3.1.1
2020-07-17 08:52:56 +02:00
dependabot[bot]
376d74c7dc Bump electron-devtools-installer from 3.1.0 to 3.1.1
Bumps [electron-devtools-installer](https://github.com/MarshallOfSound/electron-devtools-installer) from 3.1.0 to 3.1.1.
- [Release notes](https://github.com/MarshallOfSound/electron-devtools-installer/releases)
- [Changelog](https://github.com/MarshallOfSound/electron-devtools-installer/blob/master/.releaserc.json)
- [Commits](https://github.com/MarshallOfSound/electron-devtools-installer/compare/v3.1.0...v3.1.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-17 05:20:01 +00:00
307a32aff6 Starting implementation context on query Table 2020-07-10 19:51:36 +02:00
d1aaad276b Merge pull request #13 from EStarium/dependabot/npm_and_yarn/pg-8.3.0
Bump pg from 8.2.2 to 8.3.0
2020-07-10 08:57:31 +02:00
dependabot[bot]
b23b4f18b9 Bump pg from 8.2.2 to 8.3.0
Bumps [pg](https://github.com/brianc/node-postgres) from 8.2.2 to 8.3.0.
- [Release notes](https://github.com/brianc/node-postgres/releases)
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/compare/pg@8.2.2...pg@8.3.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-10 05:36:39 +00:00
334cfa9047 Merge pull request #12 from EStarium/dependabot/npm_and_yarn/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19
2020-07-09 08:40:12 +02:00
dependabot[bot]
2f6d16c730 Bump lodash from 4.17.15 to 4.17.19
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-09 05:34:34 +00:00
b8e09e7003 Merge pull request #10 from EStarium/dependabot/npm_and_yarn/pg-8.2.2
Bump pg from 8.2.1 to 8.2.2
2020-07-08 09:02:13 +02:00
9caf424331 Merge pull request #11 from EStarium/dependabot/npm_and_yarn/sass-loader-9.0.2
Bump sass-loader from 9.0.1 to 9.0.2
2020-07-08 09:01:59 +02:00
dependabot[bot]
9e5e545478 Bump sass-loader from 9.0.1 to 9.0.2
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 9.0.1 to 9.0.2.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v9.0.1...v9.0.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-08 05:22:00 +00:00
dependabot[bot]
c9ab731bb4 Bump pg from 8.2.1 to 8.2.2
Bumps [pg](https://github.com/brianc/node-postgres) from 8.2.1 to 8.2.2.
- [Release notes](https://github.com/brianc/node-postgres/releases)
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/compare/pg@8.2.1...pg@8.2.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-08 05:18:40 +00:00
57832c43aa Merge pull request #8 from EStarium/dependabot/npm_and_yarn/vuedraggable-2.24.0
Bump vuedraggable from 2.23.2 to 2.24.0
2020-07-07 08:53:49 +02:00
dependabot[bot]
184363369b Bump vuedraggable from 2.23.2 to 2.24.0
Bumps [vuedraggable](https://github.com/SortableJS/Vue.Draggable) from 2.23.2 to 2.24.0.
- [Release notes](https://github.com/SortableJS/Vue.Draggable/releases)
- [Commits](https://github.com/SortableJS/Vue.Draggable/compare/v2.23.2...v2.24.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-07 05:38:42 +00:00
b221dd12ff Merge pull request #7 from EStarium/dependabot/npm_and_yarn/sass-loader-9.0.1
Bump sass-loader from 9.0.0 to 9.0.1
2020-07-06 08:56:30 +02:00
dependabot[bot]
1324464aa3 Bump sass-loader from 9.0.0 to 9.0.1
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 9.0.0 to 9.0.1.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v9.0.0...v9.0.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-06 05:40:58 +00:00
187b4f50f9 Datetime fields precision 2020-07-05 16:06:56 +02:00
262a476f50 Merge pull request #6 from EStarium/dependabot/npm_and_yarn/sass-loader-9.0.0
Bump sass-loader from 8.0.2 to 9.0.0
2020-07-03 08:51:07 +02:00
dependabot[bot]
f00c2600e5 Bump sass-loader from 8.0.2 to 9.0.0
Bumps [sass-loader](https://github.com/webpack-contrib/sass-loader) from 8.0.2 to 9.0.0.
- [Release notes](https://github.com/webpack-contrib/sass-loader/releases)
- [Changelog](https://github.com/webpack-contrib/sass-loader/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/sass-loader/compare/v8.0.2...v9.0.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-03 06:17:52 +00:00
75a7db9c05 Merge branch 'master' of https://github.com/EStarium/antares 2020-07-02 19:17:28 +02:00
50cd852d01 Editable datetime fields 2020-07-02 19:17:25 +02:00
bb8cfa533b Merge pull request #5 from EStarium/dependabot/npm_and_yarn/spectre.css-0.5.9
Bump spectre.css from 0.5.8 to 0.5.9
2020-07-02 08:59:48 +02:00
dependabot[bot]
1ddc8b5dca Bump spectre.css from 0.5.8 to 0.5.9
Bumps [spectre.css](https://github.com/picturepan2/spectre) from 0.5.8 to 0.5.9.
- [Release notes](https://github.com/picturepan2/spectre/releases)
- [Changelog](https://github.com/picturepan2/spectre/blob/master/CHANGELOG.md)
- [Commits](https://github.com/picturepan2/spectre/compare/v0.5.8...v0.5.9)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-02 06:13:17 +00:00
8a4c628128 Minor fix 2020-06-30 18:20:07 +02:00
0076f146fa Merge branch 'master' of https://github.com/EStarium/antares 2020-06-30 18:15:01 +02:00
098d12f462 Update packages 2020-06-30 18:14:57 +02:00
b619285d94 Merge pull request #3 from EStarium/dependabot/npm_and_yarn/vuex-3.5.1
Bump vuex from 3.4.0 to 3.5.1
2020-06-30 09:02:46 +02:00
dependabot[bot]
a5fed1bb64 Bump vuex from 3.4.0 to 3.5.1
Bumps [vuex](https://github.com/vuejs/vuex) from 3.4.0 to 3.5.1.
- [Release notes](https://github.com/vuejs/vuex/releases)
- [Changelog](https://github.com/vuejs/vuex/blob/dev/CHANGELOG.md)
- [Commits](https://github.com/vuejs/vuex/compare/v3.4.0...v3.5.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-30 06:13:53 +00:00
55ec03bd8e Merge pull request #2 from EStarium/dependabot/npm_and_yarn/electron-9.0.5
Bump electron from 8.3.4 to 9.0.5
2020-06-29 09:09:19 +02:00
dependabot[bot]
d1977fbd75 Bump electron from 8.3.4 to 9.0.5
Bumps [electron](https://github.com/electron/electron) from 8.3.4 to 9.0.5.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/master/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v8.3.4...v9.0.5)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-29 07:07:31 +00:00
3c20c0733c Update dependabot.yml 2020-06-29 09:05:39 +02:00
1d4a353d5c Create dependabot.yml 2020-06-29 09:04:39 +02:00
db71777cbc FIelds edit implemented 2020-06-28 15:31:16 +02:00
f350fe8203 Partial implementation of fields edit 2020-06-27 15:14:08 +02:00
28c3f87dd8 Create FUNDING.yml 2020-06-27 13:40:26 +02:00
cc8dbb8df7 Start implementing fields edit 2020-06-26 18:14:16 +02:00
85ac1bc85f Changed readme screenshot size 2020-06-20 22:00:10 +02:00
baea5c26e2 Text connection fix 2020-06-20 21:58:57 +02:00
69 changed files with 2226 additions and 14837 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
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
View 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: "daily"

2
.gitignore vendored
View File

@@ -6,4 +6,4 @@ thumbs.db
.vscode
TODO.md
*.txt
dev-app-update.yml
package-lock.json

14
.stylelintrc Normal file
View 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
View 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

View File

@@ -1,7 +1,11 @@
# 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.4](https://github.com/EStarium/antares/compare/v0.0.3-alpha...v0.0.4) (2020-08-06)
### Features
- **Initial release:**
* 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))

View File

@@ -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:

View File

@@ -1,7 +1,35 @@
<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! 🚧
![GitHub package.json version](https://img.shields.io/github/package-json/v/estarium/antares) [![Build Status](https://travis-ci.com/EStarium/antares.svg?branch=master)](https://travis-ci.com/EStarium/antares) ![GitHub All Releases](https://img.shields.io/github/downloads/estarium/antares/total) ![GitHub](https://img.shields.io/github/license/estarium/antares)
Antares is an SQL client based on Electron.js and Vue.js 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 in a development state, it lacks many features, and is'nt 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. At moment i'm testing only on Windows.
## Currently supported
### Databases
- [x] MySQL/MariaDB
- [ ] PostrgreSQL
- [ ] MSSQL
- [ ] SQLite
- [ ] OracleDB
- [ ] More...
### Operating Systems
- [x] Windows
- [ ] Linux
- [ ] MacOS
## Translations
[Giuseppe Gigliotti](https://github.com/ReverbOD) / [Italian Translation](https://github.com/EStarium/antares/pull/20)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 25 KiB

BIN
docs/screen-alpha.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

13749
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,24 +1,56 @@
{
"name": "antares",
"productName": "Antares",
"version": "0.0.1-alpha",
"version": "0.0.4",
"description": "A cross-platform easy to use SQL client.",
"main": "src/main/index.js",
"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"
},
"author": "Fabio Di Stasio <fabio286@gmail.com>",
"build": {
"npmRebuild": false,
"asar": true,
"appId": "com.estarium.antares",
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
"files": [
"static/*"
]
"dist/**/*",
"src/**/*",
"node_modules/**/*",
"package.json"
],
"dmg": {
"contents": [
{
"x": 130,
"y": 220
},
{
"x": 410,
"y": 220,
"type": "link",
"path": "/Applications"
}
]
},
"win": {
"target": [
"nsis"
]
},
"linux": {
"target": [
"deb",
"AppImage"
],
"category": "Development"
}
},
"electronWebpack": {
"whiteListedModules": [
@@ -29,41 +61,46 @@
}
},
"dependencies": {
"codemirror": "^5.54.0",
"electron-log": "^4.2.1",
"electron-updater": "^4.3.1",
"lodash": "^4.17.15",
"codemirror": "^5.56.0",
"electron-log": "^4.2.2",
"electron-updater": "^4.3.4",
"lodash": "^4.17.19",
"material-design-icons": "^3.0.1",
"moment": "^2.26.0",
"mssql": "^6.2.0",
"moment": "^2.27.0",
"mssql": "^6.2.1",
"mysql": "^2.18.1",
"pg": "^8.2.1",
"pg": "^8.3.0",
"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.20.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": "^9.1.2",
"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-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",
"node-sass": "^4.14.1",
"sass-loader": "^8.0.2",
"sass-loader": "^9.0.3",
"standard-version": "^8.0.2",
"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
View 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'];

View 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);
}

View 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];
}

View File

@@ -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];
}

View 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"'\\\%]/g);
/**
* 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)];
});
}
export { sqlEscaper };

View File

@@ -0,0 +1,4 @@
'use strict';
export function uidGen () {
return Math.random().toString(36).substr(2, 9).toUpperCase();
};

View File

@@ -12,35 +12,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 +46,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 +65,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

View File

@@ -0,0 +1,7 @@
import { app, ipcMain } from 'electron';
export default () => {
ipcMain.on('closeApp', () => {
app.exit();
});
};

View File

@@ -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();
};

View File

@@ -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() };
}
});
};

View File

@@ -0,0 +1,47 @@
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('getTableColumns', 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('getTableData', 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('updateTableCell', 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('deleteTableRows', 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() };
}
});
};

View File

@@ -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: '',
@@ -32,7 +33,7 @@ export class AntaresConnector {
join: [],
update: [],
insert: [],
delete: []
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,12 @@ export class AntaresConnector {
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 +152,16 @@ 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;
}
/**
* @returns {string} SQL string
* @memberof AntaresConnector
@@ -156,30 +169,35 @@ 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 && !!this._query.from)
fromRaw = 'FROM';
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 +205,13 @@ 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(', ')} ` : '';
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 +229,7 @@ export class AntaresConnector {
break;
}
return `${selectRaw}${fromRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}`;
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}`;
}
/**
@@ -225,9 +248,9 @@ 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) => {

View File

@@ -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();
}
}

View File

@@ -13,13 +13,22 @@ 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,
precision: field.DATETIME_PRECISION
};
});
}
}

53
src/main/models/Tables.js Normal file
View File

@@ -0,0 +1,53 @@
'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;
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]: `= ${params.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();
}
}

View File

@@ -40,8 +40,7 @@ export default {
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings')
},
data () {
return {
};
return {};
},
computed: {
...mapGetters({
@@ -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>

View File

@@ -1,5 +1,5 @@
<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">
@@ -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>

View File

@@ -38,56 +38,57 @@ export default {
</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;
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: 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>

View File

@@ -71,25 +71,24 @@ 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;
}
.expanded .notification-message {
white-space: initial;
}
</style>

View File

@@ -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>

View File

@@ -71,7 +71,3 @@ export default {
}
};
</script>
<style>
</style>

View File

@@ -36,7 +36,7 @@
<option value="maria">
MariaDB
</option>
<option value="mssql">
<!-- <option value="mssql">
Microsoft SQL
</option>
<option value="pg">
@@ -44,7 +44,7 @@
</option>
<option value="oracledb">
Oracle DB
</option>
</option> -->
</select>
</div>
</div>
@@ -230,7 +230,7 @@ export default {
</script>
<style scoped>
.modal-container{
max-width: 450px;
}
.modal-container {
max-width: 450px;
}
</style>

View File

@@ -40,7 +40,7 @@
<option value="maria">
MariaDB
</option>
<option value="mssql">
<!-- <option value="mssql">
Microsoft SQL
</option>
<option value="pg">
@@ -48,7 +48,7 @@
</option>
<option value="oracledb">
Oracle DB
</option>
</option> -->
</select>
</div>
</div>
@@ -148,7 +148,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';
@@ -253,7 +253,7 @@ export default {
</script>
<style scoped>
.modal-container{
max-width: 450px;
}
.modal-container {
max-width: 450px;
}
</style>

View File

@@ -45,8 +45,8 @@
<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>
@@ -69,12 +69,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 +108,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>
@@ -113,6 +134,7 @@ export default {
return {
isUpdate: false,
localLocale: null,
localTimeout: null,
selectedTab: 'general'
};
},
@@ -121,7 +143,8 @@ export default {
appName: 'application/appName',
appVersion: 'application/appVersion',
selectedSettingTab: 'application/selectedSettingTab',
selectedLocale: 'settings/getLocale'
selectedLocale: 'settings/getLocale',
notificationsTimeout: 'settings/getNotificationsTimeout'
}),
locales () {
const locales = [];
@@ -133,42 +156,49 @@ export default {
},
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;
}
.form-label{
display: flex;
align-items: center;
}
}
.badge::after {
background: #32b643;
}
.form-label {
display: flex;
align-items: center;
}
}
}
</style>

View File

@@ -78,7 +78,7 @@ export default {
</script>
<style lang="scss">
.empty{
color: $body-font-color;
.empty {
color: $body-font-color;
}
</style>

View File

@@ -69,21 +69,20 @@ export default {
</script>
<style lang="scss">
.editor-wrapper{
border-bottom: 1px solid #444444;
}
.editor-wrapper {
border-bottom: 1px solid #444;
}
.CodeMirror{
height: 200px;
.CodeMirror {
height: 200px;
.CodeMirror-scroll{
max-width: 100%;
}
.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>

View File

@@ -69,7 +69,3 @@ export default {
}
};
</script>
<style>
</style>

View File

@@ -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>

View File

@@ -11,11 +11,11 @@
<div class="footer-right-elements">
<ul class="footer-elements">
<li class="footer-element footer-link">
<li class="footer-element footer-link" @click="openOutside('https://www.patreon.com/fabio286')">
<i class="material-icons md-18 mr-1">favorite</i>
<small>{{ $t('word.donate') }}</small>
</li>
<li class="footer-element footer-link">
<li class="footer-element footer-link" @click="openOutside('https://github.com/EStarium/antares/issues')">
<i class="material-icons md-18">bug_report</i>
</li>
<li class="footer-element footer-link" @click="showSettingModal('about')">
@@ -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>

View File

@@ -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: 9;
right: 1rem;
bottom: 1rem;
}
</style>

View File

@@ -106,101 +106,98 @@ 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;
}
}
}
}
}
.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;
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>

View File

@@ -5,7 +5,7 @@
<img class="titlebar-logo" :src="require('@/images/logo.svg').default">
</div>
<div class="titlebar-elements">
<!-- -->
{{ windowTitle }}
</div>
<div class="titlebar-elements">
<div
@@ -37,7 +37,8 @@
</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('closeApp');
},
minimizeApp () {
this.w.minimize();
@@ -81,55 +98,55 @@ 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-logo {
height: $titlebar-height;
padding: 0 0.4rem;
}
.titlebar-elements{
display: flex;
align-items: center;
.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;
.titlebar-logo{
height: $titlebar-height;
padding: 0 .4rem;
}
&:hover {
opacity: 1;
background: rgba($color: #fff, $alpha: 0.2);
}
.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;
}
}
&.close-button:hover {
background: red;
}
}
}
}
}
</style>

View File

@@ -82,7 +82,6 @@ export default {
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification',
addWorkspace: 'workspaces/addWorkspace',
connectWorkspace: 'workspaces/connectWorkspace',
removeConnected: 'workspaces/removeConnected',
@@ -93,89 +92,94 @@ export default {
</script>
<style lang="scss">
.workspace{
padding: 0;
margin: 0;
.workspace {
padding: 0;
margin: 0;
.workspace-tabs{
overflow: auto;
height: calc(100vh - #{$excluding-size});
.workspace-tabs {
overflow: auto;
height: calc(100vh - #{$excluding-size});
.tab-block{
background: $bg-color-light;
margin-top: 0;
.tab-block {
background: $bg-color-light;
margin-top: 0;
.tab-item{
max-width: 12rem;
width: fit-content;
flex: initial;
.tab-item {
max-width: 12rem;
width: fit-content;
flex: initial;
&.active a{
opacity: 1;
}
&.active a {
opacity: 1;
}
> a{
padding: .2rem .8rem;
color: $body-font-color;
cursor: pointer;
display: flex;
align-items: center;
opacity: .7;
transition: opacity .2s;
> a {
padding: 0.2rem 0.8rem;
color: $body-font-color;
cursor: pointer;
display: flex;
align-items: center;
opacity: 0.7;
transition: opacity 0.2s;
&:hover{
opacity: 1;
}
&:hover {
opacity: 1;
}
> span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
}
}
.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: .1rem .4rem;
font-weight: 700;
font-size: .7rem;
}
.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;
> span {
overflow: hidden;
font-size: .7rem;
&:focus{
box-shadow:inset 0px 0px 0px 1px $body-font-color;
background: rgba($color: #000000, $alpha: .3);
outline: none;
}
}
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
}
}
}
.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;
> 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;
&:focus {
box-shadow: inset 0 0 0 1px $body-font-color;
background: rgba($color: #000, $alpha: 0.3);
outline: none;
}
}
}
}
}
</style>

View File

@@ -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>

View File

@@ -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>

View File

@@ -56,35 +56,35 @@ 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;
}
}
.menu-item {
line-height: 1.2;
}
.database-tables {
margin-left: 1.2rem;
}
}
</style>

View File

@@ -8,15 +8,11 @@
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>
</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">
@@ -31,8 +27,11 @@
<div class="workspace-query-results column col-12">
<WorkspaceQueryTable
v-if="results"
ref="queryTable"
:results="results"
:fields="resultsFields"
:fields="fields"
@updateField="updateField"
@deleteSelected="deleteSelected"
/>
</div>
</div>
@@ -40,9 +39,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 +51,17 @@ export default {
QueryEditor,
WorkspaceQueryTable
},
mixins: [tableTabs],
props: {
connection: Object
},
data () {
return {
query: '',
lastQuery: '',
isQuering: false,
results: {}
results: {},
fields: []
};
},
computed: {
@@ -67,25 +71,25 @@ 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 (this.results.fields.length)
return this.results.fields[0].orgTable;
return '';
}
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
async runQuery () {
if (!this.query) return;
async runQuery (query) {
if (!query) return;
this.isQuering = true;
this.results = {};
try {
const params = {
uid: this.connection.uid,
query: this.query,
query,
schema: this.workspace.breadcrumbs.schema
};
@@ -99,44 +103,64 @@ export default {
this.addNotification({ status: 'error', message: err.stack });
}
try {
const params = {
uid: this.connection.uid,
schema: this.workspace.breadcrumbs.schema,
table: this.table
};
const { status, response } = await Tables.getTableColumns(params);
if (status === 'success')
this.fields = response;
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isQuering = false;
this.lastQuery = query;
},
reloadTable () {
this.runQuery(this.lastQuery);
}
}
};
</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>

View File

@@ -1,99 +1,83 @@
<template>
<BaseVirtualScroll
v-if="results.rows"
ref="resultTable"
:items="localResults"
:item-height="25"
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">
<i
v-if="field.key"
class="material-icons column-key c-help"
:class="`key-${field.key}`"
:title="keyName(field.key)"
>vpn_key</i>
<span>{{ field.name }}</span>
<div>
<TableContext
v-if="isContext"
:context-event="contextEvent"
:selected-rows="selectedRows"
@deleteSelected="deleteSelected"
@closeContext="isContext = false"
/>
<BaseVirtualScroll
v-if="results.rows"
ref="resultTable"
:items="sortedResults"
:item-height="25"
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 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="`key-${field.key}`"
:title="keyName(field.key)"
>vpn_key</i>
<span>{{ field.name }}</span>
<i v-if="currentSort === field.name" class="material-icons sort-icon">{{ currentSortDir === 'asc' ? 'arrow_upward':'arrow_downward' }}</i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tbody">
<div
v-for="row in items"
:key="row._id"
class="tr"
>
<div class="tbody">
<div
v-for="(col, cKey) in row"
:key="cKey"
class="td"
:class="`type-${fieldType(cKey)}${isNull(col)}`"
:style="{'display': cKey === '_id' ? 'none' : ''}"
tabindex="0"
v-for="row in items"
:key="row._id"
class="tr"
:class="{'selected': selectedRows.includes(row._id)}"
@click="selectRow($event, row._id)"
>
{{ col | typeFormat(fieldType(cKey)) }}
<WorkspaceQueryTableCell
v-for="(col, cKey) in row"
:key="cKey"
:content="col"
:field="cKey"
:precision="fieldPrecision(cKey)"
:type="fieldType(cKey)"
@updateField="updateField($event, row[primaryField.name])"
@contextmenu="contextMenu($event, {id: row._id, field: cKey})"
/>
</div>
</div>
</div>
</div>
</template>
</BaseVirtualScroll>
</template>
</BaseVirtualScroll>
</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 WorkspaceQueryTableCell from '@/components/WorkspaceQueryTableCell';
import TableContext from '@/components/WorkspaceQueryTableContext';
import { mapActions } 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,
WorkspaceQueryTableCell,
TableContext
},
props: {
results: Object,
@@ -102,11 +86,38 @@ export default {
data () {
return {
resultsSize: 1000,
localResults: []
localResults: [],
isContext: false,
contextEvent: null,
selectedCell: null,
selectedRows: [],
currentSort: '',
currentSortDir: 'asc'
};
},
computed: {
primaryField () {
return this.fields.filter(field => field.key === 'pri')[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;
}
},
watch: {
results () {
this.resetSort();
this.localResults = this.results.rows ? this.results.rows.map(item => {
return { ...item, _id: uidGen() };
}) : [];
@@ -114,7 +125,7 @@ export default {
},
updated () {
if (this.$refs.resultTable)
this.resizeResults();
this.refreshScroller();
},
mounted () {
window.addEventListener('resize', this.resizeResults);
@@ -123,6 +134,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 +145,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.precision;
return length;
},
keyName (key) {
switch (key) {
@@ -146,7 +165,7 @@ export default {
return 'UNKNOWN ' + key;
}
},
resizeResults (e) {
resizeResults () {
if (this.$refs.resultTable) {
const el = this.$refs.resultTable.$el;
const footer = document.getElementById('footer');
@@ -155,7 +174,94 @@ export default {
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('updateField', 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('deleteSelected', 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 +269,46 @@ 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;
}
.sort-icon {
font-size: 0.7rem;
line-height: 1;
margin-left: 0.2rem;
}
&.key-uni{
color: deepskyblue;
}
.column-key {
transform: rotate(90deg);
font-size: 0.7rem;
line-height: 1.5;
margin-right: 0.2rem;
&.key-mul{
color: palegreen;
}
&.key-pri {
color: goldenrod;
}
&.key-uni {
color: deepskyblue;
}
&.key-mul {
color: palegreen;
}
}
</style>

View File

@@ -0,0 +1,361 @@
<template>
<div
v-if="field !== '_id'"
ref="cell"
class="td p-0"
tabindex="0"
@contextmenu.prevent="$emit('contextmenu', $event)"
>
<span
v-if="!isInlineEditor"
class="cell-content px-2"
:class="`${isNull(content)} type-${type}`"
@dblclick="editON"
>{{ content | typeFormat(type, precision) | cutText }}</span>
<template v-else>
<input
v-if="inputProps.mask"
ref="editField"
v-model="localContent"
v-mask="inputProps.mask"
:type="inputProps.type"
autofocus
class="editable-field px-2"
@blur="editOFF"
>
<input
v-else
ref="editField"
v-model="localContent"
:type="inputProps.type"
autofocus
class="editable-field px-2"
@blur="editOFF"
>
</template>
<ConfirmModal
v-if="isTextareaEditor"
:confirm-text="$t('word.update')"
size="medium"
@confirm="editOFF"
@hide="hideEditorModal"
>
<template :slot="'header'">
{{ $t('word.edit') }} "{{ field }}"
</template>
<div :slot="'body'">
<div class="mb-2">
<div>
<textarea
v-model="localContent"
class="form-input textarea-editor"
/>
</div>
<div class="editor-field-info">
<div><b>{{ $t('word.size') }}</b>: {{ localContent.length }}</div>
<div><b>{{ $t('word.type') }}</b>: {{ type.toUpperCase() }}</div>
</div>
</div>
</div>
</ConfirmModal>
<ConfirmModal
v-if="isBlobEditor"
:confirm-text="$t('word.update')"
@confirm="editOFF"
@hide="hideEditorModal"
>
<template :slot="'header'">
{{ $t('word.edit') }} "{{ field }}"
</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(localContent)}`"
class="img-responsive p-centered bg-checkered"
>
<div v-else class="text-center">
<i class="material-icons md-36">insert_drive_file</i>
</div>
<div class="editor-buttons mt-2">
<button class="btn btn-link btn-sm" @click="downloadFile">
<span>{{ $t('word.download') }}</span>
<i class="material-icons ml-1">file_download</i>
</button>
<button class="btn btn-link btn-sm" @click="prepareToDelete">
<span>{{ $t('word.delete') }}</span>
<i class="material-icons ml-1">delete_forever</i>
</button>
</div>
</div>
</transition>
<div class="editor-field-info">
<div>
<b>{{ $t('word.size') }}</b>: {{ localContent.length | formatBytes }}<br>
<b>{{ $t('word.mimeType') }}</b>: {{ contentInfo.mime }}
</div>
<div><b>{{ $t('word.type') }}</b>: {{ type.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';
export default {
name: 'WorkspaceQueryTableCell',
components: {
ConfirmModal
},
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;
}
},
directives: {
mask
},
props: {
type: String,
field: String,
precision: [Number, null],
content: [String, Number, Object, Date, Uint8Array]
},
data () {
return {
isInlineEditor: false,
isTextareaEditor: false,
isBlobEditor: false,
willBeDeleted: false,
localContent: null,
contentInfo: {
ext: '',
mime: '',
size: null
},
fileToUpload: null
};
},
computed: {
inputProps () {
if ([...TEXT, ...LONG_TEXT].includes(this.type))
return { type: 'text', mask: false };
if (NUMBER.includes(this.type))
return { type: 'number', mask: false };
if (TIME.includes(this.type))
return { type: 'number', mask: false };
if (DATE.includes(this.type))
return { type: 'text', mask: '####-##-##' };
if (DATETIME.includes(this.type)) {
let datetimeMask = '####-##-## ##:##:##';
for (let i = 0; i < this.precision; i++)
datetimeMask += i === 0 ? '.#' : '#';
return { type: 'text', mask: datetimeMask };
}
if (BLOB.includes(this.type))
return { type: 'file', mask: false };
if (BIT.includes(this.type))
return { type: 'text', mask: false };
return { type: 'text', mask: false };
},
isImage () {
return ['gif', 'jpg', 'png', 'bmp', 'ico', 'tif'].includes(this.contentInfo.ext);
}
},
methods: {
isNull (value) {
return value === null ? ' is-null' : '';
},
bufferToBase64 (val) {
return bufferToBase64(val);
},
editON () {
if (LONG_TEXT.includes(this.type)) {
this.isTextareaEditor = true;
this.localContent = this.$options.filters.typeFormat(this.content, this.type);
return;
}
if (BLOB.includes(this.type)) {
this.isBlobEditor = true;
this.localContent = this.content ? this.content : '';
this.fileToUpload = null;
this.willBeDeleted = false;
if (this.content !== null) {
const buff = Buffer.from(this.localContent);
if (buff.length) {
const hex = buff.toString('hex').substring(0, 8).toUpperCase();
const { ext, mime } = mimeFromHex(hex);
this.contentInfo = {
ext,
mime,
size: this.localContent.length
};
}
}
return;
}
// Inline editable fields
this.localContent = this.$options.filters.typeFormat(this.content, this.type);
this.$nextTick(() => { // Focus on input
this.$refs.cell.blur();
this.$nextTick(() => this.$refs.editField.focus());
});
this.isInlineEditor = true;
},
editOFF () {
this.isInlineEditor = false;
let content;
if (!['blob', 'mediumblob', 'longblob'].includes(this.type)) {
if (this.localContent === this.$options.filters.typeFormat(this.content, this.type)) return;// If not changed
content = this.localContent;
}
else { // Handle file upload
if (this.willBeDeleted) {
content = '';
this.willBeDeleted = false;
}
else {
if (!this.fileToUpload) return;
content = this.fileToUpload.file.path;
}
}
this.$emit('updateField', {
field: this.field,
type: this.type,
content
});
},
hideEditorModal () {
this.isTextareaEditor = false;
this.isBlobEditor = false;
},
downloadFile () {
const downloadLink = document.createElement('a');
downloadLink.href = `data:${this.contentInfo.mime};base64, ${bufferToBase64(this.localContent)}`;
downloadLink.setAttribute('download', `${this.field}.${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.localContent = '';
this.contentInfo = {
ext: '',
mime: '',
size: null
};
this.willBeDeleted = true;
}
}
};
</script>
<style lang="scss">
.editable-field {
margin: 0;
border: none;
line-height: 1;
width: 100%;
}
.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>

View File

@@ -0,0 +1,69 @@
<template>
<BaseContextMenu
:context-event="contextEvent"
@closeContext="closeContext"
>
<div class="context-element" @click="showConfirmModal">
<i class="material-icons md-18 text-light pr-1">delete</i> {{ $tc('message.deleteRows', selectedRows.length) }}
</div>
<ConfirmModal
v-if="isConfirmModal"
@confirm="deleteRows"
@hide="hideConfirmModal"
>
<template :slot="'header'">
{{ $tc('message.deleteRows', selectedRows.length) }}
</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('closeContext');
},
deleteRows () {
this.$emit('deleteSelected');
this.closeContext();
}
}
};
</script>

View File

@@ -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>
</button>
<!-- <button class="btn btn-link btn-sm">
<span>{{ $t('word.save') }}</span>
<i class="material-icons ml-1">save</i>
</button> -->
<button
class="btn btn-link btn-sm"
:class="{'disabled':isQuering}"
@click="showAddModal"
>
<span>{{ $t('word.add') }}</span>
<i class="material-icons ml-1">playlist_add</i>
</button>
</div>
<div class="workspace-query-info">
<div v-if="results.rows">
@@ -29,23 +33,28 @@
<div class="workspace-query-results column col-12">
<WorkspaceQueryTable
v-if="results"
ref="queryTable"
:results="results"
:fields="resultsFields"
:fields="fields"
@updateField="updateField"
@deleteSelected="deleteSelected"
/>
</div>
</div>
</template>
<script>
import Structure from '@/ipc-api/Structure';
import Tables from '@/ipc-api/Tables';
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
import { mapGetters, mapActions } from 'vuex';
import tableTabs from '@/mixins/tableTabs';
export default {
name: 'WorkspaceTableTab',
components: {
WorkspaceQueryTable
},
mixins: [tableTabs],
props: {
connection: Object,
table: String
@@ -67,25 +76,16 @@ export default {
},
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
};
});
}
},
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;
@@ -111,9 +111,9 @@ export default {
};
try {
const { status, response } = await Structure.getTableColumns(params);
const { status, response } = await Tables.getTableColumns(params);
if (status === 'success')
this.fields = response.rows;
this.fields = response;
else
this.addNotification({ status: 'error', message: response });
}
@@ -122,7 +122,7 @@ export default {
}
try {
const { status, response } = await Structure.getTableData(params);
const { status, response } = await Tables.getTableData(params);
if (status === 'success')
this.results = response;
@@ -134,43 +134,46 @@ export default {
}
this.isQuering = false;
}
},
reloadTable () {
this.getTableData();
},
showAddModal () {}
}
};
</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>

View File

@@ -29,7 +29,13 @@ 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'
},
message: {
appWelcome: 'Welcome to Antares SQL Client!',
@@ -51,7 +57,13 @@ 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'
},
// Date and Time
short: {

View File

@@ -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: {

View File

@@ -9,4 +9,12 @@ export default class {
static getTableData (params) {
return ipcRenderer.invoke('getTableData', params);
}
static updateTableCell (params) {
return ipcRenderer.invoke('updateTableCell', params);
}
static deleteTableRows (params) {
return ipcRenderer.invoke('deleteTableRows', params);
}
}

View 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;
}
}
};

View File

@@ -1,44 +1,46 @@
@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,
"unknown": gray,
)
);
"int": cornflowerblue,
"tinyint": cornflowerblue,
"smallint": cornflowerblue,
"mediumint": cornflowerblue,
.is-null {
color: gray;
"datetime": coral,
"date": coral,
"time": coral,
"timestamp": coral,
"bit": lightskyblue,
"blob": darkorchid,
"mediumblob": darkorchid,
"longblob": darkorchid,
"unknown": gray,
));
.is-null{
color: gray;
&::after{
content: 'NULL';
}
&::after {
content: "NULL";
}
}

View File

@@ -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");
}
}

View File

@@ -1,65 +1,71 @@
.table {
border-collapse: collapse;
border-spacing: 0;
width: 100%;
display: table;
border-collapse: collapse;
border-spacing: 0;
width: 100%;
display: table;
table-layout: fixed;
&.table-striped {
.tbody {
.tr:nth-of-type(odd) {
background: $bg-color;
}
}
}
&,
&.table-striped {
.tbody {
.tr {
&.active {
background: $bg-color-dark;
}
}
}
}
&.table-hover {
.tbody {
.tr {
&:hover {
background: $bg-color-dark;
}
}
}
}
// Scollable tables
&.table-scroll {
display: block;
overflow-x: auto;
padding-bottom: .75rem;
white-space: nowrap;
}
.thead{
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;
}
}
}
}

View File

@@ -1,16 +1,38 @@
.material-icons {
// TODO: rewrite with rem
.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 sizing the icon. */
&.md-18 {
font-size: 18px;
}
/* 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); }
&.md-24 {
font-size: 24px;
}
/* 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); }
&.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);
}
}

View File

@@ -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);
}
}

View File

@@ -1,4 +1,4 @@
/*Colors*/
/* Colors */
$body-bg: #1d1d1d;
$body-font-color: #fff;
$bg-color: #1d1d1d;
@@ -7,8 +7,9 @@ $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;

View File

@@ -1,4 +1,3 @@
@import "~spectre.css/src/variables";
@import "variables";
@import "transitions";
@@ -9,142 +8,156 @@
@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;
background: $bg-color-light;
}
::-webkit-scrollbar-thumb {
background: rgba($color: #FFF, $alpha: .5);
::-webkit-scrollbar-thumb {
background: rgba($color: #fff, $alpha: 0.5);
&:hover {
background: rgba($color: #FFF, $alpha: 1);
}
&: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);
}
/* 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-container,
.modal-sm .modal-container {
box-shadow: 0 0 1px 0 #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;
}
}
.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;
}
.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-radio .form-icon {
border-color: $bg-color-light;
background: $bg-color-gray;
}
.form-select:not([multiple]):not([size]):focus{
border-color: $primary-color;
.form-select:not([multiple]):not([size]):focus {
border-color: $primary-color;
}
.menu{
font-size: .7rem;
.menu-item {
+ .menu-item{
margin-top: 0;
}
}
.input-group .input-group-addon {
border-color: #3f3f3f;
}
.menu {
font-size: 0.7rem;
.menu-item {
+ .menu-item {
margin-top: 0;
}
}
}
.accordion-body {
max-height: 500rem!important;
max-height: 500rem !important;
}
.btn.loading {
> .material-icons,
> span{
visibility: hidden;
}
> .material-icons,
> span {
visibility: hidden;
}
}

View File

@@ -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) {

View File

@@ -1,5 +1,5 @@
'use strict';
import { uidGen } from 'common/libs/utilities';
import { uidGen } from 'common/libs/uidGen';
export default {
namespaced: true,

View File

@@ -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);
}

View File

@@ -1,6 +1,6 @@
'use strict';
import Connection from '@/ipc-api/Connection';
import { uidGen } from 'common/libs/utilities';
import { uidGen } from 'common/libs/uidGen';
function remapStructure (structure) {
const databases = structure.map(table => table.TABLE_SCHEMA)

View File

@@ -17,7 +17,7 @@ module.exports = {
{
loader: 'sass-loader',
options: {
prependData: '@import "@/scss/_variables.scss";'
additionalData: '@import "@/scss/_variables.scss";'
}
}
]