mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
8cafade8b1 | |||
d13b708377 | |||
206597e5b8 | |||
c5458159d1 | |||
1476e899d1 | |||
797ab70e7c | |||
f81312aeb0 | |||
3ed5ea023e | |||
9291a7a7b4 | |||
5cfdc9b92d | |||
15b08d7ea8 | |||
acebe435ff | |||
5712b80022 | |||
d38583262e | |||
e0e2131981 | |||
7470bddd70 | |||
33d1fa2290 | |||
a4122b4eaa | |||
e6602d1bfa | |||
f8cf90a89e | |||
41505bde65 | |||
8ebc3bce92 | |||
45e9cdc591 | |||
3cbfc0e148 | |||
a47e9e1b1f | |||
e95d29c7c3 | |||
e954f04828 | |||
|
85c800f85b | ||
|
e0482244d7 | ||
27769f204f | |||
dfb24c65f3 | |||
0fe71572a5 | |||
db577bfef0 | |||
0805b96a75 |
12
.travis.yml
12
.travis.yml
@@ -39,9 +39,9 @@ jobs:
|
|||||||
script:
|
script:
|
||||||
- npm run build -- -p always
|
- npm run build -- -p always
|
||||||
|
|
||||||
- stage: Deploy ARM Linux
|
# - stage: Deploy ARM Linux
|
||||||
if: tag IS present
|
# if: tag IS present
|
||||||
os: linux
|
# os: linux
|
||||||
arch: arm64
|
# arch: arm64
|
||||||
script:
|
# script:
|
||||||
- npm run build -- --linux AppImage -p always
|
# - npm run build -- --linux AppImage -p always
|
41
CHANGELOG.md
41
CHANGELOG.md
@@ -2,6 +2,47 @@
|
|||||||
|
|
||||||
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.0.11](https://github.com/Fabio286/antares/compare/v0.0.10...v0.0.11) (2020-12-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* auto focus on first input in modals ([1476e89](https://github.com/Fabio286/antares/commit/1476e899d164562f12342ced8c76903b9bdcfa55))
|
||||||
|
* foreign keys management ([206597e](https://github.com/Fabio286/antares/commit/206597e5b891e13e6f7635075bd11599355ab778))
|
||||||
|
* improved data table sorts ([5712b80](https://github.com/Fabio286/antares/commit/5712b8002203b32027f0e820f98a61e2ec965e79))
|
||||||
|
* query tabs auto focus ([f81312a](https://github.com/Fabio286/antares/commit/f81312aeb02ef55affd2ae9e81a9b4cb4c2e6da2))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* data tab sort not maintained at refresh ([15b08d7](https://github.com/Fabio286/antares/commit/15b08d7ea858cb28111c7b548af9a13b1bf0da91))
|
||||||
|
* deletion of rows with non-numeric ID ([d385832](https://github.com/Fabio286/antares/commit/d38583262e672a2b47c5ad0aca0f13c129830a7b))
|
||||||
|
* file field editor not show ([9291a7a](https://github.com/Fabio286/antares/commit/9291a7a7b41e7aeb9b65c7f32e496f523e482272))
|
||||||
|
* improved changes dedection in props tab ([acebe43](https://github.com/Fabio286/antares/commit/acebe435ff6fa1581692fbf7ee1bc23b334e3947))
|
||||||
|
* some properties do not reset after fields changes ([3ed5ea0](https://github.com/Fabio286/antares/commit/3ed5ea023e1852d724b2b59ab156f8722876f85b))
|
||||||
|
* unable to switch tabs when no table selected ([c545815](https://github.com/Fabio286/antares/commit/c5458159d1e30cecf6615086c074d98b4b599637))
|
||||||
|
* wrong field type detection ([5cfdc9b](https://github.com/Fabio286/antares/commit/5cfdc9b92d4b778a7863b02fd64e6445236c89bc))
|
||||||
|
|
||||||
|
### [0.0.10](https://github.com/Fabio286/antares/compare/v0.0.9...v0.0.10) (2020-12-04)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* approximate totals in table tata tab ([e95d29c](https://github.com/Fabio286/antares/commit/e95d29c7c37e24e7cc14b466f9b539fa667042c2))
|
||||||
|
* create new tables ([e6602d1](https://github.com/Fabio286/antares/commit/e6602d1bfa9ca10c6bb078ee80ddc94fb338763d))
|
||||||
|
* display all keys in properties tab ([27769f2](https://github.com/Fabio286/antares/commit/27769f204f731d20c7ba2f838c02b7c2f28fa0c3))
|
||||||
|
* drop and truncate tables ([a4122b4](https://github.com/Fabio286/antares/commit/a4122b4eaaa5b30d97ba5a93df8c9d21c30bc40b))
|
||||||
|
* index management ([41505bd](https://github.com/Fabio286/antares/commit/41505bde6547c0af3c3413248ad8a0d182838bb1))
|
||||||
|
* tables options edit ([0805b96](https://github.com/Fabio286/antares/commit/0805b96a75e439a7d65e8341ecc86fa938679a9f))
|
||||||
|
* unsaved changes reminder ([33d1fa2](https://github.com/Fabio286/antares/commit/33d1fa22905f477924292135b0dcfefe168ee641))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* index deletion issue ([f8cf90a](https://github.com/Fabio286/antares/commit/f8cf90a89e7367c95e164b7dc669506df392b700))
|
||||||
|
* some problems with properties and data tabs when changing database from sidebar ([0fe7157](https://github.com/Fabio286/antares/commit/0fe71572a5e74c17a5c66237351bb0b02c33e824))
|
||||||
|
* sqlEscaper function wrong quotes conversion ([dfb24c6](https://github.com/Fabio286/antares/commit/dfb24c65f3c395d78d27a2f29e9aa8eeb427cff7))
|
||||||
|
|
||||||
### [0.0.9](https://github.com/EStarium/antares/compare/v0.0.8...v0.0.9) (2020-11-13)
|
### [0.0.9](https://github.com/EStarium/antares/compare/v0.0.8...v0.0.9) (2020-11-13)
|
||||||
|
|
||||||
|
|
||||||
|
27
README.md
27
README.md
@@ -4,14 +4,16 @@
|
|||||||
|
|
||||||
# Antares SQL Client
|
# Antares SQL Client
|
||||||
|
|
||||||
 [](https://travis-ci.com/EStarium/antares)  
|
 [](https://travis-ci.com/Fabio286/antares)  
|
||||||
|
|
||||||
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
|
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
|
||||||
My target is to support as many databases as possible, and all major operating systems, including the ARM versions.
|
My target is to support as many databases as possible, and all major operating systems, including the ARM versions.
|
||||||
|
|
||||||
**At the moment this application is an alpha, it lacks many features, and isn't ready as a main SQL client**. However i'm actively working on it (yes, i'm a lone dev), hoping to provide all essential features as soon as possible.
|
**At the moment this application is an alpha, it lacks many features** and supports only MySQL.
|
||||||
|
Most of its current features might be enough for basic MySQL use, so give it a chance and send me your feedback, I would really appreciate it.
|
||||||
|
I'm actively working on it (yes, i'm a lone dev), hoping to provide cool features and fixes 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).
|
🔗 If you are curious to try this early state of Antares you can download and install the [latest release](https://github.com/Fabio286/antares/releases).
|
||||||
👁 To stay tuned for new releases watch this repo on **Release only** channel.
|
👁 To stay tuned for new releases watch this repo on **Release only** channel.
|
||||||
🌟 Don't forget to **leave a star** if you appreciate this project.
|
🌟 Don't forget to **leave a star** if you appreciate this project.
|
||||||
|
|
||||||
@@ -23,14 +25,13 @@ An application created with minimalism and semplicity in mind, with features in
|
|||||||
|
|
||||||
## How to contribute
|
## How to contribute
|
||||||
|
|
||||||
- [Translate Antares](https://github.com/EStarium/antares/wiki/Translate-Antares)
|
- [Translate Antares](https://github.com/Fabio286/antares/wiki/Translate-Antares)
|
||||||
|
|
||||||
## Current main features
|
## Current main features
|
||||||
|
|
||||||
- Multiple database connections at same time.
|
- Multiple database connections at same time.
|
||||||
- Database management (add/edit/delete).
|
- Database management (add/edit/delete).
|
||||||
- Tables fields management (add/edit/delete).
|
- Full tables management, including indexes and foreign keys.
|
||||||
- Tables content management (add/edit/delete).
|
|
||||||
- Run queries on multiple tabs.
|
- Run queries on multiple tabs.
|
||||||
- Query suggestions.
|
- Query suggestions.
|
||||||
- Native dark theme.
|
- Native dark theme.
|
||||||
@@ -41,12 +42,13 @@ An application created with minimalism and semplicity in mind, with features in
|
|||||||
|
|
||||||
This is a roadmap with major features will come in near future.
|
This is a roadmap with major features will come in near future.
|
||||||
|
|
||||||
- Tables management (add/edit/delete).
|
|
||||||
- Users management (add/edit/delete).
|
|
||||||
- Stored procedures, views, schedulers and triggers support.
|
- Stored procedures, views, schedulers and triggers support.
|
||||||
|
- Users management (add/edit/delete).
|
||||||
- More secure password storage.
|
- More secure password storage.
|
||||||
- Database tools (variables, process list...).
|
- Database tools (variables, process list...).
|
||||||
|
- SSL and SSH tunnel support.
|
||||||
- Support for other databases.
|
- Support for other databases.
|
||||||
|
- UI/UX improvements.
|
||||||
- Improvements of query editor area.
|
- Improvements of query editor area.
|
||||||
- Improvements of query suggestions.
|
- Improvements of query suggestions.
|
||||||
- Query history.
|
- Query history.
|
||||||
@@ -55,7 +57,6 @@ This is a roadmap with major features will come in near future.
|
|||||||
- Query logs console.
|
- Query logs console.
|
||||||
- Fake data filler.
|
- Fake data filler.
|
||||||
- Import/export and migration.
|
- Import/export and migration.
|
||||||
- SSL and SSH tunnel support.
|
|
||||||
- Themes.
|
- Themes.
|
||||||
|
|
||||||
## Currently supported
|
## Currently supported
|
||||||
@@ -80,11 +81,11 @@ This is a roadmap with major features will come in near future.
|
|||||||
#### • ARM
|
#### • ARM
|
||||||
|
|
||||||
- [ ] Windows
|
- [ ] Windows
|
||||||
- [x] Linux
|
- [ ] Linux
|
||||||
- [ ] MacOS
|
- [ ] MacOS
|
||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|
||||||
[Giuseppe Gigliotti](https://github.com/ReverbOD) / [Italian Translation](https://github.com/EStarium/antares/pull/20)
|
[Giuseppe Gigliotti](https://github.com/ReverbOD) / [Italian Translation](https://github.com/Fabio286/antares/pull/20)
|
||||||
[Mohd-PH](https://github.com/Mohd-PH) / [Arabic Translation](https://github.com/EStarium/antares/pull/29)
|
[Mohd-PH](https://github.com/Mohd-PH) / [Arabic Translation](https://github.com/Fabio286/antares/pull/29)
|
||||||
[hongkfui](https://github.com/hongkfui) / [Spanish Translation](https://github.com/EStarium/antares/pull/32)
|
[hongkfui](https://github.com/hongkfui) / [Spanish Translation](https://github.com/Fabio286/antares/pull/32)
|
||||||
|
5
jsconfig.json
Normal file
5
jsconfig.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"include": [
|
||||||
|
"./src/renderer/**/*"
|
||||||
|
]
|
||||||
|
}
|
19
package.json
19
package.json
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "antares",
|
"name": "antares",
|
||||||
"productName": "Antares",
|
"productName": "Antares",
|
||||||
"version": "0.0.9",
|
"version": "0.0.11",
|
||||||
"description": "A cross-platform easy to use SQL client.",
|
"description": "A cross-platform easy to use SQL client.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "https://github.com/EStarium/antares.git",
|
"repository": "https://github.com/Fabio286/antares.git",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "cross-env NODE_ENV=development electron-webpack dev",
|
"dev": "cross-env NODE_ENV=development electron-webpack dev",
|
||||||
"compile": "electron-webpack",
|
"compile": "electron-webpack",
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
},
|
},
|
||||||
"author": "Fabio Di Stasio <fabio286@gmail.com>",
|
"author": "Fabio Di Stasio <fabio286@gmail.com>",
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.estarium.antares",
|
"appId": "com.fabio286.antares",
|
||||||
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
|
"artifactName": "${productName}-${version}-${os}_${arch}.${ext}",
|
||||||
"dmg": {
|
"dmg": {
|
||||||
"contents": [
|
"contents": [
|
||||||
@@ -58,32 +58,31 @@
|
|||||||
"pg": "^8.5.1",
|
"pg": "^8.5.1",
|
||||||
"source-map-support": "^0.5.16",
|
"source-map-support": "^0.5.16",
|
||||||
"spectre.css": "^0.5.9",
|
"spectre.css": "^0.5.9",
|
||||||
"vue-i18n": "^8.22.1",
|
"vue-i18n": "^8.22.2",
|
||||||
"vue-the-mask": "^0.11.1",
|
"vue-the-mask": "^0.11.1",
|
||||||
"vuedraggable": "^2.24.3",
|
"vuedraggable": "^2.24.3",
|
||||||
"vuex": "^3.5.1",
|
"vuex": "^3.6.0",
|
||||||
"vuex-persist": "^3.1.3"
|
"vuex-persist": "^3.1.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"cross-env": "^7.0.2",
|
"cross-env": "^7.0.2",
|
||||||
"electron": "^10.1.5",
|
"electron": "^11.0.2",
|
||||||
"electron-builder": "^22.9.1",
|
"electron-builder": "^22.9.1",
|
||||||
"electron-devtools-installer": "^3.1.1",
|
"electron-devtools-installer": "^3.1.1",
|
||||||
"electron-webpack": "^2.8.2",
|
"electron-webpack": "^2.8.2",
|
||||||
"electron-webpack-vue": "^2.4.0",
|
"electron-webpack-vue": "^2.4.0",
|
||||||
"eslint": "^7.13.0",
|
"eslint": "^7.14.0",
|
||||||
"eslint-config-standard": "^16.0.1",
|
"eslint-config-standard": "^16.0.2",
|
||||||
"eslint-plugin-import": "^2.22.1",
|
"eslint-plugin-import": "^2.22.1",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^4.2.1",
|
||||||
"eslint-plugin-standard": "^4.1.0",
|
|
||||||
"eslint-plugin-vue": "^7.1.0",
|
"eslint-plugin-vue": "^7.1.0",
|
||||||
"monaco-editor-webpack-plugin": "^1.9.1",
|
"monaco-editor-webpack-plugin": "^1.9.1",
|
||||||
"node-sass": "^5.0.0",
|
"node-sass": "^5.0.0",
|
||||||
"sass-loader": "^10.1.0",
|
"sass-loader": "^10.1.0",
|
||||||
"standard-version": "^9.0.0",
|
"standard-version": "^9.0.0",
|
||||||
"stylelint": "^13.7.2",
|
"stylelint": "^13.8.0",
|
||||||
"stylelint-config-standard": "^20.0.0",
|
"stylelint-config-standard": "^20.0.0",
|
||||||
"stylelint-scss": "^3.18.0",
|
"stylelint-scss": "^3.18.0",
|
||||||
"vue": "^2.6.12",
|
"vue": "^2.6.12",
|
||||||
|
@@ -206,7 +206,7 @@ module.exports = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'TIMESTAMP',
|
name: 'TIMESTAMP',
|
||||||
length: true,
|
length: false,
|
||||||
collation: false,
|
collation: false,
|
||||||
unsigned: false,
|
unsigned: false,
|
||||||
zerofill: false
|
zerofill: false
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
export const TEXT = ['char', 'varchar'];
|
export const TEXT = ['CHAR', 'VARCHAR'];
|
||||||
export const LONG_TEXT = ['text', 'mediumtext', 'longtext'];
|
export const LONG_TEXT = ['TEXT', 'MEDIUMTEXT', 'longtext'];
|
||||||
|
|
||||||
export const NUMBER = ['int', 'tinyint', 'smallint', 'mediumint', 'bigint', 'float', 'double', 'decimal', 'bool'];
|
export const NUMBER = ['INT', 'TINYINT', 'SMALLINT', 'MEDIUMINT', 'BIGINT', 'FLOAT', 'DOUBLE', 'DECIMAL', 'BOOL'];
|
||||||
|
|
||||||
export const DATE = ['date'];
|
export const DATE = ['DATE'];
|
||||||
export const TIME = ['time'];
|
export const TIME = ['TIME'];
|
||||||
export const DATETIME = ['datetime', 'timestamp'];
|
export const DATETIME = ['DATETIME', 'TIMESTAMP'];
|
||||||
|
|
||||||
export const BLOB = ['blob', 'mediumblob', 'longblob'];
|
export const BLOB = ['BLOB', 'MEDIUMBLOB', 'LONGBLOB'];
|
||||||
|
|
||||||
export const BIT = ['bit'];
|
export const BIT = ['BIT'];
|
||||||
|
6
src/common/index-types/mysql.js
Normal file
6
src/common/index-types/mysql.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = [
|
||||||
|
'PRIMARY',
|
||||||
|
'INDEX',
|
||||||
|
'UNIQUE',
|
||||||
|
'FULLTEXT'
|
||||||
|
];
|
@@ -11,8 +11,8 @@ const regex = new RegExp(pattern);
|
|||||||
*/
|
*/
|
||||||
function sqlEscaper (string) {
|
function sqlEscaper (string) {
|
||||||
return string.replace(regex, char => {
|
return string.replace(regex, char => {
|
||||||
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '"', '\\', '\\\\', '%'];
|
const m = ['\\0', '\\x08', '\\x09', '\\x1a', '\\n', '\\r', '\'', '\"', '\\', '\\\\', '%'];
|
||||||
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\'\'', '""', '\\\\', '\\\\\\\\', '\\%'];
|
const r = ['\\\\0', '\\\\b', '\\\\t', '\\\\z', '\\\\n', '\\\\r', '\\\'', '\\\"', '\\\\', '\\\\\\\\', '\\%'];
|
||||||
return r[m.indexOf(char)] || char;
|
return r[m.indexOf(char)] || char;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -83,6 +83,17 @@ export default connections => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-engines', async (event, uid) => {
|
||||||
|
try {
|
||||||
|
const result = await connections[uid].getEngines();
|
||||||
|
|
||||||
|
return { status: 'success', response: result };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('use-schema', async (event, { uid, schema }) => {
|
ipcMain.handle('use-schema', async (event, { uid, schema }) => {
|
||||||
if (!schema) return;
|
if (!schema) return;
|
||||||
|
|
||||||
|
@@ -14,14 +14,29 @@ export default (connections) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('get-table-data', async (event, { uid, schema, table }) => {
|
ipcMain.handle('get-table-data', async (event, { uid, schema, table, sortParams }) => {
|
||||||
try {
|
try {
|
||||||
const result = await connections[uid]
|
const query = connections[uid]
|
||||||
.select('*')
|
.select('*')
|
||||||
.schema(schema)
|
.schema(schema)
|
||||||
.from(table)
|
.from(table)
|
||||||
.limit(1000)
|
.limit(1000);
|
||||||
.run({ details: true });
|
|
||||||
|
if (sortParams && sortParams.field && sortParams.dir)
|
||||||
|
query.orderBy({ [sortParams.field]: sortParams.dir.toUpperCase() });
|
||||||
|
|
||||||
|
const result = await query.run({ details: true });
|
||||||
|
|
||||||
|
return { status: 'success', response: result };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-table-indexes', async (event, params) => {
|
||||||
|
try {
|
||||||
|
const result = await connections[params.uid].getTableIndexes(params);
|
||||||
|
|
||||||
return { status: 'success', response: result };
|
return { status: 'success', response: result };
|
||||||
}
|
}
|
||||||
@@ -78,11 +93,18 @@ export default (connections) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('delete-table-rows', async (event, params) => {
|
ipcMain.handle('delete-table-rows', async (event, params) => {
|
||||||
|
let idString;
|
||||||
|
|
||||||
|
if (typeof params.rows[0] === 'string')
|
||||||
|
idString = params.rows.map(row => `"${row}"`).join(',');
|
||||||
|
else
|
||||||
|
idString = params.rows.join(',');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await connections[params.uid]
|
const result = await connections[params.uid]
|
||||||
.schema(params.schema)
|
.schema(params.schema)
|
||||||
.delete(params.table)
|
.delete(params.table)
|
||||||
.where({ [params.primary]: `IN (${params.rows.join(',')})` })
|
.where({ [params.primary]: `IN (${idString})` })
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
return { status: 'success', response: result };
|
return { status: 'success', response: result };
|
||||||
@@ -154,6 +176,16 @@ export default (connections) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('create-table', async (event, params) => {
|
||||||
|
try {
|
||||||
|
await connections[params.uid].createTable(params);
|
||||||
|
return { status: 'success' };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('alter-table', async (event, params) => {
|
ipcMain.handle('alter-table', async (event, params) => {
|
||||||
try {
|
try {
|
||||||
await connections[params.uid].alterTable(params);
|
await connections[params.uid].alterTable(params);
|
||||||
@@ -163,4 +195,24 @@ export default (connections) => {
|
|||||||
return { status: 'error', response: err.toString() };
|
return { status: 'error', response: err.toString() };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('truncate-table', async (event, params) => {
|
||||||
|
try {
|
||||||
|
await connections[params.uid].truncateTable(params);
|
||||||
|
return { status: 'success' };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('drop-table', async (event, params) => {
|
||||||
|
try {
|
||||||
|
await connections[params.uid].dropTable(params);
|
||||||
|
return { status: 'success' };
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { status: 'error', response: err.toString() };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
@@ -177,7 +177,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: field.DATA_TYPE,
|
type: field.DATA_TYPE.toUpperCase(),
|
||||||
schema: field.TABLE_SCHEMA,
|
schema: field.TABLE_SCHEMA,
|
||||||
table: field.TABLE_NAME,
|
table: field.TABLE_NAME,
|
||||||
numPrecision: field.NUMERIC_PRECISION,
|
numPrecision: field.NUMERIC_PRECISION,
|
||||||
@@ -198,6 +198,30 @@ export class MySQLClient extends AntaresCore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Object} params
|
||||||
|
* @param {String} params.schema
|
||||||
|
* @param {String} params.table
|
||||||
|
* @returns {Object} table indexes
|
||||||
|
* @memberof MySQLClient
|
||||||
|
*/
|
||||||
|
async getTableIndexes ({ schema, table }) {
|
||||||
|
const { rows } = await this.raw(`SHOW INDEXES FROM \`${table}\` FROM \`${schema}\``);
|
||||||
|
|
||||||
|
return rows.map(row => {
|
||||||
|
return {
|
||||||
|
unique: !row.Non_unique,
|
||||||
|
name: row.Key_name,
|
||||||
|
column: row.Column_name,
|
||||||
|
indexType: row.Index_type,
|
||||||
|
type: row.Key_name === 'PRIMARY' ? 'PRIMARY' : !row.Non_unique ? 'UNIQUE' : row.Index_type === 'FULLTEXT' ? 'FULLTEXT' : 'INDEX',
|
||||||
|
cardinality: row.Cardinality,
|
||||||
|
comment: row.Comment,
|
||||||
|
indexComment: row.Index_comment
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {String} params.schema
|
* @param {String} params.schema
|
||||||
@@ -213,17 +237,27 @@ export class MySQLClient extends AntaresCore {
|
|||||||
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' })
|
.where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' })
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
|
const { rows: extras } = await this
|
||||||
|
.select('*')
|
||||||
|
.schema('information_schema')
|
||||||
|
.from('REFERENTIAL_CONSTRAINTS')
|
||||||
|
.where({ CONSTRAINT_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' })
|
||||||
|
.run();
|
||||||
|
|
||||||
return rows.map(field => {
|
return rows.map(field => {
|
||||||
|
const extra = extras.find(x => x.CONSTRAINT_NAME === field.CONSTRAINT_NAME);
|
||||||
return {
|
return {
|
||||||
schema: field.TABLE_SCHEMA,
|
schema: field.TABLE_SCHEMA,
|
||||||
table: field.TABLE_NAME,
|
table: field.TABLE_NAME,
|
||||||
column: field.COLUMN_NAME,
|
field: field.COLUMN_NAME,
|
||||||
position: field.ORDINAL_POSITION,
|
position: field.ORDINAL_POSITION,
|
||||||
constraintPosition: field.POSITION_IN_UNIQUE_CONSTRAINT,
|
constraintPosition: field.POSITION_IN_UNIQUE_CONSTRAINT,
|
||||||
constraintName: field.CONSTRAINT_NAME,
|
constraintName: field.CONSTRAINT_NAME,
|
||||||
refSchema: field.REFERENCED_TABLE_SCHEMA,
|
refSchema: field.REFERENCED_TABLE_SCHEMA,
|
||||||
refTable: field.REFERENCED_TABLE_NAME,
|
refTable: field.REFERENCED_TABLE_NAME,
|
||||||
refColumn: field.REFERENCED_COLUMN_NAME
|
refField: field.REFERENCED_COLUMN_NAME,
|
||||||
|
onUpdate: extra.UPDATE_RULE,
|
||||||
|
onDelete: extra.DELETE_RULE
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -267,6 +301,48 @@ export class MySQLClient extends AntaresCore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SHOW ENGINES
|
||||||
|
*
|
||||||
|
* @returns {Array.<Object>} engines list
|
||||||
|
* @memberof MySQLClient
|
||||||
|
*/
|
||||||
|
async getEngines () {
|
||||||
|
const sql = 'SHOW ENGINES';
|
||||||
|
const results = await this.raw(sql);
|
||||||
|
|
||||||
|
return results.rows.map(row => {
|
||||||
|
return {
|
||||||
|
name: row.Engine,
|
||||||
|
support: row.Support,
|
||||||
|
comment: row.Comment,
|
||||||
|
transactions: row.Transactions,
|
||||||
|
xa: row.XA,
|
||||||
|
savepoints: row.Savepoints,
|
||||||
|
isDefault: row.Support.includes('DEFAULT')
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CREATE TABLE
|
||||||
|
*
|
||||||
|
* @returns {Array.<Object>} parameters
|
||||||
|
* @memberof MySQLClient
|
||||||
|
*/
|
||||||
|
async createTable (params) {
|
||||||
|
const {
|
||||||
|
name,
|
||||||
|
collation,
|
||||||
|
comment,
|
||||||
|
engine
|
||||||
|
} = params;
|
||||||
|
|
||||||
|
const sql = `CREATE TABLE \`${name}\` (\`${name}_ID\` INT NULL) COMMENT='${comment}', COLLATE='${collation}', ENGINE=${engine}`;
|
||||||
|
|
||||||
|
return await this.raw(sql);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ALTER TABLE
|
* ALTER TABLE
|
||||||
*
|
*
|
||||||
@@ -278,13 +354,22 @@ export class MySQLClient extends AntaresCore {
|
|||||||
table,
|
table,
|
||||||
additions,
|
additions,
|
||||||
deletions,
|
deletions,
|
||||||
changes
|
changes,
|
||||||
|
indexChanges,
|
||||||
|
foreignChanges,
|
||||||
|
options
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
let sql = `ALTER TABLE \`${table}\` `;
|
let sql = `ALTER TABLE \`${table}\` `;
|
||||||
const alterColumns = [];
|
const alterColumns = [];
|
||||||
|
|
||||||
// ADD
|
// OPTIONS
|
||||||
|
if ('comment' in options) alterColumns.push(`COMMENT='${options.comment}'`);
|
||||||
|
if ('engine' in options) alterColumns.push(`ENGINE=${options.engine}`);
|
||||||
|
if ('autoIncrement' in options) alterColumns.push(`AUTO_INCREMENT=${+options.autoIncrement}`);
|
||||||
|
if ('collation' in options) alterColumns.push(`COLLATE='${options.collation}'`);
|
||||||
|
|
||||||
|
// ADD FIELDS
|
||||||
additions.forEach(addition => {
|
additions.forEach(addition => {
|
||||||
const length = addition.numLength || addition.charLength || addition.datePrecision;
|
const length = addition.numLength || addition.charLength || addition.datePrecision;
|
||||||
|
|
||||||
@@ -301,7 +386,27 @@ export class MySQLClient extends AntaresCore {
|
|||||||
${addition.after ? `AFTER \`${addition.after}\`` : 'FIRST'}`);
|
${addition.after ? `AFTER \`${addition.after}\`` : 'FIRST'}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// CHANGE
|
// ADD INDEX
|
||||||
|
indexChanges.additions.forEach(addition => {
|
||||||
|
const fields = addition.fields.map(field => `\`${field}\``).join(',');
|
||||||
|
let type = addition.type;
|
||||||
|
|
||||||
|
if (type === 'PRIMARY')
|
||||||
|
alterColumns.push(`ADD PRIMARY KEY (${fields})`);
|
||||||
|
else {
|
||||||
|
if (type === 'UNIQUE')
|
||||||
|
type = 'UNIQUE INDEX';
|
||||||
|
|
||||||
|
alterColumns.push(`ADD ${type} \`${addition.name}\` (${fields})`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ADD FOREIGN KEYS
|
||||||
|
foreignChanges.additions.forEach(addition => {
|
||||||
|
alterColumns.push(`ADD CONSTRAINT \`${addition.constraintName}\` FOREIGN KEY (\`${addition.field}\`) REFERENCES \`${addition.refTable}\` (\`${addition.refField}\`) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// CHANGE FIELDS
|
||||||
changes.forEach(change => {
|
changes.forEach(change => {
|
||||||
const length = change.numLength || change.charLength || change.datePrecision;
|
const length = change.numLength || change.charLength || change.datePrecision;
|
||||||
|
|
||||||
@@ -318,13 +423,77 @@ export class MySQLClient extends AntaresCore {
|
|||||||
${change.after ? `AFTER \`${change.after}\`` : 'FIRST'}`);
|
${change.after ? `AFTER \`${change.after}\`` : 'FIRST'}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// DROP
|
// CHANGE INDEX
|
||||||
|
indexChanges.changes.forEach(change => {
|
||||||
|
if (change.oldType === 'PRIMARY')
|
||||||
|
alterColumns.push('DROP PRIMARY KEY');
|
||||||
|
else
|
||||||
|
alterColumns.push(`DROP INDEX \`${change.oldName}\``);
|
||||||
|
|
||||||
|
const fields = change.fields.map(field => `\`${field}\``).join(',');
|
||||||
|
let type = change.type;
|
||||||
|
|
||||||
|
if (type === 'PRIMARY')
|
||||||
|
alterColumns.push(`ADD PRIMARY KEY (${fields})`);
|
||||||
|
else {
|
||||||
|
if (type === 'UNIQUE')
|
||||||
|
type = 'UNIQUE INDEX';
|
||||||
|
|
||||||
|
alterColumns.push(`ADD ${type} \`${change.name}\` (${fields})`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// CHANGE FOREIGN KEYS
|
||||||
|
foreignChanges.changes.forEach(change => {
|
||||||
|
alterColumns.push(`DROP FOREIGN KEY \`${change.oldName}\``);
|
||||||
|
alterColumns.push(`ADD CONSTRAINT \`${change.constraintName}\` FOREIGN KEY (\`${change.field}\`) REFERENCES \`${change.refTable}\` (\`${change.refField}\`) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// DROP FIELDS
|
||||||
deletions.forEach(deletion => {
|
deletions.forEach(deletion => {
|
||||||
alterColumns.push(`DROP COLUMN \`${deletion.name}\``);
|
alterColumns.push(`DROP COLUMN \`${deletion.name}\``);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// DROP INDEX
|
||||||
|
indexChanges.deletions.forEach(deletion => {
|
||||||
|
if (deletion.type === 'PRIMARY')
|
||||||
|
alterColumns.push('DROP PRIMARY KEY');
|
||||||
|
else
|
||||||
|
alterColumns.push(`DROP INDEX \`${deletion.name}\``);
|
||||||
|
});
|
||||||
|
|
||||||
|
// DROP FOREIGN KEYS
|
||||||
|
foreignChanges.deletions.forEach(deletion => {
|
||||||
|
alterColumns.push(`DROP FOREIGN KEY \`${deletion.constraintName}\``);
|
||||||
|
});
|
||||||
|
|
||||||
sql += alterColumns.join(', ');
|
sql += alterColumns.join(', ');
|
||||||
|
|
||||||
|
// RENAME
|
||||||
|
if (options.name) sql += `; RENAME TABLE \`${table}\` TO \`${options.name}\``;
|
||||||
|
|
||||||
|
return await this.raw(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TRUNCATE TABLE
|
||||||
|
*
|
||||||
|
* @returns {Array.<Object>} parameters
|
||||||
|
* @memberof MySQLClient
|
||||||
|
*/
|
||||||
|
async truncateTable (params) {
|
||||||
|
const sql = `TRUNCATE TABLE \`${params.table}\``;
|
||||||
|
return await this.raw(sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DROP TABLE
|
||||||
|
*
|
||||||
|
* @returns {Array.<Object>} parameters
|
||||||
|
* @memberof MySQLClient
|
||||||
|
*/
|
||||||
|
async dropTable (params) {
|
||||||
|
const sql = `DROP TABLE \`${params.table}\``;
|
||||||
return await this.raw(sql);
|
return await this.raw(sql);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -17,6 +17,7 @@
|
|||||||
<TheNotificationsBoard />
|
<TheNotificationsBoard />
|
||||||
<ModalNewConnection v-if="isNewConnModal" />
|
<ModalNewConnection v-if="isNewConnModal" />
|
||||||
<ModalSettings v-if="isSettingModal" />
|
<ModalSettings v-if="isSettingModal" />
|
||||||
|
<ModalDiscardChanges v-if="isUnsavedDiscardModal" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -35,7 +36,8 @@ export default {
|
|||||||
TheAppWelcome: () => import(/* webpackChunkName: "TheAppWelcome" */'@/components/TheAppWelcome'),
|
TheAppWelcome: () => import(/* webpackChunkName: "TheAppWelcome" */'@/components/TheAppWelcome'),
|
||||||
Workspace: () => import(/* webpackChunkName: "Workspace" */'@/components/Workspace'),
|
Workspace: () => import(/* webpackChunkName: "Workspace" */'@/components/Workspace'),
|
||||||
ModalNewConnection: () => import(/* webpackChunkName: "ModalNewConnection" */'@/components/ModalNewConnection'),
|
ModalNewConnection: () => import(/* webpackChunkName: "ModalNewConnection" */'@/components/ModalNewConnection'),
|
||||||
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings')
|
ModalSettings: () => import(/* webpackChunkName: "ModalSettings" */'@/components/ModalSettings'),
|
||||||
|
ModalDiscardChanges: () => import(/* webpackChunkName: "ModalDiscardChanges" */'@/components/ModalDiscardChanges')
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {};
|
return {};
|
||||||
@@ -46,7 +48,8 @@ export default {
|
|||||||
isNewConnModal: 'application/isNewModal',
|
isNewConnModal: 'application/isNewModal',
|
||||||
isEditModal: 'application/isEditModal',
|
isEditModal: 'application/isEditModal',
|
||||||
isSettingModal: 'application/isSettingModal',
|
isSettingModal: 'application/isSettingModal',
|
||||||
connections: 'connections/getConnections'
|
connections: 'connections/getConnections',
|
||||||
|
isUnsavedDiscardModal: 'workspaces/isUnsavedDiscardModal'
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
|
@@ -86,9 +86,8 @@ export default {
|
|||||||
|
|
||||||
.context-container {
|
.context-container {
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
max-width: 150px;
|
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
box-shadow: 0 0 1px 0 #000;
|
box-shadow: 0 0 2px 0 #000;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: #1d1d1d;
|
background: #1d1d1d;
|
||||||
border-radius: 0.1rem;
|
border-radius: 0.1rem;
|
||||||
@@ -103,9 +102,28 @@ export default {
|
|||||||
padding: 0.1rem 0.3rem;
|
padding: 0.1rem 0.3rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.context-submenu {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
position: absolute;
|
||||||
|
left: 100%;
|
||||||
|
top: 0;
|
||||||
|
background: #1d1d1d;
|
||||||
|
box-shadow: 0 0 2px 0 #000;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: $primary-color;
|
background: $primary-color;
|
||||||
|
|
||||||
|
.context-submenu {
|
||||||
|
display: block;
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<select
|
<select
|
||||||
ref="editField"
|
ref="editField"
|
||||||
class="px-1"
|
class="form-select pl-1 pr-4"
|
||||||
|
:class="{'small-select': size === 'small'}"
|
||||||
@change="onChange"
|
@change="onChange"
|
||||||
@blur="$emit('blur')"
|
@blur="$emit('blur')"
|
||||||
>
|
>
|
||||||
|
<option v-if="!isValidDefault" :value="value">
|
||||||
|
{{ value }} - {{ $t('message.invalidDefault') }}
|
||||||
|
</option>
|
||||||
<option
|
<option
|
||||||
v-for="row in foreignList"
|
v-for="row in foreignList"
|
||||||
:key="row.foreignColumn"
|
:key="row.foreignColumn"
|
||||||
@@ -30,7 +34,11 @@ export default {
|
|||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
value: [String, Number],
|
value: [String, Number],
|
||||||
keyUsage: Object
|
keyUsage: Object,
|
||||||
|
size: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -40,7 +48,10 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
selectedWorkspace: 'workspaces/getSelected'
|
selectedWorkspace: 'workspaces/getSelected'
|
||||||
})
|
}),
|
||||||
|
isValidDefault () {
|
||||||
|
return this.foreignList.some(foreign => foreign.foreignColumn.toString() === this.value.toString());
|
||||||
|
}
|
||||||
},
|
},
|
||||||
async created () {
|
async created () {
|
||||||
let firstTextField;
|
let firstTextField;
|
||||||
@@ -64,7 +75,7 @@ export default {
|
|||||||
try { // Foregn list
|
try { // Foregn list
|
||||||
const { status, response } = await Tables.getForeignList({
|
const { status, response } = await Tables.getForeignList({
|
||||||
...params,
|
...params,
|
||||||
column: this.keyUsage.refColumn,
|
column: this.keyUsage.refField,
|
||||||
description: firstTextField
|
description: firstTextField
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-9">
|
<div class="col-9">
|
||||||
<input
|
<input
|
||||||
|
ref="firstInput"
|
||||||
v-model="credentials.user"
|
v-model="credentials.user"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -63,6 +64,11 @@ export default {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
created () {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$refs.firstInput.focus();
|
||||||
|
}, 20);
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
closeModal () {
|
closeModal () {
|
||||||
this.$emit('close-asking');
|
this.$emit('close-asking');
|
||||||
|
57
src/renderer/components/ModalDiscardChanges.vue
Normal file
57
src/renderer/components/ModalDiscardChanges.vue
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<template>
|
||||||
|
<ConfirmModal
|
||||||
|
:confirm-text="$t('word.discard')"
|
||||||
|
:cancel-text="$t('word.stay')"
|
||||||
|
@confirm="discardUnsavedChanges"
|
||||||
|
@hide="closeUnsavedChangesModal"
|
||||||
|
>
|
||||||
|
<template slot="header">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-content-save-alert mr-1" /> {{ $t('message.unsavedChanges') }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div slot="body">
|
||||||
|
<div>
|
||||||
|
{{ $t('message.discardUnsavedChanges') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ConfirmModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapActions } from 'vuex';
|
||||||
|
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ModalDiscardChanges',
|
||||||
|
components: {
|
||||||
|
ConfirmModal
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
window.addEventListener('keydown', this.onKey);
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
window.removeEventListener('keydown', this.onKey);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions({
|
||||||
|
discardUnsavedChanges: 'workspaces/discardUnsavedChanges',
|
||||||
|
closeUnsavedChangesModal: 'workspaces/closeUnsavedChangesModal'
|
||||||
|
}),
|
||||||
|
closeModal () {
|
||||||
|
this.$emit('close');
|
||||||
|
},
|
||||||
|
onKey (e) {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (e.key === 'Escape')
|
||||||
|
this.closeModal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.modal-container {
|
||||||
|
max-width: 360px;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -20,6 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-8 col-sm-12">
|
<div class="col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
ref="firstInput"
|
||||||
v-model="localConnection.name"
|
v-model="localConnection.name"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -172,6 +173,10 @@ export default {
|
|||||||
created () {
|
created () {
|
||||||
this.localConnection = Object.assign({}, this.connection);
|
this.localConnection = Object.assign({}, this.connection);
|
||||||
window.addEventListener('keydown', this.onKey);
|
window.addEventListener('keydown', this.onKey);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$refs.firstInput.focus();
|
||||||
|
}, 20);
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
window.removeEventListener('keydown', this.onKey);
|
window.removeEventListener('keydown', this.onKey);
|
||||||
|
@@ -33,7 +33,11 @@
|
|||||||
<label class="form-label">{{ $t('word.collation') }}:</label>
|
<label class="form-label">{{ $t('word.collation') }}:</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-9">
|
<div class="col-9">
|
||||||
<select v-model="database.collation" class="form-select">
|
<select
|
||||||
|
ref="firstInput"
|
||||||
|
v-model="database.collation"
|
||||||
|
class="form-select"
|
||||||
|
>
|
||||||
<option
|
<option
|
||||||
v-for="collation in collations"
|
v-for="collation in collations"
|
||||||
:key="collation.id"
|
:key="collation.id"
|
||||||
@@ -114,6 +118,10 @@ export default {
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('keydown', this.onKey);
|
window.addEventListener('keydown', this.onKey);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$refs.firstInput.focus();
|
||||||
|
}, 20);
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
window.removeEventListener('keydown', this.onKey);
|
window.removeEventListener('keydown', this.onKey);
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-8 col-sm-12">
|
<div class="col-8 col-sm-12">
|
||||||
<input
|
<input
|
||||||
|
ref="firstInput"
|
||||||
v-model="connection.name"
|
v-model="connection.name"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -182,6 +183,10 @@ export default {
|
|||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
window.addEventListener('keydown', this.onKey);
|
window.addEventListener('keydown', this.onKey);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$refs.firstInput.focus();
|
||||||
|
}, 20);
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
window.removeEventListener('keydown', this.onKey);
|
window.removeEventListener('keydown', this.onKey);
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-9">
|
<div class="col-9">
|
||||||
<input
|
<input
|
||||||
|
ref="firstInput"
|
||||||
v-model="database.name"
|
v-model="database.name"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
type="text"
|
type="text"
|
||||||
@@ -89,6 +90,9 @@ export default {
|
|||||||
created () {
|
created () {
|
||||||
this.database = { ...this.database, collation: this.defaultCollation };
|
this.database = { ...this.database, collation: this.defaultCollation };
|
||||||
window.addEventListener('keydown', this.onKey);
|
window.addEventListener('keydown', this.onKey);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$refs.firstInput.focus();
|
||||||
|
}, 20);
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
window.removeEventListener('keydown', this.onKey);
|
window.removeEventListener('keydown', this.onKey);
|
||||||
|
127
src/renderer/components/ModalNewTable.vue
Normal file
127
src/renderer/components/ModalNewTable.vue
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<template>
|
||||||
|
<ConfirmModal
|
||||||
|
:confirm-text="$t('word.confirm')"
|
||||||
|
size="400"
|
||||||
|
@confirm="confirmOptionsChange"
|
||||||
|
@hide="$emit('close')"
|
||||||
|
>
|
||||||
|
<template :slot="'header'">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-table-plus mr-1" /> {{ $t('message.createNewTable') }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div :slot="'body'">
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-4">
|
||||||
|
{{ $t('word.name') }}
|
||||||
|
</label>
|
||||||
|
<div class="column">
|
||||||
|
<input
|
||||||
|
ref="firstInput"
|
||||||
|
v-model="localOptions.name"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-4">
|
||||||
|
{{ $t('word.comment') }}
|
||||||
|
</label>
|
||||||
|
<div class="column">
|
||||||
|
<input
|
||||||
|
v-model="localOptions.comment"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-4">
|
||||||
|
{{ $t('word.collation') }}
|
||||||
|
</label>
|
||||||
|
<div class="column">
|
||||||
|
<select v-model="localOptions.collation" class="form-select">
|
||||||
|
<option
|
||||||
|
v-for="collation in workspace.collations"
|
||||||
|
:key="collation.id"
|
||||||
|
:value="collation.collation"
|
||||||
|
>
|
||||||
|
{{ collation.collation }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-4">
|
||||||
|
{{ $t('word.engine') }}
|
||||||
|
</label>
|
||||||
|
<div class="column">
|
||||||
|
<select v-model="localOptions.engine" class="form-select">
|
||||||
|
<option
|
||||||
|
v-for="engine in workspace.engines"
|
||||||
|
:key="engine.name"
|
||||||
|
:value="engine.name"
|
||||||
|
>
|
||||||
|
{{ engine.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</ConfirmModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ModalNewTable',
|
||||||
|
components: {
|
||||||
|
ConfirmModal
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
table: String,
|
||||||
|
workspace: Object
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
localOptions: {
|
||||||
|
name: '',
|
||||||
|
comment: '',
|
||||||
|
collation: '',
|
||||||
|
engine: ''
|
||||||
|
},
|
||||||
|
isOptionsChanging: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
selectedWorkspace: 'workspaces/getSelected',
|
||||||
|
getDatabaseVariable: 'workspaces/getDatabaseVariable'
|
||||||
|
}),
|
||||||
|
defaultCollation () {
|
||||||
|
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || '';
|
||||||
|
},
|
||||||
|
defaultEngine () {
|
||||||
|
return this.workspace.engines.find(engine => engine.isDefault).name;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.localOptions.collation = this.defaultCollation;
|
||||||
|
this.localOptions.engine = this.defaultEngine;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$refs.firstInput.focus();
|
||||||
|
}, 20);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
confirmOptionsChange () {
|
||||||
|
this.$emit('open-create-table-editor', this.localOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@@ -25,6 +25,7 @@
|
|||||||
<div class="input-group col-8 col-sm-12">
|
<div class="input-group col-8 col-sm-12">
|
||||||
<ForeignKeySelect
|
<ForeignKeySelect
|
||||||
v-if="foreignKeys.includes(field.name)"
|
v-if="foreignKeys.includes(field.name)"
|
||||||
|
ref="formInput"
|
||||||
class="form-select"
|
class="form-select"
|
||||||
:value.sync="localRow[field.name]"
|
:value.sync="localRow[field.name]"
|
||||||
:key-usage="getKeyUsage(field.name)"
|
:key-usage="getKeyUsage(field.name)"
|
||||||
@@ -32,6 +33,7 @@
|
|||||||
/>
|
/>
|
||||||
<input
|
<input
|
||||||
v-else-if="inputProps(field).mask"
|
v-else-if="inputProps(field).mask"
|
||||||
|
ref="formInput"
|
||||||
v-model="localRow[field.name]"
|
v-model="localRow[field.name]"
|
||||||
v-mask="inputProps(field).mask"
|
v-mask="inputProps(field).mask"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
@@ -41,6 +43,7 @@
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-else-if="inputProps(field).type === 'file'"
|
v-else-if="inputProps(field).type === 'file'"
|
||||||
|
ref="formInput"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
type="file"
|
type="file"
|
||||||
:disabled="fieldsToExclude.includes(field.name)"
|
:disabled="fieldsToExclude.includes(field.name)"
|
||||||
@@ -49,13 +52,14 @@
|
|||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-else
|
v-else
|
||||||
|
ref="formInput"
|
||||||
v-model="localRow[field.name]"
|
v-model="localRow[field.name]"
|
||||||
class="form-input"
|
class="form-input"
|
||||||
:type="inputProps(field).type"
|
:type="inputProps(field).type"
|
||||||
:disabled="fieldsToExclude.includes(field.name)"
|
:disabled="fieldsToExclude.includes(field.name)"
|
||||||
:tabindex="key+1"
|
:tabindex="key+1"
|
||||||
>
|
>
|
||||||
<span class="input-group-addon" :class="`type-${field.type}`">
|
<span class="input-group-addon" :class="`type-${field.type.toLowerCase()}`">
|
||||||
{{ field.type }} {{ fieldLength(field) | wrapNumber }}
|
{{ field.type }} {{ fieldLength(field) | wrapNumber }}
|
||||||
</span>
|
</span>
|
||||||
<label class="form-checkbox ml-3" :title="$t('word.insert')">
|
<label class="form-checkbox ml-3" :title="$t('word.insert')">
|
||||||
@@ -146,7 +150,7 @@ export default {
|
|||||||
return this.getWorkspace(this.selectedWorkspace);
|
return this.getWorkspace(this.selectedWorkspace);
|
||||||
},
|
},
|
||||||
foreignKeys () {
|
foreignKeys () {
|
||||||
return this.keyUsage.map(key => key.column);
|
return this.keyUsage.map(key => key.field);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -192,6 +196,12 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.localRow = { ...rowObj };
|
this.localRow = { ...rowObj };
|
||||||
|
|
||||||
|
// Auto focus
|
||||||
|
setTimeout(() => {
|
||||||
|
const firstSelectableInput = this.$refs.formInput.find(input => !input.disabled);
|
||||||
|
firstSelectableInput.focus();
|
||||||
|
}, 20);
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
window.removeEventListener('keydown', this.onKey);
|
window.removeEventListener('keydown', this.onKey);
|
||||||
@@ -296,7 +306,7 @@ export default {
|
|||||||
this.localRow[field] = files[0].path;
|
this.localRow[field] = files[0].path;
|
||||||
},
|
},
|
||||||
getKeyUsage (keyName) {
|
getKeyUsage (keyName) {
|
||||||
return this.keyUsage.find(key => key.column === keyName);
|
return this.keyUsage.find(key => key.field === keyName);
|
||||||
},
|
},
|
||||||
onKey (e) {
|
onKey (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@@ -111,7 +111,8 @@
|
|||||||
<h4>{{ appName }}</h4>
|
<h4>{{ appName }}</h4>
|
||||||
<p>
|
<p>
|
||||||
{{ $t('word.version') }}: {{ appVersion }}<br>
|
{{ $t('word.version') }}: {{ appVersion }}<br>
|
||||||
<a class="c-hand" @click="openOutside('https://github.com/EStarium/antares')">GitHub</a><br>
|
<a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares')">GitHub</a><br>
|
||||||
|
<small>{{ $t('word.author') }}: <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">Fabio Di Stasio</a></small><br>
|
||||||
<small>{{ $t('message.madeWithJS') }}</small>
|
<small>{{ $t('message.madeWithJS') }}</small>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -14,7 +14,8 @@ monaco.languages.registerCompletionItemProvider('sql', completionItemProvider(mo
|
|||||||
export default {
|
export default {
|
||||||
name: 'QueryEditor',
|
name: 'QueryEditor',
|
||||||
props: {
|
props: {
|
||||||
value: String
|
value: String,
|
||||||
|
autoFocus: { type: Boolean, default: false }
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -40,6 +41,12 @@ export default {
|
|||||||
const content = this.editor.getValue();
|
const content = this.editor.getValue();
|
||||||
this.$emit('update:value', content);
|
this.$emit('update:value', content);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (this.autoFocus) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.editor.focus();
|
||||||
|
}, 20);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
beforeDestroy () {
|
beforeDestroy () {
|
||||||
this.editor && this.editor.dispose();
|
this.editor && this.editor.dispose();
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
<i class="mdi mdi-18px mdi-coffee mr-1" />
|
<i class="mdi mdi-18px mdi-coffee mr-1" />
|
||||||
<small>{{ $t('word.donate') }}</small>
|
<small>{{ $t('word.donate') }}</small>
|
||||||
</li>
|
</li>
|
||||||
<li class="footer-element footer-link" @click="openOutside('https://github.com/EStarium/antares/issues')">
|
<li class="footer-element footer-link" @click="openOutside('https://github.com/Fabio286/antares/issues')">
|
||||||
<i class="mdi mdi-18px mdi-bug" />
|
<i class="mdi mdi-18px mdi-bug" />
|
||||||
</li>
|
</li>
|
||||||
<li class="footer-element footer-link" @click="showSettingModal('about')">
|
<li class="footer-element footer-link" @click="showSettingModal('about')">
|
||||||
|
@@ -67,6 +67,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<WorkspacePropsTab
|
<WorkspacePropsTab
|
||||||
v-show="selectedTab === 'prop'"
|
v-show="selectedTab === 'prop'"
|
||||||
|
:is-selected="selectedTab === 'prop'"
|
||||||
:connection="connection"
|
:connection="connection"
|
||||||
:table="workspace.breadcrumbs.table"
|
:table="workspace.breadcrumbs.table"
|
||||||
/>
|
/>
|
||||||
@@ -122,7 +123,13 @@ export default {
|
|||||||
return this.selectedWorkspace === this.connection.uid;
|
return this.selectedWorkspace === this.connection.uid;
|
||||||
},
|
},
|
||||||
selectedTab () {
|
selectedTab () {
|
||||||
return this.queryTabs.find(tab => tab.uid === this.workspace.selected_tab) || ['data', 'prop'].includes(this.workspace.selected_tab) ? this.workspace.selected_tab : this.queryTabs[0].uid;
|
if (this.workspace.breadcrumbs.table === null && ['data', 'prop'].includes(this.workspace.selected_tab))
|
||||||
|
return this.queryTabs[0].uid;
|
||||||
|
|
||||||
|
return this.queryTabs.find(tab => tab.uid === this.workspace.selected_tab) ||
|
||||||
|
['data', 'prop'].includes(this.workspace.selected_tab)
|
||||||
|
? this.workspace.selected_tab
|
||||||
|
: this.queryTabs[0].uid;
|
||||||
},
|
},
|
||||||
queryTabs () {
|
queryTabs () {
|
||||||
return this.workspace.tabs.filter(tab => tab.type === 'query');
|
return this.workspace.tabs.filter(tab => tab.type === 'query');
|
||||||
|
@@ -39,6 +39,7 @@
|
|||||||
:database="db"
|
:database="db"
|
||||||
:connection="connection"
|
:connection="connection"
|
||||||
@show-database-context="openDatabaseContext"
|
@show-database-context="openDatabaseContext"
|
||||||
|
@show-table-context="openTableContext"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,11 +48,25 @@
|
|||||||
@close="hideNewDBModal"
|
@close="hideNewDBModal"
|
||||||
@reload="refresh"
|
@reload="refresh"
|
||||||
/>
|
/>
|
||||||
|
<ModalNewTable
|
||||||
|
v-if="isNewTableModal"
|
||||||
|
:workspace="workspace"
|
||||||
|
@close="hideCreateTableModal"
|
||||||
|
@open-create-table-editor="openCreateTableEditor"
|
||||||
|
/>
|
||||||
<DatabaseContext
|
<DatabaseContext
|
||||||
v-if="isDatabaseContext"
|
v-if="isDatabaseContext"
|
||||||
:selected-database="selectedDatabase"
|
:selected-database="selectedDatabase"
|
||||||
:context-event="databaseContextEvent"
|
:context-event="databaseContextEvent"
|
||||||
@close-context="closeDatabaseContext"
|
@close-context="closeDatabaseContext"
|
||||||
|
@show-create-table-modal="showCreateTableModal"
|
||||||
|
@reload="refresh"
|
||||||
|
/>
|
||||||
|
<TableContext
|
||||||
|
v-if="isTableContext"
|
||||||
|
:selected-table="selectedTable"
|
||||||
|
:context-event="tableContextEvent"
|
||||||
|
@close-context="closeTableContext"
|
||||||
@reload="refresh"
|
@reload="refresh"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,10 +75,13 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters, mapActions } from 'vuex';
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import Tables from '@/ipc-api/Tables';
|
||||||
import WorkspaceConnectPanel from '@/components/WorkspaceConnectPanel';
|
import WorkspaceConnectPanel from '@/components/WorkspaceConnectPanel';
|
||||||
import WorkspaceExploreBarDatabase from '@/components/WorkspaceExploreBarDatabase';
|
import WorkspaceExploreBarDatabase from '@/components/WorkspaceExploreBarDatabase';
|
||||||
import DatabaseContext from '@/components/WorkspaceExploreBarDatabaseContext';
|
import DatabaseContext from '@/components/WorkspaceExploreBarDatabaseContext';
|
||||||
|
import TableContext from '@/components/WorkspaceExploreBarTableContext';
|
||||||
import ModalNewDatabase from '@/components/ModalNewDatabase';
|
import ModalNewDatabase from '@/components/ModalNewDatabase';
|
||||||
|
import ModalNewTable from '@/components/ModalNewTable';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WorkspaceExploreBar',
|
name: 'WorkspaceExploreBar',
|
||||||
@@ -71,7 +89,9 @@ export default {
|
|||||||
WorkspaceConnectPanel,
|
WorkspaceConnectPanel,
|
||||||
WorkspaceExploreBarDatabase,
|
WorkspaceExploreBarDatabase,
|
||||||
DatabaseContext,
|
DatabaseContext,
|
||||||
ModalNewDatabase
|
TableContext,
|
||||||
|
ModalNewDatabase,
|
||||||
|
ModalNewTable
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
connection: Object,
|
connection: Object,
|
||||||
@@ -81,6 +101,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
isRefreshing: false,
|
isRefreshing: false,
|
||||||
isNewDBModal: false,
|
isNewDBModal: false,
|
||||||
|
isNewTableModal: false,
|
||||||
localWidth: null,
|
localWidth: null,
|
||||||
isDatabaseContext: false,
|
isDatabaseContext: false,
|
||||||
isTableContext: false,
|
isTableContext: false,
|
||||||
@@ -128,6 +149,9 @@ export default {
|
|||||||
...mapActions({
|
...mapActions({
|
||||||
disconnectWorkspace: 'workspaces/removeConnected',
|
disconnectWorkspace: 'workspaces/removeConnected',
|
||||||
refreshStructure: 'workspaces/refreshStructure',
|
refreshStructure: 'workspaces/refreshStructure',
|
||||||
|
changeBreadcrumbs: 'workspaces/changeBreadcrumbs',
|
||||||
|
selectTab: 'workspaces/selectTab',
|
||||||
|
addNotification: 'notifications/addNotification',
|
||||||
changeExplorebarSize: 'settings/changeExplorebarSize'
|
changeExplorebarSize: 'settings/changeExplorebarSize'
|
||||||
}),
|
}),
|
||||||
async refresh () {
|
async refresh () {
|
||||||
@@ -153,15 +177,44 @@ export default {
|
|||||||
hideNewDBModal () {
|
hideNewDBModal () {
|
||||||
this.isNewDBModal = false;
|
this.isNewDBModal = false;
|
||||||
},
|
},
|
||||||
|
showCreateTableModal () {
|
||||||
|
this.closeDatabaseContext();
|
||||||
|
this.isNewTableModal = true;
|
||||||
|
},
|
||||||
|
hideCreateTableModal () {
|
||||||
|
this.isNewTableModal = false;
|
||||||
|
},
|
||||||
|
async openCreateTableEditor (payload) {
|
||||||
|
const params = {
|
||||||
|
uid: this.connection.uid,
|
||||||
|
...payload
|
||||||
|
};
|
||||||
|
|
||||||
|
const { status, response } = await Tables.createTable(params);
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
await this.refresh();
|
||||||
|
this.changeBreadcrumbs({ schema: this.selectedDatabase, table: payload.name });
|
||||||
|
this.selectTab({ uid: this.workspace.uid, tab: 'prop' });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
},
|
||||||
openDatabaseContext (payload) {
|
openDatabaseContext (payload) {
|
||||||
this.isTableContext = false;
|
|
||||||
this.selectedDatabase = payload.database;
|
this.selectedDatabase = payload.database;
|
||||||
this.databaseContextEvent = payload.event;
|
this.databaseContextEvent = payload.event;
|
||||||
this.isDatabaseContext = true;
|
this.isDatabaseContext = true;
|
||||||
},
|
},
|
||||||
closeDatabaseContext () {
|
closeDatabaseContext () {
|
||||||
this.isDatabaseContext = false;
|
this.isDatabaseContext = false;
|
||||||
this.selectedDatabase = '';
|
},
|
||||||
|
openTableContext (payload) {
|
||||||
|
this.selectedTable = payload.table;
|
||||||
|
this.tableContextEvent = payload.event;
|
||||||
|
this.isTableContext = true;
|
||||||
|
},
|
||||||
|
closeTableContext () {
|
||||||
|
this.isTableContext = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -18,7 +18,7 @@
|
|||||||
:key="table.name"
|
:key="table.name"
|
||||||
class="menu-item"
|
class="menu-item"
|
||||||
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.table === table.name}"
|
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.table === table.name}"
|
||||||
@click="changeBreadcrumbs({schema: database.name, table: table.name})"
|
@click="setBreadcrumbs({schema: database.name, table: table.name})"
|
||||||
@contextmenu.prevent="showTableContext($event, table.name)"
|
@contextmenu.prevent="showTableContext($event, table.name)"
|
||||||
>
|
>
|
||||||
<a class="table-name">
|
<a class="table-name">
|
||||||
@@ -70,12 +70,16 @@ export default {
|
|||||||
showDatabaseContext (event, database) {
|
showDatabaseContext (event, database) {
|
||||||
this.$emit('show-database-context', { event, database });
|
this.$emit('show-database-context', { event, database });
|
||||||
},
|
},
|
||||||
showTableContext (table) {
|
showTableContext (event, table) {
|
||||||
this.$emit('show-table-context', table);
|
this.$emit('show-table-context', { event, table });
|
||||||
},
|
},
|
||||||
piePercentage (val) {
|
piePercentage (val) {
|
||||||
const perc = val / this.maxSize * 100;
|
const perc = val / this.maxSize * 100;
|
||||||
return { background: `conic-gradient(lime ${perc}%, white 0)` };
|
return { background: `conic-gradient(lime ${perc}%, white 0)` };
|
||||||
|
},
|
||||||
|
setBreadcrumbs (payload) {
|
||||||
|
if (this.breadcrumbs.schema === payload.schema && this.breadcrumbs.table === payload.table) return;
|
||||||
|
this.changeBreadcrumbs(payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -3,15 +3,20 @@
|
|||||||
:context-event="contextEvent"
|
:context-event="contextEvent"
|
||||||
@close-context="closeContext"
|
@close-context="closeContext"
|
||||||
>
|
>
|
||||||
<!-- <div class="context-element">
|
<div class="context-element">
|
||||||
<span class="d-flex"><i class="mdi mdi-18px mdi-plus text-light pr-1" /> {{ $t('word.add') }}</span>
|
<span class="d-flex"><i class="mdi mdi-18px mdi-plus text-light pr-1" /> {{ $t('word.add') }}</span>
|
||||||
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
|
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
|
||||||
</div> -->
|
<div class="context-submenu">
|
||||||
|
<div class="context-element" @click="showCreateTableModal">
|
||||||
|
<span class="d-flex"><i class="mdi mdi-18px mdi-table text-light pr-1" /> {{ $t('word.table') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="context-element" @click="showEditModal">
|
<div class="context-element" @click="showEditModal">
|
||||||
<span class="d-flex"><i class="mdi mdi-18px mdi-pencil text-light pr-1" /> {{ $t('word.edit') }}</span>
|
<span class="d-flex"><i class="mdi mdi-18px mdi-database-edit text-light pr-1" /> {{ $t('word.edit') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="context-element" @click="showDeleteModal">
|
<div class="context-element" @click="showDeleteModal">
|
||||||
<span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('word.delete') }}</span>
|
<span class="d-flex"><i class="mdi mdi-18px mdi-database-remove text-light pr-1" /> {{ $t('word.delete') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ConfirmModal
|
<ConfirmModal
|
||||||
@@ -64,15 +69,21 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
selectedWorkspace: 'workspaces/getSelected'
|
selectedWorkspace: 'workspaces/getSelected',
|
||||||
})
|
getWorkspace: 'workspaces/getWorkspace'
|
||||||
|
}),
|
||||||
|
workspace () {
|
||||||
|
return this.getWorkspace(this.selectedWorkspace);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
deleteConnection: 'connections/deleteConnection',
|
addNotification: 'notifications/addNotification',
|
||||||
showEditModal: 'application/showEditConnModal',
|
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||||
addNotification: 'notifications/addNotification'
|
|
||||||
}),
|
}),
|
||||||
|
showCreateTableModal () {
|
||||||
|
this.$emit('show-create-table-modal');
|
||||||
|
},
|
||||||
showDeleteModal () {
|
showDeleteModal () {
|
||||||
this.isDeleteModal = true;
|
this.isDeleteModal = true;
|
||||||
},
|
},
|
||||||
@@ -97,6 +108,9 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (status === 'success') {
|
if (status === 'success') {
|
||||||
|
if (this.selectedDatabase === this.workspace.breadcrumbs.schema)
|
||||||
|
this.changeBreadcrumbs({ schema: null });
|
||||||
|
|
||||||
this.closeContext();
|
this.closeContext();
|
||||||
this.$emit('reload');
|
this.$emit('reload');
|
||||||
}
|
}
|
||||||
|
146
src/renderer/components/WorkspaceExploreBarTableContext.vue
Normal file
146
src/renderer/components/WorkspaceExploreBarTableContext.vue
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
<template>
|
||||||
|
<BaseContextMenu
|
||||||
|
:context-event="contextEvent"
|
||||||
|
@close-context="closeContext"
|
||||||
|
>
|
||||||
|
<div class="context-element" @click="showEmptyModal">
|
||||||
|
<span class="d-flex"><i class="mdi mdi-18px mdi-table-off text-light pr-1" /> {{ $t('message.emptyTable') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="context-element" @click="showDeleteModal">
|
||||||
|
<span class="d-flex"><i class="mdi mdi-18px mdi-table-remove text-light pr-1" /> {{ $t('word.delete') }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
v-if="isEmptyModal"
|
||||||
|
@confirm="emptyTable"
|
||||||
|
@hide="hideEmptyModal"
|
||||||
|
>
|
||||||
|
<template slot="header">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-table-off mr-1" /> {{ $t('message.emptyTable') }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div slot="body">
|
||||||
|
<div class="mb-2">
|
||||||
|
{{ $t('message.emptyCorfirm') }} "<b>{{ selectedTable }}</b>"?
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ConfirmModal>
|
||||||
|
<ConfirmModal
|
||||||
|
v-if="isDeleteModal"
|
||||||
|
@confirm="deleteTable"
|
||||||
|
@hide="hideDeleteModal"
|
||||||
|
>
|
||||||
|
<template slot="header">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-table-remove mr-1" /> {{ $t('message.deleteTable') }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div slot="body">
|
||||||
|
<div class="mb-2">
|
||||||
|
{{ $t('message.deleteCorfirm') }} "<b>{{ selectedTable }}</b>"?
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ConfirmModal>
|
||||||
|
</BaseContextMenu>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters, mapActions } from 'vuex';
|
||||||
|
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||||
|
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||||
|
import Tables from '@/ipc-api/Tables';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WorkspaceExploreBarTableContext',
|
||||||
|
components: {
|
||||||
|
BaseContextMenu,
|
||||||
|
ConfirmModal
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
contextEvent: MouseEvent,
|
||||||
|
selectedTable: String
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isDeleteModal: false,
|
||||||
|
isEmptyModal: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
selectedWorkspace: 'workspaces/getSelected',
|
||||||
|
getWorkspace: 'workspaces/getWorkspace'
|
||||||
|
}),
|
||||||
|
workspace () {
|
||||||
|
return this.getWorkspace(this.selectedWorkspace);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions({
|
||||||
|
addNotification: 'notifications/addNotification',
|
||||||
|
changeBreadcrumbs: 'workspaces/changeBreadcrumbs'
|
||||||
|
}),
|
||||||
|
showCreateTableModal () {
|
||||||
|
this.$emit('show-create-table-modal');
|
||||||
|
},
|
||||||
|
showDeleteModal () {
|
||||||
|
this.isDeleteModal = true;
|
||||||
|
},
|
||||||
|
hideDeleteModal () {
|
||||||
|
this.isDeleteModal = false;
|
||||||
|
},
|
||||||
|
showEmptyModal () {
|
||||||
|
this.isEmptyModal = true;
|
||||||
|
},
|
||||||
|
hideEmptyModal () {
|
||||||
|
this.isEmptyModal = false;
|
||||||
|
},
|
||||||
|
closeContext () {
|
||||||
|
this.$emit('close-context');
|
||||||
|
},
|
||||||
|
async emptyTable () {
|
||||||
|
try {
|
||||||
|
const { status, response } = await Tables.truncateTable({
|
||||||
|
uid: this.selectedWorkspace,
|
||||||
|
table: this.selectedTable
|
||||||
|
});
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
if (this.selectedTable === this.workspace.breadcrumbs.table)
|
||||||
|
this.changeBreadcrumbs({ table: null });
|
||||||
|
|
||||||
|
this.closeContext();
|
||||||
|
this.$emit('reload');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async deleteTable () {
|
||||||
|
try {
|
||||||
|
const { status, response } = await Tables.dropTable({
|
||||||
|
uid: this.selectedWorkspace,
|
||||||
|
table: this.selectedTable
|
||||||
|
});
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
if (this.selectedTable === this.workspace.breadcrumbs.table)
|
||||||
|
this.changeBreadcrumbs({ table: null });
|
||||||
|
|
||||||
|
this.closeContext();
|
||||||
|
this.$emit('reload');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
416
src/renderer/components/WorkspacePropsForeignModal.vue
Normal file
416
src/renderer/components/WorkspacePropsForeignModal.vue
Normal file
@@ -0,0 +1,416 @@
|
|||||||
|
<template>
|
||||||
|
<ConfirmModal
|
||||||
|
:confirm-text="$t('word.confirm')"
|
||||||
|
size="medium"
|
||||||
|
@confirm="confirmForeignsChange"
|
||||||
|
@hide="$emit('hide')"
|
||||||
|
>
|
||||||
|
<template :slot="'header'">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-key-link mr-1" /> {{ $t('word.foreignKeys') }} "{{ table }}"
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div :slot="'body'">
|
||||||
|
<div class="columns col-gapless">
|
||||||
|
<div class="column col-5">
|
||||||
|
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
||||||
|
<div class="panel-header pt-0 pl-0">
|
||||||
|
<div class="d-flex">
|
||||||
|
<button class="btn btn-dark btn-sm d-flex" @click="addForeign">
|
||||||
|
<span>{{ $t('word.add') }}</span>
|
||||||
|
<i class="mdi mdi-24px mdi-link-plus ml-1" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
|
||||||
|
:title="$t('message.clearChanges')"
|
||||||
|
:disabled="!isChanged"
|
||||||
|
@click.prevent="clearChanges"
|
||||||
|
>
|
||||||
|
<span>{{ $t('word.clear') }}</span>
|
||||||
|
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ref="indexesPanel" class="panel-body p-0 pr-1">
|
||||||
|
<div
|
||||||
|
v-for="foreign in foreignProxy"
|
||||||
|
:key="foreign._id"
|
||||||
|
class="tile tile-centered c-hand mb-1 p-1"
|
||||||
|
:class="{'selected-foreign': selectedForeignID === foreign._id}"
|
||||||
|
@click="selectForeign($event, foreign._id)"
|
||||||
|
>
|
||||||
|
<div class="tile-icon">
|
||||||
|
<div>
|
||||||
|
<i class="mdi mdi-key-link mdi-24px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tile-content">
|
||||||
|
<div class="tile-title">
|
||||||
|
{{ foreign.constraintName }}
|
||||||
|
</div>
|
||||||
|
<small class="tile-subtitle text-gray d-flex">
|
||||||
|
<i class="mdi mdi-link-variant mr-1" />
|
||||||
|
<div class="fk-details-wrapper">
|
||||||
|
<span v-if="foreign.table !== ''" class="fk-details">
|
||||||
|
<i class="mdi mdi-table mr-1" />
|
||||||
|
<span>{{ foreign.table }}.{{ foreign.field }}</span>
|
||||||
|
</span>
|
||||||
|
<span v-if="foreign.refTable !== ''" class="fk-details">
|
||||||
|
<i class="mdi mdi-table mr-1" />
|
||||||
|
<span>{{ foreign.refTable }}.{{ foreign.refField }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<div class="tile-action">
|
||||||
|
<button
|
||||||
|
class="btn btn-link remove-field p-0 mr-2"
|
||||||
|
:title="$t('word.delete')"
|
||||||
|
@click.prevent="removeIndex(foreign._id)"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-close" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column col-7 pl-2 editor-col">
|
||||||
|
<form
|
||||||
|
v-if="selectedForeignObj"
|
||||||
|
:style="{ height: modalInnerHeight + 'px'}"
|
||||||
|
class="form-horizontal"
|
||||||
|
>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-3">
|
||||||
|
{{ $t('word.name') }}
|
||||||
|
</label>
|
||||||
|
<div class="column">
|
||||||
|
<input
|
||||||
|
v-model="selectedForeignObj.constraintName"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-4">
|
||||||
|
<label class="form-label col-3">
|
||||||
|
{{ $tc('word.field', 1) }}
|
||||||
|
</label>
|
||||||
|
<div class="fields-list column pt-1">
|
||||||
|
<label
|
||||||
|
v-for="(field, i) in fields"
|
||||||
|
:key="`${field.name}-${i}`"
|
||||||
|
class="form-checkbox m-0"
|
||||||
|
@click.prevent="toggleField(field.name)"
|
||||||
|
>
|
||||||
|
<input type="checkbox" :checked="selectedForeignObj.field === field.name">
|
||||||
|
<i class="form-icon" /> {{ field.name }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-3 pt-0">
|
||||||
|
{{ $t('message.referenceTable') }}
|
||||||
|
</label>
|
||||||
|
<div class="column">
|
||||||
|
<select
|
||||||
|
v-model="selectedForeignObj.refTable"
|
||||||
|
class="form-select"
|
||||||
|
@change="reloadRefFields"
|
||||||
|
>
|
||||||
|
<option
|
||||||
|
v-for="schemaTable in schemaTables"
|
||||||
|
:key="schemaTable.name"
|
||||||
|
:value="schemaTable.name"
|
||||||
|
>
|
||||||
|
{{ schemaTable.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group mb-4">
|
||||||
|
<label class="form-label col-3">
|
||||||
|
{{ $t('message.referenceField') }}
|
||||||
|
</label>
|
||||||
|
<div class="fields-list column pt-1">
|
||||||
|
<label
|
||||||
|
v-for="(field, i) in refFields[selectedForeignID]"
|
||||||
|
:key="`${field.name}-${i}`"
|
||||||
|
class="form-checkbox m-0"
|
||||||
|
@click.prevent="toggleRefField(field.name)"
|
||||||
|
>
|
||||||
|
<input type="checkbox" :checked="selectedForeignObj.refField === field.name && selectedForeignObj.refTable === field.table">
|
||||||
|
<i class="form-icon" /> {{ field.name }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-3">
|
||||||
|
{{ $t('message.onUpdate') }}
|
||||||
|
</label>
|
||||||
|
<div class="column">
|
||||||
|
<select v-model="selectedForeignObj.onUpdate" class="form-select">
|
||||||
|
<option
|
||||||
|
v-for="action in foreignActions"
|
||||||
|
:key="action"
|
||||||
|
:value="action"
|
||||||
|
>
|
||||||
|
{{ action }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-3">
|
||||||
|
{{ $t('message.onDelete') }}
|
||||||
|
</label>
|
||||||
|
<div class="column">
|
||||||
|
<select v-model="selectedForeignObj.onDelete" class="form-select">
|
||||||
|
<option
|
||||||
|
v-for="action in foreignActions"
|
||||||
|
:key="action"
|
||||||
|
:value="action"
|
||||||
|
>
|
||||||
|
{{ action }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div v-if="!foreignProxy.length" class="empty">
|
||||||
|
<div class="empty-icon">
|
||||||
|
<i class="mdi mdi-key-link mdi-48px" />
|
||||||
|
</div>
|
||||||
|
<p class="empty-title h5">
|
||||||
|
{{ $t('message.thereAreNoForeign') }}
|
||||||
|
</p>
|
||||||
|
<div class="empty-action">
|
||||||
|
<button class="btn btn-primary" @click="addForeign">
|
||||||
|
{{ $t('message.createNewForeign') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ConfirmModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapActions } from 'vuex';
|
||||||
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
|
import Tables from '@/ipc-api/Tables';
|
||||||
|
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WorkspacePropsForeignModal',
|
||||||
|
components: {
|
||||||
|
ConfirmModal
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
localKeyUsage: Array,
|
||||||
|
connection: Object,
|
||||||
|
table: String,
|
||||||
|
schema: String,
|
||||||
|
schemaTables: Array,
|
||||||
|
fields: Array,
|
||||||
|
workspace: Object
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
foreignProxy: [],
|
||||||
|
isOptionsChanging: false,
|
||||||
|
selectedForeignID: '',
|
||||||
|
modalInnerHeight: 400,
|
||||||
|
refFields: {},
|
||||||
|
foreignActions: [
|
||||||
|
'RESTRICT',
|
||||||
|
'CASCADE',
|
||||||
|
'SET NULL',
|
||||||
|
'NO ACTION'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
selectedForeignObj () {
|
||||||
|
return this.foreignProxy.find(foreign => foreign._id === this.selectedForeignID);
|
||||||
|
},
|
||||||
|
isChanged () {
|
||||||
|
return JSON.stringify(this.localKeyUsage) !== JSON.stringify(this.foreignProxy);
|
||||||
|
},
|
||||||
|
hasPrimary () {
|
||||||
|
return this.foreignProxy.some(foreign => foreign.type === 'PRIMARY');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.foreignProxy = JSON.parse(JSON.stringify(this.localKeyUsage));
|
||||||
|
|
||||||
|
if (this.foreignProxy.length)
|
||||||
|
this.resetSelectedID();
|
||||||
|
|
||||||
|
if (this.selectedForeignObj)
|
||||||
|
this.getRefFields();
|
||||||
|
|
||||||
|
this.getModalInnerHeight();
|
||||||
|
window.addEventListener('resize', this.getModalInnerHeight);
|
||||||
|
},
|
||||||
|
destroyed () {
|
||||||
|
window.removeEventListener('resize', this.getModalInnerHeight);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapActions({
|
||||||
|
addNotification: 'notifications/addNotification'
|
||||||
|
}),
|
||||||
|
confirmForeignsChange () {
|
||||||
|
this.$emit('foreigns-update', this.foreignProxy);
|
||||||
|
},
|
||||||
|
selectForeign (event, id) {
|
||||||
|
if (this.selectedForeignID !== id && !event.target.classList.contains('remove-field'))
|
||||||
|
this.selectedForeignID = id;
|
||||||
|
},
|
||||||
|
getModalInnerHeight () {
|
||||||
|
const modalBody = document.querySelector('.modal-body');
|
||||||
|
if (modalBody)
|
||||||
|
this.modalInnerHeight = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
|
||||||
|
},
|
||||||
|
addForeign () {
|
||||||
|
this.foreignProxy = [...this.foreignProxy, {
|
||||||
|
_id: uidGen(),
|
||||||
|
constraintName: `FK_${this.foreignProxy.length + 1}`,
|
||||||
|
refSchema: this.schema,
|
||||||
|
table: this.table,
|
||||||
|
refTable: '',
|
||||||
|
field: '',
|
||||||
|
refField: '',
|
||||||
|
onUpdate: this.foreignActions[0],
|
||||||
|
onDelete: this.foreignActions[0]
|
||||||
|
}];
|
||||||
|
|
||||||
|
if (this.foreignProxy.length === 1)
|
||||||
|
this.resetSelectedID();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$refs.indexesPanel.scrollTop = this.$refs.indexesPanel.scrollHeight + 60;
|
||||||
|
}, 20);
|
||||||
|
},
|
||||||
|
removeIndex (id) {
|
||||||
|
this.foreignProxy = this.foreignProxy.filter(foreign => foreign._id !== id);
|
||||||
|
|
||||||
|
if (this.selectedForeignID === id && this.foreignProxy.length)
|
||||||
|
this.resetSelectedID();
|
||||||
|
},
|
||||||
|
clearChanges () {
|
||||||
|
this.foreignProxy = JSON.parse(JSON.stringify(this.localKeyUsage));
|
||||||
|
if (!this.foreignProxy.some(foreign => foreign._id === this.selectedForeignID))
|
||||||
|
this.resetSelectedID();
|
||||||
|
},
|
||||||
|
toggleField (field) {
|
||||||
|
this.foreignProxy = this.foreignProxy.map(foreign => {
|
||||||
|
if (foreign._id === this.selectedForeignID)
|
||||||
|
foreign.field = field;
|
||||||
|
|
||||||
|
return foreign;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
toggleRefField (field) {
|
||||||
|
this.foreignProxy = this.foreignProxy.map(foreign => {
|
||||||
|
if (foreign._id === this.selectedForeignID)
|
||||||
|
foreign.refField = field;
|
||||||
|
|
||||||
|
return foreign;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resetSelectedID () {
|
||||||
|
this.selectedForeignID = this.foreignProxy.length ? this.foreignProxy[0]._id : '';
|
||||||
|
},
|
||||||
|
async getRefFields () {
|
||||||
|
const params = {
|
||||||
|
uid: this.connection.uid,
|
||||||
|
schema: this.selectedForeignObj.refSchema,
|
||||||
|
table: this.selectedForeignObj.refTable
|
||||||
|
};
|
||||||
|
|
||||||
|
try { // Field data
|
||||||
|
const { status, response } = await Tables.getTableColumns(params);
|
||||||
|
if (status === 'success') {
|
||||||
|
this.refFields = {
|
||||||
|
...this.refFields,
|
||||||
|
[this.selectedForeignID]: response
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
reloadRefFields () {
|
||||||
|
this.selectedForeignObj.refField = '';
|
||||||
|
this.getRefFields();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tile {
|
||||||
|
border-radius: 2px;
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: background 0.2s;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
|
.tile-action {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $bg-color-light;
|
||||||
|
|
||||||
|
.tile-action {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected-foreign {
|
||||||
|
background: $bg-color-light;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-col {
|
||||||
|
border-left: 2px solid $bg-color-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fields-list {
|
||||||
|
max-height: 80px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-field .mdi {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fk-details-wrapper {
|
||||||
|
max-width: calc(100% - 1rem);
|
||||||
|
|
||||||
|
.fk-details {
|
||||||
|
display: flex;
|
||||||
|
line-height: 1;
|
||||||
|
align-items: baseline;
|
||||||
|
|
||||||
|
> span {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: block;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
284
src/renderer/components/WorkspacePropsIndexesModal.vue
Normal file
284
src/renderer/components/WorkspacePropsIndexesModal.vue
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
<template>
|
||||||
|
<ConfirmModal
|
||||||
|
:confirm-text="$t('word.confirm')"
|
||||||
|
size="medium"
|
||||||
|
@confirm="confirmIndexesChange"
|
||||||
|
@hide="$emit('hide')"
|
||||||
|
>
|
||||||
|
<template :slot="'header'">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-key mdi-rotate-45 mr-1" /> {{ $t('word.indexes') }} "{{ table }}"
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div :slot="'body'">
|
||||||
|
<div class="columns col-gapless">
|
||||||
|
<div class="column col-5">
|
||||||
|
<div class="panel" :style="{ height: modalInnerHeight + 'px'}">
|
||||||
|
<div class="panel-header pt-0 pl-0">
|
||||||
|
<div class="d-flex">
|
||||||
|
<button class="btn btn-dark btn-sm d-flex" @click="addIndex">
|
||||||
|
<span>{{ $t('word.add') }}</span>
|
||||||
|
<i class="mdi mdi-24px mdi-key-plus ml-1" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="btn btn-dark btn-sm d-flex ml-2 mr-0"
|
||||||
|
:title="$t('message.clearChanges')"
|
||||||
|
:disabled="!isChanged"
|
||||||
|
@click.prevent="clearChanges"
|
||||||
|
>
|
||||||
|
<span>{{ $t('word.clear') }}</span>
|
||||||
|
<i class="mdi mdi-24px mdi-delete-sweep ml-1" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div ref="indexesPanel" class="panel-body p-0 pr-1">
|
||||||
|
<div
|
||||||
|
v-for="index in indexesProxy"
|
||||||
|
:key="index._id"
|
||||||
|
class="tile tile-centered c-hand mb-1 p-1"
|
||||||
|
:class="{'selected-index': selectedIndexID === index._id}"
|
||||||
|
@click="selectIndex($event, index._id)"
|
||||||
|
>
|
||||||
|
<div class="tile-icon">
|
||||||
|
<div>
|
||||||
|
<i class="mdi mdi-key mdi-24px column-key" :class="`key-${index.type}`" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tile-content">
|
||||||
|
<div class="tile-title">
|
||||||
|
{{ index.name }}
|
||||||
|
</div>
|
||||||
|
<small class="tile-subtitle text-gray">{{ index.type }} · {{ index.fields.length }} {{ $tc('word.field', index.fields.length) }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="tile-action">
|
||||||
|
<button
|
||||||
|
class="btn btn-link remove-field p-0 mr-2"
|
||||||
|
:title="$t('word.delete')"
|
||||||
|
@click.prevent="removeIndex(index._id)"
|
||||||
|
>
|
||||||
|
<i class="mdi mdi-close" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="column col-7 pl-2 editor-col">
|
||||||
|
<form
|
||||||
|
v-if="selectedIndexObj"
|
||||||
|
:style="{ height: modalInnerHeight + 'px'}"
|
||||||
|
class="form-horizontal"
|
||||||
|
>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-3">
|
||||||
|
{{ $t('word.name') }}
|
||||||
|
</label>
|
||||||
|
<div class="column">
|
||||||
|
<input
|
||||||
|
v-model="selectedIndexObj.name"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-3">
|
||||||
|
{{ $t('word.type') }}
|
||||||
|
</label>
|
||||||
|
<div class="column">
|
||||||
|
<select v-model="selectedIndexObj.type" class="form-select">
|
||||||
|
<option
|
||||||
|
v-for="index in indexTypes"
|
||||||
|
:key="index"
|
||||||
|
:value="index"
|
||||||
|
:disabled="index === 'PRIMARY' && hasPrimary"
|
||||||
|
>
|
||||||
|
{{ index }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-3">
|
||||||
|
{{ $tc('word.field', fields.length) }}
|
||||||
|
</label>
|
||||||
|
<div class="fields-list column pt-1">
|
||||||
|
<label
|
||||||
|
v-for="(field, i) in fields"
|
||||||
|
:key="`${field.name}-${i}`"
|
||||||
|
class="form-checkbox m-0"
|
||||||
|
@click.prevent="toggleField(field.name)"
|
||||||
|
>
|
||||||
|
<input type="checkbox" :checked="selectedIndexObj.fields.some(f => f === field.name)">
|
||||||
|
<i class="form-icon" /> {{ field.name }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div v-if="!indexesProxy.length" class="empty">
|
||||||
|
<div class="empty-icon">
|
||||||
|
<i class="mdi mdi-key-outline mdi-48px" />
|
||||||
|
</div>
|
||||||
|
<p class="empty-title h5">
|
||||||
|
{{ $t('message.thereAreNoIndexes') }}
|
||||||
|
</p>
|
||||||
|
<div class="empty-action">
|
||||||
|
<button class="btn btn-primary" @click="addIndex">
|
||||||
|
{{ $t('message.createNewIndex') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ConfirmModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
|
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WorkspacePropsIndexesModal',
|
||||||
|
components: {
|
||||||
|
ConfirmModal
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
localIndexes: Array,
|
||||||
|
table: String,
|
||||||
|
fields: Array,
|
||||||
|
workspace: Object,
|
||||||
|
indexTypes: Array
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
indexesProxy: [],
|
||||||
|
isOptionsChanging: false,
|
||||||
|
selectedIndexID: '',
|
||||||
|
modalInnerHeight: 400
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
selectedIndexObj () {
|
||||||
|
return this.indexesProxy.find(index => index._id === this.selectedIndexID);
|
||||||
|
},
|
||||||
|
isChanged () {
|
||||||
|
return JSON.stringify(this.localIndexes) !== JSON.stringify(this.indexesProxy);
|
||||||
|
},
|
||||||
|
hasPrimary () {
|
||||||
|
return this.indexesProxy.some(index => index.type === 'PRIMARY');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.indexesProxy = JSON.parse(JSON.stringify(this.localIndexes));
|
||||||
|
|
||||||
|
if (this.indexesProxy.length)
|
||||||
|
this.resetSelectedID();
|
||||||
|
|
||||||
|
this.getModalInnerHeight();
|
||||||
|
window.addEventListener('resize', this.getModalInnerHeight);
|
||||||
|
},
|
||||||
|
destroyed () {
|
||||||
|
window.removeEventListener('resize', this.getModalInnerHeight);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
confirmIndexesChange () {
|
||||||
|
this.$emit('indexes-update', this.indexesProxy);
|
||||||
|
},
|
||||||
|
selectIndex (event, id) {
|
||||||
|
if (this.selectedIndexID !== id && !event.target.classList.contains('remove-field'))
|
||||||
|
this.selectedIndexID = id;
|
||||||
|
},
|
||||||
|
getModalInnerHeight () {
|
||||||
|
const modalBody = document.querySelector('.modal-body');
|
||||||
|
if (modalBody)
|
||||||
|
this.modalInnerHeight = modalBody.clientHeight - (parseFloat(getComputedStyle(modalBody).paddingTop) + parseFloat(getComputedStyle(modalBody).paddingBottom));
|
||||||
|
},
|
||||||
|
addIndex () {
|
||||||
|
this.indexesProxy = [...this.indexesProxy, {
|
||||||
|
_id: uidGen(),
|
||||||
|
name: 'NEW_INDEX',
|
||||||
|
fields: [],
|
||||||
|
type: 'INDEX',
|
||||||
|
comment: '',
|
||||||
|
indexType: 'BTREE',
|
||||||
|
indexComment: '',
|
||||||
|
cardinality: 0
|
||||||
|
}];
|
||||||
|
|
||||||
|
if (this.indexesProxy.length === 1)
|
||||||
|
this.resetSelectedID();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$refs.indexesPanel.scrollTop = this.$refs.indexesPanel.scrollHeight + 60;
|
||||||
|
}, 20);
|
||||||
|
},
|
||||||
|
removeIndex (id) {
|
||||||
|
this.indexesProxy = this.indexesProxy.filter(index => index._id !== id);
|
||||||
|
|
||||||
|
if (this.selectedIndexID === id && this.indexesProxy.length)
|
||||||
|
this.resetSelectedID();
|
||||||
|
},
|
||||||
|
clearChanges () {
|
||||||
|
this.indexesProxy = JSON.parse(JSON.stringify(this.localIndexes));
|
||||||
|
if (!this.indexesProxy.some(index => index._id === this.selectedIndexID))
|
||||||
|
this.resetSelectedID();
|
||||||
|
},
|
||||||
|
toggleField (field) {
|
||||||
|
this.indexesProxy = this.indexesProxy.map(index => {
|
||||||
|
if (index._id === this.selectedIndexID) {
|
||||||
|
if (index.fields.includes(field))
|
||||||
|
index.fields = index.fields.filter(f => f !== field);
|
||||||
|
else
|
||||||
|
index.fields.push(field);
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
resetSelectedID () {
|
||||||
|
this.selectedIndexID = this.indexesProxy.length ? this.indexesProxy[0]._id : '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.tile {
|
||||||
|
border-radius: 2px;
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: background 0.2s;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
|
.tile-action {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: $bg-color-light;
|
||||||
|
|
||||||
|
.tile-action {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected-index {
|
||||||
|
background: $bg-color-light;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-col {
|
||||||
|
border-left: 2px solid $bg-color-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fields-list {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-field .mdi {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
130
src/renderer/components/WorkspacePropsOptionsModal.vue
Normal file
130
src/renderer/components/WorkspacePropsOptionsModal.vue
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
<template>
|
||||||
|
<ConfirmModal
|
||||||
|
:confirm-text="$t('word.confirm')"
|
||||||
|
size="400"
|
||||||
|
@confirm="confirmOptionsChange"
|
||||||
|
@hide="$emit('hide')"
|
||||||
|
>
|
||||||
|
<template :slot="'header'">
|
||||||
|
<div class="d-flex">
|
||||||
|
<i class="mdi mdi-24px mdi-cogs mr-1" /> {{ $t('word.options') }} "{{ table }}"
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div :slot="'body'">
|
||||||
|
<form class="form-horizontal">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-4">
|
||||||
|
{{ $t('word.name') }}
|
||||||
|
</label>
|
||||||
|
<div class="column">
|
||||||
|
<input
|
||||||
|
ref="firstInput"
|
||||||
|
v-model="optionsProxy.name"
|
||||||
|
class="form-input"
|
||||||
|
:class="{'is-error': !isTableNameValid}"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-4">
|
||||||
|
{{ $t('word.comment') }}
|
||||||
|
</label>
|
||||||
|
<div class="column">
|
||||||
|
<input
|
||||||
|
v-model="optionsProxy.comment"
|
||||||
|
class="form-input"
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-4">
|
||||||
|
{{ $t('word.autoIncrement') }}
|
||||||
|
</label>
|
||||||
|
<div class="column">
|
||||||
|
<input
|
||||||
|
v-model="optionsProxy.autoIncrement"
|
||||||
|
class="form-input"
|
||||||
|
type="number"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-4">
|
||||||
|
{{ $t('word.collation') }}
|
||||||
|
</label>
|
||||||
|
<div class="column">
|
||||||
|
<select v-model="optionsProxy.collation" class="form-select">
|
||||||
|
<option
|
||||||
|
v-for="collation in workspace.collations"
|
||||||
|
:key="collation.id"
|
||||||
|
:value="collation.collation"
|
||||||
|
>
|
||||||
|
{{ collation.collation }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label col-4">
|
||||||
|
{{ $t('word.engine') }}
|
||||||
|
</label>
|
||||||
|
<div class="column">
|
||||||
|
<select v-model="optionsProxy.engine" class="form-select">
|
||||||
|
<option
|
||||||
|
v-for="engine in workspace.engines"
|
||||||
|
:key="engine.name"
|
||||||
|
:value="engine.name"
|
||||||
|
>
|
||||||
|
{{ engine.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</ConfirmModal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WorkspacePropsOptionsModal',
|
||||||
|
components: {
|
||||||
|
ConfirmModal
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
localOptions: Object,
|
||||||
|
table: String,
|
||||||
|
workspace: Object
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
optionsProxy: {},
|
||||||
|
isOptionsChanging: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isTableNameValid () {
|
||||||
|
return this.optionsProxy.name !== '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
this.optionsProxy = JSON.parse(JSON.stringify(this.localOptions));
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$refs.firstInput.focus();
|
||||||
|
}, 20);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
confirmOptionsChange () {
|
||||||
|
if (!this.isTableNameValid)
|
||||||
|
this.optionsProxy.name = this.localOptions.name;
|
||||||
|
|
||||||
|
this.$emit('options-update', this.optionsProxy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
@@ -32,15 +32,19 @@
|
|||||||
<span>{{ $t('word.add') }}</span>
|
<span>{{ $t('word.add') }}</span>
|
||||||
<i class="mdi mdi-24px mdi-playlist-plus ml-1" />
|
<i class="mdi mdi-24px mdi-playlist-plus ml-1" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-dark btn-sm d-none">
|
<button
|
||||||
|
class="btn btn-dark btn-sm"
|
||||||
|
:title="$t('message.manageIndexes')"
|
||||||
|
@click="showIntdexesModal"
|
||||||
|
>
|
||||||
<span>{{ $t('word.indexes') }}</span>
|
<span>{{ $t('word.indexes') }}</span>
|
||||||
<i class="mdi mdi-24px mdi-key mdi-rotate-45 ml-1" />
|
<i class="mdi mdi-24px mdi-key mdi-rotate-45 ml-1" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-dark btn-sm d-none">
|
<button class="btn btn-dark btn-sm" @click="showForeignModal">
|
||||||
<span>{{ $t('word.foreignKeys') }}</span>
|
<span>{{ $t('word.foreignKeys') }}</span>
|
||||||
<i class="mdi mdi-24px mdi-key-link ml-1" />
|
<i class="mdi mdi-24px mdi-key-link ml-1" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-dark btn-sm d-none">
|
<button class="btn btn-dark btn-sm" @click="showOptionsModal">
|
||||||
<span>{{ $t('word.options') }}</span>
|
<span>{{ $t('word.options') }}</span>
|
||||||
<i class="mdi mdi-24px mdi-cogs ml-1" />
|
<i class="mdi mdi-24px mdi-cogs ml-1" />
|
||||||
</button>
|
</button>
|
||||||
@@ -50,16 +54,51 @@
|
|||||||
<div class="workspace-query-results column col-12">
|
<div class="workspace-query-results column col-12">
|
||||||
<WorkspacePropsTable
|
<WorkspacePropsTable
|
||||||
v-if="localFields"
|
v-if="localFields"
|
||||||
ref="queryTable"
|
ref="indexTable"
|
||||||
:fields="localFields"
|
:fields="localFields"
|
||||||
|
:indexes="localIndexes"
|
||||||
|
:foreigns="localKeyUsage"
|
||||||
:tab-uid="tabUid"
|
:tab-uid="tabUid"
|
||||||
:conn-uid="connection.uid"
|
:conn-uid="connection.uid"
|
||||||
|
:index-types="workspace.indexTypes"
|
||||||
:table="table"
|
:table="table"
|
||||||
:schema="schema"
|
:schema="schema"
|
||||||
mode="table"
|
mode="table"
|
||||||
@remove-field="removeField"
|
@remove-field="removeField"
|
||||||
|
@add-new-index="addNewIndex"
|
||||||
|
@add-to-index="addToIndex"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<WorkspacePropsOptionsModal
|
||||||
|
v-if="isOptionsModal"
|
||||||
|
:local-options="localOptions"
|
||||||
|
:table="table"
|
||||||
|
:workspace="workspace"
|
||||||
|
@hide="hideOptionsModal"
|
||||||
|
@options-update="optionsUpdate"
|
||||||
|
/>
|
||||||
|
<WorkspacePropsIndexesModal
|
||||||
|
v-if="isIndexesModal"
|
||||||
|
:local-indexes="localIndexes"
|
||||||
|
:table="table"
|
||||||
|
:fields="localFields"
|
||||||
|
:index-types="workspace.indexTypes"
|
||||||
|
:workspace="workspace"
|
||||||
|
@hide="hideIndexesModal"
|
||||||
|
@indexes-update="indexesUpdate"
|
||||||
|
/>
|
||||||
|
<WorkspacePropsForeignModal
|
||||||
|
v-if="isForeignModal"
|
||||||
|
:local-key-usage="localKeyUsage"
|
||||||
|
:connection="connection"
|
||||||
|
:table="table"
|
||||||
|
:schema="schema"
|
||||||
|
:schema-tables="schemaTables"
|
||||||
|
:fields="localFields"
|
||||||
|
:workspace="workspace"
|
||||||
|
@hide="hideForeignModal"
|
||||||
|
@foreigns-update="foreignsUpdate"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -68,11 +107,17 @@ import { mapGetters, mapActions } from 'vuex';
|
|||||||
import { uidGen } from 'common/libs/uidGen';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
import Tables from '@/ipc-api/Tables';
|
import Tables from '@/ipc-api/Tables';
|
||||||
import WorkspacePropsTable from '@/components/WorkspacePropsTable';
|
import WorkspacePropsTable from '@/components/WorkspacePropsTable';
|
||||||
|
import WorkspacePropsOptionsModal from '@/components/WorkspacePropsOptionsModal';
|
||||||
|
import WorkspacePropsIndexesModal from '@/components/WorkspacePropsIndexesModal';
|
||||||
|
import WorkspacePropsForeignModal from '@/components/WorkspacePropsForeignModal';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'WorkspacePropsTab',
|
name: 'WorkspacePropsTab',
|
||||||
components: {
|
components: {
|
||||||
WorkspacePropsTable
|
WorkspacePropsTable,
|
||||||
|
WorkspacePropsOptionsModal,
|
||||||
|
WorkspacePropsIndexesModal,
|
||||||
|
WorkspacePropsForeignModal
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
connection: Object,
|
connection: Object,
|
||||||
@@ -83,29 +128,54 @@ export default {
|
|||||||
tabUid: 'prop',
|
tabUid: 'prop',
|
||||||
isQuering: false,
|
isQuering: false,
|
||||||
isSaving: false,
|
isSaving: false,
|
||||||
|
isOptionsModal: false,
|
||||||
|
isIndexesModal: false,
|
||||||
|
isForeignModal: false,
|
||||||
|
isOptionsChanging: false,
|
||||||
originalFields: [],
|
originalFields: [],
|
||||||
localFields: [],
|
localFields: [],
|
||||||
originalKeyUsage: [],
|
originalKeyUsage: [],
|
||||||
localKeyUsage: [],
|
localKeyUsage: [],
|
||||||
|
originalIndexes: [],
|
||||||
|
localIndexes: [],
|
||||||
|
localOptions: {},
|
||||||
lastTable: null,
|
lastTable: null,
|
||||||
isAddModal: false
|
newFieldsCounter: 0
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
getWorkspace: 'workspaces/getWorkspace'
|
getWorkspace: 'workspaces/getWorkspace',
|
||||||
|
getDatabaseVariable: 'workspaces/getDatabaseVariable'
|
||||||
}),
|
}),
|
||||||
workspace () {
|
workspace () {
|
||||||
return this.getWorkspace(this.connection.uid);
|
return this.getWorkspace(this.connection.uid);
|
||||||
},
|
},
|
||||||
|
tableOptions () {
|
||||||
|
const db = this.workspace.structure.find(db => db.name === this.schema);
|
||||||
|
return db && this.table ? db.tables.find(table => table.name === this.table) : {};
|
||||||
|
},
|
||||||
|
defaultEngine () {
|
||||||
|
return this.getDatabaseVariable(this.connection.uid, 'default_storage_engine').value || '';
|
||||||
|
},
|
||||||
isSelected () {
|
isSelected () {
|
||||||
return this.workspace.selected_tab === 'prop';
|
return this.workspace.selected_tab === 'prop';
|
||||||
},
|
},
|
||||||
schema () {
|
schema () {
|
||||||
return this.workspace.breadcrumbs.schema;
|
return this.workspace.breadcrumbs.schema;
|
||||||
},
|
},
|
||||||
|
schemaTables () {
|
||||||
|
const schemaTables = this.workspace.structure
|
||||||
|
.filter(schema => schema.name === this.schema)
|
||||||
|
.map(schema => schema.tables);
|
||||||
|
|
||||||
|
return schemaTables.length ? schemaTables[0].filter(table => table.type === 'table') : [];
|
||||||
|
},
|
||||||
isChanged () {
|
isChanged () {
|
||||||
return JSON.stringify(this.originalFields) !== JSON.stringify(this.localFields) || JSON.stringify(this.originalKeyUsage) !== JSON.stringify(this.localKeyUsage);
|
return JSON.stringify(this.originalFields) !== JSON.stringify(this.localFields) ||
|
||||||
|
JSON.stringify(this.originalKeyUsage) !== JSON.stringify(this.localKeyUsage) ||
|
||||||
|
JSON.stringify(this.originalIndexes) !== JSON.stringify(this.localIndexes) ||
|
||||||
|
JSON.stringify(this.tableOptions) !== JSON.stringify(this.localOptions);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -120,15 +190,23 @@ export default {
|
|||||||
this.getFieldsData();
|
this.getFieldsData();
|
||||||
this.lastTable = this.table;
|
this.lastTable = this.table;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
isChanged (val) {
|
||||||
|
if (this.isSelected && this.lastTable === this.table && this.table !== null)
|
||||||
|
this.setUnsavedChanges(val);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions({
|
...mapActions({
|
||||||
addNotification: 'notifications/addNotification'
|
addNotification: 'notifications/addNotification',
|
||||||
|
refreshStructure: 'workspaces/refreshStructure',
|
||||||
|
setUnsavedChanges: 'workspaces/setUnsavedChanges'
|
||||||
}),
|
}),
|
||||||
async getFieldsData () {
|
async getFieldsData () {
|
||||||
if (!this.table) return;
|
if (!this.table) return;
|
||||||
|
this.newFieldsCounter = 0;
|
||||||
this.isQuering = true;
|
this.isQuering = true;
|
||||||
|
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
uid: this.connection.uid,
|
uid: this.connection.uid,
|
||||||
@@ -151,12 +229,49 @@ export default {
|
|||||||
this.addNotification({ status: 'error', message: err.stack });
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try { // Indexes
|
||||||
|
const { status, response } = await Tables.getTableIndexes(params);
|
||||||
|
|
||||||
|
if (status === 'success') {
|
||||||
|
const indexesObj = response.reduce((acc, curr) => {
|
||||||
|
acc[curr.name] = acc[curr.name] || [];
|
||||||
|
acc[curr.name].push(curr);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
this.originalIndexes = Object.keys(indexesObj).map(index => {
|
||||||
|
return {
|
||||||
|
_id: uidGen(),
|
||||||
|
name: index,
|
||||||
|
fields: indexesObj[index].map(field => field.column),
|
||||||
|
type: indexesObj[index][0].type,
|
||||||
|
comment: indexesObj[index][0].comment,
|
||||||
|
indexType: indexesObj[index][0].indexType,
|
||||||
|
indexComment: indexesObj[index][0].indexComment,
|
||||||
|
cardinality: indexesObj[index][0].cardinality
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
this.localIndexes = JSON.parse(JSON.stringify(this.originalIndexes));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.addNotification({ status: 'error', message: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
this.addNotification({ status: 'error', message: err.stack });
|
||||||
|
}
|
||||||
|
|
||||||
try { // Key usage (foreign keys)
|
try { // Key usage (foreign keys)
|
||||||
const { status, response } = await Tables.getKeyUsage(params);
|
const { status, response } = await Tables.getKeyUsage(params);
|
||||||
|
|
||||||
if (status === 'success') {
|
if (status === 'success') {
|
||||||
this.originalKeyUsage = response;
|
this.originalKeyUsage = response.map(foreign => {
|
||||||
this.localKeyUsage = JSON.parse(JSON.stringify(response));
|
return {
|
||||||
|
_id: uidGen(),
|
||||||
|
...foreign
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.localKeyUsage = JSON.parse(JSON.stringify(this.originalKeyUsage));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
this.addNotification({ status: 'error', message: response });
|
this.addNotification({ status: 'error', message: response });
|
||||||
@@ -171,20 +286,21 @@ export default {
|
|||||||
if (this.isSaving) return;
|
if (this.isSaving) return;
|
||||||
this.isSaving = true;
|
this.isSaving = true;
|
||||||
|
|
||||||
|
// FIELDS
|
||||||
const originalIDs = this.originalFields.reduce((acc, curr) => [...acc, curr._id], []);
|
const originalIDs = this.originalFields.reduce((acc, curr) => [...acc, curr._id], []);
|
||||||
const localIDs = this.localFields.reduce((acc, curr) => [...acc, curr._id], []);
|
const localIDs = this.localFields.reduce((acc, curr) => [...acc, curr._id], []);
|
||||||
|
|
||||||
// Additions
|
// Fields Additions
|
||||||
const additions = this.localFields.filter((field, i) => !originalIDs.includes(field._id)).map(field => {
|
const additions = this.localFields.filter((field, i) => !originalIDs.includes(field._id)).map(field => {
|
||||||
const lI = this.localFields.findIndex(localField => localField._id === field._id);
|
const lI = this.localFields.findIndex(localField => localField._id === field._id);
|
||||||
const after = lI > 0 ? this.localFields[lI - 1].name : false;
|
const after = lI > 0 ? this.localFields[lI - 1].name : false;
|
||||||
return { ...field, after };
|
return { ...field, after };
|
||||||
});
|
});
|
||||||
|
|
||||||
// Deletions
|
// Fields Deletions
|
||||||
const deletions = this.originalFields.filter(field => !localIDs.includes(field._id));
|
const deletions = this.originalFields.filter(field => !localIDs.includes(field._id));
|
||||||
|
|
||||||
// Changes
|
// Fields Changes
|
||||||
const changes = [];
|
const changes = [];
|
||||||
this.originalFields.forEach((originalField, oI) => {
|
this.originalFields.forEach((originalField, oI) => {
|
||||||
const lI = this.localFields.findIndex(localField => localField._id === originalField._id);
|
const lI = this.localFields.findIndex(localField => localField._id === originalField._id);
|
||||||
@@ -197,20 +313,90 @@ export default {
|
|||||||
if (this.localFields[lI]) changes.push({ ...this.localFields[lI], after, orgName });
|
if (this.localFields[lI]) changes.push({ ...this.localFields[lI], after, orgName });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// OPTIONS
|
||||||
|
const options = Object.keys(this.localOptions).reduce((acc, option) => {
|
||||||
|
if (this.localOptions[option] !== this.tableOptions[option])
|
||||||
|
acc[option] = this.localOptions[option];
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
// INDEXES
|
||||||
|
const indexChanges = {
|
||||||
|
additions: [],
|
||||||
|
changes: [],
|
||||||
|
deletions: []
|
||||||
|
};
|
||||||
|
const originalIndexIDs = this.originalIndexes.reduce((acc, curr) => [...acc, curr._id], []);
|
||||||
|
const localIndexIDs = this.localIndexes.reduce((acc, curr) => [...acc, curr._id], []);
|
||||||
|
|
||||||
|
// Index Additions
|
||||||
|
indexChanges.additions = this.localIndexes.filter(index => !originalIndexIDs.includes(index._id));
|
||||||
|
|
||||||
|
// Index Changes
|
||||||
|
this.originalIndexes.forEach(originalIndex => {
|
||||||
|
const lI = this.localIndexes.findIndex(localIndex => localIndex._id === originalIndex._id);
|
||||||
|
if (JSON.stringify(originalIndex) !== JSON.stringify(this.localIndexes[lI])) {
|
||||||
|
if (this.localIndexes[lI]) {
|
||||||
|
indexChanges.changes.push({
|
||||||
|
...this.localIndexes[lI],
|
||||||
|
oldName: originalIndex.name,
|
||||||
|
oldType: originalIndex.type
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Index Deletions
|
||||||
|
indexChanges.deletions = this.originalIndexes.filter(index => !localIndexIDs.includes(index._id));
|
||||||
|
|
||||||
|
// FOREIGN KEYS
|
||||||
|
const foreignChanges = {
|
||||||
|
additions: [],
|
||||||
|
changes: [],
|
||||||
|
deletions: []
|
||||||
|
};
|
||||||
|
const originalForeignIDs = this.originalKeyUsage.reduce((acc, curr) => [...acc, curr._id], []);
|
||||||
|
const localForeignIDs = this.localKeyUsage.reduce((acc, curr) => [...acc, curr._id], []);
|
||||||
|
|
||||||
|
// Foreigns Additions
|
||||||
|
foreignChanges.additions = this.localKeyUsage.filter(foreign => !originalForeignIDs.includes(foreign._id));
|
||||||
|
|
||||||
|
// Foreigns Changes
|
||||||
|
this.originalKeyUsage.forEach(originalForeign => {
|
||||||
|
const lI = this.localKeyUsage.findIndex(localForeign => localForeign._id === originalForeign._id);
|
||||||
|
if (JSON.stringify(originalForeign) !== JSON.stringify(this.localKeyUsage[lI])) {
|
||||||
|
if (this.localKeyUsage[lI]) {
|
||||||
|
foreignChanges.changes.push({
|
||||||
|
...this.localKeyUsage[lI],
|
||||||
|
oldName: originalForeign.constraintName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Foreigns Deletions
|
||||||
|
foreignChanges.deletions = this.originalKeyUsage.filter(foreign => !localForeignIDs.includes(foreign._id));
|
||||||
|
|
||||||
|
// ALTER
|
||||||
const params = {
|
const params = {
|
||||||
uid: this.connection.uid,
|
uid: this.connection.uid,
|
||||||
schema: this.schema,
|
schema: this.schema,
|
||||||
table: this.workspace.breadcrumbs.table,
|
table: this.workspace.breadcrumbs.table,
|
||||||
additions,
|
additions,
|
||||||
changes,
|
changes,
|
||||||
deletions
|
deletions,
|
||||||
|
indexChanges,
|
||||||
|
foreignChanges,
|
||||||
|
options
|
||||||
};
|
};
|
||||||
|
|
||||||
try { // Key usage (foreign keys)
|
try {
|
||||||
const { status, response } = await Tables.alterTable(params);
|
const { status, response } = await Tables.alterTable(params);
|
||||||
|
|
||||||
if (status === 'success')
|
if (status === 'success') {
|
||||||
|
await this.refreshStructure(this.connection.uid);
|
||||||
this.getFieldsData();
|
this.getFieldsData();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
this.addNotification({ status: 'error', message: response });
|
this.addNotification({ status: 'error', message: response });
|
||||||
}
|
}
|
||||||
@@ -219,15 +405,19 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.isSaving = false;
|
this.isSaving = false;
|
||||||
|
this.newFieldsCounter = 0;
|
||||||
},
|
},
|
||||||
clearChanges () {
|
clearChanges () {
|
||||||
this.localFields = JSON.parse(JSON.stringify(this.originalFields));
|
this.localFields = JSON.parse(JSON.stringify(this.originalFields));
|
||||||
|
this.localIndexes = JSON.parse(JSON.stringify(this.originalIndexes));
|
||||||
this.localKeyUsage = JSON.parse(JSON.stringify(this.originalKeyUsage));
|
this.localKeyUsage = JSON.parse(JSON.stringify(this.originalKeyUsage));
|
||||||
|
this.localOptions = JSON.parse(JSON.stringify(this.tableOptions));
|
||||||
|
this.newFieldsCounter = 0;
|
||||||
},
|
},
|
||||||
addField () {
|
addField () {
|
||||||
this.localFields.push({
|
this.localFields.push({
|
||||||
_id: uidGen(),
|
_id: uidGen(),
|
||||||
name: '',
|
name: `${this.$tc('word.field', 1)}_${++this.newFieldsCounter}`,
|
||||||
key: '',
|
key: '',
|
||||||
type: 'int',
|
type: 'int',
|
||||||
schema: this.schema,
|
schema: this.schema,
|
||||||
@@ -247,15 +437,59 @@ export default {
|
|||||||
onUpdate: '',
|
onUpdate: '',
|
||||||
comment: ''
|
comment: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const scrollable = this.$refs.indexTable.$refs.tableWrapper;
|
||||||
|
scrollable.scrollTop = scrollable.scrollHeight + 30;
|
||||||
|
}, 20);
|
||||||
},
|
},
|
||||||
removeField (uid) {
|
removeField (uid) {
|
||||||
this.localFields = this.localFields.filter(field => field._id !== uid);
|
this.localFields = this.localFields.filter(field => field._id !== uid);
|
||||||
},
|
},
|
||||||
showAddModal () {
|
addNewIndex (payload) {
|
||||||
this.isAddModal = true;
|
this.localIndexes = [...this.localIndexes, {
|
||||||
|
_id: uidGen(),
|
||||||
|
name: payload.index === 'PRIMARY' ? 'PRIMARY' : payload.field,
|
||||||
|
fields: [payload.field],
|
||||||
|
type: payload.index,
|
||||||
|
comment: '',
|
||||||
|
indexType: 'BTREE',
|
||||||
|
indexComment: '',
|
||||||
|
cardinality: 0
|
||||||
|
}];
|
||||||
},
|
},
|
||||||
hideAddModal () {
|
addToIndex (payload) {
|
||||||
this.isAddModal = false;
|
this.localIndexes = this.localIndexes.map(index => {
|
||||||
|
if (index._id === payload.index) index.fields.push(payload.field);
|
||||||
|
return index;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showOptionsModal () {
|
||||||
|
this.isOptionsModal = true;
|
||||||
|
},
|
||||||
|
hideOptionsModal () {
|
||||||
|
this.isOptionsModal = false;
|
||||||
|
},
|
||||||
|
optionsUpdate (options) {
|
||||||
|
this.localOptions = options;
|
||||||
|
},
|
||||||
|
showIntdexesModal () {
|
||||||
|
this.isIndexesModal = true;
|
||||||
|
},
|
||||||
|
hideIndexesModal () {
|
||||||
|
this.isIndexesModal = false;
|
||||||
|
},
|
||||||
|
indexesUpdate (indexes) {
|
||||||
|
this.localIndexes = indexes;
|
||||||
|
},
|
||||||
|
showForeignModal () {
|
||||||
|
this.isForeignModal = true;
|
||||||
|
},
|
||||||
|
hideForeignModal () {
|
||||||
|
this.isForeignModal = false;
|
||||||
|
},
|
||||||
|
foreignsUpdate (foreigns) {
|
||||||
|
this.localKeyUsage = foreigns;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -8,8 +8,12 @@
|
|||||||
v-if="isContext"
|
v-if="isContext"
|
||||||
:context-event="contextEvent"
|
:context-event="contextEvent"
|
||||||
:selected-field="selectedField"
|
:selected-field="selectedField"
|
||||||
|
:index-types="indexTypes"
|
||||||
|
:indexes="indexes"
|
||||||
@delete-selected="removeField"
|
@delete-selected="removeField"
|
||||||
@close-context="isContext = false"
|
@close-context="isContext = false"
|
||||||
|
@add-new-index="$emit('add-new-index', $event)"
|
||||||
|
@add-to-index="$emit('add-to-index', $event)"
|
||||||
/>
|
/>
|
||||||
<div ref="propTable" class="table table-hover">
|
<div ref="propTable" class="table table-hover">
|
||||||
<div class="thead">
|
<div class="thead">
|
||||||
@@ -25,14 +29,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="th">
|
<div class="th">
|
||||||
<div class="column-resizable">
|
<div class="column-resizable min-100">
|
||||||
<div class="table-column-title">
|
<div class="table-column-title">
|
||||||
{{ $t('word.name') }}
|
{{ $t('word.name') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="th">
|
<div class="th">
|
||||||
<div class="column-resizable">
|
<div class="column-resizable min-100">
|
||||||
<div class="table-column-title">
|
<div class="table-column-title">
|
||||||
{{ $t('word.type') }}
|
{{ $t('word.type') }}
|
||||||
</div>
|
</div>
|
||||||
@@ -81,7 +85,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="th">
|
<div class="th">
|
||||||
<div class="column-resizable">
|
<div class="column-resizable min-100">
|
||||||
<div class="table-column-title">
|
<div class="table-column-title">
|
||||||
{{ $t('word.collation') }}
|
{{ $t('word.collation') }}
|
||||||
</div>
|
</div>
|
||||||
@@ -99,6 +103,8 @@
|
|||||||
v-for="row in fields"
|
v-for="row in fields"
|
||||||
:key="row._id"
|
:key="row._id"
|
||||||
:row="row"
|
:row="row"
|
||||||
|
:indexes="getIndexes(row.name)"
|
||||||
|
:foreigns="getForeigns(row.name)"
|
||||||
:data-types="dataTypes"
|
:data-types="dataTypes"
|
||||||
@contextmenu="contextMenu"
|
@contextmenu="contextMenu"
|
||||||
/>
|
/>
|
||||||
@@ -122,6 +128,9 @@ export default {
|
|||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
fields: Array,
|
fields: Array,
|
||||||
|
indexes: Array,
|
||||||
|
foreigns: Array,
|
||||||
|
indexTypes: Array,
|
||||||
tabUid: [String, Number],
|
tabUid: [String, Number],
|
||||||
connUid: String,
|
connUid: String,
|
||||||
table: String,
|
table: String,
|
||||||
@@ -131,7 +140,6 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
resultsSize: 1000,
|
resultsSize: 1000,
|
||||||
localResults: [],
|
|
||||||
isContext: false,
|
isContext: false,
|
||||||
contextEvent: null,
|
contextEvent: null,
|
||||||
selectedField: null,
|
selectedField: null,
|
||||||
@@ -154,6 +162,14 @@ export default {
|
|||||||
},
|
},
|
||||||
tabProperties () {
|
tabProperties () {
|
||||||
return this.getWorkspaceTab(this.tabUid);
|
return this.getWorkspaceTab(this.tabUid);
|
||||||
|
},
|
||||||
|
fieldsLength () {
|
||||||
|
return this.fields.length;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
fieldsLength () {
|
||||||
|
this.refreshScroller();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updated () {
|
updated () {
|
||||||
@@ -182,19 +198,31 @@ export default {
|
|||||||
const size = window.innerHeight - el.getBoundingClientRect().top - footer.offsetHeight;
|
const size = window.innerHeight - el.getBoundingClientRect().top - footer.offsetHeight;
|
||||||
this.resultsSize = size;
|
this.resultsSize = size;
|
||||||
}
|
}
|
||||||
// this.$refs.resultTable.updateWindow();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
refreshScroller () {
|
refreshScroller () {
|
||||||
this.resizeResults();
|
this.resizeResults();
|
||||||
},
|
},
|
||||||
contextMenu (event, uid) {
|
contextMenu (event, uid) {
|
||||||
this.selectedField = uid;
|
this.selectedField = this.fields.find(field => field._id === uid);
|
||||||
this.contextEvent = event;
|
this.contextEvent = event;
|
||||||
this.isContext = true;
|
this.isContext = true;
|
||||||
},
|
},
|
||||||
removeField () {
|
removeField () {
|
||||||
this.$emit('remove-field', this.selectedField);
|
this.$emit('remove-field', this.selectedField._id);
|
||||||
|
},
|
||||||
|
getIndexes (field) {
|
||||||
|
return this.indexes.reduce((acc, curr) => {
|
||||||
|
acc.push(...curr.fields.map(f => ({ name: f, type: curr.type })));
|
||||||
|
return acc;
|
||||||
|
}, []).filter(f => f.name === field);
|
||||||
|
},
|
||||||
|
getForeigns (field) {
|
||||||
|
return this.foreigns.reduce((acc, curr) => {
|
||||||
|
if (curr.field === field)
|
||||||
|
acc.push(`${curr.refTable}.${curr.refField}`);
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -208,4 +236,12 @@ export default {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vscroll {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.min-100 {
|
||||||
|
min-width: 100px !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@@ -3,6 +3,36 @@
|
|||||||
:context-event="contextEvent"
|
:context-event="contextEvent"
|
||||||
@close-context="closeContext"
|
@close-context="closeContext"
|
||||||
>
|
>
|
||||||
|
<div class="context-element">
|
||||||
|
<span class="d-flex"><i class="mdi mdi-18px mdi-key-plus text-light pr-1" /> {{ $t('message.createNewIndex') }}</span>
|
||||||
|
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
|
||||||
|
<div class="context-submenu">
|
||||||
|
<div
|
||||||
|
v-for="index in indexTypes"
|
||||||
|
:key="index"
|
||||||
|
class="context-element"
|
||||||
|
:class="{'disabled': index === 'PRIMARY' && hasPrimary}"
|
||||||
|
@click="addNewIndex(index)"
|
||||||
|
>
|
||||||
|
<span class="d-flex"><i class="mdi mdi-18px mdi-key column-key pr-1" :class="`key-${index}`" /> {{ index }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="indexes.length" class="context-element">
|
||||||
|
<span class="d-flex"><i class="mdi mdi-18px mdi-key-arrow-right text-light pr-1" /> {{ $t('message.addToIndex') }}</span>
|
||||||
|
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
|
||||||
|
<div class="context-submenu">
|
||||||
|
<div
|
||||||
|
v-for="index in indexes"
|
||||||
|
:key="index.name"
|
||||||
|
class="context-element"
|
||||||
|
:class="{'disabled': index.fields.includes(selectedField.name)}"
|
||||||
|
@click="addToIndex(index._id)"
|
||||||
|
>
|
||||||
|
<span class="d-flex"><i class="mdi mdi-18px mdi-key column-key pr-1" :class="`key-${index.type}`" /> {{ index.name }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="context-element" @click="deleteField">
|
<div class="context-element" @click="deleteField">
|
||||||
<span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('message.deleteField') }}</span>
|
<span class="d-flex"><i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $t('message.deleteField') }}</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,14 +49,14 @@ export default {
|
|||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
contextEvent: MouseEvent,
|
contextEvent: MouseEvent,
|
||||||
selectedField: String
|
indexes: Array,
|
||||||
},
|
indexTypes: Array,
|
||||||
data () {
|
selectedField: Object
|
||||||
return {
|
|
||||||
isConfirmModal: false
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
hasPrimary () {
|
||||||
|
return this.indexes.some(index => index.type === 'PRIMARY');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
closeContext () {
|
closeContext () {
|
||||||
@@ -35,7 +65,23 @@ export default {
|
|||||||
deleteField () {
|
deleteField () {
|
||||||
this.$emit('delete-selected');
|
this.$emit('delete-selected');
|
||||||
this.closeContext();
|
this.closeContext();
|
||||||
|
},
|
||||||
|
addNewIndex (index) {
|
||||||
|
this.$emit('add-new-index', { field: this.selectedField.name, index });
|
||||||
|
this.closeContext();
|
||||||
|
},
|
||||||
|
addToIndex (index) {
|
||||||
|
this.$emit('add-to-index', { field: this.selectedField.name, index });
|
||||||
|
this.closeContext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.disabled {
|
||||||
|
pointer-events: none;
|
||||||
|
filter: grayscale(100%);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@@ -1,20 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tr" @contextmenu.prevent="$emit('contextmenu', $event, localRow._id)">
|
<div class="tr" @contextmenu.prevent="$emit('contextmenu', $event, localRow._id)">
|
||||||
<div class="td">
|
<div class="td" tabindex="0">
|
||||||
<div class="row-draggable">
|
<div class="row-draggable">
|
||||||
<i class="mdi mdi-drag-horizontal row-draggable-icon" />
|
<i class="mdi mdi-drag-horizontal row-draggable-icon" />
|
||||||
{{ localRow.order }}
|
{{ localRow.order }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="td" tabindex="0">
|
<div class="td" tabindex="0">
|
||||||
<i
|
<div class="text-center">
|
||||||
v-if="localRow.key"
|
<i
|
||||||
:title="keyName(localRow.key)"
|
v-for="(index, i) in indexes"
|
||||||
class="mdi mdi-key column-key c-help pl-1"
|
:key="`${index.name}-${i}`"
|
||||||
:class="`key-${localRow.key}`"
|
:title="index.type"
|
||||||
/>
|
class="d-inline-block mdi mdi-key column-key c-help"
|
||||||
|
:class="`key-${index.type}`"
|
||||||
|
/>
|
||||||
|
<i
|
||||||
|
v-for="foreign in foreigns"
|
||||||
|
:key="foreign"
|
||||||
|
:title="foreign"
|
||||||
|
class="d-inline-block mdi mdi-key-link c-help"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="td">
|
<div class="td" tabindex="0">
|
||||||
<span
|
<span
|
||||||
v-if="!isInlineEditor.name"
|
v-if="!isInlineEditor.name"
|
||||||
class="cell-content"
|
class="cell-content"
|
||||||
@@ -32,11 +41,15 @@
|
|||||||
@blur="editOFF"
|
@blur="editOFF"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="td text-uppercase text-left" :class="`type-${lowerCase(localRow.type)}`">
|
<div
|
||||||
|
class="td text-uppercase"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
v-if="!isInlineEditor.type"
|
v-if="!isInlineEditor.type"
|
||||||
class="cell-content"
|
class="cell-content text-left"
|
||||||
@dblclick="editON($event, localRow.type.toUpperCase(), 'type')"
|
:class="`type-${lowerCase(localRow.type)}`"
|
||||||
|
@click="editON($event, localRow.type.toUpperCase(), 'type')"
|
||||||
>
|
>
|
||||||
{{ localRow.type }}
|
{{ localRow.type }}
|
||||||
</span>
|
</span>
|
||||||
@@ -44,7 +57,7 @@
|
|||||||
v-else
|
v-else
|
||||||
ref="editField"
|
ref="editField"
|
||||||
v-model="editingContent"
|
v-model="editingContent"
|
||||||
class="editable-field px-1 text-uppercase"
|
class="form-select editable-field small-select text-uppercase"
|
||||||
@blur="editOFF"
|
@blur="editOFF"
|
||||||
>
|
>
|
||||||
<optgroup
|
<optgroup
|
||||||
@@ -63,7 +76,7 @@
|
|||||||
</optgroup>
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="td type-int">
|
<div class="td type-int" tabindex="0">
|
||||||
<template v-if="fieldType.length">
|
<template v-if="fieldType.length">
|
||||||
<span
|
<span
|
||||||
v-if="!isInlineEditor.length"
|
v-if="!isInlineEditor.length"
|
||||||
@@ -83,7 +96,7 @@
|
|||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="td">
|
<div class="td" tabindex="0">
|
||||||
<label class="form-checkbox">
|
<label class="form-checkbox">
|
||||||
<input
|
<input
|
||||||
v-model="localRow.unsigned"
|
v-model="localRow.unsigned"
|
||||||
@@ -93,17 +106,17 @@
|
|||||||
<i class="form-icon" />
|
<i class="form-icon" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="td">
|
<div class="td" tabindex="0">
|
||||||
<label class="form-checkbox">
|
<label class="form-checkbox">
|
||||||
<input
|
<input
|
||||||
v-model="localRow.nullable"
|
v-model="localRow.nullable"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:disabled="localRow.key === 'pri'"
|
:disabled="!isNullable"
|
||||||
>
|
>
|
||||||
<i class="form-icon" />
|
<i class="form-icon" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="td">
|
<div class="td" tabindex="0">
|
||||||
<label class="form-checkbox">
|
<label class="form-checkbox">
|
||||||
<input
|
<input
|
||||||
v-model="localRow.zerofill"
|
v-model="localRow.zerofill"
|
||||||
@@ -113,12 +126,12 @@
|
|||||||
<i class="form-icon" />
|
<i class="form-icon" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="td">
|
<div class="td" tabindex="0">
|
||||||
<span class="cell-content" @dblclick="editON($event, localRow.default, 'default')">
|
<span class="cell-content" @dblclick="editON($event, localRow.default, 'default')">
|
||||||
{{ fieldDefault }}
|
{{ fieldDefault }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="td type-varchar">
|
<div class="td type-varchar" tabindex="0">
|
||||||
<span
|
<span
|
||||||
v-if="!isInlineEditor.comment"
|
v-if="!isInlineEditor.comment"
|
||||||
class="cell-content"
|
class="cell-content"
|
||||||
@@ -136,12 +149,12 @@
|
|||||||
@blur="editOFF"
|
@blur="editOFF"
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="td">
|
<div class="td" tabindex="0">
|
||||||
<template v-if="fieldType.collation">
|
<template v-if="fieldType.collation">
|
||||||
<span
|
<span
|
||||||
v-if="!isInlineEditor.collation"
|
v-if="!isInlineEditor.collation"
|
||||||
class="cell-content"
|
class="cell-content"
|
||||||
@dblclick="editON($event, localRow.collation, 'collation')"
|
@click="editON($event, localRow.collation, 'collation')"
|
||||||
>
|
>
|
||||||
{{ localRow.collation }}
|
{{ localRow.collation }}
|
||||||
</span>
|
</span>
|
||||||
@@ -149,7 +162,7 @@
|
|||||||
v-else
|
v-else
|
||||||
ref="editField"
|
ref="editField"
|
||||||
v-model="editingContent"
|
v-model="editingContent"
|
||||||
class="editable-field px-1"
|
class="form-select small-select editable-field"
|
||||||
@blur="editOFF"
|
@blur="editOFF"
|
||||||
>
|
>
|
||||||
<option
|
<option
|
||||||
@@ -221,7 +234,7 @@
|
|||||||
<label class="form-radio form-inline">
|
<label class="form-radio form-inline">
|
||||||
<input
|
<input
|
||||||
v-model="defaultValue.type"
|
v-model="defaultValue.type"
|
||||||
:disabled="localRow.key !== 'pri'"
|
:disabled="!canAutoincrement"
|
||||||
type="radio"
|
type="radio"
|
||||||
name="default"
|
name="default"
|
||||||
value="autoincrement"
|
value="autoincrement"
|
||||||
@@ -279,7 +292,9 @@ export default {
|
|||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
row: Object,
|
row: Object,
|
||||||
dataTypes: Array
|
dataTypes: Array,
|
||||||
|
indexes: Array,
|
||||||
|
foreigns: Array
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -293,6 +308,7 @@ export default {
|
|||||||
onUpdate: ''
|
onUpdate: ''
|
||||||
},
|
},
|
||||||
editingContent: null,
|
editingContent: null,
|
||||||
|
originalContent: null,
|
||||||
editingField: null
|
editingField: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -321,6 +337,12 @@ export default {
|
|||||||
},
|
},
|
||||||
collations () {
|
collations () {
|
||||||
return this.getWorkspace(this.selectedWorkspace).collations;
|
return this.getWorkspace(this.selectedWorkspace).collations;
|
||||||
|
},
|
||||||
|
canAutoincrement () {
|
||||||
|
return this.indexes.some(index => ['PRIMARY', 'UNIQUE'].includes(index.type));
|
||||||
|
},
|
||||||
|
isNullable () {
|
||||||
|
return !this.indexes.some(index => ['PRIMARY'].includes(index.type));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -329,6 +351,13 @@ export default {
|
|||||||
},
|
},
|
||||||
row () {
|
row () {
|
||||||
this.localRow = this.row;
|
this.localRow = this.row;
|
||||||
|
},
|
||||||
|
indexes () {
|
||||||
|
if (!this.canAutoincrement)
|
||||||
|
this.localRow.autoIncrement = false;
|
||||||
|
|
||||||
|
if (!this.isNullable)
|
||||||
|
this.localRow.nullable = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
@@ -393,6 +422,7 @@ export default {
|
|||||||
this.editingField = field;
|
this.editingField = field;
|
||||||
|
|
||||||
this.editingContent = content;
|
this.editingContent = content;
|
||||||
|
this.originalContent = content;
|
||||||
|
|
||||||
const obj = { [field]: true };
|
const obj = { [field]: true };
|
||||||
this.isInlineEditor = { ...this.isInlineEditor, ...obj };
|
this.isInlineEditor = { ...this.isInlineEditor, ...obj };
|
||||||
@@ -410,10 +440,10 @@ export default {
|
|||||||
editOFF () {
|
editOFF () {
|
||||||
this.localRow[this.editingField] = this.editingContent;
|
this.localRow[this.editingField] = this.editingContent;
|
||||||
|
|
||||||
if (this.editingField === 'type') {
|
if (this.editingField === 'type' && this.editingContent !== this.originalContent) {
|
||||||
this.localRow.numLength = false;
|
this.localRow.numLength = null;
|
||||||
this.localRow.charLength = false;
|
this.localRow.charLength = null;
|
||||||
this.localRow.datePrecision = false;
|
this.localRow.datePrecision = null;
|
||||||
|
|
||||||
if (this.fieldType.length) {
|
if (this.fieldType.length) {
|
||||||
if (['integer', 'float', 'binary', 'spatial'].includes(this.fieldType.group)) this.localRow.numLength = 11;
|
if (['integer', 'float', 'binary', 'spatial'].includes(this.fieldType.group)) this.localRow.numLength = 11;
|
||||||
@@ -423,6 +453,12 @@ export default {
|
|||||||
|
|
||||||
if (!this.fieldType.collation)
|
if (!this.fieldType.collation)
|
||||||
this.localRow.collation = null;
|
this.localRow.collation = null;
|
||||||
|
|
||||||
|
if (!this.fieldType.unsigned)
|
||||||
|
this.localRow.unsigned = false;
|
||||||
|
|
||||||
|
if (!this.fieldType.zerofill)
|
||||||
|
this.localRow.zerofill = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.editingField === 'default') {
|
if (this.editingField === 'default') {
|
||||||
@@ -456,6 +492,7 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.editingContent = null;
|
this.editingContent = null;
|
||||||
|
this.originalContent = null;
|
||||||
this.editingField = null;
|
this.editingField = null;
|
||||||
},
|
},
|
||||||
hideDefaultModal () {
|
hideDefaultModal () {
|
||||||
|
@@ -1,7 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
|
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
|
||||||
<div class="workspace-query-runner column col-12">
|
<div class="workspace-query-runner column col-12">
|
||||||
<QueryEditor v-if="isSelected" :value.sync="query" />
|
<QueryEditor
|
||||||
|
v-if="isSelected"
|
||||||
|
:auto-focus="true"
|
||||||
|
:value.sync="query"
|
||||||
|
/>
|
||||||
<div class="workspace-query-runner-footer">
|
<div class="workspace-query-runner-footer">
|
||||||
<div class="workspace-query-buttons">
|
<div class="workspace-query-buttons">
|
||||||
<button
|
<button
|
||||||
@@ -17,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<div class="workspace-query-info">
|
||||||
<div v-if="resultsCount">
|
<div v-if="resultsCount">
|
||||||
{{ $t('word.results') }}: <b>{{ resultsCount }}</b>
|
{{ $t('word.results') }}: <b>{{ resultsCount.toLocaleString() }}</b>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="affectedCount">
|
<div v-if="affectedCount">
|
||||||
{{ $t('message.affectedRows') }}: <b>{{ affectedCount }}</b>
|
{{ $t('message.affectedRows') }}: <b>{{ affectedCount }}</b>
|
||||||
@@ -99,6 +103,7 @@ export default {
|
|||||||
if (!query || this.isQuering) return;
|
if (!query || this.isQuering) return;
|
||||||
this.isQuering = true;
|
this.isQuering = true;
|
||||||
this.clearTabData();
|
this.clearTabData();
|
||||||
|
this.$refs.queryTable.resetSort();
|
||||||
|
|
||||||
try { // Query Data
|
try { // Query Data
|
||||||
const params = {
|
const params = {
|
||||||
|
@@ -123,8 +123,11 @@ export default {
|
|||||||
primaryField () {
|
primaryField () {
|
||||||
return this.fields.filter(field => ['pri', 'uni'].includes(field.key))[0] || false;
|
return this.fields.filter(field => ['pri', 'uni'].includes(field.key))[0] || false;
|
||||||
},
|
},
|
||||||
|
isHardSort () {
|
||||||
|
return this.mode === 'table' && this.localResults.length === 1000;
|
||||||
|
},
|
||||||
sortedResults () {
|
sortedResults () {
|
||||||
if (this.currentSort) {
|
if (this.currentSort && !this.isHardSort) {
|
||||||
return [...this.localResults].sort((a, b) => {
|
return [...this.localResults].sort((a, b) => {
|
||||||
let modifier = 1;
|
let modifier = 1;
|
||||||
const valA = typeof a[this.currentSort] === 'string' ? a[this.currentSort].toLowerCase() : a[this.currentSort];
|
const valA = typeof a[this.currentSort] === 'string' ? a[this.currentSort].toLowerCase() : a[this.currentSort];
|
||||||
@@ -228,7 +231,6 @@ export default {
|
|||||||
return row[primaryFieldName];
|
return row[primaryFieldName];
|
||||||
},
|
},
|
||||||
setLocalResults () {
|
setLocalResults () {
|
||||||
this.resetSort();
|
|
||||||
this.localResults = this.resultsWithRows[this.resultsetIndex] && this.resultsWithRows[this.resultsetIndex].rows
|
this.localResults = this.resultsWithRows[this.resultsetIndex] && this.resultsWithRows[this.resultsetIndex].rows
|
||||||
? this.resultsWithRows[this.resultsetIndex].rows.map(item => {
|
? this.resultsWithRows[this.resultsetIndex].rows.map(item => {
|
||||||
return { ...item, _id: uidGen() };
|
return { ...item, _id: uidGen() };
|
||||||
@@ -342,6 +344,9 @@ export default {
|
|||||||
this.currentSortDir = 'asc';
|
this.currentSortDir = 'asc';
|
||||||
this.currentSort = field;
|
this.currentSort = field;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isHardSort)
|
||||||
|
this.$emit('hard-sort', { field: this.currentSort, dir: this.currentSortDir });
|
||||||
},
|
},
|
||||||
resetSort () {
|
resetSort () {
|
||||||
this.currentSort = '';
|
this.currentSort = '';
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
class="editable-field"
|
class="editable-field"
|
||||||
:value.sync="editingContent"
|
:value.sync="editingContent"
|
||||||
:key-usage="getKeyUsage(cKey)"
|
:key-usage="getKeyUsage(cKey)"
|
||||||
|
size="small"
|
||||||
@blur="editOFF"
|
@blur="editOFF"
|
||||||
/>
|
/>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@@ -157,6 +158,8 @@ export default {
|
|||||||
typeFormat (val, type, precision) {
|
typeFormat (val, type, precision) {
|
||||||
if (!val) return val;
|
if (!val) return val;
|
||||||
|
|
||||||
|
type = type.toUpperCase();
|
||||||
|
|
||||||
if (DATE.includes(type))
|
if (DATE.includes(type))
|
||||||
return moment(val).isValid() ? moment(val).format('YYYY-MM-DD') : val;
|
return moment(val).isValid() ? moment(val).format('YYYY-MM-DD') : val;
|
||||||
|
|
||||||
@@ -254,7 +257,7 @@ export default {
|
|||||||
return ['gif', 'jpg', 'png', 'bmp', 'ico', 'tif'].includes(this.contentInfo.ext);
|
return ['gif', 'jpg', 'png', 'bmp', 'ico', 'tif'].includes(this.contentInfo.ext);
|
||||||
},
|
},
|
||||||
foreignKeys () {
|
foreignKeys () {
|
||||||
return this.keyUsage.map(key => key.column);
|
return this.keyUsage.map(key => key.field);
|
||||||
},
|
},
|
||||||
isEditable () {
|
isEditable () {
|
||||||
return this.fields ? !!(this.fields[0].schema && this.fields[0].table) : false;
|
return this.fields ? !!(this.fields[0].schema && this.fields[0].table) : false;
|
||||||
@@ -274,7 +277,7 @@ export default {
|
|||||||
if (field)
|
if (field)
|
||||||
type = field.type;
|
type = field.type;
|
||||||
|
|
||||||
return type;
|
return type.toLowerCase();
|
||||||
},
|
},
|
||||||
getFieldPrecision (cKey) {
|
getFieldPrecision (cKey) {
|
||||||
let length = 0;
|
let length = 0;
|
||||||
@@ -313,7 +316,7 @@ export default {
|
|||||||
editON (event, content, field) {
|
editON (event, content, field) {
|
||||||
if (!this.isEditable) return;
|
if (!this.isEditable) return;
|
||||||
|
|
||||||
const type = this.getFieldType(field);
|
const type = this.getFieldType(field).toUpperCase(); ;
|
||||||
this.originalContent = content;
|
this.originalContent = content;
|
||||||
this.editingType = type;
|
this.editingType = type;
|
||||||
this.editingField = field;
|
this.editingField = field;
|
||||||
@@ -420,7 +423,7 @@ export default {
|
|||||||
this.$emit('select-row', event, row);
|
this.$emit('select-row', event, row);
|
||||||
},
|
},
|
||||||
getKeyUsage (keyName) {
|
getKeyUsage (keyName) {
|
||||||
return this.keyUsage.find(key => key.column === keyName);
|
return this.keyUsage.find(key => key.field === keyName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@@ -42,7 +42,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="workspace-query-info">
|
<div class="workspace-query-info">
|
||||||
<div v-if="results.length && results[0].rows">
|
<div v-if="results.length && results[0].rows">
|
||||||
{{ $t('word.results') }}: <b>{{ results[0].rows.length }}</b>
|
{{ $t('word.results') }}: <b>{{ results[0].rows.length.toLocaleString() }}</b>
|
||||||
|
</div>
|
||||||
|
<div v-if="results.length && results[0].rows && tableInfo && results[0].rows.length < tableInfo.rows">
|
||||||
|
{{ $t('word.total') }}: <b>{{ tableInfo.rows.toLocaleString() }}</b> <small>({{ $t('word.approximately') }})</small>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="workspace.breadcrumbs.database">
|
<div v-if="workspace.breadcrumbs.database">
|
||||||
{{ $t('word.schema') }}: <b>{{ workspace.breadcrumbs.database }}</b>
|
{{ $t('word.schema') }}: <b>{{ workspace.breadcrumbs.database }}</b>
|
||||||
@@ -60,6 +63,7 @@
|
|||||||
mode="table"
|
mode="table"
|
||||||
@update-field="updateField"
|
@update-field="updateField"
|
||||||
@delete-selected="deleteSelected"
|
@delete-selected="deleteSelected"
|
||||||
|
@hard-sort="hardSort"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ModalNewTableRow
|
<ModalNewTableRow
|
||||||
@@ -99,7 +103,8 @@ export default {
|
|||||||
lastTable: null,
|
lastTable: null,
|
||||||
isAddModal: false,
|
isAddModal: false,
|
||||||
autorefreshTimer: 0,
|
autorefreshTimer: 0,
|
||||||
refreshInterval: null
|
refreshInterval: null,
|
||||||
|
sortParams: {}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -117,13 +122,23 @@ export default {
|
|||||||
},
|
},
|
||||||
keyUsage () {
|
keyUsage () {
|
||||||
return this.results.length ? this.results[0].keys : [];
|
return this.results.length ? this.results[0].keys : [];
|
||||||
|
},
|
||||||
|
tableInfo () {
|
||||||
|
try {
|
||||||
|
return this.workspace.structure.find(db => db.name === this.schema).tables.find(table => table.name === this.table);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
return { rows: 0 };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
table () {
|
table () {
|
||||||
if (this.isSelected) {
|
if (this.isSelected) {
|
||||||
|
this.sortParams = {};
|
||||||
this.getTableData();
|
this.getTableData();
|
||||||
this.lastTable = this.table;
|
this.lastTable = this.table;
|
||||||
|
this.$refs.queryTable.resetSort();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isSelected (val) {
|
isSelected (val) {
|
||||||
@@ -145,7 +160,7 @@ export default {
|
|||||||
...mapActions({
|
...mapActions({
|
||||||
addNotification: 'notifications/addNotification'
|
addNotification: 'notifications/addNotification'
|
||||||
}),
|
}),
|
||||||
async getTableData () {
|
async getTableData (sortParams) {
|
||||||
if (!this.table) return;
|
if (!this.table) return;
|
||||||
this.isQuering = true;
|
this.isQuering = true;
|
||||||
|
|
||||||
@@ -156,7 +171,8 @@ export default {
|
|||||||
const params = {
|
const params = {
|
||||||
uid: this.connection.uid,
|
uid: this.connection.uid,
|
||||||
schema: this.schema,
|
schema: this.schema,
|
||||||
table: this.workspace.breadcrumbs.table
|
table: this.workspace.breadcrumbs.table,
|
||||||
|
sortParams
|
||||||
};
|
};
|
||||||
|
|
||||||
try { // Table data
|
try { // Table data
|
||||||
@@ -177,7 +193,11 @@ export default {
|
|||||||
return this.table;
|
return this.table;
|
||||||
},
|
},
|
||||||
reloadTable () {
|
reloadTable () {
|
||||||
this.getTableData();
|
this.getTableData(this.sortParams);
|
||||||
|
},
|
||||||
|
hardSort (sortParams) {
|
||||||
|
this.sortParams = sortParams;
|
||||||
|
this.getTableData(sortParams);
|
||||||
},
|
},
|
||||||
showAddModal () {
|
showAddModal () {
|
||||||
this.isAddModal = true;
|
this.isAddModal = true;
|
||||||
|
@@ -53,7 +53,16 @@ module.exports = {
|
|||||||
comment: 'Comment',
|
comment: 'Comment',
|
||||||
key: 'Key | Keys',
|
key: 'Key | Keys',
|
||||||
order: 'Order',
|
order: 'Order',
|
||||||
expression: 'Expression'
|
expression: 'Expression',
|
||||||
|
autoIncrement: 'Auto Increment',
|
||||||
|
engine: 'Engine',
|
||||||
|
field: 'Field | Fields',
|
||||||
|
approximately: 'Approximately',
|
||||||
|
total: 'Total',
|
||||||
|
table: 'Table',
|
||||||
|
discard: 'Discard',
|
||||||
|
stay: 'Stay',
|
||||||
|
author: 'Author'
|
||||||
},
|
},
|
||||||
message: {
|
message: {
|
||||||
appWelcome: 'Welcome to Antares SQL Client!',
|
appWelcome: 'Welcome to Antares SQL Client!',
|
||||||
@@ -99,7 +108,23 @@ module.exports = {
|
|||||||
zeroFill: 'Zero fill',
|
zeroFill: 'Zero fill',
|
||||||
customValue: 'Custom value',
|
customValue: 'Custom value',
|
||||||
onUpdate: 'On update',
|
onUpdate: 'On update',
|
||||||
deleteField: 'Delete field'
|
deleteField: 'Delete field',
|
||||||
|
createNewIndex: 'Create new index',
|
||||||
|
addToIndex: 'Add to index',
|
||||||
|
createNewTable: 'Create new table',
|
||||||
|
emptyTable: 'Empty table',
|
||||||
|
deleteTable: 'Delete table',
|
||||||
|
emptyCorfirm: 'Do you confirm to empty',
|
||||||
|
unsavedChanges: 'Unsaved changes',
|
||||||
|
discardUnsavedChanges: 'You have some unsaved changes. By leaving this tab these changes will be discarded.',
|
||||||
|
thereAreNoIndexes: 'There are no indexes',
|
||||||
|
thereAreNoForeign: 'There are no foreign keys',
|
||||||
|
createNewForeign: 'Create new foreign key',
|
||||||
|
referenceTable: 'Ref. table',
|
||||||
|
referenceField: 'Ref. field',
|
||||||
|
foreignFields: 'Foreign fields',
|
||||||
|
invalidDefault: 'Invalid default',
|
||||||
|
onDelete: 'On delete'
|
||||||
},
|
},
|
||||||
// Date and Time
|
// Date and Time
|
||||||
short: {
|
short: {
|
||||||
|
@@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 34 32"><path fill="#444" d="M21.576 3.59c-1.115-.994-2.465-.595-3.798.588a9.407 9.407 0 00-.591.579c-2.279 2.418-4.395 6.897-5.053 10.318.256.519.456 1.182.588 1.688.034.13.064.252.089.355.058.245.089.405.089.405s-.02-.077-.104-.321l-.055-.158a1.44 1.44 0 00-.035-.087c-.149-.346-.56-1.075-.741-1.393-.155.457-.292.884-.406 1.271.523.956.841 2.595.841 2.595s-.028-.106-.159-.477c-.117-.328-.697-1.345-.835-1.583-.235.869-.329 1.455-.244 1.598.164.277.32.754.457 1.282.309 1.189.524 2.637.524 2.637l.019.244c-.043.999-.017 2.034.06 2.97.103 1.239.295 2.303.541 2.873l.167-.091c-.361-1.122-.508-2.593-.444-4.289.097-2.593.694-5.719 1.796-8.978 1.863-4.919 4.447-8.866 6.811-10.751-2.155 1.947-5.073 8.248-5.946 10.581-.978 2.613-1.671 5.065-2.088 7.414.721-2.202 3.05-3.149 3.05-3.149s1.143-1.409 2.478-3.422c-.8.182-2.113.495-2.553.68-.649.272-.824.365-.824.365s2.102-1.28 3.905-1.86c2.48-3.906 5.182-9.456 2.461-11.884z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 34 32"><path fill="#fff" d="M21.576 3.59c-1.115-.994-2.465-.595-3.798.588a9.407 9.407 0 00-.591.579c-2.279 2.418-4.395 6.897-5.053 10.318.256.519.456 1.182.588 1.688.034.13.064.252.089.355.058.245.089.405.089.405s-.02-.077-.104-.321l-.055-.158a1.44 1.44 0 00-.035-.087c-.149-.346-.56-1.075-.741-1.393-.155.457-.292.884-.406 1.271.523.956.841 2.595.841 2.595s-.028-.106-.159-.477c-.117-.328-.697-1.345-.835-1.583-.235.869-.329 1.455-.244 1.598.164.277.32.754.457 1.282.309 1.189.524 2.637.524 2.637l.019.244c-.043.999-.017 2.034.06 2.97.103 1.239.295 2.303.541 2.873l.167-.091c-.361-1.122-.508-2.593-.444-4.289.097-2.593.694-5.719 1.796-8.978 1.863-4.919 4.447-8.866 6.811-10.751-2.155 1.947-5.073 8.248-5.946 10.581-.978 2.613-1.671 5.065-2.088 7.414.721-2.202 3.05-3.149 3.05-3.149s1.143-1.409 2.478-3.422c-.8.182-2.113.495-2.553.68-.649.272-.824.365-.824.365s2.102-1.28 3.905-1.86c2.48-3.906 5.182-9.456 2.461-11.884z"/></svg>
|
Before Width: | Height: | Size: 1004 B After Width: | Height: | Size: 1004 B |
@@ -30,6 +30,10 @@ export default class {
|
|||||||
return ipcRenderer.invoke('get-variables', uid);
|
return ipcRenderer.invoke('get-variables', uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getEngines (uid) {
|
||||||
|
return ipcRenderer.invoke('get-engines', uid);
|
||||||
|
}
|
||||||
|
|
||||||
static useSchema (params) {
|
static useSchema (params) {
|
||||||
return ipcRenderer.invoke('use-schema', params);
|
return ipcRenderer.invoke('use-schema', params);
|
||||||
}
|
}
|
||||||
|
@@ -10,6 +10,10 @@ export default class {
|
|||||||
return ipcRenderer.invoke('get-table-data', params);
|
return ipcRenderer.invoke('get-table-data', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getTableIndexes (params) {
|
||||||
|
return ipcRenderer.invoke('get-table-indexes', params);
|
||||||
|
}
|
||||||
|
|
||||||
static getKeyUsage (params) {
|
static getKeyUsage (params) {
|
||||||
return ipcRenderer.invoke('get-key-usage', params);
|
return ipcRenderer.invoke('get-key-usage', params);
|
||||||
}
|
}
|
||||||
@@ -30,7 +34,19 @@ export default class {
|
|||||||
return ipcRenderer.invoke('get-foreign-list', params);
|
return ipcRenderer.invoke('get-foreign-list', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static createTable (params) {
|
||||||
|
return ipcRenderer.invoke('create-table', params);
|
||||||
|
}
|
||||||
|
|
||||||
static alterTable (params) {
|
static alterTable (params) {
|
||||||
return ipcRenderer.invoke('alter-table', params);
|
return ipcRenderer.invoke('alter-table', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static truncateTable (params) {
|
||||||
|
return ipcRenderer.invoke('truncate-table', params);
|
||||||
|
}
|
||||||
|
|
||||||
|
static dropTable (params) {
|
||||||
|
return ipcRenderer.invoke('drop-table', params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,15 +4,22 @@
|
|||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
margin-right: 0.2rem;
|
margin-right: 0.2rem;
|
||||||
|
|
||||||
&.key-pri {
|
&.key-pri,
|
||||||
|
&.key-PRIMARY {
|
||||||
color: goldenrod;
|
color: goldenrod;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.key-uni {
|
&.key-uni,
|
||||||
|
&.key-UNIQUE {
|
||||||
color: deepskyblue;
|
color: deepskyblue;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.key-mul {
|
&.key-mul,
|
||||||
|
&.key-INDEX {
|
||||||
color: palegreen;
|
color: palegreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.key-FULLTEXT {
|
||||||
|
color: mediumvioletred;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -180,6 +180,12 @@ body {
|
|||||||
|
|
||||||
.form-select {
|
.form-select {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&.small-select {
|
||||||
|
height: 1rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
padding: 1px 0.4rem 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-select,
|
.form-select,
|
||||||
@@ -191,6 +197,11 @@ body {
|
|||||||
background-color: $bg-color-gray;
|
background-color: $bg-color-gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-input.is-error,
|
||||||
|
.form-select.is-error {
|
||||||
|
background-color: $bg-color-gray;
|
||||||
|
}
|
||||||
|
|
||||||
.form-input:not(:placeholder-shown):invalid:focus {
|
.form-input:not(:placeholder-shown):invalid:focus {
|
||||||
background: $bg-color-gray;
|
background: $bg-color-gray;
|
||||||
}
|
}
|
||||||
@@ -223,3 +234,7 @@ body {
|
|||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
color: $body-font-color;
|
||||||
|
}
|
||||||
|
@@ -3,14 +3,17 @@ import Connection from '@/ipc-api/Connection';
|
|||||||
import Database from '@/ipc-api/Database';
|
import Database from '@/ipc-api/Database';
|
||||||
import { uidGen } from 'common/libs/uidGen';
|
import { uidGen } from 'common/libs/uidGen';
|
||||||
const tabIndex = [];
|
const tabIndex = [];
|
||||||
let lastSchema = '';
|
let lastBreadcrumbs = {};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
strict: true,
|
strict: true,
|
||||||
state: {
|
state: {
|
||||||
workspaces: [],
|
workspaces: [],
|
||||||
selected_workspace: null
|
selected_workspace: null,
|
||||||
|
has_unsaved_changes: false,
|
||||||
|
is_unsaved_discard_modal: false,
|
||||||
|
pending_breadcrumbs: {}
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
getSelected: state => {
|
getSelected: state => {
|
||||||
@@ -35,18 +38,22 @@ export default {
|
|||||||
return state.workspaces
|
return state.workspaces
|
||||||
.filter(workspace => workspace.connected)
|
.filter(workspace => workspace.connected)
|
||||||
.map(workspace => workspace.uid);
|
.map(workspace => workspace.uid);
|
||||||
|
},
|
||||||
|
isUnsavedDiscardModal: state => {
|
||||||
|
return state.is_unsaved_discard_modal;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
SELECT_WORKSPACE (state, uid) {
|
SELECT_WORKSPACE (state, uid) {
|
||||||
state.selected_workspace = uid;
|
state.selected_workspace = uid;
|
||||||
},
|
},
|
||||||
ADD_CONNECTED (state, { uid, client, dataTypes, structure }) {
|
ADD_CONNECTED (state, { uid, client, dataTypes, indexTypes, structure }) {
|
||||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
|
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
|
||||||
? {
|
? {
|
||||||
...workspace,
|
...workspace,
|
||||||
client,
|
client,
|
||||||
dataTypes,
|
dataTypes,
|
||||||
|
indexTypes,
|
||||||
structure,
|
structure,
|
||||||
connected: true
|
connected: true
|
||||||
}
|
}
|
||||||
@@ -85,6 +92,14 @@ export default {
|
|||||||
}
|
}
|
||||||
: workspace);
|
: workspace);
|
||||||
},
|
},
|
||||||
|
REFRESH_ENGINES (state, { uid, engines }) {
|
||||||
|
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
|
||||||
|
? {
|
||||||
|
...workspace,
|
||||||
|
engines
|
||||||
|
}
|
||||||
|
: workspace);
|
||||||
|
},
|
||||||
ADD_WORKSPACE (state, workspace) {
|
ADD_WORKSPACE (state, workspace) {
|
||||||
state.workspaces.push(workspace);
|
state.workspaces.push(workspace);
|
||||||
},
|
},
|
||||||
@@ -96,11 +111,10 @@ export default {
|
|||||||
}
|
}
|
||||||
: workspace);
|
: workspace);
|
||||||
},
|
},
|
||||||
NEW_TAB (state, uid) {
|
NEW_TAB (state, { uid, tab }) {
|
||||||
tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1;
|
tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1;
|
||||||
|
|
||||||
const newTab = {
|
const newTab = {
|
||||||
uid: uidGen('T'),
|
uid: tab,
|
||||||
index: tabIndex[uid],
|
index: tabIndex[uid],
|
||||||
selected: false,
|
selected: false,
|
||||||
type: 'query',
|
type: 'query',
|
||||||
@@ -166,6 +180,15 @@ export default {
|
|||||||
else
|
else
|
||||||
return workspace;
|
return workspace;
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
SET_UNSAVED_CHANGES (state, val) {
|
||||||
|
state.has_unsaved_changes = !!val;
|
||||||
|
},
|
||||||
|
SET_UNSAVED_DISCARD_MODAL (state, val) {
|
||||||
|
state.is_unsaved_discard_modal = !!val;
|
||||||
|
},
|
||||||
|
SET_PENDING_BREADCRUMBS (state, payload) {
|
||||||
|
state.pending_breadcrumbs = payload;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
@@ -179,21 +202,25 @@ export default {
|
|||||||
dispatch('notifications/addNotification', { status, message: response }, { root: true });
|
dispatch('notifications/addNotification', { status, message: response }, { root: true });
|
||||||
else {
|
else {
|
||||||
let dataTypes = [];
|
let dataTypes = [];
|
||||||
|
let indexTypes = [];
|
||||||
|
|
||||||
switch (connection.client) {
|
switch (connection.client) {
|
||||||
case 'mysql':
|
case 'mysql':
|
||||||
case 'maria':
|
case 'maria':
|
||||||
dataTypes = require('common/data-types/mysql');
|
dataTypes = require('common/data-types/mysql');
|
||||||
|
indexTypes = require('common/index-types/mysql');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
commit('ADD_CONNECTED', {
|
commit('ADD_CONNECTED', {
|
||||||
uid: connection.uid,
|
uid: connection.uid,
|
||||||
client: connection.client,
|
client: connection.client,
|
||||||
dataTypes,
|
dataTypes,
|
||||||
|
indexTypes,
|
||||||
structure: response
|
structure: response
|
||||||
});
|
});
|
||||||
dispatch('refreshCollations', connection.uid);
|
dispatch('refreshCollations', connection.uid);
|
||||||
dispatch('refreshVariables', connection.uid);
|
dispatch('refreshVariables', connection.uid);
|
||||||
|
dispatch('refreshEngines', connection.uid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
@@ -236,6 +263,18 @@ export default {
|
|||||||
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async refreshEngines ({ dispatch, commit }, uid) {
|
||||||
|
try {
|
||||||
|
const { status, response } = await Database.getEngines(uid);
|
||||||
|
if (status === 'error')
|
||||||
|
dispatch('notifications/addNotification', { status, message: response }, { root: true });
|
||||||
|
else
|
||||||
|
commit('REFRESH_ENGINES', { uid, engines: response });
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
dispatch('notifications/addNotification', { status: 'error', message: err.stack }, { root: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
removeConnected ({ commit }, uid) {
|
removeConnected ({ commit }, uid) {
|
||||||
Connection.disconnect(uid);
|
Connection.disconnect(uid);
|
||||||
commit('REMOVE_CONNECTED', uid);
|
commit('REMOVE_CONNECTED', uid);
|
||||||
@@ -268,17 +307,40 @@ export default {
|
|||||||
|
|
||||||
if (getters.getWorkspace(uid).tabs.length < 3)
|
if (getters.getWorkspace(uid).tabs.length < 3)
|
||||||
dispatch('newTab', uid);
|
dispatch('newTab', uid);
|
||||||
|
|
||||||
|
dispatch('setUnsavedChanges', false);
|
||||||
},
|
},
|
||||||
changeBreadcrumbs ({ commit, getters }, payload) {
|
changeBreadcrumbs ({ state, commit, getters }, payload) {
|
||||||
if (lastSchema !== payload.schema) {
|
if (state.has_unsaved_changes) {
|
||||||
Database.useSchema({ uid: getters.getSelected, schema: payload.schema });
|
commit('SET_UNSAVED_DISCARD_MODAL', true);
|
||||||
lastSchema = payload.schema;
|
commit('SET_PENDING_BREADCRUMBS', payload);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
commit('CHANGE_BREADCRUMBS', { uid: getters.getSelected, breadcrumbs: payload });
|
const breadcrumbsObj = {
|
||||||
|
schema: null,
|
||||||
|
table: null,
|
||||||
|
trigger: null,
|
||||||
|
procedure: null,
|
||||||
|
scheduler: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasLastChildren = Object.keys(lastBreadcrumbs).filter(b => b !== 'schema').some(b => lastBreadcrumbs[b]);
|
||||||
|
const hasChildren = Object.keys(payload).filter(b => b !== 'schema').some(b => payload[b]);
|
||||||
|
|
||||||
|
if (lastBreadcrumbs.schema === payload.schema && hasLastChildren && !hasChildren) return;
|
||||||
|
|
||||||
|
if (lastBreadcrumbs.schema !== payload.schema)
|
||||||
|
Database.useSchema({ uid: getters.getSelected, schema: payload.schema });
|
||||||
|
|
||||||
|
commit('CHANGE_BREADCRUMBS', { uid: getters.getSelected, breadcrumbs: { ...breadcrumbsObj, ...payload } });
|
||||||
|
lastBreadcrumbs = { ...breadcrumbsObj, ...payload };
|
||||||
},
|
},
|
||||||
newTab ({ commit }, uid) {
|
newTab ({ commit }, uid) {
|
||||||
commit('NEW_TAB', uid);
|
const tab = uidGen('T');
|
||||||
|
|
||||||
|
commit('NEW_TAB', { uid, tab });
|
||||||
|
commit('SELECT_TAB', { uid, tab });
|
||||||
},
|
},
|
||||||
removeTab ({ commit }, payload) {
|
removeTab ({ commit }, payload) {
|
||||||
commit('REMOVE_TAB', payload);
|
commit('REMOVE_TAB', payload);
|
||||||
@@ -291,6 +353,18 @@ export default {
|
|||||||
},
|
},
|
||||||
setTabKeyUsage ({ commit }, payload) {
|
setTabKeyUsage ({ commit }, payload) {
|
||||||
commit('SET_TAB_KEY_USAGE', payload);
|
commit('SET_TAB_KEY_USAGE', payload);
|
||||||
|
},
|
||||||
|
setUnsavedChanges ({ commit }, val) {
|
||||||
|
commit('SET_UNSAVED_CHANGES', val);
|
||||||
|
},
|
||||||
|
discardUnsavedChanges ({ state, commit, dispatch }) {
|
||||||
|
dispatch('setUnsavedChanges', false);
|
||||||
|
dispatch('changeBreadcrumbs', state.pending_breadcrumbs);
|
||||||
|
commit('SET_UNSAVED_DISCARD_MODAL', false);
|
||||||
|
commit('SET_PENDING_BREADCRUMBS', {});
|
||||||
|
},
|
||||||
|
closeUnsavedChangesModal ({ commit }) {
|
||||||
|
commit('SET_UNSAVED_DISCARD_MODAL', false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user