Restore editor history after reload
This commit is contained in:
@@ -36,9 +36,9 @@ import {
|
||||
import {
|
||||
buildCircuitSectionRenumberAssignments,
|
||||
} from "../utils/circuit-section-renumber-command";
|
||||
import { loadCircuitEditorSnapshot } from "../utils/circuit-editor-history";
|
||||
import type {
|
||||
CellKey,
|
||||
CellKind,
|
||||
ColumnDef,
|
||||
RowType,
|
||||
} from "../utils/circuit-grid-model";
|
||||
@@ -53,6 +53,7 @@ import {
|
||||
deleteCircuitDeviceRowCommand,
|
||||
getCircuitTree,
|
||||
getNextCircuitIdentifier,
|
||||
getProjectHistory,
|
||||
insertCircuitCommand,
|
||||
insertCircuitDeviceRowCommand,
|
||||
listProjectDevices,
|
||||
@@ -73,6 +74,7 @@ import type {
|
||||
CreateCircuitInputDto,
|
||||
ProjectCommandResultDto,
|
||||
ProjectDeviceDto,
|
||||
ProjectHistoryStateDto,
|
||||
} from "../types";
|
||||
import type {
|
||||
CircuitDeviceRowSnapshot,
|
||||
@@ -108,11 +110,6 @@ interface SelectionIntent {
|
||||
interface HistoryCommand {
|
||||
label: string;
|
||||
redo: () => Promise<SelectionIntent | null | void>;
|
||||
undo: () => Promise<SelectionIntent | null | void>;
|
||||
persistent?: {
|
||||
undoIntent: () => SelectionIntent | null;
|
||||
redoIntent: () => SelectionIntent | null;
|
||||
};
|
||||
}
|
||||
|
||||
type ProjectDeviceDropIntent =
|
||||
@@ -214,10 +211,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
const [draggingCircuitId, setDraggingCircuitId] = useState<string | null>(null);
|
||||
const [draggingCircuitIds, setDraggingCircuitIds] = useState<string[]>([]);
|
||||
const [circuitReorderIntent, setCircuitReorderIntent] = useState<CircuitReorderDropIntent | null>(null);
|
||||
// The visible stack is session-local during the cutover. Circuit/device field
|
||||
// commands already use the persistent project-wide server history.
|
||||
const [undoStack, setUndoStack] = useState<HistoryCommand[]>([]);
|
||||
const [redoStack, setRedoStack] = useState<HistoryCommand[]>([]);
|
||||
const [historyState, setHistoryState] = useState<ProjectHistoryStateDto | null>(null);
|
||||
const [historyBusy, setHistoryBusy] = useState(false);
|
||||
const [sortState, setSortState] = useState<{ key: CellKey; direction: SortDirection } | null>(null);
|
||||
const [columnFilters, setColumnFilters] = useState<Partial<Record<CellKey, string[]>>>({});
|
||||
@@ -255,8 +249,12 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
}
|
||||
setError(null);
|
||||
try {
|
||||
const tree = await getCircuitTree(projectId, circuitListId);
|
||||
projectRevisionRef.current = tree.currentRevision;
|
||||
const { tree, history } = await loadCircuitEditorSnapshot(
|
||||
() => getCircuitTree(projectId, circuitListId),
|
||||
() => getProjectHistory(projectId)
|
||||
);
|
||||
projectRevisionRef.current = history.currentRevision;
|
||||
setHistoryState(history);
|
||||
setData(tree);
|
||||
} catch (err) {
|
||||
setError(normalizeUiError(err));
|
||||
@@ -279,17 +277,16 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
void loadTree({ showLoading: true });
|
||||
}, [projectId, circuitListId]);
|
||||
|
||||
async function loadProjectDeviceList() {
|
||||
const list = await listProjectDevices(projectId);
|
||||
setProjectDevices(list);
|
||||
}
|
||||
|
||||
// Loads project-device palette used for sidebar drag/drop and quick inserts.
|
||||
useEffect(() => {
|
||||
async function loadProjectDeviceList() {
|
||||
try {
|
||||
const list = await listProjectDevices(projectId);
|
||||
setProjectDevices(list);
|
||||
} catch (err) {
|
||||
setError(normalizeUiError(err));
|
||||
}
|
||||
}
|
||||
void loadProjectDeviceList();
|
||||
void loadProjectDeviceList().catch((err) => {
|
||||
setError(normalizeUiError(err));
|
||||
});
|
||||
}, [projectId]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -792,6 +789,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
|
||||
function applyProjectCommandResult(result: ProjectCommandResultDto) {
|
||||
projectRevisionRef.current = result.history.currentRevision;
|
||||
setHistoryState(result.history);
|
||||
setData((current) =>
|
||||
current
|
||||
? {
|
||||
@@ -886,84 +884,67 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
applyProjectCommandResult(result);
|
||||
}
|
||||
|
||||
// Runs a normal command and records it in session-local history.
|
||||
// Runs a normal command. The server records it in project-wide history.
|
||||
async function runCommand(command: HistoryCommand) {
|
||||
try {
|
||||
setError(null);
|
||||
setIsSaving(true);
|
||||
const intent = await command.redo();
|
||||
await reloadWithIntent(intent ?? null);
|
||||
// Any new forward command invalidates redo history branch.
|
||||
setUndoStack((current) => [...current, command]);
|
||||
setRedoStack([]);
|
||||
} catch (err) {
|
||||
setError(normalizeUiError(err));
|
||||
const message = normalizeUiError(err);
|
||||
await loadTree({ showLoading: false });
|
||||
setError(message);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Replays command in undo/redo mode. Commands encapsulate their own inverse logic.
|
||||
async function applyHistory(command: HistoryCommand, mode: "undo" | "redo") {
|
||||
// Applies the next eligible project-wide history operation. Selection is only
|
||||
// a best-effort local hint; command eligibility and data changes stay server-owned.
|
||||
async function applyHistory(mode: "undo" | "redo") {
|
||||
try {
|
||||
setError(null);
|
||||
setHistoryBusy(true);
|
||||
setIsSaving(true);
|
||||
let intent: SelectionIntent | null | void;
|
||||
if (command.persistent) {
|
||||
const result =
|
||||
mode === "undo"
|
||||
? await undoProjectCommand(
|
||||
projectId,
|
||||
getExpectedProjectRevision()
|
||||
)
|
||||
: await redoProjectCommand(
|
||||
projectId,
|
||||
getExpectedProjectRevision()
|
||||
);
|
||||
applyProjectCommandResult(result);
|
||||
intent =
|
||||
mode === "undo"
|
||||
? command.persistent.undoIntent()
|
||||
: command.persistent.redoIntent();
|
||||
} else {
|
||||
intent =
|
||||
mode === "undo"
|
||||
? await command.undo()
|
||||
: await command.redo();
|
||||
}
|
||||
await reloadWithIntent(intent ?? null);
|
||||
if (mode === "undo") {
|
||||
setUndoStack((current) => current.slice(0, -1));
|
||||
setRedoStack((current) => [...current, command]);
|
||||
} else {
|
||||
setRedoStack((current) => current.slice(0, -1));
|
||||
setUndoStack((current) => [...current, command]);
|
||||
}
|
||||
const intent = selectedCell ? buildSelectionIntent(selectedCell) : null;
|
||||
const result =
|
||||
mode === "undo"
|
||||
? await undoProjectCommand(
|
||||
projectId,
|
||||
getExpectedProjectRevision()
|
||||
)
|
||||
: await redoProjectCommand(
|
||||
projectId,
|
||||
getExpectedProjectRevision()
|
||||
);
|
||||
applyProjectCommandResult(result);
|
||||
await Promise.all([
|
||||
reloadWithIntent(intent),
|
||||
loadProjectDeviceList(),
|
||||
]);
|
||||
} catch (err) {
|
||||
setError(normalizeUiError(err));
|
||||
const message = normalizeUiError(err);
|
||||
await loadTree({ showLoading: false });
|
||||
setError(message);
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
setHistoryBusy(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Undo command from session-local history stack.
|
||||
async function handleUndo() {
|
||||
if (historyBusy || isSaving || undoStack.length === 0) {
|
||||
if (historyBusy || isSaving || !historyState || historyState.undoDepth === 0) {
|
||||
return;
|
||||
}
|
||||
const command = undoStack[undoStack.length - 1];
|
||||
await applyHistory(command, "undo");
|
||||
await applyHistory("undo");
|
||||
}
|
||||
|
||||
// Redo command from session-local history stack.
|
||||
async function handleRedo() {
|
||||
if (historyBusy || isSaving || redoStack.length === 0) {
|
||||
if (historyBusy || isSaving || !historyState || historyState.redoDepth === 0) {
|
||||
return;
|
||||
}
|
||||
const command = redoStack[redoStack.length - 1];
|
||||
await applyHistory(command, "redo");
|
||||
await applyHistory("redo");
|
||||
}
|
||||
|
||||
// Persists currently sorted block order into section sortOrder values.
|
||||
@@ -1029,11 +1010,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
setOpenFilterColumn(null);
|
||||
return null;
|
||||
},
|
||||
undo: async () => null,
|
||||
persistent: {
|
||||
undoIntent: () => null,
|
||||
redoIntent: () => null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1426,46 +1402,13 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
|
||||
// Placeholder rows are not persisted entities; editing them materializes a real circuit first.
|
||||
if (row.rowType === "placeholder") {
|
||||
let createdCircuitId: string | null = null;
|
||||
const command: HistoryCommand = {
|
||||
label: "Aus freier Zeile erstellen",
|
||||
redo: async () => {
|
||||
const selection = await createFromPlaceholder(row.sectionId, editingCell.cellKey, editingCell.draft);
|
||||
targetSelection = selection;
|
||||
const createdRow = selection.rowKey.startsWith("circuitCompact:")
|
||||
? selection.rowKey.replace("circuitCompact:", "")
|
||||
: selection.rowKey.replace("reserveCircuit:", "");
|
||||
createdCircuitId = createdRow;
|
||||
return nextSelectionIntent();
|
||||
},
|
||||
undo: async () => {
|
||||
return {
|
||||
rowKey: `placeholder:${row.sectionId}`,
|
||||
cellKey: editingCell.cellKey,
|
||||
rowType: "placeholder",
|
||||
sectionId: row.sectionId,
|
||||
};
|
||||
},
|
||||
persistent: {
|
||||
undoIntent: () => ({
|
||||
rowKey: `placeholder:${row.sectionId}`,
|
||||
cellKey: editingCell.cellKey,
|
||||
rowType: "placeholder",
|
||||
sectionId: row.sectionId,
|
||||
}),
|
||||
redoIntent: () =>
|
||||
createdCircuitId
|
||||
? {
|
||||
rowKey: targetSelection.rowKey,
|
||||
cellKey: editingCell.cellKey,
|
||||
rowType: targetSelection.rowKey.startsWith("circuitCompact:")
|
||||
? "circuitCompact"
|
||||
: "reserveCircuit",
|
||||
sectionId: row.sectionId,
|
||||
circuitId: createdCircuitId,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
};
|
||||
setEditingCell(null);
|
||||
await runCommand(command);
|
||||
@@ -1480,19 +1423,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
await patchCircuit(row.circuit!.id, editingCell.cellKey, next);
|
||||
return nextSelectionIntent();
|
||||
},
|
||||
undo: async () =>
|
||||
buildSelectionIntent({
|
||||
rowKey: row.rowKey,
|
||||
cellKey: editingCell.cellKey,
|
||||
}),
|
||||
persistent: {
|
||||
undoIntent: () =>
|
||||
buildSelectionIntent({
|
||||
rowKey: row.rowKey,
|
||||
cellKey: editingCell.cellKey,
|
||||
}),
|
||||
redoIntent: nextSelectionIntent,
|
||||
},
|
||||
};
|
||||
setEditingCell(null);
|
||||
await runCommand(command);
|
||||
@@ -1507,19 +1437,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
await patchDeviceRow(row.device!.id, editingCell.cellKey, next);
|
||||
return nextSelectionIntent();
|
||||
},
|
||||
undo: async () =>
|
||||
buildSelectionIntent({
|
||||
rowKey: `device:${row.device!.id}`,
|
||||
cellKey: editingCell.cellKey,
|
||||
}),
|
||||
persistent: {
|
||||
undoIntent: () =>
|
||||
buildSelectionIntent({
|
||||
rowKey: `device:${row.device!.id}`,
|
||||
cellKey: editingCell.cellKey,
|
||||
}),
|
||||
redoIntent: nextSelectionIntent,
|
||||
},
|
||||
};
|
||||
setEditingCell(null);
|
||||
await runCommand(command);
|
||||
@@ -1528,7 +1445,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
|
||||
if (cell.kind === "deviceField" && row.rowType === "reserveCircuit" && row.circuit) {
|
||||
const next = editingCell.draft;
|
||||
let createdRowId: string | null = null;
|
||||
const command: HistoryCommand = {
|
||||
label: "Gerätezeile im Reservestromkreis erstellen",
|
||||
redo: async () => {
|
||||
@@ -1553,37 +1469,9 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
snapshot,
|
||||
"Gerätezeile im Reservestromkreis erstellen"
|
||||
);
|
||||
createdRowId = snapshot.id;
|
||||
targetSelection = { rowKey: `circuitCompact:${row.circuit!.id}`, cellKey: editingCell.cellKey };
|
||||
return nextSelectionIntent();
|
||||
},
|
||||
undo: async () => ({
|
||||
rowKey: `reserveCircuit:${row.circuit!.id}`,
|
||||
cellKey: editingCell.cellKey,
|
||||
rowType: "reserveCircuit",
|
||||
sectionId: row.sectionId,
|
||||
circuitId: row.circuit!.id,
|
||||
}),
|
||||
persistent: {
|
||||
undoIntent: () => ({
|
||||
rowKey: `reserveCircuit:${row.circuit!.id}`,
|
||||
cellKey: editingCell.cellKey,
|
||||
rowType: "reserveCircuit",
|
||||
sectionId: row.sectionId,
|
||||
circuitId: row.circuit!.id,
|
||||
}),
|
||||
redoIntent: () =>
|
||||
createdRowId
|
||||
? {
|
||||
rowKey: `circuitCompact:${row.circuit!.id}`,
|
||||
cellKey: editingCell.cellKey,
|
||||
rowType: "circuitCompact",
|
||||
sectionId: row.sectionId,
|
||||
circuitId: row.circuit!.id,
|
||||
deviceId: createdRowId,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
};
|
||||
setEditingCell(null);
|
||||
await runCommand(command);
|
||||
@@ -1605,7 +1493,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
if (!section) {
|
||||
return;
|
||||
}
|
||||
let createdCircuitId: string | null = null;
|
||||
await runCommand({
|
||||
label: "Stromkreis hinzufügen",
|
||||
redo: async () => {
|
||||
@@ -1619,7 +1506,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
isReserve: true,
|
||||
});
|
||||
await persistCircuitInsert(circuit, "Stromkreis hinzufügen");
|
||||
createdCircuitId = circuit.id;
|
||||
setActiveSectionId(sectionId);
|
||||
return {
|
||||
rowKey: `reserveCircuit:${circuit.id}`,
|
||||
@@ -1629,30 +1515,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
circuitId: circuit.id,
|
||||
};
|
||||
},
|
||||
undo: async () => ({
|
||||
rowKey: `placeholder:${sectionId}`,
|
||||
cellKey: "equipmentIdentifier",
|
||||
rowType: "placeholder",
|
||||
sectionId,
|
||||
}),
|
||||
persistent: {
|
||||
undoIntent: () => ({
|
||||
rowKey: `placeholder:${sectionId}`,
|
||||
cellKey: "equipmentIdentifier",
|
||||
rowType: "placeholder",
|
||||
sectionId,
|
||||
}),
|
||||
redoIntent: () =>
|
||||
createdCircuitId
|
||||
? {
|
||||
rowKey: `reserveCircuit:${createdCircuitId}`,
|
||||
cellKey: "equipmentIdentifier",
|
||||
rowType: "reserveCircuit",
|
||||
sectionId,
|
||||
circuitId: createdCircuitId,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1663,32 +1525,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
) {
|
||||
const originalDeviceCount = circuit.deviceRows.length;
|
||||
const section = data?.sections.find((entry) => entry.id === sectionId);
|
||||
let createdRowId: string | null = null;
|
||||
const getUndoIntent = (): SelectionIntent => {
|
||||
const rowKey =
|
||||
originalDeviceCount === 0
|
||||
? `reserveCircuit:${circuit.id}`
|
||||
: originalDeviceCount === 1
|
||||
? `circuitCompact:${circuit.id}`
|
||||
: afterDeviceRowId
|
||||
? `device:${afterDeviceRowId}`
|
||||
: `circuitSummary:${circuit.id}`;
|
||||
return {
|
||||
rowKey,
|
||||
cellKey: "displayName",
|
||||
rowType:
|
||||
originalDeviceCount === 0
|
||||
? "reserveCircuit"
|
||||
: originalDeviceCount === 1
|
||||
? "circuitCompact"
|
||||
: afterDeviceRowId
|
||||
? "deviceRow"
|
||||
: "circuitSummary",
|
||||
sectionId,
|
||||
circuitId: circuit.id,
|
||||
deviceId: afterDeviceRowId,
|
||||
};
|
||||
};
|
||||
await runCommand({
|
||||
label: "Manuelles Gerät hinzufügen",
|
||||
redo: async () => {
|
||||
@@ -1706,7 +1542,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
rowSnapshot,
|
||||
"Manuelles Gerät hinzufügen"
|
||||
);
|
||||
createdRowId = rowSnapshot.id;
|
||||
setActiveSectionId(sectionId);
|
||||
return {
|
||||
rowKey: originalDeviceCount === 0 ? `circuitCompact:${circuit.id}` : `device:${rowSnapshot.id}`,
|
||||
@@ -1717,27 +1552,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
deviceId: rowSnapshot.id,
|
||||
};
|
||||
},
|
||||
undo: async () => getUndoIntent(),
|
||||
persistent: {
|
||||
undoIntent: getUndoIntent,
|
||||
redoIntent: () =>
|
||||
createdRowId
|
||||
? {
|
||||
rowKey:
|
||||
originalDeviceCount === 0
|
||||
? `circuitCompact:${circuit.id}`
|
||||
: `device:${createdRowId}`,
|
||||
cellKey: "displayName",
|
||||
rowType:
|
||||
originalDeviceCount === 0
|
||||
? "circuitCompact"
|
||||
: "deviceRow",
|
||||
sectionId,
|
||||
circuitId: circuit.id,
|
||||
deviceId: createdRowId,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1816,14 +1630,10 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
setError(getProjectDeviceTargetError(device, targetSectionId));
|
||||
return;
|
||||
}
|
||||
let createdCircuitId: string | null = null;
|
||||
let createdRowId: string | null = null;
|
||||
await runCommand({
|
||||
label: "Projektgerät als neuen Stromkreis hinzufügen",
|
||||
redo: async () => {
|
||||
const created = await insertProjectDeviceAsNewCircuit(device, targetSectionId);
|
||||
createdCircuitId = created.circuitId;
|
||||
createdRowId = created.rowId;
|
||||
return {
|
||||
rowKey: `device:${created.rowId}`,
|
||||
cellKey: "displayName",
|
||||
@@ -1833,31 +1643,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
deviceId: created.rowId,
|
||||
};
|
||||
},
|
||||
undo: async () => ({
|
||||
rowKey: `placeholder:${targetSectionId}`,
|
||||
cellKey: "displayName",
|
||||
rowType: "placeholder",
|
||||
sectionId: targetSectionId,
|
||||
}),
|
||||
persistent: {
|
||||
undoIntent: () => ({
|
||||
rowKey: `placeholder:${targetSectionId}`,
|
||||
cellKey: "displayName",
|
||||
rowType: "placeholder",
|
||||
sectionId: targetSectionId,
|
||||
}),
|
||||
redoIntent: () =>
|
||||
createdCircuitId && createdRowId
|
||||
? {
|
||||
rowKey: `device:${createdRowId}`,
|
||||
cellKey: "displayName",
|
||||
rowType: "deviceRow",
|
||||
sectionId: targetSectionId,
|
||||
circuitId: createdCircuitId,
|
||||
deviceId: createdRowId,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1884,12 +1669,10 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
return;
|
||||
}
|
||||
|
||||
let createdRowId: string | null = null;
|
||||
await runCommand({
|
||||
label: "Projektgerät zum Stromkreis hinzufügen",
|
||||
redo: async () => {
|
||||
const created = await insertProjectDeviceToCircuit(device, circuitId);
|
||||
createdRowId = created.rowId;
|
||||
return {
|
||||
rowKey: `device:${created.rowId}`,
|
||||
cellKey: "displayName",
|
||||
@@ -1899,33 +1682,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
deviceId: created.rowId,
|
||||
};
|
||||
},
|
||||
undo: async () => ({
|
||||
rowKey: `circuitSummary:${circuitId}`,
|
||||
cellKey: "displayName",
|
||||
rowType: "circuitSummary",
|
||||
sectionId,
|
||||
circuitId,
|
||||
}),
|
||||
persistent: {
|
||||
undoIntent: () => ({
|
||||
rowKey: `circuitSummary:${circuitId}`,
|
||||
cellKey: "displayName",
|
||||
rowType: "circuitSummary",
|
||||
sectionId,
|
||||
circuitId,
|
||||
}),
|
||||
redoIntent: () =>
|
||||
createdRowId
|
||||
? {
|
||||
rowKey: `device:${createdRowId}`,
|
||||
cellKey: "displayName",
|
||||
rowType: "deviceRow",
|
||||
sectionId,
|
||||
circuitId,
|
||||
deviceId: createdRowId,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2190,14 +1946,10 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
// Project-device drop logic is isolated from row/circuit move logic because
|
||||
// it can create new rows/circuits instead of moving existing ones.
|
||||
if (intent.kind === "new-circuit") {
|
||||
let createdCircuitId: string | null = null;
|
||||
let createdRowId: string | null = null;
|
||||
await runCommand({
|
||||
label: "Projektgerät in neuen Stromkreis ziehen",
|
||||
redo: async () => {
|
||||
const created = await insertProjectDeviceAsNewCircuit(device, intent.sectionId);
|
||||
createdCircuitId = created.circuitId;
|
||||
createdRowId = created.rowId;
|
||||
return {
|
||||
rowKey: `device:${created.rowId}`,
|
||||
cellKey: "displayName",
|
||||
@@ -2207,30 +1959,13 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
deviceId: created.rowId,
|
||||
};
|
||||
},
|
||||
undo: async () => null,
|
||||
persistent: {
|
||||
undoIntent: () => null,
|
||||
redoIntent: () =>
|
||||
createdCircuitId && createdRowId
|
||||
? {
|
||||
rowKey: `device:${createdRowId}`,
|
||||
cellKey: "displayName",
|
||||
rowType: "deviceRow",
|
||||
sectionId: intent.sectionId,
|
||||
circuitId: createdCircuitId,
|
||||
deviceId: createdRowId,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
let createdRowId: string | null = null;
|
||||
await runCommand({
|
||||
label: "Projektgerät in Stromkreis ziehen",
|
||||
redo: async () => {
|
||||
const created = await insertProjectDeviceToCircuit(device, intent.circuitId);
|
||||
createdRowId = created.rowId;
|
||||
return {
|
||||
rowKey: `device:${created.rowId}`,
|
||||
cellKey: "displayName",
|
||||
@@ -2240,21 +1975,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
deviceId: created.rowId,
|
||||
};
|
||||
},
|
||||
undo: async () => null,
|
||||
persistent: {
|
||||
undoIntent: () => null,
|
||||
redoIntent: () =>
|
||||
createdRowId
|
||||
? {
|
||||
rowKey: `device:${createdRowId}`,
|
||||
cellKey: "displayName",
|
||||
rowType: "deviceRow",
|
||||
sectionId: intent.sectionId,
|
||||
circuitId: intent.circuitId,
|
||||
deviceId: createdRowId,
|
||||
}
|
||||
: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2335,17 +2055,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
pendingSelectedDeviceRowIdsAfterReload.current = rowIds;
|
||||
return null;
|
||||
},
|
||||
undo: async () => null,
|
||||
persistent: {
|
||||
undoIntent: () => {
|
||||
pendingSelectedDeviceRowIdsAfterReload.current = rowIds;
|
||||
return null;
|
||||
},
|
||||
redoIntent: () => {
|
||||
pendingSelectedDeviceRowIdsAfterReload.current = rowIds;
|
||||
return null;
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -2398,17 +2107,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
pendingSelectedDeviceRowIdsAfterReload.current = rowIds;
|
||||
return null;
|
||||
},
|
||||
undo: async () => null,
|
||||
persistent: {
|
||||
undoIntent: () => {
|
||||
pendingSelectedDeviceRowIdsAfterReload.current = rowIds;
|
||||
return null;
|
||||
},
|
||||
redoIntent: () => {
|
||||
pendingSelectedDeviceRowIdsAfterReload.current = rowIds;
|
||||
return null;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2477,19 +2175,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
pendingSelectedCircuitIdsAfterReload.current = sourceCircuitIds;
|
||||
return selectionIntent;
|
||||
},
|
||||
undo: async () => null,
|
||||
persistent: {
|
||||
undoIntent: () => {
|
||||
pendingSelectedCircuitIdsAfterReload.current =
|
||||
sourceCircuitIds;
|
||||
return selectionIntent;
|
||||
},
|
||||
redoIntent: () => {
|
||||
pendingSelectedCircuitIdsAfterReload.current =
|
||||
sourceCircuitIds;
|
||||
return selectionIntent;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2533,11 +2218,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
);
|
||||
return null;
|
||||
},
|
||||
undo: async () => null,
|
||||
persistent: {
|
||||
undoIntent: () => null,
|
||||
redoIntent: () => null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2563,11 +2243,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
await persistCircuitDelete(circuitId, "Stromkreis löschen");
|
||||
return null;
|
||||
},
|
||||
undo: async () => null,
|
||||
persistent: {
|
||||
undoIntent: () => null,
|
||||
redoIntent: () => null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2625,11 +2300,6 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
applyProjectCommandResult(result);
|
||||
return null;
|
||||
},
|
||||
undo: async () => null,
|
||||
persistent: {
|
||||
undoIntent: () => null,
|
||||
redoIntent: () => null,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2755,10 +2425,18 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
return (
|
||||
<div className="tree-editor-shell">
|
||||
<div className="editor-toolbar">
|
||||
<button type="button" onClick={() => void handleUndo()} disabled={undoStack.length === 0 || historyBusy || isSaving}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleUndo()}
|
||||
disabled={!historyState || historyState.undoDepth === 0 || historyBusy || isSaving}
|
||||
>
|
||||
Rückgängig
|
||||
</button>
|
||||
<button type="button" onClick={() => void handleRedo()} disabled={redoStack.length === 0 || historyBusy || isSaving}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleRedo()}
|
||||
disabled={!historyState || historyState.redoDepth === 0 || historyBusy || isSaving}
|
||||
>
|
||||
Wiederholen
|
||||
</button>
|
||||
<button
|
||||
@@ -2822,10 +2500,28 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
return (
|
||||
<div className="tree-editor-shell">
|
||||
<div className="editor-toolbar">
|
||||
<button type="button" onClick={() => void handleUndo()} disabled={undoStack.length === 0 || historyBusy || isSaving}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleUndo()}
|
||||
disabled={!historyState || historyState.undoDepth === 0 || historyBusy || isSaving}
|
||||
title={
|
||||
historyState?.undoDepth
|
||||
? `${historyState.undoDepth} Änderung(en) können rückgängig gemacht werden.`
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
Rückgängig
|
||||
</button>
|
||||
<button type="button" onClick={() => void handleRedo()} disabled={redoStack.length === 0 || historyBusy || isSaving}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void handleRedo()}
|
||||
disabled={!historyState || historyState.redoDepth === 0 || historyBusy || isSaving}
|
||||
title={
|
||||
historyState?.redoDepth
|
||||
? `${historyState.redoDepth} Änderung(en) können wiederholt werden.`
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
Wiederholen
|
||||
</button>
|
||||
{isSortedView ? (
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import type {
|
||||
CircuitTreeResponseDto,
|
||||
ProjectHistoryStateDto,
|
||||
} from "../types";
|
||||
|
||||
export interface CircuitEditorSnapshot {
|
||||
tree: CircuitTreeResponseDto;
|
||||
history: ProjectHistoryStateDto;
|
||||
}
|
||||
|
||||
export async function loadCircuitEditorSnapshot(
|
||||
readTree: () => Promise<CircuitTreeResponseDto>,
|
||||
readHistory: () => Promise<ProjectHistoryStateDto>,
|
||||
maxAttempts = 3
|
||||
): Promise<CircuitEditorSnapshot> {
|
||||
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
||||
const tree = await readTree();
|
||||
const history = await readHistory();
|
||||
if (tree.currentRevision === history.currentRevision) {
|
||||
return { tree, history };
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"Der Projektstand hat sich während des Ladens geändert. Bitte erneut versuchen."
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user