diff --git a/CHANGELOG.md b/CHANGELOG.md index 13f6928a8..46295903b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # ChangeLog +### 4.3.2 +- FIXED: Sorted database list in PostgreSQL (#178) +- FIXED: Loading stricture of PostgreSQL database, when it contains indexes on expressions (#175) +- ADDED: Hotkey Shift+Alt+F for formatting SQL code + ### 4.3.1 - FIXED: #173 Using key phrase for SSH key file connection - ADDED: #172 Abiloity to quick search within database names diff --git a/README.md b/README.md index 22a4ff147..4d1cf1ae9 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ [![dbgate](https://snapcraft.io/dbgate/trending.svg?name=0)](https://snapcraft.io/dbgate) [![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) -# DbGate - database administration tool +# DbGate - database manager -DbGate modern, fast and easy to use database manager +DbGate is modern, fast and easy to use (no)SQL database client * Try it online - [demo.dbgate.org](https://demo.dbgate.org) - online demo application * Download application for Windows, Linux or Mac from [dbgate.org](https://dbgate.org/download/) @@ -26,6 +26,7 @@ Supported databases: ## Features * Table data editing, with SQL change script preview +* Edit table schema, indexes, primary and foreign keys * Light and dark theme * Master/detail views * Query designer diff --git a/app/src/electron.js b/app/src/electron.js index 1a194b9c9..0ef5cc6f0 100644 --- a/app/src/electron.js +++ b/app/src/electron.js @@ -154,6 +154,9 @@ function createWindow() { enableRemoteModule: true, }, }); + if (store.get('winIsMaximized')) { + mainWindow.maximize(); + } mainMenu = buildMenu(); mainWindow.setMenu(mainMenu); @@ -171,6 +174,7 @@ function createWindow() { }); mainWindow.on('close', () => { store.set('winBounds', mainWindow.getBounds()); + store.set('winIsMaximized', mainWindow.isMaximized()); }); mainWindow.loadURL(startUrl); if (os.platform() == 'linux') { diff --git a/package.json b/package.json index df0e37d92..4bd0a1768 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "4.3.2-beta.1", + "version": "4.3.3-beta.3", "name": "dbgate-all", "workspaces": [ "packages/*", diff --git a/packages/datalib/src/GridDisplay.ts b/packages/datalib/src/GridDisplay.ts index 714da9384..c3aba307e 100644 --- a/packages/datalib/src/GridDisplay.ts +++ b/packages/datalib/src/GridDisplay.ts @@ -115,6 +115,10 @@ export abstract class GridDisplay { return this.getColumns(null).filter(col => col.isChecked || col.uniquePath.length == 1); } + getFkTarget(column: DisplayColumn): TableInfo { + return null; + } + reload() { this.setCache(reloadDataCacheFunc); } diff --git a/packages/web/src/appobj/SubDatabaseList.svelte b/packages/web/src/appobj/SubDatabaseList.svelte index eb97e629c..9cb442679 100644 --- a/packages/web/src/appobj/SubDatabaseList.svelte +++ b/packages/web/src/appobj/SubDatabaseList.svelte @@ -1,5 +1,6 @@ filterName(filter, x.name)).map(db => ({ ...db, connection: data }))} + list={_.sortBy( + ($databases || []).filter(x => filterName(filter, x.name)), + 'name' + ).map(db => ({ ...db, connection: data }))} module={databaseAppObject} /> diff --git a/packages/web/src/datagrid/DataGridCore.svelte b/packages/web/src/datagrid/DataGridCore.svelte index 68f4df9be..5af89e44b 100644 --- a/packages/web/src/datagrid/DataGridCore.svelte +++ b/packages/web/src/datagrid/DataGridCore.svelte @@ -111,11 +111,29 @@ id: 'dataGrid.filterSelected', category: 'Data grid', name: 'Filter selected value', - keyText: 'Ctrl+F', + keyText: 'Ctrl+Shift+F', testEnabled: () => getCurrentDataGrid()?.getDisplay().filterable, onClick: () => getCurrentDataGrid().filterSelectedValue(), }); + registerCommand({ + id: 'dataGrid.findColumn', + category: 'Data grid', + name: 'Find colunn', + keyText: 'Ctrl+F', + testEnabled: () => getCurrentDataGrid() != null, + getSubCommands: () => getCurrentDataGrid().buildFindMenu(), + }); + + registerCommand({ + id: 'dataGrid.hideColumn', + category: 'Data grid', + name: 'Hide colunn', + keyText: 'Ctrl+H', + testEnabled: () => getCurrentDataGrid() != null, + onClick: () => getCurrentDataGrid().hideColumn(), + }); + registerCommand({ id: 'dataGrid.clearFilter', category: 'Data grid', @@ -125,6 +143,15 @@ onClick: () => getCurrentDataGrid().clearFilter(), }); + registerCommand({ + id: 'dataGrid.generateSqlFromData', + category: 'Data grid', + name: 'Generate SQL', + keyText: 'Ctrl+G', + testEnabled: () => getCurrentDataGrid()?.generateSqlFromDataEnabled(), + onClick: () => getCurrentDataGrid().generateSqlFromData(), + }); + registerCommand({ id: 'dataGrid.openFreeTable', category: 'Data grid', @@ -215,6 +242,8 @@ import { editJsonRowDocument } from '../jsonview/CollectionJsonRow.svelte'; import createActivator, { getActiveComponent } from '../utility/createActivator'; import CollapseButton from './CollapseButton.svelte'; + import GenerateSqlFromDataModal from '../modals/GenerateSqlFromDataModal.svelte'; + import { showModal } from '../modals/modalTools'; export let onLoadNextData = undefined; export let grider = undefined; @@ -347,6 +376,7 @@ }); const text = lines.join('\r\n'); copyTextToClipboard(text); + // if (domFocusField) domFocusField.focus(); } export function loadNextDataIfNeeded() { @@ -416,6 +446,119 @@ editJsonRowDocument(grider, rowIndex); } + export function buildFindMenu() { + const res = []; + + async function clickColumn(uniquePath) { + display.setColumnVisibility(uniquePath, true); + await tick(); + const invMap = _.invert(realColumnUniqueNames); + const colIndex = invMap[uniquePath.join('.')]; + scrollIntoView([null, colIndex]); + + currentCell = [currentCell[0], parseInt(colIndex)]; + selectedCells = [currentCell]; + } + + for (const column of display.columns) { + if (column.uniquePath.length > 1) continue; + + res.push({ + text: column.columnName, + icon: 'img column', + onClick: async () => { + clickColumn(column.uniquePath); + }, + }); + } + for (const column of display.columns) { + if (column.uniquePath.length > 1) continue; + if (column.isExpandable) { + const table = display.getFkTarget(column); + if (!table) continue; + + for (const childColumn of table.columns) { + res.push({ + text: `${column.columnName}.${childColumn.columnName}`, + icon: 'img column', + onClick: async () => { + display.toggleExpandedColumn(column.uniqueName, true); + clickColumn([...column.uniquePath, childColumn.columnName]); + }, + }); + } + } + } + + for (const fk of display?.baseTable?.foreignKeys || []) { + res.push({ + text: `${fk.refTableName} (${fk.columns.map(x => x.columnName).join(', ')})`, + icon: 'img link', + onClick: () => { + onReferenceClick({ + schemaName: fk.refSchemaName, + pureName: fk.refTableName, + columns: fk.columns.map(col => ({ + baseName: col.columnName, + refName: col.refColumnName, + })), + }); + }, + }); + } + + for (const fk of display?.baseTable?.dependencies || []) { + res.push({ + text: `${fk.pureName} (${fk.columns.map(x => x.columnName).join(', ')})`, + icon: 'img reference', + onClick: () => { + onReferenceClick({ + schemaName: fk.schemaName, + pureName: fk.pureName, + columns: fk.columns.map(col => ({ + baseName: col.refColumnName, + refName: col.columnName, + })), + }); + }, + }); + } + + return res; + } + + export function hideColumn() { + const columnIndexes = _.uniq(selectedCells.map(x => x[1])); + for (const index of columnIndexes) { + const name = realColumnUniqueNames[index]; + const column = display.allColumns.find(x => x.uniqueName == name); + if (column) { + display.setColumnVisibility(column.uniquePath, false); + } + } + // selectedCells = [currentCell]; + } + + export function generateSqlFromDataEnabled() { + return !!display?.baseTable; + } + + export function generateSqlFromData() { + const columnIndexes = _.uniq(selectedCells.map(x => x[1])); + columnIndexes.sort(); + + showModal(GenerateSqlFromDataModal, { + rows: getSelectedRowData(), + allColumns: display.baseTable.columns.map(x => x.columnName), + selectedColumns: columnIndexes.map(x => realColumnUniqueNames[x]), + keyColumns: display?.baseTable?.primaryKey?.columns?.map(x => x.columnName) || [ + display.baseTable.columns[0].columnName, + ], + engineDriver: display?.driver, + tableInfo: display.baseTable, + }); + } + $: autofillMarkerCell = selectedCells && selectedCells.length > 0 && _.uniq(selectedCells.map(x => x[0])).length == 1 ? [_.max(selectedCells.map(x => x[0])), _.max(selectedCells.map(x => x[1]))] @@ -611,6 +754,8 @@ selectedCells = [...selectedCells, cell]; } } + } else if (event.shiftKey) { + selectedCells = getCellRange(oldCurrentCell, cell); } else { selectedCells = getCellRange(cell, cell); dragStartCell = cell; @@ -927,7 +1072,7 @@ currentCell = cell; // @ts-ignore selectedCells = [cell]; - domFocusField.focus(); + if (domFocusField) domFocusField.focus(); }; const [inplaceEditorState, dispatchInsplaceEditor] = createReducer((state, action) => { @@ -977,6 +1122,8 @@ { command: 'dataGrid.setNull' }, { placeTag: 'edit' }, { divider: true }, + { command: 'dataGrid.findColumn' }, + { command: 'dataGrid.hideColumn' }, { command: 'dataGrid.filterSelected' }, { command: 'dataGrid.clearFilter' }, { command: 'dataGrid.undo' }, @@ -984,6 +1131,7 @@ { command: 'dataGrid.editJsonDocument' }, { divider: true }, { placeTag: 'export' }, + { command: 'dataGrid.generateSqlFromData' }, { command: 'dataGrid.openFreeTable' }, { command: 'dataGrid.openChartFromSelection' }, { placeTag: 'chart' } diff --git a/packages/web/src/elements/CheckableColumnList.svelte b/packages/web/src/elements/CheckableColumnList.svelte new file mode 100644 index 000000000..e7d6ba4ae --- /dev/null +++ b/packages/web/src/elements/CheckableColumnList.svelte @@ -0,0 +1,44 @@ + + +
+ (selectedColumns = allColumns)} {disabled} /> + (selectedColumns = [])} {disabled} /> +
+
+ {#each allColumns as column} +
+ toggleColumn(column)} + /> + + toggleColumn(column)} class="label"> + {column} + +
+ {/each} +
+ + diff --git a/packages/web/src/forms/FormSubmit.svelte b/packages/web/src/forms/FormSubmit.svelte index 3c4aa3b00..e9fcb7765 100644 --- a/packages/web/src/forms/FormSubmit.svelte +++ b/packages/web/src/forms/FormSubmit.svelte @@ -3,7 +3,7 @@ import { getFormContext } from './FormProviderCore.svelte'; import { createEventDispatcher } from 'svelte'; - export let disabled; + export let disabled = false; const dispatch = createEventDispatcher(); diff --git a/packages/web/src/formview/FormView.svelte b/packages/web/src/formview/FormView.svelte index 2392bcb87..5496e517f 100644 --- a/packages/web/src/formview/FormView.svelte +++ b/packages/web/src/formview/FormView.svelte @@ -88,7 +88,7 @@ id: 'dataForm.filterSelected', category: 'Data form', name: 'Filter this value', - keyText: 'Ctrl+F', + keyText: 'Ctrl+Shift+F', testEnabled: () => getCurrentDataForm() != null, onClick: () => getCurrentDataForm().filterSelectedValue(), }); diff --git a/packages/web/src/modals/GenerateSqlFromDataModal.svelte b/packages/web/src/modals/GenerateSqlFromDataModal.svelte new file mode 100644 index 000000000..2408fbf55 --- /dev/null +++ b/packages/web/src/modals/GenerateSqlFromDataModal.svelte @@ -0,0 +1,140 @@ + + + + + Generate SQL from data + +
+
+
Choose query type
+ + ({ name }))} + bind:selectedIndex={queryTypeIndex} + bind:domTable={domQueryType} + focusOnCreate + selectable + columns={[{ fieldName: 'name', header: 'Query type' }]} + /> +
+ +
+
Value columns
+ + +
+ +
+
WHERE columns
+ + +
+
+ +
+ +
+ + + { + newQuery({ initialData: sqlPreview }); + closeCurrentModal(); + }} + /> + + +
+
+ + diff --git a/packages/web/src/modals/InsertJoinModal.svelte b/packages/web/src/modals/InsertJoinModal.svelte index bc0687bf1..737629982 100644 --- a/packages/web/src/modals/InsertJoinModal.svelte +++ b/packages/web/src/modals/InsertJoinModal.svelte @@ -104,7 +104,7 @@ Insert join
-
+
Existing table
-
+
New table
-
+
Join
getCurrentEditor()?.isSqlEditor(), onClick: () => getCurrentEditor().formatCode(), }); @@ -203,6 +204,7 @@ onInsert: text => { const editor = domEditor.getEditor(); editor.session.insert(editor.getCursorPosition(), text); + domEditor?.getEditor()?.focus(); }, }); } diff --git a/packages/web/src/utility/clipboard.js b/packages/web/src/utility/clipboard.js index dabbe22d1..a2b7081c3 100644 --- a/packages/web/src/utility/clipboard.js +++ b/packages/web/src/utility/clipboard.js @@ -1,4 +1,6 @@ export function copyTextToClipboard(text) { + const oldFocus = document.activeElement; + const textArea = document.createElement('textarea'); // @@ -53,4 +55,6 @@ export function copyTextToClipboard(text) { } document.body.removeChild(textArea); + + if (oldFocus) oldFocus.focus(); } diff --git a/packages/web/src/widgets/ConnectionList.svelte b/packages/web/src/widgets/ConnectionList.svelte index dd912072b..9bc676e97 100644 --- a/packages/web/src/widgets/ConnectionList.svelte +++ b/packages/web/src/widgets/ConnectionList.svelte @@ -32,7 +32,7 @@ - + Refresh