mirror of https://github.com/Fabio286/antares.git
Compare commits
46 Commits
v0.7.22-be
...
master
Author | SHA1 | Date |
---|---|---|
Fabio Di Stasio | bd4502ee47 | |
allcontributors[bot] | 93de974b09 | |
allcontributors[bot] | 949bf4cbcb | |
Fabio Di Stasio | 40bf9a040a | |
tianci | 978b55fdb1 | |
tianci | 098d4e96d6 | |
Fabio Di Stasio | 957cb9e1a5 | |
Fabio Di Stasio | 09c274a724 | |
Fabio Di Stasio | 9bcd874e80 | |
Fabio Di Stasio | ece2ee05cc | |
Fabio Di Stasio | 058fc2fc0b | |
Fabio Di Stasio | 33bbc0e7e6 | |
Fabio Di Stasio | 23c59b4d4e | |
Fabio Di Stasio | 6600197b82 | |
Fabio Di Stasio | 33203aeb04 | |
Fabio Di Stasio | f4f385589f | |
Fabio Di Stasio | 0565ae1204 | |
Fabio Di Stasio | 258fbc81f7 | |
Fabio Di Stasio | 8d8650fbe7 | |
Fabio Di Stasio | af2812f2b0 | |
allcontributors[bot] | d163cbfac8 | |
allcontributors[bot] | 4537d96f3e | |
Fabio Di Stasio | 099a71a189 | |
Fabio Di Stasio | e7efb9c616 | |
Fabio Di Stasio | a752dcb6a9 | |
bagusindrayana | c1e58eb695 | |
bagusindrayana | f7204dc0ae | |
bagusindrayana | 6b56c60b68 | |
Fabio Di Stasio | 1875e895ae | |
Fabio Di Stasio | 2064294119 | |
Fabio Di Stasio | 62e3115860 | |
Fabio Di Stasio | 9aef287a98 | |
Fabio Di Stasio | 65ec4c5da6 | |
Fabio Di Stasio | e19118982b | |
Fabio Di Stasio | 11f130d91c | |
Yaskur | 0bb5cedda6 | |
Fabio Di Stasio | de9dac3e8a | |
Fabio Di Stasio | dd5b41716a | |
Fabio Di Stasio | 86acb390ac | |
Fabio Di Stasio | 2884ec3dd6 | |
Fabio Di Stasio | b542a09c01 | |
Fabio Di Stasio | 6d94a04b67 | |
Fabio Di Stasio | 8500fc40a1 | |
Fabio Di Stasio | 586f901bae | |
Fabio Di Stasio | 6d002efaf5 | |
Fabio Di Stasio | 58be1abf5f |
|
@ -266,6 +266,24 @@
|
|||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "bagusindrayana",
|
||||
"name": "Bagus Indrayana",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/36830534?v=4",
|
||||
"profile": "https://github.com/bagusindrayana",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "penguinlab",
|
||||
"name": "Naoki Ishikawa",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/10959317?v=4",
|
||||
"profile": "https://github.com/penguinlab",
|
||||
"contributions": [
|
||||
"translation"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: [fabio286]
|
||||
github: [antares-sql,fabio286]
|
||||
patreon: #fabio286
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
|
@ -9,4 +9,4 @@ community_bridge: # Replace with a single Community Bridge project-name e.g., cl
|
|||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: ['https://paypal.me/fabiodistasio']
|
||||
custom: ['https://paypal.me/fabiodistasio']
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
npx --no -- commitlint --edit $1
|
|
@ -0,0 +1 @@
|
|||
npm run lint
|
|
@ -5,14 +5,14 @@
|
|||
],
|
||||
"fix": true,
|
||||
"formatter": "verbose",
|
||||
"customSyntax": "postcss-html",
|
||||
"plugins": [
|
||||
"stylelint-scss"
|
||||
],
|
||||
"rules": {
|
||||
"at-rule-no-unknown": null,
|
||||
"no-descending-specificity": null,
|
||||
"font-family-no-missing-generic-family-keyword": null,
|
||||
"declaration-colon-newline-after": "always-multi-line"
|
||||
"font-family-no-missing-generic-family-keyword": null
|
||||
},
|
||||
"syntax": "scss"
|
||||
}
|
73
CHANGELOG.md
73
CHANGELOG.md
|
@ -2,6 +2,79 @@
|
|||
|
||||
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.7.24](https://github.com/antares-sql/antares/compare/v0.7.24-beta.1...v0.7.24) (2024-05-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* missing accent color change ([09c274a](https://github.com/antares-sql/antares/commit/09c274a724b5020efc650aaf7eecb2404343a6fc))
|
||||
|
||||
### [0.7.24-beta.1](https://github.com/antares-sql/antares/compare/v0.7.24-beta.0...v0.7.24-beta.1) (2024-04-30)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* accent color based on folder color, closes [#762](https://github.com/antares-sql/antares/issues/762) ([058fc2f](https://github.com/antares-sql/antares/commit/058fc2fc0b34cde5aa19233a4a999ef3624dae71))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **PostgreSQL:** better handle connection errors, should fix [#794](https://github.com/antares-sql/antares/issues/794) ([33bbc0e](https://github.com/antares-sql/antares/commit/33bbc0e7e6be370c944e979a34ab2cb19562d1e3))
|
||||
* **PostgreSQL:** issue with similar tabs on differend databases ([23c59b4](https://github.com/antares-sql/antares/commit/23c59b4d4e8f250acad75f54d157c7c162e1c4f8))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **UI:** hide "insert row" button in read-only mode, closes [#695](https://github.com/antares-sql/antares/issues/695) ([6600197](https://github.com/antares-sql/antares/commit/6600197b8286ced4c79378883594d21e69a83d8c))
|
||||
* **UI:** improvements on light theme ([ece2ee0](https://github.com/antares-sql/antares/commit/ece2ee05cc90a58c1926e882e3ccf4f057f02d68))
|
||||
|
||||
### [0.7.24-beta.0](https://github.com/antares-sql/antares/compare/v0.7.23...v0.7.24-beta.0) (2024-04-12)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add shortcut open,save, and save as file ([6b56c60](https://github.com/antares-sql/antares/commit/6b56c60b68647bc7182548a137cccc3413e3fbd5))
|
||||
* add translation for open,save, and save as file ([f7204dc](https://github.com/antares-sql/antares/commit/f7204dc0ae721534eaefbde097d1c26c1d72ad41))
|
||||
* open,save, and save as file in query tab ([c1e58eb](https://github.com/antares-sql/antares/commit/c1e58eb695de78fbf1d2b26c608692f0962373df))
|
||||
* unsaved file reminder closing file tabs ([8d8650f](https://github.com/antares-sql/antares/commit/8d8650fbe76c79fd66be857d049b3baaa9ab1f9f))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **translation:** missing translation for "Open notes" shortcut ([0565ae1](https://github.com/antares-sql/antares/commit/0565ae12042901b9d67fe3e0ea269562ec444994))
|
||||
|
||||
### [0.7.23](https://github.com/antares-sql/antares/compare/v0.7.23-beta.1...v0.7.23) (2024-04-07)
|
||||
|
||||
### [0.7.23-beta.1](https://github.com/antares-sql/antares/compare/v0.7.23-beta.0...v0.7.23-beta.1) (2024-04-02)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add the page reference in the export file name, closes [#772](https://github.com/antares-sql/antares/issues/772) ([2064294](https://github.com/antares-sql/antares/commit/2064294119ed9dfab2a9968dfb5b35d52e2ae03b))
|
||||
* move connections out of folder from context menu, related to [#773](https://github.com/antares-sql/antares/issues/773) ([62e3115](https://github.com/antares-sql/antares/commit/62e311586073ae7ee4896305198c7168f637c1af))
|
||||
* move connections to folders from context menu, related to [#773](https://github.com/antares-sql/antares/issues/773) ([9aef287](https://github.com/antares-sql/antares/commit/9aef287a983754158cdbdc9b2a72db9ab82f76c8))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* bad format of timestamp fields on CSV export, fixes 776 ([65ec4c5](https://github.com/antares-sql/antares/commit/65ec4c5da6187a7ab2dfff59326cd12bfa788c3b))
|
||||
|
||||
### [0.7.23-beta.0](https://github.com/antares-sql/antares/compare/v0.7.22...v0.7.23-beta.0) (2024-03-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* CSV export does not escape strings when needed, fixes [#770](https://github.com/antares-sql/antares/issues/770) ([dd5b417](https://github.com/antares-sql/antares/commit/dd5b41716a10cf9500f2c611b232f5b5b0756a68))
|
||||
* query result sort not working with aliased tables, fixes [#765](https://github.com/antares-sql/antares/issues/765) ([de9dac3](https://github.com/antares-sql/antares/commit/de9dac3e8abf3b3261f8c54c88cf2386a5be2207))
|
||||
* shortcut not working on mac os ([0bb5ced](https://github.com/antares-sql/antares/commit/0bb5cedda6a67ccbeea8c127b799f533395101a2))
|
||||
|
||||
### [0.7.22](https://github.com/antares-sql/antares/compare/v0.7.22-beta.2...v0.7.22) (2024-02-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* delete record modal pressing del when editing a field, fixes [#767](https://github.com/antares-sql/antares/issues/767) ([586f901](https://github.com/antares-sql/antares/commit/586f901bae9a80c0e53ac1d804cbae3f05e26d8e))
|
||||
|
||||
### [0.7.22-beta.2](https://github.com/antares-sql/antares/compare/v0.7.22-beta.1...v0.7.22-beta.2) (2024-02-18)
|
||||
|
||||
|
||||
|
|
|
@ -101,7 +101,7 @@ On macOS you can run `.dmg` distribution following [this guide](https://support.
|
|||
|
||||
- 🌍 [Translate Antares](https://github.com/Fabio286/antares/wiki/Translate-Antares)
|
||||
- 📖 [Contributors Guide](https://github.com/Fabio286/antares/wiki/Contributors-Guide)
|
||||
- 🚧 [Project Board](https://github.com/antares-sql/antares/projects/1)
|
||||
- 🚧 [Project Board](https://github.com/orgs/antares-sql/projects/3/views/2)
|
||||
|
||||
## Contributors ✨
|
||||
|
||||
|
@ -148,6 +148,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Lawondyss"><img src="https://avatars.githubusercontent.com/u/272130?v=4?s=100" width="100px;" alt="Ladislav Vondráček"/><br /><sub><b>Ladislav Vondráček</b></sub></a><br /><a href="#translation-Lawondyss" title="Translation">🌍</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/zvlad"><img src="https://avatars.githubusercontent.com/u/9055134?v=4?s=100" width="100px;" alt="Vladyslav"/><br /><sub><b>Vladyslav</b></sub></a><br /><a href="#translation-zvlad" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/bagusindrayana"><img src="https://avatars.githubusercontent.com/u/36830534?v=4?s=100" width="100px;" alt="Bagus Indrayana"/><br /><sub><b>Bagus Indrayana</b></sub></a><br /><a href="https://github.com/antares-sql/antares/commits?author=bagusindrayana" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/penguinlab"><img src="https://avatars.githubusercontent.com/u/10959317?v=4?s=100" width="100px;" alt="Naoki Ishikawa"/><br /><sub><b>Naoki Ishikawa</b></sub></a><br /><a href="#translation-penguinlab" title="Translation">🌍</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
module.exports = {
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
rules: {
|
||||
// TODO Add Scope Enum Here
|
||||
// 'scope-enum': [2, 'always', ['yourscope', 'yourscope']],
|
||||
'type-enum': [
|
||||
2,
|
||||
'always',
|
||||
[
|
||||
'feat',
|
||||
'fix',
|
||||
'docs',
|
||||
'chore',
|
||||
'style',
|
||||
'refactor',
|
||||
'build',
|
||||
'ci',
|
||||
'test',
|
||||
'revert',
|
||||
'perf'
|
||||
]
|
||||
],
|
||||
'subject-case': [
|
||||
2,
|
||||
'never',
|
||||
[
|
||||
'upper-case',
|
||||
'pascal-case',
|
||||
'start-case'
|
||||
]
|
||||
]
|
||||
}
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "antares",
|
||||
"productName": "Antares",
|
||||
"version": "0.7.22-beta.2",
|
||||
"version": "0.7.24",
|
||||
"description": "A modern, fast and productivity driven SQL client with a focus in UX.",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/antares-sql/antares.git",
|
||||
|
@ -25,7 +25,8 @@
|
|||
"lint": "eslint . --ext .js,.ts,.vue && stylelint \"./src/**/*.{css,scss,sass,vue}\"",
|
||||
"lint:fix": "eslint . --ext .js,.ts,.vue --fix && stylelint \"./src/**/*.{css,scss,sass,vue}\" --fix",
|
||||
"contributors:add": "all-contributors add",
|
||||
"contributors:generate": "all-contributors generate"
|
||||
"contributors:generate": "all-contributors generate",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"author": "Fabio Di Stasio <info@fabiodistasio.it>",
|
||||
"main": "./dist/main.js",
|
||||
|
@ -179,6 +180,8 @@
|
|||
"@babel/eslint-parser": "~7.15.7",
|
||||
"@babel/preset-env": "~7.15.8",
|
||||
"@babel/preset-typescript": "~7.16.7",
|
||||
"@commitlint/cli": "~19.0.3",
|
||||
"@commitlint/config-conventional": "~19.0.3",
|
||||
"@playwright/test": "~1.28.1",
|
||||
"@types/better-sqlite3": "~7.5.0",
|
||||
"@types/leaflet": "~1.7.9",
|
||||
|
@ -198,6 +201,7 @@
|
|||
"eslint-plugin-promise": "~5.2.0",
|
||||
"eslint-plugin-simple-import-sort": "~10.0.0",
|
||||
"eslint-plugin-vue": "~8.0.3",
|
||||
"husky": "~9.0.11",
|
||||
"playwright": "~1.28.1",
|
||||
"playwright-core": "~1.28.1",
|
||||
"standard-version": "~9.3.1",
|
||||
|
|
|
@ -6,6 +6,9 @@ export const shortcutEvents: Record<string, { l18n: string; l18nParam?: string |
|
|||
'kill-query': { l18n: 'database.killQuery', context: 'tab' },
|
||||
'query-history': { l18n: 'database.queryHistory', context: 'tab' },
|
||||
'clear-query': { l18n: 'database.clearQuery', context: 'tab' },
|
||||
// 'save-file': { l18n: 'application.saveFile', context: 'tab' },
|
||||
'open-file': { l18n: 'application.openFile', context: 'tab' },
|
||||
'save-file-as': { l18n: 'application.saveFileAs', context: 'tab' },
|
||||
'next-tab': { l18n: 'application.nextTab' },
|
||||
'prev-tab': { l18n: 'application.previousTab' },
|
||||
'open-all-connections': { l18n: 'application.openAllConnections' },
|
||||
|
@ -16,7 +19,7 @@ export const shortcutEvents: Record<string, { l18n: string; l18nParam?: string |
|
|||
'save-content': { l18n: 'application.saveContent' },
|
||||
'create-connection': { l18n: 'connection.createNewConnection' },
|
||||
'open-settings': { l18n: 'application.openSettings' },
|
||||
'open-scratchpad': { l18n: 'application.openScratchpad' }
|
||||
'open-scratchpad': { l18n: 'application.openNotes' }
|
||||
};
|
||||
|
||||
interface ShortcutRecord {
|
||||
|
@ -119,6 +122,21 @@ const shortcuts: ShortcutRecord[] = [
|
|||
event: 'toggle-console',
|
||||
keys: ['CommandOrControl+`'],
|
||||
os: ['darwin', 'linux', 'win32']
|
||||
},
|
||||
// {
|
||||
// event: 'save-file',
|
||||
// keys: ['CommandOrControl+S'],
|
||||
// os: ['darwin', 'linux', 'win32']
|
||||
// },
|
||||
{
|
||||
event: 'open-file',
|
||||
keys: ['CommandOrControl+O'],
|
||||
os: ['darwin', 'linux', 'win32']
|
||||
},
|
||||
{
|
||||
event: 'save-file-as',
|
||||
keys: ['Shift+CommandOrControl+S'],
|
||||
os: ['darwin', 'linux', 'win32']
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { app, dialog, ipcMain, safeStorage } from 'electron';
|
||||
import * as Store from 'electron-store';
|
||||
import * as fs from 'fs';
|
||||
|
||||
import { validateSender } from '../libs/misc/validateSender';
|
||||
import { ShortcutRegister } from '../libs/ShortcutRegister';
|
||||
|
@ -52,6 +53,11 @@ export default () => {
|
|||
return dialog.showOpenDialog(options);
|
||||
});
|
||||
|
||||
ipcMain.handle('show-save-dialog', (event, options) => {
|
||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||
return dialog.showSaveDialog(options);
|
||||
});
|
||||
|
||||
ipcMain.handle('get-download-dir-path', (event) => {
|
||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||
return app.getPath('downloads');
|
||||
|
@ -80,4 +86,26 @@ export default () => {
|
|||
const shortCutRegister = ShortcutRegister.getInstance();
|
||||
shortCutRegister.unregister();
|
||||
});
|
||||
|
||||
ipcMain.handle('read-file', (event, filePath) => {
|
||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
return content;
|
||||
}
|
||||
catch (error) {
|
||||
return { status: 'error', response: error.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('write-file', (event, filePath, content) => {
|
||||
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };
|
||||
try {
|
||||
fs.writeFileSync(filePath, content, 'utf-8');
|
||||
return { status: 'success' };
|
||||
}
|
||||
catch (error) {
|
||||
return { status: 'error', response: error.toString() };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -70,23 +70,21 @@ export class ShortcutRegister {
|
|||
}
|
||||
|
||||
private setLocalShortcuts () {
|
||||
const isMenuVisible = process.platform === 'darwin';
|
||||
const submenu = [];
|
||||
for (const shortcut of this.shortcuts) {
|
||||
if (shortcut.os.includes(process.platform)) {
|
||||
for (const key of shortcut.keys) {
|
||||
try {
|
||||
this._menu.append(new MenuItem({
|
||||
label: '.',
|
||||
visible: false,
|
||||
submenu: [{
|
||||
label: String(key),
|
||||
accelerator: key,
|
||||
visible: false,
|
||||
click: () => {
|
||||
this._mainWindow.webContents.send(shortcut.event);
|
||||
if (isDevelopment) console.log('LOCAL EVENT:', shortcut);
|
||||
}
|
||||
}]
|
||||
}));
|
||||
submenu.push({
|
||||
label: String(shortcut.event),
|
||||
accelerator: key,
|
||||
visible: isMenuVisible,
|
||||
click: () => {
|
||||
this._mainWindow.webContents.send(shortcut.event);
|
||||
if (isDevelopment) console.log('LOCAL EVENT:', shortcut);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
if (isDevelopment) console.log(error);
|
||||
|
@ -96,6 +94,11 @@ export class ShortcutRegister {
|
|||
}
|
||||
}
|
||||
}
|
||||
this._menu.append(new MenuItem({
|
||||
label: 'Shortcut',
|
||||
visible: isMenuVisible,
|
||||
submenu
|
||||
}));
|
||||
}
|
||||
|
||||
private setGlobalShortcuts () {
|
||||
|
|
|
@ -210,6 +210,10 @@ export class PostgreSQLClient extends BaseClient {
|
|||
if (this._params.readonly)
|
||||
await connection.query('SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY');
|
||||
|
||||
connection.on('error', err => { // Intercepts errors and converts to rejections
|
||||
Promise.reject(err);
|
||||
});
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
|
|
|
@ -127,8 +127,8 @@ app.on('ready', async () => {
|
|||
if (isWindows)
|
||||
mainWindow.show();
|
||||
|
||||
// if (isDevelopment)
|
||||
// mainWindow.webContents.openDevTools();
|
||||
if (isDevelopment && !isWindows)// Because on Windows you can open devtools from title-bar
|
||||
mainWindow.webContents.openDevTools();
|
||||
|
||||
process.on('uncaughtException', error => {
|
||||
mainWindow.webContents.send('unhandled-exception', error);
|
||||
|
|
|
@ -99,7 +99,7 @@ onMounted(() => {
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: $primary-color;
|
||||
background: var(--primary-color);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 5px 1px darken($body-font-color-dark, 40%);
|
||||
}
|
||||
|
|
|
@ -360,7 +360,7 @@ onBeforeUnmount(() => {
|
|||
outline: none;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 3px 0.1rem rgba($primary-color, 80%);
|
||||
box-shadow: 0 0 3px 0.1rem rgba(var(--primary-color), 80%);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -204,7 +204,7 @@ onBeforeUnmount(() => {
|
|||
cursor: pointer;
|
||||
|
||||
&.selected {
|
||||
outline: 2px solid $primary-color;
|
||||
outline: 2px solid var(--primary-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -287,7 +287,7 @@ onBeforeUnmount(() => {
|
|||
max-width: 100%;
|
||||
display: inline-block;
|
||||
font-size: 100%;
|
||||
// color: $primary-color;
|
||||
// color: var(--primary-color);
|
||||
opacity: 0.8;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
|
|
@ -703,7 +703,7 @@ onBeforeUnmount(() => {
|
|||
|
||||
&.selected {
|
||||
img {
|
||||
box-shadow: 0 0 0 3px $primary-color;
|
||||
box-shadow: 0 0 0 3px var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -731,7 +731,7 @@ onBeforeUnmount(() => {
|
|||
|
||||
.badge-update::after {
|
||||
bottom: initial;
|
||||
background: $primary-color;
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
|
|
|
@ -409,7 +409,7 @@ defineExpose({ editor });
|
|||
position: absolute;
|
||||
left: 3px;
|
||||
top: 2px;
|
||||
color: $primary-color;
|
||||
color: var(--primary-color);
|
||||
display: inline-block;
|
||||
font: normal normal normal 24px/1 "Material Design Icons", sans-serif;
|
||||
font-size: inherit;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<div class="tile-icon">
|
||||
<BaseIcon
|
||||
:icon-name="note.type === 'query'
|
||||
? 'mdiStarOutline'
|
||||
? 'mdiHeartOutline'
|
||||
: note.type === 'todo'
|
||||
? note.isArchived
|
||||
? 'mdiCheckboxMarkedOutline'
|
||||
|
|
|
@ -15,6 +15,56 @@
|
|||
:size="18"
|
||||
/> {{ t('connection.disconnect') }}</span>
|
||||
</div>
|
||||
<div v-if="!contextConnection.isFolder" class="context-element">
|
||||
<span class="d-flex">
|
||||
<BaseIcon
|
||||
class="text-light mt-1 mr-1"
|
||||
icon-name="mdiFolderMove"
|
||||
:size="18"
|
||||
/> {{ t('general.moveTo') }}</span>
|
||||
<BaseIcon
|
||||
class="text-light ml-1"
|
||||
icon-name="mdiChevronRight"
|
||||
:size="18"
|
||||
/>
|
||||
<div class="context-submenu">
|
||||
<div class="context-element" @click.stop="moveToFolder(null)">
|
||||
<span class="d-flex">
|
||||
<BaseIcon
|
||||
class="text-light mt-1 mr-1"
|
||||
icon-name="mdiFolderPlus"
|
||||
:size="18"
|
||||
/> {{ t('application.newFolder') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="folder in filteredFolders"
|
||||
:key="folder.uid"
|
||||
class="context-element"
|
||||
@click.stop="moveToFolder(folder.uid)"
|
||||
>
|
||||
<span class="d-flex">
|
||||
<BaseIcon
|
||||
class="text-light mt-1 mr-1"
|
||||
icon-name="mdiFolder"
|
||||
:size="18"
|
||||
:style="`color: ${folder.color}!important`"
|
||||
/> {{ folder.name || t('general.folder') }}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="isInFolder"
|
||||
class="context-element"
|
||||
@click="outOfFolder"
|
||||
>
|
||||
<span class="d-flex">
|
||||
<BaseIcon
|
||||
class="text-light mt-1 mr-1"
|
||||
icon-name="mdiFolderOff"
|
||||
:size="18"
|
||||
/> {{ t('application.outOfFolder') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="context-element" @click.stop="showAppearanceModal">
|
||||
<span class="d-flex">
|
||||
<BaseIcon
|
||||
|
@ -79,6 +129,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import { uidGen } from 'common/libs/uidGen';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, Prop, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
|
@ -98,9 +149,14 @@ const {
|
|||
getConnectionByUid,
|
||||
getConnectionName,
|
||||
addConnection,
|
||||
deleteConnection
|
||||
deleteConnection,
|
||||
addFolder,
|
||||
addToFolder,
|
||||
removeFromFolders
|
||||
} = connectionsStore;
|
||||
|
||||
const { getFolders: folders } = storeToRefs(connectionsStore);
|
||||
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
|
||||
const {
|
||||
|
@ -121,6 +177,8 @@ const isConnectionEdit = ref(false);
|
|||
|
||||
const connectionName = computed(() => props.contextConnection.name || getConnectionName(props.contextConnection.uid) || t('general.folder', 1));
|
||||
const isConnected = computed(() => getWorkspace(props.contextConnection.uid)?.connectionStatus === 'connected');
|
||||
const filteredFolders = computed(() => folders.value.filter(f => !f.connections.includes(props.contextConnection.uid)));
|
||||
const isInFolder = computed(() => folders.value.some(f => f.connections.includes(props.contextConnection.uid)));
|
||||
|
||||
const confirmDeleteConnection = () => {
|
||||
if (isConnected.value)
|
||||
|
@ -129,6 +187,27 @@ const confirmDeleteConnection = () => {
|
|||
closeContext();
|
||||
};
|
||||
|
||||
const moveToFolder = (folderUid?: string) => {
|
||||
if (!folderUid) {
|
||||
addFolder({
|
||||
connections: [props.contextConnection.uid]
|
||||
});
|
||||
}
|
||||
else {
|
||||
addToFolder({
|
||||
folder: folderUid,
|
||||
connection: props.contextConnection.uid
|
||||
});
|
||||
}
|
||||
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const outOfFolder = () => {
|
||||
removeFromFolders(props.contextConnection.uid);
|
||||
closeContext();
|
||||
};
|
||||
|
||||
const duplicateConnection = () => {
|
||||
let connectionCopy = getConnectionByUid(props.contextConnection.uid);
|
||||
connectionCopy = {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div
|
||||
id="footer"
|
||||
:class="[lightColors.includes(footerColor) ? 'text-dark' : 'text-light']"
|
||||
:style="`background-color: ${footerColor};`"
|
||||
:class="[lightColors.includes(accentColor) ? 'text-dark' : 'text-light']"
|
||||
:style="`background-color: ${accentColor};`"
|
||||
>
|
||||
<div class="footer-left-elements">
|
||||
<ul class="footer-elements">
|
||||
|
@ -85,10 +85,11 @@
|
|||
<script setup lang="ts">
|
||||
import { shell } from 'electron';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed, ComputedRef } from 'vue';
|
||||
import { computed, ComputedRef, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import BaseIcon from '@/components/BaseIcon.vue';
|
||||
import { hexToRGBA } from '@/libs/hexToRgba';
|
||||
import { useApplicationStore } from '@/stores/application';
|
||||
import { useConnectionsStore } from '@/stores/connections';
|
||||
import { useConsoleStore } from '@/stores/console';
|
||||
|
@ -117,7 +118,11 @@ const { getWorkspace } = workspacesStore;
|
|||
const { getConnectionFolder, getConnectionByUid } = connectionsStore;
|
||||
|
||||
const workspace = computed(() => getWorkspace(workspaceUid.value));
|
||||
const footerColor = computed(() => getConnectionFolder(workspaceUid.value)?.color || '#E36929');
|
||||
const accentColor = computed(() => {
|
||||
if (getConnectionFolder(workspaceUid.value)?.color)
|
||||
return getConnectionFolder(workspaceUid.value).color;
|
||||
return '#E36929';
|
||||
});
|
||||
const connectionInfos = computed(() => getConnectionByUid(workspaceUid.value));
|
||||
const version: ComputedRef<DatabaseInfos> = computed(() => {
|
||||
return getWorkspace(workspaceUid.value) ? workspace.value.version : null;
|
||||
|
@ -129,7 +134,17 @@ const versionString = computed(() => {
|
|||
return '';
|
||||
});
|
||||
|
||||
watch(accentColor, () => {
|
||||
changeAccentColor();
|
||||
});
|
||||
|
||||
const openOutside = (link: string) => shell.openExternal(link);
|
||||
const changeAccentColor = () => {
|
||||
document.querySelector<HTMLBodyElement>(':root').style.setProperty('--primary-color', accentColor.value);
|
||||
document.querySelector<HTMLBodyElement>(':root').style.setProperty('--primary-color-shadow', hexToRGBA(accentColor.value, 0.2));
|
||||
};
|
||||
|
||||
changeAccentColor();
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
|
|
|
@ -233,6 +233,7 @@ if (!connectionsArr.value.length)
|
|||
border-radius: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
|
|
|
@ -42,11 +42,11 @@
|
|||
>
|
||||
<BaseIcon
|
||||
class="mt-1 mr-1"
|
||||
icon-name="mdiCodeTags"
|
||||
:icon-name="element.filePath ? 'mdiFileCodeOutline' : 'mdiCodeTags'"
|
||||
:size="18"
|
||||
/>
|
||||
<span>
|
||||
<span>{{ cutText(element.content || 'Query', 20, true) }} #{{ element.index }}</span>
|
||||
<span>{{ cutText(element.elementName || element.content || 'Query', 20, true) }} #{{ element.index }}</span>
|
||||
<span
|
||||
class="btn btn-clear"
|
||||
:title="t('general.close')"
|
||||
|
|
|
@ -501,7 +501,7 @@ const toggleSearchMethod = () => {
|
|||
transition: background 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba($primary-color, 50%);
|
||||
background: rgba(var(--primary-color), 50%);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -513,7 +513,13 @@ const selectMisc = ({ schema, misc, type }: { schema: string; misc: { name: stri
|
|||
};
|
||||
|
||||
const openDataTab = ({ schema, table }: { schema: string; table: TableInfos }) => {
|
||||
newTab({ uid: props.connection.uid, elementName: table.name, schema: props.database.name, type: 'data', elementType: table.type });
|
||||
newTab({
|
||||
uid: props.connection.uid,
|
||||
elementName: table.name,
|
||||
schema: props.database.name,
|
||||
type: 'data',
|
||||
elementType: table.type
|
||||
});
|
||||
setBreadcrumbs({ schema, [table.type]: table.name });
|
||||
};
|
||||
|
||||
|
|
|
@ -145,7 +145,7 @@ onMounted(() => {
|
|||
transition: background 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba($primary-color, 50%);
|
||||
background: rgba(var(--primary-color), 50%);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -101,7 +101,13 @@ const props = defineProps({
|
|||
selectedField: Object
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close-context', 'duplicate-selected', 'delete-selected', 'add-new-index', 'add-to-index']);
|
||||
const emit = defineEmits([
|
||||
'close-context',
|
||||
'duplicate-selected',
|
||||
'delete-selected',
|
||||
'add-new-index',
|
||||
'add-to-index'
|
||||
]);
|
||||
|
||||
const hasPrimary = computed(() => props.indexes.some(index => index.type === 'PRIMARY'));
|
||||
|
||||
|
|
|
@ -150,7 +150,13 @@ const props = defineProps({
|
|||
mode: String
|
||||
});
|
||||
|
||||
const emit = defineEmits(['add-new-index', 'add-to-index', 'rename-field', 'duplicate-field', 'remove-field']);
|
||||
const emit = defineEmits([
|
||||
'add-new-index',
|
||||
'add-to-index',
|
||||
'rename-field',
|
||||
'duplicate-field',
|
||||
'remove-field'
|
||||
]);
|
||||
|
||||
const workspacesStore = useWorkspacesStore();
|
||||
const consoleStore = useConsoleStore();
|
||||
|
|
|
@ -19,7 +19,10 @@
|
|||
<div ref="resizer" class="query-area-resizer" />
|
||||
<div ref="queryAreaFooter" class="workspace-query-runner-footer">
|
||||
<div class="workspace-query-buttons">
|
||||
<div @mouseenter="setCancelButtonVisibility(true)" @mouseleave="setCancelButtonVisibility(false)">
|
||||
<div
|
||||
@mouseenter="setCancelButtonVisibility(true)"
|
||||
@mouseleave="setCancelButtonVisibility(false)"
|
||||
>
|
||||
<button
|
||||
v-if="showCancel && isQuering"
|
||||
class="btn btn-primary btn-sm cancellable"
|
||||
|
@ -94,6 +97,48 @@
|
|||
>
|
||||
<BaseIcon icon-name="mdiBrush" :size="24" />
|
||||
</button>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-dark btn-sm mr-0"
|
||||
:disabled="!filePath || lastSavedQuery === query"
|
||||
:title="t('application.saveFile')"
|
||||
@click="saveFile()"
|
||||
>
|
||||
<BaseIcon icon-name="mdiContentSaveCheckOutline" :size="24" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm mr-0"
|
||||
:title="t('application.saveFileAs')"
|
||||
@click="saveFileAs()"
|
||||
>
|
||||
<BaseIcon icon-name="mdiContentSavePlusOutline" :size="24" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
:title="t('application.openFile')"
|
||||
@click="openFile()"
|
||||
>
|
||||
<BaseIcon icon-name="mdiFolderOpenOutline" :size="24" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-dark btn-sm mr-0"
|
||||
:disabled="isQuering || (isQuerySaved || query.length < 5)"
|
||||
:title="t('application.saveAsNote')"
|
||||
@click="saveQuery()"
|
||||
>
|
||||
<BaseIcon icon-name="mdiHeartPlusOutline" :size="24" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
:disabled="isQuering"
|
||||
:title="t('database.savedQueries')"
|
||||
@click="openSavedModal()"
|
||||
>
|
||||
<BaseIcon icon-name="mdiNotebookHeartOutline" :size="24" />
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
:disabled="isQuering"
|
||||
|
@ -102,24 +147,6 @@
|
|||
>
|
||||
<BaseIcon icon-name="mdiHistory" :size="24" />
|
||||
</button>
|
||||
<div class="btn-group">
|
||||
<button
|
||||
class="btn btn-dark btn-sm mr-0"
|
||||
:disabled="isQuering || (isQuerySaved || query.length < 5)"
|
||||
:title="t('general.save')"
|
||||
@click="saveQuery()"
|
||||
>
|
||||
<BaseIcon icon-name="mdiContentSaveOutline" :size="24" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
:disabled="isQuering"
|
||||
:title="t('database.savedQueries')"
|
||||
@click="openSavedModal()"
|
||||
>
|
||||
<BaseIcon icon-name="mdiStarOutline" :size="24" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="dropdown table-dropdown pr-2">
|
||||
<button
|
||||
:disabled="!hasResults || isQuering"
|
||||
|
@ -251,7 +278,7 @@ import { uidGen } from 'common/libs/uidGen';
|
|||
import { ipcRenderer } from 'electron';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { format } from 'sql-formatter';
|
||||
import { Component, computed, onBeforeUnmount, onMounted, Prop, Ref, ref, watch } from 'vue';
|
||||
import { Component, computed, onBeforeUnmount, onMounted, Prop, Ref, ref, toRaw, watch } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import BaseIcon from '@/components/BaseIcon.vue';
|
||||
|
@ -262,6 +289,7 @@ import QueryEditor from '@/components/QueryEditor.vue';
|
|||
import WorkspaceTabQueryEmptyState from '@/components/WorkspaceTabQueryEmptyState.vue';
|
||||
import WorkspaceTabQueryTable from '@/components/WorkspaceTabQueryTable.vue';
|
||||
import { useResultTables } from '@/composables/useResultTables';
|
||||
import Application from '@/ipc-api/Application';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
import { useApplicationStore } from '@/stores/application';
|
||||
import { useConsoleStore } from '@/stores/console';
|
||||
|
@ -302,14 +330,18 @@ const {
|
|||
getWorkspace,
|
||||
changeBreadcrumbs,
|
||||
updateTabContent,
|
||||
setUnsavedChanges
|
||||
setUnsavedChanges,
|
||||
newTab
|
||||
} = workspacesStore;
|
||||
|
||||
const queryEditor: Ref<Component & { editor: Ace.Editor; $el: HTMLElement }> = ref(null);
|
||||
const queryAreaFooter: Ref<HTMLDivElement> = ref(null);
|
||||
const resizer: Ref<HTMLDivElement> = ref(null);
|
||||
const queryName = ref('');
|
||||
const query = ref('');
|
||||
const filePath = ref('');
|
||||
const lastQuery = ref('');
|
||||
const lastSavedQuery = ref('');
|
||||
const isCancelling = ref(false);
|
||||
const showCancel = ref(false);
|
||||
const autocommit = ref(true);
|
||||
|
@ -333,17 +365,41 @@ const databaseSchemas = computed(() => {
|
|||
});
|
||||
const hasResults = computed(() => results.value.length && results.value[0].rows);
|
||||
const hasAffected = computed(() => affectedCount.value || (!resultsCount.value && affectedCount.value !== null));
|
||||
const isChanged = computed(() => {
|
||||
return filePath.value && lastSavedQuery.value !== query.value;
|
||||
});
|
||||
|
||||
watch(query, (val) => {
|
||||
clearTimeout(debounceTimeout.value);
|
||||
|
||||
debounceTimeout.value = setTimeout(() => {
|
||||
updateTabContent({
|
||||
elementName: queryName.value,
|
||||
filePath: filePath.value,
|
||||
uid: props.connection.uid,
|
||||
tab: props.tab.uid,
|
||||
type: 'query',
|
||||
schema: selectedSchema.value,
|
||||
content: val
|
||||
|
||||
});
|
||||
|
||||
isQuerySaved.value = false;
|
||||
}, 200);
|
||||
});
|
||||
|
||||
watch(queryName, (val) => {
|
||||
clearTimeout(debounceTimeout.value);
|
||||
|
||||
debounceTimeout.value = setTimeout(() => {
|
||||
updateTabContent({
|
||||
elementName: val,
|
||||
filePath: filePath.value,
|
||||
uid: props.connection.uid,
|
||||
tab: props.tab.uid,
|
||||
type: 'query',
|
||||
schema: selectedSchema.value,
|
||||
content: query.value
|
||||
});
|
||||
|
||||
isQuerySaved.value = false;
|
||||
|
@ -377,6 +433,10 @@ watch(() => props.tab.content, () => {
|
|||
queryEditor.value.editor.session.setValue(query.value);
|
||||
});
|
||||
|
||||
watch(isChanged, (val) => {
|
||||
setUnsavedChanges({ uid: props.connection.uid, tUid: props.tabUid, isChanged: val });
|
||||
});
|
||||
|
||||
const runQuery = async (query: string) => {
|
||||
if (!query || isQuering.value) return;
|
||||
isQuering.value = true;
|
||||
|
@ -529,7 +589,8 @@ const saveQuery = () => {
|
|||
type: 'query',
|
||||
date: new Date(),
|
||||
note: query.value,
|
||||
isArchived: false
|
||||
isArchived: false,
|
||||
title: queryName.value
|
||||
});
|
||||
isQuerySaved.value = true;
|
||||
};
|
||||
|
@ -596,6 +657,8 @@ const rollbackTab = async () => {
|
|||
defineExpose({ resizeResults });
|
||||
|
||||
query.value = props.tab.content as string;
|
||||
queryName.value = props.tab.elementName as string;
|
||||
filePath.value = props.tab.filePath as string;
|
||||
selectedSchema.value = props.tab.schema || breadcrumbsSchema.value;
|
||||
|
||||
window.addEventListener('resize', onWindowResize);
|
||||
|
@ -630,6 +693,73 @@ const historyListener = () => {
|
|||
openHistoryModal();
|
||||
};
|
||||
|
||||
const openFileListener = () => {
|
||||
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
|
||||
if (props.isSelected && !hasModalOpen)
|
||||
openFile();
|
||||
};
|
||||
|
||||
const saveFileAsListener = () => {
|
||||
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
|
||||
if (props.isSelected && !hasModalOpen)
|
||||
saveFileAs();
|
||||
};
|
||||
|
||||
const saveContentListener = () => {
|
||||
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
|
||||
if (props.isSelected && !hasModalOpen && filePath)
|
||||
saveFile();
|
||||
};
|
||||
|
||||
const openFile = async () => {
|
||||
const result = await Application.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'SQL', extensions: ['sql', 'txt'] }] });
|
||||
if (result && !result.canceled) {
|
||||
const file = result.filePaths[0];
|
||||
const content = await Application.readFile(file);
|
||||
const fileName = file.split('/').pop().split('\\').pop();
|
||||
if (props.tab.filePath && props.tab.filePath !== file) {
|
||||
newTab({
|
||||
uid: props.connection.uid,
|
||||
type: 'query',
|
||||
filePath: file,
|
||||
content: '',
|
||||
schema: selectedSchema.value,
|
||||
elementName: fileName
|
||||
});
|
||||
}
|
||||
else {
|
||||
filePath.value = file;
|
||||
queryName.value = fileName;
|
||||
query.value = content;
|
||||
lastSavedQuery.value = content;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const saveFileAs = async () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const result: any = await Application.showSaveDialog({ filters: [{ name: 'SQL', extensions: ['sql'] }], defaultPath: `${queryName.value || 'query'}.sql` });
|
||||
if (result && !result.canceled) {
|
||||
await Application.writeFile(result.filePath, query.value);
|
||||
addNotification({ status: 'success', message: t('general.actionSuccessful', { action: t('application.saveFile') }) });
|
||||
queryName.value = result.filePath.split('/').pop().split('\\').pop();
|
||||
filePath.value = result.filePath;
|
||||
lastSavedQuery.value = toRaw(query.value);
|
||||
}
|
||||
};
|
||||
|
||||
const saveFile = async () => {
|
||||
await Application.writeFile(filePath.value, query.value);
|
||||
addNotification({ status: 'success', message: t('general.actionSuccessful', { action: t('application.saveFile') }) });
|
||||
lastSavedQuery.value = toRaw(query.value);
|
||||
};
|
||||
|
||||
const loadFileContent = async (file: string) => {
|
||||
const content = await Application.readFile(file);
|
||||
query.value = content;
|
||||
lastSavedQuery.value = content;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const localResizer = resizer.value;
|
||||
|
||||
|
@ -638,6 +768,9 @@ onMounted(() => {
|
|||
ipcRenderer.on('kill-query', killQueryListener);
|
||||
ipcRenderer.on('clear-query', clearQueryListener);
|
||||
ipcRenderer.on('query-history', historyListener);
|
||||
ipcRenderer.on('open-file', openFileListener);
|
||||
ipcRenderer.on('save-file-as', saveFileAsListener);
|
||||
ipcRenderer.on('save-content', saveContentListener);
|
||||
|
||||
localResizer.addEventListener('mousedown', (e: MouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
@ -648,6 +781,9 @@ onMounted(() => {
|
|||
|
||||
if (props.tab.autorun)
|
||||
runQuery(query.value);
|
||||
|
||||
if (props.tab.filePath)
|
||||
loadFileContent(props.tab.filePath);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
|
@ -663,6 +799,9 @@ onBeforeUnmount(() => {
|
|||
ipcRenderer.removeListener('kill-query', killQueryListener);
|
||||
ipcRenderer.removeListener('clear-query', clearQueryListener);
|
||||
ipcRenderer.removeListener('query-history', historyListener);
|
||||
ipcRenderer.removeListener('open-file', openFileListener);
|
||||
ipcRenderer.removeListener('save-file-as', saveFileAsListener);
|
||||
ipcRenderer.removeListener('save-content', saveContentListener);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -682,7 +821,7 @@ onBeforeUnmount(() => {
|
|||
transition: background 0.2s;
|
||||
|
||||
&:hover {
|
||||
background: rgba($primary-color, 50%);
|
||||
background: rgba(var(--primary-color), 50%);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -721,4 +860,4 @@ onBeforeUnmount(() => {
|
|||
min-height: 200px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>filePathsfilePathsfilePaths
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
:class="{ 'active': resultsetIndex === index }"
|
||||
@click="selectResultset(index)"
|
||||
>
|
||||
<a>{{ result.fields ? result.fields[0]?.table : '' }} ({{ result.rows.length }})</a>
|
||||
<a>{{ result.fields ? result.fields[0]?.tableAlias ?? result.fields[0]?.table : `${t('general.results')} #${index}` }} ({{ result.rows.length }})</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div ref="table" class="table table-hover">
|
||||
|
@ -44,7 +44,7 @@
|
|||
:title="`${field.type} ${fieldLength(field) ? `(${fieldLength(field)})` : ''}`"
|
||||
>
|
||||
<div ref="columnResize" class="column-resizable">
|
||||
<div class="table-column-title" @click="sort(field.name)">
|
||||
<div class="table-column-title" @click="sort(field)">
|
||||
<div v-if="field.key" :title="keyName(field.key)">
|
||||
<BaseIcon
|
||||
icon-name="mdiKey"
|
||||
|
@ -56,8 +56,8 @@
|
|||
</div>
|
||||
<span>{{ field.alias || field.name }}</span>
|
||||
<BaseIcon
|
||||
v-if="isSortable && currentSort === field.name || currentSort === `${field.table}.${field.name}`"
|
||||
:icon-name="currentSortDir === 'asc' ? 'mdiSortAscending' : 'mdiSortDescending'"
|
||||
v-if="isSortable && currentSort[resultsetIndex]?.field === field.name || currentSort[resultsetIndex]?.field === `${field.tableAlias || field.table}.${field.name}`"
|
||||
:icon-name="currentSort[resultsetIndex].dir === 'asc' ? 'mdiSortAscending' : 'mdiSortDescending'"
|
||||
:size="18"
|
||||
class="sort-icon ml-1"
|
||||
/>
|
||||
|
@ -292,6 +292,10 @@ const props = defineProps({
|
|||
results: Array as Prop<QueryResult[]>,
|
||||
connUid: String,
|
||||
mode: String as Prop<'table' | 'query'>,
|
||||
page: {
|
||||
type: Number,
|
||||
required: false
|
||||
},
|
||||
isSelected: Boolean,
|
||||
elementType: { type: String, default: 'table' }
|
||||
});
|
||||
|
@ -314,8 +318,7 @@ const hasFocus = ref(false);
|
|||
const contextEvent = ref(null);
|
||||
const selectedCell = ref(null);
|
||||
const selectedRows = ref([]);
|
||||
const currentSort = ref('');
|
||||
const currentSortDir = ref('asc');
|
||||
const currentSort: Ref<{field: string; dir: 'asc' | 'desc'}[]> = ref([]);
|
||||
const resultsetIndex = ref(0);
|
||||
const scrollElement = ref(null);
|
||||
const rowHeight = ref(23);
|
||||
|
@ -358,14 +361,16 @@ const isHardSort = computed(() => {
|
|||
});
|
||||
|
||||
const sortedResults = computed(() => {
|
||||
if (currentSort.value && !isHardSort.value) {
|
||||
if (currentSort.value[resultsetIndex.value] && !isHardSort.value) {
|
||||
const sortObj = currentSort.value[resultsetIndex.value];
|
||||
|
||||
return [...localResults.value].sort((a: any, b: any) => {
|
||||
let modifier = 1;
|
||||
let valA = typeof a[currentSort.value] === 'string' ? a[currentSort.value].toLowerCase() : a[currentSort.value];
|
||||
let valA = typeof a[sortObj.field] === 'string' ? a[sortObj.field].toLowerCase() : a[sortObj.field];
|
||||
if (!isNaN(valA)) valA = Number(valA);
|
||||
let valB = typeof b[currentSort.value] === 'string' ? b[currentSort.value].toLowerCase() : b[currentSort.value];
|
||||
let valB = typeof b[sortObj.field] === 'string' ? b[sortObj.field].toLowerCase() : b[sortObj.field];
|
||||
if (!isNaN(valB)) valB = Number(valB);
|
||||
if (currentSortDir.value === 'desc') modifier = -1;
|
||||
if (sortObj.dir === 'desc') modifier = -1;
|
||||
if (valA < valB) return -1 * modifier;
|
||||
if (valA > valB) return 1 * modifier;
|
||||
return 0;
|
||||
|
@ -784,32 +789,42 @@ const contextMenu = (event: MouseEvent, cell: any) => {
|
|||
isContext.value = true;
|
||||
};
|
||||
|
||||
const sort = (field: string) => {
|
||||
const sort = (field: TableField) => {
|
||||
if (!isSortable.value) return;
|
||||
|
||||
selectedRows.value = [];
|
||||
let fieldName = field.name;
|
||||
const hasTableInFieldname = Object.keys(localResults.value[0]).find(k => k !== '_antares_id').includes('.');
|
||||
|
||||
if (props.mode === 'query')
|
||||
field = `${getTable(resultsetIndex.value)}.${field}`;
|
||||
if (props.mode === 'query' && hasTableInFieldname)
|
||||
fieldName = `${field.tableAlias || field.table}.${field.name}`;
|
||||
|
||||
if (field === currentSort.value) {
|
||||
if (currentSortDir.value === 'asc')
|
||||
currentSortDir.value = 'desc';
|
||||
if (fieldName === currentSort.value[resultsetIndex.value]?.field) {
|
||||
if (currentSort.value[resultsetIndex.value].dir === 'asc')
|
||||
currentSort.value[resultsetIndex.value].dir = 'desc';
|
||||
else
|
||||
resetSort();
|
||||
}
|
||||
else {
|
||||
currentSortDir.value = 'asc';
|
||||
currentSort.value = field;
|
||||
currentSort.value[resultsetIndex.value] = {
|
||||
field: fieldName,
|
||||
dir: 'asc'
|
||||
};
|
||||
}
|
||||
|
||||
if (isHardSort.value)
|
||||
emit('hard-sort', { field: currentSort.value, dir: currentSortDir.value });
|
||||
if (isHardSort.value) {
|
||||
emit('hard-sort', {
|
||||
field: currentSort.value[resultsetIndex.value].field,
|
||||
dir: currentSort.value[resultsetIndex.value].dir
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const resetSort = () => {
|
||||
currentSort.value = '';
|
||||
currentSortDir.value = 'asc';
|
||||
currentSort.value[resultsetIndex.value] = {
|
||||
field: null,
|
||||
dir: 'asc'
|
||||
};
|
||||
};
|
||||
|
||||
const selectResultset = (index: number) => {
|
||||
|
@ -857,6 +872,7 @@ const downloadTable = (format: 'csv' | 'json' | 'sql' | 'php', table: string, po
|
|||
},
|
||||
client: workspaceClient.value,
|
||||
table,
|
||||
page: props.page,
|
||||
sqlOptions: popup ? { ...sqlExportOptions.value } : null,
|
||||
csvOptions: popup ? { ...csvExportOptions.value } : null
|
||||
});
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
autofocus
|
||||
class="editable-field form-input input-sm px-1"
|
||||
@blur="editOFF"
|
||||
@keyup.delete.stop
|
||||
>
|
||||
<BaseSelect
|
||||
v-else-if="inputProps.type === 'boolean'"
|
||||
|
@ -50,6 +51,7 @@
|
|||
:options="['true', 'false']"
|
||||
class="form-select small-select editable-field"
|
||||
@blur="editOFF"
|
||||
@keyup.delete.stop
|
||||
/>
|
||||
<BaseSelect
|
||||
v-else-if="enumArray"
|
||||
|
@ -58,6 +60,7 @@
|
|||
class="form-select small-select editable-field"
|
||||
dropdown-class="small-select"
|
||||
@blur="editOFF"
|
||||
@keyup.delete.stop
|
||||
/>
|
||||
<input
|
||||
v-else
|
||||
|
@ -67,6 +70,7 @@
|
|||
autofocus
|
||||
class="editable-field form-input input-sm px-1"
|
||||
@blur="editOFF"
|
||||
@keyup.delete.stop
|
||||
>
|
||||
</template>
|
||||
</template>
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
<BaseIcon icon-name="mdiMagnify" :size="24" />
|
||||
</button>
|
||||
<button
|
||||
v-if="isTable"
|
||||
v-if="isTable && !connection.readonly"
|
||||
class="btn btn-dark btn-sm"
|
||||
:disabled="isQuering"
|
||||
@click="showFakerModal()"
|
||||
|
@ -202,6 +202,7 @@
|
|||
v-if="results"
|
||||
ref="queryTable"
|
||||
:results="results"
|
||||
:page="page"
|
||||
:tab-uid="tabUid"
|
||||
:conn-uid="connection.uid"
|
||||
:is-selected="isSelected"
|
||||
|
|
|
@ -79,7 +79,8 @@ export const enUS = {
|
|||
search: 'Search',
|
||||
title: 'Title',
|
||||
archive: 'Archive', // verb
|
||||
undo: 'Undo'
|
||||
undo: 'Undo',
|
||||
moveTo: 'Move to'
|
||||
},
|
||||
connection: { // Database connection
|
||||
connection: 'Connection',
|
||||
|
@ -332,7 +333,7 @@ export const enUS = {
|
|||
wrapLongLines: 'Wrap long lines',
|
||||
markdownSupported: 'Markdown supported',
|
||||
plantATree: 'Plant a Tree',
|
||||
dataTabPageSize: 'DATA tab page size',
|
||||
dataTabPageSize: 'Results per page',
|
||||
noOpenTabs: 'There are no open tabs, navigate on the left bar or:',
|
||||
restorePreviousSession: 'Restore previous session',
|
||||
closeTab: 'Close tab',
|
||||
|
@ -363,6 +364,8 @@ export const enUS = {
|
|||
editFolder: 'Edit folder',
|
||||
folderName: 'Folder name',
|
||||
deleteFolder: 'Delete folder',
|
||||
newFolder: 'New folder',
|
||||
outOfFolder: 'Out of folder',
|
||||
editConnectionAppearance: 'Edit connection appearance',
|
||||
defaultCopyType: 'Default copy type',
|
||||
showTableSize: 'Show table size in sidebar',
|
||||
|
@ -394,9 +397,15 @@ export const enUS = {
|
|||
thereAreNoNotesYet: 'There are no notes yet',
|
||||
addNote: 'Add note',
|
||||
editNote: 'Edit note',
|
||||
saveAsNote: 'Save as note',
|
||||
showArchivedNotes: 'Show archived notes',
|
||||
hideArchivedNotes: 'Hide archived notes',
|
||||
tag: 'Tag' // Note tag
|
||||
tag: 'Tag', // Note tag,
|
||||
saveFile: 'Save file',
|
||||
saveFileAs: 'Save file as',
|
||||
openFile: 'Open file',
|
||||
openNotes: 'Open notes'
|
||||
|
||||
},
|
||||
faker: { // Faker.js methods, used in random generated content
|
||||
address: 'Address',
|
||||
|
|
|
@ -65,9 +65,16 @@ export const zhCN = {
|
|||
actionSuccessful: '{action} 成功',
|
||||
outputFormat: '输出格式',
|
||||
singleFile: '单个 {ext} 文件',
|
||||
zipCompressedFile: 'ZIP 压缩 {ext} 文件'
|
||||
zipCompressedFile: 'ZIP 压缩 {ext} 文件',
|
||||
copyName: '复制名称',
|
||||
search: '搜索',
|
||||
title: '标题',
|
||||
archive: '归档',
|
||||
undo: '重做',
|
||||
moveTo: '移动到'
|
||||
},
|
||||
connection: { // 数据库连接
|
||||
connection: '连接',
|
||||
connectionName: '连接名称',
|
||||
hostName: '主机名',
|
||||
client: '数据库类型',
|
||||
|
@ -101,7 +108,8 @@ export const zhCN = {
|
|||
readOnlyMode: '只读模式',
|
||||
allConnections: '所有连接',
|
||||
searchForConnections: '搜索连接',
|
||||
keepAliveInterval: '保持活跃间隔'
|
||||
keepAliveInterval: '保持活跃间隔',
|
||||
singleConnection: '单一连接'
|
||||
},
|
||||
database: { // 数据库相关术语
|
||||
schema: '模式(schema)',
|
||||
|
@ -265,12 +273,11 @@ export const zhCN = {
|
|||
targetTable: '目标表',
|
||||
switchDatabase: '切换数据库',
|
||||
searchForElements: '搜索元素',
|
||||
searchForSchemas: '搜索模式(schema)'
|
||||
searchForSchemas: '搜索模式(schema)',
|
||||
savedQueries: '已保存的查询'
|
||||
},
|
||||
application: { // 应用程序相关术语
|
||||
settings: '设置',
|
||||
scratchpad: '草稿栏',
|
||||
disableScratchpad: '禁用草稿栏',
|
||||
console: '控制台',
|
||||
general: '常规',
|
||||
themes: '主题',
|
||||
|
@ -375,7 +382,19 @@ export const zhCN = {
|
|||
ignoreDuplicates: '忽略重复',
|
||||
wrongImportPassword: '错误的导入密码',
|
||||
wrongFileFormat: '错误的文件格式',
|
||||
dataImportSuccess: '数据已成功导入'
|
||||
dataImportSuccess: '数据已成功导入',
|
||||
note: '笔记 | 笔记',
|
||||
thereAreNoNotesYet: '目前还没有笔记',
|
||||
addNote: '添加笔记',
|
||||
editNote: '编辑笔记',
|
||||
saveAsNote: '另存为笔记',
|
||||
showArchivedNotes: '显示归档笔记',
|
||||
hideArchivedNotes: '隐藏归档笔记',
|
||||
tag: '笔记标签', // Note tag,
|
||||
saveFile: '保存文件',
|
||||
saveFileAs: '将文件另存为',
|
||||
openFile: '打开文件',
|
||||
openNotes: '打开笔记'
|
||||
},
|
||||
faker: { // Faker.js 方法,用于随机生成的内容
|
||||
address: '地址',
|
||||
|
|
|
@ -8,6 +8,10 @@ export default class {
|
|||
return ipcRenderer.invoke('show-open-dialog', unproxify(options));
|
||||
}
|
||||
|
||||
static showSaveDialog (options: OpenDialogOptions): Promise<OpenDialogReturnValue> {
|
||||
return ipcRenderer.invoke('show-save-dialog', unproxify(options));
|
||||
}
|
||||
|
||||
static getDownloadPathDirectory (): Promise<string> {
|
||||
return ipcRenderer.invoke('get-download-dir-path');
|
||||
}
|
||||
|
@ -27,4 +31,12 @@ export default class {
|
|||
static unregisterShortcuts () {
|
||||
return ipcRenderer.invoke('unregister-shortcuts');
|
||||
}
|
||||
|
||||
static readFile (path: string): Promise<string> {
|
||||
return ipcRenderer.invoke('read-file', path);
|
||||
}
|
||||
|
||||
static writeFile (path: string, content: unknown) {
|
||||
return ipcRenderer.invoke('write-file', path, content);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
export const colorShade = (color: string, amount: number) => {
|
||||
color = color.replaceAll('#', '');
|
||||
if (color.length === 3) color = color[0] + color[0] + color[1] + color[1] + color[2] + color[2];
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
let [r, g, b] = color.match(/.{2}/g) as any;
|
||||
([r, g, b] = [parseInt(r, 16) + amount, parseInt(g, 16) + amount, parseInt(b, 16) + amount]);
|
||||
|
||||
r = Math.max(Math.min(255, r), 0).toString(16);
|
||||
g = Math.max(Math.min(255, g), 0).toString(16);
|
||||
b = Math.max(Math.min(255, b), 0).toString(16);
|
||||
|
||||
const rr = (r.length < 2 ? '0' : '') + r;
|
||||
const gg = (g.length < 2 ? '0' : '') + g;
|
||||
const bb = (b.length < 2 ? '0' : '') + b;
|
||||
|
||||
return `#${rr}${gg}${bb}`;
|
||||
};
|
|
@ -1,11 +1,13 @@
|
|||
import { ClientCode } from 'common/interfaces/antares';
|
||||
import { jsonToSqlInsert } from 'common/libs/sqlUtils';
|
||||
import * as json2php from 'json2php';
|
||||
import * as moment from 'moment';
|
||||
|
||||
export const exportRows = (args: {
|
||||
type: 'csv' | 'json'| 'sql' | 'php';
|
||||
content: object[];
|
||||
table: string;
|
||||
page?: number;
|
||||
client?: ClientCode;
|
||||
fields?: {
|
||||
[key: string]: {type: string; datePrecision: number};
|
||||
|
@ -31,7 +33,7 @@ export const exportRows = (args: {
|
|||
const csv = [];
|
||||
const sd = args.csvOptions.stringDelimiter === 'single'
|
||||
? '\''
|
||||
: args.csvOptions.stringDelimiter === 'single'
|
||||
: args.csvOptions.stringDelimiter === 'double'
|
||||
? '"'
|
||||
: '';
|
||||
|
||||
|
@ -41,6 +43,7 @@ export const exportRows = (args: {
|
|||
for (const row of args.content) {
|
||||
csv.push(Object.values(row).map(col => {
|
||||
if (typeof col === 'string') return `${sd}${col}${sd}`;
|
||||
if (col instanceof Date) return `${sd}${moment(col).format('YYYY-MM-DD HH:mm:ss')}${sd}`;
|
||||
if (col instanceof Buffer) return col.toString('base64');
|
||||
if (col instanceof Uint8Array) return Buffer.from(col).toString('base64');
|
||||
return col;
|
||||
|
@ -81,7 +84,7 @@ export const exportRows = (args: {
|
|||
|
||||
const file = new Blob([content], { type: mime });
|
||||
const downloadLink = document.createElement('a');
|
||||
downloadLink.download = `${args.sqlOptions?.targetTable || args.table}.${args.type}`;
|
||||
downloadLink.download = `${args.sqlOptions?.targetTable || args.table}${args.page ? `-${args.page}` : ''}.${args.type}`;
|
||||
downloadLink.href = window.URL.createObjectURL(file);
|
||||
downloadLink.style.display = 'none';
|
||||
document.body.appendChild(downloadLink);
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
export const hexToRGBA = (hexCode: string, opacity = 1) => {
|
||||
let hex = hexCode.replace('#', '');
|
||||
|
||||
if (hex.length === 3)
|
||||
hex = `${hex[0]}${hex[0]}${hex[1]}${hex[1]}${hex[2]}${hex[2]}`;
|
||||
|
||||
const r = parseInt(hex.substring(0, 2), 16);
|
||||
const g = parseInt(hex.substring(2, 4), 16);
|
||||
const b = parseInt(hex.substring(4, 6), 16);
|
||||
|
||||
/* Backward compatibility for whole number based opacity values. */
|
||||
if (opacity > 1 && opacity <= 100)
|
||||
opacity = opacity / 100;
|
||||
|
||||
return `rgba(${r},${g},${b},${opacity})`;
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
/* Colors */
|
||||
$body-bg: #fdfdfd;
|
||||
$body-bg: #f3f3f3;
|
||||
$body-bg-dark: #1d1d1d;
|
||||
$body-font-color-dark: #fff;
|
||||
$bg-color-dark: #1d1d1d;
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
/* stylelint-disable */
|
||||
:root {
|
||||
--primary-color: #e36929;
|
||||
--primary-color-dark: color-mix(in srgb, var(--primary-color), #000 30%);
|
||||
--primary-color-shadow: 0 0 0 0.1rem rgba(227, 105, 41, 0.2);
|
||||
}
|
||||
|
||||
@import "~spectre.css/src/variables";
|
||||
@import "variables";
|
||||
@import "transitions";
|
||||
|
@ -16,12 +22,20 @@ body {
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--primary-color);
|
||||
|
||||
&:hover {
|
||||
color: var(--primary-color-dark)
|
||||
}
|
||||
}
|
||||
|
||||
::selection,
|
||||
option:hover,
|
||||
option:focus,
|
||||
option:active,
|
||||
option:checked {
|
||||
background-color: $primary-color;
|
||||
background-color: var(--primary-color);
|
||||
color: $light-color;
|
||||
}
|
||||
|
||||
|
@ -189,6 +203,14 @@ option:checked {
|
|||
animation: rotation 0.8s infinite linear;
|
||||
}
|
||||
|
||||
.loading {
|
||||
&::after {
|
||||
border: 0.1rem solid var(--primary-color);
|
||||
border-right-color: transparent;
|
||||
border-top-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
/* Override */
|
||||
.modal {
|
||||
.modal-container,
|
||||
|
@ -248,7 +270,7 @@ option:checked {
|
|||
height: 2px;
|
||||
width: 0;
|
||||
transition: width 0.2s;
|
||||
background-color: $primary-color;
|
||||
background-color: var(--primary-color);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
|
@ -300,9 +322,23 @@ option:checked {
|
|||
height: auto;
|
||||
}
|
||||
|
||||
.form-checkbox input:checked + .form-icon, .form-radio input:checked + .form-icon, .form-switch input:checked + .form-icon {
|
||||
background: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.form-checkbox input:focus + .form-icon, .form-radio input:focus + .form-icon, .form-switch input:focus + .form-icon {
|
||||
box-shadow: 0 0 0 0.1rem var(--primary-color-shadow);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.form-select {
|
||||
cursor: pointer;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 0.1rem var(--primary-color-shadow);
|
||||
}
|
||||
|
||||
&.small-select {
|
||||
height: 21px;
|
||||
font-size: 0.7rem;
|
||||
|
@ -311,7 +347,8 @@ option:checked {
|
|||
|
||||
&.select {
|
||||
&.select--open {
|
||||
border-color: $primary-color !important;
|
||||
border-color: var(--primary-color) !important;
|
||||
box-shadow: 0 0 0 0.1rem var(--primary-color-shadow) !important;
|
||||
|
||||
@include control-shadow();
|
||||
}
|
||||
|
@ -336,19 +373,28 @@ option:checked {
|
|||
z-index: 401 !important;
|
||||
border: 1px solid transparent;
|
||||
border-radius: $border-radius;
|
||||
box-shadow: 0 8px 17px 0 rgb(0 0 0 / 20%), 0 6px 20px 0 rgb(0 0 0 / 19%);
|
||||
box-shadow:
|
||||
0 8px 17px 0 rgb(0 0 0 / 20%),
|
||||
0 6px 20px 0 rgb(0 0 0 / 19%);
|
||||
}
|
||||
|
||||
.select__option--selected {
|
||||
background: rgba($primary-color, 0.25);
|
||||
background: rgba(var(--primary-color), 0.25);
|
||||
}
|
||||
|
||||
.select__option--highlight {
|
||||
background: $primary-color;
|
||||
background: var(--primary-color);
|
||||
text-shadow: 0 0 15px #000;
|
||||
}
|
||||
|
||||
.form-input[type="file"] {
|
||||
overflow: hidden;
|
||||
.form-input {
|
||||
&[type="file"] {
|
||||
overflow: hidden;
|
||||
}
|
||||
&:focus {
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 0.1rem var(--primary-color-shadow);
|
||||
}
|
||||
}
|
||||
|
||||
.input-group .input-group-addon {
|
||||
|
@ -370,13 +416,34 @@ option:checked {
|
|||
}
|
||||
|
||||
.btn {
|
||||
color: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
|
||||
&:not(.btn-link) {
|
||||
text-shadow: 0 0 15px #000;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: var(--primary-color-dark);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 3px 1px rgba($primary-color, 90%);
|
||||
box-shadow: 0 0 3px 1px rgba(var(--primary-color), 90%);
|
||||
}
|
||||
|
||||
&.btn-success:focus {
|
||||
border-color: $primary-color;
|
||||
box-shadow: 0 0 3px 1px rgba($primary-color, 90%);
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 3px 1px rgba(var(--primary-color), 90%);
|
||||
}
|
||||
|
||||
&.btn-primary {
|
||||
background: var(--primary-color);
|
||||
border-color: var(--primary-color-dark);
|
||||
|
||||
&:hover {
|
||||
background: var(--primary-color-dark);
|
||||
border-color: var(--primary-color-dark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -435,7 +502,7 @@ code.sql {
|
|||
}
|
||||
|
||||
.sql-hl-keyword {
|
||||
color: $primary-color;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.sql-hl-function {
|
||||
|
@ -456,4 +523,4 @@ code.sql {
|
|||
|
||||
.sql-hl-bracket {
|
||||
color: darkorchid;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,12 +33,41 @@
|
|||
|
||||
.menu-item a {
|
||||
&:hover {
|
||||
color: $primary-color;
|
||||
color: var(--primary-color);
|
||||
background: $bg-color-gray;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab {
|
||||
.tab-item {
|
||||
a {
|
||||
color: $body-font-color-dark;
|
||||
opacity: .7;
|
||||
|
||||
&:hover {
|
||||
color: $body-font-color-dark;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
a {
|
||||
color: $body-font-color-dark;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tab-link {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&::after {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
&.btn-link {
|
||||
color: rgba($body-font-color-dark, 0.8);
|
||||
|
@ -67,7 +96,7 @@
|
|||
}
|
||||
|
||||
&.active {
|
||||
background-color: $primary-color;
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -124,7 +153,7 @@
|
|||
}
|
||||
|
||||
.form-select:not([multiple], [size]):focus {
|
||||
border-color: $primary-color;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.select {
|
||||
|
@ -415,8 +444,7 @@
|
|||
box-shadow: 0 0 1px 0 #000;
|
||||
|
||||
.settingbar-top-elements {
|
||||
overflow-x: hidden;
|
||||
overflow-y: overlay;
|
||||
overflow: hidden overlay;
|
||||
max-height: calc((100vh - 3.5rem) - #{$excluding-size});
|
||||
}
|
||||
|
||||
|
@ -433,7 +461,7 @@
|
|||
.settingbar-element {
|
||||
.settingbar-element-icon {
|
||||
&.badge-update::after {
|
||||
background: $primary-color;
|
||||
background: var(--primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -448,7 +476,7 @@
|
|||
}
|
||||
|
||||
#footer {
|
||||
background: $primary-color;
|
||||
background: var(--primary-color);
|
||||
box-shadow: 0 0 1px 0 #000;
|
||||
|
||||
.footer-elements {
|
||||
|
|
|
@ -12,6 +12,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
.form-select,
|
||||
.form-input,
|
||||
.form-select:not([multiple], [size]),
|
||||
.form-checkbox .form-icon,
|
||||
.form-radio .form-icon {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.form-input:disabled,
|
||||
.form-input.disabled,
|
||||
.form-select:disabled,
|
||||
|
@ -44,7 +52,43 @@
|
|||
}
|
||||
}
|
||||
|
||||
.tab {
|
||||
border-bottom: 0.05rem solid #c5c5c5;
|
||||
|
||||
.tab-item {
|
||||
a {
|
||||
color: $body-font-color;
|
||||
opacity: .7;
|
||||
|
||||
&:hover {
|
||||
color: $body-font-color;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
a {
|
||||
color: $body-font-color;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tab-link {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
&::after {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
&.btn-clear:focus, &.btn-clear:hover {
|
||||
background: #e0e0e0;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
&.btn-link {
|
||||
color: rgba($body-font-color, 0.8);
|
||||
|
||||
|
@ -72,7 +116,7 @@
|
|||
}
|
||||
|
||||
&.active {
|
||||
background-color: $primary-color;
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -164,8 +208,7 @@
|
|||
box-shadow: 0 0 1px 0 #000;
|
||||
|
||||
.settingbar-top-elements {
|
||||
overflow-x: hidden;
|
||||
overflow-y: overlay;
|
||||
overflow: hidden overlay;
|
||||
max-height: calc((100vh - 3.5rem) - #{$excluding-size});
|
||||
}
|
||||
|
||||
|
@ -182,7 +225,7 @@
|
|||
.settingbar-element {
|
||||
.settingbar-element-icon {
|
||||
&.badge-update::after {
|
||||
background: $primary-color;
|
||||
background: var(--primary-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -231,6 +274,10 @@
|
|||
.workspace-tabs {
|
||||
.tab-block {
|
||||
.tab-item {
|
||||
> a {
|
||||
color: $body-font-color;
|
||||
}
|
||||
|
||||
&.tools-dropdown {
|
||||
background-color: $body-bg;
|
||||
}
|
||||
|
@ -242,19 +289,25 @@
|
|||
.workspace-query-results {
|
||||
.table {
|
||||
.th {
|
||||
background: $body-bg;
|
||||
border-color: lighten($bg-color-light-gray, 2%);
|
||||
background: #D8D8D8;
|
||||
border-color: transparent;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.th:first-child {
|
||||
border-left: 2px solid transparent;
|
||||
}
|
||||
|
||||
.tr {
|
||||
background-color: lighten($bg-color-light-gray, 2%);
|
||||
|
||||
.td:first-child {
|
||||
border-left: 2px solid $body-bg;
|
||||
border-left: 2px solid #0000001f;
|
||||
}
|
||||
|
||||
.td {
|
||||
border-color: $body-bg;
|
||||
border-color: #0000001f;
|
||||
border-radius: 1px;
|
||||
|
||||
&:focus,
|
||||
&.selected {
|
||||
|
@ -273,7 +326,7 @@
|
|||
|
||||
.connection-panel {
|
||||
.panel {
|
||||
background: rgba($bg-color-light-gray, 100%);
|
||||
background: #e0e0e0;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -344,7 +397,7 @@
|
|||
}
|
||||
|
||||
#footer {
|
||||
background: $primary-color;
|
||||
background: var(--primary-color);
|
||||
box-shadow: 0 0 1px 0 #000;
|
||||
|
||||
.footer-elements {
|
||||
|
|
|
@ -55,10 +55,9 @@ export const useApplicationStore = defineStore('application', {
|
|||
},
|
||||
showScratchpad (tag?: string) {
|
||||
this.isScratchpad = true;
|
||||
if (tag) {
|
||||
const { selectedTag } = storeToRefs(useScratchpadStore());
|
||||
selectedTag.value = tag;
|
||||
}
|
||||
if (!tag) tag = 'all';
|
||||
const { selectedTag } = storeToRefs(useScratchpadStore());
|
||||
selectedTag.value = tag;
|
||||
},
|
||||
hideScratchpad () {
|
||||
this.isScratchpad = false;
|
||||
|
|
|
@ -90,8 +90,12 @@ export const useConnectionsStore = defineStore('connections', {
|
|||
});
|
||||
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
||||
},
|
||||
addFolder (params: {after: string; connections: [string, string]}) {
|
||||
const index = this.connectionsOrder.findIndex((conn: SidebarElement) => conn.uid === params.after);
|
||||
addFolder (params: {after?: string; connections: [string, string?]}) {
|
||||
const index = params.after
|
||||
? this.connectionsOrder.findIndex((conn: SidebarElement) => conn.uid === params.after)
|
||||
: this.connectionsOrder.length;
|
||||
|
||||
this.removeFromFolders(...params.connections);
|
||||
|
||||
this.connectionsOrder.splice(index, 0, {
|
||||
isFolder: true,
|
||||
|
@ -102,7 +106,18 @@ export const useConnectionsStore = defineStore('connections', {
|
|||
});
|
||||
persistentStore.set('connectionsOrder', this.connectionsOrder);
|
||||
},
|
||||
removeFromFolders (...connections: string[]) { // Removes connections from folders
|
||||
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).map(el => {
|
||||
if (el.isFolder)
|
||||
el.connections = el.connections.filter(uid => !connections.includes(uid));
|
||||
return el;
|
||||
});
|
||||
|
||||
this.clearEmptyFolders();
|
||||
},
|
||||
addToFolder (params: {folder: string; connection: string}) {
|
||||
this.removeFromFolders(params.connection);
|
||||
|
||||
this.connectionsOrder = this.connectionsOrder.map((conn: SidebarElement) => {
|
||||
if (conn.uid === params.folder)
|
||||
conn.connections.push(params.connection);
|
||||
|
@ -113,11 +128,7 @@ export const useConnectionsStore = defineStore('connections', {
|
|||
this.clearEmptyFolders();
|
||||
},
|
||||
deleteConnection (connection: SidebarElement | ConnectionParams) {
|
||||
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).map(el => { // Removes connection from folders
|
||||
if (el.isFolder && el.connections.includes(connection.uid))
|
||||
el.connections = el.connections.filter(uid => uid !== connection.uid);
|
||||
return el;
|
||||
});
|
||||
this.removeFromFolders(connection.uid);
|
||||
this.connectionsOrder = (this.connectionsOrder as SidebarElement[]).filter(el => el.uid !== connection.uid);
|
||||
this.lastConnections = (this.lastConnections as SidebarElement[]).filter(el => el.uid !== connection.uid);
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ export interface WorkspaceTab {
|
|||
isChanged?: boolean;
|
||||
content?: string;
|
||||
autorun?: boolean;
|
||||
filePath?: string;
|
||||
}
|
||||
|
||||
export interface WorkspaceStructure {
|
||||
|
@ -66,7 +67,7 @@ export interface Workspace {
|
|||
uid: string;
|
||||
client?: ClientCode;
|
||||
database?: string;
|
||||
connectionStatus: 'connected' | 'disconnected' | 'failed';
|
||||
connectionStatus: 'connected' | 'connecting' | 'disconnected' | 'failed';
|
||||
selectedTab: string;
|
||||
searchTerm: string;
|
||||
tabs: WorkspaceTab[];
|
||||
|
@ -492,7 +493,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||
}
|
||||
: workspace);
|
||||
},
|
||||
_addTab ({ uid, tab, content, type, autorun, schema, database, elementName, elementType }: WorkspaceTab) {
|
||||
_addTab ({ uid, tab, content, type, autorun, schema, database, elementName, elementType, filePath }: WorkspaceTab) {
|
||||
if (type === 'query')
|
||||
tabIndex[uid] = tabIndex[uid] ? ++tabIndex[uid] : 1;
|
||||
|
||||
|
@ -506,7 +507,8 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||
elementName,
|
||||
elementType,
|
||||
content: content || '',
|
||||
autorun: !!autorun
|
||||
autorun: !!autorun,
|
||||
filePath: filePath || ''
|
||||
};
|
||||
|
||||
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
|
||||
|
@ -522,14 +524,23 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||
|
||||
persistentStore.set(uid, (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid).tabs);
|
||||
},
|
||||
_replaceTab ({ uid, tab: tUid, type, schema, content, elementName, elementType }: WorkspaceTab) {
|
||||
_replaceTab ({ uid, tab: tUid, type, schema, content, elementName, elementType, filePath }: WorkspaceTab) {
|
||||
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
|
||||
if (workspace.uid === uid) {
|
||||
return {
|
||||
...workspace,
|
||||
tabs: workspace.tabs.map(tab => {
|
||||
if (tab.uid === tUid)
|
||||
return { ...tab, type, schema, content, elementName, elementType };
|
||||
if (tab.uid === tUid) {
|
||||
return {
|
||||
...tab,
|
||||
type,
|
||||
schema,
|
||||
content,
|
||||
elementName,
|
||||
elementType,
|
||||
filePath
|
||||
};
|
||||
}
|
||||
|
||||
return tab;
|
||||
})
|
||||
|
@ -541,7 +552,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||
|
||||
persistentStore.set(uid, (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid).tabs);
|
||||
},
|
||||
newTab ({ uid, content, type, autorun, schema, elementName, elementType }: WorkspaceTab) {
|
||||
newTab ({ uid, content, type, autorun, schema, elementName, elementType, filePath }: WorkspaceTab) {
|
||||
let tabUid;
|
||||
const workspaceTabs = (this.workspaces as Workspace[]).find(workspace => workspace.uid === uid);
|
||||
|
||||
|
@ -562,7 +573,8 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||
database: workspaceTabs.database,
|
||||
schema,
|
||||
elementName,
|
||||
elementType
|
||||
elementType,
|
||||
filePath
|
||||
});
|
||||
break;
|
||||
case 'temp-data':
|
||||
|
@ -576,6 +588,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||
tab.schema === schema &&
|
||||
tab.elementName === elementName &&
|
||||
tab.elementType === elementType &&
|
||||
tab.database === workspaceTabs.database &&
|
||||
[type, type.replace('temp-', '')].includes(tab.type))
|
||||
: false;
|
||||
|
||||
|
@ -594,21 +607,52 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||
type: tab.type.replace('temp-', ''),
|
||||
schema: tab.schema,
|
||||
elementName: tab.elementName,
|
||||
elementType: tab.elementType
|
||||
elementType: tab.elementType,
|
||||
filePath: tab.filePath
|
||||
});
|
||||
|
||||
tabUid = uidGen('T');
|
||||
this._addTab({ uid, tab: tabUid, content, type, autorun, database: workspaceTabs.database, schema, elementName, elementType });
|
||||
this._addTab({
|
||||
uid,
|
||||
tab: tabUid,
|
||||
content,
|
||||
type,
|
||||
autorun,
|
||||
database: workspaceTabs.database,
|
||||
schema,
|
||||
elementName,
|
||||
elementType,
|
||||
filePath
|
||||
});
|
||||
}
|
||||
else {
|
||||
this._replaceTab({ uid, tab: tab.uid, type, schema, elementName, elementType });
|
||||
this._replaceTab({
|
||||
uid,
|
||||
tab: tab.uid,
|
||||
type,
|
||||
schema,
|
||||
elementName,
|
||||
elementType,
|
||||
filePath
|
||||
});
|
||||
tabUid = tab.uid;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
tabUid = uidGen('T');
|
||||
this._addTab({ uid, tab: tabUid, content, type, autorun, database: workspaceTabs.database, schema, elementName, elementType });
|
||||
this._addTab({
|
||||
uid,
|
||||
tab: tabUid,
|
||||
content,
|
||||
type,
|
||||
autorun,
|
||||
database: workspaceTabs.database,
|
||||
schema,
|
||||
elementName,
|
||||
elementType,
|
||||
filePath
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -625,6 +669,7 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||
tab.schema === schema &&
|
||||
tab.elementName === elementName &&
|
||||
tab.elementType === elementType &&
|
||||
tab.database === workspaceTabs.database &&
|
||||
[`temp-${type}`, type].includes(tab.type))
|
||||
: false;
|
||||
|
||||
|
@ -635,7 +680,8 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||
database: workspaceTabs.database,
|
||||
schema,
|
||||
elementName,
|
||||
elementType
|
||||
elementType,
|
||||
filePath
|
||||
});
|
||||
tabUid = existentTab.uid;
|
||||
}
|
||||
|
@ -649,7 +695,8 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||
database: workspaceTabs.database,
|
||||
schema,
|
||||
elementName,
|
||||
elementType
|
||||
elementType,
|
||||
filePath
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -664,7 +711,8 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||
database: workspaceTabs.database,
|
||||
schema,
|
||||
elementName,
|
||||
elementType
|
||||
elementType,
|
||||
filePath
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
@ -687,8 +735,8 @@ export const useWorkspacesStore = defineStore('workspaces', {
|
|||
this.selectTab({ uid, tab: workspace.tabs[workspace.tabs.length - 1].uid });
|
||||
}
|
||||
},
|
||||
updateTabContent ({ uid, tab, type, schema, content }: WorkspaceTab) {
|
||||
this._replaceTab({ uid, tab, type, schema, content });
|
||||
updateTabContent ({ uid, tab, type, schema, content, elementName, filePath }: WorkspaceTab) {
|
||||
this._replaceTab({ uid, tab, type, schema, content, elementName, filePath });
|
||||
},
|
||||
renameTabs ({ uid, schema, elementName, elementNewName }: WorkspaceTab) {
|
||||
this.workspaces = (this.workspaces as Workspace[]).map(workspace => {
|
||||
|
|
Loading…
Reference in New Issue