swarm-ui: rewire a notification flow by drag or keyboard
The flow editor can now change a flow's shape on the canvas, not only through the inspector's fields: - drag a box onto another to move it and its branch there; a drop the save would refuse shows the target dashed red and gives the reason; - a palette of node kinds adds a node below the picked box on click, or below any box it is dragged onto; it offers what the controller lists; - arrow keys walk the tree, M picks a box up and Enter drops it under the focused one, Alt+Up/Down reorder a node among its siblings, Delete removes a branch; focus follows the edit; - each box with siblings says when it runs, and the inspector can move it earlier or later, since the router runs children in edge order; - request_reviewer is a second leaf kind beside todo, with a reviewer field; - leaving with unsaved edits asks first, also for in-app links and the back button (a router hook runs navigation past a page's guard).
This commit is contained in:
parent
e01770c056
commit
68e289fff5
16 changed files with 1072 additions and 133 deletions
|
|
@ -19,14 +19,22 @@ import { TextField } from "../../ui/text-field/TextField.js";
|
|||
import { fetchJson, ProblemError } from "../../ui/polled/usePolled.js";
|
||||
import { FlowCanvas } from "./FlowCanvas.js";
|
||||
import {
|
||||
addChild,
|
||||
blankNode,
|
||||
isDirty,
|
||||
limitsOf,
|
||||
moveAmongSiblings,
|
||||
paletteKinds,
|
||||
removeNode,
|
||||
reparent,
|
||||
saveOutcome,
|
||||
saveRequest,
|
||||
parentOf,
|
||||
withFlow,
|
||||
withoutFlow,
|
||||
type Edit,
|
||||
} from "./FlowDraft.js";
|
||||
import { useLeaveGuard } from "./useLeaveGuard.js";
|
||||
import { NodeInspector } from "./NodeInspector.js";
|
||||
import { FALLBACK_SCHEMA, type Flow, type FlowsView } from "./FlowTypes.js";
|
||||
import "./NotificationsPage.css";
|
||||
|
|
@ -87,13 +95,9 @@ export function FlowEditorPage({
|
|||
draft !== null &&
|
||||
isDirty(view, withFlow(view.flows, draft), view.disabled_builtins);
|
||||
|
||||
// Leaving the page with edits not saved asks first.
|
||||
useEffect(() => {
|
||||
if (!dirty) return;
|
||||
const warn = (e: BeforeUnloadEvent) => e.preventDefault();
|
||||
window.addEventListener("beforeunload", warn);
|
||||
return () => window.removeEventListener("beforeunload", warn);
|
||||
}, [dirty]);
|
||||
// Leaving the page with edits not saved asks first, whether by closing the
|
||||
// tab, following a link or pressing back.
|
||||
const leaving = useLeaveGuard(dirty);
|
||||
|
||||
// Send `flows` as the document; true when the controller took it.
|
||||
async function put(flows: Flow[]): Promise<boolean> {
|
||||
|
|
@ -137,7 +141,11 @@ export function FlowEditorPage({
|
|||
async function onDelete() {
|
||||
if (!view) return;
|
||||
setConfirmDelete(false);
|
||||
if (await put(withoutFlow(view.flows, flowId))) navigate("/notifications");
|
||||
if (await put(withoutFlow(view.flows, flowId))) {
|
||||
// The flow is gone; there is nothing left to lose by leaving.
|
||||
leaving.release();
|
||||
navigate("/notifications");
|
||||
}
|
||||
}
|
||||
|
||||
const back = <Link href="/notifications">all flows</Link>;
|
||||
|
|
@ -237,7 +245,25 @@ export function FlowEditorPage({
|
|||
{dirty ? <p class="notify-hint">Not saved yet.</p> : null}
|
||||
|
||||
<div class="notify-editor-body">
|
||||
<FlowCanvas flow={draft} selected={selected} onSelect={setSelected} />
|
||||
<FlowCanvas
|
||||
flow={draft}
|
||||
selected={selected}
|
||||
limits={limits}
|
||||
palette={paletteKinds(schema)}
|
||||
onSelect={setSelected}
|
||||
onAdd={(kind, parentId) => {
|
||||
const added = blankNode(draft, kind);
|
||||
onEdit(addChild(draft, parentId, added, limits), added.id);
|
||||
}}
|
||||
onMove={(id, parentId) =>
|
||||
onEdit(reparent(draft, id, parentId, limits), id)
|
||||
}
|
||||
onReorder={(id, delta) =>
|
||||
onEdit(moveAmongSiblings(draft, id, delta), id)
|
||||
}
|
||||
onRemove={(id) => onEdit(removeNode(draft, id), parentOf(draft, id))}
|
||||
onRefused={setNotice}
|
||||
/>
|
||||
{node ? (
|
||||
<NodeInspector
|
||||
flow={draft}
|
||||
|
|
@ -258,6 +284,15 @@ export function FlowEditorPage({
|
|||
>
|
||||
<p>Delete the flow {draft.name}? The router stops running it.</p>
|
||||
</ConfirmDialog>
|
||||
<ConfirmDialog
|
||||
open={leaving.pending}
|
||||
label="leave without saving"
|
||||
confirmLabel="leave and drop edits"
|
||||
onCancel={leaving.stay}
|
||||
onConfirm={leaving.leave}
|
||||
>
|
||||
<p>This flow has edits that are not saved. Leaving drops them.</p>
|
||||
</ConfirmDialog>
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue