mirror of
https://github.com/chartdb/chartdb.git
synced 2025-11-18 12:53:14 +00:00
952 lines
31 KiB
TypeScript
952 lines
31 KiB
TypeScript
import React, { useCallback, useMemo, useState } from 'react';
|
|
import { DBTable } from '@/lib/domain/db-table';
|
|
import { generateId, randomHSLA } from '@/lib/utils';
|
|
import { ChartDBContext, chartDBContext } from './chartdb-context';
|
|
import { DatabaseType } from '@/lib/domain/database-type';
|
|
import { DBField } from '@/lib/domain/db-field';
|
|
import { DBIndex } from '@/lib/domain/db-index';
|
|
import { DBRelationship } from '@/lib/domain/db-relationship';
|
|
import { useStorage } from '@/hooks/use-storage';
|
|
import { useRedoUndoStack } from '@/hooks/use-redo-undo-stack';
|
|
import { Diagram } from '@/lib/domain/diagram';
|
|
|
|
export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
|
|
children,
|
|
}) => {
|
|
const db = useStorage();
|
|
const { addUndoAction, resetRedoStack } = useRedoUndoStack();
|
|
const [diagramId, setDiagramId] = useState('');
|
|
const [diagramName, setDiagramName] = useState('');
|
|
const [diagramCreatedAt, setDiagramCreatedAt] = useState<Date>(new Date());
|
|
const [diagramUpdatedAt, setDiagramUpdatedAt] = useState<Date>(new Date());
|
|
const [databaseType, setDatabaseType] = useState<DatabaseType>(
|
|
DatabaseType.GENERIC
|
|
);
|
|
const [tables, setTables] = useState<DBTable[]>([]);
|
|
const [relationships, setRelationships] = useState<DBRelationship[]>([]);
|
|
|
|
const currentDiagram: Diagram = useMemo(
|
|
() => ({
|
|
id: diagramId,
|
|
name: diagramName,
|
|
createdAt: diagramCreatedAt,
|
|
updatedAt: diagramUpdatedAt,
|
|
databaseType,
|
|
tables,
|
|
relationships,
|
|
}),
|
|
[
|
|
diagramId,
|
|
diagramName,
|
|
databaseType,
|
|
tables,
|
|
relationships,
|
|
diagramCreatedAt,
|
|
diagramUpdatedAt,
|
|
]
|
|
);
|
|
|
|
const updateDiagramUpdatedAt: ChartDBContext['updateDiagramUpdatedAt'] =
|
|
useCallback(async () => {
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
await db.updateDiagram({
|
|
id: diagramId,
|
|
attributes: { updatedAt },
|
|
});
|
|
}, [db, diagramId, setDiagramUpdatedAt]);
|
|
|
|
const updateDatabaseType: ChartDBContext['updateDatabaseType'] =
|
|
useCallback(
|
|
async (databaseType) => {
|
|
setDatabaseType(databaseType);
|
|
await db.updateDiagram({
|
|
id: diagramId,
|
|
attributes: {
|
|
databaseType,
|
|
},
|
|
});
|
|
},
|
|
[db, diagramId, setDatabaseType]
|
|
);
|
|
const updateDiagramId: ChartDBContext['updateDiagramId'] = useCallback(
|
|
async (id) => {
|
|
const prevId = diagramId;
|
|
setDiagramId(id);
|
|
await db.updateDiagram({ id: prevId, attributes: { id } });
|
|
},
|
|
[db, diagramId, setDiagramId]
|
|
);
|
|
|
|
const updateDiagramName: ChartDBContext['updateDiagramName'] = useCallback(
|
|
async (name, options = { updateHistory: true }) => {
|
|
const prevName = diagramName;
|
|
setDiagramName(name);
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
await db.updateDiagram({
|
|
id: diagramId,
|
|
attributes: { name, updatedAt },
|
|
});
|
|
|
|
if (options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'updateDiagramName',
|
|
redoData: { name },
|
|
undoData: { name: prevName },
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[
|
|
db,
|
|
diagramId,
|
|
setDiagramName,
|
|
addUndoAction,
|
|
diagramName,
|
|
resetRedoStack,
|
|
]
|
|
);
|
|
|
|
const addTable: ChartDBContext['addTable'] = useCallback(
|
|
async (table: DBTable, options = { updateHistory: true }) => {
|
|
setTables((tables) => [...tables, table]);
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
await Promise.all([
|
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
|
db.addTable({ diagramId, table }),
|
|
]);
|
|
|
|
if (options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'addTable',
|
|
redoData: { table },
|
|
undoData: { tableId: table.id },
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[db, diagramId, setTables, addUndoAction, resetRedoStack]
|
|
);
|
|
|
|
const createTable: ChartDBContext['createTable'] = useCallback(async () => {
|
|
const table: DBTable = {
|
|
id: generateId(),
|
|
name: `table_${tables.length + 1}`,
|
|
x: 0,
|
|
y: 0,
|
|
fields: [
|
|
{
|
|
id: generateId(),
|
|
name: 'id',
|
|
type:
|
|
databaseType === DatabaseType.SQLITE
|
|
? 'integer'
|
|
: 'bigint',
|
|
unique: true,
|
|
nullable: false,
|
|
primaryKey: true,
|
|
createdAt: Date.now(),
|
|
},
|
|
],
|
|
indexes: [],
|
|
color: randomHSLA(),
|
|
createdAt: Date.now(),
|
|
isView: false,
|
|
};
|
|
await addTable(table);
|
|
|
|
return table;
|
|
}, [addTable, tables, databaseType]);
|
|
|
|
const getTable: ChartDBContext['getTable'] = useCallback(
|
|
(id: string) => tables.find((table) => table.id === id) ?? null,
|
|
[tables]
|
|
);
|
|
|
|
const removeTable: ChartDBContext['removeTable'] = useCallback(
|
|
async (id: string, options = { updateHistory: true }) => {
|
|
const table = getTable(id);
|
|
setTables((tables) => tables.filter((table) => table.id !== id));
|
|
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
await Promise.all([
|
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
|
db.deleteTable({ diagramId, id }),
|
|
]);
|
|
|
|
if (!!table && options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'removeTable',
|
|
redoData: { tableId: id },
|
|
undoData: { table },
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[db, diagramId, setTables, addUndoAction, resetRedoStack, getTable]
|
|
);
|
|
|
|
const updateTable: ChartDBContext['updateTable'] = useCallback(
|
|
async (
|
|
id: string,
|
|
table: Partial<DBTable>,
|
|
options = { updateHistory: true }
|
|
) => {
|
|
const prevTable = getTable(id);
|
|
setTables((tables) =>
|
|
tables.map((t) => (t.id === id ? { ...t, ...table } : t))
|
|
);
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
await Promise.all([
|
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
|
db.updateTable({ id, attributes: table }),
|
|
]);
|
|
|
|
if (!!prevTable && options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'updateTable',
|
|
redoData: { tableId: id, table },
|
|
undoData: { tableId: id, table: prevTable },
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[db, setTables, addUndoAction, resetRedoStack, getTable, diagramId]
|
|
);
|
|
|
|
const updateTablesState: ChartDBContext['updateTablesState'] = useCallback(
|
|
async (
|
|
updateFn: (tables: DBTable[]) => PartialExcept<DBTable, 'id'>[],
|
|
options = { updateHistory: true, forceOverride: false }
|
|
) => {
|
|
const updateTables = (prevTables: DBTable[]) => {
|
|
const updatedTables = updateFn(prevTables);
|
|
if (options.forceOverride) {
|
|
return updatedTables as DBTable[];
|
|
}
|
|
|
|
return prevTables
|
|
.map((prevTable) => {
|
|
const updatedTable = updatedTables.find(
|
|
(t) => t.id === prevTable.id
|
|
);
|
|
return updatedTable
|
|
? { ...prevTable, ...updatedTable }
|
|
: prevTable;
|
|
})
|
|
.filter((prevTable) =>
|
|
updatedTables.some((t) => t.id === prevTable.id)
|
|
);
|
|
};
|
|
|
|
const prevTables = [...tables];
|
|
// const updatedTablesAttrs = updateFn(tables);
|
|
const updatedTables = updateTables(tables);
|
|
setTables(updateTables);
|
|
|
|
const promises = [];
|
|
for (const updatedTable of updatedTables) {
|
|
promises.push(
|
|
db.updateTable({
|
|
id: updatedTable.id,
|
|
attributes: updatedTable,
|
|
})
|
|
);
|
|
}
|
|
|
|
const tablesToDelete = tables.filter(
|
|
(table) => !updatedTables.some((t) => t.id === table.id)
|
|
);
|
|
|
|
for (const table of tablesToDelete) {
|
|
promises.push(db.deleteTable({ diagramId, id: table.id }));
|
|
}
|
|
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
promises.push(
|
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } })
|
|
);
|
|
|
|
await Promise.all(promises);
|
|
|
|
if (options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'updateTablesState',
|
|
redoData: { tables: updatedTables },
|
|
undoData: { tables: prevTables },
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[db, tables, setTables, diagramId, addUndoAction, resetRedoStack]
|
|
);
|
|
|
|
const getField: ChartDBContext['getField'] = useCallback(
|
|
(tableId: string, fieldId: string) => {
|
|
const table = getTable(tableId);
|
|
return table?.fields.find((f) => f.id === fieldId) ?? null;
|
|
},
|
|
[getTable]
|
|
);
|
|
|
|
const updateField: ChartDBContext['updateField'] = useCallback(
|
|
async (
|
|
tableId: string,
|
|
fieldId: string,
|
|
field: Partial<DBField>,
|
|
options = { updateHistory: true }
|
|
) => {
|
|
const prevField = getField(tableId, fieldId);
|
|
setTables((tables) =>
|
|
tables.map((table) =>
|
|
table.id === tableId
|
|
? {
|
|
...table,
|
|
fields: table.fields.map((f) =>
|
|
f.id === fieldId ? { ...f, ...field } : f
|
|
),
|
|
}
|
|
: table
|
|
)
|
|
);
|
|
|
|
const table = await db.getTable({ diagramId, id: tableId });
|
|
if (!table) {
|
|
return;
|
|
}
|
|
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
await Promise.all([
|
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
|
db.updateTable({
|
|
id: tableId,
|
|
attributes: {
|
|
...table,
|
|
fields: table.fields.map((f) =>
|
|
f.id === fieldId ? { ...f, ...field } : f
|
|
),
|
|
},
|
|
}),
|
|
]);
|
|
|
|
if (!!prevField && options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'updateField',
|
|
redoData: {
|
|
tableId,
|
|
fieldId,
|
|
field: { ...prevField, ...field },
|
|
},
|
|
undoData: { tableId, fieldId, field: prevField },
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[db, diagramId, setTables, addUndoAction, resetRedoStack, getField]
|
|
);
|
|
|
|
const removeField: ChartDBContext['removeField'] = useCallback(
|
|
async (
|
|
tableId: string,
|
|
fieldId: string,
|
|
options = { updateHistory: true }
|
|
) => {
|
|
const prevField = getField(tableId, fieldId);
|
|
setTables((tables) =>
|
|
tables.map((table) =>
|
|
table.id === tableId
|
|
? {
|
|
...table,
|
|
fields: table.fields.filter(
|
|
(f) => f.id !== fieldId
|
|
),
|
|
}
|
|
: table
|
|
)
|
|
);
|
|
|
|
const table = await db.getTable({ diagramId, id: tableId });
|
|
if (!table) {
|
|
return;
|
|
}
|
|
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
await Promise.all([
|
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
|
db.updateTable({
|
|
id: tableId,
|
|
attributes: {
|
|
...table,
|
|
fields: table.fields.filter((f) => f.id !== fieldId),
|
|
},
|
|
}),
|
|
]);
|
|
|
|
if (!!prevField && options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'removeField',
|
|
redoData: { tableId, fieldId },
|
|
undoData: { tableId, field: prevField },
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[db, diagramId, setTables, addUndoAction, resetRedoStack, getField]
|
|
);
|
|
|
|
const addField: ChartDBContext['addField'] = useCallback(
|
|
async (
|
|
tableId: string,
|
|
field: DBField,
|
|
options = { updateHistory: true }
|
|
) => {
|
|
setTables((tables) =>
|
|
tables.map((table) =>
|
|
table.id === tableId
|
|
? { ...table, fields: [...table.fields, field] }
|
|
: table
|
|
)
|
|
);
|
|
|
|
const table = await db.getTable({ diagramId, id: tableId });
|
|
|
|
if (!table) {
|
|
return;
|
|
}
|
|
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
await Promise.all([
|
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
|
db.updateTable({
|
|
id: tableId,
|
|
attributes: {
|
|
...table,
|
|
fields: [...table.fields, field],
|
|
},
|
|
}),
|
|
]);
|
|
|
|
if (options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'addField',
|
|
redoData: { tableId, field },
|
|
undoData: { tableId, fieldId: field.id },
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[db, diagramId, setTables, addUndoAction, resetRedoStack]
|
|
);
|
|
|
|
const createField: ChartDBContext['createField'] = useCallback(
|
|
async (tableId: string) => {
|
|
const table = getTable(tableId);
|
|
const field: DBField = {
|
|
id: generateId(),
|
|
name: `field_${(table?.fields?.length ?? 0) + 1}`,
|
|
type: 'bigint',
|
|
unique: false,
|
|
nullable: true,
|
|
primaryKey: false,
|
|
createdAt: Date.now(),
|
|
};
|
|
|
|
await addField(tableId, field);
|
|
|
|
return field;
|
|
},
|
|
[addField, getTable]
|
|
);
|
|
|
|
const getIndex: ChartDBContext['getIndex'] = useCallback(
|
|
(tableId: string, indexId: string) => {
|
|
const table = getTable(tableId);
|
|
return table?.indexes.find((i) => i.id === indexId) ?? null;
|
|
},
|
|
[getTable]
|
|
);
|
|
|
|
const addIndex: ChartDBContext['addIndex'] = useCallback(
|
|
async (
|
|
tableId: string,
|
|
index: DBIndex,
|
|
options = { updateHistory: true }
|
|
) => {
|
|
setTables((tables) =>
|
|
tables.map((table) =>
|
|
table.id === tableId
|
|
? { ...table, indexes: [...table.indexes, index] }
|
|
: table
|
|
)
|
|
);
|
|
|
|
const dbTable = await db.getTable({ diagramId, id: tableId });
|
|
if (!dbTable) {
|
|
return;
|
|
}
|
|
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
await Promise.all([
|
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
|
db.updateTable({
|
|
id: tableId,
|
|
attributes: {
|
|
...dbTable,
|
|
indexes: [...dbTable.indexes, index],
|
|
},
|
|
}),
|
|
]);
|
|
|
|
if (options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'addIndex',
|
|
redoData: { tableId, index },
|
|
undoData: { tableId, indexId: index.id },
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[db, diagramId, setTables, addUndoAction, resetRedoStack]
|
|
);
|
|
|
|
const removeIndex: ChartDBContext['removeIndex'] = useCallback(
|
|
async (
|
|
tableId: string,
|
|
indexId: string,
|
|
options = { updateHistory: true }
|
|
) => {
|
|
const prevIndex = getIndex(tableId, indexId);
|
|
setTables((tables) =>
|
|
tables.map((table) =>
|
|
table.id === tableId
|
|
? {
|
|
...table,
|
|
indexes: table.indexes.filter(
|
|
(i) => i.id !== indexId
|
|
),
|
|
}
|
|
: table
|
|
)
|
|
);
|
|
|
|
const dbTable = await db.getTable({
|
|
diagramId,
|
|
id: tableId,
|
|
});
|
|
|
|
if (!dbTable) {
|
|
return;
|
|
}
|
|
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
await Promise.all([
|
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
|
db.updateTable({
|
|
id: tableId,
|
|
attributes: {
|
|
...dbTable,
|
|
indexes: dbTable.indexes.filter(
|
|
(i) => i.id !== indexId
|
|
),
|
|
},
|
|
}),
|
|
]);
|
|
|
|
if (!!prevIndex && options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'removeIndex',
|
|
redoData: { indexId, tableId },
|
|
undoData: { tableId, index: prevIndex },
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[db, diagramId, setTables, addUndoAction, resetRedoStack, getIndex]
|
|
);
|
|
|
|
const createIndex: ChartDBContext['createIndex'] = useCallback(
|
|
async (tableId: string) => {
|
|
const table = getTable(tableId);
|
|
const index: DBIndex = {
|
|
id: generateId(),
|
|
name: `index_${(table?.indexes?.length ?? 0) + 1}`,
|
|
fieldIds: [],
|
|
unique: false,
|
|
createdAt: Date.now(),
|
|
};
|
|
|
|
await addIndex(tableId, index);
|
|
|
|
return index;
|
|
},
|
|
[addIndex, getTable]
|
|
);
|
|
|
|
const updateIndex: ChartDBContext['updateIndex'] = useCallback(
|
|
async (
|
|
tableId: string,
|
|
indexId: string,
|
|
index: Partial<DBIndex>,
|
|
options = { updateHistory: true }
|
|
) => {
|
|
const prevIndex = getIndex(tableId, indexId);
|
|
setTables((tables) =>
|
|
tables.map((table) =>
|
|
table.id === tableId
|
|
? {
|
|
...table,
|
|
indexes: table.indexes.map((i) =>
|
|
i.id === indexId ? { ...i, ...index } : i
|
|
),
|
|
}
|
|
: table
|
|
)
|
|
);
|
|
|
|
const dbTable = await db.getTable({ diagramId, id: tableId });
|
|
|
|
if (!dbTable) {
|
|
return;
|
|
}
|
|
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
await Promise.all([
|
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
|
db.updateTable({
|
|
id: tableId,
|
|
attributes: {
|
|
...dbTable,
|
|
indexes: dbTable.indexes.map((i) =>
|
|
i.id === indexId ? { ...i, ...index } : i
|
|
),
|
|
},
|
|
}),
|
|
]);
|
|
|
|
if (!!prevIndex && options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'updateIndex',
|
|
redoData: { tableId, indexId, index },
|
|
undoData: { tableId, indexId, index: prevIndex },
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[db, diagramId, setTables, addUndoAction, resetRedoStack, getIndex]
|
|
);
|
|
|
|
const addRelationship: ChartDBContext['addRelationship'] = useCallback(
|
|
async (
|
|
relationship: DBRelationship,
|
|
options = { updateHistory: true }
|
|
) => {
|
|
setRelationships((relationships) => [
|
|
...relationships,
|
|
relationship,
|
|
]);
|
|
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
await Promise.all([
|
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
|
db.addRelationship({ diagramId, relationship }),
|
|
]);
|
|
|
|
if (options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'addRelationship',
|
|
redoData: { relationship },
|
|
undoData: { relationshipId: relationship.id },
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[db, diagramId, setRelationships, addUndoAction, resetRedoStack]
|
|
);
|
|
|
|
const addRelationships: ChartDBContext['addRelationships'] = useCallback(
|
|
async (
|
|
relationships: DBRelationship[],
|
|
options = { updateHistory: true }
|
|
) => {
|
|
setRelationships((currentRelationships) => [
|
|
...currentRelationships,
|
|
...relationships,
|
|
]);
|
|
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
|
|
await Promise.all([
|
|
...relationships.map((relationship) =>
|
|
db.addRelationship({ diagramId, relationship })
|
|
),
|
|
db.updateDiagram({ id: diagramId, attributes: { updatedAt } }),
|
|
]);
|
|
|
|
if (options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'addRelationships',
|
|
redoData: { relationships },
|
|
undoData: {
|
|
relationshipIds: relationships.map((r) => r.id),
|
|
},
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[db, diagramId, setRelationships, addUndoAction, resetRedoStack]
|
|
);
|
|
|
|
const createRelationship: ChartDBContext['createRelationship'] =
|
|
useCallback(
|
|
async ({
|
|
sourceTableId,
|
|
targetTableId,
|
|
sourceFieldId,
|
|
targetFieldId,
|
|
}) => {
|
|
const sourceTableName = getTable(sourceTableId)?.name ?? '';
|
|
const targetTableName = getTable(targetTableId)?.name ?? '';
|
|
const relationship: DBRelationship = {
|
|
id: generateId(),
|
|
name: `${sourceTableName}_${targetTableName}_fk`,
|
|
sourceTableId,
|
|
targetTableId,
|
|
sourceFieldId,
|
|
targetFieldId,
|
|
type: 'one_to_one',
|
|
createdAt: Date.now(),
|
|
};
|
|
|
|
await addRelationship(relationship);
|
|
|
|
return relationship;
|
|
},
|
|
[addRelationship, getTable]
|
|
);
|
|
|
|
const getRelationship: ChartDBContext['getRelationship'] = useCallback(
|
|
(id: string) =>
|
|
relationships.find((relationship) => relationship.id === id) ??
|
|
null,
|
|
[relationships]
|
|
);
|
|
|
|
const removeRelationship: ChartDBContext['removeRelationship'] =
|
|
useCallback(
|
|
async (id: string, options = { updateHistory: true }) => {
|
|
const relationship = getRelationship(id);
|
|
setRelationships((relationships) =>
|
|
relationships.filter(
|
|
(relationship) => relationship.id !== id
|
|
)
|
|
);
|
|
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
await Promise.all([
|
|
db.updateDiagram({
|
|
id: diagramId,
|
|
attributes: { updatedAt },
|
|
}),
|
|
db.deleteRelationship({ diagramId, id }),
|
|
]);
|
|
|
|
if (!!relationship && options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'removeRelationship',
|
|
redoData: { relationshipId: id },
|
|
undoData: { relationship },
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[
|
|
db,
|
|
diagramId,
|
|
setRelationships,
|
|
addUndoAction,
|
|
resetRedoStack,
|
|
getRelationship,
|
|
]
|
|
);
|
|
|
|
const removeRelationships: ChartDBContext['removeRelationships'] =
|
|
useCallback(
|
|
async (ids: string[], options = { updateHistory: true }) => {
|
|
const prevRelationships = [
|
|
...relationships.filter((relationship) =>
|
|
ids.includes(relationship.id)
|
|
),
|
|
];
|
|
|
|
setRelationships((relationships) =>
|
|
relationships.filter(
|
|
(relationship) => !ids.includes(relationship.id)
|
|
)
|
|
);
|
|
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
await Promise.all([
|
|
...ids.map((id) =>
|
|
db.deleteRelationship({ diagramId, id })
|
|
),
|
|
db.updateDiagram({
|
|
id: diagramId,
|
|
attributes: { updatedAt },
|
|
}),
|
|
]);
|
|
|
|
if (prevRelationships.length > 0 && options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'removeRelationships',
|
|
redoData: { relationshipsIds: ids },
|
|
undoData: { relationships: prevRelationships },
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[
|
|
db,
|
|
diagramId,
|
|
setRelationships,
|
|
relationships,
|
|
addUndoAction,
|
|
resetRedoStack,
|
|
]
|
|
);
|
|
|
|
const updateRelationship: ChartDBContext['updateRelationship'] =
|
|
useCallback(
|
|
async (
|
|
id: string,
|
|
relationship: Partial<DBRelationship>,
|
|
options = { updateHistory: true }
|
|
) => {
|
|
const prevRelationship = getRelationship(id);
|
|
setRelationships((relationships) =>
|
|
relationships.map((r) =>
|
|
r.id === id ? { ...r, ...relationship } : r
|
|
)
|
|
);
|
|
|
|
const updatedAt = new Date();
|
|
setDiagramUpdatedAt(updatedAt);
|
|
await Promise.all([
|
|
db.updateDiagram({
|
|
id: diagramId,
|
|
attributes: { updatedAt },
|
|
}),
|
|
db.updateRelationship({ id, attributes: relationship }),
|
|
]);
|
|
|
|
if (!!prevRelationship && options.updateHistory) {
|
|
addUndoAction({
|
|
action: 'updateRelationship',
|
|
redoData: { relationshipId: id, relationship },
|
|
undoData: {
|
|
relationshipId: id,
|
|
relationship: prevRelationship,
|
|
},
|
|
});
|
|
resetRedoStack();
|
|
}
|
|
},
|
|
[
|
|
db,
|
|
setRelationships,
|
|
addUndoAction,
|
|
getRelationship,
|
|
resetRedoStack,
|
|
diagramId,
|
|
]
|
|
);
|
|
|
|
const loadDiagram: ChartDBContext['loadDiagram'] = useCallback(
|
|
async (diagramId: string) => {
|
|
const diagram = await db.getDiagram(diagramId, {
|
|
includeRelationships: true,
|
|
includeTables: true,
|
|
});
|
|
|
|
if (diagram) {
|
|
setDiagramId(diagram.id);
|
|
setDiagramName(diagram.name);
|
|
setDatabaseType(diagram.databaseType);
|
|
setTables(diagram?.tables ?? []);
|
|
setRelationships(diagram?.relationships ?? []);
|
|
setDiagramCreatedAt(diagram.createdAt);
|
|
setDiagramUpdatedAt(diagram.updatedAt);
|
|
}
|
|
|
|
return diagram;
|
|
},
|
|
[
|
|
db,
|
|
setDiagramId,
|
|
setDiagramName,
|
|
setDatabaseType,
|
|
setTables,
|
|
setRelationships,
|
|
setDiagramCreatedAt,
|
|
setDiagramUpdatedAt,
|
|
]
|
|
);
|
|
|
|
return (
|
|
<chartDBContext.Provider
|
|
value={{
|
|
diagramId,
|
|
diagramName,
|
|
databaseType,
|
|
tables,
|
|
relationships,
|
|
currentDiagram,
|
|
updateDiagramId,
|
|
updateDiagramName,
|
|
loadDiagram,
|
|
updateDatabaseType,
|
|
updateDiagramUpdatedAt,
|
|
createTable,
|
|
addTable,
|
|
getTable,
|
|
removeTable,
|
|
updateTable,
|
|
updateTablesState,
|
|
updateField,
|
|
removeField,
|
|
createField,
|
|
addField,
|
|
addIndex,
|
|
createIndex,
|
|
removeIndex,
|
|
getField,
|
|
getIndex,
|
|
updateIndex,
|
|
addRelationship,
|
|
addRelationships,
|
|
createRelationship,
|
|
getRelationship,
|
|
removeRelationship,
|
|
removeRelationships,
|
|
updateRelationship,
|
|
}}
|
|
>
|
|
{children}
|
|
</chartDBContext.Provider>
|
|
);
|
|
};
|