feat(ui): undo for deleted label elements, warn before leaving unsaved changes
The Delete key removed a label element for good; it now shows a toast with "Rückgängig" that puts it back in place. Closing or reloading the designer with unsaved changes asks first. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
b4c8d80c33
commit
d57162e709
1 file changed
+37
-1
+37
-1
@@ -184,9 +184,25 @@ export function LabelDesigner({ initial, onSubmit, submitLabel }: LabelDesignerP
|
||||
);
|
||||
}
|
||||
|
||||
// Deleting is one key press away, so it is always undoable.
|
||||
function deleteElement(id: string) {
|
||||
const index = elements.findIndex((element) => element.id === id);
|
||||
const removed = elements[index];
|
||||
if (!removed) return;
|
||||
setElements((current) => current.filter((element) => element.id !== id));
|
||||
setSelectedId((current) => (current === id ? null : current));
|
||||
toast("Element gelöscht", {
|
||||
action: {
|
||||
label: "Rückgängig",
|
||||
onClick: () =>
|
||||
setElements((current) => [
|
||||
...current.slice(0, index),
|
||||
removed,
|
||||
...current.slice(index),
|
||||
]),
|
||||
},
|
||||
duration: 8000,
|
||||
});
|
||||
}
|
||||
|
||||
function duplicateElement(id: string) {
|
||||
@@ -210,8 +226,28 @@ export function LabelDesigner({ initial, onSubmit, submitLabel }: LabelDesignerP
|
||||
}
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
return () => window.removeEventListener("keydown", onKeyDown);
|
||||
// deleteElement only reads `elements`, which is listed.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedId, elements]);
|
||||
|
||||
// Leaving the page (tab close, reload) with unsaved changes asks first.
|
||||
const currentSnapshot = JSON.stringify({ name, elements, widthMm, heightMm });
|
||||
const initialSnapshot = JSON.stringify({
|
||||
name: initial?.name ?? "",
|
||||
elements: initial?.elements ?? [],
|
||||
widthMm: initial?.widthMm ?? 90,
|
||||
heightMm: initial?.heightMm ?? 35,
|
||||
});
|
||||
const hasUnsavedChanges = currentSnapshot !== initialSnapshot;
|
||||
useEffect(() => {
|
||||
if (!hasUnsavedChanges || isSubmitting) return;
|
||||
function onBeforeUnload(event: BeforeUnloadEvent) {
|
||||
event.preventDefault();
|
||||
}
|
||||
window.addEventListener("beforeunload", onBeforeUnload);
|
||||
return () => window.removeEventListener("beforeunload", onBeforeUnload);
|
||||
}, [hasUnsavedChanges, isSubmitting]);
|
||||
|
||||
function handlePlaceholderInsert(key: PlaceholderKey) {
|
||||
addElement(newTextElement({ placeholderKey: key, text: "" }));
|
||||
}
|
||||
@@ -640,7 +676,7 @@ export function LabelDesigner({ initial, onSubmit, submitLabel }: LabelDesignerP
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Element im Etikett anklicken, um es zu bearbeiten. Entf/Backspace löscht das
|
||||
ausgewählte Element.
|
||||
ausgewählte Element – „Rückgängig“ im Hinweis holt es zurück.
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
Reference in new issue
Block a user