diff --git a/packages/web/src/designer/Designer.js b/packages/web/src/designer/Designer.js
index 2111addbf..7d951bb05 100644
--- a/packages/web/src/designer/Designer.js
+++ b/packages/web/src/designer/Designer.js
@@ -128,10 +128,13 @@ export default function Designer({ value, onChange, conid, database }) {
const bringToFront = React.useCallback(
(table) => {
- onChange((current) => ({
- ...current,
- tables: [...(current.tables || []).filter((x) => x.designerId != table.designerId), table],
- }));
+ onChange(
+ (current) => ({
+ ...current,
+ tables: [...(current.tables || []).filter((x) => x.designerId != table.designerId), table],
+ }),
+ true
+ );
},
[onChange]
);
@@ -260,14 +263,17 @@ export default function Designer({ value, onChange, conid, database }) {
const handleSelectColumn = React.useCallback(
(column) => {
- onChange((current) => ({
- ...current,
- columns: (current.columns || []).find(
- (x) => x.designerId == column.designerId && x.columnName == column.columnName
- )
- ? current.columns
- : [...cleanupDesignColumns(current.columns), _.pick(column, ['designerId', 'columnName'])],
- }));
+ onChange(
+ (current) => ({
+ ...current,
+ columns: (current.columns || []).find(
+ (x) => x.designerId == column.designerId && x.columnName == column.columnName
+ )
+ ? current.columns
+ : [...cleanupDesignColumns(current.columns), _.pick(column, ['designerId', 'columnName'])],
+ }),
+ true
+ );
},
[onChange]
);
@@ -275,19 +281,20 @@ export default function Designer({ value, onChange, conid, database }) {
const handleChangeColumn = React.useCallback(
(column, changeFunc) => {
onChange((current) => {
- const existing = (current.columns || []).find(
+ const currentColumns = (current || {}).columns || [];
+ const existing = currentColumns.find(
(x) => x.designerId == column.designerId && x.columnName == column.columnName
);
if (existing) {
return {
...current,
- columns: current.columns.map((x) => (x == existing ? changeFunc(existing) : x)),
+ columns: currentColumns.map((x) => (x == existing ? changeFunc(existing) : x)),
};
} else {
return {
...current,
columns: [
- ...cleanupDesignColumns(current.columns),
+ ...cleanupDesignColumns(currentColumns),
changeFunc(_.pick(column, ['designerId', 'columnName'])),
],
};
diff --git a/packages/web/src/designer/QueryDesignColumns.js b/packages/web/src/designer/QueryDesignColumns.js
index acea7edd8..6a90fd136 100644
--- a/packages/web/src/designer/QueryDesignColumns.js
+++ b/packages/web/src/designer/QueryDesignColumns.js
@@ -6,7 +6,7 @@ import InlineButton from '../widgets/InlineButton';
import { findDesignerFilterType } from './designerTools';
function getTableDisplayName(column, tables) {
- const table = tables.find((x) => x.designerId == column.designerId);
+ const table = (tables || []).find((x) => x.designerId == column.designerId);
if (table) return table.alias || table.pureName;
return '';
}
diff --git a/packages/web/src/designer/QueryDesignToolbar.js b/packages/web/src/designer/QueryDesignToolbar.js
index 58efb9f30..69962401e 100644
--- a/packages/web/src/designer/QueryDesignToolbar.js
+++ b/packages/web/src/designer/QueryDesignToolbar.js
@@ -2,7 +2,7 @@ import React from 'react';
import useHasPermission from '../utility/useHasPermission';
import ToolbarButton from '../widgets/ToolbarButton';
-export default function QueryDesignToolbar({ execute, isDatabaseDefined, busy, save }) {
+export default function QueryDesignToolbar({ execute, isDatabaseDefined, busy, save, modelState, dispatchModel }) {
const hasPermission = useHasPermission();
return (
<>
@@ -14,6 +14,12 @@ export default function QueryDesignToolbar({ execute, isDatabaseDefined, busy, s
Save
)}
+ dispatchModel({ type: 'undo' })} icon="icon undo">
+ Undo
+
+ dispatchModel({ type: 'redo' })} icon="icon redo">
+ Redo
+
>
);
}
diff --git a/packages/web/src/tabs/QueryDesignTab.js b/packages/web/src/tabs/QueryDesignTab.js
index 4effaacac..db4f2c9ee 100644
--- a/packages/web/src/tabs/QueryDesignTab.js
+++ b/packages/web/src/tabs/QueryDesignTab.js
@@ -24,6 +24,7 @@ import QueryDesigner from '../designer/QueryDesigner';
import QueryDesignColumns from '../designer/QueryDesignColumns';
import { findEngineDriver } from 'dbgate-tools';
import { generateDesignedQuery } from '../designer/designerTools';
+import useUndoReducer from '../utility/useUndoReducer';
export default function QueryDesignTab({ tabid, conid, database, tabVisible, toolbarPortalRef, ...other }) {
const [sessionId, setSessionId] = React.useState(null);
@@ -36,11 +37,26 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too
const connection = useConnectionInfo({ conid });
const engine = findEngineDriver(connection, extensions);
const [sqlPreview, setSqlPreview] = React.useState('');
- const { editorData, setEditorData, isLoading } = useEditorData({
+ const { initialData, setEditorData, isLoading } = useEditorData({
tabid,
});
+ const [modelState, dispatchModel] = useUndoReducer(
+ {
+ tables: [],
+ references: [],
+ columns: [],
+ },
+ { mergeNearActions: true }
+ );
- const editorRef = React.useRef(null);
+ React.useEffect(() => {
+ // @ts-ignore
+ if (initialData) dispatchModel({ type: 'reset', value: initialData });
+ }, [initialData]);
+
+ React.useEffect(() => {
+ setEditorData(modelState.value);
+ }, [modelState]);
const handleSessionDone = React.useCallback(() => {
setBusy(false);
@@ -53,8 +69,19 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too
};
React.useEffect(() => {
- generatePreview(editorData, engine);
- }, [editorData, engine]);
+ generatePreview(modelState.value, engine);
+ }, [modelState.value, engine]);
+
+ const handleChange = React.useCallback(
+ (value, skipUndoChain) =>
+ // @ts-ignore
+ dispatchModel({
+ type: 'compute',
+ useMerge: skipUndoChain,
+ compute: (v) => (_.isFunction(value) ? value(v) : value),
+ }),
+ [dispatchModel]
+ );
React.useEffect(() => {
if (sessionId && socket) {
@@ -136,15 +163,15 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too
<>
-
+
@@ -164,6 +191,8 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too
tabVisible &&
ReactDOM.createPortal(
(state, action) => {
+ const { mergeNearActions } = options || {};
+
+ const useMerge =
+ action.useMerge || (mergeNearActions && state.lastActionTm && new Date().getTime() - state.lastActionTm < 100);
+
switch (action.type) {
case 'set':
return {
- history: [...state.history.slice(0, state.current + 1), action.value],
- current: state.current + 1,
+ history: [...state.history.slice(0, useMerge ? state.current : state.current + 1), action.value],
+ current: useMerge ? state.current : state.current + 1,
value: action.value,
canUndo: true,
canRedo: false,
+ lastActionTm: new Date().getTime(),
};
case 'compute': {
const newValue = action.compute(state.history[state.current]);
return {
- history: [...state.history.slice(0, state.current + 1), newValue],
- current: state.current + 1,
+ history: [...state.history.slice(0, useMerge ? state.current : state.current + 1), newValue],
+ current: useMerge ? state.current : state.current + 1,
value: newValue,
canUndo: true,
canRedo: false,
+ lastActionTm: new Date().getTime(),
};
}
case 'undo':
@@ -29,6 +36,7 @@ function reducer(state, action) {
value: state.history[state.current - 1],
canUndo: state.current > 1,
canRedo: true,
+ lastActionTm: null,
};
return state;
case 'redo':
@@ -39,6 +47,7 @@ function reducer(state, action) {
value: state.history[state.current + 1],
canUndo: true,
canRedo: state.current < state.history.length - 2,
+ lastActionTm: null,
};
return state;
case 'reset':
@@ -46,12 +55,13 @@ function reducer(state, action) {
history: [action.value],
current: 0,
value: action.value,
+ lastActionTm: null,
};
}
-}
+};
-export default function useUndoReducer(initialValue) {
- return React.useReducer(reducer, {
+export default function useUndoReducer(initialValue, options) {
+ return React.useReducer(reducer(options), {
history: [initialValue],
current: 0,
value: initialValue,
diff --git a/packages/web/src/widgets/Toolbar.js b/packages/web/src/widgets/Toolbar.js
index 34d5bef79..d6321a522 100644
--- a/packages/web/src/widgets/Toolbar.js
+++ b/packages/web/src/widgets/Toolbar.js
@@ -109,7 +109,6 @@ export default function ToolBar({ toolbarPortalRef }) {
} else if (openTabdata) {
try {
const json = JSON.parse(decodeURIComponent(openTabdata));
- console.log('TABDATA', json);
openFavorite(json);
window.history.replaceState(null, null, ' ');
} catch (err) {