From ca4ff95316e68ae8b2022b3896fad4bd095e1415 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 28 Jan 2021 08:16:31 +0100 Subject: [PATCH 01/33] optimalized connection ping --- .../api/src/controllers/serverConnections.js | 8 +++++++- packages/web/src/appobj/ConnectionAppObject.js | 4 ++-- packages/web/src/utility/ConnectionsPinger.js | 17 +++++++++++++---- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/api/src/controllers/serverConnections.js b/packages/api/src/controllers/serverConnections.js index bbb058c5f..17c7154ef 100644 --- a/packages/api/src/controllers/serverConnections.js +++ b/packages/api/src/controllers/serverConnections.js @@ -8,6 +8,7 @@ const lock = new AsyncLock(); module.exports = { opened: [], closed: {}, + lastPinged: {}, handle_databases(conid, { databases }) { const existing = this.opened.find(x => x.conid == conid); @@ -88,7 +89,12 @@ module.exports = { ping_meta: 'post', async ping({ connections }) { await Promise.all( - connections.map(async conid => { + _.uniq(connections).map(async conid => { + const last = this.lastPinged[conid]; + if (last && new Date().getTime() - last < 30 * 1000) { + return Promise.resolve(); + } + this.lastPinged[conid] = new Date().getTime(); const opened = await this.ensureOpened(conid); opened.subprocess.send({ msgtype: 'ping' }); }) diff --git a/packages/web/src/appobj/ConnectionAppObject.js b/packages/web/src/appobj/ConnectionAppObject.js index b13313b26..4e5c7c3b0 100644 --- a/packages/web/src/appobj/ConnectionAppObject.js +++ b/packages/web/src/appobj/ConnectionAppObject.js @@ -40,7 +40,7 @@ function Menu({ data }) { setOpenedConnections(list => list.filter(x => x != data._id)); }; const handleConnect = () => { - setOpenedConnections(list => [...list, data._id]); + setOpenedConnections(list => _.uniq([...list, data._id])); }; return ( <> @@ -72,7 +72,7 @@ function ConnectionAppObject({ data, commonProps }) { const extensions = useExtensions(); const isBold = _.get(currentDatabase, 'connection._id') == _id; - const onClick = () => setOpenedConnections(c => [...c, _id]); + const onClick = () => setOpenedConnections(c => _.uniq([...c, _id])); let statusIcon = null; let statusTitle = null; diff --git a/packages/web/src/utility/ConnectionsPinger.js b/packages/web/src/utility/ConnectionsPinger.js index 0af32169a..67784ca45 100644 --- a/packages/web/src/utility/ConnectionsPinger.js +++ b/packages/web/src/utility/ConnectionsPinger.js @@ -7,9 +7,11 @@ export default function ConnectionsPinger({ children }) { const openedConnections = useOpenedConnections(); const currentDatabase = useCurrentDatabase(); - const doPing = () => { + const doServerPing = () => { axios.post('server-connections/ping', { connections: openedConnections }); + }; + const doDatabasePing = () => { const database = _.get(currentDatabase, 'name'); const conid = _.get(currentDatabase, 'connection._id'); if (conid && database) { @@ -18,9 +20,16 @@ export default function ConnectionsPinger({ children }) { }; React.useEffect(() => { - doPing(); - const handle = window.setInterval(doPing, 30 * 1000); + doServerPing(); + const handle = window.setInterval(doServerPing, 30 * 1000); return () => window.clearInterval(handle); - }, [openedConnections, currentDatabase]); + }, [openedConnections]); + + React.useEffect(() => { + doDatabasePing(); + const handle = window.setInterval(doDatabasePing, 30 * 1000); + return () => window.clearInterval(handle); + }, [currentDatabase]); + return children; } From a5c1966a9463cc282e42efbd81e136f5fd41996c Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 28 Jan 2021 09:12:10 +0100 Subject: [PATCH 02/33] makeUniqueColumnNames function --- packages/tools/src/nameTools.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/tools/src/nameTools.ts b/packages/tools/src/nameTools.ts index cabecb01b..cebe2796a 100644 --- a/packages/tools/src/nameTools.ts +++ b/packages/tools/src/nameTools.ts @@ -49,3 +49,15 @@ export function findObjectLike( export function findForeignKeyForColumn(table: TableInfo, column: ColumnInfo) { return (table.foreignKeys || []).find(fk => fk.columns.find(col => col.columnName == column.columnName)); } + +export function makeUniqueColumnNames(res: ColumnInfo[]) { + const usedNames = new Set(); + for (let i = 0; i < res.length; i++) { + if (usedNames.has(res[i].columnName)) { + let suffix = 2; + while (usedNames.has(`${res[i].columnName}${suffix}`)) suffix++; + res[i].columnName = `${res[i].columnName}${suffix}`; + } + usedNames.add(res[i].columnName); + } +} From 75ca3cbb11bd3d3fe54636377e40e2013353f01d Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 28 Jan 2021 09:12:38 +0100 Subject: [PATCH 03/33] packages-tools v1.0.8 --- packages/tools/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tools/package.json b/packages/tools/package.json index a4b8569f9..d2e5e3ded 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -1,5 +1,5 @@ { - "version": "1.0.7", + "version": "1.0.8", "name": "dbgate-tools", "main": "lib/index.js", "typings": "lib/index.d.ts", From 420a58380a0c643bc50e828d2762a8035ad7e239 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 28 Jan 2021 09:44:48 +0100 Subject: [PATCH 04/33] upgraded required plugin-postgres version --- packages/api/src/controllers/plugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/controllers/plugins.js b/packages/api/src/controllers/plugins.js index f3bfd1bb9..69abca738 100644 --- a/packages/api/src/controllers/plugins.js +++ b/packages/api/src/controllers/plugins.js @@ -30,7 +30,7 @@ const hasPermission = require('../utility/hasPermission'); const preinstallPluginMinimalVersions = { 'dbgate-plugin-mssql': '1.0.10', 'dbgate-plugin-mysql': '1.0.3', - 'dbgate-plugin-postgres': '1.0.2', + 'dbgate-plugin-postgres': '1.0.3', 'dbgate-plugin-csv': '1.0.8', 'dbgate-plugin-excel': '1.0.6', }; From a9cb9f1874497d80af3442698ab87825f5cae420 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 28 Jan 2021 09:48:33 +0100 Subject: [PATCH 05/33] style fix --- packages/web/src/freetable/MacroDetail.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/freetable/MacroDetail.js b/packages/web/src/freetable/MacroDetail.js index ac222d204..078415135 100644 --- a/packages/web/src/freetable/MacroDetail.js +++ b/packages/web/src/freetable/MacroDetail.js @@ -15,7 +15,7 @@ const Container = styled.div` display: flex; justify-content: space-between; align-items: center; - background: #ddeeee; + background: ${props => props.theme.gridheader_background_cyan[0]}; height: ${dimensions.toolBar.height}px; min-height: ${dimensions.toolBar.height}px; overflow: hidden; From 99381536d7d04e7c2e262b241d84b0af29365d12 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 28 Jan 2021 10:05:27 +0100 Subject: [PATCH 06/33] numbering tabs --- packages/web/src/appobj/DatabaseAppObject.js | 2 +- packages/web/src/appobj/SavedFileAppObject.js | 2 +- packages/web/src/datagrid/DataGridCore.js | 4 ++-- packages/web/src/datagrid/SqlDataGridCore.js | 4 ++-- packages/web/src/freetable/useNewFreeTable.js | 2 +- packages/web/src/modals/ImportExportModal.js | 2 +- packages/web/src/query/useNewQuery.js | 4 ++-- packages/web/src/utility/useOpenNewTab.js | 11 ++++++++++- packages/web/src/widgets/Toolbar.js | 4 ++-- 9 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/web/src/appobj/DatabaseAppObject.js b/packages/web/src/appobj/DatabaseAppObject.js index c8a137b5a..04cef6bd9 100644 --- a/packages/web/src/appobj/DatabaseAppObject.js +++ b/packages/web/src/appobj/DatabaseAppObject.js @@ -20,7 +20,7 @@ function Menu({ data }) { const handleNewQuery = () => { openNewTab({ - title: 'Query', + title: 'Query #', icon: 'img sql-file', tooltip, tabComponent: 'QueryTab', diff --git a/packages/web/src/appobj/SavedFileAppObject.js b/packages/web/src/appobj/SavedFileAppObject.js index 244b7cd1d..3feb1e9fb 100644 --- a/packages/web/src/appobj/SavedFileAppObject.js +++ b/packages/web/src/appobj/SavedFileAppObject.js @@ -104,7 +104,7 @@ export function SavedSqlFileAppObject({ data, commonProps }) { openNewTab( { - title: 'Shell', + title: 'Shell #', icon: 'img shell', tabComponent: 'ShellTab', }, diff --git a/packages/web/src/datagrid/DataGridCore.js b/packages/web/src/datagrid/DataGridCore.js index 63f0cc9f8..e1ea0dc28 100644 --- a/packages/web/src/datagrid/DataGridCore.js +++ b/packages/web/src/datagrid/DataGridCore.js @@ -353,7 +353,7 @@ export default function DataGridCore(props) { const handleOpenFreeTable = () => { openNewTab( { - title: 'selection', + title: 'Data #', icon: 'img free-table', tabComponent: 'FreeTableTab', props: {}, @@ -365,7 +365,7 @@ export default function DataGridCore(props) { const handleOpenChart = () => { openNewTab( { - title: 'Chart', + title: 'Chart #', icon: 'img chart', tabComponent: 'ChartTab', props: {}, diff --git a/packages/web/src/datagrid/SqlDataGridCore.js b/packages/web/src/datagrid/SqlDataGridCore.js index 62c02ec85..fd3fc60af 100644 --- a/packages/web/src/datagrid/SqlDataGridCore.js +++ b/packages/web/src/datagrid/SqlDataGridCore.js @@ -83,7 +83,7 @@ export default function SqlDataGridCore(props) { function openActiveChart() { openNewTab( { - title: 'Chart', + title: 'Chart #', icon: 'img chart', tabComponent: 'ChartTab', props: { @@ -104,7 +104,7 @@ export default function SqlDataGridCore(props) { function openQuery() { openNewTab( { - title: 'Query', + title: 'Query #', icon: 'img sql-file', tabComponent: 'QueryTab', props: { diff --git a/packages/web/src/freetable/useNewFreeTable.js b/packages/web/src/freetable/useNewFreeTable.js index 947e4cde5..97830a607 100644 --- a/packages/web/src/freetable/useNewFreeTable.js +++ b/packages/web/src/freetable/useNewFreeTable.js @@ -6,7 +6,7 @@ export default function useNewFreeTable() { return ({ title = undefined, ...props } = {}) => openNewTab({ - title: title || 'Table', + title: title || 'Data #', icon: 'img free-table', tabComponent: 'FreeTableTab', props, diff --git a/packages/web/src/modals/ImportExportModal.js b/packages/web/src/modals/ImportExportModal.js index 17b34827c..02836387f 100644 --- a/packages/web/src/modals/ImportExportModal.js +++ b/packages/web/src/modals/ImportExportModal.js @@ -100,7 +100,7 @@ function GenerateSctriptButton({ modalState }) { const code = await createImpExpScript(extensions, values); openNewTab( { - title: 'Shell', + title: 'Shell #', icon: 'img shell', tabComponent: 'ShellTab', }, diff --git a/packages/web/src/query/useNewQuery.js b/packages/web/src/query/useNewQuery.js index 53cdd749d..ee796771e 100644 --- a/packages/web/src/query/useNewQuery.js +++ b/packages/web/src/query/useNewQuery.js @@ -14,7 +14,7 @@ export default function useNewQuery() { return ({ title = undefined, initialData = undefined, ...props } = {}) => openNewTab( { - title: title || 'Query', + title: title || 'Query #', icon: 'img sql-file', tooltip, tabComponent: 'QueryTab', @@ -40,7 +40,7 @@ export function useNewQueryDesign() { return ({ title = undefined, initialData = undefined, ...props } = {}) => openNewTab( { - title: title || 'Query', + title: title || 'Query #', icon: 'img query-design', tooltip, tabComponent: 'QueryDesignTab', diff --git a/packages/web/src/utility/useOpenNewTab.js b/packages/web/src/utility/useOpenNewTab.js index 611b8fd61..0a24181ea 100644 --- a/packages/web/src/utility/useOpenNewTab.js +++ b/packages/web/src/utility/useOpenNewTab.js @@ -42,6 +42,15 @@ export default function useOpenNewTab() { return; } + // new tab will be created + if (newTab.title.endsWith('#')) { + const numbers = openedTabs + .filter(x => x.title && x.title.startsWith(newTab.title)) + .map(x => parseInt(x.title.substring(newTab.title.length))); + + newTab.title = `${newTab.title}${numbers.length > 0 ? _.max(numbers) + 1 : 1}`; + } + const tabid = uuidv1(); if (initialData) { for (const key of _.keys(initialData)) { @@ -61,7 +70,7 @@ export default function useOpenNewTab() { }, ]); }, - [setOpenedTabs] + [setOpenedTabs, openedTabs] ); return openNewTab; diff --git a/packages/web/src/widgets/Toolbar.js b/packages/web/src/widgets/Toolbar.js index fd01432ce..e5b835995 100644 --- a/packages/web/src/widgets/Toolbar.js +++ b/packages/web/src/widgets/Toolbar.js @@ -91,7 +91,7 @@ export default function ToolBar({ toolbarPortalRef }) { const newMarkdown = () => { openNewTab({ - title: 'Page', + title: 'Page #', tabComponent: 'MarkdownEditorTab', icon: 'img markdown', }); @@ -103,7 +103,7 @@ export default function ToolBar({ toolbarPortalRef }) { const newShell = () => { openNewTab({ - title: 'Shell', + title: 'Shell #', icon: 'img shell', tabComponent: 'ShellTab', }); From cb67b57faf5bcc269d10823d6276ab03abb61fd6 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 28 Jan 2021 10:07:02 +0100 Subject: [PATCH 07/33] numbering tabs fix --- packages/web/src/utility/useOpenNewTab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web/src/utility/useOpenNewTab.js b/packages/web/src/utility/useOpenNewTab.js index 0a24181ea..5811e29e1 100644 --- a/packages/web/src/utility/useOpenNewTab.js +++ b/packages/web/src/utility/useOpenNewTab.js @@ -45,7 +45,7 @@ export default function useOpenNewTab() { // new tab will be created if (newTab.title.endsWith('#')) { const numbers = openedTabs - .filter(x => x.title && x.title.startsWith(newTab.title)) + .filter(x => x.closedTime == null && x.title && x.title.startsWith(newTab.title)) .map(x => parseInt(x.title.substring(newTab.title.length))); newTab.title = `${newTab.title}${numbers.length > 0 ? _.max(numbers) + 1 : 1}`; From 8396e726ecb519a12246441d21b53cec6ae64180 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 28 Jan 2021 10:10:41 +0100 Subject: [PATCH 08/33] numbering tabs --- packages/web/src/utility/useOpenNewTab.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/web/src/utility/useOpenNewTab.js b/packages/web/src/utility/useOpenNewTab.js index 5811e29e1..dc997eb1b 100644 --- a/packages/web/src/utility/useOpenNewTab.js +++ b/packages/web/src/utility/useOpenNewTab.js @@ -7,6 +7,14 @@ import { useOpenedTabs, useSetOpenedTabs } from './globalState'; import tabs from '../tabs'; import { setSelectedTabFunc } from './common'; +function findFreeNumber(numbers) { + if (numbers.length == 0) return 1; + return _.max(numbers) + 1; + // let res = 1; + // while (numbers.includes(res)) res += 1; + // return res; +} + export default function useOpenNewTab() { const setOpenedTabs = useSetOpenedTabs(); const openedTabs = useOpenedTabs(); @@ -48,7 +56,7 @@ export default function useOpenNewTab() { .filter(x => x.closedTime == null && x.title && x.title.startsWith(newTab.title)) .map(x => parseInt(x.title.substring(newTab.title.length))); - newTab.title = `${newTab.title}${numbers.length > 0 ? _.max(numbers) + 1 : 1}`; + newTab.title = `${newTab.title}${findFreeNumber(numbers)}`; } const tabid = uuidv1(); From 51ba9d3b5a1d70c775ac1348dd01a1039fc081a3 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 28 Jan 2021 12:49:00 +0100 Subject: [PATCH 09/33] statusbar - show query execution duration --- packages/web/src/Screen.js | 5 +- packages/web/src/TabContent.js | 13 ++- packages/web/src/tabs/QueryTab.js | 21 ++++- packages/web/src/utility/useTimerLabel.js | 37 ++++++++ packages/web/src/widgets/StatusBar.js | 105 ++++++++++++---------- 5 files changed, 128 insertions(+), 53 deletions(-) create mode 100644 packages/web/src/utility/useTimerLabel.js diff --git a/packages/web/src/Screen.js b/packages/web/src/Screen.js index db7ebae6e..8299dce9f 100644 --- a/packages/web/src/Screen.js +++ b/packages/web/src/Screen.js @@ -100,6 +100,7 @@ export default function Screen() { ? dimensions.widgetMenu.iconSize + leftPanelWidth + dimensions.splitter.thickness : dimensions.widgetMenu.iconSize; const toolbarPortalRef = React.useRef(); + const statusbarPortalRef = React.useRef(); const onSplitDown = useSplitterDrag('clientX', diff => setLeftPanelWidth(v => v + diff)); const { getRootProps, getInputProps, isDragActive } = useUploadsZone(); @@ -131,10 +132,10 @@ export default function Screen() { - + - + diff --git a/packages/web/src/TabContent.js b/packages/web/src/TabContent.js index f4733d669..f90da055f 100644 --- a/packages/web/src/TabContent.js +++ b/packages/web/src/TabContent.js @@ -18,12 +18,18 @@ const TabContainerStyled = styled.div` `; function TabContainer({ TabComponent, ...props }) { - const { tabVisible, tabid, toolbarPortalRef } = props; + const { tabVisible, tabid, toolbarPortalRef, statusbarPortalRef } = props; return ( // @ts-ignore - + ); @@ -42,7 +48,7 @@ function createTabComponent(selectedTab) { return null; } -export default function TabContent({ toolbarPortalRef }) { +export default function TabContent({ toolbarPortalRef, statusbarPortalRef }) { const files = useOpenedTabs(); const [mountedTabs, setMountedTabs] = React.useState({}); @@ -84,6 +90,7 @@ export default function TabContent({ toolbarPortalRef }) { tabid={tabid} tabVisible={tabVisible} toolbarPortalRef={toolbarPortalRef} + statusbarPortalRef={statusbarPortalRef} TabComponent={TabComponent} /> ); diff --git a/packages/web/src/tabs/QueryTab.js b/packages/web/src/tabs/QueryTab.js index a945fc861..bc50240b7 100644 --- a/packages/web/src/tabs/QueryTab.js +++ b/packages/web/src/tabs/QueryTab.js @@ -21,8 +21,19 @@ import useEditorData from '../utility/useEditorData'; import applySqlTemplate from '../utility/applySqlTemplate'; import LoadingInfo from '../widgets/LoadingInfo'; import useExtensions from '../utility/useExtensions'; +import useTimerLabel from '../utility/useTimerLabel'; +import { StatusBarItem } from '../widgets/StatusBar'; -export default function QueryTab({ tabid, conid, database, initialArgs, tabVisible, toolbarPortalRef, ...other }) { +export default function QueryTab({ + tabid, + conid, + database, + initialArgs, + tabVisible, + toolbarPortalRef, + statusbarPortalRef, + ...other +}) { const [sessionId, setSessionId] = React.useState(null); const [visibleResultTabs, setVisibleResultTabs] = React.useState(false); const [executeNumber, setExecuteNumber] = React.useState(0); @@ -31,6 +42,7 @@ export default function QueryTab({ tabid, conid, database, initialArgs, tabVisib const [busy, setBusy] = React.useState(false); const saveFileModalState = useModalState(); const extensions = useExtensions(); + const timerLabel = useTimerLabel(); const { editorData, setEditorData, isLoading } = useEditorData({ tabid, loadFromArgs: @@ -43,6 +55,7 @@ export default function QueryTab({ tabid, conid, database, initialArgs, tabVisib const handleSessionDone = React.useCallback(() => { setBusy(false); + timerLabel.stop(); }, []); React.useEffect(() => { @@ -77,6 +90,7 @@ export default function QueryTab({ tabid, conid, database, initialArgs, tabVisib setSessionId(sesid); } setBusy(true); + timerLabel.start(); await axios.post('sessions/execute-query', { sesid, sql: selectedText || editorData, @@ -95,6 +109,7 @@ export default function QueryTab({ tabid, conid, database, initialArgs, tabVisib }); setSessionId(null); setBusy(false); + timerLabel.stop(); }; const handleKeyDown = (data, hash, keyString, keyCode, event) => { @@ -167,6 +182,10 @@ export default function QueryTab({ tabid, conid, database, initialArgs, tabVisib />, toolbarPortalRef.current )} + {statusbarPortalRef && + statusbarPortalRef.current && + tabVisible && + ReactDOM.createPortal({timerLabel.text}, statusbarPortalRef.current)} { + if (busy) { + setDuration(0); + const handle = setInterval(() => setDuration(x => x + 1), 1000); + return () => window.clearInterval(handle); + } + }, [busy]); + + const start = React.useCallback(() => { + setBusy(true); + }, []); + + const stop = React.useCallback(() => { + setBusy(false); + }, []); + + return { + start, + stop, + text: formatSeconds(duration), + duration, + }; +} diff --git a/packages/web/src/widgets/StatusBar.js b/packages/web/src/widgets/StatusBar.js index 097b4c135..7d4849b69 100644 --- a/packages/web/src/widgets/StatusBar.js +++ b/packages/web/src/widgets/StatusBar.js @@ -10,9 +10,10 @@ const Container = styled.div` display: flex; color: ${props => props.theme.statusbar_font1}; align-items: stretch; + justify-content: space-between; `; -const Item = styled.div` +export const StatusBarItem = styled.div` padding: 2px 10px; // margin: auto; // flex-grow: 0; @@ -30,62 +31,72 @@ const InfoWrapper = styled.span` props.theme.statusbar_font_green[5]}; `; -export default function StatusBar() { +const StatusbarContainer = styled.div` + display: flex; + // align-items: flex-end; + // display: flex; + // user-select: none; +`; + +export default function StatusBar({ statusbarPortalRef }) { const { name, connection } = useCurrentDatabase() || {}; const status = useDatabaseStatus(connection ? { conid: connection._id, database: name } : {}); const { displayName, server, user, engine } = connection || {}; const theme = useTheme(); return ( - {name && ( - - {name} - - )} - {(displayName || server) && ( - - {displayName || server} - - )} + + {name && ( + + {name} + + )} + {(displayName || server) && ( + + {displayName || server} + + )} - {user && ( - - {user} - - )} + {user && ( + + {user} + + )} - {connection && status && ( - - {status.name == 'pending' && ( + {connection && status && ( + + {status.name == 'pending' && ( + <> + Loading + + )} + {status.name == 'ok' && ( + <> + + + {' '} + Connected + + )} + {status.name == 'error' && ( + <> + + + {' '} + Error + + )} + + )} + {!connection && ( + <> - Loading + Not connected - )} - {status.name == 'ok' && ( - <> - - - {' '} - Connected - - )} - {status.name == 'error' && ( - <> - - - {' '} - Error - - )} - - )} - {!connection && ( - - <> - Not connected - - - )} + + )} + + ); } From c2b7c775c0e1ae24c7f383fb68f504033194fcec Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 28 Jan 2021 12:56:06 +0100 Subject: [PATCH 10/33] code cleanup --- packages/web/src/widgets/StatusBar.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/web/src/widgets/StatusBar.js b/packages/web/src/widgets/StatusBar.js index 7d4849b69..621c320e9 100644 --- a/packages/web/src/widgets/StatusBar.js +++ b/packages/web/src/widgets/StatusBar.js @@ -15,8 +15,6 @@ const Container = styled.div` export const StatusBarItem = styled.div` padding: 2px 10px; - // margin: auto; - // flex-grow: 0; `; const ErrorWrapper = styled.span` @@ -33,9 +31,6 @@ const InfoWrapper = styled.span` const StatusbarContainer = styled.div` display: flex; - // align-items: flex-end; - // display: flex; - // user-select: none; `; export default function StatusBar({ statusbarPortalRef }) { From 45d99a41264e41b995848cc1609a92f360032d53 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 28 Jan 2021 13:00:24 +0100 Subject: [PATCH 11/33] timer labels in query design tab and shell tab --- packages/web/src/tabs/QueryDesignTab.js | 20 +++++++++++++++++++- packages/web/src/tabs/ShellTab.js | 12 +++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/web/src/tabs/QueryDesignTab.js b/packages/web/src/tabs/QueryDesignTab.js index c50ff5bdc..1d03eeea7 100644 --- a/packages/web/src/tabs/QueryDesignTab.js +++ b/packages/web/src/tabs/QueryDesignTab.js @@ -25,8 +25,18 @@ import QueryDesignColumns from '../designer/QueryDesignColumns'; import { findEngineDriver } from 'dbgate-tools'; import { generateDesignedQuery } from '../designer/designerTools'; import useUndoReducer from '../utility/useUndoReducer'; +import { StatusBarItem } from '../widgets/StatusBar'; +import useTimerLabel from '../utility/useTimerLabel'; -export default function QueryDesignTab({ tabid, conid, database, tabVisible, toolbarPortalRef, ...other }) { +export default function QueryDesignTab({ + tabid, + conid, + database, + tabVisible, + toolbarPortalRef, + statusbarPortalRef, + ...other +}) { const [sessionId, setSessionId] = React.useState(null); const [visibleResultTabs, setVisibleResultTabs] = React.useState(false); const [executeNumber, setExecuteNumber] = React.useState(0); @@ -49,6 +59,7 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too }, { mergeNearActions: true } ); + const timerLabel = useTimerLabel(); React.useEffect(() => { // @ts-ignore @@ -61,6 +72,7 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too const handleSessionDone = React.useCallback(() => { setBusy(false); + timerLabel.stop(); }, []); const generatePreview = (value, engine) => { @@ -114,6 +126,7 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too setSessionId(sesid); } setBusy(true); + timerLabel.start(); await axios.post('sessions/execute-query', { sesid, sql: sqlPreview, @@ -126,6 +139,7 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too }); setSessionId(null); setBusy(false); + timerLabel.stop(); }; const handleKeyDown = React.useCallback( @@ -200,6 +214,10 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too />, toolbarPortalRef.current )} + {statusbarPortalRef && + statusbarPortalRef.current && + tabVisible && + ReactDOM.createPortal({timerLabel.text}, statusbarPortalRef.current)} { setBusy(false); + timerLabel.stop(); }, []); React.useEffect(() => { @@ -69,12 +73,14 @@ export default function ShellTab({ tabid, tabVisible, toolbarPortalRef, ...other runid = resp.data.runid; setRunnerId(runid); setBusy(true); + timerLabel.start(); }; const handleCancel = () => { axios.post('runners/cancel', { runid: runnerId, }); + timerLabel.stop(); }; const handleKeyDown = (data, hash, keyString, keyCode, event) => { @@ -128,6 +134,10 @@ export default function ShellTab({ tabid, tabVisible, toolbarPortalRef, ...other />, toolbarPortalRef.current )} + {statusbarPortalRef && + statusbarPortalRef.current && + tabVisible && + ReactDOM.createPortal({timerLabel.text}, statusbarPortalRef.current)} Date: Thu, 28 Jan 2021 15:29:45 +0100 Subject: [PATCH 12/33] upgrade mysql plugin dependency --- packages/api/src/controllers/plugins.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/controllers/plugins.js b/packages/api/src/controllers/plugins.js index 69abca738..86bcdf040 100644 --- a/packages/api/src/controllers/plugins.js +++ b/packages/api/src/controllers/plugins.js @@ -29,7 +29,7 @@ const hasPermission = require('../utility/hasPermission'); const preinstallPluginMinimalVersions = { 'dbgate-plugin-mssql': '1.0.10', - 'dbgate-plugin-mysql': '1.0.3', + 'dbgate-plugin-mysql': '1.0.4', 'dbgate-plugin-postgres': '1.0.3', 'dbgate-plugin-csv': '1.0.8', 'dbgate-plugin-excel': '1.0.6', From 420e94600e68442d8db70734b5185ba00b1673d6 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 28 Jan 2021 16:41:30 +0100 Subject: [PATCH 13/33] closed tab - show more info --- packages/web/src/appobj/AppObjectCore.js | 56 ++++++++++--------- packages/web/src/appobj/ClosedTabAppObject.js | 30 +++++++--- packages/web/src/tabs/QueryTab.js | 35 ++++++++++++ packages/web/src/widgets/FavoritesWidget.js | 8 +-- 4 files changed, 91 insertions(+), 38 deletions(-) diff --git a/packages/web/src/appobj/AppObjectCore.js b/packages/web/src/appobj/AppObjectCore.js index 15978b0a8..58efa6b2d 100644 --- a/packages/web/src/appobj/AppObjectCore.js +++ b/packages/web/src/appobj/AppObjectCore.js @@ -49,6 +49,7 @@ export function AppObjectCore({ extInfo = undefined, statusTitle = undefined, disableHover = false, + children = null, Menu = undefined, ...other }) { @@ -63,31 +64,34 @@ export function AppObjectCore({ }; return ( - { - if (onClick) onClick(data); - if (onClick2) onClick2(data); - if (onClick3) onClick3(data); - }} - theme={theme} - isBold={isBold} - draggable - onDragStart={e => { - e.dataTransfer.setData('app_object_drag_data', JSON.stringify(data)); - }} - disableHover={disableHover} - {...other} - > - {prefix} - {isBusy ? : } - {title} - {statusIcon && ( - - - - )} - {extInfo && {extInfo}} - + <> + { + if (onClick) onClick(data); + if (onClick2) onClick2(data); + if (onClick3) onClick3(data); + }} + theme={theme} + isBold={isBold} + draggable + onDragStart={e => { + e.dataTransfer.setData('app_object_drag_data', JSON.stringify(data)); + }} + disableHover={disableHover} + {...other} + > + {prefix} + {isBusy ? : } + {title} + {statusIcon && ( + + + + )} + {extInfo && {extInfo}} + + {children} + ); } diff --git a/packages/web/src/appobj/ClosedTabAppObject.js b/packages/web/src/appobj/ClosedTabAppObject.js index 360d9a2de..0ef7e11b9 100644 --- a/packages/web/src/appobj/ClosedTabAppObject.js +++ b/packages/web/src/appobj/ClosedTabAppObject.js @@ -5,6 +5,14 @@ import { DropDownMenuItem } from '../modals/DropDownMenu'; import { useSetOpenedTabs } from '../utility/globalState'; import { AppObjectCore } from './AppObjectCore'; import { setSelectedTabFunc } from '../utility/common'; +import styled from 'styled-components'; +import { FontIcon } from '../icons'; +import useTheme from '../theme/useTheme'; + +const InfoDiv = styled.div` + margin-left: 30px; + color: ${props => props.theme.left_font3}; +`; function Menu({ data }) { const setOpenedTabs = useSetOpenedTabs(); @@ -25,17 +33,16 @@ function Menu({ data }) { function ClosedTabAppObject({ data, commonProps }) { const { tabid, props, selected, icon, title, closedTime, busy } = data; const setOpenedTabs = useSetOpenedTabs(); + const theme = useTheme(); const onClick = () => { setOpenedTabs(files => setSelectedTabFunc( - files.map( - x => ({ - ...x, - closedTime: x.tabid == tabid ? undefined : x.closedTime, - }), - tabid - ) + files.map(x => ({ + ...x, + closedTime: x.tabid == tabid ? undefined : x.closedTime, + })), + tabid ) ); }; @@ -50,7 +57,14 @@ function ClosedTabAppObject({ data, commonProps }) { onClick={onClick} isBusy={busy} Menu={Menu} - /> + > + {data.props && data.props.database && ( + + {data.props.database} + + )} + {data.contentPreview && {data.contentPreview}} + ); } diff --git a/packages/web/src/tabs/QueryTab.js b/packages/web/src/tabs/QueryTab.js index bc50240b7..4801f29c1 100644 --- a/packages/web/src/tabs/QueryTab.js +++ b/packages/web/src/tabs/QueryTab.js @@ -24,6 +24,24 @@ import useExtensions from '../utility/useExtensions'; import useTimerLabel from '../utility/useTimerLabel'; import { StatusBarItem } from '../widgets/StatusBar'; +function createSqlPreview(sql) { + if (!sql) return undefined; + let data = sql.substring(0, 500); + data = data.replace(/\[[^\]]+\]\./g, ''); + data = data.replace(/\[a-zA-Z0-9_]+\./g, ''); + data = data.replace(/\/\*.*\*\//g, ''); + data = data.replace(/[\[\]]/g, ''); + data = data.replace(/--[^\n]*\n/g, ''); + + for (let step = 1; step <= 5; step++) { + data = data.replace(/\([^\(^\)]+\)/g, ''); + } + data = data.replace(/\s+/g, ' '); + data = data.trim(); + data = data.replace(/^(.{50}[^\s]*).*/, '$1'); + return data; +} + export default function QueryTab({ tabid, conid, @@ -74,6 +92,23 @@ export default function QueryTab({ useUpdateDatabaseForTab(tabVisible, conid, database); const connection = useConnectionInfo({ conid }); + const updateContentPreviewDebounced = React.useRef( + _.debounce( + // @ts-ignore + sql => + changeTab(tabid, setOpenedTabs, tab => ({ + ...tab, + contentPreview: createSqlPreview(sql), + })), + 500 + ) + ); + + React.useEffect(() => { + // @ts-ignore + updateContentPreviewDebounced.current(editorData); + }, [editorData]); + const handleExecute = async () => { if (busy) return; setExecuteNumber(num => num + 1); diff --git a/packages/web/src/widgets/FavoritesWidget.js b/packages/web/src/widgets/FavoritesWidget.js index 27e67da31..ce0f3191f 100644 --- a/packages/web/src/widgets/FavoritesWidget.js +++ b/packages/web/src/widgets/FavoritesWidget.js @@ -44,14 +44,14 @@ export default function FavoritesWidget() { const hasPermission = useHasPermission(); return ( - - - {hasPermission('files/favorites/read') && ( - + )} + + + ); } From df976a84d210abaab729b7a8cef1dafdf2e88327 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 28 Jan 2021 16:56:47 +0100 Subject: [PATCH 14/33] electron menu sinplified --- app/src/electron.js | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/app/src/electron.js b/app/src/electron.js index 9b95b162d..a61f372dc 100644 --- a/app/src/electron.js +++ b/app/src/electron.js @@ -51,12 +51,28 @@ function buildMenu() { mainWindow.webContents.executeJavaScript(`dbgate_newQuery()`); }, }, + { + label: 'Close all tabs', + click() { + mainWindow.webContents.executeJavaScript('dbgate_closeAll()'); + }, + }, + { type: 'separator' }, + { role: 'minimize' }, + { role: 'close' }, ], }, - { - label: 'Edit', - submenu: [{ role: 'copy' }, { role: 'paste' }], - }, + // { + // label: 'Edit', + // submenu: [ + // { role: 'undo' }, + // { role: 'redo' }, + // { type: 'separator' }, + // { role: 'cut' }, + // { role: 'copy' }, + // { role: 'paste' }, + // ], + // }, { label: 'View', submenu: [ @@ -71,20 +87,6 @@ function buildMenu() { { role: 'togglefullscreen' }, ], }, - { - role: 'window', - submenu: [ - { - label: 'Close all tabs', - click() { - mainWindow.webContents.executeJavaScript('dbgate_closeAll()'); - }, - }, - { type: 'separator' }, - { role: 'minimize' }, - { role: 'close' }, - ], - }, { role: 'help', submenu: [ From fe1c5f5801a0816e87c7b881f1f4b391ad116377 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 28 Jan 2021 18:52:59 +0100 Subject: [PATCH 15/33] electron: save file to custom location --- app/src/electron.js | 6 ++ packages/api/src/controllers/files.js | 5 ++ packages/web/src/TabsPanel.js | 11 +++- .../web/src/appobj/DatabaseObjectAppObject.js | 2 +- packages/web/src/modals/SaveFileModal.js | 57 ++++++++++++++++++- packages/web/src/modals/SaveTabModal.js | 14 +++-- packages/web/src/tabs/ChartTab.js | 1 + packages/web/src/tabs/MarkdownEditorTab.js | 1 + packages/web/src/tabs/QueryDesignTab.js | 1 + packages/web/src/tabs/QueryTab.js | 1 + packages/web/src/tabs/ShellTab.js | 1 + .../web/src/utility/useOpenElectronFile.js | 31 ++++++++++ packages/web/src/utility/useOpenNewTab.js | 11 +++- packages/web/src/widgets/Toolbar.js | 3 + 14 files changed, 132 insertions(+), 13 deletions(-) create mode 100644 packages/web/src/utility/useOpenElectronFile.js diff --git a/app/src/electron.js b/app/src/electron.js index a61f372dc..9eb6b9707 100644 --- a/app/src/electron.js +++ b/app/src/electron.js @@ -51,6 +51,12 @@ function buildMenu() { mainWindow.webContents.executeJavaScript(`dbgate_newQuery()`); }, }, + { + label: 'Open file', + click() { + mainWindow.webContents.executeJavaScript(`dbgate_openFile()`); + }, + }, { label: 'Close all tabs', click() { diff --git a/packages/api/src/controllers/files.js b/packages/api/src/controllers/files.js index efb75d1bc..ad3e91cb2 100644 --- a/packages/api/src/controllers/files.js +++ b/packages/api/src/controllers/files.js @@ -78,6 +78,11 @@ module.exports = { } }, + saveAs_meta: 'post', + async saveAs({ filePath, data, format }) { + await fs.writeFile(filePath, serialize(format, data)); + }, + favorites_meta: 'get', async favorites() { if (!hasPermission(`files/favorites/read`)) return []; diff --git a/packages/web/src/TabsPanel.js b/packages/web/src/TabsPanel.js index c976e50e2..1da14e220 100644 --- a/packages/web/src/TabsPanel.js +++ b/packages/web/src/TabsPanel.js @@ -124,6 +124,15 @@ function getDbIcon(key) { return 'icon file'; } +function buildTooltip(tab) { + let res = tab.tooltip; + if (tab.props && tab.props.savedFilePath) { + if (res) res += '\n'; + res += tab.props.savedFilePath; + } + return res; +} + export default function TabsPanel() { // const formatDbKey = (conid, database) => `${database}-${conid}`; const theme = useTheme(); @@ -252,7 +261,7 @@ export default function TabsPanel() { {_.sortBy(tabsByDb[dbKey], ['title', 'tabid']).map(tab => ( handleTabClick(e, tab.tabid)} diff --git a/packages/web/src/appobj/DatabaseObjectAppObject.js b/packages/web/src/appobj/DatabaseObjectAppObject.js index 88fc4badc..e091eff4e 100644 --- a/packages/web/src/appobj/DatabaseObjectAppObject.js +++ b/packages/web/src/appobj/DatabaseObjectAppObject.js @@ -149,7 +149,7 @@ export async function openDatabaseObjectDetail( openNewTab( { - title: pureName, + title: 'Query #', tooltip, icon: sqlTemplate ? 'img sql-file' : icons[objectTypeField], tabComponent: sqlTemplate ? 'QueryTab' : tabComponent, diff --git a/packages/web/src/modals/SaveFileModal.js b/packages/web/src/modals/SaveFileModal.js index 583fb8078..829eea28c 100644 --- a/packages/web/src/modals/SaveFileModal.js +++ b/packages/web/src/modals/SaveFileModal.js @@ -6,14 +6,52 @@ import ModalHeader from './ModalHeader'; import ModalContent from './ModalContent'; import ModalFooter from './ModalFooter'; import { FormProvider } from '../utility/FormProvider'; +import FormStyledButton from '../widgets/FormStyledButton'; +import getElectron from '../utility/getElectron'; + +export default function SaveFileModal({ + data, + folder, + format, + modalState, + name, + fileExtension, + filePath, + onSave = undefined, +}) { + const electron = getElectron(); -export default function SaveFileModal({ data, folder, format, modalState, name, onSave = undefined }) { const handleSubmit = async values => { const { name } = values; await axios.post('files/save', { folder, file: name, data, format }); modalState.close(); - if (onSave) onSave(name); + if (onSave) { + onSave(name, { + savedFile: name, + savedFolder: folder, + savedFilePath: null, + }); + } }; + + const handleSaveAs = async filePath => { + const path = window.require('path'); + + const parsed = path.parse(filePath); + if (!parsed.ext) filePath += `.${fileExtension}`; + + await axios.post('files/save-as', { filePath, data, format }); + modalState.close(); + + if (onSave) { + onSave(parsed.name, { + savedFile: null, + savedFolder: null, + savedFilePath: filePath, + }); + } + }; + return ( Save file @@ -23,6 +61,21 @@ export default function SaveFileModal({ data, folder, format, modalState, name, + {electron && ( + { + const file = electron.remote.dialog.showSaveDialogSync(electron.remote.getCurrentWindow(), { + filters: { name: `${fileExtension.toUpperCase()} files`, extensions: [fileExtension] }, + defaultPath: filePath, + }); + if (file) { + handleSaveAs(file); + } + }} + /> + )} diff --git a/packages/web/src/modals/SaveTabModal.js b/packages/web/src/modals/SaveTabModal.js index d581649b4..0111d1114 100644 --- a/packages/web/src/modals/SaveTabModal.js +++ b/packages/web/src/modals/SaveTabModal.js @@ -4,22 +4,22 @@ import { useOpenedTabs, useSetOpenedTabs } from '../utility/globalState'; import keycodes from '../utility/keycodes'; import SaveFileModal from './SaveFileModal'; -export default function SaveTabModal({ data, folder, format, modalState, tabid, tabVisible }) { +export default function SaveTabModal({ data, folder, format, modalState, tabid, tabVisible, fileExtension }) { const setOpenedTabs = useSetOpenedTabs(); const openedTabs = useOpenedTabs(); - const { savedFile } = openedTabs.find(x => x.tabid == tabid).props || {}; - const onSave = name => + const { savedFile, savedFilePath } = openedTabs.find(x => x.tabid == tabid).props || {}; + const onSave = (title, newProps) => { changeTab(tabid, setOpenedTabs, tab => ({ ...tab, - title: name, + title, props: { ...tab.props, - savedFile: name, - savedFolder: folder, savedFormat: format, + ...newProps, }, })); + }; const handleKeyboard = React.useCallback( e => { @@ -47,6 +47,8 @@ export default function SaveTabModal({ data, folder, format, modalState, tabid, format={format} modalState={modalState} name={savedFile || 'newFile'} + filePath={savedFilePath} + fileExtension={fileExtension} onSave={onSave} /> ); diff --git a/packages/web/src/tabs/ChartTab.js b/packages/web/src/tabs/ChartTab.js index 3a3882dc4..61a880a29 100644 --- a/packages/web/src/tabs/ChartTab.js +++ b/packages/web/src/tabs/ChartTab.js @@ -63,6 +63,7 @@ export default function ChartTab({ tabVisible, toolbarPortalRef, conid, database format="json" folder="charts" tabid={tabid} + fileExtension='chart' /> {toolbarPortalRef && toolbarPortalRef.current && diff --git a/packages/web/src/tabs/MarkdownEditorTab.js b/packages/web/src/tabs/MarkdownEditorTab.js index b45f1e936..4725464a6 100644 --- a/packages/web/src/tabs/MarkdownEditorTab.js +++ b/packages/web/src/tabs/MarkdownEditorTab.js @@ -75,6 +75,7 @@ export default function MarkdownEditorTab({ tabid, tabVisible, toolbarPortalRef, format="text" folder="markdown" tabid={tabid} + fileExtension='md' /> ); diff --git a/packages/web/src/tabs/QueryDesignTab.js b/packages/web/src/tabs/QueryDesignTab.js index 1d03eeea7..deb2f16d1 100644 --- a/packages/web/src/tabs/QueryDesignTab.js +++ b/packages/web/src/tabs/QueryDesignTab.js @@ -225,6 +225,7 @@ export default function QueryDesignTab({ format="json" folder="query" tabid={tabid} + fileExtension='qdesign' /> ); diff --git a/packages/web/src/tabs/QueryTab.js b/packages/web/src/tabs/QueryTab.js index 4801f29c1..fcf6a8787 100644 --- a/packages/web/src/tabs/QueryTab.js +++ b/packages/web/src/tabs/QueryTab.js @@ -228,6 +228,7 @@ export default function QueryTab({ format="text" folder="sql" tabid={tabid} + fileExtension='sql' /> ); diff --git a/packages/web/src/tabs/ShellTab.js b/packages/web/src/tabs/ShellTab.js index c7cd3766b..f541704cb 100644 --- a/packages/web/src/tabs/ShellTab.js +++ b/packages/web/src/tabs/ShellTab.js @@ -145,6 +145,7 @@ export default function ShellTab({ tabid, tabVisible, toolbarPortalRef, statusba format="text" folder="shell" tabid={tabid} + fileExtension='js' /> ); diff --git a/packages/web/src/utility/useOpenElectronFile.js b/packages/web/src/utility/useOpenElectronFile.js new file mode 100644 index 000000000..b3133f5df --- /dev/null +++ b/packages/web/src/utility/useOpenElectronFile.js @@ -0,0 +1,31 @@ +import useNewQuery from '../query/useNewQuery'; +import getElectron from './getElectron'; + +export default function useOpenElectronFile() { + const electron = getElectron(); + + const newQuery = useNewQuery(); + + return () => { + const filePaths = electron.remote.dialog.showOpenDialogSync(electron.remote.getCurrentWindow(), { + filters: { name: `SQL files`, extensions: ['sql'] }, + }); + const filePath = filePaths && filePaths[0]; + if (filePath) { + if (filePath.match(/.sql$/i)) { + const path = window.require('path'); + const fs = window.require('fs'); + const parsed = path.parse(filePath); + const data = fs.readFileSync(filePath, { encoding: 'utf-8' }); + + newQuery({ + title: parsed.name, + initialData: data, + // @ts-ignore + savedFilePath: filePath, + savedFormat: 'text', + }); + } + } + }; +} diff --git a/packages/web/src/utility/useOpenNewTab.js b/packages/web/src/utility/useOpenNewTab.js index dc997eb1b..2c20dc0d7 100644 --- a/packages/web/src/utility/useOpenNewTab.js +++ b/packages/web/src/utility/useOpenNewTab.js @@ -23,11 +23,16 @@ export default function useOpenNewTab() { async (newTab, initialData = undefined, options) => { let existing = null; - const { savedFile } = newTab.props || {}; - if (savedFile) { + const { savedFile, savedFolder, savedFilePath } = newTab.props || {}; + if (savedFile || savedFilePath) { existing = openedTabs.find( x => - x.props && x.tabComponent == newTab.tabComponent && x.closedTime == null && x.props.savedFile == savedFile + x.props && + x.tabComponent == newTab.tabComponent && + x.closedTime == null && + x.props.savedFile == savedFile && + x.props.savedFolder == savedFolder && + x.props.savedFilePath == savedFilePath ); } diff --git a/packages/web/src/widgets/Toolbar.js b/packages/web/src/widgets/Toolbar.js index e5b835995..3873c1edc 100644 --- a/packages/web/src/widgets/Toolbar.js +++ b/packages/web/src/widgets/Toolbar.js @@ -25,6 +25,7 @@ import tabs from '../tabs'; import FavoriteModal from '../modals/FavoriteModal'; import { useOpenFavorite } from '../appobj/FavoriteFileAppObject'; import ErrorMessageModal from '../modals/ErrorMessageModal'; +import useOpenElectronFile from '../utility/useOpenElectronFile'; const ToolbarContainer = styled.div` display: flex; @@ -48,6 +49,7 @@ export default function ToolBar({ toolbarPortalRef }) { const electron = getElectron(); const favorites = useFavorites(); const openFavorite = useOpenFavorite(); + const openElectronFile = useOpenElectronFile(); const currentTab = openedTabs.find(x => x.selected); @@ -58,6 +60,7 @@ export default function ToolBar({ toolbarPortalRef }) { window['dbgate_newQuery'] = newQuery; window['dbgate_closeAll'] = () => setOpenedTabs([]); window['dbgate_showAbout'] = showAbout; + window['dbgate_openFile'] = openElectronFile; }); const showAbout = () => { From 8d6d1d979e1097081e077d55c2f49c907b58d0e0 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Thu, 28 Jan 2021 18:54:15 +0100 Subject: [PATCH 16/33] v3.9.4-beta.1 --- app/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/package.json b/app/package.json index 83ebc9ba1..b37ccfed0 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "dbgate", - "version": "3.9.3", + "version": "3.9.4-beta.1", "private": true, "author": "Jan Prochazka ", "description": "Opensource database administration tool", From 31dd80b79aff7b93744cb1a131925b2a5fdf2060 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 30 Jan 2021 09:41:50 +0100 Subject: [PATCH 17/33] fixed qorking with tabs --- packages/web/src/appobj/DatabaseObjectAppObject.js | 4 ++-- packages/web/src/datagrid/DataGridCore.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/web/src/appobj/DatabaseObjectAppObject.js b/packages/web/src/appobj/DatabaseObjectAppObject.js index e091eff4e..dbf5bff84 100644 --- a/packages/web/src/appobj/DatabaseObjectAppObject.js +++ b/packages/web/src/appobj/DatabaseObjectAppObject.js @@ -149,7 +149,7 @@ export async function openDatabaseObjectDetail( openNewTab( { - title: 'Query #', + title: sqlTemplate ? 'Query #' : pureName, tooltip, icon: sqlTemplate ? 'img sql-file' : icons[objectTypeField], tabComponent: sqlTemplate ? 'QueryTab' : tabComponent, @@ -245,7 +245,7 @@ function Menu({ data }) { } else if (menu.isQueryDesigner) { openNewTab( { - title: data.pureName, + title: 'Query #', icon: 'img query-design', tabComponent: 'QueryDesignTab', props: { diff --git a/packages/web/src/datagrid/DataGridCore.js b/packages/web/src/datagrid/DataGridCore.js index e1ea0dc28..933089ce7 100644 --- a/packages/web/src/datagrid/DataGridCore.js +++ b/packages/web/src/datagrid/DataGridCore.js @@ -312,7 +312,7 @@ export default function DataGridCore(props) { } } : null, - [formViewAvailable, display] + [formViewAvailable, display, openNewTab] ); if (!columns || columns.length == 0) return ; From d3039a924868e127064839146f0febe0f5e64410 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 30 Jan 2021 10:29:56 +0100 Subject: [PATCH 18/33] useStorage improved - setter never changes (behaves more like useState) --- packages/web/src/utility/useStorage.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/web/src/utility/useStorage.js b/packages/web/src/utility/useStorage.js index cd132c917..fa436897b 100644 --- a/packages/web/src/utility/useStorage.js +++ b/packages/web/src/utility/useStorage.js @@ -15,13 +15,15 @@ export default function useStorage(key, storageObject, initialValue) { return initialValue; } }); + const storedValueRef = React.useRef(storedValue); + storedValueRef.current = storedValue; // Return a wrapped version of useState's setter function that ... // ... persists the new value to localStorage. - const setValue = value => { + const setValue = React.useCallback(value => { try { // Allow value to be a function so we have same API as useState - const valueToStore = value instanceof Function ? value(storedValue) : value; + const valueToStore = value instanceof Function ? value(storedValueRef.current) : value; // Save state setStoredValue(valueToStore); // Save to local storage @@ -31,7 +33,7 @@ export default function useStorage(key, storageObject, initialValue) { console.error(error); console.log('Error saving storage value', key, value); } - }; + }, []); return [storedValue, setValue]; } From dcfefc78a26ecfd26dcb9b744925bb1d7ea89c94 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 30 Jan 2021 10:37:28 +0100 Subject: [PATCH 19/33] fixed save generated content in useEditorData --- packages/web/src/utility/useEditorData.js | 2 ++ packages/web/src/utility/useStorage.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/web/src/utility/useEditorData.js b/packages/web/src/utility/useEditorData.js index aa5d8df1c..c6b80b9ef 100644 --- a/packages/web/src/utility/useEditorData.js +++ b/packages/web/src/utility/useEditorData.js @@ -43,6 +43,8 @@ export default function useEditorData({ tabid, reloadToken = 0, loadFromArgs = n setValue(init); valueRef.current = init; initialDataRef.current = init; + // mark as not saved + changeCounterRef.current += 1; } catch (err) { const message = (err && err.response && err.response.data && err.response.data.error) || 'Loading failed'; setErrorMessage(message); diff --git a/packages/web/src/utility/useStorage.js b/packages/web/src/utility/useStorage.js index fa436897b..175820faf 100644 --- a/packages/web/src/utility/useStorage.js +++ b/packages/web/src/utility/useStorage.js @@ -15,6 +15,8 @@ export default function useStorage(key, storageObject, initialValue) { return initialValue; } }); + + // use storedValue to ref, so that setValue with function argument works without changeing setValue itself const storedValueRef = React.useRef(storedValue); storedValueRef.current = storedValue; From 3907b1ae8ba9fdd7366f07217974ea6ec90548fe Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 30 Jan 2021 10:43:07 +0100 Subject: [PATCH 20/33] v3.9.4-beta.2 --- app/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/package.json b/app/package.json index b37ccfed0..72e8bf7f7 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "dbgate", - "version": "3.9.4-beta.1", + "version": "3.9.4-beta.2", "private": true, "author": "Jan Prochazka ", "description": "Opensource database administration tool", From 84e475192e0ad2c6bcccf934d0b491b77064fb9b Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 30 Jan 2021 10:56:22 +0100 Subject: [PATCH 21/33] upgraded electron - fixed problem with deleted localstorage --- app/package.json | 2 +- app/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/package.json b/app/package.json index 72e8bf7f7..35869e322 100644 --- a/app/package.json +++ b/app/package.json @@ -68,7 +68,7 @@ "devDependencies": { "copyfiles": "^2.2.0", "cross-env": "^6.0.3", - "electron": "11.1.1", + "electron": "11.2.1", "electron-builder": "22.9.1" }, "optionalDependencies": { diff --git a/app/yarn.lock b/app/yarn.lock index 623f4561b..e99033a0b 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -717,10 +717,10 @@ electron-updater@^4.3.5: lodash.isequal "^4.5.0" semver "^7.3.2" -electron@11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/electron/-/electron-11.1.1.tgz#188f036f8282798398dca9513e9bb3b10213e3aa" - integrity sha512-tlbex3xosJgfileN6BAQRotevPRXB/wQIq48QeQ08tUJJrXwE72c8smsM/hbHx5eDgnbfJ2G3a60PmRjHU2NhA== +electron@11.2.1: + version "11.2.1" + resolved "https://registry.yarnpkg.com/electron/-/electron-11.2.1.tgz#8641dd1a62911a1144e0c73c34fd9f37ccc65c2b" + integrity sha512-Im1y29Bnil+Nzs+FCTq01J1OtLbs+2ZGLLllaqX/9n5GgpdtDmZhS/++JHBsYZ+4+0n7asO+JKQgJD+CqPClzg== dependencies: "@electron/get" "^1.0.1" "@types/node" "^12.0.12" From 79fdde73ae1768cdf8cb70176eb50cb533478714 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 30 Jan 2021 11:01:03 +0100 Subject: [PATCH 22/33] v3.9.4-beta.3 --- app/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/package.json b/app/package.json index 35869e322..adbed5ee4 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "dbgate", - "version": "3.9.4-beta.2", + "version": "3.9.4-beta.3", "private": true, "author": "Jan Prochazka ", "description": "Opensource database administration tool", From 059eabf2fa7e276828aaa6a61924881e526d4598 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 30 Jan 2021 12:49:02 +0100 Subject: [PATCH 23/33] rename --- packages/web/src/tabs/FreeTableTab.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/web/src/tabs/FreeTableTab.js b/packages/web/src/tabs/FreeTableTab.js index a366820b9..7495c885b 100644 --- a/packages/web/src/tabs/FreeTableTab.js +++ b/packages/web/src/tabs/FreeTableTab.js @@ -15,7 +15,7 @@ import useEditorData from '../utility/useEditorData'; export default function FreeDataTab({ archiveFolder, archiveFile, tabVisible, toolbarPortalRef, tabid, initialArgs }) { const [config, setConfig] = useGridConfig(tabid); const [modelState, dispatchModel] = useUndoReducer(createFreeTableModel()); - const saveFileModalState = useModalState(); + const saveArchiveModalState = useModalState(); const setOpenedTabs = useSetOpenedTabs(); const { initialData, setEditorData, errorMessage, isLoading } = useEditorData({ tabid, @@ -59,9 +59,9 @@ export default function FreeDataTab({ archiveFolder, archiveFile, tabVisible, to dispatchModel={dispatchModel} tabVisible={tabVisible} toolbarPortalRef={toolbarPortalRef} - onSave={() => saveFileModalState.open()} + onSave={() => saveArchiveModalState.open()} /> - + ); } From 255c3e5ef44142ed91738776d623cc4cffbb3cef Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 30 Jan 2021 18:23:05 +0100 Subject: [PATCH 24/33] improved save file experience --- app/src/electron.js | 23 ++++-- packages/web/src/charts/ChartToolbar.js | 10 +-- .../web/src/designer/QueryDesignToolbar.js | 8 -- packages/web/src/markdown/MarkdownToolbar.js | 10 +-- packages/web/src/modals/SaveFileModal.js | 15 ++-- packages/web/src/modals/SaveTabModal.js | 74 +++++++++++++++---- packages/web/src/query/QueryToolbar.js | 6 +- packages/web/src/query/ShellToolbar.js | 9 +-- packages/web/src/tabs/ChartTab.js | 17 ++--- packages/web/src/tabs/MarkdownEditorTab.js | 16 ++-- packages/web/src/tabs/QueryDesignTab.js | 39 +++++----- packages/web/src/tabs/QueryTab.js | 22 ++++-- packages/web/src/tabs/ShellTab.js | 30 +++----- .../web/src/utility/SaveFileToolbarButton.js | 22 ++++++ packages/web/src/utility/ToolbarPortal.js | 13 ++++ 15 files changed, 182 insertions(+), 132 deletions(-) create mode 100644 packages/web/src/utility/SaveFileToolbarButton.js create mode 100644 packages/web/src/utility/ToolbarPortal.js diff --git a/app/src/electron.js b/app/src/electron.js index 9eb6b9707..6a403a8d7 100644 --- a/app/src/electron.js +++ b/app/src/electron.js @@ -45,29 +45,36 @@ function buildMenu() { mainWindow.webContents.executeJavaScript(`dbgate_createNewConnection()`); }, }, - { - label: 'New query', - click() { - mainWindow.webContents.executeJavaScript(`dbgate_newQuery()`); - }, - }, { label: 'Open file', click() { mainWindow.webContents.executeJavaScript(`dbgate_openFile()`); }, }, + { type: 'separator' }, + { role: 'close' }, + ], + }, + { + label: 'Window', + submenu: [ + { + label: 'New query', + click() { + mainWindow.webContents.executeJavaScript(`dbgate_newQuery()`); + }, + }, + { type: 'separator' }, { label: 'Close all tabs', click() { mainWindow.webContents.executeJavaScript('dbgate_closeAll()'); }, }, - { type: 'separator' }, { role: 'minimize' }, - { role: 'close' }, ], }, + // { // label: 'Edit', // submenu: [ diff --git a/packages/web/src/charts/ChartToolbar.js b/packages/web/src/charts/ChartToolbar.js index fc60b4832..62b3a1aaf 100644 --- a/packages/web/src/charts/ChartToolbar.js +++ b/packages/web/src/charts/ChartToolbar.js @@ -1,17 +1,9 @@ import React from 'react'; -import useHasPermission from '../utility/useHasPermission'; import ToolbarButton from '../widgets/ToolbarButton'; -export default function ChartToolbar({ save, modelState, dispatchModel }) { - const hasPermission = useHasPermission(); - +export default function ChartToolbar({ modelState, dispatchModel }) { return ( <> - {hasPermission('files/charts/write') && ( - - Save - - )} dispatchModel({ type: 'undo' })} icon="icon undo"> Undo diff --git a/packages/web/src/designer/QueryDesignToolbar.js b/packages/web/src/designer/QueryDesignToolbar.js index 22c2fa3c4..c53092e69 100644 --- a/packages/web/src/designer/QueryDesignToolbar.js +++ b/packages/web/src/designer/QueryDesignToolbar.js @@ -1,18 +1,15 @@ import React from 'react'; -import useHasPermission from '../utility/useHasPermission'; import ToolbarButton from '../widgets/ToolbarButton'; export default function QueryDesignToolbar({ execute, isDatabaseDefined, busy, - save, modelState, dispatchModel, isConnected, kill, }) { - const hasPermission = useHasPermission(); return ( <> @@ -21,11 +18,6 @@ export default function QueryDesignToolbar({ Kill - {hasPermission('files/query/write') && ( - - Save - - )} dispatchModel({ type: 'undo' })} icon="icon undo"> Undo diff --git a/packages/web/src/markdown/MarkdownToolbar.js b/packages/web/src/markdown/MarkdownToolbar.js index 108d325ce..7c87dab63 100644 --- a/packages/web/src/markdown/MarkdownToolbar.js +++ b/packages/web/src/markdown/MarkdownToolbar.js @@ -1,17 +1,9 @@ import React from 'react'; -import useHasPermission from '../utility/useHasPermission'; import ToolbarButton from '../widgets/ToolbarButton'; -export default function MarkdownToolbar({ save, showPreview }) { - const hasPermission = useHasPermission(); - +export default function MarkdownToolbar({ showPreview }) { return ( <> - {hasPermission('files/markdown/write') && ( - - Save - - )} Preview diff --git a/packages/web/src/modals/SaveFileModal.js b/packages/web/src/modals/SaveFileModal.js index 829eea28c..db587740d 100644 --- a/packages/web/src/modals/SaveFileModal.js +++ b/packages/web/src/modals/SaveFileModal.js @@ -34,11 +34,10 @@ export default function SaveFileModal({ } }; - const handleSaveAs = async filePath => { + const handleSaveToDisk = async filePath => { const path = window.require('path'); - const parsed = path.parse(filePath); - if (!parsed.ext) filePath += `.${fileExtension}`; + // if (!parsed.ext) filePath += `.${fileExtension}`; await axios.post('files/save-as', { filePath, data, format }); modalState.close(); @@ -67,11 +66,15 @@ export default function SaveFileModal({ value="Save to disk" onClick={() => { const file = electron.remote.dialog.showSaveDialogSync(electron.remote.getCurrentWindow(), { - filters: { name: `${fileExtension.toUpperCase()} files`, extensions: [fileExtension] }, - defaultPath: filePath, + filters: [ + { name: `${fileExtension.toUpperCase()} files`, extensions: [fileExtension] }, + { name: `All files`, extensions: ['*'] }, + ], + defaultPath: filePath || `${name}.${fileExtension}`, + properties: ['showOverwriteConfirmation'], }); if (file) { - handleSaveAs(file); + handleSaveToDisk(file); } }} /> diff --git a/packages/web/src/modals/SaveTabModal.js b/packages/web/src/modals/SaveTabModal.js index 0111d1114..3161292b2 100644 --- a/packages/web/src/modals/SaveTabModal.js +++ b/packages/web/src/modals/SaveTabModal.js @@ -1,12 +1,28 @@ import React from 'react'; +import axios from '../utility/axios'; import { changeTab } from '../utility/common'; import { useOpenedTabs, useSetOpenedTabs } from '../utility/globalState'; import keycodes from '../utility/keycodes'; +import SaveFileToolbarButton from '../utility/SaveFileToolbarButton'; +import ToolbarPortal from '../utility/ToolbarPortal'; +import useHasPermission from '../utility/useHasPermission'; import SaveFileModal from './SaveFileModal'; +import useModalState from './useModalState'; -export default function SaveTabModal({ data, folder, format, modalState, tabid, tabVisible, fileExtension }) { +export default function SaveTabModal({ + data, + folder, + format, + tabid, + tabVisible, + fileExtension, + toolbarPortalRef = undefined, +}) { const setOpenedTabs = useSetOpenedTabs(); const openedTabs = useOpenedTabs(); + const saveFileModalState = useModalState(); + const hasPermission = useHasPermission(); + const canSave = hasPermission(`files/${folder}/write`); const { savedFile, savedFilePath } = openedTabs.find(x => x.tabid == tabid).props || {}; const onSave = (title, newProps) => { @@ -21,35 +37,63 @@ export default function SaveTabModal({ data, folder, format, modalState, tabid, })); }; + const handleSave = async () => { + if (savedFile) { + await axios.post('files/save', { folder, file: savedFile, data, format }); + } + if (savedFilePath) { + await axios.post('files/save-as', { filePath: savedFilePath, data, format }); + } + }; + const handleSaveRef = React.useRef(handleSave); + handleSaveRef.current = handleSave; + const handleKeyboard = React.useCallback( e => { if (e.keyCode == keycodes.s && e.ctrlKey) { e.preventDefault(); - modalState.open(); + if (e.shiftKey) { + saveFileModalState.open(); + } else { + if (savedFile || savedFilePath) handleSaveRef.current(); + else saveFileModalState.open(); + } } }, - [modalState] + [saveFileModalState] ); React.useEffect(() => { - if (tabVisible) { + if (tabVisible && canSave) { document.addEventListener('keydown', handleKeyboard); return () => { document.removeEventListener('keydown', handleKeyboard); }; } - }, [tabVisible, handleKeyboard]); + }, [tabVisible, handleKeyboard, canSave]); return ( - + <> + + + {canSave && ( + + + + )} + ); } diff --git a/packages/web/src/query/QueryToolbar.js b/packages/web/src/query/QueryToolbar.js index 72567f0f3..7c51409be 100644 --- a/packages/web/src/query/QueryToolbar.js +++ b/packages/web/src/query/QueryToolbar.js @@ -2,7 +2,7 @@ import React from 'react'; import useHasPermission from '../utility/useHasPermission'; import ToolbarButton from '../widgets/ToolbarButton'; -export default function QueryToolbar({ execute, isDatabaseDefined, busy, save, format, isConnected, kill }) { +export default function QueryToolbar({ execute, isDatabaseDefined, busy, format, isConnected, kill }) { const hasPermission = useHasPermission(); return ( <> @@ -15,11 +15,11 @@ export default function QueryToolbar({ execute, isDatabaseDefined, busy, save, f Kill - {hasPermission('files/sql/write') && ( + {/* {hasPermission('files/sql/write') && ( Save - )} + )} */} Format diff --git a/packages/web/src/query/ShellToolbar.js b/packages/web/src/query/ShellToolbar.js index f141a8bd7..a9c747f40 100644 --- a/packages/web/src/query/ShellToolbar.js +++ b/packages/web/src/query/ShellToolbar.js @@ -1,9 +1,7 @@ import React from 'react'; -import useHasPermission from '../utility/useHasPermission'; import ToolbarButton from '../widgets/ToolbarButton'; -export default function ShellToolbar({ execute, cancel, busy, edit, save, editAvailable }) { - const hasPermission = useHasPermission(); +export default function ShellToolbar({ execute, cancel, busy, edit, editAvailable }) { return ( <> @@ -15,11 +13,6 @@ export default function ShellToolbar({ execute, cancel, busy, edit, save, editAv Show wizard - {hasPermission('files/shell/write') && ( - - Save - - )} ); } diff --git a/packages/web/src/tabs/ChartTab.js b/packages/web/src/tabs/ChartTab.js index 61a880a29..934ce1279 100644 --- a/packages/web/src/tabs/ChartTab.js +++ b/packages/web/src/tabs/ChartTab.js @@ -4,17 +4,16 @@ import { createFreeTableModel } from 'dbgate-datalib'; import useUndoReducer from '../utility/useUndoReducer'; import ReactDOM from 'react-dom'; import { useUpdateDatabaseForTab } from '../utility/globalState'; -import useModalState from '../modals/useModalState'; import LoadingInfo from '../widgets/LoadingInfo'; import ErrorInfo from '../widgets/ErrorInfo'; import useEditorData from '../utility/useEditorData'; import SaveTabModal from '../modals/SaveTabModal'; import ChartEditor from '../charts/ChartEditor'; import ChartToolbar from '../charts/ChartToolbar'; +import ToolbarPortal from '../utility/ToolbarPortal'; export default function ChartTab({ tabVisible, toolbarPortalRef, conid, database, tabid }) { const [modelState, dispatchModel] = useUndoReducer(createFreeTableModel()); - const saveFileModalState = useModalState(); const { initialData, setEditorData, errorMessage, isLoading } = useEditorData({ tabid, }); @@ -57,21 +56,17 @@ export default function ChartTab({ tabVisible, toolbarPortalRef, conid, database database={database} /> - {toolbarPortalRef && - toolbarPortalRef.current && - tabVisible && - ReactDOM.createPortal( - , - toolbarPortalRef.current - )} + + + ); } diff --git a/packages/web/src/tabs/MarkdownEditorTab.js b/packages/web/src/tabs/MarkdownEditorTab.js index 4725464a6..63241a8e7 100644 --- a/packages/web/src/tabs/MarkdownEditorTab.js +++ b/packages/web/src/tabs/MarkdownEditorTab.js @@ -11,10 +11,10 @@ import LoadingInfo from '../widgets/LoadingInfo'; import { useOpenedTabs, useSetOpenedTabs } from '../utility/globalState'; import useOpenNewTab from '../utility/useOpenNewTab'; import { setSelectedTabFunc } from '../utility/common'; +import ToolbarPortal from '../utility/ToolbarPortal'; export default function MarkdownEditorTab({ tabid, tabVisible, toolbarPortalRef, ...other }) { const { editorData, setEditorData, isLoading, saveToStorage } = useEditorData({ tabid }); - const saveFileModalState = useModalState(); const openedTabs = useOpenedTabs(); const setOpenedTabs = useSetOpenedTabs(); const openNewTab = useOpenNewTab(); @@ -61,21 +61,17 @@ export default function MarkdownEditorTab({ tabid, tabVisible, toolbarPortalRef, onKeyDown={handleKeyDown} mode="markdown" /> - {toolbarPortalRef && - toolbarPortalRef.current && - tabVisible && - ReactDOM.createPortal( - , - toolbarPortalRef.current - )} + + + ); diff --git a/packages/web/src/tabs/QueryDesignTab.js b/packages/web/src/tabs/QueryDesignTab.js index deb2f16d1..c9b9648a8 100644 --- a/packages/web/src/tabs/QueryDesignTab.js +++ b/packages/web/src/tabs/QueryDesignTab.js @@ -15,7 +15,6 @@ import keycodes from '../utility/keycodes'; import { changeTab } from '../utility/common'; import useSocket from '../utility/SocketProvider'; import SaveTabModal from '../modals/SaveTabModal'; -import useModalState from '../modals/useModalState'; import sqlFormatter from 'sql-formatter'; import useEditorData from '../utility/useEditorData'; import LoadingInfo from '../widgets/LoadingInfo'; @@ -27,6 +26,7 @@ import { generateDesignedQuery } from '../designer/designerTools'; import useUndoReducer from '../utility/useUndoReducer'; import { StatusBarItem } from '../widgets/StatusBar'; import useTimerLabel from '../utility/useTimerLabel'; +import ToolbarPortal from '../utility/ToolbarPortal'; export default function QueryDesignTab({ tabid, @@ -43,7 +43,6 @@ export default function QueryDesignTab({ const setOpenedTabs = useSetOpenedTabs(); const socket = useSocket(); const [busy, setBusy] = React.useState(false); - const saveFileModalState = useModalState(); const extensions = useExtensions(); const connection = useConnectionInfo({ conid }); const engine = findEngineDriver(connection, extensions); @@ -196,36 +195,32 @@ export default function QueryDesignTab({ )} - {toolbarPortalRef && - toolbarPortalRef.current && - tabVisible && - ReactDOM.createPortal( - , - toolbarPortalRef.current - )} + + + {statusbarPortalRef && statusbarPortalRef.current && tabVisible && ReactDOM.createPortal({timerLabel.text}, statusbarPortalRef.current)} ); diff --git a/packages/web/src/tabs/QueryTab.js b/packages/web/src/tabs/QueryTab.js index fcf6a8787..8f19529a1 100644 --- a/packages/web/src/tabs/QueryTab.js +++ b/packages/web/src/tabs/QueryTab.js @@ -23,6 +23,7 @@ import LoadingInfo from '../widgets/LoadingInfo'; import useExtensions from '../utility/useExtensions'; import useTimerLabel from '../utility/useTimerLabel'; import { StatusBarItem } from '../widgets/StatusBar'; +import ToolbarPortal from '../utility/ToolbarPortal'; function createSqlPreview(sql) { if (!sql) return undefined; @@ -58,7 +59,6 @@ export default function QueryTab({ const setOpenedTabs = useSetOpenedTabs(); const socket = useSocket(); const [busy, setBusy] = React.useState(false); - const saveFileModalState = useModalState(); const extensions = useExtensions(); const timerLabel = useTimerLabel(); const { editorData, setEditorData, isLoading } = useEditorData({ @@ -201,7 +201,7 @@ export default function QueryTab({ )} - {toolbarPortalRef && + {/* {toolbarPortalRef && toolbarPortalRef.current && tabVisible && ReactDOM.createPortal( @@ -216,19 +216,31 @@ export default function QueryTab({ kill={handleKill} />, toolbarPortalRef.current - )} + )} */} {statusbarPortalRef && statusbarPortalRef.current && tabVisible && ReactDOM.createPortal({timerLabel.text}, statusbarPortalRef.current)} + + + ); diff --git a/packages/web/src/tabs/ShellTab.js b/packages/web/src/tabs/ShellTab.js index f541704cb..3f9174b6d 100644 --- a/packages/web/src/tabs/ShellTab.js +++ b/packages/web/src/tabs/ShellTab.js @@ -14,10 +14,10 @@ import useShowModal from '../modals/showModal'; import ImportExportModal from '../modals/ImportExportModal'; import useEditorData from '../utility/useEditorData'; import SaveTabModal from '../modals/SaveTabModal'; -import useModalState from '../modals/useModalState'; import LoadingInfo from '../widgets/LoadingInfo'; import useTimerLabel from '../utility/useTimerLabel'; import { StatusBarItem } from '../widgets/StatusBar'; +import ToolbarPortal from '../utility/ToolbarPortal'; const configRegex = /\s*\/\/\s*@ImportExportConfigurator\s*\n\s*\/\/\s*(\{[^\n]+\})\n/; const requireRegex = /\s*(\/\/\s*@require\s+[^\n]+)\n/g; @@ -27,7 +27,6 @@ export default function ShellTab({ tabid, tabVisible, toolbarPortalRef, statusba const [busy, setBusy] = React.useState(false); const showModal = useShowModal(); const { editorData, setEditorData, isLoading } = useEditorData({ tabid }); - const saveFileModalState = useModalState(); const timerLabel = useTimerLabel(); const setOpenedTabs = useSetOpenedTabs(); @@ -120,32 +119,27 @@ export default function ShellTab({ tabid, tabVisible, toolbarPortalRef, statusba /> - {toolbarPortalRef && - toolbarPortalRef.current && - tabVisible && - ReactDOM.createPortal( - , - toolbarPortalRef.current - )} + + + {statusbarPortalRef && statusbarPortalRef.current && tabVisible && ReactDOM.createPortal({timerLabel.text}, statusbarPortalRef.current)} ); diff --git a/packages/web/src/utility/SaveFileToolbarButton.js b/packages/web/src/utility/SaveFileToolbarButton.js new file mode 100644 index 000000000..7e35cda3e --- /dev/null +++ b/packages/web/src/utility/SaveFileToolbarButton.js @@ -0,0 +1,22 @@ +import React from 'react'; +import { DropDownMenuItem } from '../modals/DropDownMenu'; +import ToolbarButton, { ToolbarDropDownButton } from '../widgets/ToolbarButton'; + +export default function SaveFileToolbarButton({ tabid, save, saveAs }) { + if (!saveAs) return null; + + if (save) { + return ( + + Save + Save As + + ); + } + + return ( + + Save As + + ); +} diff --git a/packages/web/src/utility/ToolbarPortal.js b/packages/web/src/utility/ToolbarPortal.js new file mode 100644 index 000000000..ba184681f --- /dev/null +++ b/packages/web/src/utility/ToolbarPortal.js @@ -0,0 +1,13 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; + +export default function ToolbarPortal({ toolbarPortalRef, tabVisible, children }) { + return ( + (toolbarPortalRef && + toolbarPortalRef.current && + tabVisible && + children && + ReactDOM.createPortal(children, toolbarPortalRef.current)) || + null + ); +} From 54d476a972bc216ed386d80d8670cd37b0912972 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 30 Jan 2021 19:06:30 +0100 Subject: [PATCH 25/33] open sql file with drag & drop --- packages/web/src/DragAndDropFileTarget.js | 12 ++--- .../web/src/utility/SaveFileToolbarButton.js | 8 ++- packages/web/src/utility/UploadsProvider.js | 10 ++++ .../web/src/utility/useOpenElectronFile.js | 53 +++++++++++-------- 4 files changed, 53 insertions(+), 30 deletions(-) diff --git a/packages/web/src/DragAndDropFileTarget.js b/packages/web/src/DragAndDropFileTarget.js index 334eedbae..5741ca470 100644 --- a/packages/web/src/DragAndDropFileTarget.js +++ b/packages/web/src/DragAndDropFileTarget.js @@ -2,6 +2,7 @@ import React from 'react'; import styled from 'styled-components'; import { FontIcon } from './icons'; import useTheme from './theme/useTheme'; +import getElectron from './utility/getElectron'; import useExtensions from './utility/useExtensions'; const TargetStyled = styled.div` @@ -41,6 +42,9 @@ const TitleWrapper = styled.div` export default function DragAndDropFileTarget({ isDragActive, inputProps }) { const theme = useTheme(); const { fileFormats } = useExtensions(); + const electron = getElectron(); + const fileTypeNames = fileFormats.filter(x => x.readerFunc).map(x => x.name); + if (electron) fileTypeNames.push('SQL'); return ( !!isDragActive && ( @@ -49,13 +53,7 @@ export default function DragAndDropFileTarget({ isDragActive, inputProps }) { Drop the files to upload to DbGate - - Supported file types:{' '} - {fileFormats - .filter(x => x.readerFunc) - .map(x => x.name) - .join(', ')} - + Supported file types: {fileTypeNames.join(', ')} diff --git a/packages/web/src/utility/SaveFileToolbarButton.js b/packages/web/src/utility/SaveFileToolbarButton.js index 7e35cda3e..d4af2f855 100644 --- a/packages/web/src/utility/SaveFileToolbarButton.js +++ b/packages/web/src/utility/SaveFileToolbarButton.js @@ -8,8 +8,12 @@ export default function SaveFileToolbarButton({ tabid, save, saveAs }) { if (save) { return ( - Save - Save As + + Save + + + Save As + ); } diff --git a/packages/web/src/utility/UploadsProvider.js b/packages/web/src/utility/UploadsProvider.js index 3e4d31de3..bd4e49e80 100644 --- a/packages/web/src/utility/UploadsProvider.js +++ b/packages/web/src/utility/UploadsProvider.js @@ -3,8 +3,10 @@ import { useDropzone } from 'react-dropzone'; import ImportExportModal from '../modals/ImportExportModal'; import useShowModal from '../modals/showModal'; import { findFileFormat } from './fileformats'; +import getElectron from './getElectron'; import resolveApi from './resolveApi'; import useExtensions from './useExtensions'; +import { useOpenElectronFileCore, canOpenByElectron } from './useOpenElectronFile'; const UploadsContext = React.createContext(null); @@ -21,6 +23,8 @@ export function useUploadFiles() { const { uploadListener } = useUploadsProvider(); const showModal = useShowModal(); const extensions = useExtensions(); + const electron = getElectron(); + const openElectronFileCore = useOpenElectronFileCore(); const handleUploadFiles = React.useCallback( files => { @@ -31,6 +35,12 @@ export function useUploadFiles() { } console.log('FILE', file); + + if (electron && canOpenByElectron(file.path)) { + openElectronFileCore(file.path); + return; + } + const formData = new FormData(); formData.append('data', file); diff --git a/packages/web/src/utility/useOpenElectronFile.js b/packages/web/src/utility/useOpenElectronFile.js index b3133f5df..04af37064 100644 --- a/packages/web/src/utility/useOpenElectronFile.js +++ b/packages/web/src/utility/useOpenElectronFile.js @@ -1,31 +1,42 @@ import useNewQuery from '../query/useNewQuery'; import getElectron from './getElectron'; -export default function useOpenElectronFile() { - const electron = getElectron(); +export function canOpenByElectron(file) { + return file && file.toLowerCase().endsWith('.sql'); +} +export function useOpenElectronFileCore() { const newQuery = useNewQuery(); - return () => { - const filePaths = electron.remote.dialog.showOpenDialogSync(electron.remote.getCurrentWindow(), { - filters: { name: `SQL files`, extensions: ['sql'] }, - }); - const filePath = filePaths && filePaths[0]; - if (filePath) { - if (filePath.match(/.sql$/i)) { - const path = window.require('path'); - const fs = window.require('fs'); - const parsed = path.parse(filePath); - const data = fs.readFileSync(filePath, { encoding: 'utf-8' }); + return filePath => { + if (filePath.toLowerCase().endsWith('.sql')) { + const path = window.require('path'); + const fs = window.require('fs'); + const parsed = path.parse(filePath); + const data = fs.readFileSync(filePath, { encoding: 'utf-8' }); - newQuery({ - title: parsed.name, - initialData: data, - // @ts-ignore - savedFilePath: filePath, - savedFormat: 'text', - }); - } + newQuery({ + title: parsed.name, + initialData: data, + // @ts-ignore + savedFilePath: filePath, + savedFormat: 'text', + }); + } + }; +} + +export default function useOpenElectronFile() { + const electron = getElectron(); + const openElectronFileCore = useOpenElectronFileCore(); + + return () => { + const filePaths = electron.remote.dialog.showOpenDialogSync(electron.remote.getCurrentWindow(), { + filters: [{ name: `SQL files`, extensions: ['sql'] }], + }); + const filePath = filePaths && filePaths[0]; + if (canOpenByElectron(filePath)) { + openElectronFileCore(filePath); } }; } From a17b76c5701a0a1b26671e21c742a707479f7e61 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 30 Jan 2021 20:01:54 +0100 Subject: [PATCH 26/33] save from electron menu --- app/src/electron.js | 29 +++++++++++++++++++++++-- packages/web/src/TabsPanel.js | 11 ++++++++++ packages/web/src/index.js | 12 ++++++++++ packages/web/src/modals/SaveTabModal.js | 18 +++++++++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) diff --git a/app/src/electron.js b/app/src/electron.js index 6a403a8d7..9e2ab2956 100644 --- a/app/src/electron.js +++ b/app/src/electron.js @@ -1,6 +1,6 @@ const electron = require('electron'); const os = require('os'); -const { Menu } = require('electron'); +const { Menu, ipcMain } = require('electron'); const { fork } = require('child_process'); const { autoUpdater } = require('electron-updater'); const Store = require('electron-store'); @@ -20,6 +20,7 @@ const store = new Store(); // be closed automatically when the JavaScript object is garbage collected. let mainWindow; let splashWindow; +let mainMenu; log.transports.file.level = 'debug'; autoUpdater.logger = log; @@ -51,6 +52,22 @@ function buildMenu() { mainWindow.webContents.executeJavaScript(`dbgate_openFile()`); }, }, + { + label: 'Save', + click() { + mainWindow.webContents.executeJavaScript(`dbgate_tabCommand('save')`); + }, + accelerator: 'Ctrl+S', + id: 'save', + }, + { + label: 'Save As', + click() { + mainWindow.webContents.executeJavaScript(`dbgate_tabCommand('saveAs')`); + }, + accelerator: 'Ctrl+Shift+S', + id: 'saveAs', + }, { type: 'separator' }, { role: 'close' }, ], @@ -134,6 +151,13 @@ function buildMenu() { return Menu.buildFromTemplate(template); } +ipcMain.on('update-menu', async (event, arg) => { + const commands = await mainWindow.webContents.executeJavaScript(`getCurrentTabCommands()`); + console.log('getCurrentTabCommands', commands); + mainMenu.getMenuItemById('save').enabled = !!commands.save; + mainMenu.getMenuItemById('saveAs').enabled = !!commands.saveAs; +}); + function createWindow() { const bounds = store.get('winBounds'); @@ -150,7 +174,8 @@ function createWindow() { }, }); - mainWindow.setMenu(buildMenu()); + mainMenu = buildMenu(); + mainWindow.setMenu(mainMenu); function loadMainWindow() { const startUrl = diff --git a/packages/web/src/TabsPanel.js b/packages/web/src/TabsPanel.js index 1da14e220..60891ee2b 100644 --- a/packages/web/src/TabsPanel.js +++ b/packages/web/src/TabsPanel.js @@ -10,6 +10,7 @@ import useTheme from './theme/useTheme'; import usePropsCompare from './utility/usePropsCompare'; import { useShowMenu } from './modals/showMenu'; import { setSelectedTabFunc } from './utility/common'; +import getElectron from './utility/getElectron'; // const files = [ // { name: 'app.js' }, @@ -219,6 +220,16 @@ export default function TabsPanel() { ); }; + React.useEffect(() => { + const electron = getElectron(); + if (electron) { + const { ipcRenderer } = electron; + const activeTab = tabs.find(x => x.selected); + window['activeTabId'] = activeTab ? activeTab.tabid : null; + ipcRenderer.send('update-menu'); + } + }, [tabs]); + // console.log( // 't', // tabs.map(x => x.tooltip) diff --git a/packages/web/src/index.js b/packages/web/src/index.js index eea7e10fa..29e524d25 100644 --- a/packages/web/src/index.js +++ b/packages/web/src/index.js @@ -1,5 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import _ from 'lodash'; import './index.css'; import '@mdi/font/css/materialdesignicons.css'; import App from './App'; @@ -22,6 +23,17 @@ import localStorageGarbageCollector from './utility/localStorageGarbageCollector // import 'ace-builds/src-noconflict/snippets/mysql'; localStorageGarbageCollector(); +window['tabExports'] = {}; +window['getCurrentTabCommands'] = () => { + const tabid = window['activeTabId']; + return _.mapValues(window['tabExports'][tabid] || {}, v => !!v); +}; +window['dbgate_tabCommand'] = cmd => { + const tabid = window['activeTabId']; + const commands = window['tabExports'][tabid]; + const func = (commands || {})[cmd]; + if (func) func(); +}; ReactDOM.render(, document.getElementById('root')); diff --git a/packages/web/src/modals/SaveTabModal.js b/packages/web/src/modals/SaveTabModal.js index 3161292b2..34b4f0d91 100644 --- a/packages/web/src/modals/SaveTabModal.js +++ b/packages/web/src/modals/SaveTabModal.js @@ -1,6 +1,7 @@ import React from 'react'; import axios from '../utility/axios'; import { changeTab } from '../utility/common'; +import getElectron from '../utility/getElectron'; import { useOpenedTabs, useSetOpenedTabs } from '../utility/globalState'; import keycodes from '../utility/keycodes'; import SaveFileToolbarButton from '../utility/SaveFileToolbarButton'; @@ -72,6 +73,23 @@ export default function SaveTabModal({ } }, [tabVisible, handleKeyboard, canSave]); + React.useEffect(() => { + const electron = getElectron(); + if (electron) { + const { ipcRenderer } = electron; + window['tabExports'][tabid] = { + save: handleSaveRef.current, + saveAs: saveFileModalState.open, + }; + ipcRenderer.send('update-menu'); + + return () => { + delete window['tabExports'][tabid]; + ipcRenderer.send('update-menu'); + }; + } + }, []); + return ( <> Date: Sat, 30 Jan 2021 20:05:50 +0100 Subject: [PATCH 27/33] small refactor --- app/src/electron.js | 3 +-- packages/web/src/TabsPanel.js | 2 +- packages/web/src/index.js | 12 ++++++------ packages/web/src/modals/SaveTabModal.js | 4 ++-- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/src/electron.js b/app/src/electron.js index 9e2ab2956..1b676da0f 100644 --- a/app/src/electron.js +++ b/app/src/electron.js @@ -152,8 +152,7 @@ function buildMenu() { } ipcMain.on('update-menu', async (event, arg) => { - const commands = await mainWindow.webContents.executeJavaScript(`getCurrentTabCommands()`); - console.log('getCurrentTabCommands', commands); + const commands = await mainWindow.webContents.executeJavaScript(`dbgate_getCurrentTabCommands()`); mainMenu.getMenuItemById('save').enabled = !!commands.save; mainMenu.getMenuItemById('saveAs').enabled = !!commands.saveAs; }); diff --git a/packages/web/src/TabsPanel.js b/packages/web/src/TabsPanel.js index 60891ee2b..37c3e121d 100644 --- a/packages/web/src/TabsPanel.js +++ b/packages/web/src/TabsPanel.js @@ -225,7 +225,7 @@ export default function TabsPanel() { if (electron) { const { ipcRenderer } = electron; const activeTab = tabs.find(x => x.selected); - window['activeTabId'] = activeTab ? activeTab.tabid : null; + window['dbgate_activeTabId'] = activeTab ? activeTab.tabid : null; ipcRenderer.send('update-menu'); } }, [tabs]); diff --git a/packages/web/src/index.js b/packages/web/src/index.js index 29e524d25..6cb89510d 100644 --- a/packages/web/src/index.js +++ b/packages/web/src/index.js @@ -23,14 +23,14 @@ import localStorageGarbageCollector from './utility/localStorageGarbageCollector // import 'ace-builds/src-noconflict/snippets/mysql'; localStorageGarbageCollector(); -window['tabExports'] = {}; -window['getCurrentTabCommands'] = () => { - const tabid = window['activeTabId']; - return _.mapValues(window['tabExports'][tabid] || {}, v => !!v); +window['dbgate_tabExports'] = {}; +window['dbgate_getCurrentTabCommands'] = () => { + const tabid = window['dbgate_activeTabId']; + return _.mapValues(window['dbgate_tabExports'][tabid] || {}, v => !!v); }; window['dbgate_tabCommand'] = cmd => { - const tabid = window['activeTabId']; - const commands = window['tabExports'][tabid]; + const tabid = window['dbgate_activeTabId']; + const commands = window['dbgate_tabExports'][tabid]; const func = (commands || {})[cmd]; if (func) func(); }; diff --git a/packages/web/src/modals/SaveTabModal.js b/packages/web/src/modals/SaveTabModal.js index 34b4f0d91..c60b4a955 100644 --- a/packages/web/src/modals/SaveTabModal.js +++ b/packages/web/src/modals/SaveTabModal.js @@ -77,14 +77,14 @@ export default function SaveTabModal({ const electron = getElectron(); if (electron) { const { ipcRenderer } = electron; - window['tabExports'][tabid] = { + window['dbgate_tabExports'][tabid] = { save: handleSaveRef.current, saveAs: saveFileModalState.open, }; ipcRenderer.send('update-menu'); return () => { - delete window['tabExports'][tabid]; + delete window['dbgate_tabExports'][tabid]; ipcRenderer.send('update-menu'); }; } From 4e350e99c4e02bfdc6e4b47e3120e1de7517d981 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sat, 30 Jan 2021 20:07:28 +0100 Subject: [PATCH 28/33] v3.9.4-beta.4 --- app/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/package.json b/app/package.json index adbed5ee4..a1185c9be 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "dbgate", - "version": "3.9.4-beta.3", + "version": "3.9.4-beta.4", "private": true, "author": "Jan Prochazka ", "description": "Opensource database administration tool", From 5c7a011efbe20a8651b221f9be92731eb21f1da3 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 31 Jan 2021 07:08:15 +0100 Subject: [PATCH 29/33] report problem menu --- app/src/electron.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/electron.js b/app/src/electron.js index 1b676da0f..431398aba 100644 --- a/app/src/electron.js +++ b/app/src/electron.js @@ -138,6 +138,12 @@ function buildMenu() { require('electron').shell.openExternal('https://hub.docker.com/r/dbgate/dbgate'); }, }, + { + label: 'Report problem or feature request', + click() { + require('electron').shell.openExternal('https://github.com/dbshell/dbgate/issues/new'); + }, + }, { label: 'About', click() { From 18bf6e5979bb9c68445cb17a7bd8823157c15937 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 31 Jan 2021 09:21:54 +0100 Subject: [PATCH 30/33] open data files using open dialog in electron + drag & drop in electron without uploading --- app/src/electron.js | 4 +- .../src/impexp/ImportExportConfigurator.js | 21 ++++++- packages/web/src/modals/ImportExportModal.js | 7 ++- packages/web/src/utility/UploadsProvider.js | 2 +- .../web/src/utility/useOpenElectronFile.js | 62 ++++++++++++++++--- 5 files changed, 83 insertions(+), 13 deletions(-) diff --git a/app/src/electron.js b/app/src/electron.js index 431398aba..d6df7c9b1 100644 --- a/app/src/electron.js +++ b/app/src/electron.js @@ -129,7 +129,7 @@ function buildMenu() { { label: 'DbGate on GitHub', click() { - require('electron').shell.openExternal('https://github.com/dbshell/dbgate'); + require('electron').shell.openExternal('https://github.com/dbgate/dbgate'); }, }, { @@ -141,7 +141,7 @@ function buildMenu() { { label: 'Report problem or feature request', click() { - require('electron').shell.openExternal('https://github.com/dbshell/dbgate/issues/new'); + require('electron').shell.openExternal('https://github.com/dbgate/dbgate/issues/new'); }, }, { diff --git a/packages/web/src/impexp/ImportExportConfigurator.js b/packages/web/src/impexp/ImportExportConfigurator.js index b575c0deb..17c843aac 100644 --- a/packages/web/src/impexp/ImportExportConfigurator.js +++ b/packages/web/src/impexp/ImportExportConfigurator.js @@ -411,7 +411,11 @@ function SourceName({ name }) { ); } -export default function ImportExportConfigurator({ uploadedFile = undefined, onChangePreview = undefined }) { +export default function ImportExportConfigurator({ + uploadedFile = undefined, + openedFile = undefined, + onChangePreview = undefined, +}) { const { values, setFieldValue, setValues } = useForm(); const targetDbinfo = useDatabaseInfo({ conid: values.targetConnectionId, database: values.targetDatabaseName }); const sourceConnectionInfo = useConnectionInfo({ conid: values.sourceConnectionId }); @@ -453,6 +457,21 @@ export default function ImportExportConfigurator({ uploadedFile = undefined, onC if (uploadedFile) { handleUpload(uploadedFile); } + if (openedFile) { + addFilesToSourceList( + extensions, + [ + { + fileName: openedFile.filePath, + shortName: openedFile.shortName, + }, + ], + values, + setValues, + !sourceList || sourceList.length == 0 ? openedFile.storageType : null, + setPreviewSource + ); + } }, []); const supportsPreview = diff --git a/packages/web/src/modals/ImportExportModal.js b/packages/web/src/modals/ImportExportModal.js index 02836387f..b7b2b319a 100644 --- a/packages/web/src/modals/ImportExportModal.js +++ b/packages/web/src/modals/ImportExportModal.js @@ -120,6 +120,7 @@ export default function ImportExportModal({ modalState, initialValues, uploadedFile = undefined, + openedFile = undefined, importToArchive = false, }) { const [executeNumber, setExecuteNumber] = React.useState(0); @@ -195,7 +196,11 @@ export default function ImportExportModal({ Import/Export {busy && } - + diff --git a/packages/web/src/utility/UploadsProvider.js b/packages/web/src/utility/UploadsProvider.js index bd4e49e80..726a54dbd 100644 --- a/packages/web/src/utility/UploadsProvider.js +++ b/packages/web/src/utility/UploadsProvider.js @@ -36,7 +36,7 @@ export function useUploadFiles() { console.log('FILE', file); - if (electron && canOpenByElectron(file.path)) { + if (electron && canOpenByElectron(file.path, extensions)) { openElectronFileCore(file.path); return; } diff --git a/packages/web/src/utility/useOpenElectronFile.js b/packages/web/src/utility/useOpenElectronFile.js index 04af37064..5240cca00 100644 --- a/packages/web/src/utility/useOpenElectronFile.js +++ b/packages/web/src/utility/useOpenElectronFile.js @@ -1,18 +1,33 @@ +import _ from 'lodash'; +import React from 'react'; +import ImportExportModal from '../modals/ImportExportModal'; +import useShowModal from '../modals/showModal'; import useNewQuery from '../query/useNewQuery'; import getElectron from './getElectron'; +import useExtensions from './useExtensions'; -export function canOpenByElectron(file) { - return file && file.toLowerCase().endsWith('.sql'); +export function canOpenByElectron(file, extensions) { + if (!file) return false; + const nameLower = file.toLowerCase(); + if (nameLower.endsWith('.sql')) return true; + for (const format of extensions.fileFormats) { + if (nameLower.endsWith(`.${format.extension}`)) return true; + } + return false; } export function useOpenElectronFileCore() { const newQuery = useNewQuery(); + const extensions = useExtensions(); + const showModal = useShowModal(); return filePath => { - if (filePath.toLowerCase().endsWith('.sql')) { - const path = window.require('path'); - const fs = window.require('fs'); - const parsed = path.parse(filePath); + const nameLower = filePath.toLowerCase(); + const path = window.require('path'); + const fs = window.require('fs'); + const parsed = path.parse(filePath); + + if (nameLower.endsWith('.sql')) { const data = fs.readFileSync(filePath, { encoding: 'utf-8' }); newQuery({ @@ -23,19 +38,50 @@ export function useOpenElectronFileCore() { savedFormat: 'text', }); } + for (const format of extensions.fileFormats) { + if (nameLower.endsWith(`.${format.extension}`)) { + showModal(modalState => ( + + )); + } + } }; } +function getFileFormatFilters(extensions) { + return extensions.fileFormats.filter(x => x.readerFunc).map(x => ({ name: x.name, extensions: [x.extension] })); +} + +function getFileFormatExtensions(extensions) { + return extensions.fileFormats.filter(x => x.readerFunc).map(x => x.extension); +} + export default function useOpenElectronFile() { const electron = getElectron(); const openElectronFileCore = useOpenElectronFileCore(); + const extensions = useExtensions(); return () => { const filePaths = electron.remote.dialog.showOpenDialogSync(electron.remote.getCurrentWindow(), { - filters: [{ name: `SQL files`, extensions: ['sql'] }], + filters: [ + { name: `All supported files`, extensions: ['sql', ...getFileFormatExtensions(extensions)] }, + { name: `SQL files`, extensions: ['sql'] }, + ...getFileFormatFilters(extensions), + ], }); const filePath = filePaths && filePaths[0]; - if (canOpenByElectron(filePath)) { + if (canOpenByElectron(filePath, extensions)) { openElectronFileCore(filePath); } }; From bd3c18d883742c7fe7d85391299f1628a6635299 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Sun, 31 Jan 2021 09:24:27 +0100 Subject: [PATCH 31/33] v3.9.4-beta.5 --- app/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/package.json b/app/package.json index a1185c9be..c6d527b97 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "dbgate", - "version": "3.9.4-beta.4", + "version": "3.9.4-beta.5", "private": true, "author": "Jan Prochazka ", "description": "Opensource database administration tool", From fdf60b526716e09164b8c5c5387bf02148d47fdf Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 1 Feb 2021 18:09:57 +0100 Subject: [PATCH 32/33] dbgate package --- app/README.md | 24 ++++++++++++++++++++++++ app/package.json | 6 +++++- app/src/dbgate.js | 25 +++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 app/README.md create mode 100644 app/src/dbgate.js diff --git a/app/README.md b/app/README.md new file mode 100644 index 000000000..f2e9391aa --- /dev/null +++ b/app/README.md @@ -0,0 +1,24 @@ +[![styled with prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) +[![Donate](https://img.shields.io/badge/donate-paypal-blue.svg)](https://paypal.me/JanProchazkaCz/30eur) +[![NPM version](https://img.shields.io/npm/v/dbgate.svg)](https://www.npmjs.com/package/dbgate) + +# DbGate - database administration tool + +DbGate is fast and easy to use database administration tool for MySQL, PostgreSQL, SQL Server. + +## Install using npm +```sh +npm install -g dbgate +``` + +Then run dbgate from commandline. + +You can also download binary packages from https://dbgate.org + +## Other dbgate packages +You can use some functionality of dbgate from your JavaScript code. See [dbgate-api](https://npmjs.com/dbgate-api) package. + +## Screenshot + +![Screenshot](https://raw.githubusercontent.com/dbshell/dbgate/master/screenshot.png) + diff --git a/app/package.json b/app/package.json index c6d527b97..b456b7050 100644 --- a/app/package.json +++ b/app/package.json @@ -11,7 +11,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/dbshell/dbgate.git" + "url": "https://github.com/dbgate/dbgate.git" }, "build": { "appId": "org.dbgate", @@ -58,12 +58,16 @@ "scripts": { "start": "cross-env ELECTRON_START_URL=http://localhost:5000 electron .", "start:local": "cross-env electron .", + "start:dbgate": "dbgate", "dist": "electron-builder", "build": "cd ../packages/api && yarn build && cd ../web && yarn build:app && cd ../../app && yarn dist", "build:local": "cd ../packages/api && yarn build && cd ../web && yarn build:app && cd ../../app && yarn predist", "postinstall": "electron-builder install-app-deps", "predist": "copyfiles ../packages/api/dist/* packages && copyfiles \"../packages/web/build/*\" packages && copyfiles \"../packages/web/build/**/*\" packages" }, + "bin": { + "dbgate": "./src/dbgate" + }, "main": "src/electron.js", "devDependencies": { "copyfiles": "^2.2.0", diff --git a/app/src/dbgate.js b/app/src/dbgate.js new file mode 100644 index 000000000..cf048c38d --- /dev/null +++ b/app/src/dbgate.js @@ -0,0 +1,25 @@ +#!/usr/bin/env node + +var electron = require('electron'); + +var proc = require('child_process'); + +var child = proc.spawn(electron, ['.'], { stdio: 'inherit', windowsHide: false }); +child.on('close', function (code, signal) { + if (code === null) { + console.error(electron, 'exited with signal', signal); + process.exit(1); + } + process.exit(code); +}); + +const handleTerminationSignal = function (signal) { + process.on(signal, function signalHandler() { + if (!child.killed) { + child.kill(signal); + } + }); +}; + +handleTerminationSignal('SIGINT'); +handleTerminationSignal('SIGTERM'); From df359aea58eadb59e4f2a9f15ef4dd668d54b128 Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Mon, 1 Feb 2021 18:39:55 +0100 Subject: [PATCH 33/33] reverted try to dbgate global package --- app/README.md | 10 +--------- app/package.json | 5 ----- app/src/dbgate.js | 25 ------------------------- 3 files changed, 1 insertion(+), 39 deletions(-) delete mode 100644 app/src/dbgate.js diff --git a/app/README.md b/app/README.md index f2e9391aa..fa3ff6e7d 100644 --- a/app/README.md +++ b/app/README.md @@ -3,17 +3,10 @@ [![NPM version](https://img.shields.io/npm/v/dbgate.svg)](https://www.npmjs.com/package/dbgate) # DbGate - database administration tool - DbGate is fast and easy to use database administration tool for MySQL, PostgreSQL, SQL Server. ## Install using npm -```sh -npm install -g dbgate -``` - -Then run dbgate from commandline. - -You can also download binary packages from https://dbgate.org +Please download binary packages from https://dbgate.org . Or run from source code, as described on [github](https://github.com/dbgate/dbgate) ## Other dbgate packages You can use some functionality of dbgate from your JavaScript code. See [dbgate-api](https://npmjs.com/dbgate-api) package. @@ -21,4 +14,3 @@ You can use some functionality of dbgate from your JavaScript code. See [dbgate- ## Screenshot ![Screenshot](https://raw.githubusercontent.com/dbshell/dbgate/master/screenshot.png) - diff --git a/app/package.json b/app/package.json index b456b7050..d170d04cc 100644 --- a/app/package.json +++ b/app/package.json @@ -1,7 +1,6 @@ { "name": "dbgate", "version": "3.9.4-beta.5", - "private": true, "author": "Jan Prochazka ", "description": "Opensource database administration tool", "dependencies": { @@ -58,16 +57,12 @@ "scripts": { "start": "cross-env ELECTRON_START_URL=http://localhost:5000 electron .", "start:local": "cross-env electron .", - "start:dbgate": "dbgate", "dist": "electron-builder", "build": "cd ../packages/api && yarn build && cd ../web && yarn build:app && cd ../../app && yarn dist", "build:local": "cd ../packages/api && yarn build && cd ../web && yarn build:app && cd ../../app && yarn predist", "postinstall": "electron-builder install-app-deps", "predist": "copyfiles ../packages/api/dist/* packages && copyfiles \"../packages/web/build/*\" packages && copyfiles \"../packages/web/build/**/*\" packages" }, - "bin": { - "dbgate": "./src/dbgate" - }, "main": "src/electron.js", "devDependencies": { "copyfiles": "^2.2.0", diff --git a/app/src/dbgate.js b/app/src/dbgate.js deleted file mode 100644 index cf048c38d..000000000 --- a/app/src/dbgate.js +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env node - -var electron = require('electron'); - -var proc = require('child_process'); - -var child = proc.spawn(electron, ['.'], { stdio: 'inherit', windowsHide: false }); -child.on('close', function (code, signal) { - if (code === null) { - console.error(electron, 'exited with signal', signal); - process.exit(1); - } - process.exit(code); -}); - -const handleTerminationSignal = function (signal) { - process.on(signal, function signalHandler() { - if (!child.killed) { - child.kill(signal); - } - }); -}; - -handleTerminationSignal('SIGINT'); -handleTerminationSignal('SIGTERM');