Compare commits
119 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f11c4881f3 | |||
| 6398c6d7ce | |||
| 973ce8c3a7 | |||
| fe7b0e2bc7 | |||
| 0d82fd51c7 | |||
| 3b4d905485 | |||
| 53c63f0f4b | |||
| 5553e3cd8d | |||
| 67ee130a9e | |||
| 18d908fa63 | |||
| c61f58854e | |||
| f789ecd2f1 | |||
| 1fdc30804a | |||
| 5ba10d0acb | |||
| 12803d8154 | |||
| 36c391ccff | |||
| 765fb6297c | |||
| 66255769ad | |||
| 04a8d38641 | |||
| 859d020031 | |||
| 3c541117d0 | |||
| 80ca2e5215 | |||
| 19f2aa2997 | |||
| ec657f30c7 | |||
| 7e84d495f5 | |||
| c3baedd93c | |||
| ae9676f744 | |||
| 7ec156a5d1 | |||
| b80cbea1bc | |||
| 4600fa9f32 | |||
| 6e0b3e5cdc | |||
| 519ff87f5d | |||
| d4a363e37e | |||
| a3cfc45fef | |||
| 60602e02d9 | |||
| 44366f7872 | |||
| 2f18d8c204 | |||
| 08efbee52b | |||
| eac8d78c5d | |||
| db73673374 | |||
| 281cdb7264 | |||
| 101c80d820 | |||
| 1e06f65d9e | |||
| eea85709ed | |||
| cbca974529 | |||
| fc27f57580 | |||
| e50dd6606e | |||
| 8c9e232d65 | |||
| ef98888394 | |||
| e4b9ba34df | |||
| 169e0ec9df | |||
| 2779353a32 | |||
| 35aabb987c | |||
| 06f02070c7 | |||
| d138d3e786 | |||
| dbea68d33a | |||
| 72bcabf615 | |||
| 5db68eac24 | |||
| 7d9d88860e | |||
| 06c80ad982 | |||
| c4335527f8 | |||
| 90d27a2ad8 | |||
| 93fd0a9af0 | |||
| 987b0aeb41 | |||
| 8dc5ac0b25 | |||
| d310a47523 | |||
| bd8fa3776d | |||
| 305796af53 | |||
| 60c10a69a3 | |||
| c8aad9839f | |||
| 64016a1326 | |||
| a489c7ad8e | |||
| afb9ba7ad6 | |||
| b1de5b1120 | |||
| 144a23e89b | |||
| a6763a3e5d | |||
| f047ec787a | |||
| d80c368ccb | |||
| 9b60173b8c | |||
| 8556974ef1 | |||
| edf9f3a2be | |||
| 7bb9414be8 | |||
| 6e439adb51 | |||
| f6b783e74a | |||
| 171b81461c | |||
| 83db76aed8 | |||
| e6d1bb7e5c | |||
| a3d9fe76d6 | |||
| 0f4f154637 | |||
| 7d112a208f | |||
| c867d39d8d | |||
| 83b6c939f7 | |||
| e1e4eb5d6f | |||
| a14c08f122 | |||
| 5fd50dcf45 | |||
| b2ac4ee245 | |||
| 0ad7c0546b | |||
| 748381fef3 | |||
| 7eb9e42210 | |||
| 36a4b67ef4 | |||
| 94b35e3d5f | |||
| c5aa82b76a | |||
| c2920e195f | |||
| 92a78a419e | |||
| 4f27c2b852 | |||
| ece5779b41 | |||
| 167aaa8491 | |||
| 9ee0b32cac | |||
| d14ffcb736 | |||
| e694aca70b | |||
| dfeb910ac9 | |||
| 98f2b5dd08 | |||
| dca9ea24d7 | |||
| 37d54811e0 | |||
| 0f2af6eb37 | |||
| d6ae3d4f16 | |||
| 9c1819467a | |||
| 417334d140 | |||
| aa7fb74312 |
@@ -43,7 +43,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: f27a03d4aff5b00a009643df146a9c17bdbf7801
|
||||
ref: 626a30d67f40e910e8c3ed89ec34d5aa58c1f7e2
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: f27a03d4aff5b00a009643df146a9c17bdbf7801
|
||||
ref: 626a30d67f40e910e8c3ed89ec34d5aa58c1f7e2
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: f27a03d4aff5b00a009643df146a9c17bdbf7801
|
||||
ref: 626a30d67f40e910e8c3ed89ec34d5aa58c1f7e2
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: f27a03d4aff5b00a009643df146a9c17bdbf7801
|
||||
ref: 626a30d67f40e910e8c3ed89ec34d5aa58c1f7e2
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: f27a03d4aff5b00a009643df146a9c17bdbf7801
|
||||
ref: 626a30d67f40e910e8c3ed89ec34d5aa58c1f7e2
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
repository: dbgate/dbgate-pro
|
||||
token: ${{ secrets.GH_TOKEN }}
|
||||
path: dbgate-pro
|
||||
ref: f27a03d4aff5b00a009643df146a9c17bdbf7801
|
||||
ref: 626a30d67f40e910e8c3ed89ec34d5aa58c1f7e2
|
||||
- name: Merge dbgate/dbgate-pro
|
||||
run: |
|
||||
mkdir ../dbgate-pro
|
||||
|
||||
@@ -24,6 +24,7 @@ docker/plugins
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.translation
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
|
||||
@@ -8,6 +8,32 @@ Builds:
|
||||
- linux - application for linux
|
||||
- win - application for Windows
|
||||
|
||||
## 6.7.1 - not released yet
|
||||
- ADDED: LANGUAGE environment variable for the web version. #1266
|
||||
- ADDED: New localizations (Italian, Portugese (Brazil), Japanese)
|
||||
- ADDED: Option to detect language from browser settings in web version
|
||||
- FIXED: Check updates option no longer available in 6.7.0 #1263
|
||||
- FIXED: A MERGE statement must be terminated by a semi-colon (;), but dbgate stripped it. #1257
|
||||
- ADDED: Show table size #552
|
||||
- ADDED: Sort tables by size and by row count
|
||||
- ADDED: Connect to Legacy MongoDB (Premium) #540
|
||||
|
||||
## 6.7.0
|
||||
- ADDED: Added localization support, now you can use DbGate in multiple languages (French, Spanish, German, Czech, Slovak, Simplified Chinese) #347 #705 #939 #1079
|
||||
- CHANGED: Solved many issues with binary fields, huge performance improvements in binary fields processing
|
||||
- FIXED: Export to CSV produces empty file #1247
|
||||
- CHANGED: Upgraded electron to version 38 #1243
|
||||
- FIXED: PostgreSQL export to SQL and XML doesn't include bytea field contents #1228
|
||||
- FIXED: Export CSV broken #1080
|
||||
- FIXED: Inconsistent handling of hex-like strings #680
|
||||
- FIXED: Export mongodb binary cell as binary file #292
|
||||
- CHANGED: SSL is used automatically for connections to Azure databases
|
||||
- ADDED: New export formats CSV for Excel, TSV
|
||||
- FIXED: Horizontal scrolling on macOS trackpad/Magic Mouse #1250
|
||||
|
||||
## 6.6.12
|
||||
- FIXED: Cannot paste license key on Mac (special commands like copy/paste were disabled on license screen)
|
||||
|
||||
## 6.6.11
|
||||
- FIXED: Fixed theming on application startup
|
||||
- CHANGED: Improved licensing page
|
||||
|
||||
+31
-6
@@ -31,6 +31,16 @@ let mainModule;
|
||||
let appUpdateStatus = '';
|
||||
let settingsJson = {};
|
||||
|
||||
function getTranslated(key) {
|
||||
if (typeof key === 'string' && global.TRANSLATION_DATA?.[key]) {
|
||||
return global.TRANSLATION_DATA?.[key];
|
||||
}
|
||||
if (typeof key?._transKey === 'string') {
|
||||
return global.TRANSLATION_DATA?.[key._transKey] ?? key._transOptions?.defaultMessage;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
process.on('uncaughtException', function (error) {
|
||||
console.error('uncaughtException', error);
|
||||
});
|
||||
@@ -63,6 +73,7 @@ try {
|
||||
let mainWindow;
|
||||
let mainMenu;
|
||||
let runCommandOnLoad = null;
|
||||
let mainWindowMenuSet = false;
|
||||
|
||||
log.transports.file.level = 'debug';
|
||||
autoUpdater.logger = log;
|
||||
@@ -91,11 +102,16 @@ function commandItem(item, disableAll = false) {
|
||||
if (item.skipInApp) {
|
||||
return { skip: true };
|
||||
}
|
||||
if (!command) {
|
||||
return { skip: true };
|
||||
}
|
||||
return {
|
||||
id,
|
||||
label: command ? command.menuName || command.toolbarName || command.name : id,
|
||||
label: command
|
||||
? getTranslated(command.menuName) || getTranslated(command.toolbarName) || getTranslated(command.name)
|
||||
: id,
|
||||
accelerator: formatKeyText(command ? command.keyText : undefined),
|
||||
enabled: command ? command.enabled && !disableAll : false,
|
||||
enabled: command ? command.enabled && (!disableAll || command.systemCommand) : false,
|
||||
click() {
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('run-command', id);
|
||||
@@ -155,11 +171,14 @@ ipcMain.on('update-commands', async (event, arg) => {
|
||||
const command = commands[key];
|
||||
|
||||
// rebuild menu
|
||||
if (menu.label != command.text || menu.accelerator != command.keyText) {
|
||||
if (global.TRANSLATION_DATA && (menu.label != command.text || menu.accelerator != command.keyText)) {
|
||||
mainMenu = buildMenu(isModalOpened || !!dbgatePage);
|
||||
|
||||
Menu.setApplicationMenu(mainMenu);
|
||||
// mainWindow.setMenu(mainMenu);
|
||||
if (!mainWindowMenuSet) {
|
||||
mainWindow.setMenu(mainMenu);
|
||||
mainWindowMenuSet = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -306,6 +325,12 @@ ipcMain.on('check-for-updates', async (event, url) => {
|
||||
autoUpdater.autoDownload = false;
|
||||
autoUpdater.checkForUpdates();
|
||||
});
|
||||
ipcMain.on('translation-data', async (event, arg) => {
|
||||
global.TRANSLATION_DATA = JSON.parse(arg);
|
||||
mainMenu = buildMenu();
|
||||
Menu.setApplicationMenu(mainMenu);
|
||||
mainWindow.setMenu(mainMenu);
|
||||
});
|
||||
|
||||
function fillMissingSettings(value) {
|
||||
const res = {
|
||||
@@ -382,8 +407,8 @@ function createWindow() {
|
||||
mainWindow.setFullScreen(true);
|
||||
}
|
||||
|
||||
mainMenu = buildMenu();
|
||||
mainWindow.setMenu(mainMenu);
|
||||
// mainMenu = buildMenu();
|
||||
// mainWindow.setMenu(mainMenu);
|
||||
|
||||
function loadMainWindow() {
|
||||
const startUrl =
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
module.exports = ({ editMenu, isMac }) => [
|
||||
function _t(key, { defaultMessage, currentTranslations } = {}) {
|
||||
return (currentTranslations || global.TRANSLATION_DATA)?.[key] || defaultMessage;
|
||||
}
|
||||
|
||||
module.exports = ({ editMenu, isMac }, currentTranslations = null) => [
|
||||
{
|
||||
label: 'File',
|
||||
label: _t('menu.file', { defaultMessage: 'File', currentTranslations }),
|
||||
submenu: [
|
||||
{ command: 'new.connection', hideDisabled: true },
|
||||
{ command: 'new.sqliteDatabase', hideDisabled: true },
|
||||
@@ -28,7 +32,7 @@ module.exports = ({ editMenu, isMac }) => [
|
||||
},
|
||||
editMenu
|
||||
? {
|
||||
label: 'Edit',
|
||||
label: _t('menu.edit', { defaultMessage: 'Edit', currentTranslations }),
|
||||
submenu: [
|
||||
{ command: 'edit.undo' },
|
||||
{ command: 'edit.redo' },
|
||||
@@ -53,7 +57,7 @@ module.exports = ({ editMenu, isMac }) => [
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
label: 'View',
|
||||
label: _t('menu.view', { defaultMessage: 'View', currentTranslations }),
|
||||
submenu: [
|
||||
{ command: 'app.reload', hideDisabled: true },
|
||||
{ command: 'app.toggleDevTools', hideDisabled: true },
|
||||
@@ -75,7 +79,7 @@ module.exports = ({ editMenu, isMac }) => [
|
||||
],
|
||||
},
|
||||
{
|
||||
label: 'Tools',
|
||||
label: _t('menu.tools', { defaultMessage: 'Tools', currentTranslations }),
|
||||
submenu: [
|
||||
{ command: 'database.search', hideDisabled: true },
|
||||
{ command: 'commandPalette.show', hideDisabled: true },
|
||||
@@ -102,7 +106,7 @@ module.exports = ({ editMenu, isMac }) => [
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: 'Help',
|
||||
label: _t('menu.help', { defaultMessage: 'Help', currentTranslations }),
|
||||
submenu: [
|
||||
{ command: 'app.openDocs', hideDisabled: true },
|
||||
{ command: 'app.openWeb', hideDisabled: true },
|
||||
@@ -114,7 +118,7 @@ module.exports = ({ editMenu, isMac }) => [
|
||||
{ command: 'tabs.changelog', hideDisabled: true },
|
||||
{ command: 'about.show', hideDisabled: true },
|
||||
{ divider: true },
|
||||
{ command: 'file.checkForUpdates', hideDisabled: true },
|
||||
{ command: 'app.checkForUpdates', hideDisabled: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -160,4 +160,31 @@ program
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('sort')
|
||||
.description('Sort translation files by keys')
|
||||
.action(() => {
|
||||
try {
|
||||
const languages = getAllNonDefaultLanguages();
|
||||
for (const language of languages) {
|
||||
const filePath = `./translations/${language}.json`;
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
const translations = JSON.parse(content);
|
||||
const sortedTranslations = {};
|
||||
Object.keys(translations)
|
||||
.sort()
|
||||
.forEach(key => {
|
||||
// @ts-ignore
|
||||
sortedTranslations[key] = translations[key];
|
||||
});
|
||||
fs.writeFileSync(filePath, JSON.stringify(sortedTranslations, null, 2), 'utf-8');
|
||||
console.log(`Sorted translations for language: ${language}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
console.error('Error during sort:', error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = { program };
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
require('dotenv').config({ path: '.env.translation' });
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const OpenAI = require('openai');
|
||||
|
||||
const client = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
||||
|
||||
const translationsDir = path.join(__dirname, '../../translations');
|
||||
const enFilePath = path.join(translationsDir, 'en.json');
|
||||
|
||||
const languageNames = {
|
||||
'cs.json': 'Czech',
|
||||
'de.json': 'German',
|
||||
'es.json': 'Spanish',
|
||||
'fr.json': 'French',
|
||||
'it.json': 'Italian',
|
||||
'ja.json': 'Japanese',
|
||||
'pt.json': 'Portuguese',
|
||||
'sk.json': 'Slovak',
|
||||
'zh.json': 'Chinese'
|
||||
};
|
||||
|
||||
// Read source (english)
|
||||
const enTranslations = JSON.parse(fs.readFileSync(enFilePath, 'utf8'));
|
||||
const enKeys = Object.keys(enTranslations);
|
||||
|
||||
// Get all translation files
|
||||
const translationFiles = fs.readdirSync(translationsDir)
|
||||
.filter(file => file.endsWith('.json') && file !== 'en.json')
|
||||
.sort();
|
||||
|
||||
console.log(`Found ${enKeys.length} keys in en.json\n`);
|
||||
console.log('='.repeat(80));
|
||||
|
||||
async function translateMissingIds({file, translations, missingIds}){
|
||||
const languageName = languageNames[file];
|
||||
if (!languageName) {
|
||||
console.log(`No language name mapping for file: ${file}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Build object with only missing translations
|
||||
const needed = {};
|
||||
missingIds.forEach(key => {
|
||||
needed[key] = enTranslations[key];
|
||||
});
|
||||
|
||||
// Get all existing translations as style examples
|
||||
const existingTranslations = {};
|
||||
Object.keys(translations).forEach(key => {
|
||||
if (translations[key] && !translations[key].startsWith('***')) {
|
||||
existingTranslations[key] = {
|
||||
en: enTranslations[key],
|
||||
translated: translations[key]
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const prompt = `You are a professional translator for DbGate, a database management application.
|
||||
|
||||
Translate the following English UI strings to ${languageName}.
|
||||
|
||||
IMPORTANT RULES:
|
||||
1. Preserve ALL placeholders exactly as they appear: {plugin}, {columnNumber}, {0}, {1}, etc.
|
||||
2. Maintain technical terminology appropriately for database software
|
||||
3. Match the translation style, tone, and formality of the existing translations shown below
|
||||
4. Keep the same level of brevity or verbosity as the existing translations
|
||||
5. Return ONLY valid JSON - no markdown, no explanations, no code blocks
|
||||
6. Use the same keys as provided
|
||||
|
||||
EXISTING TRANSLATIONS (for style reference):
|
||||
${JSON.stringify(existingTranslations, null, 2)}
|
||||
|
||||
STRINGS TO TRANSLATE:
|
||||
${JSON.stringify(needed, null, 2)}
|
||||
|
||||
Return format: {"key": "translated value", ...}`;
|
||||
|
||||
const response = await client.chat.completions.create({
|
||||
model: 'gpt-5.1',
|
||||
messages: [
|
||||
{ role: 'system', content: 'You are a professional translator specializing in software localization. Match the style and tone of existing translations. Return only valid JSON.' },
|
||||
{ role: 'user', content: prompt }
|
||||
],
|
||||
temperature: 0.2
|
||||
});
|
||||
|
||||
let translatedJson = response.choices[0].message.content.trim();
|
||||
|
||||
// Remove markdown code blocks if present
|
||||
translatedJson = translatedJson.replace(/^```json\n?/, '').replace(/\n?```$/, '');
|
||||
|
||||
return JSON.parse(translatedJson);
|
||||
}
|
||||
|
||||
(async () => {
|
||||
for (const file of translationFiles) {
|
||||
const filePath = path.join(translationsDir, file);
|
||||
const translations = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||
|
||||
const missingIds = enKeys.filter(key => !translations.hasOwnProperty(key) || (typeof translations[key] === 'string' && translations[key].startsWith('***')));
|
||||
|
||||
|
||||
console.log(`\n${file.toUpperCase()}`);
|
||||
console.log('-'.repeat(80));
|
||||
|
||||
if (missingIds.length === 0) {
|
||||
console.log('✓ All translations complete!');
|
||||
continue;
|
||||
} else {
|
||||
console.log(`Found ${missingIds.length} untranslated IDs\n`);
|
||||
}
|
||||
|
||||
const newTranslations = await translateMissingIds({file, translations, missingIds});
|
||||
|
||||
if (!newTranslations) {
|
||||
console.log(`Skipping file due to translation error: ${file}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(newTranslations)) {
|
||||
translations[key] = value;
|
||||
console.log(`Translated: ${key} => ${value}`);
|
||||
}
|
||||
|
||||
fs.writeFileSync(filePath, JSON.stringify(translations, null, 2) + '\n', 'utf8');
|
||||
console.log(`\n✓ Updated translations written to ${file}`);
|
||||
}
|
||||
|
||||
console.log('\n' + '='.repeat(80));
|
||||
console.log('Translation complete!\n');
|
||||
})();
|
||||
@@ -4,6 +4,7 @@ const volatilePackages = [
|
||||
'@clickhouse/client',
|
||||
'bson', // this package is already bundled and is used in mongodb
|
||||
'mongodb',
|
||||
'mongodb-old',
|
||||
'mongodb-client-encryption',
|
||||
'tedious',
|
||||
'msnodesqlv8',
|
||||
|
||||
@@ -110,7 +110,7 @@ describe('Charts', () => {
|
||||
cy.themeshot('new-object-window');
|
||||
});
|
||||
|
||||
it.only('Database chat - charts', () => {
|
||||
it('Database chat - charts', () => {
|
||||
cy.contains('MySql-connection').click();
|
||||
cy.contains('MyChinook').click();
|
||||
cy.testid('TabsPanel_buttonNewObject').click();
|
||||
@@ -119,7 +119,9 @@ describe('Charts', () => {
|
||||
cy.get('body').realType('show me chart of most popular genres');
|
||||
cy.get('body').realPress('{enter}');
|
||||
cy.testid('DatabaseChatTab_executeAllQueries', { timeout: 30000 }).click();
|
||||
cy.testid('chart-canvas', { timeout: 30000 }).should($c => expect($c[0].toDataURL()).to.match(/^data:image\/png;base64/));
|
||||
cy.testid('chart-canvas', { timeout: 30000 }).should($c =>
|
||||
expect($c[0].toDataURL()).to.match(/^data:image\/png;base64/)
|
||||
);
|
||||
cy.themeshot('database-chat-chart');
|
||||
});
|
||||
|
||||
@@ -155,15 +157,51 @@ describe('Charts', () => {
|
||||
cy.testid('MessageViewRow-explainErrorButton-1').click();
|
||||
cy.testid('ChatCodeRenderer_useSqlButton', { timeout: 30000 });
|
||||
cy.themeshot('explain-query-error');
|
||||
});
|
||||
|
||||
// cy.testid('TabsPanel_buttonNewObject').click();
|
||||
// cy.testid('NewObjectModal_databaseChat').click();
|
||||
// cy.wait(1000);
|
||||
// cy.get('body').realType('show me chart of most popular genres');
|
||||
// cy.get('body').realPress('{enter}');
|
||||
// cy.testid('DatabaseChatTab_executeAllQueries', { timeout: 30000 }).click();
|
||||
// cy.wait(5000);
|
||||
// cy.testid('chart-canvas').should($c => expect($c[0].toDataURL()).to.match(/^data:image\/png;base64/));
|
||||
// cy.themeshot('database-chat-chart');
|
||||
it('Switch language', () => {
|
||||
cy.contains('MySql-connection').click();
|
||||
cy.contains('MyChinook').click();
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.contains('Settings').click();
|
||||
|
||||
cy.testid('SettingsModal_languageSelect').select('Deutsch');
|
||||
cy.testid('ConfirmModal_okButton').click();
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.contains('Einstellungen').click();
|
||||
cy.contains('Lokalisierung');
|
||||
cy.themeshot('switch-language-de');
|
||||
|
||||
cy.testid('SettingsModal_languageSelect').select('Français');
|
||||
cy.testid('ConfirmModal_okButton').click();
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.contains('Paramètres').click();
|
||||
cy.contains('Localisation');
|
||||
cy.themeshot('switch-language-fr');
|
||||
|
||||
cy.testid('SettingsModal_languageSelect').select('Español');
|
||||
cy.testid('ConfirmModal_okButton').click();
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.contains('Configuración').click();
|
||||
cy.contains('Localización');
|
||||
cy.themeshot('switch-language-es');
|
||||
|
||||
cy.testid('SettingsModal_languageSelect').select('Čeština');
|
||||
cy.testid('ConfirmModal_okButton').click();
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.contains('Nastavení').click();
|
||||
cy.contains('Lokalizace');
|
||||
cy.themeshot('switch-language-cs');
|
||||
|
||||
cy.testid('SettingsModal_languageSelect').select('中文');
|
||||
cy.testid('ConfirmModal_okButton').click();
|
||||
cy.testid('WidgetIconPanel_settings').click();
|
||||
cy.contains('设置').click();
|
||||
cy.contains('本地化');
|
||||
cy.themeshot('switch-language-zh');
|
||||
|
||||
cy.testid('SettingsModal_languageSelect').select('English');
|
||||
cy.testid('ConfirmModal_okButton').click();
|
||||
cy.testid('WidgetIconPanel_settings');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,6 +49,32 @@ class StreamHandler {
|
||||
}
|
||||
}
|
||||
|
||||
class BinaryTestStreamHandler {
|
||||
constructor(resolve, reject, expectedValue) {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
this.expectedValue = expectedValue;
|
||||
this.rowsReceived = [];
|
||||
}
|
||||
row(row) {
|
||||
try {
|
||||
this.rowsReceived.push(row);
|
||||
if (this.expectedValue) {
|
||||
expect(row).toEqual(this.expectedValue);
|
||||
}
|
||||
} catch (error) {
|
||||
this.reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
recordset(columns) {}
|
||||
done(result) {
|
||||
this.resolve(this.rowsReceived);
|
||||
}
|
||||
info(msg) {}
|
||||
}
|
||||
|
||||
|
||||
function executeStreamItem(driver, conn, sql) {
|
||||
return new Promise(resolve => {
|
||||
const handler = new StreamHandler(resolve);
|
||||
@@ -223,4 +249,51 @@ describe('Query', () => {
|
||||
expect(row[keys[0]] == 1).toBeTruthy();
|
||||
})
|
||||
);
|
||||
|
||||
test.each(engines.filter(x => x.binaryDataType).map(engine => [engine.label, engine]))(
|
||||
'Binary - %s',
|
||||
testWrapper(async (dbhan, driver, engine) => {
|
||||
await runCommandOnDriver(dbhan, driver, dmp =>
|
||||
dmp.createTable({
|
||||
pureName: 't1',
|
||||
columns: [
|
||||
{ columnName: 'id', dataType: 'int', notNull: true, autoIncrement: true },
|
||||
{ columnName: 'val', dataType: engine.binaryDataType },
|
||||
],
|
||||
primaryKey: {
|
||||
columns: [{ columnName: 'id' }],
|
||||
},
|
||||
})
|
||||
);
|
||||
const structure = await driver.analyseFull(dbhan);
|
||||
const table = structure.tables.find(x => x.pureName == 't1');
|
||||
|
||||
const dmp = driver.createDumper();
|
||||
dmp.putCmd("INSERT INTO ~t1 (~val) VALUES (%v)", {
|
||||
$binary: { base64: 'iVBORw0KWgo=' },
|
||||
});
|
||||
await driver.query(dbhan, dmp.s, {discardResult: true});
|
||||
|
||||
const dmp2 = driver.createDumper();
|
||||
dmp2.put('SELECT ~val FROM ~t1');
|
||||
const res = await driver.query(dbhan, dmp2.s);
|
||||
|
||||
const row = res.rows[0];
|
||||
const keys = Object.keys(row);
|
||||
expect(keys.length).toEqual(1);
|
||||
expect(row[keys[0]]).toEqual({$binary: {base64: 'iVBORw0KWgo='}});
|
||||
|
||||
const res2 = await driver.readQuery(dbhan, dmp2.s);
|
||||
const rows = await Array.fromAsync(res2);
|
||||
const rowsVal = rows.filter(r => r.val != null);
|
||||
|
||||
expect(rowsVal.length).toEqual(1);
|
||||
expect(rowsVal[0].val).toEqual({$binary: {base64: 'iVBORw0KWgo='}});
|
||||
|
||||
const res3 = await new Promise((resolve, reject) => {
|
||||
const handler = new BinaryTestStreamHandler(resolve, reject, {val: {$binary: {base64: 'iVBORw0KWgo='}}});
|
||||
driver.stream(dbhan, dmp2.s, handler);
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -44,6 +44,7 @@ const mysqlEngine = {
|
||||
supportRenameSqlObject: false,
|
||||
dbSnapshotBySeconds: true,
|
||||
dumpFile: 'data/chinook-mysql.sql',
|
||||
binaryDataType: 'blob',
|
||||
dumpChecks: [
|
||||
{
|
||||
sql: 'select count(*) as res from genre',
|
||||
@@ -216,6 +217,7 @@ const postgreSqlEngine = {
|
||||
supportSchemas: true,
|
||||
supportRenameSqlObject: true,
|
||||
defaultSchemaName: 'public',
|
||||
binaryDataType: 'bytea',
|
||||
dumpFile: 'data/chinook-postgre.sql',
|
||||
dumpChecks: [
|
||||
{
|
||||
@@ -446,6 +448,7 @@ const sqlServerEngine = {
|
||||
supportTableComments: true,
|
||||
supportColumnComments: true,
|
||||
// skipSeparateSchemas: true,
|
||||
binaryDataType: 'varbinary(100)',
|
||||
triggers: [
|
||||
{
|
||||
testName: 'triggers before each row',
|
||||
@@ -506,6 +509,7 @@ const sqliteEngine = {
|
||||
},
|
||||
},
|
||||
],
|
||||
binaryDataType: 'blob',
|
||||
};
|
||||
|
||||
const libsqlFileEngine = {
|
||||
@@ -619,6 +623,7 @@ const oracleEngine = {
|
||||
},
|
||||
},
|
||||
],
|
||||
binaryDataType: 'blob',
|
||||
};
|
||||
|
||||
/** @type {import('dbgate-types').TestEngineInfo} */
|
||||
@@ -752,18 +757,18 @@ const enginesOnCi = [
|
||||
const enginesOnLocal = [
|
||||
// all engines, which would be run on local test
|
||||
// cassandraEngine,
|
||||
// mysqlEngine,
|
||||
//mysqlEngine,
|
||||
// mariaDbEngine,
|
||||
// postgreSqlEngine,
|
||||
// sqlServerEngine,
|
||||
// sqliteEngine,
|
||||
//postgreSqlEngine,
|
||||
//sqlServerEngine,
|
||||
sqliteEngine,
|
||||
// cockroachDbEngine,
|
||||
// clickhouseEngine,
|
||||
// libsqlFileEngine,
|
||||
// libsqlWsEngine,
|
||||
// oracleEngine,
|
||||
//oracleEngine,
|
||||
// duckdbEngine,
|
||||
firebirdEngine,
|
||||
//firebirdEngine,
|
||||
];
|
||||
|
||||
/** @type {import('dbgate-types').TestEngineInfo[] & Record<string, import('dbgate-types').TestEngineInfo>} */
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "6.6.12-premium-beta.3",
|
||||
"version": "6.7.1-premium-beta.3",
|
||||
"name": "dbgate-all",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
@@ -74,6 +74,7 @@
|
||||
"translations:add-missing": "node common/translations-cli/index.js add-missing",
|
||||
"translations:remove-unused": "node common/translations-cli/index.js remove-unused",
|
||||
"translations:check": "node common/translations-cli/index.js check",
|
||||
"translations:sort": "node common/translations-cli/index.js sort",
|
||||
"errors": "node common/assign-dbgm-codes.mjs ."
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ DEVMODE=1
|
||||
SHELL_SCRIPTING=1
|
||||
ALLOW_DBGATE_PRIVATE_CLOUD=1
|
||||
DEVWEB=1
|
||||
LOCAL_AUTH_PROXY=1
|
||||
# LOCAL_AUTH_PROXY=1
|
||||
# LOCAL_AI_GATEWAY=true
|
||||
|
||||
# REDIRECT_TO_DBGATE_CLOUD_LOGIN=1
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"cors": "^2.8.5",
|
||||
"cross-env": "^6.0.3",
|
||||
"dbgate-datalib": "^6.0.0-alpha.1",
|
||||
"dbgate-query-splitter": "^4.11.7",
|
||||
"dbgate-query-splitter": "^4.11.9",
|
||||
"dbgate-sqltree": "^6.0.0-alpha.1",
|
||||
"dbgate-tools": "^6.0.0-alpha.1",
|
||||
"debug": "^4.3.4",
|
||||
|
||||
@@ -35,8 +35,8 @@ module.exports = {
|
||||
},
|
||||
|
||||
refreshPublicFiles_meta: true,
|
||||
async refreshPublicFiles({ isRefresh }) {
|
||||
await refreshPublicFiles(isRefresh);
|
||||
async refreshPublicFiles({ isRefresh }, req) {
|
||||
await refreshPublicFiles(isRefresh, req?.headers?.['x-ui-language']);
|
||||
return {
|
||||
status: 'ok',
|
||||
};
|
||||
|
||||
@@ -71,6 +71,7 @@ module.exports = {
|
||||
const isLicenseValid = checkedLicense?.status == 'ok';
|
||||
const logoutUrl = storageConnectionError ? null : await authProvider.getLogoutUrl();
|
||||
const adminConfig = storageConnectionError ? null : await storage.readConfig({ group: 'admin' });
|
||||
const settingsConfig = storageConnectionError ? null : await storage.readConfig({ group: 'settings' });
|
||||
|
||||
storage.startRefreshLicense();
|
||||
|
||||
@@ -121,6 +122,7 @@ module.exports = {
|
||||
allowPrivateCloud: platformInfo.isElectron || !!process.env.ALLOW_DBGATE_PRIVATE_CLOUD,
|
||||
...currentVersion,
|
||||
redirectToDbGateCloudLogin: !!process.env.REDIRECT_TO_DBGATE_CLOUD_LOGIN,
|
||||
preferrendLanguage: settingsConfig?.['storage.language'] || process.env.LANGUAGE || null,
|
||||
};
|
||||
|
||||
return configResult;
|
||||
|
||||
@@ -14,7 +14,11 @@ const JsonLinesDatabase = require('../utility/JsonLinesDatabase');
|
||||
const processArgs = require('../utility/processArgs');
|
||||
const { safeJsonParse, getLogger, extractErrorLogData } = require('dbgate-tools');
|
||||
const platformInfo = require('../utility/platformInfo');
|
||||
const { connectionHasPermission, testConnectionPermission, loadPermissionsFromRequest } = require('../utility/hasPermission');
|
||||
const {
|
||||
connectionHasPermission,
|
||||
testConnectionPermission,
|
||||
loadPermissionsFromRequest,
|
||||
} = require('../utility/hasPermission');
|
||||
const pipeForkLogs = require('../utility/pipeForkLogs');
|
||||
const requireEngineDriver = require('../utility/requireEngineDriver');
|
||||
const { getAuthProviderById } = require('../auth/authProvider');
|
||||
@@ -116,7 +120,10 @@ function getPortalCollections() {
|
||||
}
|
||||
}
|
||||
|
||||
logger.info({ connections: connections.map(pickSafeConnectionInfo) }, 'DBGM-00005 Using connections from ENV variables');
|
||||
logger.info(
|
||||
{ connections: connections.map(pickSafeConnectionInfo) },
|
||||
'DBGM-00005 Using connections from ENV variables'
|
||||
);
|
||||
const noengine = connections.filter(x => !x.engine);
|
||||
if (noengine.length > 0) {
|
||||
logger.warn(
|
||||
@@ -502,7 +509,11 @@ module.exports = {
|
||||
state,
|
||||
client: 'web',
|
||||
});
|
||||
res.redirect(authResp.url);
|
||||
if (authResp?.url) {
|
||||
res.redirect(authResp.url);
|
||||
return;
|
||||
}
|
||||
res.json({ error: 'No URL returned from auth provider' });
|
||||
},
|
||||
|
||||
dbloginApp_meta: true,
|
||||
|
||||
@@ -1533,6 +1533,12 @@ module.exports = {
|
||||
"columnName": "name",
|
||||
"dataType": "varchar(250)",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"pureName": "team_file_types",
|
||||
"columnName": "format",
|
||||
"dataType": "varchar(50)",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"foreignKeys": [],
|
||||
@@ -1549,7 +1555,13 @@ module.exports = {
|
||||
"preloadedRows": [
|
||||
{
|
||||
"id": -1,
|
||||
"name": "sql"
|
||||
"name": "sql",
|
||||
"format": "text"
|
||||
},
|
||||
{
|
||||
"id": -2,
|
||||
"name": "diagrams",
|
||||
"format": "json"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -193,7 +193,7 @@ async function getCloudSigninHeaders(holder = null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
async function updateCloudFiles(isRefresh) {
|
||||
async function updateCloudFiles(isRefresh, language) {
|
||||
let lastCloudFilesTags;
|
||||
try {
|
||||
lastCloudFilesTags = await fs.readFile(path.join(datadir(), 'cloud-files-tags.txt'), 'utf-8');
|
||||
@@ -218,6 +218,7 @@ async function updateCloudFiles(isRefresh) {
|
||||
...getLicenseHttpHeaders(),
|
||||
...(await getCloudInstanceHeaders()),
|
||||
'x-app-version': currentVersion.version,
|
||||
'x-app-language': language || 'en',
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -274,7 +275,7 @@ async function ensurePromoWidgetDataLoaded() {
|
||||
promoWidgetDataLoaded = true;
|
||||
}
|
||||
|
||||
async function updatePremiumPromoWidget() {
|
||||
async function updatePremiumPromoWidget(language) {
|
||||
await ensurePromoWidgetDataLoaded();
|
||||
|
||||
const tags = (await collectCloudFilesSearchTags()).join(',');
|
||||
@@ -283,8 +284,10 @@ async function updatePremiumPromoWidget() {
|
||||
`${DBGATE_CLOUD_URL}/premium-promo-widget?identifier=${promoWidgetData?.identifier ?? 'empty'}&tags=${tags}`,
|
||||
{
|
||||
headers: {
|
||||
...getLicenseHttpHeaders(),
|
||||
...(await getCloudInstanceHeaders()),
|
||||
'x-app-version': currentVersion.version,
|
||||
'x-app-language': language || 'en',
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -299,17 +302,21 @@ async function updatePremiumPromoWidget() {
|
||||
socket.emitChanged(`promo-widget-changed`);
|
||||
}
|
||||
|
||||
async function refreshPublicFiles(isRefresh) {
|
||||
async function refreshPublicFiles(isRefresh, uiLanguage) {
|
||||
const language = platformInfo.isElectron
|
||||
? (await config.getCachedSettings())?.['localization.language'] || 'en'
|
||||
: uiLanguage;
|
||||
if (!cloudFiles) {
|
||||
await loadCloudFiles();
|
||||
}
|
||||
try {
|
||||
await updateCloudFiles(isRefresh);
|
||||
await updateCloudFiles(isRefresh, language);
|
||||
} catch (err) {
|
||||
logger.error(extractErrorLogData(err), 'DBGM-00166 Error updating cloud files');
|
||||
}
|
||||
if (!isProApp()) {
|
||||
await updatePremiumPromoWidget();
|
||||
const configSettings = await config.get();
|
||||
if (!isProApp() || configSettings?.trialDaysLeft != null) {
|
||||
await updatePremiumPromoWidget(language);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ class QueryStreamTableWriter {
|
||||
this.started = new Date().getTime();
|
||||
}
|
||||
|
||||
initializeFromQuery(structure, resultIndex, chartDefinition, autoDetectCharts = false) {
|
||||
initializeFromQuery(structure, resultIndex, chartDefinition, autoDetectCharts = false, options = {}) {
|
||||
this.jslid = crypto.randomUUID();
|
||||
this.currentFile = path.join(jsldir(), `${this.jslid}.jsonl`);
|
||||
fs.writeFileSync(
|
||||
@@ -25,6 +25,7 @@ class QueryStreamTableWriter {
|
||||
JSON.stringify({
|
||||
...structure,
|
||||
__isStreamHeader: true,
|
||||
...options
|
||||
}) + '\n'
|
||||
);
|
||||
this.currentStream = fs.createWriteStream(this.currentFile, { flags: 'a' });
|
||||
@@ -179,7 +180,7 @@ class StreamHandler {
|
||||
process.send({ msgtype: 'changedCurrentDatabase', database, sesid: this.sesid });
|
||||
}
|
||||
|
||||
recordset(columns) {
|
||||
recordset(columns, options) {
|
||||
if (this.rowsLimitOverflow) {
|
||||
return;
|
||||
}
|
||||
@@ -189,7 +190,8 @@ class StreamHandler {
|
||||
Array.isArray(columns) ? { columns } : columns,
|
||||
this.queryStreamInfoHolder.resultIndex,
|
||||
this.frontMatter?.[`chart-${this.queryStreamInfoHolder.resultIndex + 1}`],
|
||||
this.autoDetectCharts
|
||||
this.autoDetectCharts,
|
||||
options
|
||||
);
|
||||
this.queryStreamInfoHolder.resultIndex += 1;
|
||||
this.rowCounter = 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { arrayToHexString, evalFilterBehaviour, isTypeDateTime } from 'dbgate-tools';
|
||||
import { arrayToHexString, base64ToHex, evalFilterBehaviour, isTypeDateTime } from 'dbgate-tools';
|
||||
import { format, toDate } from 'date-fns';
|
||||
import _isString from 'lodash/isString';
|
||||
import _cloneDeepWith from 'lodash/cloneDeepWith';
|
||||
@@ -24,7 +24,9 @@ export function getFilterValueExpression(value, dataType?) {
|
||||
if (value.type == 'Buffer' && Array.isArray(value.data)) {
|
||||
return '0x' + arrayToHexString(value.data);
|
||||
}
|
||||
|
||||
if (value?.$binary?.base64) {
|
||||
return base64ToHex(value.$binary.base64);
|
||||
}
|
||||
return `="${value}"`;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import P from 'parsimmon';
|
||||
import moment from 'moment';
|
||||
import { Condition } from 'dbgate-sqltree';
|
||||
import { interpretEscapes, token, word, whitespace } from './common';
|
||||
import { hexStringToArray, parseNumberSafe } from 'dbgate-tools';
|
||||
import { hexToBase64, parseNumberSafe } from 'dbgate-tools';
|
||||
import { FilterBehaviour, TransformType } from 'dbgate-types';
|
||||
|
||||
const binaryCondition =
|
||||
@@ -385,10 +385,7 @@ const createParser = (filterBehaviour: FilterBehaviour) => {
|
||||
|
||||
hexstring: () =>
|
||||
token(P.regexp(/0x(([0-9a-fA-F][0-9a-fA-F])+)/, 1))
|
||||
.map(x => ({
|
||||
type: 'Buffer',
|
||||
data: hexStringToArray(x),
|
||||
}))
|
||||
.map(x => ({ $binary: { base64: hexToBase64(x) } }))
|
||||
.desc('hex string'),
|
||||
|
||||
noQuotedString: () => P.regexp(/[^\s^,^'^"]+/).desc('string unquoted'),
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"blueimp-md5": "^2.19.0",
|
||||
"dbgate-query-splitter": "^4.11.7",
|
||||
"dbgate-query-splitter": "^4.11.9",
|
||||
"dbgate-sqltree": "^6.0.0-alpha.1",
|
||||
"debug": "^4.3.4",
|
||||
"json-stable-stringify": "^1.0.1",
|
||||
|
||||
@@ -78,6 +78,14 @@ export class SqlDumper implements AlterProcessor {
|
||||
else if (_isNumber(value)) this.putRaw(value.toString());
|
||||
else if (_isDate(value)) this.putStringValue(new Date(value).toISOString());
|
||||
else if (value?.type == 'Buffer' && _isArray(value?.data)) this.putByteArrayValue(value?.data);
|
||||
else if (value?.$binary?.base64) {
|
||||
const binary = atob(value.$binary.base64);
|
||||
const bytes = new Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i++) {
|
||||
bytes[i] = binary.charCodeAt(i);
|
||||
}
|
||||
this.putByteArrayValue(bytes);
|
||||
}
|
||||
else if (value?.$bigint) this.putRaw(value?.$bigint);
|
||||
else if (_isPlainObject(value) || _isArray(value)) this.putStringValue(JSON.stringify(value));
|
||||
else this.put('^null');
|
||||
|
||||
@@ -47,6 +47,7 @@ export const mongoFilterBehaviour: FilterBehaviour = {
|
||||
allowStringToken: true,
|
||||
allowNumberDualTesting: true,
|
||||
allowObjectIdTesting: true,
|
||||
allowHexString: true,
|
||||
};
|
||||
|
||||
export const evalFilterBehaviour: FilterBehaviour = {
|
||||
|
||||
@@ -43,6 +43,19 @@ export function hexStringToArray(inputString) {
|
||||
return res;
|
||||
}
|
||||
|
||||
export function base64ToHex(base64String) {
|
||||
const binaryString = atob(base64String);
|
||||
const hexString = Array.from(binaryString, c =>
|
||||
c.charCodeAt(0).toString(16).padStart(2, '0')
|
||||
).join('');
|
||||
return '0x' + hexString.toUpperCase();
|
||||
};
|
||||
|
||||
export function hexToBase64(hexString) {
|
||||
const binaryString = hexString.match(/.{1,2}/g).map(byte => String.fromCharCode(parseInt(byte, 16))).join('');
|
||||
return btoa(binaryString);
|
||||
}
|
||||
|
||||
export function parseCellValue(value, editorTypes?: DataEditorTypesBehaviour) {
|
||||
if (!_isString(value)) return value;
|
||||
|
||||
@@ -54,9 +67,10 @@ export function parseCellValue(value, editorTypes?: DataEditorTypesBehaviour) {
|
||||
const mHex = value.match(/^0x([0-9a-fA-F][0-9a-fA-F])+$/);
|
||||
if (mHex) {
|
||||
return {
|
||||
type: 'Buffer',
|
||||
data: hexStringToArray(value.substring(2)),
|
||||
};
|
||||
$binary: {
|
||||
base64: hexToBase64(value.substring(2))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,11 +244,19 @@ export function stringifyCellValue(
|
||||
if (value === true) return { value: 'true', gridStyle: 'valueCellStyle' };
|
||||
if (value === false) return { value: 'false', gridStyle: 'valueCellStyle' };
|
||||
|
||||
if (editorTypes?.parseHexAsBuffer) {
|
||||
if (value?.type == 'Buffer' && _isArray(value.data)) {
|
||||
return { value: '0x' + arrayToHexString(value.data), gridStyle: 'valueCellStyle' };
|
||||
}
|
||||
if (value?.$binary?.base64) {
|
||||
return {
|
||||
value: base64ToHex(value.$binary.base64),
|
||||
gridStyle: 'valueCellStyle',
|
||||
};
|
||||
}
|
||||
|
||||
if (editorTypes?.parseHexAsBuffer) {
|
||||
// if (value?.type == 'Buffer' && _isArray(value.data)) {
|
||||
// return { value: '0x' + arrayToHexString(value.data), gridStyle: 'valueCellStyle' };
|
||||
// }
|
||||
}
|
||||
|
||||
if (editorTypes?.parseObjectIdAsDollar) {
|
||||
if (value?.$oid) {
|
||||
switch (intent) {
|
||||
@@ -482,6 +504,9 @@ export function getAsImageSrc(obj) {
|
||||
if (obj?.type == 'Buffer' && _isArray(obj?.data)) {
|
||||
return `data:image/png;base64, ${arrayBufferToBase64(obj?.data)}`;
|
||||
}
|
||||
if (obj?.$binary?.base64) {
|
||||
return `data:image/png;base64, ${obj.$binary.base64}`;
|
||||
}
|
||||
|
||||
if (_isString(obj) && (obj.startsWith('http://') || obj.startsWith('https://'))) {
|
||||
return obj;
|
||||
|
||||
Vendored
+2
@@ -96,4 +96,6 @@ export type TestEngineInfo = {
|
||||
}>;
|
||||
|
||||
objects?: Array<TestObjectInfo>;
|
||||
|
||||
binaryDataType?: string;
|
||||
};
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"chartjs-plugin-datalabels": "^2.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dbgate-datalib": "^6.0.0-alpha.1",
|
||||
"dbgate-query-splitter": "^4.11.7",
|
||||
"dbgate-query-splitter": "^4.11.9",
|
||||
"dbgate-sqltree": "^6.0.0-alpha.1",
|
||||
"dbgate-tools": "^6.0.0-alpha.1",
|
||||
"dbgate-types": "^6.0.0-alpha.1",
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
import SettingsListener from './utility/SettingsListener.svelte';
|
||||
import { handleAuthOnStartup } from './clientAuth';
|
||||
import { initializeAppUpdates } from './utility/appUpdate';
|
||||
import { _t, saveSelectedLanguageToCache } from './translations';
|
||||
import { _t, getCurrentTranslations, saveSelectedLanguageToCache } from './translations';
|
||||
import { installCloudListeners } from './utility/cloudListeners';
|
||||
|
||||
export let isAdminPage = false;
|
||||
@@ -61,7 +61,13 @@
|
||||
initializeAppUpdates();
|
||||
installCloudListeners();
|
||||
refreshPublicCloudFiles();
|
||||
saveSelectedLanguageToCache();
|
||||
saveSelectedLanguageToCache(config.preferrendLanguage);
|
||||
|
||||
const electron = getElectron();
|
||||
if (electron) {
|
||||
electron.send('translation-data', JSON.stringify(getCurrentTranslations()));
|
||||
global.TRANSLATION_DATA = getCurrentTranslations();
|
||||
}
|
||||
}
|
||||
|
||||
loadedApi = loadedApiValue;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import Link from '../elements/Link.svelte';
|
||||
import { focusedConnectionOrDatabase } from '../stores';
|
||||
import { tick } from 'svelte';
|
||||
import { _tval } from '../translations';
|
||||
|
||||
export let list;
|
||||
export let module;
|
||||
@@ -40,12 +41,12 @@
|
||||
|
||||
$: listTranslated = (list || []).map(data => ({
|
||||
...data,
|
||||
group: data?.group && _.isFunction(data.group) ? data.group() : data.group,
|
||||
title: data?.title && _.isFunction(data.title) ? data.title() : data.title,
|
||||
description: data?.description && _.isFunction(data.description) ? data.description() : data.description,
|
||||
group: data?.group && _tval(data.group),
|
||||
title: data?.title && _tval(data.title),
|
||||
description: data?.description && _tval(data.description),
|
||||
args: (data?.args || []).map(x => ({
|
||||
...x,
|
||||
label: x?.label && _.isFunction(x.label) ? x.label() : x.label,
|
||||
label: x?.label && _tval(x.label),
|
||||
})),
|
||||
}));
|
||||
|
||||
|
||||
@@ -446,8 +446,7 @@ await dbgateApi.executeQuery(${JSON.stringify(
|
||||
driver?.databaseEngineTypes?.includes('document') && {
|
||||
onClick: handleNewCollection,
|
||||
text: _t('database.newCollection', {
|
||||
defaultMessage: 'New {collectionLabel}',
|
||||
values: { collectionLabel: driver?.collectionSingularLabel ?? 'collection/container' },
|
||||
defaultMessage: 'New collection/container'
|
||||
}),
|
||||
},
|
||||
hasPermission(`dbops/query`) &&
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts" context="module">
|
||||
import { copyTextToClipboard } from '../utility/clipboard';
|
||||
import { _t, _tval, DefferedTranslationResult } from '../translations';
|
||||
|
||||
export const extractKey = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName);
|
||||
export const createMatcher =
|
||||
@@ -69,13 +70,13 @@
|
||||
|
||||
function createScriptTemplatesSubmenu(objectTypeField) {
|
||||
return {
|
||||
label: 'SQL template',
|
||||
label: _t('dbObject.sqlTemplate', { defaultMessage: 'SQL template' }),
|
||||
submenu: getSupportedScriptTemplates(objectTypeField),
|
||||
};
|
||||
}
|
||||
|
||||
interface DbObjMenuItem {
|
||||
label?: string;
|
||||
label?: string | DefferedTranslationResult;
|
||||
tab?: string;
|
||||
forceNewTab?: boolean;
|
||||
initialData?: any;
|
||||
@@ -113,19 +114,19 @@
|
||||
divider: true,
|
||||
},
|
||||
isProApp() && {
|
||||
label: 'Design query',
|
||||
label: _t('dbObject.designQuery', { defaultMessage: 'Design query' }),
|
||||
isQueryDesigner: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
isProApp() && {
|
||||
label: 'Design perspective query',
|
||||
label: _t('dbObject.designPerspectiveQuery', { defaultMessage: 'Design perspective query' }),
|
||||
tab: 'PerspectiveTab',
|
||||
forceNewTab: true,
|
||||
icon: 'img perspective',
|
||||
},
|
||||
createScriptTemplatesSubmenu('tables'),
|
||||
{
|
||||
label: 'SQL generator',
|
||||
label: _t('dbObject.sqlGenerator', { defaultMessage: 'SQL generator' }),
|
||||
submenu: [
|
||||
{
|
||||
label: 'CREATE TABLE',
|
||||
@@ -154,33 +155,33 @@
|
||||
divider: true,
|
||||
},
|
||||
hasPermission('dbops/model/edit') && {
|
||||
label: 'Drop table',
|
||||
label: _t('dbObject.dropTable', { defaultMessage: 'Drop table' }),
|
||||
isDrop: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
hasPermission('dbops/table/rename') &&
|
||||
!driver?.dialect.disableRenameTable && {
|
||||
label: 'Rename table',
|
||||
label: _t('dbObject.renameTable', { defaultMessage: 'Rename table' }),
|
||||
isRename: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
hasPermission('dbops/table/truncate') && {
|
||||
label: 'Truncate table',
|
||||
label: _t('dbObject.truncateTable', { defaultMessage: 'Truncate table' }),
|
||||
isTruncate: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
{
|
||||
label: 'Copy table name',
|
||||
label: _t('dbObject.copyTableName', { defaultMessage: 'Copy table name' }),
|
||||
isCopyTableName: true,
|
||||
requiresWriteAccess: false,
|
||||
},
|
||||
hasPermission('dbops/table/backup') && {
|
||||
label: 'Create table backup',
|
||||
label: _t('dbObject.createTableBackup', { defaultMessage: 'Create table backup' }),
|
||||
isDuplicateTable: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
hasPermission('dbops/model/view') && {
|
||||
label: 'Show diagram',
|
||||
label: _t('dbObject.showDiagram', { defaultMessage: 'Show diagram' }),
|
||||
isDiagram: true,
|
||||
},
|
||||
{
|
||||
@@ -204,18 +205,18 @@
|
||||
divider: true,
|
||||
},
|
||||
isProApp() && {
|
||||
label: 'Design query',
|
||||
label: _t('dbObject.designQuery', { defaultMessage: 'Design query' }),
|
||||
isQueryDesigner: true,
|
||||
},
|
||||
isProApp() && {
|
||||
label: 'Design perspective query',
|
||||
label: _t('dbObject.designPerspectiveQuery', { defaultMessage: 'Design perspective query' }),
|
||||
tab: 'PerspectiveTab',
|
||||
forceNewTab: true,
|
||||
icon: 'img perspective',
|
||||
},
|
||||
createScriptTemplatesSubmenu('views'),
|
||||
{
|
||||
label: 'SQL generator',
|
||||
label: _t('dbObject.sqlGenerator', { defaultMessage: 'SQL generator' }),
|
||||
submenu: [
|
||||
{
|
||||
label: 'CREATE VIEW',
|
||||
@@ -235,12 +236,12 @@
|
||||
divider: true,
|
||||
},
|
||||
hasPermission('dbops/model/edit') && {
|
||||
label: 'Drop view',
|
||||
label: _t('dbObject.dropView', { defaultMessage: 'Drop view' }),
|
||||
isDrop: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
hasPermission('dbops/model/edit') && {
|
||||
label: 'Rename view',
|
||||
label: _t('dbObject.renameView', { defaultMessage: 'Rename view' }),
|
||||
isRename: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
@@ -260,12 +261,12 @@
|
||||
divider: true,
|
||||
},
|
||||
hasPermission('dbops/model/edit') && {
|
||||
label: 'Drop view',
|
||||
label: _t('dbObject.dropView', { defaultMessage: 'Drop view' }),
|
||||
isDrop: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
hasPermission('dbops/model/edit') && {
|
||||
label: 'Rename view',
|
||||
label: _t('dbObject.renameView', { defaultMessage: 'Rename view' }),
|
||||
isRename: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
@@ -273,12 +274,12 @@
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: 'Query designer',
|
||||
label: _t('dbObject.queryDesigner', { defaultMessage: 'Query designer' }),
|
||||
isQueryDesigner: true,
|
||||
},
|
||||
createScriptTemplatesSubmenu('matviews'),
|
||||
{
|
||||
label: 'SQL generator',
|
||||
label: _t('dbObject.sqlGenerator', { defaultMessage: 'SQL generator' }),
|
||||
submenu: [
|
||||
{
|
||||
label: 'CREATE MATERIALIZED VIEW',
|
||||
@@ -306,7 +307,7 @@
|
||||
case 'queries':
|
||||
return [
|
||||
{
|
||||
label: 'Open data',
|
||||
label: _t('dbObject.openData', { defaultMessage: 'Open data' }),
|
||||
tab: 'QueryDataTab',
|
||||
forceNewTab: true,
|
||||
},
|
||||
@@ -318,18 +319,18 @@
|
||||
divider: true,
|
||||
},
|
||||
hasPermission('dbops/model/edit') && {
|
||||
label: 'Drop procedure',
|
||||
label: _t('dbObject.dropProcedure', { defaultMessage: 'Drop procedure' }),
|
||||
isDrop: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
hasPermission('dbops/model/edit') && {
|
||||
label: 'Rename procedure',
|
||||
label: _t('dbObject.renameProcedure', { defaultMessage: 'Rename procedure' }),
|
||||
isRename: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
createScriptTemplatesSubmenu('procedures'),
|
||||
{
|
||||
label: 'SQL generator',
|
||||
label: _t('dbObject.sqlGenerator', { defaultMessage: 'SQL generator' }),
|
||||
submenu: [
|
||||
{
|
||||
label: 'CREATE PROCEDURE',
|
||||
@@ -352,7 +353,7 @@
|
||||
return [
|
||||
...defaultDatabaseObjectAppObjectActions['triggers'],
|
||||
hasPermission('dbops/model/edit') && {
|
||||
label: 'Drop trigger',
|
||||
label: _t('dbObject.dropTrigger', { defaultMessage: 'Drop trigger' }),
|
||||
isDrop: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
@@ -360,7 +361,7 @@
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: 'SQL generator',
|
||||
label: _t('dbObject.sqlGenerator', { defaultMessage: 'SQL generator' }),
|
||||
submenu: [
|
||||
{
|
||||
label: 'CREATE TRIGGER',
|
||||
@@ -384,7 +385,7 @@
|
||||
divider: true,
|
||||
},
|
||||
isProApp() && {
|
||||
label: 'Design perspective query',
|
||||
label: _t('dbObject.designPerspectiveQuery', { defaultMessage: 'Design perspective query' }),
|
||||
tab: 'PerspectiveTab',
|
||||
forceNewTab: true,
|
||||
icon: 'img perspective',
|
||||
@@ -395,17 +396,17 @@
|
||||
functionName: 'tableReader',
|
||||
},
|
||||
hasPermission('dbops/model/edit') && {
|
||||
label: `Drop ${driver?.collectionSingularLabel ?? 'collection/container'}`,
|
||||
label: _t('dbObject.dropCollection', { defaultMessage: 'Drop collection/container' }),
|
||||
isDropCollection: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
hasPermission('dbops/table/rename') && {
|
||||
label: `Rename ${driver?.collectionSingularLabel ?? 'collection/container'}`,
|
||||
label: _t('dbObject.renameCollection', { defaultMessage: 'Rename collection/container' }),
|
||||
isRenameCollection: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
hasPermission('dbops/table/backup') && {
|
||||
label: `Create ${driver?.collectionSingularLabel ?? 'collection/container'} backup`,
|
||||
label: _t('dbObject.createCollectionBackup', { defaultMessage: 'Create collection/container backup' }),
|
||||
isDuplicateCollection: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
@@ -418,7 +419,7 @@
|
||||
const menu: DbObjMenuItem[] = [
|
||||
...defaultDatabaseObjectAppObjectActions['schedulerEvents'],
|
||||
hasPermission('dbops/model/edit') && {
|
||||
label: 'Drop event',
|
||||
label: _t('dbObject.dropEvent', { defaultMessage: 'Drop event' }),
|
||||
isDrop: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
@@ -426,12 +427,12 @@
|
||||
|
||||
if (data?.status === 'ENABLED') {
|
||||
menu.push({
|
||||
label: 'Disable',
|
||||
label: _t('dbObject.disable', { defaultMessage: 'Disable' }),
|
||||
isDisableEvent: true,
|
||||
});
|
||||
} else {
|
||||
menu.push({
|
||||
label: 'Enable',
|
||||
label: _t('dbObject.enable', { defaultMessage: 'Enable' }),
|
||||
isEnableEvent: true,
|
||||
});
|
||||
}
|
||||
@@ -441,7 +442,7 @@
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
label: 'SQL generator',
|
||||
label: _t('dbObject.sqlGenerator', { defaultMessage: 'SQL generator' }),
|
||||
submenu: [
|
||||
{
|
||||
label: 'CREATE SCHEDULER EVENT',
|
||||
@@ -474,7 +475,7 @@
|
||||
if (menu.isQueryDesigner) {
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Query #',
|
||||
title: _t('dbObject.query', { defaultMessage: 'Query #' }),
|
||||
icon: 'img query-design',
|
||||
tabComponent: 'QueryDesignTab',
|
||||
focused: true,
|
||||
@@ -499,7 +500,7 @@
|
||||
} else if (menu.isDiagram) {
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Diagram #',
|
||||
title: _t('dbObject.diagram', { defaultMessage: 'Diagram #' }),
|
||||
icon: 'img diagram',
|
||||
tabComponent: 'DiagramTab',
|
||||
props: {
|
||||
@@ -589,7 +590,10 @@
|
||||
});
|
||||
} else if (menu.isDropCollection) {
|
||||
showModal(ConfirmModal, {
|
||||
message: `Really drop collection ${data.pureName}?`,
|
||||
message: _t('dbObject.confirmDropCollection', {
|
||||
defaultMessage: 'Really drop collection {name}?',
|
||||
values: { name: data.pureName },
|
||||
}),
|
||||
onConfirm: async () => {
|
||||
const dbid = _.pick(data, ['conid', 'database']);
|
||||
runOperationOnDatabase(dbid, {
|
||||
@@ -603,8 +607,8 @@
|
||||
} else if (menu.isRenameCollection) {
|
||||
const driver = await getDriver();
|
||||
showModal(InputTextModal, {
|
||||
label: `New ${driver?.collectionSingularLabel ?? 'collection/container'} name`,
|
||||
header: `Rename ${driver?.collectionSingularLabel ?? 'collection/container'}`,
|
||||
label: _t('dbObject.newCollectionName', { defaultMessage: 'New collection/container name' }),
|
||||
header: _t('dbObject.renameCollection', { defaultMessage: 'Rename collection/container' }),
|
||||
value: data.pureName,
|
||||
onConfirm: async newName => {
|
||||
const dbid = _.pick(data, ['conid', 'database']);
|
||||
@@ -620,7 +624,10 @@
|
||||
const driver = await getDriver();
|
||||
|
||||
showModal(ConfirmModal, {
|
||||
message: `Really create ${driver?.collectionSingularLabel ?? 'collection/container'} copy named ${newName}?`,
|
||||
message: _t('dbObject.confirmCloneCollection', {
|
||||
defaultMessage: 'Really create collection/container copy named {name}?',
|
||||
values: { name: newName },
|
||||
}),
|
||||
onConfirm: async () => {
|
||||
const dbid = _.pick(data, ['conid', 'database']);
|
||||
runOperationOnDatabase(dbid, {
|
||||
@@ -718,7 +725,9 @@
|
||||
|
||||
const filteredSumenus = coreMenus.map(item => {
|
||||
if (!item.submenu) {
|
||||
return item;
|
||||
if (!item) return item;
|
||||
|
||||
return { ...item, label: _tval(item.label) };
|
||||
}
|
||||
return {
|
||||
...item,
|
||||
@@ -768,7 +777,9 @@
|
||||
openNewTab(
|
||||
{
|
||||
// title: getObjectTitle(connection, schemaName, pureName),
|
||||
title: tabComponent ? getObjectTitle(connection, schemaName, pureName) : 'Query #',
|
||||
title: tabComponent
|
||||
? getObjectTitle(connection, schemaName, pureName)
|
||||
: _t('dbObject.query', { defaultMessage: 'Query #' }),
|
||||
focused: tabComponent == null,
|
||||
tooltip,
|
||||
icon:
|
||||
@@ -1035,6 +1046,7 @@
|
||||
import { getSupportedScriptTemplates } from '../utility/applyScriptTemplate';
|
||||
import { getBoolSettingsValue, getOpenDetailOnArrowsSettings } from '../settings/settingsTools';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
import formatFileSize from '../utility/formatFileSize';
|
||||
|
||||
export let data;
|
||||
export let passProps;
|
||||
@@ -1063,6 +1075,9 @@
|
||||
if (data.tableRowCount != null) {
|
||||
res.push(`${formatRowCount(data.tableRowCount)} rows`);
|
||||
}
|
||||
if (data.sizeBytes) {
|
||||
res.push(formatFileSize(data.sizeBytes));
|
||||
}
|
||||
if (data.tableEngine) {
|
||||
res.push(data.tableEngine);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { __t } from '../translations';
|
||||
export function matchDatabaseObjectAppObject(obj1, obj2) {
|
||||
return (
|
||||
obj1?.objectTypeField == obj2?.objectTypeField &&
|
||||
@@ -11,12 +12,12 @@ export function matchDatabaseObjectAppObject(obj1, obj2) {
|
||||
function getTableLikeActions(dataTab) {
|
||||
return [
|
||||
{
|
||||
label: 'Open data',
|
||||
label: __t('dbObject.openData', { defaultMessage: 'Open data' }),
|
||||
tab: dataTab,
|
||||
defaultActionId: 'openTable',
|
||||
},
|
||||
{
|
||||
label: 'Open raw data',
|
||||
label: __t('dbObject.openRawData', { defaultMessage: 'Open raw data' }),
|
||||
tab: dataTab,
|
||||
defaultActionId: 'openRawTable',
|
||||
isRawMode: true,
|
||||
@@ -33,13 +34,13 @@ function getTableLikeActions(dataTab) {
|
||||
// defaultActionId: 'openForm',
|
||||
// },
|
||||
{
|
||||
label: 'Open structure',
|
||||
label: __t('dbObject.openStructure', { defaultMessage: 'Open structure' }),
|
||||
tab: 'TableStructureTab',
|
||||
icon: 'img table-structure',
|
||||
defaultActionId: 'openStructure',
|
||||
},
|
||||
{
|
||||
label: 'Show SQL',
|
||||
label: __t('dbObject.showSql', { defaultMessage: 'Show SQL' }),
|
||||
tab: 'SqlObjectTab',
|
||||
defaultActionId: 'showSql',
|
||||
icon: 'img sql-file',
|
||||
@@ -53,7 +54,7 @@ export const defaultDatabaseObjectAppObjectActions = {
|
||||
matviews: getTableLikeActions('ViewDataTab'),
|
||||
procedures: [
|
||||
{
|
||||
label: 'Show SQL',
|
||||
label: __t('dbObject.showSql', { defaultMessage: 'Show SQL' }),
|
||||
tab: 'SqlObjectTab',
|
||||
defaultActionId: 'showSql',
|
||||
icon: 'img sql-file',
|
||||
@@ -61,7 +62,7 @@ export const defaultDatabaseObjectAppObjectActions = {
|
||||
],
|
||||
functions: [
|
||||
{
|
||||
label: 'Show SQL',
|
||||
label: __t('dbObject.showSql', { defaultMessage: 'Show SQL' }),
|
||||
tab: 'SqlObjectTab',
|
||||
defaultActionId: 'showSql',
|
||||
icon: 'img sql-file',
|
||||
@@ -69,7 +70,7 @@ export const defaultDatabaseObjectAppObjectActions = {
|
||||
],
|
||||
triggers: [
|
||||
{
|
||||
label: 'Show SQL',
|
||||
label: __t('dbObject.showSql', { defaultMessage: 'Show SQL' }),
|
||||
tab: 'SqlObjectTab',
|
||||
defaultActionId: 'showSql',
|
||||
icon: 'img sql-file',
|
||||
@@ -77,12 +78,12 @@ export const defaultDatabaseObjectAppObjectActions = {
|
||||
],
|
||||
collections: [
|
||||
{
|
||||
label: 'Open data',
|
||||
label: __t('dbObject.openData', { defaultMessage: 'Open data' }),
|
||||
tab: 'CollectionDataTab',
|
||||
defaultActionId: 'openTable',
|
||||
},
|
||||
{
|
||||
label: 'Open JSON',
|
||||
label: __t('dbObject.openJson', { defaultMessage: 'Open JSON' }),
|
||||
tab: 'CollectionDataTab',
|
||||
defaultActionId: 'openJson',
|
||||
initialData: {
|
||||
@@ -94,7 +95,7 @@ export const defaultDatabaseObjectAppObjectActions = {
|
||||
],
|
||||
schedulerEvents: [
|
||||
{
|
||||
label: 'Show SQL',
|
||||
label: __t('dbObject.showSql', { defaultMessage: 'Show SQL' }),
|
||||
tab: 'SqlObjectTab',
|
||||
defaultActionId: 'showSql',
|
||||
icon: 'img sql-file',
|
||||
@@ -102,7 +103,7 @@ export const defaultDatabaseObjectAppObjectActions = {
|
||||
],
|
||||
queries: [
|
||||
{
|
||||
label: 'Show query',
|
||||
label: __t('dbObject.showQuery', { defaultMessage: 'Show query' }),
|
||||
tab: 'QueryDataTab',
|
||||
defaultActionId: 'showAppQuery',
|
||||
icon: 'img app-query',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script context="module">
|
||||
function getCommandTitle(command) {
|
||||
let res = _.isFunction(command.text) ? command.text() : command.text;
|
||||
let res = _tval(command.text);
|
||||
if (command.keyText || command.keyTextFromGroup) {
|
||||
res += ` (${formatKeyText(command.keyText || command.keyTextFromGroup)})`;
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
import { formatKeyText } from '../utility/common';
|
||||
import ToolStripButton from './ToolStripButton.svelte';
|
||||
import _ from 'lodash';
|
||||
import { _tval } from '../translations';
|
||||
|
||||
export let command;
|
||||
export let component = ToolStripButton;
|
||||
@@ -33,6 +34,6 @@
|
||||
{iconAfter}
|
||||
{...$$restProps}
|
||||
>
|
||||
{(_.isFunction(buttonLabel) ? buttonLabel() : buttonLabel) || (_.isFunction(cmd?.toolbarName) ? cmd.toolbarName() : cmd.toolbarName) || (_.isFunction(cmd?.name) ? cmd.name() : cmd.name)}
|
||||
{(_tval(buttonLabel) || _tval(cmd?.toolbarName) || _tval(cmd?.name))}
|
||||
</svelte:component>
|
||||
{/if}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
import ToolStripCommandButton from './ToolStripCommandButton.svelte';
|
||||
import ToolStripDropDownButton from './ToolStripDropDownButton.svelte';
|
||||
import _ from 'lodash';
|
||||
import { _tval } from '../translations';
|
||||
export let quickExportHandlerRef = null;
|
||||
export let command = 'sqlDataGrid.export';
|
||||
export let label = 'Export';
|
||||
@@ -39,7 +40,7 @@
|
||||
|
||||
{#if hasPermission('dbops/export')}
|
||||
{#if quickExportHandlerRef}
|
||||
<ToolStripDropDownButton menu={getExportMenu} label={_.isFunction(label) ? label() : label} icon="icon export" />
|
||||
<ToolStripDropDownButton menu={getExportMenu} label={_tval(label)} icon="icon export" />
|
||||
{:else}
|
||||
<ToolStripCommandButton {command} />
|
||||
{/if}
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
if (value?.type == 'Buffer' && _.isArray(value?.data)) {
|
||||
return 'data:image/png;base64, ' + btoa(String.fromCharCode.apply(null, value?.data));
|
||||
}
|
||||
if (value?.$binary?.base64) {
|
||||
return 'data:image/png;base64, ' + value.$binary.base64;
|
||||
}
|
||||
return null;
|
||||
} catch (err) {
|
||||
console.log('Error showing picture', err);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script context="module">
|
||||
registerCommand({
|
||||
id: 'commandPalette.show',
|
||||
category: 'Command palette',
|
||||
name: 'Show',
|
||||
toolbarName: 'Command palette',
|
||||
category: __t('command.commandPalette', { defaultMessage: 'Command palette' }),
|
||||
name: __t('command.commandPalette.show', { defaultMessage: 'Show' }),
|
||||
toolbarName: __t('command.commandPalette', { defaultMessage: 'Command palette' }),
|
||||
toolbarOrder: 0,
|
||||
keyText: 'F1',
|
||||
toolbar: true,
|
||||
@@ -15,9 +15,9 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'database.search',
|
||||
category: 'Database',
|
||||
toolbarName: 'Database search',
|
||||
name: 'Search',
|
||||
category: __t('command.database', { defaultMessage: 'Database' }),
|
||||
toolbarName: __t('command.database.databaseSearch', { defaultMessage: 'Database search' }),
|
||||
name: __t('command.database.search', { defaultMessage: 'Search' }),
|
||||
keyText: isElectronAvailable() ? 'CtrlOrCommand+P' : 'F3',
|
||||
onClick: () => visibleCommandPalette.set('database'),
|
||||
testEnabled: () => getVisibleCommandPalette() != 'database',
|
||||
@@ -81,7 +81,7 @@
|
||||
import { getLocalStorage } from '../utility/storageCache';
|
||||
import registerCommand from './registerCommand';
|
||||
import { formatKeyText, switchCurrentDatabase } from '../utility/common';
|
||||
import { _val } from '../translations';
|
||||
import { _tval, __t } from '../translations';
|
||||
|
||||
let domInput;
|
||||
let filter = '';
|
||||
@@ -114,11 +114,11 @@
|
||||
($visibleCommandPalette == 'database'
|
||||
? extractDbItems($databaseInfo, { conid, database }, $connectionList)
|
||||
: parentCommand
|
||||
? parentCommand.getSubCommands()
|
||||
: sortedComands
|
||||
? parentCommand.getSubCommands()
|
||||
: sortedComands
|
||||
).filter(x => !x.isGroupCommand),
|
||||
{
|
||||
extract: x => _val(x.text),
|
||||
extract: x => _tval(x.text),
|
||||
pre: '<b>',
|
||||
post: '</b>',
|
||||
}
|
||||
@@ -163,10 +163,10 @@
|
||||
on:clickOutside={() => {
|
||||
$visibleCommandPalette = null;
|
||||
}}
|
||||
data-testid='CommandPalette_main'
|
||||
data-testid="CommandPalette_main"
|
||||
>
|
||||
<div
|
||||
class="overlay"
|
||||
<div
|
||||
class="overlay"
|
||||
on:click={() => {
|
||||
$visibleCommandPalette = null;
|
||||
}}
|
||||
|
||||
@@ -4,11 +4,12 @@ import getElectron from '../utility/getElectron';
|
||||
import registerCommand from './registerCommand';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { switchCurrentDatabase } from '../utility/common';
|
||||
import { __t } from '../translations';
|
||||
|
||||
registerCommand({
|
||||
id: 'database.changeState',
|
||||
category: 'Database',
|
||||
name: 'Change status',
|
||||
category: __t('command.database', { defaultMessage: 'Database' }),
|
||||
name: __t('command.database.changeStatus', { defaultMessage: 'Change status' }),
|
||||
getSubCommands: () => {
|
||||
const current = getCurrentDatabase();
|
||||
if (!current) return [];
|
||||
|
||||
@@ -3,6 +3,7 @@ import { recentDatabases, currentDatabase, getRecentDatabases } from '../stores'
|
||||
import registerCommand from './registerCommand';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
import { switchCurrentDatabase } from '../utility/common';
|
||||
import { __t } from '../translations';
|
||||
|
||||
currentDatabase.subscribe(value => {
|
||||
if (!value) return;
|
||||
@@ -24,9 +25,9 @@ function switchDatabaseCommand(db) {
|
||||
|
||||
registerCommand({
|
||||
id: 'database.switch',
|
||||
category: 'Database',
|
||||
name: 'Change to recent',
|
||||
menuName: 'Switch recent database',
|
||||
category: __t('command.database', { defaultMessage: 'Database' }),
|
||||
name: __t('command.database.changeRecent', { defaultMessage: 'Change to recent' }),
|
||||
menuName: __t('command.database.switchRecent', { defaultMessage: 'Switch recent database' }),
|
||||
keyText: 'CtrlOrCommand+D',
|
||||
getSubCommands: () => getRecentDatabases().map(switchDatabaseCommand),
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { commands } from '../stores';
|
||||
import { invalidateCommandDefinitions } from './invalidateCommands';
|
||||
import _ from 'lodash';
|
||||
import { _tval, DefferedTranslationResult, isDefferedTranslationResult } from '../translations';
|
||||
|
||||
export interface SubCommand {
|
||||
text: string;
|
||||
@@ -9,10 +10,10 @@ export interface SubCommand {
|
||||
|
||||
export interface GlobalCommand {
|
||||
id: string;
|
||||
category: string | (() => string); // null for group commands
|
||||
category: string | DefferedTranslationResult; // null for group commands
|
||||
isGroupCommand?: boolean;
|
||||
name: string | (() => string);
|
||||
text?: string | (() => string);
|
||||
name: string | DefferedTranslationResult;
|
||||
text?: string | DefferedTranslationResult;
|
||||
keyText?: string;
|
||||
keyTextFromGroup?: string; // automatically filled from group
|
||||
group?: string;
|
||||
@@ -24,8 +25,8 @@ export interface GlobalCommand {
|
||||
toolbar?: boolean;
|
||||
enabled?: boolean;
|
||||
showDisabled?: boolean;
|
||||
toolbarName?: string | (() => string);
|
||||
menuName?: string;
|
||||
toolbarName?: string | DefferedTranslationResult;
|
||||
menuName?: string | DefferedTranslationResult;
|
||||
toolbarOrder?: number;
|
||||
disableHandleKeyText?: string;
|
||||
isRelatedToTab?: boolean;
|
||||
@@ -43,11 +44,10 @@ export default function registerCommand(command: GlobalCommand) {
|
||||
...x,
|
||||
[command.id]: {
|
||||
text:
|
||||
_.isFunction(command.category) || _.isFunction(command.name)
|
||||
? () =>
|
||||
`${_.isFunction(command.category) ? command.category() : command.category}: ${
|
||||
_.isFunction(command.name) ? command.name() : command.name
|
||||
}`
|
||||
isDefferedTranslationResult(command.category) || isDefferedTranslationResult(command.name)
|
||||
? {
|
||||
_transCallback: () => `${_tval(command.category)}: ${_tval(command.name)}`,
|
||||
}
|
||||
: `${command.category}: ${command.name}`,
|
||||
...command,
|
||||
enabled: !testEnabled,
|
||||
|
||||
@@ -52,6 +52,7 @@ import { openWebLink } from '../utility/simpleTools';
|
||||
import { _t } from '../translations';
|
||||
import ExportImportConnectionsModal from '../modals/ExportImportConnectionsModal.svelte';
|
||||
import { getBoolSettingsValue } from '../settings/settingsTools';
|
||||
import { __t } from '../translations';
|
||||
|
||||
// function themeCommand(theme: ThemeDefinition) {
|
||||
// return {
|
||||
@@ -69,42 +70,42 @@ import { getBoolSettingsValue } from '../settings/settingsTools';
|
||||
|
||||
registerCommand({
|
||||
id: 'theme.changeTheme',
|
||||
category: 'Theme',
|
||||
name: 'Change',
|
||||
toolbarName: 'Change theme',
|
||||
category: __t('command.theme', { defaultMessage: 'Theme' }),
|
||||
name: __t('command.theme.change', { defaultMessage: 'Change' }),
|
||||
toolbarName: __t('command.theme.changeToolbar', { defaultMessage: 'Change theme' }),
|
||||
onClick: () => showModal(SettingsModal, { selectedTab: 'theme' }),
|
||||
// getSubCommands: () => get(extensions).themes.map(themeCommand),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'toolbar.show',
|
||||
category: 'Toolbar',
|
||||
name: 'Show',
|
||||
category: __t('command.toolbar', { defaultMessage: 'Toolbar' }),
|
||||
name: __t('command.toolbar.show', { defaultMessage: 'Show' }),
|
||||
onClick: () => visibleToolbar.set(true),
|
||||
testEnabled: () => !getVisibleToolbar(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'toolbar.hide',
|
||||
category: 'Toolbar',
|
||||
name: 'Hide',
|
||||
category: __t('command.toolbar', { defaultMessage: 'Toolbar' }),
|
||||
name: __t('command.toolbar.hide', { defaultMessage: 'Hide' }),
|
||||
onClick: () => visibleToolbar.set(false),
|
||||
testEnabled: () => getVisibleToolbar(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'about.show',
|
||||
category: 'About',
|
||||
name: 'Show',
|
||||
toolbarName: 'About',
|
||||
category: __t('command.about', { defaultMessage: 'About' }),
|
||||
name: __t('command.about.show', { defaultMessage: 'Show' }),
|
||||
toolbarName: __t('command.about.toolbar', { defaultMessage: 'About' }),
|
||||
onClick: () => showModal(AboutModal),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'toggle.sidebar',
|
||||
category: 'Sidebar',
|
||||
name: 'Show',
|
||||
toolbarName: 'Toggle sidebar',
|
||||
category: __t('command.sidebar', { defaultMessage: 'Sidebar' }),
|
||||
name: __t('command.sidebar.show', { defaultMessage: 'Show' }),
|
||||
toolbarName: __t('command.sidebar.toggleToolbar', { defaultMessage: 'Toggle sidebar' }),
|
||||
keyText: 'CtrlOrCommand+B',
|
||||
onClick: () => visibleWidgetSideBar.update(x => !x),
|
||||
});
|
||||
@@ -113,10 +114,10 @@ registerCommand({
|
||||
id: 'new.connection',
|
||||
toolbar: true,
|
||||
icon: 'icon new-connection',
|
||||
toolbarName: 'Add connection',
|
||||
category: 'New',
|
||||
toolbarName: __t('command.new.connection', { defaultMessage: 'Add connection' }),
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
toolbarOrder: 1,
|
||||
name: 'Connection',
|
||||
name: __t('command.new.connection', { defaultMessage: 'Connection' }),
|
||||
testEnabled: () => !getCurrentConfig()?.runAsPortal && !getCurrentConfig()?.storageDatabase,
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
@@ -131,10 +132,10 @@ registerCommand({
|
||||
id: 'new.connectionOnCloud',
|
||||
toolbar: true,
|
||||
icon: 'img cloud-connection',
|
||||
toolbarName: 'Add connection',
|
||||
category: 'New',
|
||||
toolbarName: __t('command.new.connection', { defaultMessage: 'Add connection' }),
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
toolbarOrder: 1,
|
||||
name: 'Connection on Cloud',
|
||||
name: __t('command.new.connectionCloud', { defaultMessage: 'Connection on Cloud' }),
|
||||
testEnabled: () =>
|
||||
!getCurrentConfig()?.runAsPortal && !getCurrentConfig()?.storageDatabase && !!getCloudSigninTokenHolder(),
|
||||
onClick: () => {
|
||||
@@ -153,10 +154,10 @@ registerCommand({
|
||||
id: 'new.connection.folder',
|
||||
toolbar: true,
|
||||
icon: 'icon add-folder',
|
||||
toolbarName: 'Add connection folder',
|
||||
category: 'New',
|
||||
toolbarName: __t('command.new.connectionFolderToolbar', { defaultMessage: 'Add connection folder' }),
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
toolbarOrder: 1,
|
||||
name: 'Connection folder',
|
||||
name: __t('command.new.connectionFolder', { defaultMessage: 'Connection folder' }),
|
||||
testEnabled: () => !getCurrentConfig()?.runAsPortal,
|
||||
onClick: () => {
|
||||
showModal(InputTextModal, {
|
||||
@@ -176,22 +177,22 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'new.query',
|
||||
category: 'New',
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
icon: 'icon file',
|
||||
toolbar: true,
|
||||
toolbarOrder: 2,
|
||||
name: 'Query',
|
||||
toolbarName: 'New query',
|
||||
name: __t('command.new.query', { defaultMessage: 'Query' }),
|
||||
toolbarName: __t('command.new.queryToolbar', { defaultMessage: 'New query' }),
|
||||
keyText: 'CtrlOrCommand+T',
|
||||
onClick: () => newQuery(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'new.shell',
|
||||
category: 'New',
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
icon: 'img shell',
|
||||
name: 'JavaScript Shell',
|
||||
menuName: 'New JavaScript shell',
|
||||
name: __t('command.new.shell', { defaultMessage: 'JavaScript Shell' }),
|
||||
menuName: __t('command.new.JSShell', { defaultMessage: 'New JavaScript shell' }),
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: 'Shell #',
|
||||
@@ -204,10 +205,10 @@ registerCommand({
|
||||
if (isProApp()) {
|
||||
registerCommand({
|
||||
id: 'new.queryDesign',
|
||||
category: 'New',
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
icon: 'img query-design',
|
||||
name: 'Query design',
|
||||
menuName: 'New query design',
|
||||
name: __t('command.new.queryDesign', { defaultMessage: 'Query design' }),
|
||||
menuName: __t('command.new.newQueryDesign', { defaultMessage: 'New query design' }),
|
||||
onClick: () => newQueryDesign(),
|
||||
testEnabled: () =>
|
||||
getCurrentDatabase() &&
|
||||
@@ -218,10 +219,10 @@ if (isProApp()) {
|
||||
if (isProApp()) {
|
||||
registerCommand({
|
||||
id: 'new.modelTransform',
|
||||
category: 'New',
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
icon: 'img transform',
|
||||
name: 'Model transform',
|
||||
menuName: 'New model transform',
|
||||
name: __t('command.new.modelTransform', { defaultMessage: 'Model transform' }),
|
||||
menuName: __t('command.new.newModelTransform', { defaultMessage: 'New model transform' }),
|
||||
onClick: () => {
|
||||
openNewTab(
|
||||
{
|
||||
@@ -262,10 +263,10 @@ if (isProApp()) {
|
||||
if (isProApp()) {
|
||||
registerCommand({
|
||||
id: 'new.perspective',
|
||||
category: 'New',
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
icon: 'img perspective',
|
||||
name: 'Perspective',
|
||||
menuName: 'New perspective',
|
||||
name: __t('command.new.perspective', { defaultMessage: 'Perspective' }),
|
||||
menuName: __t('command.new.newPerspective', { defaultMessage: 'New perspective' }),
|
||||
onClick: () => newPerspective(),
|
||||
});
|
||||
}
|
||||
@@ -273,10 +274,10 @@ if (isProApp()) {
|
||||
if (isProApp()) {
|
||||
registerCommand({
|
||||
id: 'new.application',
|
||||
category: 'New',
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
icon: 'img app',
|
||||
name: 'Application',
|
||||
menuName: 'New application',
|
||||
name: __t('command.new.application', { defaultMessage: 'Application' }),
|
||||
menuName: __t('command.new.newApplication', { defaultMessage: 'New application' }),
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: 'Application #',
|
||||
@@ -289,10 +290,10 @@ if (isProApp()) {
|
||||
|
||||
registerCommand({
|
||||
id: 'new.diagram',
|
||||
category: 'New',
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
icon: 'img diagram',
|
||||
name: 'ER Diagram',
|
||||
menuName: 'New ER diagram',
|
||||
name: __t('command.new.diagram', { defaultMessage: 'ER Diagram' }),
|
||||
menuName: __t('command.new.newDiagram', { defaultMessage: 'New ER diagram' }),
|
||||
testEnabled: () =>
|
||||
getCurrentDatabase() &&
|
||||
findEngineDriver(getCurrentDatabase()?.connection, getExtensions())?.databaseEngineTypes?.includes('sql'),
|
||||
@@ -301,9 +302,9 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'new.archiveFolder',
|
||||
category: 'New',
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
icon: 'img archive',
|
||||
name: 'Archive folder',
|
||||
name: __t('command.new.archiveFolder', { defaultMessage: 'Archive folder' }),
|
||||
onClick: () => {
|
||||
showModal(InputTextModal, {
|
||||
value: '',
|
||||
@@ -335,11 +336,11 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'new.table',
|
||||
category: 'New',
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
icon: 'icon table',
|
||||
name: 'Table',
|
||||
name: __t('command.new.table', { defaultMessage: 'Table' }),
|
||||
toolbar: true,
|
||||
toolbarName: 'New table',
|
||||
toolbarName: __t('command.new.tableToolbar', { defaultMessage: 'New table' }),
|
||||
testEnabled: () => {
|
||||
if (!hasPermission('dbops/model/edit')) return false;
|
||||
const driver = findEngineDriver(get(currentDatabase)?.connection, getExtensions());
|
||||
@@ -355,11 +356,11 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'new.collection',
|
||||
category: 'New',
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
icon: 'icon table',
|
||||
name: 'Collection',
|
||||
name: __t('command.new.collection', { defaultMessage: 'Collection' }),
|
||||
toolbar: true,
|
||||
toolbarName: 'New collection/container',
|
||||
toolbarName: __t('command.new.collectionToolbar', { defaultMessage: 'New collection/container' }),
|
||||
testEnabled: () => {
|
||||
const driver = findEngineDriver(get(currentDatabase)?.connection, getExtensions());
|
||||
return !!get(currentDatabase) && driver?.databaseEngineTypes?.includes('document');
|
||||
@@ -381,9 +382,9 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'new.markdown',
|
||||
category: 'New',
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
icon: 'img markdown',
|
||||
name: 'Markdown page',
|
||||
name: __t('command.new.markdown', { defaultMessage: 'Markdown page' }),
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: 'Page #',
|
||||
@@ -396,9 +397,9 @@ registerCommand({
|
||||
if (isProApp()) {
|
||||
registerCommand({
|
||||
id: 'new.modelCompare',
|
||||
category: 'New',
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
icon: 'icon compare',
|
||||
name: 'Compare DB',
|
||||
name: __t('command.new.modelCompare', { defaultMessage: 'Compare DB' }),
|
||||
toolbar: true,
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
@@ -412,10 +413,10 @@ if (isProApp()) {
|
||||
|
||||
registerCommand({
|
||||
id: 'new.jsonl',
|
||||
category: 'New',
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
icon: 'img archive',
|
||||
name: 'JSON Lines',
|
||||
menuName: 'New JSON lines file',
|
||||
name: __t('command.new.jsonl', { defaultMessage: 'JSON Lines' }),
|
||||
menuName: __t('command.new.newJsonl', { defaultMessage: 'New JSON lines file' }),
|
||||
onClick: () => {
|
||||
openNewTab(
|
||||
{
|
||||
@@ -432,10 +433,10 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'new.sqliteDatabase',
|
||||
category: 'New',
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
icon: 'img sqlite-database',
|
||||
name: 'SQLite database',
|
||||
menuName: _t('command.new.sqliteDatabase', { defaultMessage: 'New SQLite database' }),
|
||||
name: __t('command.new.sqliteDatabase', { defaultMessage: 'SQLite database' }),
|
||||
menuName: __t('command.new.sqliteDatabase', { defaultMessage: 'New SQLite database' }),
|
||||
onClick: () => {
|
||||
showModal(InputTextModal, {
|
||||
value: 'newdb',
|
||||
@@ -452,10 +453,10 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'new.duckdbDatabase',
|
||||
category: 'New',
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
icon: 'img sqlite-database',
|
||||
name: 'DuckDB database',
|
||||
menuName: _t('command.new.duckdbDatabase', { defaultMessage: 'New DuckDB database' }),
|
||||
name: __t('command.new.duckdbDatabase', { defaultMessage: 'DuckDB database' }),
|
||||
menuName: __t('command.new.duckdbDatabase', { defaultMessage: 'New DuckDB database' }),
|
||||
onClick: () => {
|
||||
showModal(InputTextModal, {
|
||||
value: 'newdb',
|
||||
@@ -472,8 +473,8 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'tabs.changelog',
|
||||
category: 'Tabs',
|
||||
name: 'Changelog',
|
||||
category: __t('command.tabs', { defaultMessage: 'Tabs' }),
|
||||
name: __t('command.tabs.changelog', { defaultMessage: 'Changelog' }),
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: 'ChangeLog',
|
||||
@@ -488,7 +489,7 @@ registerCommand({
|
||||
id: 'group.save',
|
||||
category: null,
|
||||
isGroupCommand: true,
|
||||
name: 'Save',
|
||||
name: __t('command.save', { defaultMessage: 'Save' }),
|
||||
keyText: 'CtrlOrCommand+S',
|
||||
group: 'save',
|
||||
});
|
||||
@@ -497,7 +498,7 @@ registerCommand({
|
||||
id: 'group.saveAs',
|
||||
category: null,
|
||||
isGroupCommand: true,
|
||||
name: 'Save As',
|
||||
name: __t('command.saveAs', { defaultMessage: 'Save As' }),
|
||||
keyText: 'CtrlOrCommand+Shift+S',
|
||||
group: 'saveAs',
|
||||
});
|
||||
@@ -506,7 +507,7 @@ registerCommand({
|
||||
id: 'group.undo',
|
||||
category: null,
|
||||
isGroupCommand: true,
|
||||
name: 'Undo',
|
||||
name: __t('command.undo', { defaultMessage: 'Undo' }),
|
||||
keyText: 'CtrlOrCommand+Z',
|
||||
group: 'undo',
|
||||
});
|
||||
@@ -515,15 +516,15 @@ registerCommand({
|
||||
id: 'group.redo',
|
||||
category: null,
|
||||
isGroupCommand: true,
|
||||
name: 'Redo',
|
||||
name: __t('command.redo', { defaultMessage: 'Redo' }),
|
||||
keyText: 'CtrlOrCommand+Y',
|
||||
group: 'redo',
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'file.open',
|
||||
category: 'File',
|
||||
name: 'Open',
|
||||
category: __t('command.file', { defaultMessage: 'File' }),
|
||||
name: __t('command.file.open', { defaultMessage: 'Open' }),
|
||||
keyText: 'CtrlOrCommand+O',
|
||||
testEnabled: () => getElectron() != null,
|
||||
onClick: openElectronFile,
|
||||
@@ -531,36 +532,39 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'file.openArchive',
|
||||
category: 'File',
|
||||
name: 'Open DB Model/Archive',
|
||||
category: __t('command.file', { defaultMessage: 'File' }),
|
||||
name: __t('command.file.openArchive', { defaultMessage: 'Open DB Model/Archive' }),
|
||||
testEnabled: () => getElectron() != null,
|
||||
onClick: openArchiveFolder,
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'folder.showLogs',
|
||||
category: 'Folder',
|
||||
name: 'Open logs',
|
||||
category: __t('command.folder', { defaultMessage: 'Folder' }),
|
||||
name: __t('command.folder.openLogs', { defaultMessage: 'Open logs' }),
|
||||
testEnabled: () => getElectron() != null,
|
||||
onClick: () => electron.showItemInFolder(getCurrentConfig().logsFilePath),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'folder.showData',
|
||||
category: 'Folder',
|
||||
name: 'Open data folder',
|
||||
category: __t('command.folder', { defaultMessage: 'Folder' }),
|
||||
name: __t('command.folder.openData', { defaultMessage: 'Open data folder' }),
|
||||
testEnabled: () => getElectron() != null,
|
||||
onClick: () => electron.showItemInFolder(getCurrentConfig().connectionsFilePath),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'app.resetSettings',
|
||||
category: 'File',
|
||||
name: 'Reset layout data & settings',
|
||||
category: __t('command.file', { defaultMessage: 'File' }),
|
||||
name: __t('command.file.resetLayout', { defaultMessage: 'Reset layout data & settings' }),
|
||||
testEnabled: () => true,
|
||||
onClick: () => {
|
||||
showModal(ConfirmModal, {
|
||||
message: `Really reset layout data? All opened tabs, settings and layout data will be lost. Connections and saved files will be preserved. After this, restart DbGate for applying changes.`,
|
||||
message: _t('command.file.resetLayoutConfirm', {
|
||||
defaultMessage:
|
||||
'Really reset layout data? All opened tabs, settings and layout data will be lost. Connections and saved files will be preserved. After this, restart DbGate for applying changes.',
|
||||
}),
|
||||
onConfirm: async () => {
|
||||
await apiCall('config/delete-settings');
|
||||
localStorage.clear();
|
||||
@@ -577,8 +581,8 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'app.exportConnections',
|
||||
category: 'Settings',
|
||||
name: 'Export connections',
|
||||
category: __t('command.settings', { defaultMessage: 'Settings' }),
|
||||
name: __t('command.settings.exportConnections', { defaultMessage: 'Export connections' }),
|
||||
testEnabled: () => !getCurrentConfig()?.runAsPortal && !getCurrentConfig()?.storageDatabase,
|
||||
onClick: () => {
|
||||
showModal(ExportImportConnectionsModal, {
|
||||
@@ -589,8 +593,8 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'app.importConnections',
|
||||
category: 'Settings',
|
||||
name: 'Import connections',
|
||||
category: __t('command.settings', { defaultMessage: 'Settings' }),
|
||||
name: __t('command.settings.importConnections', { defaultMessage: 'Import connections' }),
|
||||
testEnabled: () => !getCurrentConfig()?.runAsPortal && !getCurrentConfig()?.storageDatabase,
|
||||
onClick: async () => {
|
||||
const files = await electron.showOpenDialog({
|
||||
@@ -615,8 +619,8 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'file.import',
|
||||
category: 'File',
|
||||
name: 'Import data',
|
||||
category: __t('command.file', { defaultMessage: 'File' }),
|
||||
name: __t('command.file.import', { defaultMessage: 'Import data' }),
|
||||
toolbar: true,
|
||||
icon: 'icon import',
|
||||
onClick: () =>
|
||||
@@ -636,8 +640,8 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'view.reset',
|
||||
category: 'View',
|
||||
name: 'Reset view',
|
||||
category: __t('command.view', { defaultMessage: 'View' }),
|
||||
name: __t('command.view.reset', { defaultMessage: 'Reset view' }),
|
||||
onClick: () => {
|
||||
const keys = [
|
||||
'leftPanelWidth',
|
||||
@@ -664,14 +668,16 @@ registerCommand({
|
||||
'currentArchive',
|
||||
];
|
||||
for (const key of keys) removeLocalStorage(key);
|
||||
showSnackbarSuccess('Restart DbGate (or reload on web) for applying changes');
|
||||
showSnackbarSuccess(
|
||||
_t('command.view.restart', { defaultMessage: 'Restart DbGate (or reload on web) for applying changes' })
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'sql.generator',
|
||||
category: 'SQL',
|
||||
name: 'SQL Generator',
|
||||
category: __t('command.sql', { defaultMessage: 'SQL' }),
|
||||
name: __t('command.sql.generator', { defaultMessage: 'SQL Generator' }),
|
||||
toolbar: true,
|
||||
icon: 'icon sql-generator',
|
||||
testEnabled: () =>
|
||||
@@ -687,8 +693,8 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'database.export',
|
||||
category: 'Database',
|
||||
name: 'Export database',
|
||||
category: __t('command.database', { defaultMessage: 'Database' }),
|
||||
name: __t('command.database.export', { defaultMessage: 'Export database' }),
|
||||
toolbar: true,
|
||||
icon: 'icon export',
|
||||
testEnabled: () => getCurrentDatabase() != null && hasPermission(`dbops/export`) && isProApp(),
|
||||
@@ -705,8 +711,8 @@ registerCommand({
|
||||
if (isProApp()) {
|
||||
registerCommand({
|
||||
id: 'database.compare',
|
||||
category: 'Database',
|
||||
name: 'Compare databases',
|
||||
category: __t('command.database', { defaultMessage: 'Database' }),
|
||||
name: __t('command.database.compare', { defaultMessage: 'Compare databases' }),
|
||||
toolbar: true,
|
||||
icon: 'icon compare',
|
||||
testEnabled: () =>
|
||||
@@ -738,8 +744,8 @@ if (isProApp()) {
|
||||
|
||||
registerCommand({
|
||||
id: 'database.chat',
|
||||
category: 'Database',
|
||||
name: 'Database chat',
|
||||
category: __t('command.database', { defaultMessage: 'Database' }),
|
||||
name: __t('command.database.chat', { defaultMessage: 'Database chat' }),
|
||||
toolbar: true,
|
||||
icon: 'icon ai',
|
||||
testEnabled: () =>
|
||||
@@ -763,11 +769,11 @@ if (isProApp()) {
|
||||
if (hasPermission('settings/change')) {
|
||||
registerCommand({
|
||||
id: 'settings.commands',
|
||||
category: 'Settings',
|
||||
name: 'Keyboard shortcuts',
|
||||
category: __t('command.settings', { defaultMessage: 'Settings' }),
|
||||
name: __t('command.settings.shortcuts', { defaultMessage: 'Keyboard shortcuts' }),
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: 'Keyboard Shortcuts',
|
||||
title: _t('command.settings.shortcuts', { defaultMessage: 'Keyboard shortcuts' }),
|
||||
icon: 'icon keyboard',
|
||||
tabComponent: 'CommandListTab',
|
||||
props: {},
|
||||
@@ -778,9 +784,9 @@ if (hasPermission('settings/change')) {
|
||||
|
||||
registerCommand({
|
||||
id: 'settings.show',
|
||||
category: 'Settings',
|
||||
name: 'Change',
|
||||
toolbarName: 'Settings',
|
||||
category: __t('command.settings', { defaultMessage: 'Settings' }),
|
||||
name: __t('command.settings.change', { defaultMessage: 'Change' }),
|
||||
toolbarName: __t('command.settings', { defaultMessage: 'Settings' }),
|
||||
onClick: () => showModal(SettingsModal),
|
||||
testEnabled: () => hasPermission('settings/change'),
|
||||
});
|
||||
@@ -788,8 +794,8 @@ if (hasPermission('settings/change')) {
|
||||
|
||||
registerCommand({
|
||||
id: 'cloud.logout',
|
||||
category: 'Cloud',
|
||||
name: 'Logout',
|
||||
category: __t('command.cloud', { defaultMessage: 'Cloud' }),
|
||||
name: __t('command.cloud.logout', { defaultMessage: 'Logout' }),
|
||||
onClick: () => {
|
||||
cloudSigninTokenHolder.set(null);
|
||||
},
|
||||
@@ -797,8 +803,10 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'file.exit',
|
||||
category: 'File',
|
||||
name: isMac() ? 'Quit' : 'Exit',
|
||||
category: __t('command.file', { defaultMessage: 'File' }),
|
||||
name: isMac()
|
||||
? __t('command.file.quit', { defaultMessage: 'Quit' })
|
||||
: __t('command.file.exit', { defaultMessage: 'Exit' }),
|
||||
// keyText: isMac() ? 'Command+Q' : null,
|
||||
testEnabled: () => getElectron() != null,
|
||||
onClick: () => getElectron().send('quit-app'),
|
||||
@@ -806,16 +814,16 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'app.logout',
|
||||
category: 'App',
|
||||
name: 'Logout',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.app.logout', { defaultMessage: 'Logout' }),
|
||||
testEnabled: () => getCurrentConfig()?.isUserLoggedIn,
|
||||
onClick: doLogout,
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'app.loggedUserCommands',
|
||||
category: 'App',
|
||||
name: 'Logged user',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.app.loggedUser', { defaultMessage: 'Logged user' }),
|
||||
getSubCommands: () => {
|
||||
const config = getCurrentConfig();
|
||||
if (!config) return [];
|
||||
@@ -832,16 +840,16 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'app.disconnect',
|
||||
category: 'App',
|
||||
name: 'Disconnect',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.app.disconnect', { defaultMessage: 'Disconnect' }),
|
||||
testEnabled: () => getCurrentConfig()?.singleConnection != null && !getCurrentConfig()?.isUserLoggedIn,
|
||||
onClick: () => disconnectServerConnection(getCurrentConfig()?.singleConnection?._id),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'file.checkForUpdates',
|
||||
category: 'App',
|
||||
name: 'Check for updates',
|
||||
id: 'app.checkForUpdates',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.app.checkForUpdates', { defaultMessage: 'Check for updates' }),
|
||||
// testEnabled: () => true,
|
||||
testEnabled: () => getAppUpdaterActive(),
|
||||
onClick: () => getElectron().send('check-for-updates'),
|
||||
@@ -861,34 +869,35 @@ export function registerFileCommands({
|
||||
undoRedo = false,
|
||||
executeAdditionalCondition = null,
|
||||
copyPaste = false,
|
||||
defaultTeamFolder = false,
|
||||
}) {
|
||||
if (save) {
|
||||
registerCommand({
|
||||
id: idPrefix + '.save',
|
||||
group: 'save',
|
||||
category,
|
||||
name: 'Save',
|
||||
name: __t('command.save', { defaultMessage: 'Save' }),
|
||||
// keyText: 'CtrlOrCommand+S',
|
||||
icon: 'icon save',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => saveTabFile(getCurrentEditor(), 'save', folder, format, fileExtension),
|
||||
onClick: () => saveTabFile(getCurrentEditor(), 'save', folder, format, fileExtension, defaultTeamFolder),
|
||||
});
|
||||
registerCommand({
|
||||
id: idPrefix + '.saveAs',
|
||||
group: 'saveAs',
|
||||
category,
|
||||
name: 'Save As',
|
||||
name: __t('command.saveAs', { defaultMessage: 'Save As' }),
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => saveTabFile(getCurrentEditor(), 'save-as', folder, format, fileExtension),
|
||||
onClick: () => saveTabFile(getCurrentEditor(), 'save-as', folder, format, fileExtension, defaultTeamFolder),
|
||||
});
|
||||
registerCommand({
|
||||
id: idPrefix + '.saveToDisk',
|
||||
category,
|
||||
name: 'Save to disk',
|
||||
name: __t('command.saveToDisk', { defaultMessage: 'Save to disk' }),
|
||||
testEnabled: () => getCurrentEditor() != null && getElectron() != null,
|
||||
onClick: () => saveTabFile(getCurrentEditor(), 'save-to-disk', folder, format, fileExtension),
|
||||
onClick: () => saveTabFile(getCurrentEditor(), 'save-to-disk', folder, format, fileExtension, defaultTeamFolder),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -896,7 +905,7 @@ export function registerFileCommands({
|
||||
registerCommand({
|
||||
id: idPrefix + '.execute',
|
||||
category,
|
||||
name: 'Execute',
|
||||
name: __t('command.execute', { defaultMessage: 'Execute' }),
|
||||
icon: 'icon run',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -910,7 +919,7 @@ export function registerFileCommands({
|
||||
registerCommand({
|
||||
id: idPrefix + '.kill',
|
||||
category,
|
||||
name: 'Kill',
|
||||
name: __t('command.kill', { defaultMessage: 'Kill' }),
|
||||
icon: 'icon close',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -923,7 +932,7 @@ export function registerFileCommands({
|
||||
registerCommand({
|
||||
id: idPrefix + '.toggleComment',
|
||||
category,
|
||||
name: 'Toggle comment',
|
||||
name: __t('command.toggleComment', { defaultMessage: 'Toggle comment' }),
|
||||
keyText: 'CtrlOrCommand+/',
|
||||
disableHandleKeyText: 'CtrlOrCommand+/',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
@@ -935,7 +944,7 @@ export function registerFileCommands({
|
||||
registerCommand({
|
||||
id: idPrefix + '.copy',
|
||||
category,
|
||||
name: 'Copy',
|
||||
name: __t('command.copy', { defaultMessage: 'Copy' }),
|
||||
disableHandleKeyText: 'CtrlOrCommand+C',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().copy(),
|
||||
@@ -943,7 +952,7 @@ export function registerFileCommands({
|
||||
registerCommand({
|
||||
id: idPrefix + '.paste',
|
||||
category,
|
||||
name: 'Paste',
|
||||
name: __t('command.paste', { defaultMessage: 'Paste' }),
|
||||
disableHandleKeyText: 'CtrlOrCommand+V',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().paste(),
|
||||
@@ -954,7 +963,7 @@ export function registerFileCommands({
|
||||
registerCommand({
|
||||
id: idPrefix + '.find',
|
||||
category,
|
||||
name: 'Find',
|
||||
name: __t('command.find', { defaultMessage: 'Find' }),
|
||||
keyText: 'CtrlOrCommand+F',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().find(),
|
||||
@@ -963,7 +972,7 @@ export function registerFileCommands({
|
||||
id: idPrefix + '.replace',
|
||||
category,
|
||||
keyText: isMac() ? 'Alt+Command+F' : 'CtrlOrCommand+H',
|
||||
name: 'Replace',
|
||||
name: __t('command.replace', { defaultMessage: 'Replace' }),
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().replace(),
|
||||
});
|
||||
@@ -972,7 +981,7 @@ export function registerFileCommands({
|
||||
registerCommand({
|
||||
id: idPrefix + '.undo',
|
||||
category,
|
||||
name: 'Undo',
|
||||
name: __t('command.undo', { defaultMessage: 'Undo' }),
|
||||
group: 'undo',
|
||||
icon: 'icon undo',
|
||||
testEnabled: () => getCurrentEditor()?.canUndo(),
|
||||
@@ -982,7 +991,7 @@ export function registerFileCommands({
|
||||
id: idPrefix + '.redo',
|
||||
category,
|
||||
group: 'redo',
|
||||
name: 'Redo',
|
||||
name: __t('command.redo', { defaultMessage: 'Redo' }),
|
||||
icon: 'icon redo',
|
||||
testEnabled: () => getCurrentEditor()?.canRedo(),
|
||||
onClick: () => getCurrentEditor().redo(),
|
||||
@@ -992,24 +1001,24 @@ export function registerFileCommands({
|
||||
|
||||
registerCommand({
|
||||
id: 'app.minimize',
|
||||
category: 'Application',
|
||||
name: 'Minimize',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.application.minimize', { defaultMessage: 'Minimize' }),
|
||||
testEnabled: () => getElectron() != null,
|
||||
onClick: () => getElectron().send('window-action', 'minimize'),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'app.maximize',
|
||||
category: 'Application',
|
||||
name: 'Maximize',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.application.maximize', { defaultMessage: 'Maximize' }),
|
||||
testEnabled: () => getElectron() != null,
|
||||
onClick: () => getElectron().send('window-action', 'maximize'),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'app.toggleFullScreen',
|
||||
category: 'Application',
|
||||
name: 'Toggle full screen',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.application.toggleFullScreen', { defaultMessage: 'Toggle full screen' }),
|
||||
keyText: 'F11',
|
||||
testEnabled: () => getElectron() != null,
|
||||
onClick: async () => {
|
||||
@@ -1023,45 +1032,45 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'app.toggleDevTools',
|
||||
category: 'Application',
|
||||
name: 'Toggle Dev Tools',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.application.toggleDevTools', { defaultMessage: 'Toggle Dev Tools' }),
|
||||
testEnabled: () => getElectron() != null,
|
||||
onClick: () => getElectron().send('window-action', 'devtools'),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'app.reload',
|
||||
category: 'Application',
|
||||
name: 'Reload',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.application.reload', { defaultMessage: 'Reload' }),
|
||||
testEnabled: () => getElectron() != null,
|
||||
onClick: () => getElectron().send('window-action', 'reload'),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'app.openDocs',
|
||||
category: 'Application',
|
||||
name: 'Documentation',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.application.documentation', { defaultMessage: 'Documentation' }),
|
||||
onClick: () => openWebLink('https://docs.dbgate.io/'),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'app.openWeb',
|
||||
category: 'Application',
|
||||
name: 'DbGate web',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.application.web', { defaultMessage: 'DbGate web' }),
|
||||
onClick: () => openWebLink('https://www.dbgate.io/'),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'app.openIssue',
|
||||
category: 'Application',
|
||||
name: 'Report problem or feature request',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.application.openIssue', { defaultMessage: 'Report problem or feature request' }),
|
||||
onClick: () => openWebLink('https://github.com/dbgate/dbgate/issues/new'),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'app.openSponsoring',
|
||||
category: 'Application',
|
||||
name: 'Become sponsor',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.application.becomeSponsor', { defaultMessage: 'Become sponsor' }),
|
||||
testEnabled: () => !isProApp(),
|
||||
onClick: () => openWebLink('https://opencollective.com/dbgate'),
|
||||
});
|
||||
@@ -1075,8 +1084,8 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'app.zoomIn',
|
||||
category: 'Application',
|
||||
name: 'Zoom in',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.application.zoomIn', { defaultMessage: 'Zoom in' }),
|
||||
keyText: 'CtrlOrCommand+=',
|
||||
testEnabled: () => getElectron() != null,
|
||||
onClick: () => getElectron().send('window-action', 'zoomin'),
|
||||
@@ -1084,8 +1093,8 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'app.zoomOut',
|
||||
category: 'Application',
|
||||
name: 'Zoom out',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.application.zoomOut', { defaultMessage: 'Zoom out' }),
|
||||
keyText: 'CtrlOrCommand+-',
|
||||
testEnabled: () => getElectron() != null,
|
||||
onClick: () => getElectron().send('window-action', 'zoomout'),
|
||||
@@ -1093,16 +1102,16 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'app.zoomReset',
|
||||
category: 'Application',
|
||||
name: 'Reset zoom',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.application.zoomReset', { defaultMessage: 'Reset zoom' }),
|
||||
testEnabled: () => getElectron() != null,
|
||||
onClick: () => getElectron().send('window-action', 'zoomreset'),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'edit.undo',
|
||||
category: 'Edit',
|
||||
name: 'Undo',
|
||||
category: __t('command.edit', { defaultMessage: 'Edit' }),
|
||||
name: __t('command.edit.undo', { defaultMessage: 'Undo' }),
|
||||
keyText: 'CtrlOrCommand+Z',
|
||||
systemCommand: true,
|
||||
testEnabled: () => getElectron() != null,
|
||||
@@ -1111,8 +1120,8 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'edit.redo',
|
||||
category: 'Edit',
|
||||
name: 'Redo',
|
||||
category: __t('command.edit', { defaultMessage: 'Edit' }),
|
||||
name: __t('command.edit.redo', { defaultMessage: 'Redo' }),
|
||||
systemCommand: true,
|
||||
testEnabled: () => getElectron() != null,
|
||||
onClick: () => getElectron().send('window-action', 'redo'),
|
||||
@@ -1120,8 +1129,8 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'edit.cut',
|
||||
category: 'Edit',
|
||||
name: 'Cut',
|
||||
category: __t('command.edit', { defaultMessage: 'Edit' }),
|
||||
name: __t('command.edit.cut', { defaultMessage: 'Cut' }),
|
||||
keyText: 'CtrlOrCommand+X',
|
||||
systemCommand: true,
|
||||
testEnabled: () => getElectron() != null,
|
||||
@@ -1130,8 +1139,8 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'edit.copy',
|
||||
category: 'Edit',
|
||||
name: 'Copy',
|
||||
category: __t('command.edit', { defaultMessage: 'Edit' }),
|
||||
name: __t('command.edit.copy', { defaultMessage: 'Copy' }),
|
||||
keyText: 'CtrlOrCommand+C',
|
||||
systemCommand: true,
|
||||
testEnabled: () => getElectron() != null,
|
||||
@@ -1140,8 +1149,8 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'edit.paste',
|
||||
category: 'Edit',
|
||||
name: 'Paste',
|
||||
category: __t('command.edit', { defaultMessage: 'Edit' }),
|
||||
name: __t('command.edit.paste', { defaultMessage: 'Paste' }),
|
||||
keyText: 'CtrlOrCommand+V',
|
||||
systemCommand: true,
|
||||
testEnabled: () => getElectron() != null,
|
||||
@@ -1150,8 +1159,8 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'edit.selectAll',
|
||||
category: 'Edit',
|
||||
name: 'Select All',
|
||||
category: __t('command.edit', { defaultMessage: 'Edit' }),
|
||||
name: __t('command.edit.selectAll', { defaultMessage: 'Select All' }),
|
||||
keyText: 'CtrlOrCommand+A',
|
||||
systemCommand: true,
|
||||
testEnabled: () => getElectron() != null,
|
||||
@@ -1160,8 +1169,8 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'app.unsetCurrentDatabase',
|
||||
category: 'Application',
|
||||
name: 'Unset current database',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.application.unsetCurrentDatabase', { defaultMessage: 'Unset current database' }),
|
||||
testEnabled: () => getCurrentDatabase() != null,
|
||||
onClick: () => currentDatabase.set(null),
|
||||
});
|
||||
@@ -1170,8 +1179,8 @@ let loadedCampaignList = [];
|
||||
|
||||
registerCommand({
|
||||
id: 'internal.loadCampaigns',
|
||||
category: 'Internal',
|
||||
name: 'Load campaign list',
|
||||
category: __t('command.internal', { defaultMessage: 'Internal' }),
|
||||
name: __t('command.internal.loadCampaigns', { defaultMessage: 'Load campaign list' }),
|
||||
testEnabled: () => getBoolSettingsValue('internal.showCampaigns', false),
|
||||
onClick: async () => {
|
||||
const resp = await apiCall('cloud/promo-widget-list', {});
|
||||
@@ -1181,8 +1190,8 @@ registerCommand({
|
||||
|
||||
registerCommand({
|
||||
id: 'internal.showCampaigns',
|
||||
category: 'Internal',
|
||||
name: 'Show campaigns',
|
||||
category: __t('command.internal', { defaultMessage: 'Internal' }),
|
||||
name: __t('command.internal.showCampaigns', { defaultMessage: 'Show campaigns' }),
|
||||
testEnabled: () => getBoolSettingsValue('internal.showCampaigns', false) && loadedCampaignList?.length > 0,
|
||||
getSubCommands: () => {
|
||||
return loadedCampaignList.map(campaign => ({
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'collectionDataGrid.openQuery',
|
||||
category: 'Data grid',
|
||||
name: 'Open query',
|
||||
category: __t('command.dataGrid', { defaultMessage: 'Data grid' }),
|
||||
name: __t('command.dataGrid.openQuery', { defaultMessage: 'Open query' }),
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().openQuery(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'collectionDataGrid.export',
|
||||
category: 'Data grid',
|
||||
name: 'Export',
|
||||
category: __t('command.dataGrid', { defaultMessage: 'Data grid' }),
|
||||
name: __t('command.dataGrid.export', { defaultMessage: 'Export' }),
|
||||
keyText: 'CtrlOrCommand+E',
|
||||
icon: 'icon export',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
@@ -140,6 +140,7 @@
|
||||
import LoadingDataGridCore from './LoadingDataGridCore.svelte';
|
||||
import { mongoFilterBehaviour, standardFilterBehaviours } from 'dbgate-tools';
|
||||
import { openImportExportTab } from '../utility/importExportTools';
|
||||
import { __t } from '../translations';
|
||||
|
||||
export let conid;
|
||||
export let display;
|
||||
|
||||
@@ -361,6 +361,7 @@
|
||||
detectSqlFilterBehaviour,
|
||||
stringifyCellValue,
|
||||
shouldOpenMultilineDialog,
|
||||
base64ToHex,
|
||||
} from 'dbgate-tools';
|
||||
import { getContext, onDestroy } from 'svelte';
|
||||
import _, { map } from 'lodash';
|
||||
@@ -421,7 +422,7 @@
|
||||
import { openJsonLinesData } from '../utility/openJsonLinesData';
|
||||
import contextMenuActivator from '../utility/contextMenuActivator';
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
import { __t, _t } from '../translations';
|
||||
import { __t, _t, _tval } from '../translations';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
import SaveArchiveModal from '../modals/SaveArchiveModal.svelte';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
@@ -758,7 +759,7 @@
|
||||
|
||||
export function saveCellToFileEnabled() {
|
||||
const value = getSelectedExportableCell();
|
||||
return _.isString(value) || (value?.type == 'Buffer' && _.isArray(value?.data));
|
||||
return _.isString(value) || (value?.type == 'Buffer' && _.isArray(value?.data)) || (value?.$binary?.base64);
|
||||
}
|
||||
|
||||
export async function saveCellToFile() {
|
||||
@@ -771,6 +772,8 @@
|
||||
fs.promises.writeFile(file, value);
|
||||
} else if (value?.type == 'Buffer' && _.isArray(value?.data)) {
|
||||
fs.promises.writeFile(file, window['Buffer'].from(value.data));
|
||||
} else if (value?.$binary?.base64) {
|
||||
fs.promises.writeFile(file, window['Buffer'].from(value.$binary.base64, 'base64'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -796,8 +799,9 @@
|
||||
isText
|
||||
? data
|
||||
: {
|
||||
type: 'Buffer',
|
||||
data: [...data],
|
||||
$binary: {
|
||||
base64: data.toString('base64'),
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1795,12 +1799,12 @@
|
||||
text: _t('datagrid.copyAdvanced', { defaultMessage: 'Copy advanced'}),
|
||||
submenu: [
|
||||
_.keys(copyRowsFormatDefs).map(format => ({
|
||||
text: _.isFunction(copyRowsFormatDefs[format].label) ? copyRowsFormatDefs[format].label() : copyRowsFormatDefs[format].label,
|
||||
text: _tval(copyRowsFormatDefs[format].label),
|
||||
onClick: () => copyToClipboardCore(format),
|
||||
})),
|
||||
{ divider: true },
|
||||
_.keys(copyRowsFormatDefs).map(format => ({
|
||||
text: _t('datagrid.setFormat', { defaultMessage: 'Set format: ' }) + (_.isFunction(copyRowsFormatDefs[format].name) ? copyRowsFormatDefs[format].name() : copyRowsFormatDefs[format].name),
|
||||
text: _t('datagrid.setFormat', { defaultMessage: 'Set format: ' }) + (_tval(copyRowsFormatDefs[format].name)),
|
||||
onClick: () => ($copyRowsFormat = format),
|
||||
})),
|
||||
|
||||
@@ -1870,7 +1874,7 @@
|
||||
return [
|
||||
menu,
|
||||
{
|
||||
text: _.isFunction(copyRowsFormatDefs[$copyRowsFormat].label) ? copyRowsFormatDefs[$copyRowsFormat].label() : copyRowsFormatDefs[$copyRowsFormat].label,
|
||||
text: _tval(copyRowsFormatDefs[$copyRowsFormat].label),
|
||||
onClick: () => copyToClipboardCore($copyRowsFormat),
|
||||
keyText: 'CtrlOrCommand+C',
|
||||
tag: 'copy',
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'jslTableGrid.export',
|
||||
category: 'Data grid',
|
||||
name: 'Export',
|
||||
category: __t('command.dataGrid', { defaultMessage: 'Data grid' }),
|
||||
name: __t('command.dataGrid.export', { defaultMessage: 'Export' }),
|
||||
icon: 'icon export',
|
||||
keyText: 'CtrlOrCommand+E',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
@@ -56,6 +56,7 @@
|
||||
|
||||
import LoadingDataGridCore from './LoadingDataGridCore.svelte';
|
||||
import { openImportExportTab } from '../utility/importExportTools';
|
||||
import { __t } from '../translations';
|
||||
|
||||
export let jslid;
|
||||
export let display;
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
{/if}
|
||||
|
||||
{#if dependencies.length > 0}
|
||||
<div class="bold nowrap ml-1">Dependent tables ({dependencies.length})</div>
|
||||
<div class="bold nowrap ml-1">{_t('dataGrid.dependentTables', { defaultMessage: 'Dependent tables' })} ({dependencies.length})</div>
|
||||
{#each dependencies.filter(fk => filterName(filter, fk.pureName)) as fk}
|
||||
<div
|
||||
class="link"
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'designer.arrange',
|
||||
category: 'Designer',
|
||||
category: __t('command.designer', { defaultMessage: 'Designer' }),
|
||||
icon: 'icon arrange',
|
||||
name: 'Arrange',
|
||||
name: __t('command.designer.arrange', { defaultMessage: 'Arrange' }),
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
testEnabled: () => getCurrentEditor()?.canArrange(),
|
||||
@@ -15,9 +15,9 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'diagram.export',
|
||||
category: 'Designer',
|
||||
toolbarName: 'Export diagram',
|
||||
name: 'Export diagram',
|
||||
category: __t('command.designer', { defaultMessage: 'Designer' }),
|
||||
toolbarName: __t('command.designer.exportDiagram', { defaultMessage: 'Export diagram' }),
|
||||
name: __t('command.designer.exportDiagram', { defaultMessage: 'Export diagram' }),
|
||||
icon: 'icon report',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -27,9 +27,9 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'diagram.deleteSelectedTables',
|
||||
category: 'Designer',
|
||||
toolbarName: 'Remove',
|
||||
name: 'Remove selected tables',
|
||||
category: __t('command.designer', { defaultMessage: 'Designer' }),
|
||||
toolbarName: __t('command.designer.remove', { defaultMessage: 'Remove' }),
|
||||
name: __t('command.designer.removeSelectedTables', { defaultMessage: 'Remove selected tables' }),
|
||||
icon: 'icon delete',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -67,6 +67,7 @@
|
||||
import { isProApp } from '../utility/proTools';
|
||||
import dragScroll from '../utility/dragScroll';
|
||||
import FormStyledButton from '../buttons/FormStyledButton.svelte';
|
||||
import { __t } from '../translations';
|
||||
|
||||
export let value;
|
||||
export let onChange;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
import { evaluateCondition } from 'dbgate-sqltree';
|
||||
import { compileCompoudEvalCondition } from 'dbgate-filterparser';
|
||||
import { chevronExpandIcon } from '../icons/expandIcons';
|
||||
import { _tval } from '../translations';
|
||||
|
||||
export let columns: (TableControlColumn | false)[];
|
||||
export let rows = null;
|
||||
@@ -368,7 +369,7 @@
|
||||
{/if}
|
||||
{/key}
|
||||
{:else}
|
||||
{ _.isFunction(row[col.fieldName]) ? row[col.fieldName]() : row[col.fieldName] || ''}
|
||||
{ _tval(row[col.fieldName]) || '' }
|
||||
{/if}
|
||||
</td>
|
||||
{/each}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { getFormContext } from './FormProviderCore.svelte';
|
||||
import TextField from './TextField.svelte';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let name;
|
||||
export let disabled = false;
|
||||
@@ -29,7 +30,7 @@
|
||||
setFieldValue(name, e.target['value']);
|
||||
}
|
||||
}}
|
||||
placeholder={isCrypted ? '(Password is encrypted)' : undefined}
|
||||
placeholder={isCrypted ? _t('common.passwordEncrypted', { defaultMessage: 'Password is encrypted' }) : undefined}
|
||||
type={isCrypted || showPassword ? 'text' : 'password'}
|
||||
/>
|
||||
{#if !isCrypted}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { getFormContext } from './FormProviderCore.svelte';
|
||||
import TextField from './TextField.svelte';
|
||||
import { _tval } from '../translations';
|
||||
|
||||
export let name;
|
||||
export let defaultValue;
|
||||
@@ -11,7 +12,7 @@
|
||||
|
||||
<TextField
|
||||
{...$$restProps}
|
||||
value={$values?.[name] ?? defaultValue}
|
||||
value={$values?.[name] ? _tval($values[name]) : defaultValue}
|
||||
on:input={e => setFieldValue(name, e.target['value'])}
|
||||
on:input={e => {
|
||||
if (saveOnInput) {
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'collectionJsonView.expandAll',
|
||||
category: 'Collection data',
|
||||
name: 'Expand all',
|
||||
category: __t('command.collectionData', { defaultMessage: 'Collection data' }),
|
||||
name: __t('command.collectionData.expandAll', { defaultMessage: 'Expand all' }),
|
||||
isRelatedToTab: true,
|
||||
icon: 'icon expand-all',
|
||||
onClick: () => getCurrentEditor().handleExpandAll(),
|
||||
@@ -12,8 +12,8 @@
|
||||
});
|
||||
registerCommand({
|
||||
id: 'collectionJsonView.collapseAll',
|
||||
category: 'Collection data',
|
||||
name: 'Collapse all',
|
||||
category: __t('command.collectionData', { defaultMessage: 'Collection data' }),
|
||||
name: __t('command.collectionData.collapseAll', { defaultMessage: 'Collapse all' }),
|
||||
isRelatedToTab: true,
|
||||
icon: 'icon collapse-all',
|
||||
onClick: () => getCurrentEditor().handleCollapseAll(),
|
||||
@@ -37,6 +37,7 @@
|
||||
import CollectionJsonRow from './CollectionJsonRow.svelte';
|
||||
import { getIntSettingsValue } from '../settings/settingsTools';
|
||||
import invalidateCommands from '../commands/invalidateCommands';
|
||||
import { __t } from '../translations';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'dataForm.refresh',
|
||||
category: 'Data form',
|
||||
name: _t('common.refresh', { defaultMessage: 'Refresh' }),
|
||||
category: __t('command.dataForm', { defaultMessage: 'Data form' }),
|
||||
name: __t('common.refresh', { defaultMessage: 'Refresh' }),
|
||||
keyText: 'F5 | CtrlOrCommand+R',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -26,8 +26,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'dataForm.copyToClipboard',
|
||||
category: 'Data form',
|
||||
name: 'Copy to clipboard',
|
||||
category: __t('command.dataForm', { defaultMessage: 'Data form' }),
|
||||
name: __t('command.dataForm.copyToClipboard', { defaultMessage: 'Copy to clipboard' }),
|
||||
keyText: 'CtrlOrCommand+C',
|
||||
disableHandleKeyText: 'CtrlOrCommand+C',
|
||||
testEnabled: () => getCurrentDataForm() != null,
|
||||
@@ -36,8 +36,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'dataForm.revertRowChanges',
|
||||
category: 'Data form',
|
||||
name: 'Revert row changes',
|
||||
category: __t('command.dataForm', { defaultMessage: 'Data form' }),
|
||||
name: __t('command.dataForm.revertRowChanges', { defaultMessage: 'Revert row changes' }),
|
||||
keyText: 'CtrlOrCommand+U',
|
||||
testEnabled: () => getCurrentDataForm()?.getGrider()?.containsChanges,
|
||||
onClick: () => getCurrentDataForm().getGrider().revertRowChanges(0),
|
||||
@@ -45,8 +45,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'dataForm.setNull',
|
||||
category: 'Data form',
|
||||
name: 'Set NULL',
|
||||
category: __t('command.dataForm', { defaultMessage: 'Data form' }),
|
||||
name: __t('command.dataForm.setNull', { defaultMessage: 'Set NULL' }),
|
||||
keyText: 'CtrlOrCommand+0',
|
||||
testEnabled: () => getCurrentDataForm() != null && !getCurrentDataForm()?.getEditorTypes()?.supportFieldRemoval,
|
||||
onClick: () => getCurrentDataForm().setFixedValue(null),
|
||||
@@ -54,8 +54,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'dataForm.removeField',
|
||||
category: 'Data form',
|
||||
name: 'Remove field',
|
||||
category: __t('command.dataForm', { defaultMessage: 'Data form' }),
|
||||
name: __t('command.dataForm.removeField', { defaultMessage: 'Remove field' }),
|
||||
keyText: 'CtrlOrCommand+0',
|
||||
testEnabled: () => getCurrentDataForm() != null && getCurrentDataForm()?.getEditorTypes()?.supportFieldRemoval,
|
||||
onClick: () => getCurrentDataForm().setFixedValue(undefined),
|
||||
@@ -63,8 +63,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'dataForm.undo',
|
||||
category: 'Data form',
|
||||
name: 'Undo',
|
||||
category: __t('command.dataForm', { defaultMessage: 'Data form' }),
|
||||
name: __t('command.dataForm.undo', { defaultMessage: 'Undo' }),
|
||||
group: 'undo',
|
||||
icon: 'icon undo',
|
||||
toolbar: true,
|
||||
@@ -75,8 +75,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'dataForm.redo',
|
||||
category: 'Data form',
|
||||
name: 'Redo',
|
||||
category: __t('command.dataForm', { defaultMessage: 'Data form' }),
|
||||
name: __t('command.dataForm.redo', { defaultMessage: 'Redo' }),
|
||||
group: 'redo',
|
||||
icon: 'icon redo',
|
||||
toolbar: true,
|
||||
@@ -87,16 +87,16 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'dataForm.reconnect',
|
||||
category: 'Data grid',
|
||||
name: 'Reconnect',
|
||||
category: __t('command.dataGrid', { defaultMessage: 'Data grid' }),
|
||||
name: __t('command.dataGrid.reconnect', { defaultMessage: 'Reconnect' }),
|
||||
testEnabled: () => getCurrentDataForm() != null,
|
||||
onClick: () => getCurrentDataForm().reconnect(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataForm.filterSelected',
|
||||
category: 'Data form',
|
||||
name: 'Filter this value',
|
||||
category: __t('command.dataForm', { defaultMessage: 'Data form' }),
|
||||
name: __t('command.dataForm.filterSelected', { defaultMessage: 'Filter this value' }),
|
||||
keyText: 'CtrlOrCommand+Shift+F',
|
||||
testEnabled: () => getCurrentDataForm() != null,
|
||||
onClick: () => getCurrentDataForm().filterSelectedValue(),
|
||||
@@ -104,16 +104,16 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'dataForm.addToFilter',
|
||||
category: 'Data form',
|
||||
name: 'Add to filter',
|
||||
category: __t('command.dataForm', { defaultMessage: 'Data form' }),
|
||||
name: __t('command.dataForm.addToFilter', { defaultMessage: 'Add to filter' }),
|
||||
testEnabled: () => getCurrentDataForm() != null,
|
||||
onClick: () => getCurrentDataForm().addToFilter(),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'dataForm.goToFirst',
|
||||
category: 'Data form',
|
||||
name: 'First',
|
||||
category: __t('command.dataForm', { defaultMessage: 'Data form' }),
|
||||
name: __t('command.dataForm.goToFirst', { defaultMessage: 'First' }),
|
||||
keyText: 'CtrlOrCommand+Home',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -124,8 +124,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'dataForm.goToPrevious',
|
||||
category: 'Data form',
|
||||
name: 'Previous',
|
||||
category: __t('command.dataForm', { defaultMessage: 'Data form' }),
|
||||
name: __t('command.dataForm.goToPrevious', { defaultMessage: 'Previous' }),
|
||||
keyText: 'CtrlOrCommand+ArrowUp',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -136,8 +136,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'dataForm.goToNext',
|
||||
category: 'Data form',
|
||||
name: 'Next',
|
||||
category: __t('command.dataForm', { defaultMessage: 'Data form' }),
|
||||
name: __t('command.dataForm.goToNext', { defaultMessage: 'Next' }),
|
||||
keyText: 'CtrlOrCommand+ArrowDown',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -148,8 +148,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'dataForm.goToLast',
|
||||
category: 'Data form',
|
||||
name: 'Last',
|
||||
category: __t('command.dataForm', { defaultMessage: 'Data form' }),
|
||||
name: __t('command.dataForm.goToLast', { defaultMessage: 'Last' }),
|
||||
keyText: 'CtrlOrCommand+End',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -197,7 +197,7 @@
|
||||
import resizeObserver from '../utility/resizeObserver';
|
||||
import openReferenceForm from './openReferenceForm';
|
||||
import { useSettings } from '../utility/metadataLoaders';
|
||||
import { _t } from '../translations';
|
||||
import { _t, __t } from '../translations';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
@@ -243,20 +243,16 @@
|
||||
|
||||
function getRowCountInfo(allRowCount) {
|
||||
if (rowCountNotAvailable) {
|
||||
return `Row: ${((display.config.formViewRecordNumber || 0) + 1).toLocaleString()} / ???`;
|
||||
return _t('dataForm.rowCount', { defaultMessage: 'Row: {rowCount} / ???', values: { rowCount: ((display.config.formViewRecordNumber || 0) + 1).toLocaleString() } });
|
||||
}
|
||||
if (rowData == null) {
|
||||
if (allRowCount != null) {
|
||||
return `Out of bounds: ${(
|
||||
(display.config.formViewRecordNumber || 0) + 1
|
||||
).toLocaleString()} / ${allRowCount.toLocaleString()}`;
|
||||
return _t('dataForm.outOfBounds', { defaultMessage: 'Out of bounds: {current} / {total}', values: { current: ((display.config.formViewRecordNumber || 0) + 1).toLocaleString(), total: allRowCount.toLocaleString() } });
|
||||
}
|
||||
return 'No data';
|
||||
return _t('dataForm.noData', { defaultMessage: 'No data' });
|
||||
}
|
||||
if (allRowCount == null || display == null) return 'Loading row count...';
|
||||
return `Row: ${(
|
||||
(display.config.formViewRecordNumber || 0) + 1
|
||||
).toLocaleString()} / ${allRowCount.toLocaleString()}`;
|
||||
if (allRowCount == null || display == null) return _t('dataForm.loadingRowCount', { defaultMessage: 'Loading row count...' });
|
||||
return _t('dataForm.rowCount', { defaultMessage: 'Row: {current} / {total}', values: { current: ((display.config.formViewRecordNumber || 0) + 1).toLocaleString(), total: allRowCount.toLocaleString() } });
|
||||
}
|
||||
|
||||
export function getGrider() {
|
||||
@@ -720,7 +716,7 @@
|
||||
</div>
|
||||
|
||||
{#if isLoading}
|
||||
<LoadingInfo wrapper message="Loading data" />
|
||||
<LoadingInfo wrapper message={_t('common.loadingData', { defaultMessage: 'Loading data' })} />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import KeyboardModal from './KeyboardModal.svelte';
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal, showModal } from './modalTools';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let command;
|
||||
|
||||
@@ -23,16 +24,16 @@
|
||||
|
||||
<FormProviderCore {values}>
|
||||
<ModalBase {...$$restProps}>
|
||||
<svelte:fragment slot="header">Configure commmand</svelte:fragment>
|
||||
<svelte:fragment slot="header">{_t('commandModal.configure', { defaultMessage: 'Configure command' })}</svelte:fragment>
|
||||
|
||||
<FormTextField label="Category" name="category" disabled />
|
||||
<FormTextField label="Name" name="name" disabled />
|
||||
<FormTextField label={_t('commandModal.category', { defaultMessage: 'Category' })} name="category" disabled />
|
||||
<FormTextField label={_t('commandModal.name', { defaultMessage: 'Name' })} name="name" disabled />
|
||||
|
||||
<div class="row">
|
||||
<FormTextField label="Keyboard shortcut" name="keyText" templateProps={{ noMargin: true }} focused />
|
||||
<FormTextField label={_t('commandModal.keyboardShortcut', { defaultMessage: 'Keyboard shortcut' })} name="keyText" templateProps={{ noMargin: true }} focused />
|
||||
<FormStyledButton
|
||||
type="button"
|
||||
value="Keyboard"
|
||||
value={_t('commandModal.keyboard', { defaultMessage: 'Keyboard' })}
|
||||
on:click={handleKeyboard}
|
||||
data-testid="CommandModal_keyboardButton"
|
||||
/>
|
||||
@@ -56,7 +57,7 @@
|
||||
/>
|
||||
<FormStyledButton
|
||||
type="button"
|
||||
value="Reset"
|
||||
value={_t('common.reset', { defaultMessage: 'Reset' })}
|
||||
on:click={() => {
|
||||
closeCurrentModal();
|
||||
apiCall('config/update-settings', {
|
||||
@@ -64,7 +65,7 @@
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
|
||||
<FormStyledButton type="button" value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
|
||||
</svelte:fragment>
|
||||
</ModalBase>
|
||||
</FormProviderCore>
|
||||
|
||||
@@ -86,14 +86,26 @@
|
||||
submenuKey += 1;
|
||||
return;
|
||||
}
|
||||
if (item.switchStore && item.switchValue) {
|
||||
item.switchStore.update(x => ({
|
||||
...x,
|
||||
[item.switchValue]: !x[item.switchValue],
|
||||
}));
|
||||
if (item.switchStore) {
|
||||
if (item.switchValue) {
|
||||
item.switchStore.update(x => ({
|
||||
...x,
|
||||
[item.switchValue]: !x[item.switchValue],
|
||||
}));
|
||||
}
|
||||
|
||||
if (item.switchOption && item.switchOptionValue) {
|
||||
item.switchStore.update(x => ({
|
||||
...x,
|
||||
[item.switchOption]: item.switchOptionValue,
|
||||
}));
|
||||
}
|
||||
switchIndex++;
|
||||
return;
|
||||
if (!item.closeOnSwitchClick) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
dispatchClose();
|
||||
if (onCloseParent) onCloseParent();
|
||||
if (item.onClick) item.onClick();
|
||||
@@ -163,6 +175,16 @@
|
||||
{/if}
|
||||
{/key}
|
||||
{/if}
|
||||
{#if item.switchOption && item.switchStoreGetter}
|
||||
{@const optionValue = item.switchStoreGetter()[item.switchOption]}
|
||||
{#key switchIndex}
|
||||
{#if optionValue === item.switchOptionValue || (item.switchOptionIsDefault && !optionValue)}
|
||||
<FontIcon icon="icon check" padRight />
|
||||
{:else}
|
||||
<FontIcon icon="icon invisible-box" padRight />
|
||||
{/if}
|
||||
{/key}
|
||||
{/if}
|
||||
{item.text || item.label}
|
||||
</span>
|
||||
{#if item.keyText}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { parseCellValue, safeJsonParse, stringifyCellValue } from 'dbgate-tools';
|
||||
import { showSnackbarError } from '../utility/snackbar';
|
||||
import ErrorMessageModal from './ErrorMessageModal.svelte';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let onSave;
|
||||
export let value;
|
||||
@@ -49,14 +50,14 @@
|
||||
if (parsed) {
|
||||
textValue = JSON.stringify(parsed, null, 2);
|
||||
} else {
|
||||
showModal(ErrorMessageModal, { message: 'Not valid JSON' });
|
||||
showModal(ErrorMessageModal, { message: _t('dataGrid.formatJson.invalid', { defaultMessage: 'Not valid JSON' }) });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormProvider>
|
||||
<ModalBase {...$$restProps}>
|
||||
<div slot="header">Edit cell value</div>
|
||||
<div slot="header">{_t('dataGrid.editCellValue', { defaultMessage: 'Edit cell value' })}</div>
|
||||
|
||||
<div class="editor">
|
||||
<AceEditor bind:value={textValue} bind:this={editor} onKeyDown={handleKeyDown} mode={syntaxMode} />
|
||||
@@ -72,21 +73,21 @@
|
||||
closeCurrentModal();
|
||||
}}
|
||||
/>
|
||||
<FormStyledButton type="button" value="Cancel" on:click={closeCurrentModal} />
|
||||
<FormStyledButton type="button" value={_t('common.cancel', { defaultMessage: 'Cancel' })} on:click={closeCurrentModal} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FormStyledButton type="button" value="Format JSON" on:click={handleFormatJson} />
|
||||
<FormStyledButton type="button" skipWidth={true} value={_t('dataGrid.formatJson', { defaultMessage: 'Format JSON' })} on:click={handleFormatJson} />
|
||||
|
||||
Code highlighting:
|
||||
{_t('dataGrid.codeHighlighting', { defaultMessage: 'Code highlighting:' })}
|
||||
<SelectField
|
||||
isNative
|
||||
value={syntaxMode}
|
||||
on:change={e => (syntaxMode = e.detail)}
|
||||
options={[
|
||||
{ value: 'text', label: 'None (raw text)' },
|
||||
{ value: 'text', label: _t('dataGrid.codeHighlighting.none', { defaultMessage: 'None (raw text)' }) },
|
||||
{ value: 'json', label: 'JSON' },
|
||||
{ value: 'html', label: 'HTML' },
|
||||
{ value: 'html', label: 'HTML'},
|
||||
{ value: 'xml', label: 'XML' },
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -6,8 +6,9 @@
|
||||
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal } from './modalTools';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let title = 'Error';
|
||||
export let title = _t('common.error', { defaultMessage: 'Error' });
|
||||
export let message;
|
||||
export let showAsCode = false;
|
||||
</script>
|
||||
@@ -30,7 +31,7 @@
|
||||
{/if}
|
||||
|
||||
<div slot="footer">
|
||||
<FormSubmit value="Close" on:click={closeCurrentModal} data-testid="ErrorMessageModal_closeButton" />
|
||||
<FormSubmit value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} data-testid="ErrorMessageModal_closeButton" />
|
||||
</div>
|
||||
</ModalBase>
|
||||
</FormProvider>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { commandsCustomized, currentDropDownMenu } from '../stores';
|
||||
import { prepareMenuItems } from '../utility/contextMenu';
|
||||
import DropDownMenu from './DropDownMenu.svelte';
|
||||
|
||||
export let items;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import keycodes from '../utility/keycodes';
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal } from './modalTools';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let onChange;
|
||||
let value;
|
||||
@@ -38,7 +39,7 @@
|
||||
</script>
|
||||
|
||||
<ModalBase {...$$restProps} simple>
|
||||
<div class="mb-2">Show desired key combination and press ENTER</div>
|
||||
<div class="mb-2">_{_t('commandModal.showKeyCombination', { defaultMessage: 'Show desired key combination and press ENTER' })}</div>
|
||||
<div class="largeFormMarker">
|
||||
<TextField on:keydown={handleKeyDown} bind:value focused />
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import ErrorMessageModal from './ErrorMessageModal.svelte';
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal, showModal } from './modalTools';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let driver;
|
||||
export let dbid;
|
||||
@@ -44,14 +45,14 @@
|
||||
<FormProvider initialValues={{ name: '' }}>
|
||||
<ModalBase {...$$restProps}>
|
||||
<svelte:fragment slot="header">
|
||||
Create {driver?.collectionSingularLabel ?? 'collection/container'}
|
||||
{_t('dbObject.createCollection', { defaultMessage: 'Create collection/container'})}
|
||||
</svelte:fragment>
|
||||
|
||||
<FormArgumentList args={driver?.newCollectionFormParams} />
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<FormSubmit value="OK" on:click={e => handleSubmit(e.detail)} disabled={isSaving} />
|
||||
<FormStyledButton type="button" value="Cancel" on:click={closeCurrentModal} />
|
||||
<FormStyledButton type="button" value={_t('common.cancel', { defaultMessage: 'Cancel' })} on:click={closeCurrentModal} />
|
||||
</svelte:fragment>
|
||||
</ModalBase>
|
||||
</FormProvider>
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
import { closeCurrentModal, showModal } from './modalTools';
|
||||
import FormCloudFolderSelect from '../forms/FormCloudFolderSelect.svelte';
|
||||
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
|
||||
import { useConfig } from '../utility/metadataLoaders';
|
||||
import { showSnackbarError } from '../utility/snackbar';
|
||||
|
||||
export let data;
|
||||
export let name;
|
||||
@@ -24,26 +26,39 @@
|
||||
export let onSave = undefined;
|
||||
export let folid;
|
||||
export let skipLocal = false;
|
||||
export let defaultTeamFolder = false;
|
||||
// export let cntid;
|
||||
|
||||
const values = writable({ name, cloudFolder: folid ?? '__local' });
|
||||
const configValue = useConfig();
|
||||
|
||||
const values = writable({
|
||||
name,
|
||||
cloudFolder: folid ?? '__local',
|
||||
saveToTeamFolder: !!(getCurrentConfig()?.storageDatabase && defaultTeamFolder),
|
||||
});
|
||||
|
||||
const electron = getElectron();
|
||||
|
||||
const handleSubmit = async e => {
|
||||
const { name, cloudFolder } = e.detail;
|
||||
if ($values['saveToTeamFolder']) {
|
||||
const { teamFileId } = await apiCall('team-files/create-new', { fileType: folder, file: name, data });
|
||||
closeCurrentModal();
|
||||
if (onSave) {
|
||||
onSave(name, {
|
||||
savedFile: name,
|
||||
savedFolder: folder,
|
||||
savedFilePath: null,
|
||||
savedCloudFolderId: null,
|
||||
savedCloudContentId: null,
|
||||
savedTeamFileId: teamFileId,
|
||||
});
|
||||
const resp = await apiCall('team-files/create-new', { fileType: folder, file: name, data });
|
||||
if (resp?.apiErrorMessage) {
|
||||
showSnackbarError(resp.apiErrorMessage);
|
||||
} else if (resp?.teamFileId) {
|
||||
closeCurrentModal();
|
||||
if (onSave) {
|
||||
onSave(name, {
|
||||
savedFile: name,
|
||||
savedFolder: folder,
|
||||
savedFilePath: null,
|
||||
savedCloudFolderId: null,
|
||||
savedCloudContentId: null,
|
||||
savedTeamFileId: resp.teamFileId,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
showSnackbarError('Failed to save to team folder.');
|
||||
}
|
||||
} else if (cloudFolder === '__local') {
|
||||
await apiCall('files/save', { folder, file: name, data, format });
|
||||
@@ -124,7 +139,7 @@
|
||||
]}
|
||||
/>
|
||||
{/if}
|
||||
{#if getCurrentConfig().storageDatabase}
|
||||
{#if $configValue?.storageDatabase}
|
||||
<FormCheckboxField label="Save to team folder" name="saveToTeamFolder" />
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
import _ from 'lodash';
|
||||
import { apiCall } from '../utility/api';
|
||||
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
||||
import { base64ToHex } from 'dbgate-tools';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let onConfirm;
|
||||
export let conid;
|
||||
@@ -73,15 +75,15 @@
|
||||
|
||||
<FormProvider>
|
||||
<ModalBase {...$$restProps}>
|
||||
<svelte:fragment slot="header">Choose value from {field}</svelte:fragment>
|
||||
<svelte:fragment slot="header">{_t('dataGrid.chooseValue', { defaultMessage: 'Choose value from {field}', values: { field } })}</svelte:fragment>
|
||||
|
||||
<!-- <FormTextField name="search" label='Search' placeholder="Search" bind:value={search} /> -->
|
||||
<div class="largeFormMarker">
|
||||
<SearchInput placeholder="Search" bind:value={search} isDebounced />
|
||||
<SearchInput placeholder={_t('common.search', { defaultMessage: 'Search' })} bind:value={search} isDebounced />
|
||||
</div>
|
||||
|
||||
{#if isLoading}
|
||||
<LoadingInfo message="Loading data" />
|
||||
<LoadingInfo message={_t('common.loadingData', { defaultMessage: 'Loading data' })} />
|
||||
{/if}
|
||||
|
||||
{#if !isLoading && rows}
|
||||
@@ -111,8 +113,8 @@
|
||||
},
|
||||
{
|
||||
fieldName: 'value',
|
||||
header: 'Value',
|
||||
formatter: row => (row.value == null ? '(NULL)' : row.value),
|
||||
header: _t('dataGrid.value', { defaultMessage: 'Value' }),
|
||||
formatter: row => (row.value == null ? '(NULL)' : row.value?.$binary?.base64 ? base64ToHex(row.value.$binary.base64) : row.value),
|
||||
},
|
||||
]}
|
||||
>
|
||||
@@ -146,7 +148,7 @@
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
|
||||
<FormStyledButton type="button" value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
|
||||
</svelte:fragment>
|
||||
</ModalBase>
|
||||
</FormProvider>
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
if (force && value?.type == 'Buffer' && _.isArray(value.data)) {
|
||||
return String.fromCharCode.apply(String, value.data);
|
||||
}
|
||||
else if (force && value?.$binary?.base64) {
|
||||
return atob(value.$binary.base64);
|
||||
}
|
||||
return stringifyCellValue(value, 'gridCellIntent').value;
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
|
||||
{#if driver?.showConnectionField('authToken', $values, showConnectionFieldArgs)}
|
||||
<FormTextField
|
||||
label={_t('authToken', { defaultMessage: 'Auth token' })}
|
||||
label={_t('connection.authToken', { defaultMessage: 'Auth token' })}
|
||||
name="authToken"
|
||||
data-testid="ConnectionDriverFields_authToken"
|
||||
disabled={isConnected || disabledFields.includes('authToken')}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||
import SelectField from '../forms/SelectField.svelte';
|
||||
import { lastUsedDefaultActions } from '../stores';
|
||||
import { _tval } from '../translations';
|
||||
|
||||
export let label;
|
||||
export let objectTypeField;
|
||||
@@ -18,7 +19,7 @@
|
||||
defaultValue={defaultDatabaseObjectAppObjectActions[objectTypeField][0]?.defaultActionId}
|
||||
options={defaultDatabaseObjectAppObjectActions[objectTypeField].map(x => ({
|
||||
value: x.defaultActionId,
|
||||
label: x.label,
|
||||
label: _tval(x.label),
|
||||
}))}
|
||||
value={$lastUsedDefaultActions[objectTypeField]}
|
||||
on:change={e => {
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
import { safeFormatDate } from 'dbgate-tools';
|
||||
import FormDefaultActionField from './FormDefaultActionField.svelte';
|
||||
import AiSettingsTab from './AiSettingsTab.svelte';
|
||||
import { _t } from '../translations';
|
||||
import { _t, setSelectedLanguage } from '../translations';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
@@ -110,14 +110,43 @@ ORDER BY
|
||||
maxHeight100
|
||||
flex1
|
||||
tabs={[
|
||||
hasPermission('settings/change') && { identifier: 'general', label: _t('settings.general', { defaultMessage: 'General' }), slot: 1 },
|
||||
isProApp() && electron && { identifier: 'license', label: _t('settings.license', { defaultMessage: 'License' }), slot: 7 },
|
||||
hasPermission('settings/change') && { identifier: 'connection', label: _t('settings.connection', { defaultMessage: 'Connection' }), slot: 2 },
|
||||
hasPermission('settings/change') && {
|
||||
identifier: 'general',
|
||||
label: _t('settings.general', { defaultMessage: 'General' }),
|
||||
slot: 1,
|
||||
},
|
||||
isProApp() &&
|
||||
electron && {
|
||||
identifier: 'license',
|
||||
label: _t('settings.license', { defaultMessage: 'License' }),
|
||||
slot: 7,
|
||||
},
|
||||
hasPermission('settings/change') && {
|
||||
identifier: 'connection',
|
||||
label: _t('settings.connection', { defaultMessage: 'Connection' }),
|
||||
slot: 2,
|
||||
},
|
||||
{ identifier: 'theme', label: _t('settings.theme', { defaultMessage: 'Themes' }), slot: 3 },
|
||||
hasPermission('settings/change') && { identifier: 'default-actions', label: _t('settings.defaultActions', { defaultMessage: 'Default Actions' }), slot: 4 },
|
||||
hasPermission('settings/change') && { identifier: 'behaviour', label: _t('settings.behaviour', { defaultMessage: 'Behaviour' }), slot: 5 },
|
||||
hasPermission('settings/change') && { identifier: 'external-tools', label: _t('settings.externalTools', { defaultMessage: 'External tools' }), slot: 8 },
|
||||
hasPermission('settings/change') && { identifier: 'other', label: _t('settings.other', { defaultMessage: 'Other' }), slot: 6 },
|
||||
hasPermission('settings/change') && {
|
||||
identifier: 'default-actions',
|
||||
label: _t('settings.defaultActions', { defaultMessage: 'Default Actions' }),
|
||||
slot: 4,
|
||||
},
|
||||
hasPermission('settings/change') && {
|
||||
identifier: 'behaviour',
|
||||
label: _t('settings.behaviour', { defaultMessage: 'Behaviour' }),
|
||||
slot: 5,
|
||||
},
|
||||
hasPermission('settings/change') && {
|
||||
identifier: 'external-tools',
|
||||
label: _t('settings.externalTools', { defaultMessage: 'External tools' }),
|
||||
slot: 8,
|
||||
},
|
||||
hasPermission('settings/change') && {
|
||||
identifier: 'other',
|
||||
label: _t('settings.other', { defaultMessage: 'Other' }),
|
||||
slot: 6,
|
||||
},
|
||||
isProApp() && hasPermission('settings/change') && { identifier: 'ai', label: 'AI', slot: 9 },
|
||||
]}
|
||||
>
|
||||
@@ -126,67 +155,112 @@ ORDER BY
|
||||
<div class="heading">{_t('settings.appearance', { defaultMessage: 'Appearance' })}</div>
|
||||
<FormCheckboxField
|
||||
name="app.useNativeMenu"
|
||||
label={isMac() ? 'Use native window title' : 'Use system native menu'}
|
||||
label={isMac()
|
||||
? _t('settings.useNativeWindowTitle', { defaultMessage: 'Use native window title' })
|
||||
: _t('settings.useSystemNativeMenu', { defaultMessage: 'Use system native menu' })}
|
||||
on:change={() => {
|
||||
restartWarning = true;
|
||||
}}
|
||||
/>
|
||||
{#if restartWarning}
|
||||
<div class="ml-5 mb-3">
|
||||
<FontIcon icon="img warn" /> {_t('settings.nativeMenuRestartWarning', { defaultMessage: 'Native menu settings will be applied after app restart' })}
|
||||
<FontIcon icon="img warn" />
|
||||
{_t('settings.nativeMenuRestartWarning', {
|
||||
defaultMessage: 'Native menu settings will be applied after app restart',
|
||||
})}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<FormCheckboxField
|
||||
name="tabGroup.showServerName"
|
||||
label={_t('settings.tabGroup.showServerName', { defaultMessage: 'Show server name alongside database name in title of the tab group' })}
|
||||
label={_t('settings.tabGroup.showServerName', {
|
||||
defaultMessage: 'Show server name alongside database name in title of the tab group',
|
||||
})}
|
||||
defaultValue={false}
|
||||
/>
|
||||
<!-- <div class="heading">{_t('settings.localization', { defaultMessage: 'Localization' })}</div>
|
||||
<FormSelectField
|
||||
label="Language"
|
||||
name="localization.language"
|
||||
defaultValue={getSelectedLanguage()}
|
||||
isNative
|
||||
options={[
|
||||
{ value: 'en', label: 'English' },
|
||||
{ value: 'cs', label: 'Czech' },
|
||||
]}
|
||||
on:change={() => {
|
||||
showModal(ConfirmModal, {
|
||||
message: 'Application will be reloaded to apply new language settings',
|
||||
onConfirm: () => {
|
||||
setTimeout(() => {
|
||||
internalRedirectTo('/');
|
||||
}, 100);
|
||||
},
|
||||
});
|
||||
}}
|
||||
/> -->
|
||||
<div class="heading">{_t('settings.localization', { defaultMessage: 'Localization' })}</div>
|
||||
|
||||
<FormFieldTemplateLarge
|
||||
label={_t('settings.localization.language', { defaultMessage: 'Language' })}
|
||||
type="combo"
|
||||
>
|
||||
<SelectField
|
||||
isNative
|
||||
data-testid="SettingsModal_languageSelect"
|
||||
options={[
|
||||
{ value: 'cs', label: 'Čeština' },
|
||||
{ value: 'de', label: 'Deutsch' },
|
||||
{ value: 'en', label: 'English' },
|
||||
{ value: 'es', label: 'Español' },
|
||||
{ value: 'fr', label: 'Français' },
|
||||
{ value: 'it', label: 'Italiano' },
|
||||
{ value: 'pt', label: 'Português (Brasil)' },
|
||||
{ value: 'sk', label: 'Slovenčina' },
|
||||
{ value: 'ja', label: '日本語' },
|
||||
{ value: 'zh', label: '中文' },
|
||||
]}
|
||||
defaultValue={getSelectedLanguage()}
|
||||
value={getSelectedLanguage()}
|
||||
on:change={e => {
|
||||
setSelectedLanguage(e.detail);
|
||||
showModal(ConfirmModal, {
|
||||
message: _t('settings.localization.reloadWarning', {
|
||||
defaultMessage: 'Application will be reloaded to apply new language settings',
|
||||
}),
|
||||
onConfirm: () => {
|
||||
setTimeout(() => {
|
||||
internalRedirectTo(electron ? '/index.html' : '/');
|
||||
}, 100);
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
|
||||
<div class="heading">{_t('settings.dataGrid.title', { defaultMessage: 'Data grid' })}</div>
|
||||
<FormTextField
|
||||
name="dataGrid.pageSize"
|
||||
label={_t('settings.dataGrid.pageSize', { defaultMessage: 'Page size (number of rows for incremental loading, must be between 5 and 50000)' })}
|
||||
label={_t('settings.dataGrid.pageSize', {
|
||||
defaultMessage: 'Page size (number of rows for incremental loading, must be between 5 and 50000)',
|
||||
})}
|
||||
defaultValue="100"
|
||||
/>
|
||||
<FormCheckboxField name="dataGrid.showHintColumns" label={_t('settings.dataGrid.showHintColumns', { defaultMessage: 'Show foreign key hints' })} defaultValue={true} />
|
||||
{#if isProApp()}
|
||||
<FormCheckboxField
|
||||
name="dataGrid.showHintColumns"
|
||||
label={_t('settings.dataGrid.showHintColumns', { defaultMessage: 'Show foreign key hints' })}
|
||||
defaultValue={true}
|
||||
/>
|
||||
{/if}
|
||||
<!-- <FormCheckboxField name="dataGrid.showHintColumns" label="Show foreign key hints" defaultValue={true} /> -->
|
||||
|
||||
<FormCheckboxField name="dataGrid.thousandsSeparator" label={_t('settings.dataGrid.thousandsSeparator', { defaultMessage: 'Use thousands separator for numbers' })} />
|
||||
<FormCheckboxField
|
||||
name="dataGrid.thousandsSeparator"
|
||||
label={_t('settings.dataGrid.thousandsSeparator', {
|
||||
defaultMessage: 'Use thousands separator for numbers',
|
||||
})}
|
||||
/>
|
||||
|
||||
<FormTextField
|
||||
name="dataGrid.defaultAutoRefreshInterval"
|
||||
label={_t('settings.dataGrid.defaultAutoRefreshInterval', { defaultMessage: 'Default grid auto refresh interval in seconds' })}
|
||||
label={_t('settings.dataGrid.defaultAutoRefreshInterval', {
|
||||
defaultMessage: 'Default grid auto refresh interval in seconds',
|
||||
})}
|
||||
defaultValue="10"
|
||||
/>
|
||||
|
||||
<FormCheckboxField name="dataGrid.alignNumbersRight" label={_t('settings.dataGrid.alignNumbersRight', { defaultMessage: 'Align numbers to right' })} defaultValue={false} />
|
||||
<FormCheckboxField
|
||||
name="dataGrid.alignNumbersRight"
|
||||
label={_t('settings.dataGrid.alignNumbersRight', { defaultMessage: 'Align numbers to right' })}
|
||||
defaultValue={false}
|
||||
/>
|
||||
|
||||
<FormTextField
|
||||
name="dataGrid.collectionPageSize"
|
||||
label={_t('settings.dataGrid.collectionPageSize', { defaultMessage: 'Collection page size (for MongoDB JSON view, must be between 5 and 1000)' })}
|
||||
label={_t('settings.dataGrid.collectionPageSize', {
|
||||
defaultMessage: 'Collection page size (for MongoDB JSON view, must be between 5 and 1000)',
|
||||
})}
|
||||
defaultValue="50"
|
||||
/>
|
||||
|
||||
@@ -196,16 +270,31 @@ ORDER BY
|
||||
isNative
|
||||
defaultValue="36"
|
||||
options={[
|
||||
{ value: '36', label: _t('settings.dataGrid.coloringMode.36', { defaultMessage: 'Every 3rd and 6th row' }) },
|
||||
{ value: '2-primary', label: _t('settings.dataGrid.coloringMode.2-primary', { defaultMessage: 'Every 2-nd row, primary color' }) },
|
||||
{ value: '2-secondary', label: _t('settings.dataGrid.coloringMode.2-secondary', { defaultMessage: 'Every 2-nd row, secondary color' }) },
|
||||
{
|
||||
value: '36',
|
||||
label: _t('settings.dataGrid.coloringMode.36', { defaultMessage: 'Every 3rd and 6th row' }),
|
||||
},
|
||||
{
|
||||
value: '2-primary',
|
||||
label: _t('settings.dataGrid.coloringMode.2-primary', {
|
||||
defaultMessage: 'Every 2-nd row, primary color',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: '2-secondary',
|
||||
label: _t('settings.dataGrid.coloringMode.2-secondary', {
|
||||
defaultMessage: 'Every 2-nd row, secondary color',
|
||||
}),
|
||||
},
|
||||
{ value: 'none', label: _t('settings.dataGrid.coloringMode.none', { defaultMessage: 'None' }) },
|
||||
]}
|
||||
/>
|
||||
|
||||
<FormCheckboxField
|
||||
name="dataGrid.showAllColumnsWhenSearch"
|
||||
label={_t('settings.dataGrid.showAllColumnsWhenSearch', { defaultMessage: 'Show all columns when searching' })}
|
||||
label={_t('settings.dataGrid.showAllColumnsWhenSearch', {
|
||||
defaultMessage: 'Show all columns when searching',
|
||||
})}
|
||||
defaultValue={false}
|
||||
/>
|
||||
|
||||
@@ -225,7 +314,10 @@ ORDER BY
|
||||
/>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<FormFieldTemplateLarge label={_t('settings.editor.keybinds', { defaultMessage: 'Editor keybinds' })} type="combo">
|
||||
<FormFieldTemplateLarge
|
||||
label={_t('settings.editor.keybinds', { defaultMessage: 'Editor keybinds' })}
|
||||
type="combo"
|
||||
>
|
||||
<SelectField
|
||||
isNative
|
||||
defaultValue="default"
|
||||
@@ -236,7 +328,10 @@ ORDER BY
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<FormFieldTemplateLarge label={_t('settings.editor.wordWrap', { defaultMessage: 'Enable word wrap' })} type="combo">
|
||||
<FormFieldTemplateLarge
|
||||
label={_t('settings.editor.wordWrap', { defaultMessage: 'Enable word wrap' })}
|
||||
type="combo"
|
||||
>
|
||||
<CheckboxField
|
||||
checked={$currentEditorWrapEnabled}
|
||||
on:change={e => ($currentEditorWrapEnabled = e.target.checked)}
|
||||
@@ -253,7 +348,9 @@ ORDER BY
|
||||
|
||||
<FormCheckboxField
|
||||
name="sqlEditor.showTableAliasesInCodeCompletion"
|
||||
label={_t('settings.sqlEditor.showTableAliasesInCodeCompletion', { defaultMessage: 'Show table aliases in code completion' })}
|
||||
label={_t('settings.sqlEditor.showTableAliasesInCodeCompletion', {
|
||||
defaultMessage: 'Show table aliases in code completion',
|
||||
})}
|
||||
defaultValue={false}
|
||||
/>
|
||||
|
||||
@@ -265,7 +362,9 @@ ORDER BY
|
||||
|
||||
<FormCheckboxField
|
||||
name="sqlEditor.disableExecuteCurrentLine"
|
||||
label={_t('settings.sqlEditor.disableExecuteCurrentLine', { defaultMessage: 'Disable current line execution (Execute current)' })}
|
||||
label={_t('settings.sqlEditor.disableExecuteCurrentLine', {
|
||||
defaultMessage: 'Disable current line execution (Execute current)',
|
||||
})}
|
||||
defaultValue={false}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
@@ -273,7 +372,9 @@ ORDER BY
|
||||
<div class="heading">{_t('settings.connection', { defaultMessage: 'Connection' })}</div>
|
||||
|
||||
<FormFieldTemplateLarge
|
||||
label={_t('settings.connection.showOnlyTabsFromSelectedDatabase', { defaultMessage: 'Show only tabs from selected database' })}
|
||||
label={_t('settings.connection.showOnlyTabsFromSelectedDatabase', {
|
||||
defaultMessage: 'Show only tabs from selected database',
|
||||
})}
|
||||
type="checkbox"
|
||||
labelProps={{
|
||||
onClick: () => {
|
||||
@@ -286,12 +387,16 @@ ORDER BY
|
||||
|
||||
<FormCheckboxField
|
||||
name="connection.autoRefresh"
|
||||
label={_t('settings.connection.autoRefresh', { defaultMessage: 'Automatic refresh of database model on background' })}
|
||||
label={_t('settings.connection.autoRefresh', {
|
||||
defaultMessage: 'Automatic refresh of database model on background',
|
||||
})}
|
||||
defaultValue={false}
|
||||
/>
|
||||
<FormTextField
|
||||
name="connection.autoRefreshInterval"
|
||||
label={_t('settings.connection.autoRefreshInterval', { defaultMessage: 'Interval between automatic DB structure reloads in seconds' })}
|
||||
label={_t('settings.connection.autoRefreshInterval', {
|
||||
defaultMessage: 'Interval between automatic DB structure reloads in seconds',
|
||||
})}
|
||||
defaultValue="30"
|
||||
disabled={values['connection.autoRefresh'] === false}
|
||||
/>
|
||||
@@ -310,12 +415,16 @@ ORDER BY
|
||||
<div class="heading">{_t('settings.session', { defaultMessage: 'Query sessions' })}</div>
|
||||
<FormCheckboxField
|
||||
name="session.autoClose"
|
||||
label={_t('settings.session.autoClose', { defaultMessage: 'Automatic close query sessions after period without any activity' })}
|
||||
label={_t('settings.session.autoClose', {
|
||||
defaultMessage: 'Automatic close query sessions after period without any activity',
|
||||
})}
|
||||
defaultValue={true}
|
||||
/>
|
||||
<FormTextField
|
||||
name="session.autoCloseTimeout"
|
||||
label={_t('settings.session.autoCloseTimeout', { defaultMessage: 'Interval, after which query session without activity is closed (in minutes)' })}
|
||||
label={_t('settings.session.autoCloseTimeout', {
|
||||
defaultMessage: 'Interval, after which query session without activity is closed (in minutes)',
|
||||
})}
|
||||
defaultValue="15"
|
||||
disabled={values['session.autoClose'] === false}
|
||||
/>
|
||||
@@ -356,16 +465,23 @@ ORDER BY
|
||||
</div>
|
||||
|
||||
<div class="m-5">
|
||||
{_t('settings.appearance.moreThemes', { defaultMessage: 'More themes are available as' })} <Link onClick={openThemePlugins}>plugins</Link>
|
||||
{_t('settings.appearance.moreThemes', { defaultMessage: 'More themes are available as' })}
|
||||
<Link onClick={openThemePlugins}>plugins</Link>
|
||||
<br />
|
||||
{_t('settings.appearance.afterInstalling', { defaultMessage: 'After installing theme plugin (try search "theme" in available extensions) new themes will be available here.' })}
|
||||
{_t('settings.appearance.afterInstalling', {
|
||||
defaultMessage:
|
||||
'After installing theme plugin (try search "theme" in available extensions) new themes will be available here.',
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div class="heading">{_t('settings.appearance.editorTheme', { defaultMessage: 'Editor theme' })}</div>
|
||||
|
||||
<div class="flex">
|
||||
<div class="col-3">
|
||||
<FormFieldTemplateLarge label={_t('settings.appearance.editorTheme', { defaultMessage: 'Theme' })} type="combo">
|
||||
<FormFieldTemplateLarge
|
||||
label={_t('settings.appearance.editorTheme', { defaultMessage: 'Theme' })}
|
||||
type="combo"
|
||||
>
|
||||
<SelectField
|
||||
isNative
|
||||
notSelected={_t('settings.appearance.editorTheme.default', { defaultMessage: '(use theme default)' })}
|
||||
@@ -377,7 +493,10 @@ ORDER BY
|
||||
</div>
|
||||
|
||||
<div class="col-3">
|
||||
<FormFieldTemplateLarge label={_t('settings.appearance.fontSize', { defaultMessage: 'Font size' })} type="combo">
|
||||
<FormFieldTemplateLarge
|
||||
label={_t('settings.appearance.fontSize', { defaultMessage: 'Font size' })}
|
||||
type="combo"
|
||||
>
|
||||
<SelectField
|
||||
isNative
|
||||
notSelected="(default)"
|
||||
@@ -389,7 +508,10 @@ ORDER BY
|
||||
</div>
|
||||
|
||||
<div class="col-3">
|
||||
<FormFieldTemplateLarge label={_t('settings.appearance.customSize', { defaultMessage: 'Custom size' })} type="text">
|
||||
<FormFieldTemplateLarge
|
||||
label={_t('settings.appearance.customSize', { defaultMessage: 'Custom size' })}
|
||||
type="text"
|
||||
>
|
||||
<TextField
|
||||
value={$currentEditorFontSize == 'custom' ? '' : $currentEditorFontSize}
|
||||
on:change={e => ($currentEditorFontSize = e.target['value'])}
|
||||
@@ -400,7 +522,10 @@ ORDER BY
|
||||
</div>
|
||||
|
||||
<div class="col-3">
|
||||
<FormTextField name="editor.fontFamily" label={_t('settings.appearance.fontFamily', { defaultMessage: 'Editor font family' })} />
|
||||
<FormTextField
|
||||
name="editor.fontFamily"
|
||||
label={_t('settings.appearance.fontFamily', { defaultMessage: 'Editor font family' })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -417,9 +542,20 @@ ORDER BY
|
||||
isNative
|
||||
defaultValue="connect"
|
||||
options={[
|
||||
{ value: 'openDetails', label: _t('settings.defaultActions.connectionClick.openDetails', { defaultMessage: 'Edit / open details' }) },
|
||||
{ value: 'connect', label: _t('settings.defaultActions.connectionClick.connect', { defaultMessage: 'Connect' }) },
|
||||
{ value: 'none', label: _t('settings.defaultActions.connectionClick.none', { defaultMessage: 'Do nothing' }) },
|
||||
{
|
||||
value: 'openDetails',
|
||||
label: _t('settings.defaultActions.connectionClick.openDetails', {
|
||||
defaultMessage: 'Edit / open details',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'connect',
|
||||
label: _t('settings.defaultActions.connectionClick.connect', { defaultMessage: 'Connect' }),
|
||||
},
|
||||
{
|
||||
value: 'none',
|
||||
label: _t('settings.defaultActions.connectionClick.none', { defaultMessage: 'Do nothing' }),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -429,12 +565,22 @@ ORDER BY
|
||||
isNative
|
||||
defaultValue="switch"
|
||||
options={[
|
||||
{ value: 'switch', label: _t('settings.defaultActions.databaseClick.switch', { defaultMessage: 'Switch database' }) },
|
||||
{ value: 'none', label: _t('settings.defaultActions.databaseClick.none', { defaultMessage: 'Do nothing' }) },
|
||||
{
|
||||
value: 'switch',
|
||||
label: _t('settings.defaultActions.databaseClick.switch', { defaultMessage: 'Switch database' }),
|
||||
},
|
||||
{
|
||||
value: 'none',
|
||||
label: _t('settings.defaultActions.databaseClick.none', { defaultMessage: 'Do nothing' }),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<FormCheckboxField name="defaultAction.useLastUsedAction" label={_t('settings.defaultActions.useLastUsedAction', { defaultMessage: 'Use last used action' })} defaultValue={true} />
|
||||
<FormCheckboxField
|
||||
name="defaultAction.useLastUsedAction"
|
||||
label={_t('settings.defaultActions.useLastUsedAction', { defaultMessage: 'Use last used action' })}
|
||||
defaultValue={true}
|
||||
/>
|
||||
|
||||
<FormDefaultActionField
|
||||
label={_t('settings.defaultActions.tableClick', { defaultMessage: 'Table click' })}
|
||||
@@ -470,7 +616,11 @@ ORDER BY
|
||||
<svelte:fragment slot="5">
|
||||
<div class="heading">{_t('settings.behaviour', { defaultMessage: 'Behaviour' })}</div>
|
||||
|
||||
<FormCheckboxField name="behaviour.useTabPreviewMode" label={_t('settings.behaviour.useTabPreviewMode', { defaultMessage: 'Use tab preview mode' })} defaultValue={true} />
|
||||
<FormCheckboxField
|
||||
name="behaviour.useTabPreviewMode"
|
||||
label={_t('settings.behaviour.useTabPreviewMode', { defaultMessage: 'Use tab preview mode' })}
|
||||
defaultValue={true}
|
||||
/>
|
||||
|
||||
<FormCheckboxField
|
||||
name="behaviour.jsonPreviewWrap"
|
||||
@@ -479,28 +629,45 @@ ORDER BY
|
||||
/>
|
||||
|
||||
<div class="tip">
|
||||
<FontIcon icon="img tip" /> {_t('settings.behaviour.singleClickPreview', { defaultMessage: 'When you single-click or select a file in the "Tables, Views, Functions" view, it is shown in a preview mode and reuses an existing tab (preview tab). This is useful if you are quickly browsing tables and don\'t want every visited table to have its own tab. When you start editing the table or use double-click to open the table from the "Tables" view, a new tab is dedicated to that table.' })}
|
||||
<FontIcon icon="img tip" />
|
||||
{_t('settings.behaviour.singleClickPreview', {
|
||||
defaultMessage:
|
||||
'When you single-click or select a file in the "Tables, Views, Functions" view, it is shown in a preview mode and reuses an existing tab (preview tab). This is useful if you are quickly browsing tables and don\'t want every visited table to have its own tab. When you start editing the table or use double-click to open the table from the "Tables" view, a new tab is dedicated to that table.',
|
||||
})}
|
||||
</div>
|
||||
|
||||
<FormCheckboxField
|
||||
name="behaviour.openDetailOnArrows"
|
||||
label={_t('settings.behaviour.openDetailOnArrows', { defaultMessage: 'Open detail on keyboard navigation' })}
|
||||
label={_t('settings.behaviour.openDetailOnArrows', {
|
||||
defaultMessage: 'Open detail on keyboard navigation',
|
||||
})}
|
||||
defaultValue={true}
|
||||
disabled={values['behaviour.useTabPreviewMode'] === false}
|
||||
/>
|
||||
|
||||
<div class="heading">{_t('settings.confirmations', { defaultMessage: 'Confirmations' })}</div>
|
||||
|
||||
<FormCheckboxField name="skipConfirm.tableDataSave" label={_t('settings.confirmations.skipConfirm.tableDataSave', { defaultMessage: 'Skip confirmation when saving table data (SQL)' })} />
|
||||
<FormCheckboxField
|
||||
name="skipConfirm.tableDataSave"
|
||||
label={_t('settings.confirmations.skipConfirm.tableDataSave', {
|
||||
defaultMessage: 'Skip confirmation when saving table data (SQL)',
|
||||
})}
|
||||
/>
|
||||
<FormCheckboxField
|
||||
name="skipConfirm.collectionDataSave"
|
||||
label={_t('settings.confirmations.skipConfirm.collectionDataSave', { defaultMessage: 'Skip confirmation when saving collection data (NoSQL)' })}
|
||||
label={_t('settings.confirmations.skipConfirm.collectionDataSave', {
|
||||
defaultMessage: 'Skip confirmation when saving collection data (NoSQL)',
|
||||
})}
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="6">
|
||||
<div class="heading">{_t('settings.other', { defaultMessage: 'Other' })}</div>
|
||||
|
||||
<FormTextField name="other.gistCreateToken" label={_t('settings.other.gistCreateToken', { defaultMessage: 'API token for creating error gists' })} defaultValue="" />
|
||||
<FormTextField
|
||||
name="other.gistCreateToken"
|
||||
label={_t('settings.other.gistCreateToken', { defaultMessage: 'API token for creating error gists' })}
|
||||
defaultValue=""
|
||||
/>
|
||||
|
||||
<FormSelectField
|
||||
label={_t('settings.other.autoUpdateApplication', { defaultMessage: 'Auto update application' })}
|
||||
@@ -508,16 +675,31 @@ ORDER BY
|
||||
isNative
|
||||
defaultValue=""
|
||||
options={[
|
||||
{ value: 'skip', label: _t('settings.other.autoUpdateApplication.skip', { defaultMessage: 'Do not check for new versions' }) },
|
||||
{ value: '', label: _t('settings.other.autoUpdateApplication.check', { defaultMessage: 'Check for new versions' }) },
|
||||
{ value: 'download', label: _t('settings.other.autoUpdateApplication.download', { defaultMessage: 'Check and download new versions' }) },
|
||||
{
|
||||
value: 'skip',
|
||||
label: _t('settings.other.autoUpdateApplication.skip', {
|
||||
defaultMessage: 'Do not check for new versions',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: '',
|
||||
label: _t('settings.other.autoUpdateApplication.check', { defaultMessage: 'Check for new versions' }),
|
||||
},
|
||||
{
|
||||
value: 'download',
|
||||
label: _t('settings.other.autoUpdateApplication.download', {
|
||||
defaultMessage: 'Check and download new versions',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{#if isProApp()}
|
||||
<FormCheckboxField
|
||||
name="ai.allowSendModels"
|
||||
label={_t('settings.other.ai.allowSendModels', { defaultMessage: 'Allow to send DB models and query snippets to AI service' })}
|
||||
label={_t('settings.other.ai.allowSendModels', {
|
||||
defaultMessage: 'Allow to send DB models and query snippets to AI service',
|
||||
})}
|
||||
defaultValue={false}
|
||||
/>
|
||||
{/if}
|
||||
@@ -537,28 +719,39 @@ ORDER BY
|
||||
<div class="m-3 ml-5">
|
||||
{#if licenseKeyCheckResult.status == 'ok'}
|
||||
<div>
|
||||
<FontIcon icon="img ok" /> { _t('settings.other.licenseKey.valid', { defaultMessage: 'License key is valid' }) }
|
||||
<FontIcon icon="img ok" />
|
||||
{_t('settings.other.licenseKey.valid', { defaultMessage: 'License key is valid' })}
|
||||
</div>
|
||||
{#if licenseKeyCheckResult.validTo}
|
||||
<div>
|
||||
{ _t('settings.other.licenseKey.validTo', { defaultMessage: 'License valid to:' }) } {licenseKeyCheckResult.validTo}
|
||||
{_t('settings.other.licenseKey.validTo', { defaultMessage: 'License valid to:' })}
|
||||
{licenseKeyCheckResult.validTo}
|
||||
</div>
|
||||
{/if}
|
||||
{#if licenseKeyCheckResult.expiration}
|
||||
<div>{ _t('settings.other.licenseKey.expiration', { defaultMessage: 'License key expiration:' }) } <b>{safeFormatDate(licenseKeyCheckResult.expiration)}</b></div>
|
||||
<div>
|
||||
{_t('settings.other.licenseKey.expiration', { defaultMessage: 'License key expiration:' })}
|
||||
<b>{safeFormatDate(licenseKeyCheckResult.expiration)}</b>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if licenseKeyCheckResult.status == 'error'}
|
||||
<div>
|
||||
<FontIcon icon="img error" />
|
||||
{licenseKeyCheckResult.errorMessage ?? _t('settings.other.licenseKey.invalid', { defaultMessage: 'License key is invalid' })}
|
||||
{licenseKeyCheckResult.errorMessage ??
|
||||
_t('settings.other.licenseKey.invalid', { defaultMessage: 'License key is invalid' })}
|
||||
{#if licenseKeyCheckResult.expiration}
|
||||
<div>{ _t('settings.other.licenseKey.expiration', { defaultMessage: 'License key expiration:' }) } <b>{safeFormatDate(licenseKeyCheckResult.expiration)}</b></div>
|
||||
<div>
|
||||
{_t('settings.other.licenseKey.expiration', { defaultMessage: 'License key expiration:' })}
|
||||
<b>{safeFormatDate(licenseKeyCheckResult.expiration)}</b>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if licenseKeyCheckResult.isExpired}
|
||||
<div class="mt-2">
|
||||
<FormStyledButton
|
||||
value={_t('settings.other.licenseKey.checkForNew', { defaultMessage: 'Check for new license key' })}
|
||||
value={_t('settings.other.licenseKey.checkForNew', {
|
||||
defaultMessage: 'Check for new license key',
|
||||
})}
|
||||
skipWidth
|
||||
on:click={async () => {
|
||||
licenseKeyCheckResult = await apiCall('config/get-new-license', { oldLicenseKey: licenseKey });
|
||||
@@ -578,7 +771,9 @@ ORDER BY
|
||||
<div class="heading">{_t('settings.externalTools', { defaultMessage: 'External tools' })}</div>
|
||||
<FormTextField
|
||||
name="externalTools.mysqldump"
|
||||
label={_t('settings.other.externalTools.mysqldump', { defaultMessage: 'mysqldump (backup MySQL database)' })}
|
||||
label={_t('settings.other.externalTools.mysqldump', {
|
||||
defaultMessage: 'mysqldump (backup MySQL database)',
|
||||
})}
|
||||
defaultValue="mysqldump"
|
||||
/>
|
||||
<FormTextField
|
||||
@@ -588,12 +783,17 @@ ORDER BY
|
||||
/>
|
||||
<FormTextField
|
||||
name="externalTools.mysqlPlugins"
|
||||
label={_t('settings.other.externalTools.mysqlPlugins', { defaultMessage: 'Folder with mysql plugins (for example for authentication). Set only in case of problems' })}
|
||||
label={_t('settings.other.externalTools.mysqlPlugins', {
|
||||
defaultMessage:
|
||||
'Folder with mysql plugins (for example for authentication). Set only in case of problems',
|
||||
})}
|
||||
defaultValue=""
|
||||
/>
|
||||
<FormTextField
|
||||
name="externalTools.pg_dump"
|
||||
label={_t('settings.other.externalTools.pg_dump', { defaultMessage: 'pg_dump (backup PostgreSQL database)' })}
|
||||
label={_t('settings.other.externalTools.pg_dump', {
|
||||
defaultMessage: 'pg_dump (backup PostgreSQL database)',
|
||||
})}
|
||||
defaultValue="pg_dump"
|
||||
/>
|
||||
<FormTextField
|
||||
|
||||
@@ -200,6 +200,7 @@ export const DEFAULT_OBJECT_SEARCH_SETTINGS = {
|
||||
sqlObjectText: false,
|
||||
tableEngine: false,
|
||||
tablesWithRows: false,
|
||||
sortBy: undefined as string
|
||||
};
|
||||
|
||||
export const DEFAULT_CONNECTION_SEARCH_SETTINGS = {
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
<FormProvider>
|
||||
<ModalBase {...$$restProps}>
|
||||
<svelte:fragment slot="header"
|
||||
>{constraintInfo ? _t('tableEdit.editConstraintLabel', { defaultMessage: 'Edit {constraintLabel}', values: { constraintLabel: _.isFunction(constraintLabel) ? constraintLabel() : constraintLabel } }) : _t('tableEdit.addConstraintLabel', { defaultMessage: 'Add {constraintLabel}', values: { constraintLabel: _.isFunction(constraintLabel) ? constraintLabel() : constraintLabel } })}</svelte:fragment
|
||||
>{constraintInfo ? _t('tableEdit.editConstraintLabel', { defaultMessage: 'Edit {constraintLabel}', values: { constraintLabel } }) : _t('tableEdit.addConstraintLabel', { defaultMessage: 'Add {constraintLabel}', values: { constraintLabel } })}</svelte:fragment
|
||||
>
|
||||
|
||||
<div class="largeFormMarker">
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script lang="ts" context="module">
|
||||
import { _t } from '../translations';
|
||||
|
||||
const getCurrentValueMarker: any = {};
|
||||
|
||||
export function shouldShowTab(tab, lockedDbModeArg = getCurrentValueMarker, currentDbArg = getCurrentValueMarker) {
|
||||
@@ -239,8 +237,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'tabs.nextTab',
|
||||
category: 'Tabs',
|
||||
name: _t('command.tabs.nextTab', { defaultMessage: 'Next tab' }),
|
||||
category: __t('command.tabs', { defaultMessage: 'Tabs' }),
|
||||
name: __t('command.tabs.nextTab', { defaultMessage: 'Next tab' }),
|
||||
keyText: 'Ctrl+Tab',
|
||||
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 2,
|
||||
onClick: () => switchTabByOrder(false),
|
||||
@@ -248,8 +246,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'tabs.previousTab',
|
||||
category: 'Tabs',
|
||||
name: _t('command.tabs.previousTab', { defaultMessage: 'Previous tab' }),
|
||||
category: __t('command.tabs', { defaultMessage: 'Tabs' }),
|
||||
name: __t('command.tabs.previousTab', { defaultMessage: 'Previous tab' }),
|
||||
keyText: 'Ctrl+Shift+Tab',
|
||||
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 2,
|
||||
onClick: () => switchTabByOrder(true),
|
||||
@@ -257,16 +255,16 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'tabs.closeAll',
|
||||
category: 'Tabs',
|
||||
name: _t('command.tabs.closeAll', { defaultMessage: 'Close all tabs' }),
|
||||
category: __t('command.tabs', { defaultMessage: 'Tabs' }),
|
||||
name: __t('command.tabs.closeAll', { defaultMessage: 'Close all tabs' }),
|
||||
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 1,
|
||||
onClick: closeAll,
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'tabs.closeTab',
|
||||
category: 'Tabs',
|
||||
name: _t('command.tabs.closeTab', { defaultMessage: 'Close tab' }),
|
||||
category: __t('command.tabs', { defaultMessage: 'Tabs' }),
|
||||
name: __t('command.tabs.closeTab', { defaultMessage: 'Close tab' }),
|
||||
keyText: isElectronAvailable() ? 'CtrlOrCommand+W' : 'Alt+W',
|
||||
testEnabled: () => {
|
||||
const hasAnyOtherTab = getOpenedTabs().filter(x => !x.closedTime).length >= 1;
|
||||
@@ -279,24 +277,24 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'tabs.closeTabsWithCurrentDb',
|
||||
category: 'Tabs',
|
||||
name: _t('command.tabs.closeTabsWithCurrentDb', { defaultMessage: 'Close tabs with current DB' }),
|
||||
category: __t('command.tabs', { defaultMessage: 'Tabs' }),
|
||||
name: __t('command.tabs.closeTabsWithCurrentDb', { defaultMessage: 'Close tabs with current DB' }),
|
||||
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 1 && !!getCurrentDatabase(),
|
||||
onClick: closeTabsWithCurrentDb,
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'tabs.closeTabsButCurrentDb',
|
||||
category: 'Tabs',
|
||||
name: _t('command.tabs.closeTabsButCurrentDb', { defaultMessage: 'Close tabs but current DB' }),
|
||||
category: __t('command.tabs', { defaultMessage: 'Tabs' }),
|
||||
name: __t('command.tabs.closeTabsButCurrentDb', { defaultMessage: 'Close tabs but current DB' }),
|
||||
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 1 && !!getCurrentDatabase(),
|
||||
onClick: closeTabsButCurrentDb,
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'tabs.reopenClosedTab',
|
||||
category: 'Tabs',
|
||||
name: _t('command.tabs.reopenClosedTab', { defaultMessage: 'Reopen closed tab' }),
|
||||
category: __t('command.tabs', { defaultMessage: 'Tabs' }),
|
||||
name: __t('command.tabs.reopenClosedTab', { defaultMessage: 'Reopen closed tab' }),
|
||||
keyText: 'CtrlOrCommand+Shift+T',
|
||||
testEnabled: () => getOpenedTabs().filter(x => x.closedTime).length >= 1,
|
||||
onClick: reopenClosedTab,
|
||||
@@ -304,8 +302,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'tabs.addToFavorites',
|
||||
category: 'Tabs',
|
||||
name: _t('command.tabs.addToFavorites', { defaultMessage: 'Add current tab to favorites' }),
|
||||
category: __t('command.tabs', { defaultMessage: 'Tabs' }),
|
||||
name: __t('command.tabs.addToFavorites', { defaultMessage: 'Add current tab to favorites' }),
|
||||
// icon: 'icon favorite',
|
||||
// toolbar: true,
|
||||
testEnabled: () =>
|
||||
@@ -360,6 +358,7 @@
|
||||
import NewObjectModal from '../modals/NewObjectModal.svelte';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
import { openWebLink } from '../utility/simpleTools';
|
||||
import { __t, _t } from '../translations';
|
||||
|
||||
export let multiTabIndex;
|
||||
export let shownTab;
|
||||
@@ -436,27 +435,27 @@
|
||||
|
||||
return [
|
||||
tab.tabPreviewMode && {
|
||||
text: 'Pin tab',
|
||||
text: _t('tabsPanel.pinTab', { defaultMessage: 'Pin tab' }),
|
||||
onClick: () => pinTab(tabid),
|
||||
},
|
||||
{
|
||||
text: 'Close',
|
||||
text: _t('common.close', { defaultMessage: 'Close' }),
|
||||
onClick: () => closeTab(tabid),
|
||||
},
|
||||
{
|
||||
text: 'Close all',
|
||||
text: _t('tabsPanel.closeAll', { defaultMessage: 'Close all' }),
|
||||
onClick: () => closeAll(multiTabIndex),
|
||||
},
|
||||
{
|
||||
text: 'Close others',
|
||||
text: _t('tabsPanel.closeOthers', { defaultMessage: 'Close others' }),
|
||||
onClick: () => closeOthersInMultiTab(multiTabIndex)(tabid),
|
||||
},
|
||||
{
|
||||
text: 'Close to the right',
|
||||
text: _t('tabsPanel.closeToTheRight', { defaultMessage: 'Close to the right' }),
|
||||
onClick: () => closeRightTabs(multiTabIndex)(tabid),
|
||||
},
|
||||
{
|
||||
text: 'Duplicate',
|
||||
text: _t('tabsPanel.duplicate', { defaultMessage: 'Duplicate' }),
|
||||
onClick: () => duplicateTab(tab),
|
||||
},
|
||||
tabComponent &&
|
||||
@@ -465,7 +464,7 @@
|
||||
tabs[tabComponent].allowAddToFavorites(props) && [
|
||||
{ divider: true },
|
||||
{
|
||||
text: 'Add to favorites',
|
||||
text: _t('tabsPanel.addToFavorites', { defaultMessage: 'Add to favorites' }),
|
||||
onClick: () => showModal(FavoriteModal, { savingTab: tab }),
|
||||
},
|
||||
],
|
||||
@@ -475,7 +474,7 @@
|
||||
tabs[tabComponent].allowSwitchDatabase(props) && [
|
||||
{ divider: true },
|
||||
{
|
||||
text: 'Switch database',
|
||||
text: _t('tabsPanel.switchDatabase', { defaultMessage: 'Switch database' }),
|
||||
onClick: () => showModal(SwitchDatabaseModal, { callingTab: tab }),
|
||||
},
|
||||
],
|
||||
@@ -498,11 +497,17 @@
|
||||
conid &&
|
||||
database && [
|
||||
{
|
||||
text: `Close tabs with DB ${database}`,
|
||||
text: _t('tabsPanel.closeTabsWithDb', {
|
||||
defaultMessage: 'Close tabs with DB {database}',
|
||||
values: { database },
|
||||
}),
|
||||
onClick: () => closeWithSameDb(tabid),
|
||||
},
|
||||
{
|
||||
text: `Close tabs with other DB than ${database}`,
|
||||
text: _t('tabsPanel.closeTabsWithOtherDb', {
|
||||
defaultMessage: `Close tabs with other DB than {database}`,
|
||||
values: { database },
|
||||
}),
|
||||
onClick: () => closeWithOtherDb(tabid),
|
||||
},
|
||||
],
|
||||
@@ -577,9 +582,19 @@
|
||||
let domTabs;
|
||||
|
||||
function handleTabsWheel(e) {
|
||||
if (!e.shiftKey) {
|
||||
// if (!e.shiftKey) {
|
||||
// e.preventDefault();
|
||||
// domTabs.scrollBy({ top: 0, left: e.deltaY < 0 ? -150 : 150, behavior: 'smooth' });
|
||||
// }
|
||||
|
||||
// Handle horizontal scrolling from trackpad gestures (deltaX)
|
||||
// or vertical scrolling converted to horizontal (deltaY with Shift)
|
||||
|
||||
const scrollAmount = Math.abs(e.deltaX) > Math.abs(e.deltaY) ? e.deltaX : e.shiftKey ? 0 : e.deltaY;
|
||||
|
||||
if (scrollAmount !== 0) {
|
||||
e.preventDefault();
|
||||
domTabs.scrollBy({ top: 0, left: e.deltaY < 0 ? -150 : 150, behavior: 'smooth' });
|
||||
domTabs.scrollBy({ left: scrollAmount, behavior: 'auto' });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -743,7 +758,7 @@
|
||||
title="Upgrade to Premium"
|
||||
data-testid="TabsPanel_buttonUpgrade"
|
||||
>
|
||||
<FontIcon icon="icon premium" padRight /> Upgrade
|
||||
<FontIcon icon="icon premium" /> Upgrade
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -788,6 +803,9 @@
|
||||
cursor: pointer;
|
||||
font-size: 10pt;
|
||||
padding: 5px;
|
||||
margin-top: 3px;
|
||||
margin-right: 3px;
|
||||
font-size: 8pt;
|
||||
}
|
||||
.upgrade-button:hover {
|
||||
background: linear-gradient(135deg, #0f5a85, #5c1870);
|
||||
@@ -807,7 +825,7 @@
|
||||
}
|
||||
|
||||
.tabs-upgrade-button {
|
||||
right: 120px;
|
||||
right: 110px;
|
||||
}
|
||||
.tabs.can-split {
|
||||
right: 60px;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
registerCommand({
|
||||
id: 'archiveFile.save',
|
||||
group: 'save',
|
||||
category: 'Archive file',
|
||||
name: 'Save',
|
||||
category: __t('command.archiveFile', { defaultMessage: 'Archive file' }),
|
||||
name: __t('command.archiveFile.save', { defaultMessage: 'Save' }),
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
icon: 'icon save',
|
||||
@@ -17,8 +17,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'archiveFile.saveAs',
|
||||
category: 'Archive file',
|
||||
name: 'Save as',
|
||||
category: __t('command.archiveFile', { defaultMessage: 'Archive file' }),
|
||||
name: __t('command.archiveFile.saveAs', { defaultMessage: 'Save as' }),
|
||||
icon: 'icon save',
|
||||
isRelatedToTab: true,
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
@@ -49,6 +49,7 @@
|
||||
import { changeTab, markTabSaved, markTabUnsaved, sleep } from '../utility/common';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import createUndoReducer from '../utility/createUndoReducer';
|
||||
import { __t } from '../translations';
|
||||
|
||||
export const activator = createActivator('ArchiveFileTab', true);
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
registerCommand({
|
||||
id: 'collectionTable.save',
|
||||
group: 'save',
|
||||
category: 'Collection data',
|
||||
name: 'Save',
|
||||
category: __t('command.collectionData', { defaultMessage: 'Collection data' }),
|
||||
name: __t('command.collectionData.save', { defaultMessage: 'Save' }),
|
||||
// keyText: 'CtrlOrCommand+S',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -56,6 +56,7 @@
|
||||
import useEditorData from '../query/useEditorData';
|
||||
import { markTabSaved, markTabUnsaved } from '../utility/common';
|
||||
import { getNumberIcon } from '../icons/FontIcon.svelte';
|
||||
import { __t } from '../translations';
|
||||
|
||||
export let tabid;
|
||||
export let conid;
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
|
||||
registerFileCommands({
|
||||
idPrefix: 'diagram',
|
||||
category: 'Diagram',
|
||||
category: __t('command.diagram', { defaultMessage: 'Diagram' }),
|
||||
getCurrentEditor,
|
||||
folder: 'diagrams',
|
||||
format: 'json',
|
||||
fileExtension: 'diagram',
|
||||
defaultTeamFolder: true,
|
||||
|
||||
undoRedo: true,
|
||||
});
|
||||
@@ -32,6 +33,7 @@
|
||||
import DiagramSettings from '../designer/DiagramSettings.svelte';
|
||||
import { derived } from 'svelte/store';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
import { __t } from '../translations';
|
||||
|
||||
export let tabid;
|
||||
export let conid;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
registerFileCommands({
|
||||
idPrefix: 'favoriteJsonEditor',
|
||||
category: 'Favorite JSON editor',
|
||||
category: __t('command.favoriteJsonEditor', { defaultMessage: 'Favorite JSON editor' }),
|
||||
getCurrentEditor,
|
||||
folder: null,
|
||||
format: null,
|
||||
@@ -15,15 +15,15 @@
|
||||
registerCommand({
|
||||
id: 'favoriteJsonEditor.save',
|
||||
group: 'save',
|
||||
name: 'Save',
|
||||
category: 'Favorite JSON editor',
|
||||
name: __t('command.favoriteJsonEditor.save', { defaultMessage: 'Save' }),
|
||||
category: __t('command.favoriteJsonEditor', { defaultMessage: 'Favorite JSON editor' }),
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().save(),
|
||||
});
|
||||
registerCommand({
|
||||
id: 'favoriteJsonEditor.preview',
|
||||
name: 'Preview',
|
||||
category: 'Favorite JSON editor',
|
||||
name: __t('command.favoriteJsonEditor.preview', { defaultMessage: 'Preview' }),
|
||||
category: __t('command.favoriteJsonEditor', { defaultMessage: 'Favorite JSON editor' }),
|
||||
keyText: 'F5 | CtrlOrCommand+Enter',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().preview(),
|
||||
@@ -43,6 +43,7 @@
|
||||
import { openFavorite } from '../appobj/FavoriteFileAppObject.svelte';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { __t } from '../translations';
|
||||
|
||||
export let tabid;
|
||||
export let savedFile;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
id: 'jsonl.save',
|
||||
group: 'save',
|
||||
category: 'JSON Lines editor',
|
||||
name: 'Save',
|
||||
name: __t('command.jsonl.save', { defaultMessage: 'Save' }),
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
icon: 'icon save',
|
||||
@@ -28,7 +28,7 @@
|
||||
registerCommand({
|
||||
id: 'jsonl.preview',
|
||||
category: 'JSON Lines editor',
|
||||
name: 'Preview',
|
||||
name: __t('command.jsonl.preview', { defaultMessage: 'Preview' }),
|
||||
icon: 'icon preview',
|
||||
keyText: 'F5',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
@@ -38,7 +38,7 @@
|
||||
registerCommand({
|
||||
id: 'jsonl.previewNewTab',
|
||||
category: 'JSON Lines editor',
|
||||
name: 'Preview in new tab',
|
||||
name: __t('command.jsonl.previewNewTab', { defaultMessage: 'Preview in new tab' }),
|
||||
icon: 'icon preview',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().previewMewTab(),
|
||||
@@ -47,7 +47,7 @@
|
||||
registerCommand({
|
||||
id: 'jsonl.closePreview',
|
||||
category: 'JSON Lines editor',
|
||||
name: 'Close preview',
|
||||
name: __t('command.jsonl.closePreview', { defaultMessage: 'Close preview' }),
|
||||
icon: 'icon close',
|
||||
testEnabled: () => getCurrentEditor()?.isPreview(),
|
||||
onClick: () => getCurrentEditor().closePreview(),
|
||||
@@ -74,6 +74,7 @@
|
||||
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
|
||||
import JslDataGrid from '../datagrid/JslDataGrid.svelte';
|
||||
import ToolStripCommandSplitButton from '../buttons/ToolStripCommandSplitButton.svelte';
|
||||
import { __t } from '../translations';
|
||||
|
||||
export let tabid;
|
||||
export let archiveFolder;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
registerCommand({
|
||||
id: 'markdown.preview',
|
||||
category: 'Markdown',
|
||||
name: 'Preview',
|
||||
name: __t('command.markdown.preview', { defaultMessage: 'Preview' }),
|
||||
icon: 'icon run',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -38,6 +38,7 @@
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import { setSelectedTab } from '../utility/common';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import { __t } from '../translations';
|
||||
|
||||
export let tabid;
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'queryData.stopLoading',
|
||||
category: 'Query data',
|
||||
name: 'Stop loading',
|
||||
category: __t('command.queryData', { defaultMessage: 'Query data' }),
|
||||
name: __t('command.queryData.stopLoading', { defaultMessage: 'Stop loading' }),
|
||||
icon: 'icon stop',
|
||||
testEnabled: () => getCurrentEditor()?.isLoading(),
|
||||
onClick: () => getCurrentEditor().stopLoading(),
|
||||
@@ -32,6 +32,7 @@
|
||||
import yaml from 'js-yaml';
|
||||
import JslChart from '../charts/JslChart.svelte';
|
||||
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
||||
import { __t } from '../translations';
|
||||
|
||||
export const activator = createActivator('QueryDataTab', true);
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
findReplace: true,
|
||||
executeAdditionalCondition: () => getCurrentEditor()?.hasConnection() && hasPermission('dbops/query'),
|
||||
copyPaste: true,
|
||||
defaultTeamFolder: true,
|
||||
});
|
||||
registerCommand({
|
||||
id: 'query.executeCurrent',
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'serverSummary.refresh',
|
||||
category: 'Server sumnmary',
|
||||
name: _t('common.refresh', { defaultMessage: 'Refresh' }),
|
||||
category: __t('command.serverSummary', { defaultMessage: 'Server summary' }),
|
||||
name: __t('common.refresh', { defaultMessage: 'Refresh' }),
|
||||
keyText: 'F5 | CtrlOrCommand+R',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -21,7 +21,7 @@
|
||||
import LoadingInfo from '../elements/LoadingInfo.svelte';
|
||||
import TabControl from '../elements/TabControl.svelte';
|
||||
|
||||
import { _t } from '../translations';
|
||||
import { _t, __t } from '../translations';
|
||||
import { apiCall } from '../utility/api';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
registerCommand({
|
||||
id: 'shell.copyNodeScript',
|
||||
category: 'Shell',
|
||||
name: 'Copy nodejs script',
|
||||
name: __t('command.shell.copyNodeScript', { defaultMessage: 'Copy nodejs script' }),
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().copyNodeScript(),
|
||||
});
|
||||
@@ -49,6 +49,7 @@
|
||||
import { showSnackbarError } from '../utility/snackbar';
|
||||
import useEffect from '../utility/useEffect';
|
||||
import useTimerLabel from '../utility/useTimerLabel';
|
||||
import { __t } from '../translations';
|
||||
|
||||
export let tabid;
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'sqlObject.find',
|
||||
category: 'SQL Object',
|
||||
name: 'Find',
|
||||
category: __t('command.sqlObject', { defaultMessage: 'SQL Object' }),
|
||||
name: __t('command.sqlObject.find', { defaultMessage: 'Find' }),
|
||||
keyText: 'CtrlOrCommand+F',
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => getCurrentEditor().find(),
|
||||
@@ -31,6 +31,7 @@
|
||||
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import { getBoolSettingsValue } from '../settings/settingsTools';
|
||||
import { __t } from '../translations';
|
||||
|
||||
export let tabid;
|
||||
export let appObjectData;
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
import cs from '../../../translations/cs.json';
|
||||
import sk from '../../../translations/sk.json';
|
||||
import de from '../../../translations/de.json';
|
||||
import fr from '../../../translations/fr.json';
|
||||
import es from '../../../translations/es.json';
|
||||
import zh from '../../../translations/zh.json';
|
||||
import pt from '../../../translations/pt.json';
|
||||
import it from '../../../translations/it.json';
|
||||
import ja from '../../../translations/ja.json';
|
||||
|
||||
import MessageFormat, { MessageFunction } from '@messageformat/core';
|
||||
import { getStringSettingsValue } from './settings/settingsTools';
|
||||
import getElectron from './utility/getElectron';
|
||||
import { apiCall } from './utility/api';
|
||||
|
||||
const translations = {
|
||||
en: {},
|
||||
cs,
|
||||
sk,
|
||||
de,
|
||||
fr,
|
||||
zh,
|
||||
es,
|
||||
pt,
|
||||
it,
|
||||
ja,
|
||||
};
|
||||
const supportedLanguages = Object.keys(translations);
|
||||
|
||||
@@ -17,28 +33,40 @@ const defaultLanguage = 'en';
|
||||
|
||||
let selectedLanguageCache: string | null = null;
|
||||
|
||||
export function getSelectedLanguage(): string {
|
||||
export function getSelectedLanguage(preferrendLanguage?: string): string {
|
||||
if (selectedLanguageCache) return selectedLanguageCache;
|
||||
|
||||
const browserLanguage = getBrowserLanguage();
|
||||
const selectedLanguage = getStringSettingsValue('localization.language', browserLanguage);
|
||||
if (preferrendLanguage == 'auto') {
|
||||
preferrendLanguage = getBrowserLanguage();
|
||||
}
|
||||
|
||||
if (!supportedLanguages.includes(selectedLanguage)) return defaultLanguage;
|
||||
const selectedLanguage = getElectron()
|
||||
? getStringSettingsValue('localization.language', preferrendLanguage)
|
||||
: localStorage.getItem('selectedLanguage') ?? preferrendLanguage;
|
||||
|
||||
if (!selectedLanguage || !supportedLanguages.includes(selectedLanguage)) return defaultLanguage;
|
||||
return selectedLanguage;
|
||||
}
|
||||
|
||||
export function saveSelectedLanguageToCache() {
|
||||
selectedLanguageCache = getSelectedLanguage();
|
||||
export async function setSelectedLanguage(language: string) {
|
||||
if (getElectron()) {
|
||||
await apiCall('config/update-settings', { 'localization.language': language });
|
||||
} else {
|
||||
localStorage.setItem('selectedLanguage', language);
|
||||
}
|
||||
}
|
||||
|
||||
export function saveSelectedLanguageToCache(preferrendLanguage?: string) {
|
||||
selectedLanguageCache = getSelectedLanguage(preferrendLanguage);
|
||||
}
|
||||
|
||||
export function getBrowserLanguage(): string {
|
||||
return 'en';
|
||||
// if (typeof window !== 'undefined') {
|
||||
// return (
|
||||
// (navigator.languages && navigator.languages[0]).slice(0, 2) || navigator.language.slice(0, 2) || defaultLanguage
|
||||
// );
|
||||
// }
|
||||
// return defaultLanguage;
|
||||
if (typeof window !== 'undefined') {
|
||||
return (
|
||||
(navigator.languages && navigator.languages[0]).slice(0, 2) || navigator.language.slice(0, 2) || defaultLanguage
|
||||
);
|
||||
}
|
||||
return defaultLanguage;
|
||||
}
|
||||
|
||||
type TranslateOptions = {
|
||||
@@ -58,8 +86,13 @@ function getTranslation(key: string, defaultMessage: string, language: string) {
|
||||
return translation;
|
||||
}
|
||||
|
||||
export function getCurrentTranslations(): Record<string, string> {
|
||||
const selectedLanguage = getSelectedLanguage();
|
||||
return translations[selectedLanguage] || {};
|
||||
}
|
||||
|
||||
export function _t(key: string, options: TranslateOptions): string {
|
||||
const { defaultMessage, values } = options;
|
||||
const { defaultMessage, values } = options || {};
|
||||
|
||||
const selectedLanguage = getSelectedLanguage();
|
||||
|
||||
@@ -78,10 +111,30 @@ export function _t(key: string, options: TranslateOptions): string {
|
||||
return compliledTranslation(values ?? {});
|
||||
}
|
||||
|
||||
export function __t(key: string, options: TranslateOptions): () => string {
|
||||
return () => _t(key, options);
|
||||
export type DefferedTranslationResult = {
|
||||
_transKey?: string;
|
||||
_transOptions?: TranslateOptions;
|
||||
_transCallback?: () => string;
|
||||
};
|
||||
|
||||
export function __t(key: string, options: TranslateOptions): DefferedTranslationResult {
|
||||
return {
|
||||
_transKey: key,
|
||||
_transOptions: options,
|
||||
};
|
||||
}
|
||||
|
||||
export function _val<T>(x: T | (() => T)): T {
|
||||
return typeof x === 'function' ? (x as () => T)() : x;
|
||||
export function _tval(x: string | DefferedTranslationResult): string {
|
||||
if (typeof x === 'string') return x;
|
||||
if (typeof x?._transKey === 'string') {
|
||||
return _t(x._transKey, x._transOptions);
|
||||
}
|
||||
if (typeof x?._transCallback === 'function') {
|
||||
return x._transCallback();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
export function isDefferedTranslationResult(x: string | DefferedTranslationResult): x is DefferedTranslationResult {
|
||||
return typeof x !== 'string' && typeof x?._transKey === 'string';
|
||||
}
|
||||
|
||||
@@ -186,6 +186,7 @@ export async function apiCall(
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-session-id': getApiSessionId(),
|
||||
'x-ui-language': localStorage.getItem('selectedLanguage') || 'en',
|
||||
...resolveApiHeaders(),
|
||||
},
|
||||
body: JSON.stringify(args, serializeJsTypesReplacer),
|
||||
|
||||
@@ -3,6 +3,7 @@ import { currentDatabase, getExtensions, getOpenedTabs, loadingSchemaLists, open
|
||||
import _ from 'lodash';
|
||||
import { getSchemaList } from './metadataLoaders';
|
||||
import { showSnackbarError } from './snackbar';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export class LoadingToken {
|
||||
isCanceled = false;
|
||||
@@ -57,8 +58,14 @@ export function setSelectedTab(tabid) {
|
||||
}
|
||||
|
||||
export function getObjectTypeFieldLabel(objectTypeField, driver?) {
|
||||
if (objectTypeField == 'matviews') return 'Materialized Views';
|
||||
if (objectTypeField == 'collections') return _.startCase(driver?.collectionPluralLabel) ?? 'Collections/Containers';
|
||||
if (objectTypeField == 'tables') return _t('dbObject.tables', { defaultMessage: 'Tables' });
|
||||
if (objectTypeField == 'views') return _t('dbObject.views', { defaultMessage: 'Views' });
|
||||
if (objectTypeField == 'procedures') return _t('dbObject.procedures', { defaultMessage: 'Procedures' });
|
||||
if (objectTypeField == 'functions') return _t('dbObject.functions', { defaultMessage: 'Functions' });
|
||||
if (objectTypeField == 'triggers') return _t('dbObject.triggers', { defaultMessage: 'Triggers' });
|
||||
if (objectTypeField == 'schedulerEvents') return _t('dbObject.schedulerEvents', { defaultMessage: 'Scheduler Events' });
|
||||
if (objectTypeField == 'matviews') return _t('dbObject.matviews', { defaultMessage: 'Materialized Views' });
|
||||
if (objectTypeField == 'collections') return _t('dbObject.collections', { defaultMessage: 'Collections/Containers' });
|
||||
return _.startCase(objectTypeField);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import invalidateCommands from '../commands/invalidateCommands';
|
||||
import { runGroupCommand } from '../commands/runCommand';
|
||||
import { currentDropDownMenu, visibleCommandPalette } from '../stores';
|
||||
import getAsArray from './getAsArray';
|
||||
import { _tval } from '../translations';
|
||||
|
||||
let isContextMenuSupressed = false;
|
||||
|
||||
@@ -114,7 +115,7 @@ function mapItem(item, commands) {
|
||||
if (command) {
|
||||
const commandText = item.text || command.menuName || command.toolbarName || command.name;
|
||||
return {
|
||||
text: _.isFunction(commandText) ? commandText() : commandText,
|
||||
text: _tval(commandText),
|
||||
keyText: command.keyText || command.keyTextFromGroup || command.disableHandleKeyText,
|
||||
onClick: () => {
|
||||
if (command.isGroupCommand) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import getElectron from './getElectron';
|
||||
// return derived(editorStore, editor => editor != null);
|
||||
// }
|
||||
|
||||
export default async function saveTabFile(editor, saveMode, folder, format, fileExtension) {
|
||||
export default async function saveTabFile(editor, saveMode, folder, format, fileExtension, defaultTeamFolder) {
|
||||
const tabs = get(openedTabs);
|
||||
const tabid = editor.activator.tabid;
|
||||
const data = editor.getData();
|
||||
@@ -94,6 +94,7 @@ export default async function saveTabFile(editor, saveMode, folder, format, file
|
||||
filePath: savedFilePath,
|
||||
onSave,
|
||||
folid: savedCloudFolderId,
|
||||
defaultTeamFolder,
|
||||
// cntid: savedCloudContentId,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
<WidgetColumnBarItem
|
||||
title={driver?.databaseEngineTypes?.includes('document')
|
||||
? (driver?.collectionPluralLabel ?? 'Collections/containers')
|
||||
? _t('widget.collectionsContainers', { defaultMessage: 'Collections/containers' })
|
||||
: _t('widget.tablesViewsFunctions', { defaultMessage: 'Tables, views, functions' })}
|
||||
name="dbObjects"
|
||||
storageName="dbObjectsWidget"
|
||||
|
||||
@@ -79,12 +79,30 @@
|
||||
|
||||
// $: console.log('OBJECTS', $objects);
|
||||
|
||||
$: sortArgs =
|
||||
$databaseObjectAppObjectSearchSettings.sortBy == 'rowCount'
|
||||
? [
|
||||
['rowCount', 'sizeBytes', 'schemaName', 'pureName'],
|
||||
['desc', 'desc', 'asc', 'asc'],
|
||||
]
|
||||
: $databaseObjectAppObjectSearchSettings.sortBy == 'sizeBytes'
|
||||
? [
|
||||
['sizeBytes', 'rowCount', 'schemaName', 'pureName'],
|
||||
['desc', 'desc', 'asc', 'asc'],
|
||||
]
|
||||
: [
|
||||
['schemaName', 'pureName'],
|
||||
['asc', 'asc'],
|
||||
];
|
||||
|
||||
$: objectList = _.flatten([
|
||||
...['tables', 'collections', 'views', 'matviews', 'procedures', 'functions', 'triggers', 'schedulerEvents'].map(
|
||||
objectTypeField =>
|
||||
_.sortBy(
|
||||
_.orderBy(
|
||||
(($objects || {})[objectTypeField] || []).map(obj => ({ ...obj, objectTypeField })),
|
||||
['schemaName', 'pureName']
|
||||
sortArgs[0],
|
||||
// @ts-ignore
|
||||
sortArgs[1]
|
||||
)
|
||||
),
|
||||
...appsForDb.map(app =>
|
||||
@@ -133,19 +151,62 @@
|
||||
const res = [];
|
||||
res.push({ label: _t('sqlObject.searchBy', { defaultMessage: 'Search by:' }), isBold: true, disabled: true });
|
||||
if (driver?.databaseEngineTypes?.includes('document')) {
|
||||
res.push({ label: _t('sqlObject.collectionName', { defaultMessage: 'Collection name' }), switchValue: 'pureName' });
|
||||
res.push({
|
||||
label: _t('sqlObject.collectionName', { defaultMessage: 'Collection name' }),
|
||||
switchValue: 'pureName',
|
||||
});
|
||||
}
|
||||
if (driver?.databaseEngineTypes?.includes('sql')) {
|
||||
res.push({ label: _t('sqlObject.tableViewProcedureName', { defaultMessage: 'Table/view/procedure name' }), switchValue: 'pureName' });
|
||||
res.push({
|
||||
label: _t('sqlObject.tableViewProcedureName', { defaultMessage: 'Table/view/procedure name' }),
|
||||
switchValue: 'pureName',
|
||||
});
|
||||
res.push({ label: _t('sqlObject.schemaName', { defaultMessage: 'Schema' }), switchValue: 'schemaName' });
|
||||
res.push({ label: _t('sqlObject.columnName', { defaultMessage: 'Column name' }), switchValue: 'columnName' });
|
||||
res.push({ label: _t('sqlObject.columnDataType', { defaultMessage: 'Column data type' }), switchValue: 'columnDataType' });
|
||||
res.push({ label: _t('sqlObject.tableComment', { defaultMessage: 'Table comment' }), switchValue: 'tableComment' });
|
||||
res.push({ label: _t('sqlObject.columnComment', { defaultMessage: 'Column comment' }), switchValue: 'columnComment' });
|
||||
res.push({ label: _t('sqlObject.viewProcedureTriggerText', { defaultMessage: 'View/procedure/trigger text' }), switchValue: 'sqlObjectText' });
|
||||
res.push({
|
||||
label: _t('sqlObject.columnDataType', { defaultMessage: 'Column data type' }),
|
||||
switchValue: 'columnDataType',
|
||||
});
|
||||
res.push({
|
||||
label: _t('sqlObject.tableComment', { defaultMessage: 'Table comment' }),
|
||||
switchValue: 'tableComment',
|
||||
});
|
||||
res.push({
|
||||
label: _t('sqlObject.columnComment', { defaultMessage: 'Column comment' }),
|
||||
switchValue: 'columnComment',
|
||||
});
|
||||
res.push({
|
||||
label: _t('sqlObject.viewProcedureTriggerText', { defaultMessage: 'View/procedure/trigger text' }),
|
||||
switchValue: 'sqlObjectText',
|
||||
});
|
||||
res.push({ label: _t('sqlObject.tableEngine', { defaultMessage: 'Table engine' }), switchValue: 'tableEngine' });
|
||||
res.push({ label: _t('sqlObject.tablesWithRows', { defaultMessage: 'Only tables with rows' }), switchValue: 'tablesWithRows' });
|
||||
res.push({
|
||||
label: _t('sqlObject.tablesWithRows', { defaultMessage: 'Only tables with rows' }),
|
||||
switchValue: 'tablesWithRows',
|
||||
});
|
||||
}
|
||||
|
||||
res.push({ label: _t('sqlObject.sortBy', { defaultMessage: 'Sort by:' }), isBold: true, disabled: true });
|
||||
res.push({
|
||||
label: _t('sqlObject.name', { defaultMessage: 'Name' }),
|
||||
switchOption: 'sortBy',
|
||||
switchOptionValue: 'name',
|
||||
switchOptionIsDefault: true,
|
||||
closeOnSwitchClick: true,
|
||||
});
|
||||
res.push({
|
||||
label: _t('sqlObject.rowCount', { defaultMessage: 'Row count' }),
|
||||
switchOption: 'sortBy',
|
||||
switchOptionValue: 'rowCount',
|
||||
closeOnSwitchClick: true,
|
||||
});
|
||||
res.push({
|
||||
label: _t('sqlObject.sizeBytes', { defaultMessage: 'Size (bytes)' }),
|
||||
switchOption: 'sortBy',
|
||||
switchOptionValue: 'sizeBytes',
|
||||
closeOnSwitchClick: true,
|
||||
});
|
||||
|
||||
return res.map(item => ({
|
||||
...item,
|
||||
switchStore: databaseObjectAppObjectSearchSettings,
|
||||
@@ -193,19 +254,25 @@
|
||||
|
||||
<WidgetsInnerContainer hideContent={differentFocusedDb}>
|
||||
<ErrorInfo
|
||||
message={_t('sqlObject.databaseEmpty', { defaultMessage: 'Database {database} is empty or structure is not loaded, press Refresh button to reload structure', values: { database } })}
|
||||
message={_t('sqlObject.databaseEmpty', {
|
||||
defaultMessage:
|
||||
'Database {database} is empty or structure is not loaded, press Refresh button to reload structure',
|
||||
values: { database },
|
||||
})}
|
||||
icon="img alert"
|
||||
/>
|
||||
<div class="m-1" />
|
||||
<InlineButton on:click={handleRefreshDatabase}>{_t('common.refresh', { defaultMessage: 'Refresh' })}</InlineButton>
|
||||
{#if driver?.databaseEngineTypes?.includes('sql')}
|
||||
<div class="m-1" />
|
||||
<InlineButton on:click={() => runCommand('new.table')}>{_t('database.newTable', { defaultMessage: 'New table' })}</InlineButton>
|
||||
<InlineButton on:click={() => runCommand('new.table')}
|
||||
>{_t('database.newTable', { defaultMessage: 'New table' })}</InlineButton
|
||||
>
|
||||
{/if}
|
||||
{#if driver?.databaseEngineTypes?.includes('document')}
|
||||
<div class="m-1" />
|
||||
<InlineButton on:click={() => runCommand('new.collection')}
|
||||
>New {driver?.collectionSingularLabel ?? 'collection/container'}</InlineButton
|
||||
>{_t('sqlObject.newCollection', { defaultMessage: 'New collection/container' })}</InlineButton
|
||||
>
|
||||
{/if}
|
||||
</WidgetsInnerContainer>
|
||||
@@ -233,7 +300,7 @@
|
||||
{/if}
|
||||
<InlineButton
|
||||
on:click={handleRefreshDatabase}
|
||||
title={_t('sqlObjectList.refreshDatabase', { defaultMessage: "Refresh database connection and object list" })}
|
||||
title={_t('sqlObjectList.refreshDatabase', { defaultMessage: 'Refresh database connection and object list' })}
|
||||
square
|
||||
data-testid="SqlObjectList_refreshButton"
|
||||
>
|
||||
@@ -260,7 +327,10 @@
|
||||
data-testid="SqlObjectList_container"
|
||||
>
|
||||
{#if ($status && ($status.name == 'pending' || $status.name == 'checkStructure' || $status.name == 'loadStructure') && $objects) || !$objects}
|
||||
<LoadingInfo message={$status?.feedback?.analysingMessage || _t('sqlObject.loadingStructure', { defaultMessage: 'Loading database structure' })} />
|
||||
<LoadingInfo
|
||||
message={$status?.feedback?.analysingMessage ||
|
||||
_t('sqlObject.loadingStructure', { defaultMessage: 'Loading database structure' })}
|
||||
/>
|
||||
{:else}
|
||||
<AppObjectListHandler
|
||||
bind:this={domListHandler}
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import NewObjectModal from '../modals/NewObjectModal.svelte';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import { usePromoWidget } from '../utility/metadataLoaders';
|
||||
import { useConfig, usePromoWidget } from '../utility/metadataLoaders';
|
||||
import { _t, getCurrentTranslations } from '../translations';
|
||||
|
||||
let domSettings;
|
||||
let domCloudAccount;
|
||||
@@ -120,14 +121,14 @@
|
||||
{ command: 'theme.changeTheme' },
|
||||
hasPermission('settings/change') && { command: 'settings.commands' },
|
||||
hasPermission('widgets/plugins') && {
|
||||
text: 'Manage plugins',
|
||||
text: _t('widgets.managePlugins', { defaultMessage: 'Manage plugins' }),
|
||||
onClick: () => {
|
||||
$selectedWidget = 'plugins';
|
||||
$visibleWidgetSideBar = true;
|
||||
},
|
||||
},
|
||||
hasPermission('application-log') && {
|
||||
text: 'View application logs',
|
||||
text: _t('widgets.viewApplicationLogs', { defaultMessage: 'View application logs' }),
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: 'Application log',
|
||||
@@ -152,7 +153,7 @@
|
||||
const rect = domMainMenu.getBoundingClientRect();
|
||||
const left = rect.right;
|
||||
const top = rect.top;
|
||||
const items = mainMenuDefinition({ editMenu: false });
|
||||
const items = mainMenuDefinition({ editMenu: false }, getCurrentTranslations());
|
||||
currentDropDownMenu.set({ left, top, items });
|
||||
}
|
||||
|
||||
@@ -170,6 +171,7 @@
|
||||
}
|
||||
|
||||
$: promoWidgetData = $promoWidgetPreview || $promoWidget;
|
||||
$: config = useConfig();
|
||||
</script>
|
||||
|
||||
<div class="main">
|
||||
@@ -180,7 +182,7 @@
|
||||
{/if}
|
||||
{#each widgets
|
||||
.filter(x => x && hasPermission(`widgets/${x.name}`))
|
||||
.filter(x => !x.isPremiumPromo || (!isProApp() && promoWidgetData?.state == 'data'))
|
||||
.filter(x => !x.isPremiumPromo || (($config?.trialDaysLeft != null || !isProApp()) && promoWidgetData?.state == 'data'))
|
||||
// .filter(x => !x.isPremiumOnly || isProApp())
|
||||
.filter(x => x.name != 'cloud-private' || $cloudSigninTokenHolder) as item}
|
||||
<div
|
||||
|
||||
@@ -6,38 +6,168 @@ const { getLogger } = global.DBGATE_PACKAGES['dbgate-tools'];
|
||||
|
||||
const logger = getLogger('csvWriter');
|
||||
|
||||
class CsvPrepareStream extends stream.Transform {
|
||||
constructor({ header }) {
|
||||
super({ objectMode: true });
|
||||
this.structure = null;
|
||||
this.header = header;
|
||||
class RecodeTransform extends stream.Transform {
|
||||
constructor(toEncoding = 'utf8') {
|
||||
super({ readableObjectMode: false, writableObjectMode: false });
|
||||
this.to = String(toEncoding).toLowerCase();
|
||||
this.decoder = new (global.TextDecoder || require('util').TextDecoder)('utf-8', { fatal: false });
|
||||
}
|
||||
_transform(chunk, encoding, done) {
|
||||
if (this.structure) {
|
||||
this.push(this.structure.columns.map((col) => chunk[col.columnName]));
|
||||
done();
|
||||
} else {
|
||||
this.structure = chunk;
|
||||
if (this.header) {
|
||||
this.push(chunk.columns.map((x) => x.columnName));
|
||||
|
||||
_encodeString(str) {
|
||||
if (this.to === 'utf8' || this.to === 'utf-8') {
|
||||
return Buffer.from(str, 'utf8');
|
||||
}
|
||||
if (this.to === 'utf16le' || this.to === 'ucs2' || this.to === 'utf-16le') {
|
||||
return Buffer.from(str, 'utf16le');
|
||||
}
|
||||
if (this.to === 'utf16be' || this.to === 'utf-16be') {
|
||||
const le = Buffer.from(str, 'utf16le');
|
||||
for (let i = 0; i + 1 < le.length; i += 2) {
|
||||
const a = le[i];
|
||||
le[i] = le[i + 1];
|
||||
le[i + 1] = a;
|
||||
}
|
||||
done();
|
||||
return le;
|
||||
}
|
||||
throw new Error(`Unsupported target encoding: ${this.to}`);
|
||||
}
|
||||
|
||||
_transform(chunk, enc, cb) {
|
||||
try {
|
||||
if (!Buffer.isBuffer(chunk)) chunk = Buffer.from(chunk, enc);
|
||||
const part = this.decoder.decode(chunk, { stream: true });
|
||||
if (part.length) this.push(this._encodeString(part));
|
||||
cb();
|
||||
} catch (e) {
|
||||
cb(e);
|
||||
}
|
||||
}
|
||||
|
||||
_flush(cb) {
|
||||
try {
|
||||
const rest = this.decoder.decode();
|
||||
if (rest.length) this.push(this._encodeString(rest));
|
||||
cb();
|
||||
} catch (e) {
|
||||
cb(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function writer({ fileName, encoding = 'utf-8', header = true, delimiter, quoted }) {
|
||||
const INFER_STRUCTURE_ROWS = 100;
|
||||
|
||||
class CsvPrepareStream extends stream.Transform {
|
||||
constructor({ header }) {
|
||||
super({ objectMode: true });
|
||||
this.columns = null;
|
||||
this.header = header;
|
||||
this.cachedRows = null;
|
||||
}
|
||||
_transform(chunk, encoding, done) {
|
||||
if (this.columns) {
|
||||
this.push(this.columns.map((col) => chunk[col]));
|
||||
done();
|
||||
} else {
|
||||
if (chunk.__isStreamHeader && chunk.columns?.length > 0) {
|
||||
this.columns = chunk.columns.map((x) => x.columnName);
|
||||
if (this.header) {
|
||||
this.push(this.columns);
|
||||
}
|
||||
done();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.cachedRows) {
|
||||
this.cachedRows = [];
|
||||
}
|
||||
this.cachedRows.push(chunk);
|
||||
if (this.cachedRows.length < INFER_STRUCTURE_ROWS) {
|
||||
done();
|
||||
return;
|
||||
}
|
||||
this.inferStructureFromCachedRows();
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
inferStructureFromCachedRows() {
|
||||
const allKeys = {};
|
||||
for (const row of this.cachedRows) {
|
||||
for (const key of Object.keys(row)) {
|
||||
allKeys[key] = true;
|
||||
}
|
||||
}
|
||||
this.columns = Object.keys(allKeys);
|
||||
if (this.header) {
|
||||
this.push(this.columns);
|
||||
}
|
||||
for (const row of this.cachedRows) {
|
||||
this.push(this.columns.map((col) => row[col]));
|
||||
}
|
||||
this.cachedRows = null;
|
||||
}
|
||||
|
||||
_final(callback) {
|
||||
if (this.cachedRows) {
|
||||
this.inferStructureFromCachedRows();
|
||||
}
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
async function writer({
|
||||
fileName,
|
||||
encoding = 'utf-8',
|
||||
header = true,
|
||||
delimiter,
|
||||
quoted,
|
||||
writeBom,
|
||||
writeSepHeader,
|
||||
recordDelimiter,
|
||||
}) {
|
||||
logger.info(`DBGM-00133 Writing file ${fileName}`);
|
||||
const csvPrepare = new CsvPrepareStream({ header });
|
||||
const csvStream = csv.stringify({ delimiter, quoted });
|
||||
const csvStream = csv.stringify({ delimiter, quoted, record_delimiter: recordDelimiter || undefined });
|
||||
const fileStream = fs.createWriteStream(fileName, encoding);
|
||||
// csvPrepare.pipe(csvStream);
|
||||
// csvStream.pipe(fileStream);
|
||||
// csvPrepare['finisher'] = fileStream;
|
||||
if (writeBom) {
|
||||
switch (encoding.toLowerCase()) {
|
||||
case 'utf-8':
|
||||
case 'utf8':
|
||||
fileStream.write(Buffer.from([0xef, 0xbb, 0xbf]));
|
||||
break;
|
||||
case 'utf-16':
|
||||
case 'utf16':
|
||||
fileStream.write(Buffer.from([0xff, 0xfe]));
|
||||
break;
|
||||
case 'utf-16le':
|
||||
case 'utf16le':
|
||||
fileStream.write(Buffer.from([0xff, 0xfe]));
|
||||
break;
|
||||
case 'utf-16be':
|
||||
case 'utf16be':
|
||||
fileStream.write(Buffer.from([0xfe, 0xff]));
|
||||
break;
|
||||
case 'utf-32le':
|
||||
case 'utf32le':
|
||||
fileStream.write(Buffer.from([0xff, 0xfe, 0x00, 0x00]));
|
||||
break;
|
||||
case 'utf-32be':
|
||||
case 'utf32be':
|
||||
fileStream.write(Buffer.from([0x00, 0x00, 0xfe, 0xff]));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (writeSepHeader) {
|
||||
fileStream.write(`sep=${delimiter}${recordDelimiter || '\n'}`);
|
||||
}
|
||||
|
||||
csvPrepare.requireFixedStructure = true;
|
||||
|
||||
return [csvPrepare, csvStream, fileStream];
|
||||
// return csvPrepare;
|
||||
return encoding.toLowerCase() === 'utf8' || encoding.toLowerCase() === 'utf-8'
|
||||
? [csvPrepare, csvStream, fileStream]
|
||||
: [csvPrepare, csvStream, new RecodeTransform(encoding), fileStream];
|
||||
}
|
||||
|
||||
module.exports = writer;
|
||||
|
||||
@@ -39,6 +39,24 @@ const fileFormat = {
|
||||
apiName: 'header',
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
name: 'writeBom',
|
||||
label: 'Write BOM (Byte Order Mark)',
|
||||
apiName: 'writeBom',
|
||||
direction: 'target',
|
||||
},
|
||||
{
|
||||
type: 'select',
|
||||
name: 'recordDelimiter',
|
||||
label: 'Record Delimiter',
|
||||
options: [
|
||||
{ name: 'CR', value: '\r' },
|
||||
{ name: 'CRLF', value: '\r\n' },
|
||||
],
|
||||
apiName: 'recordDelimiter',
|
||||
direction: 'target',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -68,5 +86,31 @@ export default {
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: 'CSV file for MS Excel',
|
||||
extension: 'csv',
|
||||
createWriter: (fileName) => ({
|
||||
functionName: 'writer@dbgate-plugin-csv',
|
||||
props: {
|
||||
fileName,
|
||||
delimiter: ';',
|
||||
recordDelimiter: '\r\n',
|
||||
encoding: 'utf16le',
|
||||
writeSepHeader: true,
|
||||
writeBom: true,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: 'TSV file (tab separated)',
|
||||
extension: 'tsv',
|
||||
createWriter: (fileName) => ({
|
||||
functionName: 'writer@dbgate-plugin-csv',
|
||||
props: {
|
||||
fileName,
|
||||
delimiter: '\t',
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"dependencies": {
|
||||
"dbgate-tools": "^6.0.0-alpha.1",
|
||||
"lodash": "^4.17.21",
|
||||
"dbgate-query-splitter": "^4.11.7"
|
||||
"dbgate-query-splitter": "^4.11.9"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@duckdb/node-api": "^1.2.1-alpha.16"
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"wkx": "^0.5.0",
|
||||
"pg-copy-streams": "^6.0.6",
|
||||
"node-firebird": "^1.1.9",
|
||||
"dbgate-query-splitter": "^4.11.7",
|
||||
"dbgate-query-splitter": "^4.11.9",
|
||||
"dbgate-tools": "^6.0.0-alpha.1",
|
||||
"lodash": "^4.17.21",
|
||||
"pg": "^8.11.5"
|
||||
|
||||
@@ -37,11 +37,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"bson": "^6.8.0",
|
||||
"dbgate-query-splitter": "^4.11.7",
|
||||
"dbgate-query-splitter": "^4.11.9",
|
||||
"dbgate-tools": "^6.0.0-alpha.1",
|
||||
"is-promise": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mongodb": "^6.3.0",
|
||||
"mongodb-old": "npm:mongodb@6.16.0",
|
||||
"@mongosh/browser-runtime-electron": "^3.16.4",
|
||||
"@mongosh/service-provider-node-driver": "^3.10.2"
|
||||
},
|
||||
|
||||
@@ -16,12 +16,16 @@ class Analyser extends DatabaseAnalyser {
|
||||
collections
|
||||
.filter((x) => x.type == 'collection')
|
||||
.map((x) =>
|
||||
this.dbhan
|
||||
.getDatabase()
|
||||
.collection(x.name)
|
||||
.aggregate([{ $collStats: { count: {} } }])
|
||||
.toArray()
|
||||
.then((resp) => ({ name: x.name, count: resp[0].count }))
|
||||
this.dbhan
|
||||
.getDatabase()
|
||||
.collection(x.name)
|
||||
.aggregate([{ $collStats: { count: {}, storageStats: {} } }])
|
||||
.toArray()
|
||||
.then((resp) => ({
|
||||
name: x.name,
|
||||
count: resp[0].count,
|
||||
size: resp[0].storageStats?.size
|
||||
}))
|
||||
)
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -29,11 +33,13 @@ class Analyser extends DatabaseAnalyser {
|
||||
stats = {};
|
||||
}
|
||||
|
||||
|
||||
const res = this.mergeAnalyseResult({
|
||||
collections: [
|
||||
...collections.map((x, index) => ({
|
||||
pureName: x.name,
|
||||
tableRowCount: stats[index]?.count,
|
||||
sizeBytes: stats[index]?.size,
|
||||
uniqueKey: [{ columnName: '_id' }],
|
||||
partitionKey: [{ columnName: '_id' }],
|
||||
clusterKey: [{ columnName: '_id' }],
|
||||
|
||||
+41
-27
@@ -1,10 +1,11 @@
|
||||
const _ = require('lodash');
|
||||
const { EventEmitter } = require('events');
|
||||
const stream = require('stream');
|
||||
const driverBase = require('../frontend/driver');
|
||||
const driverBases = require('../frontend/drivers');
|
||||
const Analyser = require('./Analyser');
|
||||
const isPromise = require('is-promise');
|
||||
const { MongoClient, ObjectId, AbstractCursor, Long } = require('mongodb');
|
||||
const mongodb = require('mongodb');
|
||||
const { ObjectId } = require('mongodb');
|
||||
const { EJSON } = require('bson');
|
||||
const { serializeJsTypesForJsonStringify, deserializeJsTypesFromJsonParse, getLogger } = require('dbgate-tools');
|
||||
const createBulkInsertStream = require('./createBulkInsertStream');
|
||||
@@ -18,7 +19,8 @@ let isProApp;
|
||||
|
||||
const logger = getLogger('mongoDriver');
|
||||
|
||||
function serializeMongoData(row) {
|
||||
function serializeMongoData(row, driverBase) {
|
||||
const { Long } = driverBase.useLegacyDriver ? require('mongodb-old') : mongodb;
|
||||
return EJSON.serialize(
|
||||
serializeJsTypesForJsonStringify(row, (value) => {
|
||||
if (value instanceof Long) {
|
||||
@@ -33,10 +35,10 @@ function serializeMongoData(row) {
|
||||
);
|
||||
}
|
||||
|
||||
async function readCursor(cursor, options) {
|
||||
async function readCursor(cursor, options, driverBase) {
|
||||
options.recordset({ __isDynamicStructure: true });
|
||||
await cursor.forEach((row) => {
|
||||
options.row(serializeMongoData(row));
|
||||
options.row(serializeMongoData(row, driverBase));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -51,6 +53,10 @@ function findArrayResult(resValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function BinData(_subType, base64) {
|
||||
return Buffer.from(base64, 'base64');
|
||||
}
|
||||
|
||||
async function getScriptableDb(dbhan) {
|
||||
const db = dbhan.getDatabase();
|
||||
db.getCollection = (name) => db.collection(name);
|
||||
@@ -82,8 +88,8 @@ async function getScriptableDb(dbhan) {
|
||||
// }
|
||||
// }
|
||||
|
||||
/** @type {import('dbgate-types').EngineDriver<MongoClient, import('mongodb').Db>} */
|
||||
const driver = {
|
||||
/** @type {import('dbgate-types').EngineDriver<import('mongodb').MongoClient, import('mongodb').Db>} */
|
||||
const drivers = driverBases.map((driverBase) => ({
|
||||
...driverBase,
|
||||
analyserClass: Analyser,
|
||||
async connect({ server, port, user, password, database, useDatabaseUrl, databaseUrl, ssl, useSshTunnel }) {
|
||||
@@ -116,6 +122,8 @@ const driver = {
|
||||
options.tlsInsecure = !ssl.rejectUnauthorized;
|
||||
}
|
||||
|
||||
const { MongoClient } = driverBase.useLegacyDriver ? require('mongodb-old') : mongodb;
|
||||
|
||||
const client = new MongoClient(mongoUrl, options);
|
||||
await client.connect();
|
||||
return {
|
||||
@@ -156,9 +164,9 @@ const driver = {
|
||||
// return printable;
|
||||
// }
|
||||
let func;
|
||||
func = eval(`(db,ObjectId) => ${sql}`);
|
||||
func = eval(`(db,ObjectId,BinData) => ${sql}`);
|
||||
const db = await getScriptableDb(dbhan);
|
||||
const res = func(db, ObjectId.createFromHexString);
|
||||
const res = func(db, ObjectId.createFromHexString, BinData);
|
||||
if (isPromise(res)) await res;
|
||||
},
|
||||
async operation(dbhan, operation, options) {
|
||||
@@ -285,7 +293,7 @@ const driver = {
|
||||
} else {
|
||||
let func;
|
||||
try {
|
||||
func = eval(`(db,ObjectId) => ${sql}`);
|
||||
func = eval(`(db,ObjectId,BinData) => ${sql}`);
|
||||
} catch (err) {
|
||||
options.info({
|
||||
message: 'Error compiling expression: ' + err.message,
|
||||
@@ -299,7 +307,7 @@ const driver = {
|
||||
|
||||
let exprValue;
|
||||
try {
|
||||
exprValue = func(db, ObjectId.createFromHexString);
|
||||
exprValue = func(db, ObjectId.createFromHexString, BinData);
|
||||
} catch (err) {
|
||||
options.info({
|
||||
message: 'Error evaluating expression: ' + err.message,
|
||||
@@ -310,8 +318,10 @@ const driver = {
|
||||
return;
|
||||
}
|
||||
|
||||
const { AbstractCursor } = driverBase.useLegacyDriver ? require('mongodb-old') : mongodb;
|
||||
|
||||
if (exprValue instanceof AbstractCursor) {
|
||||
await readCursor(exprValue, options);
|
||||
await readCursor(exprValue, options, driverBase);
|
||||
} else if (isPromise(exprValue)) {
|
||||
try {
|
||||
const resValue = await exprValue;
|
||||
@@ -411,9 +421,9 @@ const driver = {
|
||||
// highWaterMark: 100,
|
||||
// });
|
||||
|
||||
func = eval(`(db,ObjectId) => ${sql}`);
|
||||
func = eval(`(db,ObjectId,BinData) => ${sql}`);
|
||||
const db = await getScriptableDb(dbhan);
|
||||
exprValue = func(db, ObjectId.createFromHexString);
|
||||
exprValue = func(db, ObjectId.createFromHexString, BinData);
|
||||
|
||||
const pass = new stream.PassThrough({
|
||||
objectMode: true,
|
||||
@@ -423,7 +433,7 @@ const driver = {
|
||||
const cursorStream = exprValue.stream();
|
||||
|
||||
cursorStream.on('data', (row) => {
|
||||
pass.write(serializeMongoData(row));
|
||||
pass.write(serializeMongoData(row, driverBase));
|
||||
});
|
||||
|
||||
// propagate error
|
||||
@@ -483,10 +493,12 @@ const driver = {
|
||||
let cursor = await collection.aggregate(deserializeMongoData(convertToMongoAggregate(options.aggregate)));
|
||||
const rows = await cursor.toArray();
|
||||
return {
|
||||
rows: rows.map(serializeMongoData).map((x) => ({
|
||||
...x._id,
|
||||
..._.omit(x, ['_id']),
|
||||
})),
|
||||
rows: rows
|
||||
.map((row) => serializeMongoData(row, driverBase))
|
||||
.map((x) => ({
|
||||
...x._id,
|
||||
..._.omit(x, ['_id']),
|
||||
})),
|
||||
};
|
||||
} else {
|
||||
// console.log('options.condition', JSON.stringify(options.condition, undefined, 2));
|
||||
@@ -496,7 +508,7 @@ const driver = {
|
||||
if (options.limit) cursor = cursor.limit(options.limit);
|
||||
const rows = await cursor.toArray();
|
||||
return {
|
||||
rows: rows.map(serializeMongoData),
|
||||
rows: rows.map((row) => serializeMongoData(row, driverBase)),
|
||||
};
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -609,10 +621,12 @@ const driver = {
|
||||
]);
|
||||
const rows = await cursor.toArray();
|
||||
return _.uniqBy(
|
||||
rows.map(serializeMongoData).map(({ _id }) => {
|
||||
if (_.isArray(_id) || _.isPlainObject(_id)) return { value: null };
|
||||
return { value: _id };
|
||||
}),
|
||||
rows
|
||||
.map((row) => serializeMongoData(row, driverBase))
|
||||
.map(({ _id }) => {
|
||||
if (_.isArray(_id) || _.isPlainObject(_id)) return { value: null };
|
||||
return { value: _id };
|
||||
}),
|
||||
(x) => x.value
|
||||
);
|
||||
} catch (err) {
|
||||
@@ -749,10 +763,10 @@ const driver = {
|
||||
|
||||
return result;
|
||||
},
|
||||
};
|
||||
}));
|
||||
|
||||
driver.initialize = (dbgateEnv) => {
|
||||
drivers.initialize = (dbgateEnv) => {
|
||||
isProApp = dbgateEnv.isProApp;
|
||||
};
|
||||
|
||||
module.exports = driver;
|
||||
module.exports = drivers;
|
||||
@@ -1,4 +1,4 @@
|
||||
const driver = require('./driver');
|
||||
const drivers = require('./drivers');
|
||||
const {
|
||||
formatProfilerEntry,
|
||||
extractProfileTimestamp,
|
||||
@@ -7,13 +7,13 @@ const {
|
||||
|
||||
module.exports = {
|
||||
packageName: 'dbgate-plugin-mongo',
|
||||
drivers: [driver],
|
||||
drivers,
|
||||
functions: {
|
||||
formatProfilerEntry,
|
||||
extractProfileTimestamp,
|
||||
aggregateProfileChartEntry,
|
||||
},
|
||||
initialize(dbgateEnv) {
|
||||
driver.initialize(dbgateEnv);
|
||||
drivers.initialize(dbgateEnv);
|
||||
},
|
||||
};
|
||||
|
||||
+23
-4
@@ -15,7 +15,13 @@ function mongoReplacer(key, value) {
|
||||
function jsonStringifyWithObjectId(obj) {
|
||||
return JSON.stringify(obj, mongoReplacer, 2)
|
||||
.replace(/\{\s*\"\$oid\"\s*\:\s*\"([0-9a-f]+)\"\s*\}/g, (m, id) => `ObjectId("${id}")`)
|
||||
.replace(/\{\s*\"\$bigint\"\s*\:\s*\"([0-9]+)\"\s*\}/g, (m, num) => `${num}n`);
|
||||
.replace(/\{\s*\"\$bigint\"\s*\:\s*\"([0-9]+)\"\s*\}/g, (m, num) => `${num}n`)
|
||||
.replace(
|
||||
/\{\s*"\$binary"\s*:\s*\{\s*"base64"\s*:\s*"([^"]+)"(?:\s*,\s*"subType"\s*:\s*"([0-9a-fA-F]{2})")?\s*\}\s*\}/g,
|
||||
(m, base64, subType) => {
|
||||
return `BinData(${parseInt(subType || '00', 16)}, "${base64}")`;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/** @type {import('dbgate-types').SqlDialect} */
|
||||
@@ -31,7 +37,7 @@ const dialect = {
|
||||
};
|
||||
|
||||
/** @type {import('dbgate-types').EngineDriver} */
|
||||
const driver = {
|
||||
const mongoDriverBase = {
|
||||
...driverBase,
|
||||
dumperClass: Dumper,
|
||||
databaseEngineTypes: ['document'],
|
||||
@@ -129,7 +135,7 @@ const driver = {
|
||||
|
||||
getCollectionExportQueryScript(collection, condition, sort) {
|
||||
return `db.getCollection('${collection}')
|
||||
.find(${JSON.stringify(convertToMongoCondition(condition) || {})})
|
||||
.find(${jsonStringifyWithObjectId(convertToMongoCondition(condition) || {})})
|
||||
.sort(${JSON.stringify(convertToMongoSort(sort) || {})})`;
|
||||
},
|
||||
getCollectionExportQueryJson(collection, condition, sort) {
|
||||
@@ -148,6 +154,7 @@ const driver = {
|
||||
parseJsonObject: true,
|
||||
parseObjectIdAsDollar: true,
|
||||
parseDateAsDollar: true,
|
||||
parseHexAsBuffer: true,
|
||||
|
||||
explicitDataType: true,
|
||||
supportNumberType: true,
|
||||
@@ -189,4 +196,16 @@ const driver = {
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = driver;
|
||||
const mongoDriver = {
|
||||
...mongoDriverBase,
|
||||
};
|
||||
|
||||
const legacyMongoDriver = {
|
||||
...mongoDriverBase,
|
||||
engine: 'mongo-legacy@dbgate-plugin-mongo',
|
||||
title: 'MongoDB 4 - Legacy',
|
||||
premiumOnly: true,
|
||||
useLegacyDriver: true,
|
||||
};
|
||||
|
||||
module.exports = [mongoDriver, legacyMongoDriver];
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user