mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-05-01 20:13:57 +00:00
query designer - undo
This commit is contained in:
@@ -128,10 +128,13 @@ export default function Designer({ value, onChange, conid, database }) {
|
|||||||
|
|
||||||
const bringToFront = React.useCallback(
|
const bringToFront = React.useCallback(
|
||||||
(table) => {
|
(table) => {
|
||||||
onChange((current) => ({
|
onChange(
|
||||||
...current,
|
(current) => ({
|
||||||
tables: [...(current.tables || []).filter((x) => x.designerId != table.designerId), table],
|
...current,
|
||||||
}));
|
tables: [...(current.tables || []).filter((x) => x.designerId != table.designerId), table],
|
||||||
|
}),
|
||||||
|
true
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[onChange]
|
[onChange]
|
||||||
);
|
);
|
||||||
@@ -260,14 +263,17 @@ export default function Designer({ value, onChange, conid, database }) {
|
|||||||
|
|
||||||
const handleSelectColumn = React.useCallback(
|
const handleSelectColumn = React.useCallback(
|
||||||
(column) => {
|
(column) => {
|
||||||
onChange((current) => ({
|
onChange(
|
||||||
...current,
|
(current) => ({
|
||||||
columns: (current.columns || []).find(
|
...current,
|
||||||
(x) => x.designerId == column.designerId && x.columnName == column.columnName
|
columns: (current.columns || []).find(
|
||||||
)
|
(x) => x.designerId == column.designerId && x.columnName == column.columnName
|
||||||
? current.columns
|
)
|
||||||
: [...cleanupDesignColumns(current.columns), _.pick(column, ['designerId', 'columnName'])],
|
? current.columns
|
||||||
}));
|
: [...cleanupDesignColumns(current.columns), _.pick(column, ['designerId', 'columnName'])],
|
||||||
|
}),
|
||||||
|
true
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[onChange]
|
[onChange]
|
||||||
);
|
);
|
||||||
@@ -275,19 +281,20 @@ export default function Designer({ value, onChange, conid, database }) {
|
|||||||
const handleChangeColumn = React.useCallback(
|
const handleChangeColumn = React.useCallback(
|
||||||
(column, changeFunc) => {
|
(column, changeFunc) => {
|
||||||
onChange((current) => {
|
onChange((current) => {
|
||||||
const existing = (current.columns || []).find(
|
const currentColumns = (current || {}).columns || [];
|
||||||
|
const existing = currentColumns.find(
|
||||||
(x) => x.designerId == column.designerId && x.columnName == column.columnName
|
(x) => x.designerId == column.designerId && x.columnName == column.columnName
|
||||||
);
|
);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return {
|
return {
|
||||||
...current,
|
...current,
|
||||||
columns: current.columns.map((x) => (x == existing ? changeFunc(existing) : x)),
|
columns: currentColumns.map((x) => (x == existing ? changeFunc(existing) : x)),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
...current,
|
...current,
|
||||||
columns: [
|
columns: [
|
||||||
...cleanupDesignColumns(current.columns),
|
...cleanupDesignColumns(currentColumns),
|
||||||
changeFunc(_.pick(column, ['designerId', 'columnName'])),
|
changeFunc(_.pick(column, ['designerId', 'columnName'])),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import InlineButton from '../widgets/InlineButton';
|
|||||||
import { findDesignerFilterType } from './designerTools';
|
import { findDesignerFilterType } from './designerTools';
|
||||||
|
|
||||||
function getTableDisplayName(column, tables) {
|
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;
|
if (table) return table.alias || table.pureName;
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import useHasPermission from '../utility/useHasPermission';
|
import useHasPermission from '../utility/useHasPermission';
|
||||||
import ToolbarButton from '../widgets/ToolbarButton';
|
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();
|
const hasPermission = useHasPermission();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -14,6 +14,12 @@ export default function QueryDesignToolbar({ execute, isDatabaseDefined, busy, s
|
|||||||
Save
|
Save
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
)}
|
)}
|
||||||
|
<ToolbarButton disabled={!modelState.canUndo} onClick={() => dispatchModel({ type: 'undo' })} icon="icon undo">
|
||||||
|
Undo
|
||||||
|
</ToolbarButton>
|
||||||
|
<ToolbarButton disabled={!modelState.canRedo} onClick={() => dispatchModel({ type: 'redo' })} icon="icon redo">
|
||||||
|
Redo
|
||||||
|
</ToolbarButton>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import QueryDesigner from '../designer/QueryDesigner';
|
|||||||
import QueryDesignColumns from '../designer/QueryDesignColumns';
|
import QueryDesignColumns from '../designer/QueryDesignColumns';
|
||||||
import { findEngineDriver } from 'dbgate-tools';
|
import { findEngineDriver } from 'dbgate-tools';
|
||||||
import { generateDesignedQuery } from '../designer/designerTools';
|
import { generateDesignedQuery } from '../designer/designerTools';
|
||||||
|
import useUndoReducer from '../utility/useUndoReducer';
|
||||||
|
|
||||||
export default function QueryDesignTab({ tabid, conid, database, tabVisible, toolbarPortalRef, ...other }) {
|
export default function QueryDesignTab({ tabid, conid, database, tabVisible, toolbarPortalRef, ...other }) {
|
||||||
const [sessionId, setSessionId] = React.useState(null);
|
const [sessionId, setSessionId] = React.useState(null);
|
||||||
@@ -36,11 +37,26 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too
|
|||||||
const connection = useConnectionInfo({ conid });
|
const connection = useConnectionInfo({ conid });
|
||||||
const engine = findEngineDriver(connection, extensions);
|
const engine = findEngineDriver(connection, extensions);
|
||||||
const [sqlPreview, setSqlPreview] = React.useState('');
|
const [sqlPreview, setSqlPreview] = React.useState('');
|
||||||
const { editorData, setEditorData, isLoading } = useEditorData({
|
const { initialData, setEditorData, isLoading } = useEditorData({
|
||||||
tabid,
|
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(() => {
|
const handleSessionDone = React.useCallback(() => {
|
||||||
setBusy(false);
|
setBusy(false);
|
||||||
@@ -53,8 +69,19 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too
|
|||||||
};
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
generatePreview(editorData, engine);
|
generatePreview(modelState.value, engine);
|
||||||
}, [editorData, 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(() => {
|
React.useEffect(() => {
|
||||||
if (sessionId && socket) {
|
if (sessionId && socket) {
|
||||||
@@ -136,15 +163,15 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too
|
|||||||
<>
|
<>
|
||||||
<VerticalSplitter initialValue="70%">
|
<VerticalSplitter initialValue="70%">
|
||||||
<QueryDesigner
|
<QueryDesigner
|
||||||
value={editorData || {}}
|
value={modelState.value || {}}
|
||||||
conid={conid}
|
conid={conid}
|
||||||
database={database}
|
database={database}
|
||||||
engine={connection && connection.engine}
|
engine={connection && connection.engine}
|
||||||
onChange={setEditorData}
|
onChange={handleChange}
|
||||||
></QueryDesigner>
|
></QueryDesigner>
|
||||||
<ResultTabs sessionId={sessionId} executeNumber={executeNumber}>
|
<ResultTabs sessionId={sessionId} executeNumber={executeNumber}>
|
||||||
<TabPage label="Columns" key="columns">
|
<TabPage label="Columns" key="columns">
|
||||||
<QueryDesignColumns value={editorData || {}} onChange={setEditorData} />
|
<QueryDesignColumns value={modelState.value || {}} onChange={handleChange} />
|
||||||
</TabPage>
|
</TabPage>
|
||||||
<TabPage label="SQL" key="sql">
|
<TabPage label="SQL" key="sql">
|
||||||
<SqlEditor value={sqlPreview} engine={engine} readOnly />
|
<SqlEditor value={sqlPreview} engine={engine} readOnly />
|
||||||
@@ -164,6 +191,8 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too
|
|||||||
tabVisible &&
|
tabVisible &&
|
||||||
ReactDOM.createPortal(
|
ReactDOM.createPortal(
|
||||||
<QueryDesignToolbar
|
<QueryDesignToolbar
|
||||||
|
modelState={modelState}
|
||||||
|
dispatchModel={dispatchModel}
|
||||||
isDatabaseDefined={conid && database}
|
isDatabaseDefined={conid && database}
|
||||||
execute={handleExecute}
|
execute={handleExecute}
|
||||||
busy={busy}
|
busy={busy}
|
||||||
@@ -178,7 +207,7 @@ export default function QueryDesignTab({ tabid, conid, database, tabVisible, too
|
|||||||
<SaveTabModal
|
<SaveTabModal
|
||||||
modalState={saveFileModalState}
|
modalState={saveFileModalState}
|
||||||
tabVisible={tabVisible}
|
tabVisible={tabVisible}
|
||||||
data={editorData}
|
data={modelState.value}
|
||||||
format="json"
|
format="json"
|
||||||
folder="query"
|
folder="query"
|
||||||
tabid={tabid}
|
tabid={tabid}
|
||||||
|
|||||||
@@ -1,24 +1,31 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
function reducer(state, action) {
|
const reducer = (options) => (state, action) => {
|
||||||
|
const { mergeNearActions } = options || {};
|
||||||
|
|
||||||
|
const useMerge =
|
||||||
|
action.useMerge || (mergeNearActions && state.lastActionTm && new Date().getTime() - state.lastActionTm < 100);
|
||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'set':
|
case 'set':
|
||||||
return {
|
return {
|
||||||
history: [...state.history.slice(0, state.current + 1), action.value],
|
history: [...state.history.slice(0, useMerge ? state.current : state.current + 1), action.value],
|
||||||
current: state.current + 1,
|
current: useMerge ? state.current : state.current + 1,
|
||||||
value: action.value,
|
value: action.value,
|
||||||
canUndo: true,
|
canUndo: true,
|
||||||
canRedo: false,
|
canRedo: false,
|
||||||
|
lastActionTm: new Date().getTime(),
|
||||||
};
|
};
|
||||||
case 'compute': {
|
case 'compute': {
|
||||||
const newValue = action.compute(state.history[state.current]);
|
const newValue = action.compute(state.history[state.current]);
|
||||||
return {
|
return {
|
||||||
history: [...state.history.slice(0, state.current + 1), newValue],
|
history: [...state.history.slice(0, useMerge ? state.current : state.current + 1), newValue],
|
||||||
current: state.current + 1,
|
current: useMerge ? state.current : state.current + 1,
|
||||||
value: newValue,
|
value: newValue,
|
||||||
canUndo: true,
|
canUndo: true,
|
||||||
canRedo: false,
|
canRedo: false,
|
||||||
|
lastActionTm: new Date().getTime(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case 'undo':
|
case 'undo':
|
||||||
@@ -29,6 +36,7 @@ function reducer(state, action) {
|
|||||||
value: state.history[state.current - 1],
|
value: state.history[state.current - 1],
|
||||||
canUndo: state.current > 1,
|
canUndo: state.current > 1,
|
||||||
canRedo: true,
|
canRedo: true,
|
||||||
|
lastActionTm: null,
|
||||||
};
|
};
|
||||||
return state;
|
return state;
|
||||||
case 'redo':
|
case 'redo':
|
||||||
@@ -39,6 +47,7 @@ function reducer(state, action) {
|
|||||||
value: state.history[state.current + 1],
|
value: state.history[state.current + 1],
|
||||||
canUndo: true,
|
canUndo: true,
|
||||||
canRedo: state.current < state.history.length - 2,
|
canRedo: state.current < state.history.length - 2,
|
||||||
|
lastActionTm: null,
|
||||||
};
|
};
|
||||||
return state;
|
return state;
|
||||||
case 'reset':
|
case 'reset':
|
||||||
@@ -46,12 +55,13 @@ function reducer(state, action) {
|
|||||||
history: [action.value],
|
history: [action.value],
|
||||||
current: 0,
|
current: 0,
|
||||||
value: action.value,
|
value: action.value,
|
||||||
|
lastActionTm: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function useUndoReducer(initialValue) {
|
export default function useUndoReducer(initialValue, options) {
|
||||||
return React.useReducer(reducer, {
|
return React.useReducer(reducer(options), {
|
||||||
history: [initialValue],
|
history: [initialValue],
|
||||||
current: 0,
|
current: 0,
|
||||||
value: initialValue,
|
value: initialValue,
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ export default function ToolBar({ toolbarPortalRef }) {
|
|||||||
} else if (openTabdata) {
|
} else if (openTabdata) {
|
||||||
try {
|
try {
|
||||||
const json = JSON.parse(decodeURIComponent(openTabdata));
|
const json = JSON.parse(decodeURIComponent(openTabdata));
|
||||||
console.log('TABDATA', json);
|
|
||||||
openFavorite(json);
|
openFavorite(json);
|
||||||
window.history.replaceState(null, null, ' ');
|
window.history.replaceState(null, null, ' ');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user