mirror of
https://github.com/chartdb/chartdb.git
synced 2026-02-06 19:49:18 -06:00
14
package-lock.json
generated
14
package-lock.json
generated
@@ -24,6 +24,7 @@
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@uidotdev/usehooks": "^2.4.1",
|
||||
"@xyflow/react": "^12.0.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -2736,6 +2737,19 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@uidotdev/usehooks": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@uidotdev/usehooks/-/usehooks-2.4.1.tgz",
|
||||
"integrity": "sha512-1I+RwWyS+kdv3Mv0Vmc+p0dPYH0DTRAo04HLyXReYBL9AeseDWUJyi4THuksBJcu9F0Pih69Ak150VDnqbVnXg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18.0.0",
|
||||
"react-dom": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ungap/structured-clone": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"@radix-ui/react-toggle": "^1.1.0",
|
||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||
"@radix-ui/react-tooltip": "^1.1.2",
|
||||
"@uidotdev/usehooks": "^2.4.1",
|
||||
"@xyflow/react": "^12.0.4",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.1",
|
||||
|
||||
@@ -16,7 +16,10 @@ export interface ChartDBContext {
|
||||
|
||||
// General operations
|
||||
updateDiagramId: (id: string) => Promise<void>;
|
||||
updateDiagramName: (name: string) => Promise<void>;
|
||||
updateDiagramName: (
|
||||
name: string,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
loadDiagram: (diagramId: string) => Promise<Diagram | undefined>;
|
||||
|
||||
// Database type operations
|
||||
@@ -24,13 +27,23 @@ export interface ChartDBContext {
|
||||
|
||||
// Table operations
|
||||
createTable: () => Promise<DBTable>;
|
||||
addTable: (table: DBTable) => Promise<void>;
|
||||
addTable: (
|
||||
table: DBTable,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
getTable: (id: string) => DBTable | null;
|
||||
removeTable: (id: string) => Promise<void>;
|
||||
updateTable: (id: string, table: Partial<DBTable>) => Promise<void>;
|
||||
updateTables: (tables: PartialExcept<DBTable, 'id'>[]) => Promise<void>;
|
||||
removeTable: (
|
||||
id: string,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
updateTable: (
|
||||
id: string,
|
||||
table: Partial<DBTable>,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
updateTablesState: (
|
||||
updateFn: (tables: DBTable[]) => PartialExcept<DBTable, 'id'>[]
|
||||
updateFn: (tables: DBTable[]) => PartialExcept<DBTable, 'id'>[],
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
|
||||
// Field operations
|
||||
@@ -38,21 +51,39 @@ export interface ChartDBContext {
|
||||
updateField: (
|
||||
tableId: string,
|
||||
fieldId: string,
|
||||
field: Partial<DBField>
|
||||
field: Partial<DBField>,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
removeField: (
|
||||
tableId: string,
|
||||
fieldId: string,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
removeField: (tableId: string, fieldId: string) => Promise<void>;
|
||||
createField: (tableId: string) => Promise<DBField>;
|
||||
addField: (tableId: string, field: DBField) => Promise<void>;
|
||||
addField: (
|
||||
tableId: string,
|
||||
field: DBField,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
|
||||
// Index operations
|
||||
createIndex: (tableId: string) => Promise<DBIndex>;
|
||||
addIndex: (tableId: string, index: DBIndex) => Promise<void>;
|
||||
addIndex: (
|
||||
tableId: string,
|
||||
index: DBIndex,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
getIndex: (tableId: string, indexId: string) => DBIndex | null;
|
||||
removeIndex: (tableId: string, indexId: string) => Promise<void>;
|
||||
removeIndex: (
|
||||
tableId: string,
|
||||
indexId: string,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
updateIndex: (
|
||||
tableId: string,
|
||||
indexId: string,
|
||||
index: Partial<DBIndex>
|
||||
index: Partial<DBIndex>,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
|
||||
// Relationship operations
|
||||
@@ -62,19 +93,33 @@ export interface ChartDBContext {
|
||||
sourceFieldId: string;
|
||||
targetFieldId: string;
|
||||
}) => Promise<DBRelationship>;
|
||||
addRelationship: (relationship: DBRelationship) => Promise<void>;
|
||||
addRelationship: (
|
||||
relationship: DBRelationship,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
addRelationships: (
|
||||
relationships: DBRelationship[],
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
getRelationship: (id: string) => DBRelationship | null;
|
||||
removeRelationship: (id: string) => Promise<void>;
|
||||
removeRelationships: (...ids: string[]) => Promise<void>;
|
||||
removeRelationship: (
|
||||
id: string,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
removeRelationships: (
|
||||
ids: string[],
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
updateRelationship: (
|
||||
id: string,
|
||||
relationship: Partial<DBRelationship>
|
||||
relationship: Partial<DBRelationship>,
|
||||
options?: { updateHistory: boolean }
|
||||
) => Promise<void>;
|
||||
}
|
||||
|
||||
export const chartDBContext = createContext<ChartDBContext>({
|
||||
databaseType: DatabaseType.GENERIC,
|
||||
diagramName: 'New Diagram',
|
||||
diagramName: '',
|
||||
diagramId: '',
|
||||
tables: [],
|
||||
relationships: [],
|
||||
@@ -88,7 +133,7 @@ export const chartDBContext = createContext<ChartDBContext>({
|
||||
updateDatabaseType: emptyFn,
|
||||
|
||||
// Table operations
|
||||
updateTables: emptyFn,
|
||||
// updateTables: emptyFn,
|
||||
createTable: emptyFn,
|
||||
getTable: emptyFn,
|
||||
addTable: emptyFn,
|
||||
@@ -117,4 +162,5 @@ export const chartDBContext = createContext<ChartDBContext>({
|
||||
removeRelationship: emptyFn,
|
||||
updateRelationship: emptyFn,
|
||||
removeRelationships: emptyFn,
|
||||
addRelationships: emptyFn,
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
16
src/context/history-context/history-context.tsx
Normal file
16
src/context/history-context/history-context.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { emptyFn } from '@/lib/utils';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export interface HistoryContext {
|
||||
undo: () => void;
|
||||
redo: () => void;
|
||||
hasUndo: boolean;
|
||||
hasRedo: boolean;
|
||||
}
|
||||
|
||||
export const historyContext = createContext<HistoryContext>({
|
||||
undo: emptyFn,
|
||||
redo: emptyFn,
|
||||
hasUndo: false,
|
||||
hasRedo: false,
|
||||
});
|
||||
246
src/context/history-context/history-provider.tsx
Normal file
246
src/context/history-context/history-provider.tsx
Normal file
@@ -0,0 +1,246 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import { historyContext } from './history-context';
|
||||
import { useChartDB } from '@/hooks/use-chartdb';
|
||||
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
|
||||
import { RedoUndoActionHandlers } from './redo-undo-action';
|
||||
|
||||
export const HistoryProvider: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const {
|
||||
addRedoAction,
|
||||
addUndoAction,
|
||||
undoStack,
|
||||
redoStack,
|
||||
hasRedo,
|
||||
hasUndo,
|
||||
} = useRedoUndoStack();
|
||||
const {
|
||||
addTable,
|
||||
removeTable,
|
||||
updateTable,
|
||||
updateDiagramName,
|
||||
removeField,
|
||||
addField,
|
||||
updateField,
|
||||
addRelationship,
|
||||
addRelationships,
|
||||
removeRelationship,
|
||||
updateRelationship,
|
||||
updateTablesState,
|
||||
addIndex,
|
||||
removeIndex,
|
||||
updateIndex,
|
||||
removeRelationships,
|
||||
} = useChartDB();
|
||||
|
||||
const redoActionHandlers = useMemo(
|
||||
(): RedoUndoActionHandlers => ({
|
||||
updateDiagramName: ({ redoData: { name } }) => {
|
||||
return updateDiagramName(name, { updateHistory: false });
|
||||
},
|
||||
addTable: ({ redoData: { table } }) => {
|
||||
return addTable(table, { updateHistory: false });
|
||||
},
|
||||
removeTable: ({ redoData: { tableId } }) => {
|
||||
return removeTable(tableId, { updateHistory: false });
|
||||
},
|
||||
updateTable: ({ redoData: { tableId, table } }) => {
|
||||
return updateTable(tableId, table, { updateHistory: false });
|
||||
},
|
||||
updateTablesState: ({ redoData: { tables } }) => {
|
||||
return updateTablesState(() => tables, {
|
||||
updateHistory: false,
|
||||
});
|
||||
},
|
||||
addField: ({ redoData: { tableId, field } }) => {
|
||||
return addField(tableId, field, { updateHistory: false });
|
||||
},
|
||||
removeField: ({ redoData: { tableId, fieldId } }) => {
|
||||
return removeField(tableId, fieldId, { updateHistory: false });
|
||||
},
|
||||
updateField: ({ redoData: { tableId, fieldId, field } }) => {
|
||||
return updateField(tableId, fieldId, field, {
|
||||
updateHistory: false,
|
||||
});
|
||||
},
|
||||
addRelationship: ({ redoData: { relationship } }) => {
|
||||
return addRelationship(relationship, { updateHistory: false });
|
||||
},
|
||||
addRelationships: ({ redoData: { relationships } }) => {
|
||||
return addRelationships(relationships, {
|
||||
updateHistory: false,
|
||||
});
|
||||
},
|
||||
removeRelationship: ({ redoData: { relationshipId } }) => {
|
||||
return removeRelationship(relationshipId, {
|
||||
updateHistory: false,
|
||||
});
|
||||
},
|
||||
updateRelationship: ({
|
||||
redoData: { relationshipId, relationship },
|
||||
}) => {
|
||||
return updateRelationship(relationshipId, relationship, {
|
||||
updateHistory: false,
|
||||
});
|
||||
},
|
||||
removeRelationships: ({ redoData: { relationshipsIds } }) => {
|
||||
return removeRelationships(relationshipsIds, {
|
||||
updateHistory: false,
|
||||
});
|
||||
},
|
||||
addIndex: ({ redoData: { tableId, index } }) => {
|
||||
return addIndex(tableId, index, { updateHistory: false });
|
||||
},
|
||||
removeIndex: ({ redoData: { tableId, indexId } }) => {
|
||||
return removeIndex(tableId, indexId, { updateHistory: false });
|
||||
},
|
||||
updateIndex: ({ redoData: { tableId, indexId, index } }) => {
|
||||
return updateIndex(tableId, indexId, index, {
|
||||
updateHistory: false,
|
||||
});
|
||||
},
|
||||
}),
|
||||
[
|
||||
addTable,
|
||||
removeTable,
|
||||
updateTable,
|
||||
updateDiagramName,
|
||||
removeField,
|
||||
addField,
|
||||
updateField,
|
||||
addRelationship,
|
||||
addRelationships,
|
||||
removeRelationship,
|
||||
updateRelationship,
|
||||
updateTablesState,
|
||||
addIndex,
|
||||
removeIndex,
|
||||
updateIndex,
|
||||
removeRelationships,
|
||||
]
|
||||
);
|
||||
|
||||
const undoActionHandlers = useMemo(
|
||||
(): RedoUndoActionHandlers => ({
|
||||
updateDiagramName: ({ undoData: { name } }) => {
|
||||
return updateDiagramName(name, { updateHistory: false });
|
||||
},
|
||||
addTable: ({ undoData: { tableId } }) => {
|
||||
return removeTable(tableId, { updateHistory: false });
|
||||
},
|
||||
removeTable: ({ undoData: { table } }) => {
|
||||
return addTable(table, { updateHistory: false });
|
||||
},
|
||||
updateTable: ({ undoData: { tableId, table } }) => {
|
||||
return updateTable(tableId, table, { updateHistory: false });
|
||||
},
|
||||
addField: ({ undoData: { fieldId, tableId } }) => {
|
||||
return removeField(tableId, fieldId, { updateHistory: false });
|
||||
},
|
||||
removeField: ({ undoData: { tableId, field } }) => {
|
||||
return addField(tableId, field, { updateHistory: false });
|
||||
},
|
||||
updateField: ({ undoData: { tableId, fieldId, field } }) => {
|
||||
return updateField(tableId, fieldId, field, {
|
||||
updateHistory: false,
|
||||
});
|
||||
},
|
||||
addRelationship: ({ undoData: { relationshipId } }) => {
|
||||
return removeRelationship(relationshipId, {
|
||||
updateHistory: false,
|
||||
});
|
||||
},
|
||||
removeRelationship: ({ undoData: { relationship } }) => {
|
||||
return addRelationship(relationship, { updateHistory: false });
|
||||
},
|
||||
removeRelationships: ({ undoData: { relationships } }) => {
|
||||
return addRelationships(relationships, {
|
||||
updateHistory: false,
|
||||
});
|
||||
},
|
||||
updateRelationship: ({
|
||||
undoData: { relationshipId, relationship },
|
||||
}) => {
|
||||
return updateRelationship(relationshipId, relationship, {
|
||||
updateHistory: false,
|
||||
});
|
||||
},
|
||||
updateTablesState: ({ undoData: { tables } }) => {
|
||||
return updateTablesState(() => tables, {
|
||||
updateHistory: false,
|
||||
});
|
||||
},
|
||||
addIndex: ({ undoData: { tableId, indexId } }) => {
|
||||
return removeIndex(tableId, indexId, { updateHistory: false });
|
||||
},
|
||||
removeIndex: ({ undoData: { tableId, index } }) => {
|
||||
return addIndex(tableId, index, { updateHistory: false });
|
||||
},
|
||||
updateIndex: ({ undoData: { tableId, indexId, index } }) => {
|
||||
return updateIndex(tableId, indexId, index, {
|
||||
updateHistory: false,
|
||||
});
|
||||
},
|
||||
addRelationships: ({ undoData: { relationshipIds } }) => {
|
||||
return removeRelationships(relationshipIds, {
|
||||
updateHistory: false,
|
||||
});
|
||||
},
|
||||
}),
|
||||
[
|
||||
addTable,
|
||||
removeTable,
|
||||
updateTable,
|
||||
updateDiagramName,
|
||||
removeField,
|
||||
addField,
|
||||
updateField,
|
||||
addRelationship,
|
||||
addRelationships,
|
||||
removeRelationship,
|
||||
updateRelationship,
|
||||
updateTablesState,
|
||||
addIndex,
|
||||
removeIndex,
|
||||
updateIndex,
|
||||
removeRelationships,
|
||||
]
|
||||
);
|
||||
|
||||
const undo = async () => {
|
||||
const action = undoStack.pop();
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = undoActionHandlers[action.action];
|
||||
addRedoAction(action);
|
||||
|
||||
await handler?.({
|
||||
undoData: action.undoData,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any);
|
||||
};
|
||||
|
||||
const redo = async () => {
|
||||
const action = redoStack.pop();
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handler = redoActionHandlers[action.action];
|
||||
addUndoAction(action);
|
||||
|
||||
await handler?.({
|
||||
redoData: action.redoData,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any);
|
||||
};
|
||||
|
||||
return (
|
||||
<historyContext.Provider value={{ undo, redo, hasRedo, hasUndo }}>
|
||||
{children}
|
||||
</historyContext.Provider>
|
||||
);
|
||||
};
|
||||
144
src/context/history-context/redo-undo-action.ts
Normal file
144
src/context/history-context/redo-undo-action.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
import { DBTable } from '@/lib/domain/db-table';
|
||||
import { ChartDBContext } from '../chartdb-context/chartdb-context';
|
||||
import { DBField } from '@/lib/domain/db-field';
|
||||
import { DBIndex } from '@/lib/domain/db-index';
|
||||
import { DBRelationship } from '@/lib/domain/db-relationship';
|
||||
|
||||
type Action = keyof ChartDBContext;
|
||||
|
||||
type RedoUndoActionBase<T extends Action, RD, UD> = {
|
||||
action: T;
|
||||
redoData: RD;
|
||||
undoData: UD;
|
||||
};
|
||||
|
||||
type RedoUndoActionUpdateDiagramName = RedoUndoActionBase<
|
||||
'updateDiagramName',
|
||||
{ name: string },
|
||||
{ name: string }
|
||||
>;
|
||||
|
||||
type RedoUndoActionUpdateTable = RedoUndoActionBase<
|
||||
'updateTable',
|
||||
{ tableId: string; table: Partial<DBTable> },
|
||||
{ tableId: string; table: Partial<DBTable> }
|
||||
>;
|
||||
|
||||
type RedoUndoActionAddTable = RedoUndoActionBase<
|
||||
'addTable',
|
||||
{ table: DBTable },
|
||||
{ tableId: string }
|
||||
>;
|
||||
|
||||
type RedoUndoActionRemoveTable = RedoUndoActionBase<
|
||||
'removeTable',
|
||||
{ tableId: string },
|
||||
{ table: DBTable }
|
||||
>;
|
||||
|
||||
type RedoUndoActionUpdateTablesState = RedoUndoActionBase<
|
||||
'updateTablesState',
|
||||
{ tables: DBTable[] },
|
||||
{ tables: DBTable[] }
|
||||
>;
|
||||
|
||||
type RedoUndoActionAddField = RedoUndoActionBase<
|
||||
'addField',
|
||||
{ tableId: string; field: DBField },
|
||||
{ tableId: string; fieldId: string }
|
||||
>;
|
||||
|
||||
type RedoUndoActionRemoveField = RedoUndoActionBase<
|
||||
'removeField',
|
||||
{ tableId: string; fieldId: string },
|
||||
{ tableId: string; field: DBField }
|
||||
>;
|
||||
|
||||
type RedoUndoActionUpdateField = RedoUndoActionBase<
|
||||
'updateField',
|
||||
{ tableId: string; fieldId: string; field: Partial<DBField> },
|
||||
{ tableId: string; fieldId: string; field: Partial<DBField> }
|
||||
>;
|
||||
|
||||
type RedoUndoActionAddIndex = RedoUndoActionBase<
|
||||
'addIndex',
|
||||
{ tableId: string; index: DBIndex },
|
||||
{ tableId: string; indexId: string }
|
||||
>;
|
||||
|
||||
type RedoUndoActionRemoveIndex = RedoUndoActionBase<
|
||||
'removeIndex',
|
||||
{ tableId: string; indexId: string },
|
||||
{ tableId: string; index: DBIndex }
|
||||
>;
|
||||
|
||||
type RedoUndoActionUpdateIndex = RedoUndoActionBase<
|
||||
'updateIndex',
|
||||
{ tableId: string; indexId: string; index: Partial<DBIndex> },
|
||||
{ tableId: string; indexId: string; index: Partial<DBIndex> }
|
||||
>;
|
||||
|
||||
type RedoUndoActionAddRelationship = RedoUndoActionBase<
|
||||
'addRelationship',
|
||||
{ relationship: DBRelationship },
|
||||
{ relationshipId: string }
|
||||
>;
|
||||
|
||||
type RedoUndoActionAddRelationships = RedoUndoActionBase<
|
||||
'addRelationships',
|
||||
{ relationships: DBRelationship[] },
|
||||
{ relationshipIds: string[] }
|
||||
>;
|
||||
|
||||
type RedoUndoActionRemoveRelationship = RedoUndoActionBase<
|
||||
'removeRelationship',
|
||||
{ relationshipId: string },
|
||||
{ relationship: DBRelationship }
|
||||
>;
|
||||
|
||||
type RedoUndoActionUpdateRelationship = RedoUndoActionBase<
|
||||
'updateRelationship',
|
||||
{ relationshipId: string; relationship: Partial<DBRelationship> },
|
||||
{ relationshipId: string; relationship: Partial<DBRelationship> }
|
||||
>;
|
||||
|
||||
type RedoUndoActionRemoveRelationships = RedoUndoActionBase<
|
||||
'removeRelationships',
|
||||
{ relationshipsIds: string[] },
|
||||
{ relationships: DBRelationship[] }
|
||||
>;
|
||||
|
||||
export type RedoUndoAction =
|
||||
| RedoUndoActionAddTable
|
||||
| RedoUndoActionRemoveTable
|
||||
| RedoUndoActionUpdateTable
|
||||
| RedoUndoActionUpdateDiagramName
|
||||
| RedoUndoActionUpdateTablesState
|
||||
| RedoUndoActionAddField
|
||||
| RedoUndoActionRemoveField
|
||||
| RedoUndoActionUpdateField
|
||||
| RedoUndoActionAddIndex
|
||||
| RedoUndoActionRemoveIndex
|
||||
| RedoUndoActionUpdateIndex
|
||||
| RedoUndoActionAddRelationship
|
||||
| RedoUndoActionAddRelationships
|
||||
| RedoUndoActionRemoveRelationship
|
||||
| RedoUndoActionUpdateRelationship
|
||||
| RedoUndoActionRemoveRelationships;
|
||||
|
||||
export type RedoActionData<T extends Action> = Extract<
|
||||
RedoUndoAction,
|
||||
{ action: T }
|
||||
>['redoData'];
|
||||
|
||||
export type UndoActionData<T extends Action> = Extract<
|
||||
RedoUndoAction,
|
||||
{ action: T }
|
||||
>['undoData'];
|
||||
|
||||
export type RedoUndoActionHandlers = {
|
||||
[K in RedoUndoAction['action']]: (args: {
|
||||
redoData: RedoActionData<K>;
|
||||
undoData: UndoActionData<K>;
|
||||
}) => Promise<void>;
|
||||
};
|
||||
23
src/context/history-context/redo-undo-stack-context.tsx
Normal file
23
src/context/history-context/redo-undo-stack-context.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createContext } from 'react';
|
||||
import { RedoUndoAction } from './redo-undo-action';
|
||||
import { emptyFn } from '@/lib/utils';
|
||||
|
||||
export interface RedoUndoStackContext {
|
||||
redoStack: RedoUndoAction[];
|
||||
undoStack: RedoUndoAction[];
|
||||
addRedoAction: (action: RedoUndoAction) => void;
|
||||
addUndoAction: (action: RedoUndoAction) => void;
|
||||
resetRedoStack: () => void;
|
||||
hasRedo: boolean;
|
||||
hasUndo: boolean;
|
||||
}
|
||||
|
||||
export const redoUndoStackContext = createContext<RedoUndoStackContext>({
|
||||
redoStack: [],
|
||||
undoStack: [],
|
||||
addRedoAction: emptyFn,
|
||||
addUndoAction: emptyFn,
|
||||
resetRedoStack: emptyFn,
|
||||
hasRedo: false,
|
||||
hasUndo: false,
|
||||
});
|
||||
51
src/context/history-context/redo-undo-stack-provider.tsx
Normal file
51
src/context/history-context/redo-undo-stack-provider.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { RedoUndoAction } from './redo-undo-action';
|
||||
import {
|
||||
RedoUndoStackContext,
|
||||
redoUndoStackContext,
|
||||
} from './redo-undo-stack-context';
|
||||
|
||||
export const RedoUndoStackProvider: React.FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [undoStack, setUndoStack] = React.useState<RedoUndoAction[]>([]);
|
||||
const [redoStack, setRedoStack] = React.useState<RedoUndoAction[]>([]);
|
||||
|
||||
const addRedoAction: RedoUndoStackContext['addRedoAction'] = useCallback(
|
||||
(action) => {
|
||||
setRedoStack((prev) => [...prev, action]);
|
||||
},
|
||||
[setRedoStack]
|
||||
);
|
||||
|
||||
const addUndoAction: RedoUndoStackContext['addUndoAction'] = useCallback(
|
||||
(action) => {
|
||||
setUndoStack((prev) => [...prev, action]);
|
||||
},
|
||||
[setUndoStack]
|
||||
);
|
||||
|
||||
const resetRedoStack: RedoUndoStackContext['resetRedoStack'] =
|
||||
useCallback(() => {
|
||||
setRedoStack([]);
|
||||
}, [setRedoStack]);
|
||||
|
||||
const hasRedo = redoStack.length > 0;
|
||||
const hasUndo = undoStack.length > 0;
|
||||
|
||||
return (
|
||||
<redoUndoStackContext.Provider
|
||||
value={{
|
||||
redoStack,
|
||||
undoStack,
|
||||
addRedoAction,
|
||||
addUndoAction,
|
||||
resetRedoStack,
|
||||
hasRedo,
|
||||
hasUndo,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</redoUndoStackContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -16,7 +16,7 @@ import { getDatabaseLogo } from '@/lib/databases';
|
||||
import { CodeSnippet } from '@/components/code-snippet/code-snippet';
|
||||
import { Textarea } from '@/components/textarea/textarea';
|
||||
import { useStorage } from '@/hooks/use-storage';
|
||||
import { loadFromDatabaseMetadata } from '@/lib/domain/diagram';
|
||||
import { Diagram, loadFromDatabaseMetadata } from '@/lib/domain/diagram';
|
||||
import { useCreateDiagramDialog } from '@/hooks/use-create-diagram-dialog';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useConfig } from '@/hooks/use-config';
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
DatabaseMetadata,
|
||||
loadDatabaseMetadata,
|
||||
} from '@/lib/data/import-metadata/metadata-types/database-metadata';
|
||||
import { generateId } from '@/lib/utils';
|
||||
|
||||
enum CreateDiagramDialogStep {
|
||||
SELECT_DATABASE = 'SELECT_DATABASE',
|
||||
@@ -59,14 +60,22 @@ export const CreateDiagramDialog: React.FC<CreateDiagramDialogProps> = ({
|
||||
}, [listDiagrams, setDiagramNumber]);
|
||||
|
||||
const createNewDiagram = useCallback(async () => {
|
||||
const databaseMetadata: DatabaseMetadata =
|
||||
loadDatabaseMetadata(scriptResult);
|
||||
let diagram: Diagram = {
|
||||
id: generateId(),
|
||||
name: `Diagram ${diagramNumber}`,
|
||||
databaseType: databaseType ?? DatabaseType.GENERIC,
|
||||
};
|
||||
|
||||
const diagram = loadFromDatabaseMetadata({
|
||||
databaseType,
|
||||
databaseMetadata,
|
||||
diagramNumber,
|
||||
});
|
||||
if (scriptResult.trim().length !== 0) {
|
||||
const databaseMetadata: DatabaseMetadata =
|
||||
loadDatabaseMetadata(scriptResult);
|
||||
|
||||
diagram = loadFromDatabaseMetadata({
|
||||
databaseType,
|
||||
databaseMetadata,
|
||||
diagramNumber,
|
||||
});
|
||||
}
|
||||
|
||||
await addDiagram({ diagram });
|
||||
await updateConfig({ defaultDiagramId: diagram.id });
|
||||
|
||||
4
src/hooks/use-history.ts
Normal file
4
src/hooks/use-history.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { useContext } from 'react';
|
||||
import { historyContext } from '@/context/history-context/history-context';
|
||||
|
||||
export const useHistory = () => useContext(historyContext);
|
||||
4
src/hooks/use-redo-undo-stack.ts
Normal file
4
src/hooks/use-redo-undo-stack.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { redoUndoStackContext } from '@/context/history-context/redo-undo-stack-context';
|
||||
import { useContext } from 'react';
|
||||
|
||||
export const useRedoUndoStack = () => useContext(redoUndoStackContext);
|
||||
@@ -156,7 +156,7 @@ export const Canvas: React.FC<CanvasProps> = () => {
|
||||
.filter((id) => !!id) as string[];
|
||||
|
||||
if (relationshipsToRemove.length > 0) {
|
||||
removeRelationships(...relationshipsToRemove);
|
||||
removeRelationships(relationshipsToRemove);
|
||||
}
|
||||
|
||||
const selectionChanges = changes.filter(
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import React from 'react';
|
||||
import { Card, CardContent } from '@/components/card/card';
|
||||
import { ZoomIn, ZoomOut, Save } from 'lucide-react';
|
||||
import { ZoomIn, ZoomOut, Save, Redo, Undo } from 'lucide-react';
|
||||
import { Separator } from '@/components/separator/separator';
|
||||
import { ToolbarButton } from './toolbar-button';
|
||||
import { useHistory } from '@/hooks/use-history';
|
||||
|
||||
export interface ToolbarProps {}
|
||||
|
||||
export const Toolbar: React.FC<ToolbarProps> = () => {
|
||||
const { redo, undo, hasRedo, hasUndo } = useHistory();
|
||||
return (
|
||||
<div className="px-1">
|
||||
<Card className="shadow-none p-0 bg-secondary h-[44px]">
|
||||
@@ -21,6 +23,13 @@ export const Toolbar: React.FC<ToolbarProps> = () => {
|
||||
<ToolbarButton>
|
||||
<Save />
|
||||
</ToolbarButton>
|
||||
<Separator orientation="vertical" />
|
||||
<ToolbarButton onClick={undo} disabled={!hasUndo}>
|
||||
<Undo />
|
||||
</ToolbarButton>
|
||||
<ToolbarButton onClick={redo} disabled={!hasRedo}>
|
||||
<Redo />
|
||||
</ToolbarButton>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,8 @@ import { ReactFlowProvider } from '@xyflow/react';
|
||||
import { StorageProvider } from './context/storage-context/storage-provider';
|
||||
import { CreateDiagramDialogProvider } from './dialogs/create-diagram-dialog/create-diagram-dialog-provider';
|
||||
import { ConfigProvider } from './context/config-context/config-provider';
|
||||
import { HistoryProvider } from './context/history-context/history-provider';
|
||||
import { RedoUndoStackProvider } from './context/history-context/redo-undo-stack-provider';
|
||||
|
||||
const routes: RouteObject[] = [
|
||||
...['', 'diagrams/:diagramId'].map((path) => ({
|
||||
@@ -14,13 +16,17 @@ const routes: RouteObject[] = [
|
||||
element: (
|
||||
<StorageProvider>
|
||||
<ConfigProvider>
|
||||
<ChartDBProvider>
|
||||
<CreateDiagramDialogProvider>
|
||||
<ReactFlowProvider>
|
||||
<EditorPage />
|
||||
</ReactFlowProvider>
|
||||
</CreateDiagramDialogProvider>
|
||||
</ChartDBProvider>
|
||||
<RedoUndoStackProvider>
|
||||
<ChartDBProvider>
|
||||
<HistoryProvider>
|
||||
<CreateDiagramDialogProvider>
|
||||
<ReactFlowProvider>
|
||||
<EditorPage />
|
||||
</ReactFlowProvider>
|
||||
</CreateDiagramDialogProvider>
|
||||
</HistoryProvider>
|
||||
</ChartDBProvider>
|
||||
</RedoUndoStackProvider>
|
||||
</ConfigProvider>
|
||||
</StorageProvider>
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user