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

Compare commits

..

36 Commits

Author SHA1 Message Date
020ce36312 chore(release): 0.4.2 2022-01-10 08:50:00 +01:00
b4545b178f feat(UI): textarea autofocus selecting a query tab, closes #166 2022-01-09 12:28:01 +01:00
d9a3eab015 perf(MySQL): support to ANSI_QUOTES sql_mode, closes #158 2022-01-05 18:23:31 +01:00
2ab49c218d Merge pull request #164 from toriphes/feat/keep-window-state
feat: keep window state
2021-12-28 19:30:51 +01:00
Giulio Ganci
8f9385d508 feat: save window state
open the main window in the last used position of the screen
2021-12-28 17:12:10 +01:00
0c002918eb feat(PostgreSQL): ability to cancel queries 2021-12-26 21:13:02 +01:00
3d56ec7b49 Merge pull request #157 from Fabio286/dependabot/npm_and_yarn/eslint-plugin-promise-6.0.0
build(deps-dev): bump eslint-plugin-promise from 5.2.0 to 6.0.0
2021-12-24 18:04:58 +01:00
48c3e6afc4 perf: hash for foreign key default names 2021-12-23 11:47:17 +01:00
dependabot[bot]
50a5c8fe1e build(deps-dev): bump eslint-plugin-promise from 5.2.0 to 6.0.0
Bumps [eslint-plugin-promise](https://github.com/xjamundx/eslint-plugin-promise) from 5.2.0 to 6.0.0.
- [Release notes](https://github.com/xjamundx/eslint-plugin-promise/releases)
- [Changelog](https://github.com/xjamundx/eslint-plugin-promise/blob/development/CHANGELOG.md)
- [Commits](https://github.com/xjamundx/eslint-plugin-promise/commits)

---
updated-dependencies:
- dependency-name: eslint-plugin-promise
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-20 19:03:06 +00:00
b0d1f115c9 ci: update actions/checkout to v2 2021-12-20 09:27:36 +01:00
a59f77f618 feat(MySQL): ability to cancel queries 2021-12-19 11:59:09 +01:00
e7a1858091 fix(SQLite): exception with some fields 2021-12-16 09:16:15 +01:00
648a51efe8 Merge pull request #154 from Fabio286/all-contributors/add-wenj91
docs: add wenj91 as a contributor for code
2021-12-14 09:49:24 +01:00
38962c4807 Merge pull request #153 from wenj91/master
[TypeError: Cannot read properties of undefined (reading 'type') #152] bugfix
2021-12-14 09:48:58 +01:00
allcontributors[bot]
43e4cdd4cf docs: update .all-contributorsrc [skip ci] 2021-12-14 08:48:00 +00:00
allcontributors[bot]
91046c1ac1 docs: update README.md [skip ci] 2021-12-14 08:47:59 +00:00
文杰
63f8b9b6a1 Merge branch 'Fabio286:master' into master 2021-12-14 09:40:42 +08:00
文杰
ed476d9b5c Merge pull request #1 from wenj91:152-bugfix
[TypeError: Cannot read properties of undefined (reading 'type') #152] bugfix
2021-12-14 09:40:02 +08:00
文杰
f41d8c0480 [TypeError: Cannot read properties of undefined (reading 'type') #152] bugfix 2021-12-14 01:37:59 +00:00
e3b54a8be1 Merge pull request #151 from datlechin/master
Update vi-VN translation
2021-12-13 08:08:30 +01:00
Ngo Quoc Dat
c2c0394624 Update vi-VN translation 2021-12-13 08:48:07 +07:00
8a6f5eac59 chore(release): 0.4.1 2021-12-11 10:28:29 +01:00
a5fdcc1a85 feat: language format detection for text fields 2021-12-10 23:30:03 +01:00
1df21da47c refactor: moved to new vue slots API 2021-12-10 17:34:44 +01:00
8da0224876 fix(MySQL): wrong datetime fields default in table filler in some cases 2021-12-09 18:26:59 +01:00
359e14a9eb fix(MySQL): wrong value for fields "on update" in some conditions 2021-12-09 12:22:38 +01:00
813aa320d9 perf(UI): avoid columns size change when editing cells or scrolling results 2021-12-08 11:19:10 +01:00
aaa5549609 fix: cell disappear on edit in one column tables 2021-12-08 10:37:23 +01:00
35cb7e1dc4 fix: select all rows with ctrl+a when editing a cell 2021-12-08 10:09:01 +01:00
992a033cb2 fix: false positive with Windows Defender 2021-12-01 09:23:40 +01:00
5267b37eaf chore: updated appx icons 2021-11-28 16:28:54 +01:00
8fe30d8b6d ci: run tests before build 2021-11-25 21:37:48 +01:00
37ccb6b00d test: basic e2e tests 2021-11-25 18:34:33 +01:00
e8af2d24a8 perf(UI): disable save button in table creation when no fields are added 2021-11-25 17:23:46 +01:00
d7f1aa97af fix(SQLite): update rows with a text primary key 2021-11-25 16:25:40 +01:00
c46224635a chore(release): 0.4.0 2021-11-24 17:27:51 +01:00
53 changed files with 603 additions and 205 deletions

View File

@@ -120,6 +120,15 @@
"contributions": [ "contributions": [
"code" "code"
] ]
},
{
"login": "wenj91",
"name": "文杰",
"avatar_url": "https://avatars.githubusercontent.com/u/12549338?v=4",
"profile": "https://github.com/wenj91",
"contributions": [
"code"
]
} }
], ],
"contributorsPerLine": 7, "contributorsPerLine": 7,

View File

@@ -12,13 +12,19 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v1 uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn - name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 14 node-version: 14
- name: Install dependencies
run: npm i
- name: Run tests
run: npm run test
- name: Build/release Electron app - name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1 uses: samuelmeuli/action-electron-builder@v1
with: with:

View File

@@ -12,13 +12,19 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v1 uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn - name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 14 node-version: 14
- name: Install dependencies
run: npm i
- name: Run tests
run: npm run test
- name: Build/release Electron app - name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1 uses: samuelmeuli/action-electron-builder@v1
with: with:

View File

@@ -12,13 +12,19 @@ jobs:
steps: steps:
- name: Check out Git repository - name: Check out Git repository
uses: actions/checkout@v1 uses: actions/checkout@v2
- name: Install Node.js, NPM and Yarn - name: Install Node.js, NPM and Yarn
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
node-version: 14 node-version: 14
- name: Install dependencies
run: npm i
- name: Run tests
run: npm run test
- name: Build/release Electron app - name: Build/release Electron app
uses: samuelmeuli/action-electron-builder@v1 uses: samuelmeuli/action-electron-builder@v1
with: with:

View File

@@ -2,7 +2,51 @@
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. 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.3.10](https://github.com/Fabio286/antares/compare/v0.3.9...v0.3.10) (2021-11-24) ### [0.4.2](https://github.com/Fabio286/antares/compare/v0.4.1...v0.4.2) (2022-01-10)
### Features
* **MySQL:** ability to cancel queries ([a59f77f](https://github.com/Fabio286/antares/commit/a59f77f618aea6156fc80fb832d3efcb9848411f))
* **PostgreSQL:** ability to cancel queries ([0c00291](https://github.com/Fabio286/antares/commit/0c002918eb0226f6b3f21ed62117495f86396fb1))
* save window state ([8f9385d](https://github.com/Fabio286/antares/commit/8f9385d50815635d091758ecd5d00884e3297ca0))
* **UI:** textarea autofocus selecting a query tab, closes [#166](https://github.com/Fabio286/antares/issues/166) ([b4545b1](https://github.com/Fabio286/antares/commit/b4545b178f795712c781a3f4fc35eec31b5ad902))
### Bug Fixes
* **SQLite:** exception with some fields ([e7a1858](https://github.com/Fabio286/antares/commit/e7a18580915e7739bfa97948c6a0c4fc90a7e78a))
### Improvements
* hash for foreign key default names ([48c3e6a](https://github.com/Fabio286/antares/commit/48c3e6afc43c51f70a16703f1a71194f43da7a3e))
* **MySQL:** support to ANSI_QUOTES sql_mode, closes [#158](https://github.com/Fabio286/antares/issues/158) ([d9a3eab](https://github.com/Fabio286/antares/commit/d9a3eab015302e9f23112f659658073ab3242191))
### [0.4.1](https://github.com/Fabio286/antares/compare/v0.4.0...v0.4.1) (2021-12-11)
### Features
* language format detection for text fields ([a5fdcc1](https://github.com/Fabio286/antares/commit/a5fdcc1a85aa188ff1b9a15b1a768aced026f360))
### Bug Fixes
* cell disappear on edit in one column tables ([aaa5549](https://github.com/Fabio286/antares/commit/aaa5549609664665bd4513632d621cb249b379c1))
* false positive with Windows Defender ([992a033](https://github.com/Fabio286/antares/commit/992a033cb2bede3d1eb52e19482d810f6692de1e))
* **MySQL:** wrong datetime fields default in table filler in some cases ([8da0224](https://github.com/Fabio286/antares/commit/8da022487650039b7f34a9c86a7bd9045eba65e2))
* **MySQL:** wrong value for fields "on update" in some conditions ([359e14a](https://github.com/Fabio286/antares/commit/359e14a9ebd48f86069ba7762fe00a7056f52d47))
* select all rows with ctrl+a when editing a cell ([35cb7e1](https://github.com/Fabio286/antares/commit/35cb7e1dc48d3a74e9d106cb1a37f454c1b4a4d1))
* **SQLite:** update rows with a text primary key ([d7f1aa9](https://github.com/Fabio286/antares/commit/d7f1aa97af32a4c51fc7022498bd47e15fa08430))
### Improvements
* **UI:** avoid columns size change when editing cells or scrolling results ([813aa32](https://github.com/Fabio286/antares/commit/813aa320d9ab799efea38a7110b7c0bdf7549123))
* **UI:** disable save button in table creation when no fields are added ([e8af2d2](https://github.com/Fabio286/antares/commit/e8af2d24a869f7667c069936648808952d2062ab))
## [0.4.0](https://github.com/Fabio286/antares/compare/v0.3.9...v0.4.0) (2021-11-24)
### Features ### Features

View File

@@ -130,6 +130,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://github.com/IsamuSugi"><img src="https://avatars.githubusercontent.com/u/7746658?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Isamu Sugiura</b></sub></a><br /><a href="#translation-IsamuSugi" title="Translation">🌍</a></td> <td align="center"><a href="https://github.com/IsamuSugi"><img src="https://avatars.githubusercontent.com/u/7746658?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Isamu Sugiura</b></sub></a><br /><a href="#translation-IsamuSugi" title="Translation">🌍</a></td>
<td align="center"><a href="http://rsacchetto.nexxontech.it/"><img src="https://avatars.githubusercontent.com/u/18429412?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Riccardo Sacchetto</b></sub></a><br /><a href="#platform-Occhioverde" title="Packaging/porting to new platform">📦</a></td> <td align="center"><a href="http://rsacchetto.nexxontech.it/"><img src="https://avatars.githubusercontent.com/u/18429412?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Riccardo Sacchetto</b></sub></a><br /><a href="#platform-Occhioverde" title="Packaging/porting to new platform">📦</a></td>
<td align="center"><a href="https://kilianstallinger.com"><img src="https://avatars.githubusercontent.com/u/5290318?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kilian Stallinger</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=kilianstallz" title="Code">💻</a></td> <td align="center"><a href="https://kilianstallinger.com"><img src="https://avatars.githubusercontent.com/u/5290318?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Kilian Stallinger</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=kilianstallz" title="Code">💻</a></td>
<td align="center"><a href="https://github.com/wenj91"><img src="https://avatars.githubusercontent.com/u/12549338?v=4?s=100" width="100px;" alt=""/><br /><sub><b>文杰</b></sub></a><br /><a href="https://github.com/Fabio286/antares/commits?author=wenj91" title="Code">💻</a></td>
</tr> </tr>
</table> </table>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -1,7 +1,7 @@
{ {
"name": "antares", "name": "antares",
"productName": "Antares", "productName": "Antares",
"version": "0.3.10", "version": "0.4.2",
"description": "A modern, fast and productivity driven SQL client with a focus in UX.", "description": "A modern, fast and productivity driven SQL client with a focus in UX.",
"license": "MIT", "license": "MIT",
"repository": "https://github.com/Fabio286/antares.git", "repository": "https://github.com/Fabio286/antares.git",
@@ -18,7 +18,7 @@
"release": "standard-version", "release": "standard-version",
"release:pre": "npm run release -- --prerelease alpha", "release:pre": "npm run release -- --prerelease alpha",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"test": "npm run lint", "test": "npm run compile && node tests/app.spec.js",
"lint": "eslint . --ext .js,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"", "lint": "eslint . --ext .js,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
"lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix", "lint:fix": "eslint . --ext .js,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
"contributors:add": "all-contributors add", "contributors:add": "all-contributors add",
@@ -82,6 +82,7 @@
"appx": { "appx": {
"displayName": "Antares SQL", "displayName": "Antares SQL",
"backgroundColor": "transparent", "backgroundColor": "transparent",
"showNameOnTiles": true,
"identityName": "62514FabioDiStasio.AntaresSQLClient", "identityName": "62514FabioDiStasio.AntaresSQLClient",
"publisher": "CN=1A2729ED-865C-41D2-9038-39AE2A63AA52", "publisher": "CN=1A2729ED-865C-41D2-9038-39AE2A63AA52",
"applicationId": "FabioDiStasio.AntaresSQLClient" "applicationId": "FabioDiStasio.AntaresSQLClient"
@@ -104,11 +105,13 @@
"dependencies": { "dependencies": {
"@electron/remote": "^2.0.1", "@electron/remote": "^2.0.1",
"@mdi/font": "^6.1.95", "@mdi/font": "^6.1.95",
"@vscode/vscode-languagedetection": "^1.0.21",
"ace-builds": "^1.4.13", "ace-builds": "^1.4.13",
"better-sqlite3": "^7.4.4", "better-sqlite3": "^7.4.4",
"electron-log": "^4.4.1", "electron-log": "^4.4.1",
"electron-store": "^8.0.1", "electron-store": "^8.0.1",
"electron-updater": "^4.3.9", "electron-updater": "^4.3.9",
"electron-window-state": "^5.0.3",
"faker": "^5.5.3", "faker": "^5.5.3",
"marked": "^4.0.0", "marked": "^4.0.0",
"moment": "^2.29.1", "moment": "^2.29.1",
@@ -134,18 +137,19 @@
"cross-env": "^7.0.2", "cross-env": "^7.0.2",
"css-loader": "^6.5.0", "css-loader": "^6.5.0",
"electron": "^16.0.1", "electron": "^16.0.1",
"electron-builder": "^22.13.1", "electron-builder": "^22.14.11",
"electron-devtools-installer": "^3.2.0", "electron-devtools-installer": "^3.2.0",
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-standard": "^16.0.3", "eslint-config-standard": "^16.0.3",
"eslint-plugin-import": "^2.24.2", "eslint-plugin-import": "^2.24.2",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0", "eslint-plugin-promise": "^6.0.0",
"eslint-plugin-vue": "^8.0.3", "eslint-plugin-vue": "^8.0.3",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.0", "html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^2.4.3", "mini-css-extract-plugin": "^2.4.3",
"node-loader": "^2.0.0", "node-loader": "^2.0.0",
"playwright": "^1.16.3",
"progress-webpack-plugin": "^1.0.12", "progress-webpack-plugin": "^1.0.12",
"sass": "^1.42.1", "sass": "^1.42.1",
"sass-loader": "^12.3.0", "sass-loader": "^12.3.0",

View File

@@ -11,6 +11,7 @@ module.exports = {
sslConnection: false, sslConnection: false,
sshConnection: false, sshConnection: false,
fileConnection: false, fileConnection: false,
cancelQueries: false,
// Tools // Tools
processesList: false, processesList: false,
usersManagement: false, usersManagement: false,

View File

@@ -12,6 +12,7 @@ module.exports = {
engines: true, engines: true,
sslConnection: true, sslConnection: true,
sshConnection: true, sshConnection: true,
cancelQueries: true,
// Tools // Tools
processesList: true, processesList: true,
// Structure // Structure

View File

@@ -10,6 +10,7 @@ module.exports = {
database: true, database: true,
sslConnection: true, sslConnection: true,
sshConnection: true, sshConnection: true,
cancelQueries: true,
// Tools // Tools
processesList: true, processesList: true,
// Structure // Structure

View File

@@ -7,8 +7,8 @@ module.exports = {
views: true, views: true,
triggers: true, triggers: true,
// Settings // Settings
elementsWrapper: '', elementsWrapper: '"',
stringsWrapper: '"', stringsWrapper: '\'',
tableAdd: true, tableAdd: true,
viewAdd: true, viewAdd: true,
triggerAdd: true, triggerAdd: true,

View File

@@ -52,7 +52,7 @@ export default connections => {
return { status: 'success' }; return { status: 'success' };
} }
catch (err) { catch (err) {
return { status: 'error', response: err }; return { status: 'error', response: err.toString() };
} }
}); });

View File

@@ -135,7 +135,7 @@ export default connections => {
} }
}); });
ipcMain.handle('raw-query', async (event, { uid, query, schema }) => { ipcMain.handle('raw-query', async (event, { uid, query, schema, tabUid }) => {
if (!query) return; if (!query) return;
try { try {
@@ -143,6 +143,7 @@ export default connections => {
nest: true, nest: true,
details: true, details: true,
schema, schema,
tabUid,
comments: false comments: false
}); });
@@ -152,4 +153,16 @@ export default connections => {
return { status: 'error', response: err.toString() }; return { status: 'error', response: err.toString() };
} }
}); });
ipcMain.handle('kill-tab-query', async (event, { uid, tabUid }) => {
if (!tabUid) return;
try {
await connections[uid].killTabQuery(tabUid);
return { status: 'success' };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
}; };

View File

@@ -3,6 +3,7 @@ import faker from 'faker';
import moment from 'moment'; import moment from 'moment';
import { sqlEscaper } from 'common/libs/sqlEscaper'; import { sqlEscaper } from 'common/libs/sqlEscaper';
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes'; import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes';
import * as customizations from 'common/customizations';
import fs from 'fs'; import fs from 'fs';
export default (connections) => { export default (connections) => {
@@ -85,11 +86,12 @@ export default (connections) => {
ipcMain.handle('update-table-cell', async (event, params) => { ipcMain.handle('update-table-cell', async (event, params) => {
delete params.row._antares_id; delete params.row._antares_id;
const { stringsWrapper: sw } = customizations[connections[params.uid]._client];
try { // TODO: move to client classes try { // TODO: move to client classes
let escapedParam; let escapedParam;
let reload = false; let reload = false;
const id = typeof params.id === 'number' ? params.id : `"${params.id}"`; const id = typeof params.id === 'number' ? params.id : `${sw}${params.id}${sw}`;
if ([...NUMBER, ...FLOAT].includes(params.type)) if ([...NUMBER, ...FLOAT].includes(params.type))
escapedParam = params.content; escapedParam = params.content;

View File

@@ -9,6 +9,7 @@ export class MySQLClient extends AntaresCore {
super(args); super(args);
this._schema = null; this._schema = null;
this._runningConnections = new Map();
this.types = { this.types = {
0: 'DECIMAL', 0: 'DECIMAL',
@@ -136,8 +137,16 @@ export class MySQLClient extends AntaresCore {
if (!this._poolSize) { if (!this._poolSize) {
this._connection = await mysql.createConnection(dbConfig); this._connection = await mysql.createConnection(dbConfig);
// ANSI_QUOTES check
const res = await this.getVariable('sql_mode', 'global');
const sqlMode = res?.value.split(',');
const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES');
if (this._params.readonly) if (this._params.readonly)
await this.raw('SET SESSION TRANSACTION READ ONLY'); await this.raw('SET SESSION TRANSACTION READ ONLY');
if (hasAnsiQuotes)
await this.raw(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`);
} }
else { else {
this._connection = mysql.createPool({ this._connection = mysql.createPool({
@@ -151,13 +160,23 @@ export class MySQLClient extends AntaresCore {
} }
}); });
if (this._params.readonly) { // ANSI_QUOTES check
const res = await this.getVariable('sql_mode', 'global');
const sqlMode = res?.value.split(',');
const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES');
if (hasAnsiQuotes)
await this._connection.query(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`);
this._connection.on('connection', connection => { this._connection.on('connection', connection => {
if (this._params.readonly)
connection.query('SET SESSION TRANSACTION READ ONLY'); connection.query('SET SESSION TRANSACTION READ ONLY');
if (hasAnsiQuotes)
connection.query(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`);
}); });
} }
} }
}
/** /**
* @memberof MySQLClient * @memberof MySQLClient
@@ -424,7 +443,7 @@ export class MySQLClient extends AntaresCore {
return { return {
name: field.COLUMN_NAME, name: field.COLUMN_NAME,
key: field.COLUMN_KEY.toLowerCase(), key: field.COLUMN_KEY.toLowerCase(),
type: remappedFields ? remappedFields[field.COLUMN_NAME].type : field.DATA_TYPE, type: (remappedFields && remappedFields[field.COLUMN_NAME]) ? remappedFields[field.COLUMN_NAME].type : field.DATA_TYPE,
schema: field.TABLE_SCHEMA, schema: field.TABLE_SCHEMA,
table: field.TABLE_NAME, table: field.TABLE_NAME,
numPrecision: field.NUMERIC_PRECISION, numPrecision: field.NUMERIC_PRECISION,
@@ -436,11 +455,13 @@ export class MySQLClient extends AntaresCore {
unsigned: field.COLUMN_TYPE.includes('unsigned'), unsigned: field.COLUMN_TYPE.includes('unsigned'),
zerofill: field.COLUMN_TYPE.includes('zerofill'), zerofill: field.COLUMN_TYPE.includes('zerofill'),
order: field.ORDINAL_POSITION, order: field.ORDINAL_POSITION,
default: remappedFields ? remappedFields[field.COLUMN_NAME].default : field.COLUMN_DEFAULT, default: (remappedFields && remappedFields[field.COLUMN_NAME]) ? remappedFields[field.COLUMN_NAME].default : field.COLUMN_DEFAULT,
charset: field.CHARACTER_SET_NAME, charset: field.CHARACTER_SET_NAME,
collation: field.COLLATION_NAME, collation: field.COLLATION_NAME,
autoIncrement: field.EXTRA.includes('auto_increment'), autoIncrement: field.EXTRA.includes('auto_increment'),
onUpdate: field.EXTRA.toLowerCase().includes('on update') ? field.EXTRA.replace('on update', '') : '', onUpdate: field.EXTRA.toLowerCase().includes('on update')
? field.EXTRA.substr(field.EXTRA.indexOf('on update') + 9, field.EXTRA.length).trim()
: '',
comment: field.COLUMN_COMMENT comment: field.COLUMN_COMMENT
}; };
}); });
@@ -1137,6 +1158,26 @@ export class MySQLClient extends AntaresCore {
}); });
} }
/**
* SHOW VARIABLES LIKE %variable%
*
* @param {String} variable
* @param {'global'|'session'|null} level
* @returns {Object} variable
* @memberof MySQLClient
*/
async getVariable (variable, level) {
const sql = `SHOW${level ? ' ' + level.toUpperCase() : ''} VARIABLES LIKE '%${variable}%'`;
const results = await this.raw(sql);
if (results.rows.length) {
return {
name: results.rows[0].Variable_name,
value: results.rows[0].Value
};
}
}
/** /**
* SHOW ENGINES * SHOW ENGINES
* *
@@ -1208,10 +1249,26 @@ export class MySQLClient extends AntaresCore {
}); });
} }
/**
*
* @param {number} id
* @returns {Promise<null>}
*/
async killProcess (id) { async killProcess (id) {
return await this.raw(`KILL ${id}`); return await this.raw(`KILL ${id}`);
} }
/**
*
* @param {string} tabUid
* @returns {Promise<null>}
*/
async killTabQuery (tabUid) {
const id = this._runningConnections.get(tabUid);
if (id)
return await this.killProcess(id);
}
/** /**
* CREATE TABLE * CREATE TABLE
* *
@@ -1533,6 +1590,9 @@ export class MySQLClient extends AntaresCore {
const isPool = typeof this._connection.getConnection === 'function'; const isPool = typeof this._connection.getConnection === 'function';
const connection = isPool ? await this._connection.getConnection() : this._connection; const connection = isPool ? await this._connection.getConnection() : this._connection;
if (args.tabUid && isPool)
this._runningConnections.set(args.tabUid, connection.connection.connectionId);
if (args.schema) if (args.schema)
await connection.query(`USE \`${args.schema}\``); await connection.query(`USE \`${args.schema}\``);
@@ -1593,7 +1653,10 @@ export class MySQLClient extends AntaresCore {
}); });
} }
catch (err) { catch (err) {
if (isPool) connection.release(); if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
reject(err); reject(err);
} }
@@ -1602,7 +1665,10 @@ export class MySQLClient extends AntaresCore {
keysArr = keysArr ? [...keysArr, ...response] : response; keysArr = keysArr ? [...keysArr, ...response] : response;
} }
catch (err) { catch (err) {
if (isPool) connection.release(); if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
reject(err); reject(err);
} }
} }
@@ -1617,7 +1683,10 @@ export class MySQLClient extends AntaresCore {
keys: keysArr keys: keysArr
}); });
}).catch((err) => { }).catch((err) => {
if (isPool) connection.release(); if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
reject(err); reject(err);
}); });
}); });
@@ -1625,7 +1694,10 @@ export class MySQLClient extends AntaresCore {
resultsArr.push({ rows, report, fields, keys, duration }); resultsArr.push({ rows, report, fields, keys, duration });
} }
if (isPool) connection.release(); if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
return resultsArr.length === 1 ? resultsArr[0] : resultsArr; return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
} }

View File

@@ -21,6 +21,7 @@ export class PostgreSQLClient extends AntaresCore {
super(args); super(args);
this._schema = null; this._schema = null;
this._runningConnections = new Map();
this.types = {}; this.types = {};
for (const key in types.builtins) for (const key in types.builtins)
@@ -1088,10 +1089,26 @@ export class PostgreSQLClient extends AntaresCore {
}); });
} }
/**
*
* @param {number} id
* @returns {Promise<null>}
*/
async killProcess (id) { async killProcess (id) {
return await this.raw(`SELECT pg_terminate_backend(${id})`); return await this.raw(`SELECT pg_terminate_backend(${id})`);
} }
/**
*
* @param {string} tabUid
* @returns {Promise<null>}
*/
async killTabQuery (tabUid) {
const id = this._runningConnections.get(tabUid);
if (id)
return await this.raw(`SELECT pg_cancel_backend(${id})`);
}
/** /**
* CREATE TABLE * CREATE TABLE
* *
@@ -1396,6 +1413,8 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async raw (sql, args) { async raw (sql, args) {
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
args = { args = {
nest: false, nest: false,
details: false, details: false,
@@ -1404,9 +1423,6 @@ export class PostgreSQLClient extends AntaresCore {
...args ...args
}; };
if (args.schema && args.schema !== 'public')
await this.use(args.schema);
if (!args.comments) if (!args.comments)
sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments
@@ -1418,7 +1434,14 @@ export class PostgreSQLClient extends AntaresCore {
.map(q => q.trim()) .map(q => q.trim())
: [sql]; : [sql];
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder const isPool = this._connection instanceof Pool;
const connection = isPool ? await this._connection.connect() : this._connection;
if (args.tabUid && isPool)
this._runningConnections.set(args.tabUid, connection.processID);
if (args.schema && args.schema !== 'public')
await this.use(args.schema);
for (const query of queries) { for (const query of queries) {
if (!query) continue; if (!query) continue;
@@ -1428,15 +1451,12 @@ export class PostgreSQLClient extends AntaresCore {
let keysArr = []; let keysArr = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => { const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => {
this._connection.query({ (async () => {
rowMode: args.nest ? 'array' : null, try {
text: query const res = await connection.query({ rowMode: args.nest ? 'array' : null, text: query });
}, async (err, res) => {
timeStop = new Date(); timeStop = new Date();
if (err)
reject(err);
else {
let ast; let ast;
try { try {
@@ -1525,6 +1545,10 @@ export class PostgreSQLClient extends AntaresCore {
}); });
} }
catch (err) { catch (err) {
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
reject(err); reject(err);
} }
@@ -1533,6 +1557,10 @@ export class PostgreSQLClient extends AntaresCore {
keysArr = keysArr ? [...keysArr, ...response] : response; keysArr = keysArr ? [...keysArr, ...response] : response;
} }
catch (err) { catch (err) {
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
reject(err); reject(err);
} }
} }
@@ -1547,12 +1575,24 @@ export class PostgreSQLClient extends AntaresCore {
keys: keysArr keys: keysArr
}); });
} }
}); catch (err) {
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
reject(err);
}
})();
}); });
resultsArr.push({ rows, report, fields, keys, duration }); resultsArr.push({ rows, report, fields, keys, duration });
} }
if (isPool) {
connection.release();
this._runningConnections.delete(args.tabUid);
}
return resultsArr.length === 1 ? resultsArr[0] : resultsArr; return resultsArr.length === 1 ? resultsArr[0] : resultsArr;
} }
} }

View File

@@ -1,7 +1,7 @@
'use strict'; 'use strict';
import sqlite from 'better-sqlite3'; import sqlite from 'better-sqlite3';
import { AntaresCore } from '../AntaresCore'; import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/mysql'; import dataTypes from 'common/data-types/sqlite';
import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes'; import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes';
export class SQLiteClient extends AntaresCore { export class SQLiteClient extends AntaresCore {
@@ -732,7 +732,7 @@ export class SQLiteClient extends AntaresCore {
if ([...TIME, ...DATETIME].includes(parsedType)) { if ([...TIME, ...DATETIME].includes(parsedType)) {
const firstNotNull = queryResult.find(res => res[field.name] !== null); const firstNotNull = queryResult.find(res => res[field.name] !== null);
if (firstNotNull[field.name].includes('.')) if (firstNotNull && firstNotNull[field.name].includes('.'))
length = firstNotNull[field.name].split('.').pop().length; length = firstNotNull[field.name].split('.').pop().length;
} }

View File

@@ -3,6 +3,7 @@
import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron'; import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron';
import * as path from 'path'; import * as path from 'path';
import Store from 'electron-store'; import Store from 'electron-store';
import * as windowStateKeeper from 'electron-window-state';
import * as remoteMain from '@electron/remote/main'; import * as remoteMain from '@electron/remote/main';
import ipcHandlers from './ipc-handlers'; import ipcHandlers from './ipc-handlers';
@@ -18,12 +19,15 @@ process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
// global reference to mainWindow (necessary to prevent window from being garbage collected) // global reference to mainWindow (necessary to prevent window from being garbage collected)
let mainWindow; let mainWindow;
let mainWindowState;
async function createMainWindow () { async function createMainWindow () {
const icon = require('../renderer/images/logo-32.png'); const icon = require('../renderer/images/logo-32.png');
const window = new BrowserWindow({ const window = new BrowserWindow({
width: 1024, width: mainWindowState.width,
height: 800, height: mainWindowState.height,
x: mainWindowState.x,
y: mainWindowState.y,
minWidth: 900, minWidth: 900,
minHeight: 550, minHeight: 550,
title: 'Antares SQL', title: 'Antares SQL',
@@ -41,6 +45,9 @@ async function createMainWindow () {
backgroundColor: '#1d1d1d' backgroundColor: '#1d1d1d'
}); });
mainWindowState.manage(window);
window.on('moved', saveWindowState);
remoteMain.enable(window.webContents); remoteMain.enable(window.webContents);
try { try {
@@ -70,6 +77,7 @@ async function createMainWindow () {
} }
window.on('closed', () => { window.on('closed', () => {
window.removeListener('moved', saveWindowState);
mainWindow = null; mainWindow = null;
}); });
@@ -104,6 +112,11 @@ else {
// create main BrowserWindow when electron is ready // create main BrowserWindow when electron is ready
app.on('ready', async () => { app.on('ready', async () => {
mainWindowState = windowStateKeeper({
defaultWidth: 1024,
defaultHeight: 800
});
mainWindow = await createMainWindow(); mainWindow = await createMainWindow();
createAppMenu(); createAppMenu();
@@ -160,3 +173,7 @@ function createAppMenu () {
Menu.setApplicationMenu(menu); Menu.setApplicationMenu(menu);
} }
function saveWindowState () {
mainWindowState.saveState(mainWindow);
}

View File

@@ -6,13 +6,13 @@
@confirm="runRoutine" @confirm="runRoutine"
@hide="closeModal" @hide="closeModal"
> >
<template slot="header"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-play mr-1" /> <i class="mdi mdi-24px mdi-play mr-1" />
<span class="cut-text">{{ $t('word.parameters') }}: {{ localRoutine.name }}</span> <span class="cut-text">{{ $t('word.parameters') }}: {{ localRoutine.name }}</span>
</div> </div>
</template> </template>
<div slot="body"> <template #body>
<div class="content"> <div class="content">
<form class="form-horizontal"> <form class="form-horizontal">
<div <div
@@ -43,7 +43,7 @@
</div> </div>
</form> </form>
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
</template> </template>

View File

@@ -5,16 +5,16 @@
@confirm="$emit('confirm')" @confirm="$emit('confirm')"
@hide="$emit('close')" @hide="$emit('close')"
> >
<template slot="header"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-content-save-alert mr-1" /> {{ $t('message.unsavedChanges') }} <i class="mdi mdi-24px mdi-content-save-alert mr-1" /> {{ $t('message.unsavedChanges') }}
</div> </div>
</template> </template>
<div slot="body"> <template #body>
<div> <div>
{{ $t('message.discardUnsavedChanges') }} {{ $t('message.discardUnsavedChanges') }}
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
</template> </template>

View File

@@ -41,7 +41,7 @@
<label class="form-checkbox ml-3" :title="$t('word.insert')"> <label class="form-checkbox ml-3" :title="$t('word.insert')">
<input <input
type="checkbox" type="checkbox"
:checked="!field.autoIncrement" :checked="!fieldsToExclude.includes(field.name)"
@change.prevent="toggleFields($event, field)" @change.prevent="toggleFields($event, field)"
><i class="form-icon" /> ><i class="form-icon" />
</label> </label>
@@ -264,7 +264,7 @@ export default {
else if (BIT.includes(field.type)) else if (BIT.includes(field.type))
fieldDefault = field.default.replaceAll('\'', '').replaceAll('b', ''); fieldDefault = field.default.replaceAll('\'', '').replaceAll('b', '');
else if (DATETIME.includes(field.type)) { else if (DATETIME.includes(field.type)) {
if (field.default && ['current_timestamp', 'now()'].includes(field.default.toLowerCase())) { if (field.default && ['current_timestamp', 'now()'].some(term => field.default.toLowerCase().includes(term))) {
let datePrecision = ''; let datePrecision = '';
for (let i = 0; i < field.datePrecision; i++) for (let i = 0; i < field.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S'; datePrecision += i === 0 ? '.S' : 'S';
@@ -281,7 +281,7 @@ export default {
rowObj[field.name] = { value: fieldDefault }; rowObj[field.name] = { value: fieldDefault };
if (field.autoIncrement)// Disable by default auto increment fields if (field.autoIncrement || !!field.onUpdate)// Disable by default auto increment or "on update" fields
this.fieldsToExclude = [...this.fieldsToExclude, field.name]; this.fieldsToExclude = [...this.fieldsToExclude, field.name];
} }

View File

@@ -22,12 +22,12 @@
:hide-footer="true" :hide-footer="true"
@hide="hideInfoModal" @hide="hideInfoModal"
> >
<template :slot="'header'"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-information-outline mr-1" /> {{ $t('message.processInfo') }} <i class="mdi mdi-24px mdi-information-outline mr-1" /> {{ $t('message.processInfo') }}
</div> </div>
</template> </template>
<div :slot="'body'"> <template #body>
<div> <div>
<div> <div>
<TextEditor <TextEditor
@@ -38,7 +38,7 @@
/> />
</div> </div>
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
</div> </div>
</template> </template>

View File

@@ -15,16 +15,16 @@
@confirm="confirmDeleteConnection" @confirm="confirmDeleteConnection"
@hide="hideConfirmModal" @hide="hideConfirmModal"
> >
<template :slot="'header'"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-server-remove mr-1" /> {{ $t('message.deleteConnection') }} <i class="mdi mdi-24px mdi-server-remove mr-1" /> {{ $t('message.deleteConnection') }}
</div> </div>
</template> </template>
<div :slot="'body'"> <template #body>
<div class="mb-2"> <div class="mb-2">
{{ $t('message.deleteCorfirm') }} <b>{{ connectionName }}</b>? {{ $t('message.deleteCorfirm') }} <b>{{ connectionName }}</b>?
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
</BaseContextMenu> </BaseContextMenu>
</template> </template>

View File

@@ -6,12 +6,12 @@
:hide-footer="true" :hide-footer="true"
@hide="hideScratchpad" @hide="hideScratchpad"
> >
<template :slot="'header'"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-notebook-edit-outline mr-1" /> {{ $t('word.scratchpad') }} <i class="mdi mdi-24px mdi-notebook-edit-outline mr-1" /> {{ $t('word.scratchpad') }}
</div> </div>
</template> </template>
<div :slot="'body'"> <template #body>
<div> <div>
<div> <div>
<TextEditor <TextEditor
@@ -24,7 +24,7 @@
</div> </div>
<small class="text-gray">{{ $t('message.markdownSupported') }}</small> <small class="text-gray">{{ $t('message.markdownSupported') }}</small>
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
</template> </template>

View File

@@ -200,7 +200,7 @@ export default {
} }
&::before { &::before {
content: ''; content: "";
height: 0; height: 0;
width: 3px; width: 3px;
transition: height 0.2s; transition: height 0.2s;

View File

@@ -18,7 +18,6 @@
<li <li
v-for="(tab, i) of draggableTabs" v-for="(tab, i) of draggableTabs"
:key="i" :key="i"
:ref="selectedTab === tab.uid ? 'tab-selected' : ''"
class="tab-item tab-draggable" class="tab-item tab-draggable"
draggable="true" draggable="true"
:class="{'active': selectedTab === tab.uid}" :class="{'active': selectedTab === tab.uid}"
@@ -256,9 +255,9 @@
</span> </span>
</a> </a>
</li> </li>
<template #header>
<li <li
v-if="workspace.customizations.processesList" v-if="workspace.customizations.processesList"
slot="header"
class="tab-item dropdown tools-dropdown" class="tab-item dropdown tools-dropdown"
> >
<a <a
@@ -297,7 +296,9 @@
</li> </li>
</ul> </ul>
</li> </li>
<li slot="footer" class="tab-item"> </template>
<template #footer>
<li class="tab-item">
<a <a
class="tab-add" class="tab-add"
:title="$t('message.openNewTab')" :title="$t('message.openNewTab')"
@@ -306,6 +307,7 @@
<i class="mdi mdi-24px mdi-plus" /> <i class="mdi mdi-24px mdi-plus" />
</a> </a>
</li> </li>
</template>
</Draggable> </Draggable>
<WorkspaceEmptyState v-if="!workspace.tabs.length" @new-tab="addQueryTab" /> <WorkspaceEmptyState v-if="!workspace.tabs.length" @new-tab="addQueryTab" />
<template v-for="tab of workspace.tabs"> <template v-for="tab of workspace.tabs">
@@ -565,7 +567,7 @@ export default {
return this.workspace ? this.workspace.selectedTab : null; return this.workspace ? this.workspace.selectedTab : null;
}, },
queryTabs () { queryTabs () {
return this.workspace.tabs.filter(tab => tab.type === 'query'); return this.workspace ? this.workspace.tabs.filter(tab => tab.type === 'query') : [];
}, },
schemaChild () { schemaChild () {
for (const key in this.workspace.breadcrumbs) { for (const key in this.workspace.breadcrumbs) {
@@ -581,16 +583,12 @@ export default {
} }
}, },
watch: { watch: {
selectedTab (newVal, oldVal) { queryTabs: function (newVal, oldVal) {
if (newVal !== oldVal) { if (newVal.length > oldVal.length) {
setTimeout(() => { setTimeout(() => {
const element = this.$refs['tab-selected'] ? this.$refs['tab-selected'][0] : null; const scroller = this.$refs.tabWrap;
if (element) { if (scroller) scroller.$el.scrollLeft = scroller.$el.scrollWidth;
element.setAttribute('tabindex', '-1'); }, 0);
element.focus();
element.removeAttribute('tabindex');
}
}, 50);
} }
} }
}, },

View File

@@ -42,17 +42,17 @@
@confirm="deleteMisc" @confirm="deleteMisc"
@hide="hideDeleteModal" @hide="hideDeleteModal"
> >
<template slot="header"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-delete mr-1" /> <i class="mdi mdi-24px mdi-delete mr-1" />
<span class="cut-text">{{ deleteMessage }}</span> <span class="cut-text">{{ deleteMessage }}</span>
</div> </div>
</template> </template>
<div slot="body"> <template #body>
<div class="mb-2"> <div class="mb-2">
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedMisc.name }}</b>"? {{ $t('message.deleteCorfirm') }} "<b>{{ selectedMisc.name }}</b>"?
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
<ModalAskParameters <ModalAskParameters
v-if="isAskingParameters" v-if="isAskingParameters"

View File

@@ -452,7 +452,7 @@ export default {
top: 0; top: 0;
z-index: 2; z-index: 2;
.schema-size{ .schema-size {
visibility: hidden; visibility: hidden;
width: 22.5px; width: 22.5px;
} }
@@ -502,7 +502,7 @@ export default {
&:hover { &:hover {
border-radius: $border-radius; border-radius: $border-radius;
.schema-size{ .schema-size {
visibility: visible; visibility: visible;
} }
} }

View File

@@ -78,17 +78,17 @@
@confirm="deleteSchema" @confirm="deleteSchema"
@hide="hideDeleteModal" @hide="hideDeleteModal"
> >
<template slot="header"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-remove mr-1" /> <i class="mdi mdi-24px mdi-database-remove mr-1" />
<span class="cut-text">{{ $t('message.deleteSchema') }}</span> <span class="cut-text">{{ $t('message.deleteSchema') }}</span>
</div> </div>
</template> </template>
<div slot="body"> <template #body>
<div class="mb-2"> <div class="mb-2">
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedSchema }}</b>"? {{ $t('message.deleteCorfirm') }} "<b>{{ selectedSchema }}</b>"?
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
<ModalEditSchema <ModalEditSchema
v-if="isEditModal" v-if="isEditModal"

View File

@@ -40,33 +40,33 @@
@confirm="emptyTable" @confirm="emptyTable"
@hide="hideEmptyModal" @hide="hideEmptyModal"
> >
<template slot="header"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-table-off mr-1" /> <span class="cut-text">{{ $t('message.emptyTable') }}</span> <i class="mdi mdi-24px mdi-table-off mr-1" /> <span class="cut-text">{{ $t('message.emptyTable') }}</span>
</div> </div>
</template> </template>
<div slot="body"> <template #body>
<div class="mb-2"> <div class="mb-2">
{{ $t('message.emptyCorfirm') }} "<b>{{ selectedTable.name }}</b>"? {{ $t('message.emptyCorfirm') }} "<b>{{ selectedTable.name }}</b>"?
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
<ConfirmModal <ConfirmModal
v-if="isDeleteModal" v-if="isDeleteModal"
@confirm="deleteTable" @confirm="deleteTable"
@hide="hideDeleteModal" @hide="hideDeleteModal"
> >
<template slot="header"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-table-remove mr-1" /> <i class="mdi mdi-24px mdi-table-remove mr-1" />
<span class="cut-text">{{ selectedTable.type === 'table' ? $t('message.deleteTable') : $t('message.deleteView') }}</span> <span class="cut-text">{{ selectedTable.type === 'table' ? $t('message.deleteTable') : $t('message.deleteView') }}</span>
</div> </div>
</template> </template>
<div slot="body"> <template #body>
<div class="mb-2"> <div class="mb-2">
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedTable.name }}</b>"? {{ $t('message.deleteCorfirm') }} "<b>{{ selectedTable.name }}</b>"?
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
</BaseContextMenu> </BaseContextMenu>
</template> </template>

View File

@@ -5,7 +5,7 @@
<div class="workspace-query-buttons"> <div class="workspace-query-buttons">
<button <button
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:disabled="!isChanged" :disabled="!isChanged || !isValid"
:class="{'loading':isSaving}" :class="{'loading':isSaving}"
title="CTRL+S" title="CTRL+S"
@click="saveChanges" @click="saveChanges"
@@ -242,6 +242,9 @@ export default {
JSON.stringify(this.originalKeyUsage) !== JSON.stringify(this.localKeyUsage) || JSON.stringify(this.originalKeyUsage) !== JSON.stringify(this.localKeyUsage) ||
JSON.stringify(this.originalIndexes) !== JSON.stringify(this.localIndexes) || JSON.stringify(this.originalIndexes) !== JSON.stringify(this.localIndexes) ||
JSON.stringify(this.tableOptions) !== JSON.stringify(this.localOptions); JSON.stringify(this.tableOptions) !== JSON.stringify(this.localOptions);
},
isValid () {
return !!this.localFields.length && !!this.localOptions.name.trim().length;
} }
}, },
watch: { watch: {
@@ -287,7 +290,7 @@ export default {
changeBreadcrumbs: 'workspaces/changeBreadcrumbs' changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
}), }),
async saveChanges () { async saveChanges () {
if (this.isSaving) return; if (this.isSaving || !this.isValid) return;
this.isSaving = true; this.isSaving = true;
const params = { const params = {

View File

@@ -6,13 +6,13 @@
@confirm="confirmParametersChange" @confirm="confirmParametersChange"
@hide="$emit('hide')" @hide="$emit('hide')"
> >
<template :slot="'header'"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" /> <i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span class="cut-text">{{ $t('word.parameters') }} "{{ func }}"</span> <span class="cut-text">{{ $t('word.parameters') }} "{{ func }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <template #body>
<div class="columns col-gapless"> <div class="columns col-gapless">
<div class="column col-5"> <div class="column col-5">
<div class="panel" :style="{ height: modalInnerHeight + 'px'}"> <div class="panel" :style="{ height: modalInnerHeight + 'px'}">
@@ -167,7 +167,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
</template> </template>

View File

@@ -6,13 +6,13 @@
@confirm="confirmParametersChange" @confirm="confirmParametersChange"
@hide="$emit('hide')" @hide="$emit('hide')"
> >
<template :slot="'header'"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-dots-horizontal mr-1" /> <i class="mdi mdi-24px mdi-dots-horizontal mr-1" />
<span class="cut-text">{{ $t('word.parameters') }} "{{ routine }}"</span> <span class="cut-text">{{ $t('word.parameters') }} "{{ routine }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <template #body>
<div class="columns col-gapless"> <div class="columns col-gapless">
<div class="column col-5"> <div class="column col-5">
<div class="panel" :style="{ height: modalInnerHeight + 'px'}"> <div class="panel" :style="{ height: modalInnerHeight + 'px'}">
@@ -167,7 +167,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
</template> </template>

View File

@@ -5,13 +5,13 @@
@confirm="confirmOptionsChange" @confirm="confirmOptionsChange"
@hide="$emit('hide')" @hide="$emit('hide')"
> >
<template :slot="'header'"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-timer mr-1" /> <i class="mdi mdi-24px mdi-timer mr-1" />
<span class="cut-text">{{ $t('word.timing') }} "{{ localOptions.name }}"</span> <span class="cut-text">{{ $t('word.timing') }} "{{ localOptions.name }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <template #body>
<form class="form-horizontal"> <form class="form-horizontal">
<div class="form-group"> <div class="form-group">
<label class="form-label col-4"> <label class="form-label col-4">
@@ -133,7 +133,7 @@
</div> </div>
</div> </div>
</form> </form>
</div> </template>
</ConfirmModal> </ConfirmModal>
</template> </template>

View File

@@ -6,13 +6,13 @@
@confirm="confirmForeignsChange" @confirm="confirmForeignsChange"
@hide="$emit('hide')" @hide="$emit('hide')"
> >
<template :slot="'header'"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-key-link mr-1" /> <i class="mdi mdi-24px mdi-key-link mr-1" />
<span class="cut-text">{{ $t('word.foreignKeys') }} "{{ table }}"</span> <span class="cut-text">{{ $t('word.foreignKeys') }} "{{ table }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <template #body>
<div class="columns col-gapless"> <div class="columns col-gapless">
<div class="column col-5"> <div class="column col-5">
<div class="panel" :style="{ height: modalInnerHeight + 'px'}"> <div class="panel" :style="{ height: modalInnerHeight + 'px'}">
@@ -197,7 +197,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
</template> </template>
@@ -289,7 +289,7 @@ export default {
addForeign () { addForeign () {
this.foreignProxy = [...this.foreignProxy, { this.foreignProxy = [...this.foreignProxy, {
_antares_id: uidGen(), _antares_id: uidGen(),
constraintName: `FK_${this.foreignProxy.length + 1}`, constraintName: `FK_${uidGen()}`,
refSchema: this.schema, refSchema: this.schema,
table: this.table, table: this.table,
refTable: '', refTable: '',

View File

@@ -6,13 +6,13 @@
@confirm="confirmIndexesChange" @confirm="confirmIndexesChange"
@hide="$emit('hide')" @hide="$emit('hide')"
> >
<template :slot="'header'"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" /> <i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" />
<span class="cut-text">{{ $t('word.indexes') }} "{{ table }}"</span> <span class="cut-text">{{ $t('word.indexes') }} "{{ table }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <template #body>
<div class="columns col-gapless"> <div class="columns col-gapless">
<div class="column col-5"> <div class="column col-5">
<div class="panel" :style="{ height: modalInnerHeight + 'px'}"> <div class="panel" :style="{ height: modalInnerHeight + 'px'}">
@@ -133,7 +133,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
</template> </template>

View File

@@ -230,13 +230,13 @@
@confirm="editOFF" @confirm="editOFF"
@hide="hideDefaultModal" @hide="hideDefaultModal"
> >
<template :slot="'header'"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-edit mr-1" /> <i class="mdi mdi-24px mdi-playlist-edit mr-1" />
<span class="cut-text">{{ $t('word.default') }} "{{ row.name }}"</span> <span class="cut-text">{{ $t('word.default') }} "{{ row.name }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <template #body>
<form class="form-horizontal"> <form class="form-horizontal">
<div class="mb-2"> <div class="mb-2">
<label class="form-radio form-inline"> <label class="form-radio form-inline">
@@ -324,7 +324,7 @@
</div> </div>
</div> </div>
</form> </form>
</div> </template>
</ConfirmModal> </ConfirmModal>
</div> </div>
</template> </template>

View File

@@ -4,6 +4,7 @@
class="workspace-query-tab column col-12 columns col-gapless no-outline p-0" class="workspace-query-tab column col-12 columns col-gapless no-outline p-0"
tabindex="0" tabindex="0"
@keydown.116="runQuery(query)" @keydown.116="runQuery(query)"
@keydown.75="killTabQuery"
@keydown.ctrl.alt.87="clear" @keydown.ctrl.alt.87="clear"
@keydown.ctrl.66="beautify" @keydown.ctrl.66="beautify"
@keydown.ctrl.71="openHistoryModal" @keydown.ctrl.71="openHistoryModal"
@@ -22,7 +23,19 @@
<div ref="resizer" class="query-area-resizer" /> <div ref="resizer" class="query-area-resizer" />
<div class="workspace-query-runner-footer"> <div class="workspace-query-runner-footer">
<div class="workspace-query-buttons"> <div class="workspace-query-buttons">
<div @mouseenter="setCancelButtonVisibility(true)" @mouseleave="setCancelButtonVisibility(false)">
<button <button
v-if="showCancel && isQuering"
class="btn btn-primary btn-sm cancellable"
:disabled="!query"
:title="$t('word.cancel')"
@click="killTabQuery()"
>
<i class="mdi mdi-24px mdi-window-close" />
<span class="d-invisible pr-1">{{ $t('word.run') }}</span>
</button>
<button
v-else
class="btn btn-primary btn-sm" class="btn btn-primary btn-sm"
:class="{'loading':isQuering}" :class="{'loading':isQuering}"
:disabled="!query" :disabled="!query"
@@ -32,6 +45,7 @@
<i class="mdi mdi-24px mdi-play pr-1" /> <i class="mdi mdi-24px mdi-play pr-1" />
<span>{{ $t('word.run') }}</span> <span>{{ $t('word.run') }}</span>
</button> </button>
</div>
<button <button
class="btn btn-link btn-sm mr-0" class="btn btn-link btn-sm mr-0"
:disabled="!query || isQuering" :disabled="!query || isQuering"
@@ -110,7 +124,7 @@
</div> </div>
</div> </div>
</div> </div>
<WorkspaceTabQueryEmptyState v-if="!results.length && !isQuering" /> <WorkspaceTabQueryEmptyState v-if="!results.length && !isQuering" :customizations="workspace.customizations" />
<div class="workspace-query-results p-relative column col-12"> <div class="workspace-query-results p-relative column col-12">
<BaseLoader v-if="isQuering" /> <BaseLoader v-if="isQuering" />
<WorkspaceTabQueryTable <WorkspaceTabQueryTable
@@ -166,6 +180,8 @@ export default {
query: '', query: '',
lastQuery: '', lastQuery: '',
isQuering: false, isQuering: false,
isCancelling: false,
showCancel: false,
results: [], results: [],
selectedSchema: null, selectedSchema: null,
resultsCount: 0, resultsCount: 0,
@@ -202,8 +218,13 @@ export default {
}, },
watch: { watch: {
isSelected (val) { isSelected (val) {
if (val) if (val) {
this.changeBreadcrumbs({ schema: this.selectedSchema, query: `Query #${this.tab.index}` }); this.changeBreadcrumbs({ schema: this.selectedSchema, query: `Query #${this.tab.index}` });
setTimeout(() => {
if (this.$refs.queryEditor)
this.$refs.queryEditor.editor.focus();
}, 0);
}
}, },
selectedSchema () { selectedSchema () {
this.changeBreadcrumbs({ schema: this.selectedSchema, query: `Query #${this.tab.index}` }); this.changeBreadcrumbs({ schema: this.selectedSchema, query: `Query #${this.tab.index}` });
@@ -248,6 +269,7 @@ export default {
const params = { const params = {
uid: this.connection.uid, uid: this.connection.uid,
schema: this.selectedSchema, schema: this.selectedSchema,
tabUid: this.tab.uid,
query query
}; };
@@ -283,6 +305,29 @@ export default {
this.isQuering = false; this.isQuering = false;
this.lastQuery = query; this.lastQuery = query;
}, },
async killTabQuery () {
if (this.isCancelling) return;
this.isCancelling = true;
try {
const params = {
uid: this.connection.uid,
tabUid: this.tab.uid
};
await Schema.killTabQuery(params);
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isCancelling = false;
},
setCancelButtonVisibility (val) {
if (this.workspace.customizations.cancelQueries)
this.showCancel = val;
},
reloadTable () { reloadTable () {
this.runQuery(this.lastQuery); this.runQuery(this.lastQuery);
}, },

View File

@@ -5,6 +5,9 @@
<div class="mb-4"> <div class="mb-4">
{{ $t('message.runQuery') }} {{ $t('message.runQuery') }}
</div> </div>
<div v-if="customizations.cancelQueries" class="mb-4">
{{ $t('message.killQuery') }}
</div>
<div class="mb-4"> <div class="mb-4">
{{ $t('word.format') }} {{ $t('word.format') }}
</div> </div>
@@ -25,6 +28,9 @@
<div class="mb-4"> <div class="mb-4">
<code>F5</code> <code>F5</code>
</div> </div>
<div v-if="customizations.cancelQueries" class="mb-4">
<code>CTRL</code> + <code>K</code>
</div>
<div class="mb-4"> <div class="mb-4">
<code>CTRL</code> + <code>B</code> <code>CTRL</code> + <code>B</code>
</div> </div>
@@ -47,7 +53,10 @@
<script> <script>
export default { export default {
name: 'WorkspaceTabQueryEmptyState' name: 'WorkspaceTabQueryEmptyState',
props: {
customizations: Object
}
}; };
</script> </script>

View File

@@ -5,7 +5,7 @@
tabindex="0" tabindex="0"
:style="{'height': resultsSize+'px'}" :style="{'height': resultsSize+'px'}"
@keyup.46="showDeleteConfirmModal" @keyup.46="showDeleteConfirmModal"
@keydown.ctrl.65="selectAllRows" @keydown.ctrl.65="selectAllRows($event)"
@keydown.esc="deselectRows" @keydown.esc="deselectRows"
> >
<TableContext <TableContext
@@ -53,6 +53,7 @@
class="mdi sort-icon" class="mdi sort-icon"
:class="currentSortDir === 'asc' ? 'mdi-sort-ascending':'mdi-sort-descending'" :class="currentSortDir === 'asc' ? 'mdi-sort-ascending':'mdi-sort-descending'"
/> />
<i v-else class="mdi sort-icon mdi-minus d-invisible" />
</div> </div>
</div> </div>
</div> </div>
@@ -62,7 +63,7 @@
v-if="resultsWithRows[resultsetIndex] && resultsWithRows[resultsetIndex].rows" v-if="resultsWithRows[resultsetIndex] && resultsWithRows[resultsetIndex].rows"
ref="resultTable" ref="resultTable"
:items="sortedResults" :items="sortedResults"
:item-height="23" :item-height="rowHeight"
class="tbody" class="tbody"
:visible-height="resultsSize" :visible-height="resultsSize"
:scroll-element="scrollElement" :scroll-element="scrollElement"
@@ -71,6 +72,7 @@
<WorkspaceTabQueryTableRow <WorkspaceTabQueryTableRow
v-for="row in items" v-for="row in items"
:key="row._antares_id" :key="row._antares_id"
:item-height="rowHeight"
:row="row" :row="row"
:fields="fieldsObj" :fields="fieldsObj"
:key-usage="keyUsage" :key-usage="keyUsage"
@@ -89,17 +91,17 @@
@confirm="deleteSelected" @confirm="deleteSelected"
@hide="hideDeleteConfirmModal" @hide="hideDeleteConfirmModal"
> >
<template :slot="'header'"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-delete mr-1" /> <i class="mdi mdi-24px mdi-delete mr-1" />
<span class="cut-text">{{ $tc('message.deleteRows', selectedRows.length) }}</span> <span class="cut-text">{{ $tc('message.deleteRows', selectedRows.length) }}</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <template #body>
<div class="mb-2"> <div class="mb-2">
{{ $tc('message.confirmToDeleteRows', selectedRows.length) }} {{ $tc('message.confirmToDeleteRows', selectedRows.length) }}
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
</div> </div>
</template> </template>
@@ -142,7 +144,8 @@ export default {
currentSort: '', currentSort: '',
currentSortDir: 'asc', currentSortDir: 'asc',
resultsetIndex: 0, resultsetIndex: 0,
scrollElement: null scrollElement: null,
rowHeight: 23
}; };
}, },
computed: { computed: {
@@ -242,6 +245,11 @@ export default {
if (this.$refs.tableWrapper) if (this.$refs.tableWrapper)
this.scrollElement = this.$refs.tableWrapper; this.scrollElement = this.$refs.tableWrapper;
document.querySelectorAll('.column-resizable').forEach(element => {
if (element.clientWidth !== 0)
element.style.width = element.clientWidth + 'px';
});
}, },
mounted () { mounted () {
window.addEventListener('resize', this.resizeResults); window.addEventListener('resize', this.resizeResults);
@@ -450,7 +458,9 @@ export default {
else else
this.selectedRows = [row]; this.selectedRows = [row];
}, },
selectAllRows () { selectAllRows (e) {
if (e.target.classList.contains('editable-field')) return;
this.selectedRows = this.localResults.reduce((acc, curr) => { this.selectedRows = this.localResults.reduce((acc, curr) => {
acc.push(curr._antares_id); acc.push(curr._antares_id);
return acc; return acc;

View File

@@ -1,5 +1,9 @@
<template> <template>
<div class="tr" @click="selectRow($event, row._antares_id)"> <div
class="tr"
:style="{height: itemHeight+'px'}"
@click="selectRow($event, row._antares_id)"
>
<div <div
v-for="(col, cKey) in row" v-for="(col, cKey) in row"
v-show="cKey !== '_antares_id'" v-show="cKey !== '_antares_id'"
@@ -72,12 +76,12 @@
@confirm="editOFF" @confirm="editOFF"
@hide="hideEditorModal" @hide="hideEditorModal"
> >
<template :slot="'header'"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-edit mr-1" /> <span class="cut-text">{{ $t('word.edit') }} "{{ editingField }}"</span> <i class="mdi mdi-24px mdi-playlist-edit mr-1" /> <span class="cut-text">{{ $t('word.edit') }} "{{ editingField }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <template #body>
<div class="mb-2"> <div class="mb-2">
<div> <div>
<TextEditor <TextEditor
@@ -96,23 +100,12 @@
v-model="editorMode" v-model="editorMode"
class="form-select select-sm" class="form-select select-sm"
> >
<option value="text"> <option
TEXT v-for="language in availableLanguages"
</option> :key="language.slug"
<option value="html"> :value="language.slug"
HTML >
</option> {{ language.name }}
<option value="xml">
XML
</option>
<option value="json">
JSON
</option>
<option value="svg">
SVG
</option>
<option value="yaml">
YAML
</option> </option>
</select> </select>
</div> </div>
@@ -128,7 +121,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
<ConfirmModal <ConfirmModal
v-if="isBlobEditor" v-if="isBlobEditor"
@@ -136,13 +129,13 @@
@confirm="editOFF" @confirm="editOFF"
@hide="hideEditorModal" @hide="hideEditorModal"
> >
<template :slot="'header'"> <template #header>
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-edit mr-1" /> <i class="mdi mdi-24px mdi-playlist-edit mr-1" />
<span class="cut-text">{{ $t('word.edit') }} "{{ editingField }}"</span> <span class="cut-text">{{ $t('word.edit') }} "{{ editingField }}"</span>
</div> </div>
</template> </template>
<div :slot="'body'"> <template #body>
<div class="mb-2"> <div class="mb-2">
<transition name="jump-down"> <transition name="jump-down">
<div v-if="contentInfo.size"> <div v-if="contentInfo.size">
@@ -182,13 +175,14 @@
> >
</div> </div>
</div> </div>
</div> </template>
</ConfirmModal> </ConfirmModal>
</div> </div>
</template> </template>
<script> <script>
import moment from 'moment'; import moment from 'moment';
import { ModelOperations } from '@vscode/vscode-languagedetection';
import { mimeFromHex } from 'common/libs/mimeFromHex'; import { mimeFromHex } from 'common/libs/mimeFromHex';
import { formatBytes } from 'common/libs/formatBytes'; import { formatBytes } from 'common/libs/formatBytes';
import { bufferToBase64 } from 'common/libs/bufferToBase64'; import { bufferToBase64 } from 'common/libs/bufferToBase64';
@@ -261,6 +255,7 @@ export default {
row: Object, row: Object,
fields: Object, fields: Object,
keyUsage: Array, keyUsage: Array,
itemHeight: Number,
elementType: { type: String, default: 'table' } elementType: { type: String, default: 'table' }
}, },
data () { data () {
@@ -280,7 +275,17 @@ export default {
mime: '', mime: '',
size: null size: null
}, },
fileToUpload: null fileToUpload: null,
availableLanguages: [
{ name: 'TEXT', slug: 'text', id: 'text' },
{ name: 'HTML', slug: 'html', id: 'html' },
{ name: 'XML', slug: 'xml', id: 'xml' },
{ name: 'JSON', slug: 'json', id: 'json' },
{ name: 'SVG', slug: 'svg', id: 'svg' },
{ name: 'INI', slug: 'ini', id: 'ini' },
{ name: 'MARKDOWN', slug: 'markdown', id: 'md' },
{ name: 'YAML', slug: 'yaml', id: 'yaml' }
]
}; };
}, },
computed: { computed: {
@@ -362,6 +367,21 @@ export default {
Object.keys(this.fields).forEach(field => { Object.keys(this.fields).forEach(field => {
this.isInlineEditor[field.name] = false; this.isInlineEditor[field.name] = false;
}); });
},
isTextareaEditor (val) {
if (val) {
const modelOperations = new ModelOperations();
(async () => {
const detected = await modelOperations.runModel(this.editingContent);
const filteredLanguages = detected.filter(dLang =>
this.availableLanguages.some(aLang => aLang.id === dLang.languageId) &&
dLang.confidence > 0.1
);
if (filteredLanguages.length)
this.editorMode = this.availableLanguages.find(lang => lang.id === filteredLanguages[0].languageId).slug;
})();
}
} }
}, },
methods: { methods: {

View File

@@ -251,7 +251,8 @@ module.exports = {
killProcess: 'Kill process', killProcess: 'Kill process',
closeTab: 'Close tab', closeTab: 'Close tab',
goToDownloadPage: 'Go to download page', goToDownloadPage: 'Go to download page',
readOnlyMode: 'Read-only mode' readOnlyMode: 'Read-only mode',
killQuery: 'Kill query'
}, },
faker: { faker: {
address: 'Address', address: 'Address',

View File

@@ -120,7 +120,11 @@ module.exports = {
new: 'Mới', new: 'Mới',
history: 'Lịch sử', history: 'Lịch sử',
select: 'Chọn', select: 'Chọn',
passphrase: 'Cụm mật khẩu' passphrase: 'Cụm mật khẩu',
filter: 'Bộ lọc',
disabled: 'Đã tắt',
enable: 'Bật',
disable: 'Tắt'
}, },
message: { message: {
appWelcome: 'Chào bạn đến với Antares SQL Client!', appWelcome: 'Chào bạn đến với Antares SQL Client!',
@@ -244,7 +248,10 @@ module.exports = {
newTriggerFunction: 'Chức năng kích hoạt mới', newTriggerFunction: 'Chức năng kích hoạt mới',
thereIsNoQueriesYet: 'Không có truy vấn nào', thereIsNoQueriesYet: 'Không có truy vấn nào',
searchForQueries: 'Tìm kiếm truy vấn', searchForQueries: 'Tìm kiếm truy vấn',
killProcess: 'Huỷ quá trình' killProcess: 'Huỷ quá trình',
closeTab: 'Đóng tab',
goToDownloadPage: 'Tới trang tải về',
readOnlyMode: 'Chế độ chỉ đọc'
}, },
faker: { faker: {
address: 'Địa chỉ', address: 'Địa chỉ',

View File

@@ -46,6 +46,10 @@ export default class {
return ipcRenderer.invoke('kill-process', params); return ipcRenderer.invoke('kill-process', params);
} }
static killTabQuery (params) {
return ipcRenderer.invoke('kill-tab-query', params);
}
static useSchema (params) { static useSchema (params) {
return ipcRenderer.invoke('use-schema', params); return ipcRenderer.invoke('use-schema', params);
} }

View File

@@ -59,6 +59,34 @@ option:checked {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.cancellable {
color: transparent !important;
min-height: 0.8rem;
position: relative;
> .mdi,
> .span {
visibility: hidden;
}
&::after {
content: "\2715";
color: $light-color;
font-weight: 700;
top: 36%;
display: block;
height: 0.8rem;
left: 50%;
margin-left: -0.4rem;
margin-top: -0.4rem;
opacity: 1;
padding: 0;
position: absolute;
width: 0.8rem;
z-index: 1;
}
}
.workspace-tabs { .workspace-tabs {
align-content: baseline; align-content: baseline;

50
tests/app.spec.js Normal file
View File

@@ -0,0 +1,50 @@
const { _electron: electron } = require('playwright');
const { strict: assert } = require('assert');
(async () => {
console.log('Starting tests');
// Launch Electron app.
const electronApp = await electron.launch({ args: ['dist/main.js'] });
/**
* App main window state
* @type {{isVisible: boolean; isDevToolsOpened: boolean; isCrashed: boolean}}
*/
const windowState = await electronApp.evaluate(({ BrowserWindow }) => {
const mainWindow = BrowserWindow.getAllWindows()[0];
const getState = () => ({
isVisible: mainWindow.isVisible(),
isDevToolsOpened: mainWindow.webContents.isDevToolsOpened()
});
return new Promise((resolve) => {
if (mainWindow.isVisible())
resolve(getState());
else
mainWindow.once('ready-to-show', () => setTimeout(() => resolve(getState()), 0));
});
});
// Check main window state
assert.ok(windowState.isVisible, 'Main window not visible');
assert.ok(!windowState.isDevToolsOpened, 'DevTools opened');
assert.ok(!windowState.isCrashed, 'Window crashed');
/**
* Rendered Main window web-page
* @type {Page}
*/
const page = await electronApp.firstWindow();
console.log(await page.title());
// Check web-page content
const element = await page.$('#wrapper', { strict: true });
assert.notStrictEqual(element, null, 'Can\'t find root element');
assert.notStrictEqual((await element.innerHTML()).trim(), '', 'Window content is empty');
// Close app
await electronApp.close();
console.log('Tests finished');
})();