diff --git a/packages/web/src/appobj/SavedFileAppObject.js b/packages/web/src/appobj/SavedFileAppObject.js
index 7b6751679..63da73193 100644
--- a/packages/web/src/appobj/SavedFileAppObject.js
+++ b/packages/web/src/appobj/SavedFileAppObject.js
@@ -42,8 +42,7 @@ export function SavedSqlFileAppObject({ data, commonProps }) {
onLoad={(data) => {
newQuery({
title: file,
- // @ts-ignore
- initialScript: data,
+ initialData: data,
});
}}
/>
@@ -60,14 +59,15 @@ export function SavedShellFileAppObject({ data, commonProps }) {
format="text"
icon="img shell"
onLoad={(data) => {
- openNewTab(setOpenedTabs, {
- title: file,
- icon: 'img shell',
- tabComponent: 'ShellTab',
- props: {
- initialScript: data,
+ openNewTab(
+ setOpenedTabs,
+ {
+ title: file,
+ icon: 'img shell',
+ tabComponent: 'ShellTab',
},
- });
+ data
+ );
}}
/>
);
diff --git a/packages/web/src/modals/SaveFileModal.js b/packages/web/src/modals/SaveFileModal.js
index 572ad3e97..5a08cec19 100644
--- a/packages/web/src/modals/SaveFileModal.js
+++ b/packages/web/src/modals/SaveFileModal.js
@@ -9,10 +9,10 @@ import ModalContent from './ModalContent';
import ModalFooter from './ModalFooter';
// import FormikForm from '../utility/FormikForm';
-export default function SaveFileModal({ getData, folder, format, modalState, name, onSave = undefined }) {
+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: getData(), format });
+ await axios.post('files/save', { folder, file: name, data, format });
modalState.close();
if (onSave) onSave(name);
};
diff --git a/packages/web/src/modals/SaveTabModal.js b/packages/web/src/modals/SaveTabModal.js
new file mode 100644
index 000000000..0fc238709
--- /dev/null
+++ b/packages/web/src/modals/SaveTabModal.js
@@ -0,0 +1,16 @@
+import React from 'react';
+import { changeTab } from '../utility/common';
+import { useOpenedTabs, useSetOpenedTabs } from '../utility/globalState';
+import SaveFileModal from './SaveFileModal';
+
+export default function SaveTabModal({ data, folder, format, modalState, tabid }) {
+ const setOpenedTabs = useSetOpenedTabs();
+ const openedTabs = useOpenedTabs();
+
+ const name = openedTabs.find((x) => x.tabid == tabid).title;
+ const onSave = (name) => changeTab(tabid, setOpenedTabs, (tab) => ({ ...tab, title: name }));
+
+ return (
+
+ );
+}
diff --git a/packages/web/src/query/useNewQuery.js b/packages/web/src/query/useNewQuery.js
index a6e37ff6d..6aa8ea3b2 100644
--- a/packages/web/src/query/useNewQuery.js
+++ b/packages/web/src/query/useNewQuery.js
@@ -11,16 +11,20 @@ export default function useNewQuery() {
const tooltip = `${connection.displayName || connection.server}\n${database}`;
- return ({ title = undefined, ...props } = {}) =>
- openNewTab(setOpenedTabs, {
- title: title || 'Query',
- icon: 'img sql-file',
- tooltip,
- tabComponent: 'QueryTab',
- props: {
- ...props,
- conid: connection._id,
- database,
+ return ({ title = undefined, initialData = undefined, ...props } = {}) =>
+ openNewTab(
+ setOpenedTabs,
+ {
+ title: title || 'Query',
+ icon: 'img sql-file',
+ tooltip,
+ tabComponent: 'QueryTab',
+ props: {
+ ...props,
+ conid: connection._id,
+ database,
+ },
},
- });
+ initialData
+ );
}
diff --git a/packages/web/src/tabs/QueryTab.js b/packages/web/src/tabs/QueryTab.js
index 562993d45..5cbc7f292 100644
--- a/packages/web/src/tabs/QueryTab.js
+++ b/packages/web/src/tabs/QueryTab.js
@@ -1,11 +1,11 @@
import React from 'react';
-import ReactDOM from 'react-dom';
import _ from 'lodash';
+import ReactDOM from 'react-dom';
import axios from '../utility/axios';
-import { useConnectionInfo, getDbCore, getConnectionInfo, getSqlObjectInfo } from '../utility/metadataLoaders';
+import { useConnectionInfo } from '../utility/metadataLoaders';
import SqlEditor from '../sqleditor/SqlEditor';
-import { useUpdateDatabaseForTab, useSetOpenedTabs, useOpenedTabs } from '../utility/globalState';
+import { useUpdateDatabaseForTab, useSetOpenedTabs } from '../utility/globalState';
import QueryToolbar from '../query/QueryToolbar';
import SocketMessagesView from '../query/SocketMessagesView';
import { TabPage } from '../widgets/TabControl';
@@ -14,103 +14,38 @@ import { VerticalSplitter } from '../widgets/Splitter';
import keycodes from '../utility/keycodes';
import { changeTab } from '../utility/common';
import useSocket from '../utility/SocketProvider';
-import SaveFileModal from '../modals/SaveFileModal';
+import SaveTabModal from '../modals/SaveTabModal';
import useModalState from '../modals/useModalState';
import sqlFormatter from 'sql-formatter';
-import useExtensions from '../utility/useExtensions';
-import { driverBase, findEngineDriver } from 'dbgate-tools';
+import useEditorData from '../utility/useEditorData';
+import useSqlTemplate from '../utility/useSqlTemplate';
+import LoadingInfo from '../widgets/LoadingInfo';
-function useSqlTemplate(sqlTemplate, props) {
- const [sql, setSql] = React.useState('');
- const extensions = useExtensions();
-
- async function loadTemplate() {
- if (sqlTemplate == 'CREATE TABLE') {
- const tableInfo = await getDbCore(props, props.objectTypeField || 'tables');
- const connection = await getConnectionInfo(props);
- const driver = findEngineDriver(connection, extensions) || driverBase;
- const dmp = driver.createDumper();
- if (tableInfo) dmp.createTable(tableInfo);
- setSql(dmp.s);
- }
- if (sqlTemplate == 'CREATE OBJECT') {
- const objectInfo = await getSqlObjectInfo(props);
- if (objectInfo) {
- if (objectInfo.requiresFormat && objectInfo.createSql) setSql(sqlFormatter.format(objectInfo.createSql));
- else setSql(objectInfo.createSql);
- }
- }
- if (sqlTemplate == 'EXECUTE PROCEDURE') {
- const procedureInfo = await getSqlObjectInfo(props);
- const connection = await getConnectionInfo(props);
-
- const driver = findEngineDriver(connection, extensions) || driverBase;
- const dmp = driver.createDumper();
- if (procedureInfo) dmp.put('^execute %f', procedureInfo);
- setSql(dmp.s);
- }
- }
-
- React.useEffect(() => {
- if (sqlTemplate) {
- loadTemplate();
- }
- }, []);
-
- return sql;
-}
-
-export default function QueryTab({
- tabid,
- conid,
- database,
- initialArgs,
- tabVisible,
- toolbarPortalRef,
- initialScript,
- storageKey,
- ...other
-}) {
- const loadingText = 'Loading SQL template...';
- const localStorageKey = storageKey || `tabdata_sql_${tabid}`;
+export default function QueryTab({ tabid, conid, database, initialArgs, tabVisible, toolbarPortalRef, ...other }) {
const { sqlTemplate } = initialArgs || {};
- const [queryText, setQueryText] = React.useState(
- () => localStorage.getItem(localStorageKey) || initialScript || (sqlTemplate ? loadingText : '')
- );
- const queryTextRef = React.useRef(queryText);
const [sessionId, setSessionId] = React.useState(null);
const [executeNumber, setExecuteNumber] = React.useState(0);
const setOpenedTabs = useSetOpenedTabs();
- const openedTabs = useOpenedTabs();
const socket = useSocket();
const [busy, setBusy] = React.useState(false);
const saveFileModalState = useModalState();
+ const { editorData, setEditorData, isLoading } = useEditorData(tabid);
+
+ const [sqlFromTemplate, isLoadingTemplate] = useSqlTemplate(sqlTemplate, { conid, database, ...other });
+ const editorRef = React.useRef(null);
- const sqlFromTemplate = useSqlTemplate(sqlTemplate, { conid, database, ...other });
React.useEffect(() => {
- if (sqlFromTemplate && queryText == loadingText) {
- editorRef.current.editor.setValue(sqlFromTemplate);
- editorRef.current.editor.clearSelection();
+ if (sqlFromTemplate && sqlTemplate) {
+ setEditorData(sqlFromTemplate);
+ // editorRef.current.editor.setValue(sqlFromTemplate);
+ // editorRef.current.editor.clearSelection();
+ changeTab(tabid, setOpenedTabs, (tab) => ({
+ ...tab,
+ props: _.omit(tab.props, ['initialArgs']),
+ }));
}
}, [sqlFromTemplate]);
- const saveToStorage = React.useCallback(() => {
- try {
- localStorage.setItem(localStorageKey, queryTextRef.current);
- } catch (err) {
- console.error(err);
- }
- }, [localStorageKey, queryTextRef]);
- const saveToStorageDebounced = React.useMemo(() => _.debounce(saveToStorage, 5000), [saveToStorage]);
-
- React.useEffect(() => {
- window.addEventListener('beforeunload', saveToStorage);
- return () => {
- saveToStorage();
- window.removeEventListener('beforeunload', saveToStorage);
- };
- }, []);
-
const handleSessionDone = React.useCallback(() => {
setBusy(false);
}, []);
@@ -124,32 +59,13 @@ export default function QueryTab({
}
}, [sessionId, socket]);
- React.useEffect(() => {
- if (!storageKey)
- changeTab(tabid, setOpenedTabs, (tab) => ({
- ...tab,
- props: {
- ...tab.props,
- storageKey: localStorageKey,
- },
- }));
- }, [storageKey]);
-
React.useEffect(() => {
changeTab(tabid, setOpenedTabs, (tab) => ({ ...tab, busy }));
}, [busy]);
- const editorRef = React.useRef(null);
-
useUpdateDatabaseForTab(tabVisible, conid, database);
const connection = useConnectionInfo({ conid });
- const handleChange = (text) => {
- if (text != null) queryTextRef.current = text;
- setQueryText(text);
- saveToStorageDebounced();
- };
-
const handleExecute = async () => {
if (busy) return;
setExecuteNumber((num) => num + 1);
@@ -167,7 +83,7 @@ export default function QueryTab({
setBusy(true);
await axios.post('sessions/execute-query', {
sesid,
- sql: selectedText || queryText,
+ sql: selectedText || editorData,
});
};
@@ -204,17 +120,23 @@ export default function QueryTab({
editorRef.current.editor.clearSelection();
};
+ if (isLoading || isLoadingTemplate)
+ return (
+
+
+
+ );
+
return (
<>
@@ -248,14 +170,7 @@ export default function QueryTab({
/>,
toolbarPortalRef.current
)}
- localStorage.getItem(localStorageKey)}
- format="text"
- folder="sql"
- name={openedTabs.find((x) => x.tabid == tabid).title}
- onSave={(name) => changeTab(tabid, setOpenedTabs, (tab) => ({ ...tab, title: name }))}
- />
+
>
);
}
diff --git a/packages/web/src/tabs/ShellTab.js b/packages/web/src/tabs/ShellTab.js
index 2a6c5150a..f2e0d3206 100644
--- a/packages/web/src/tabs/ShellTab.js
+++ b/packages/web/src/tabs/ShellTab.js
@@ -1,6 +1,7 @@
import React from 'react';
import ReactDOM from 'react-dom';
import _ from 'lodash';
+import localforage from 'localforage';
import axios from '../utility/axios';
import { useSetOpenedTabs } from '../utility/globalState';
import { VerticalSplitter } from '../widgets/Splitter';
@@ -22,13 +23,13 @@ export default function ShellTab({
storageKey,
...other
}) {
- const localStorageKey = storageKey || `tabdata_shell_${tabid}`;
- const [shellText, setShellText] = React.useState(() => localStorage.getItem(localStorageKey) || initialScript || '');
+ const localStorageKey = `tabdata_shell_${tabid}`;
+ const [shellText, setShellText] = React.useState(initialScript || '');
const shellTextRef = React.useRef(shellText);
const [busy, setBusy] = React.useState(false);
const showModal = useShowModal();
- const saveToStorage = React.useCallback(() => localStorage.setItem(localStorageKey, shellTextRef.current), [
+ const saveToStorage = React.useCallback(() => localforage.setItem(localStorageKey, shellTextRef.current), [
localStorageKey,
shellTextRef,
]);
diff --git a/packages/web/src/utility/common.js b/packages/web/src/utility/common.js
index afd6699c4..e01ddabf3 100644
--- a/packages/web/src/utility/common.js
+++ b/packages/web/src/utility/common.js
@@ -1,4 +1,5 @@
import uuidv1 from 'uuid/v1';
+import localforage from 'localforage';
export class LoadingToken {
constructor() {
@@ -14,8 +15,11 @@ export function sleep(milliseconds) {
return new Promise((resolve) => window.setTimeout(() => resolve(null), milliseconds));
}
-export function openNewTab(setOpenedTabs, newTab) {
+export async function openNewTab(setOpenedTabs, newTab, initialData = undefined) {
const tabid = uuidv1();
+ if (initialData) {
+ await localforage.setItem(`tabdata_${tabid}`, initialData);
+ }
setOpenedTabs((files) => [
...(files || []).map((x) => ({ ...x, selected: false })),
{
diff --git a/packages/web/src/utility/useEditorData.js b/packages/web/src/utility/useEditorData.js
new file mode 100644
index 000000000..caeb45963
--- /dev/null
+++ b/packages/web/src/utility/useEditorData.js
@@ -0,0 +1,52 @@
+import React from 'react';
+import _ from 'lodash';
+import localforage from 'localforage';
+
+export default function useEditorData(tabid, initialData = null) {
+ const localStorageKey = `tabdata_${tabid}`;
+
+ const [value, setValue] = React.useState(initialData);
+ const [isLoading, setIsLoading] = React.useState(true);
+
+ const valueRef = React.useRef(null);
+
+ const initialLoad = async () => {
+ const init = await localforage.getItem(localStorageKey);
+ if (init) {
+ setValue(init);
+ valueRef.current = init;
+ }
+ setIsLoading(false);
+ };
+
+ React.useEffect(() => {
+ initialLoad();
+ }, []);
+
+ const saveToStorage = React.useCallback(async () => {
+ if (valueRef.current == null) return;
+ try {
+ await localforage.setItem(localStorageKey, valueRef.current);
+ } catch (err) {
+ console.error(err);
+ }
+ }, [localStorageKey, valueRef]);
+
+ const saveToStorageDebounced = React.useMemo(() => _.debounce(saveToStorage, 500), [saveToStorage]);
+
+ const handleChange = (newValue) => {
+ if (newValue != null) valueRef.current = newValue;
+ setValue(newValue);
+ saveToStorageDebounced();
+ };
+
+ React.useEffect(() => {
+ window.addEventListener('beforeunload', saveToStorage);
+ return () => {
+ saveToStorage();
+ window.removeEventListener('beforeunload', saveToStorage);
+ };
+ }, []);
+
+ return { editorData: value, setEditorData: handleChange, isLoading };
+}
diff --git a/packages/web/src/utility/useSqlTemplate.js b/packages/web/src/utility/useSqlTemplate.js
new file mode 100644
index 000000000..bfc769407
--- /dev/null
+++ b/packages/web/src/utility/useSqlTemplate.js
@@ -0,0 +1,48 @@
+import React from 'react';
+
+import { getDbCore, getConnectionInfo, getSqlObjectInfo } from '../utility/metadataLoaders';
+import sqlFormatter from 'sql-formatter';
+import useExtensions from '../utility/useExtensions';
+import { driverBase, findEngineDriver } from 'dbgate-tools';
+
+export default function useSqlTemplate(sqlTemplate, props) {
+ const [sql, setSql] = React.useState('');
+ const extensions = useExtensions();
+ const [isLoading, setIsLoading] = React.useState(!!sqlTemplate);
+
+ async function loadTemplate() {
+ if (sqlTemplate == 'CREATE TABLE') {
+ const tableInfo = await getDbCore(props, props.objectTypeField || 'tables');
+ const connection = await getConnectionInfo(props);
+ const driver = findEngineDriver(connection, extensions) || driverBase;
+ const dmp = driver.createDumper();
+ if (tableInfo) dmp.createTable(tableInfo);
+ setSql(dmp.s);
+ }
+ if (sqlTemplate == 'CREATE OBJECT') {
+ const objectInfo = await getSqlObjectInfo(props);
+ if (objectInfo) {
+ if (objectInfo.requiresFormat && objectInfo.createSql) setSql(sqlFormatter.format(objectInfo.createSql));
+ else setSql(objectInfo.createSql);
+ }
+ }
+ if (sqlTemplate == 'EXECUTE PROCEDURE') {
+ const procedureInfo = await getSqlObjectInfo(props);
+ const connection = await getConnectionInfo(props);
+
+ const driver = findEngineDriver(connection, extensions) || driverBase;
+ const dmp = driver.createDumper();
+ if (procedureInfo) dmp.put('^execute %f', procedureInfo);
+ setSql(dmp.s);
+ }
+ setIsLoading(false);
+ }
+
+ React.useEffect(() => {
+ if (sqlTemplate) {
+ loadTemplate();
+ }
+ }, []);
+
+ return [sql, isLoading];
+}