@heyramzi/ui 2.11.1 → 2.11.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/helpers.js +38 -38
- package/dist/agents/page-state.js +49 -50
- package/dist/agents/vendor/color.js +146 -127
- package/dist/agents/vendor/draw.js +617 -508
- package/dist/agents/vendor/engine.js +698 -599
- package/dist/agents/vendor/mesh.js +653 -683
- package/dist/agents/vendor/plastic.js +3913 -3112
- package/dist/agents/vendor/presets.js +163 -164
- package/dist/agents/vendor/shapes.js +21 -20
- package/dist/agents/vendor/ticker.js +41 -42
- package/dist/agents/vendor/types.js +1 -0
- package/dist/agents/vendor/wear.js +1421 -1448
- package/dist/ai/confirmation/confirmation-context.svelte.js +6 -6
- package/dist/ai/confirmation/index.js +3 -1
- package/dist/ai/index.js +33 -0
- package/dist/ai/prompt-input/index.js +3 -1
- package/dist/ai/reasoning/index.js +3 -1
- package/dist/ai/reasoning/reasoning-context.svelte.js +6 -6
- package/dist/ai/suggestion/index.js +3 -1
- package/dist/ai/typing-dots/index.js +3 -1
- package/dist/animations/text-swap.js +24 -24
- package/dist/brand/index.js +54 -48
- package/dist/canvas/card/resize.js +5 -3
- package/dist/canvas/edges/edge-path.js +255 -242
- package/dist/canvas/editor/context-menu.js +39 -36
- package/dist/canvas/editor/dispatch.svelte.js +154 -151
- package/dist/canvas/editor/editor-context.js +7 -2
- package/dist/canvas/editor/editor-mode.js +1 -0
- package/dist/canvas/editor/frame.js +73 -76
- package/dist/canvas/editor/hand-context.js +5 -2
- package/dist/canvas/editor/history-core.js +163 -191
- package/dist/canvas/editor/keymap.js +95 -169
- package/dist/canvas/editor/layout-actions.js +143 -144
- package/dist/canvas/editor/lock.js +52 -51
- package/dist/canvas/editor/rebase.js +217 -240
- package/dist/canvas/editor/style.js +132 -137
- package/dist/canvas/editor/text-field-context.js +7 -2
- package/dist/canvas/editor/z-order.js +65 -70
- package/dist/canvas/headless/alignment.js +22 -16
- package/dist/canvas/headless/break-apart-find.js +286 -279
- package/dist/canvas/headless/break-apart-pieces.js +62 -58
- package/dist/canvas/headless/break-apart-plan.js +299 -325
- package/dist/canvas/headless/break-apart-style.js +75 -70
- package/dist/canvas/headless/break-apart.js +188 -181
- package/dist/canvas/headless/canvas-cleanup.js +78 -58
- package/dist/canvas/headless/edge-anchors.js +115 -116
- package/dist/canvas/headless/edge-memo.js +24 -22
- package/dist/canvas/headless/edge-serialization.js +17 -27
- package/dist/canvas/headless/figure-geometry.js +232 -264
- package/dist/canvas/headless/fit-viewport.js +84 -67
- package/dist/canvas/headless/grid.js +88 -86
- package/dist/canvas/headless/icon-node.js +44 -36
- package/dist/canvas/headless/index.js +1 -0
- package/dist/canvas/headless/linkify.js +17 -19
- package/dist/canvas/headless/pen-marks.js +101 -95
- package/dist/canvas/headless/sketch.js +231 -209
- package/dist/canvas/headless/snap-guides.js +99 -83
- package/dist/canvas/headless/svg-figure.js +133 -117
- package/dist/canvas/headless/table-layout.js +146 -107
- package/dist/canvas/index.js +2 -0
- package/dist/canvas/lod/lod-mode.svelte.js +20 -18
- package/dist/canvas/lod/render-all.svelte.js +36 -39
- package/dist/canvas/ops/arrange.js +78 -71
- package/dist/canvas/ops/ops.js +260 -255
- package/dist/canvas/pen/colors.js +37 -35
- package/dist/canvas/pen/eraser.js +28 -27
- package/dist/canvas/pen/geometry.js +120 -130
- package/dist/canvas/pen/index.js +1 -0
- package/dist/canvas/pen/ops.js +11 -11
- package/dist/canvas/pen/pen-session.js +150 -159
- package/dist/canvas/pen/plugin.js +107 -104
- package/dist/canvas/pen/shapes.js +73 -68
- package/dist/canvas/pen/stroke-path.js +32 -30
- package/dist/canvas/pen/types.js +21 -21
- package/dist/canvas/pen/ui.js +10 -8
- package/dist/canvas/plugin.js +46 -46
- package/dist/canvas/screen/glyphs.js +12 -11
- package/dist/canvas/screen/labels.js +22 -22
- package/dist/canvas/screen/parts.js +98 -111
- package/dist/canvas/screen/plugin.js +47 -56
- package/dist/canvas/screen/types.js +30 -35
- package/dist/canvas/screen/ui.js +7 -7
- package/dist/canvas/test-utils.js +14 -14
- package/dist/canvas/types.js +1 -0
- package/dist/canvas/ui/forward-wheel.js +24 -25
- package/dist/canvas/ui/icons.js +64 -131
- package/dist/canvas/ui/place-panel.js +58 -49
- package/dist/canvas/ui/shortcut-tip.js +14 -12
- package/dist/canvas/ui/tools.js +300 -385
- package/dist/charts/dashboard/chart.js +4 -0
- package/dist/charts/dashboard/data-table.js +11 -0
- package/dist/charts/dashboard/windows.js +8 -3
- package/dist/charts/edge-fade.js +10 -5
- package/dist/charts/hover-band.js +16 -11
- package/dist/charts/index.js +0 -1
- package/dist/charts/motion.js +11 -7
- package/dist/charts/primitives/chart-utils.js +12 -12
- package/dist/components/copy/clipboard.js +32 -32
- package/dist/components/copy/copied.svelte.js +31 -23
- package/dist/components/data-table/data-table.js +39 -39
- package/dist/components/docs/index.js +4 -0
- package/dist/components/docs/scroll-spy.svelte.js +73 -68
- package/dist/components/list-page/filter-section.js +4 -1
- package/dist/components/list-page/index.js +2 -0
- package/dist/components/list-page/list-table.js +7 -2
- package/dist/components/list-page/queue-rail.js +14 -0
- package/dist/components/list-page/section-host.js +4 -2
- package/dist/components/list-page/step.js +4 -3
- package/dist/components/list-page/types.js +1 -0
- package/dist/components/picker/trigger.js +48 -30
- package/dist/components/picker/types.js +1 -0
- package/dist/components/segmented/collapse.js +38 -27
- package/dist/components/segmented/index.js +8 -0
- package/dist/components/segmented/segmented.js +67 -42
- package/dist/components/star-rating-input/index.js +3 -1
- package/dist/components/table-view/types.js +1 -0
- package/dist/components/tags-input/tags.js +81 -69
- package/dist/components/theme/index.js +4 -0
- package/dist/components/toolbar/index.js +4 -0
- package/dist/components/toolbar/toolbar-trigger.js +14 -1
- package/dist/editor/anchors.js +17 -13
- package/dist/editor/block-id.js +52 -51
- package/dist/editor/block-ops.js +108 -80
- package/dist/editor/doc-blocks.js +155 -166
- package/dist/editor/extensions.js +139 -180
- package/dist/editor/index.js +4 -0
- package/dist/editor/markdown-test-helpers.js +4 -2
- package/dist/editor/markdown.js +420 -408
- package/dist/editor/serializer.js +204 -200
- package/dist/editor/slash-items.js +89 -101
- package/dist/editor/youtube-url.js +25 -20
- package/dist/effects/animated-beam/path-calculator.svelte.js +43 -38
- package/dist/effects/animated-beam/types.js +1 -0
- package/dist/effects/border-beam/css.js +92 -78
- package/dist/effects/border-beam/presets.js +1705 -1720
- package/dist/effects/border-beam/register-properties.js +43 -33
- package/dist/effects/border-beam/types.js +1 -0
- package/dist/effects/image-comparison/compare.js +3 -2
- package/dist/effects/image-reveal/types.js +1 -0
- package/dist/effects/magnetic/magnetic.js +58 -52
- package/dist/effects/reveal/observe.js +50 -40
- package/dist/effects/reveal/types.js +1 -0
- package/dist/effects/shader-gradient/color.js +36 -29
- package/dist/effects/shader-gradient/renderer.js +384 -295
- package/dist/effects/shader-gradient/shaders.js +31 -0
- package/dist/effects/shader-gradient/types.js +1 -0
- package/dist/icons/apple-symbol-exports.js +99 -124
- package/dist/icons/sf-symbols.js +357 -461
- package/dist/icons/trades.js +43 -28
- package/dist/inbox/index.js +1 -0
- package/dist/inbox/logic.js +126 -122
- package/dist/inbox/types.js +43 -43
- package/dist/lib/arrive.js +19 -12
- package/dist/lib/icon-types.js +1 -0
- package/dist/lib/initials.js +16 -10
- package/dist/lib/layout.js +5 -0
- package/dist/lib/panel.js +13 -0
- package/dist/lib/result.js +22 -22
- package/dist/lib/sliding-pill.js +59 -44
- package/dist/lib/theme-transition.js +43 -43
- package/dist/lib/tone.js +31 -24
- package/dist/lib/utils.js +3 -1
- package/dist/onboarding/define-tours.js +24 -23
- package/dist/onboarding/onboarding.svelte.js +16 -12
- package/dist/onboarding/pick.js +10 -6
- package/dist/onboarding/stores.js +40 -41
- package/dist/onboarding/wait.js +24 -23
- package/dist/panel/side-panel-types.js +1 -0
- package/dist/record/demo-data.js +459 -486
- package/dist/record/fields.js +129 -131
- package/dist/record/format.js +70 -82
- package/dist/record/gutter.js +1 -0
- package/dist/record/kinds.js +95 -105
- package/dist/record/logic.js +238 -219
- package/dist/record/types.js +49 -48
- package/dist/shadcn/accordion/index.js +3 -1
- package/dist/shadcn/alert/index.js +3 -1
- package/dist/shadcn/alert-dialog/index.js +3 -1
- package/dist/shadcn/attachment/index.js +3 -1
- package/dist/shadcn/avatar/index.js +3 -1
- package/dist/shadcn/breadcrumb/index.js +3 -1
- package/dist/shadcn/bubble/index.js +3 -1
- package/dist/shadcn/button/index.js +3 -1
- package/dist/shadcn/calendar/index.js +3 -1
- package/dist/shadcn/card/index.js +3 -1
- package/dist/shadcn/checkbox/index.js +3 -1
- package/dist/shadcn/code-block/index.js +3 -1
- package/dist/shadcn/collapsible/index.js +3 -1
- package/dist/shadcn/command/index.js +4 -1
- package/dist/shadcn/context-menu/index.js +3 -1
- package/dist/shadcn/dialog/index.js +3 -1
- package/dist/shadcn/dropdown-menu/menu-item.js +40 -20
- package/dist/shadcn/empty/index.js +3 -1
- package/dist/shadcn/field/index.js +3 -1
- package/dist/shadcn/hooks/highlighter.js +77 -75
- package/dist/shadcn/hooks/is-mobile.svelte.js +3 -3
- package/dist/shadcn/hooks/use-highlighted-code.svelte.js +62 -62
- package/dist/shadcn/input/index.js +3 -1
- package/dist/shadcn/kbd/index.js +3 -1
- package/dist/shadcn/label/index.js +3 -1
- package/dist/shadcn/marker/index.js +3 -1
- package/dist/shadcn/message/index.js +3 -1
- package/dist/shadcn/message-scroller/index.js +3 -1
- package/dist/shadcn/popover/index.js +3 -1
- package/dist/shadcn/progress/index.js +3 -1
- package/dist/shadcn/progress/tone.js +8 -7
- package/dist/shadcn/radio-group/index.js +3 -1
- package/dist/shadcn/scroll-area/index.js +3 -1
- package/dist/shadcn/select/index.js +3 -1
- package/dist/shadcn/separator/index.js +3 -1
- package/dist/shadcn/sheet/index.js +3 -1
- package/dist/shadcn/sidebar/context.svelte.js +42 -28
- package/dist/shadcn/sidebar/index.js +3 -1
- package/dist/shadcn/skeleton/index.js +3 -1
- package/dist/shadcn/slider/index.js +3 -1
- package/dist/shadcn/switch/index.js +3 -1
- package/dist/shadcn/table/index.js +3 -1
- package/dist/shadcn/tabs/index.js +3 -1
- package/dist/shadcn/textarea/index.js +3 -1
- package/dist/shadcn/toggle/index.js +3 -1
- package/dist/shadcn/toggle-group/index.js +3 -1
- package/dist/shadcn/tooltip/context.js +7 -2
- package/dist/shadcn/tooltip/index.js +3 -1
- package/dist/shadcn/trace/index.js +3 -1
- package/dist/slides/bridges.js +65 -59
- package/dist/slides/deck-controller.svelte.js +210 -207
- package/dist/slides/export/capture.js +66 -71
- package/dist/slides/export/ffmpeg.js +76 -72
- package/dist/slides/export/index.js +3 -0
- package/dist/slides/export/launch.js +9 -6
- package/dist/slides/export/pdf.js +60 -54
- package/dist/slides/export/types.js +1 -0
- package/dist/slides/labels.js +24 -24
- package/dist/slides/layout.js +39 -41
- package/dist/slides/model.js +9 -7
- package/dist/slides/navigation.js +78 -73
- package/dist/slides/types.js +2 -2
- package/dist/status/define-statuses.js +18 -14
- package/dist/status/status-types.js +1 -0
- package/dist/test-setup.js +24 -20
- package/dist/thread/logic.js +73 -77
- package/dist/thread/types.js +21 -21
- package/dist/work/board-order.js +94 -90
- package/dist/work/dates.js +16 -9
- package/dist/work/detail/detail-types.js +18 -18
- package/dist/work/docs/edited.js +32 -29
- package/dist/work/docs/index.js +4 -0
- package/dist/work/docs/labels.js +34 -34
- package/dist/work/docs/menu.js +113 -106
- package/dist/work/docs/outline.js +39 -29
- package/dist/work/gantt/logic.js +211 -235
- package/dist/work/inbox/inbox-logic.js +71 -75
- package/dist/work/inbox/inbox-statuses.js +75 -72
- package/dist/work/inbox/inbox-types.js +1 -0
- package/dist/work/kanban-drag.js +27 -22
- package/dist/work/kanban-types.js +1 -0
- package/dist/work/presets/sales.js +36 -32
- package/dist/work/reorder.js +9 -8
- package/dist/work/task-types.js +1 -0
- package/dist/work/time/duration.js +51 -46
- package/dist/work/time/time-types.js +23 -23
- package/dist/work/use-kanban-dnd.svelte.js +126 -125
- package/package.json +3 -1
- package/src/components/list-page/master-detail.svelte +3 -1
- package/src/components/list-page/rail-row.svelte +4 -4
- package/src/editor/markdown.ts +30 -35
|
@@ -1,65 +1,66 @@
|
|
|
1
1
|
export function isLocked(node) {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
const data = node?.data;
|
|
3
|
+
return data?.locked === true;
|
|
4
4
|
}
|
|
5
|
+
/** What xyflow needs to hold a locked card still. */
|
|
5
6
|
export const LOCK_FLAGS = {
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
draggable: false,
|
|
8
|
+
deletable: false
|
|
8
9
|
};
|
|
10
|
+
/** The card with its flow flags matching `data.locked`. A synced copy keeps its own flags. */
|
|
9
11
|
export function withLockFlags(node) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
if (node.data?.syncedCopy) return node;
|
|
13
|
+
if (isLocked(node)) return {
|
|
14
|
+
...node,
|
|
15
|
+
...LOCK_FLAGS
|
|
16
|
+
};
|
|
17
|
+
if (node.draggable !== false && node.deletable !== false) return node;
|
|
18
|
+
const { draggable: _drag, deletable: _delete, ...rest } = node;
|
|
19
|
+
return rest;
|
|
18
20
|
}
|
|
21
|
+
/** `locked` alone is the one data change a locked card takes: it is how it comes unlocked. */
|
|
19
22
|
function onlyUnlocks(op) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
if (op.kind !== "patchNode") return false;
|
|
24
|
+
const patch = op;
|
|
25
|
+
return patch.data !== undefined && patch.style === undefined && patch.width === undefined && patch.height === undefined && Object.keys(patch.data).every((key) => key === "locked");
|
|
23
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The one gate every dispatched edit passes: ops that don't reach a locked card. An op on an
|
|
29
|
+
* arrow, or a card not on the board, passes.
|
|
30
|
+
*/
|
|
24
31
|
export function refuseLockedOps(nodes, ops) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return typeof nodeId !== "string" || !locked.has(nodeId) || onlyUnlocks(op);
|
|
32
|
-
});
|
|
32
|
+
const locked = new Set(nodes.filter(isLocked).map((node) => node.id));
|
|
33
|
+
if (locked.size === 0) return [...ops];
|
|
34
|
+
return ops.filter((op) => {
|
|
35
|
+
const nodeId = op.nodeId;
|
|
36
|
+
return typeof nodeId !== "string" || !locked.has(nodeId) || onlyUnlocks(op);
|
|
37
|
+
});
|
|
33
38
|
}
|
|
39
|
+
/** Of these ids, the ones a delete may take: not locked, and not a group holding something locked. */
|
|
34
40
|
export function deletableIds(nodes, ids) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
const byId = new Map(nodes.map((node)=>[
|
|
45
|
-
node.id,
|
|
46
|
-
node
|
|
47
|
-
]));
|
|
48
|
-
return ids.filter((id)=>!isLocked(byId.get(id)) && !holdsLocked.has(id));
|
|
41
|
+
const parentOf = new Map(nodes.map((node) => [node.id, node.parentId]));
|
|
42
|
+
const holdsLocked = new Set();
|
|
43
|
+
for (const node of nodes) {
|
|
44
|
+
if (!isLocked(node)) continue;
|
|
45
|
+
for (let up = node.parentId; up; up = parentOf.get(up)) holdsLocked.add(up);
|
|
46
|
+
}
|
|
47
|
+
const byId = new Map(nodes.map((node) => [node.id, node]));
|
|
48
|
+
return ids.filter((id) => !isLocked(byId.get(id)) && !holdsLocked.has(id));
|
|
49
49
|
}
|
|
50
|
+
/** The board with the picked cards locked, or all unlocked when every one of them already was locked. */
|
|
50
51
|
export function toggleLock(nodes, picked) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
52
|
+
const targets = nodes.filter((node) => picked.has(node.id) && !node.data.syncedCopy);
|
|
53
|
+
if (targets.length === 0) return null;
|
|
54
|
+
const locked = !targets.every(isLocked);
|
|
55
|
+
const ids = new Set(targets.map((node) => node.id));
|
|
56
|
+
return {
|
|
57
|
+
locked,
|
|
58
|
+
nodes: nodes.map((node) => ids.has(node.id) ? withLockFlags({
|
|
59
|
+
...node,
|
|
60
|
+
data: {
|
|
61
|
+
...node.data,
|
|
62
|
+
locked
|
|
63
|
+
}
|
|
64
|
+
}) : node)
|
|
65
|
+
};
|
|
65
66
|
}
|
|
@@ -1,279 +1,256 @@
|
|
|
1
|
+
// Conflict rebase for the autosave PUT. On a 409, local changes since the last
|
|
2
|
+
// save replay as ops on the server document, instead of overwriting the other writer.
|
|
1
3
|
import { tryCatch } from "../../lib/result.js";
|
|
2
4
|
import { applyOps } from "../ops/ops.js";
|
|
3
5
|
import { sortParentsFirst } from "../headless/canvas-cleanup.js";
|
|
4
6
|
const EDGE_PATCH_FIELDS = [
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
"source",
|
|
8
|
+
"target",
|
|
9
|
+
"label",
|
|
10
|
+
"animated",
|
|
11
|
+
"type",
|
|
12
|
+
"style",
|
|
13
|
+
"sourceHandle",
|
|
14
|
+
"targetHandle"
|
|
13
15
|
];
|
|
14
16
|
function json(value) {
|
|
15
|
-
|
|
17
|
+
return JSON.stringify(value ?? null);
|
|
16
18
|
}
|
|
17
19
|
function objectStyle(node) {
|
|
18
|
-
|
|
20
|
+
return node.style && typeof node.style === "object" ? node.style : undefined;
|
|
19
21
|
}
|
|
20
22
|
function numberOrUndefined(value) {
|
|
21
|
-
|
|
23
|
+
return typeof value === "number" ? value : undefined;
|
|
22
24
|
}
|
|
23
25
|
function addNodeOp(node) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
...height !== undefined ? {
|
|
44
|
-
height
|
|
45
|
-
} : {},
|
|
46
|
-
...node.parentId ? {
|
|
47
|
-
parentId: node.parentId
|
|
48
|
-
} : {}
|
|
49
|
-
}
|
|
50
|
-
};
|
|
26
|
+
const style = objectStyle(node);
|
|
27
|
+
const width = numberOrUndefined(node.width);
|
|
28
|
+
const height = numberOrUndefined(node.height);
|
|
29
|
+
return {
|
|
30
|
+
kind: "addNode",
|
|
31
|
+
node: {
|
|
32
|
+
id: node.id,
|
|
33
|
+
type: String(node.type ?? "node"),
|
|
34
|
+
position: node.position ?? {
|
|
35
|
+
x: 0,
|
|
36
|
+
y: 0
|
|
37
|
+
},
|
|
38
|
+
data: node.data ?? {},
|
|
39
|
+
...style ? { style } : {},
|
|
40
|
+
...width !== undefined ? { width } : {},
|
|
41
|
+
...height !== undefined ? { height } : {},
|
|
42
|
+
...node.parentId ? { parentId: node.parentId } : {}
|
|
43
|
+
}
|
|
44
|
+
};
|
|
51
45
|
}
|
|
52
46
|
function sizePatchOf(before, node) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
47
|
+
const patch = {};
|
|
48
|
+
const style = objectStyle(node);
|
|
49
|
+
if (style && json(before.style) !== json(node.style)) patch.style = style;
|
|
50
|
+
const width = numberOrUndefined(node.width);
|
|
51
|
+
if (width !== undefined && before.width !== node.width) patch.width = width;
|
|
52
|
+
const height = numberOrUndefined(node.height);
|
|
53
|
+
if (height !== undefined && before.height !== node.height) patch.height = height;
|
|
54
|
+
return patch;
|
|
61
55
|
}
|
|
56
|
+
/** Only the top-level keys local changed, so a key the server edited alone keeps its value. */
|
|
62
57
|
function changedData(before, data) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
if (!data) return undefined;
|
|
59
|
+
const changed = Object.entries(data).filter(([key, value]) => json(before?.[key]) !== json(value));
|
|
60
|
+
return changed.length > 0 ? Object.fromEntries(changed) : undefined;
|
|
66
61
|
}
|
|
67
62
|
function changedNodeOps(before, node) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
63
|
+
const ops = [];
|
|
64
|
+
if (node.position && json(before.position) !== json(node.position)) {
|
|
65
|
+
ops.push({
|
|
66
|
+
kind: "moveNode",
|
|
67
|
+
nodeId: node.id,
|
|
68
|
+
position: node.position
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
const parentId = node.parentId ?? null;
|
|
72
|
+
if ((before.parentId ?? null) !== parentId) {
|
|
73
|
+
ops.push({
|
|
74
|
+
kind: "setParent",
|
|
75
|
+
nodeId: node.id,
|
|
76
|
+
parentId
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const data = changedData(before.data, node.data);
|
|
80
|
+
if (data) ops.push({
|
|
81
|
+
kind: "patchNode",
|
|
82
|
+
nodeId: node.id,
|
|
83
|
+
data
|
|
84
|
+
});
|
|
85
|
+
const sizePatch = sizePatchOf(before, node);
|
|
86
|
+
if (Object.keys(sizePatch).length > 0) {
|
|
87
|
+
ops.push({
|
|
88
|
+
kind: "patchNode",
|
|
89
|
+
nodeId: node.id,
|
|
90
|
+
...sizePatch
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return ops;
|
|
99
94
|
}
|
|
100
95
|
function nodeOps(base, current) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
];
|
|
96
|
+
const baseNodes = new Map(base.nodes.map((n) => [n.id, n]));
|
|
97
|
+
const currentIds = new Set(current.nodes.map((n) => n.id));
|
|
98
|
+
const removed = base.nodes.filter((n) => !currentIds.has(n.id)).map((n) => ({
|
|
99
|
+
kind: "removeNode",
|
|
100
|
+
nodeId: n.id
|
|
101
|
+
}));
|
|
102
|
+
// WHY parents first: an added child references its added parent in the same
|
|
103
|
+
// batch, and applyOps validates parent existence at each step.
|
|
104
|
+
const added = sortParentsFirst(current.nodes.filter((n) => !baseNodes.has(n.id))).map(addNodeOp);
|
|
105
|
+
const changed = current.nodes.flatMap((node) => {
|
|
106
|
+
const before = baseNodes.get(node.id);
|
|
107
|
+
return before ? changedNodeOps(before, node) : [];
|
|
108
|
+
});
|
|
109
|
+
return [
|
|
110
|
+
...removed,
|
|
111
|
+
...added,
|
|
112
|
+
...changed
|
|
113
|
+
];
|
|
120
114
|
}
|
|
121
115
|
function addEdgeOp(edge) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
animated: edge.animated
|
|
143
|
-
} : {},
|
|
144
|
-
...record("style"),
|
|
145
|
-
...record("data")
|
|
146
|
-
}
|
|
147
|
-
};
|
|
116
|
+
const optional = (key) => typeof edge[key] === "string" ? { [key]: edge[key] } : {};
|
|
117
|
+
const record = (key) => {
|
|
118
|
+
const value = edge[key];
|
|
119
|
+
return value && typeof value === "object" && !Array.isArray(value) ? { [key]: value } : {};
|
|
120
|
+
};
|
|
121
|
+
return {
|
|
122
|
+
kind: "addEdge",
|
|
123
|
+
edge: {
|
|
124
|
+
id: edge.id,
|
|
125
|
+
source: String(edge.source),
|
|
126
|
+
target: String(edge.target),
|
|
127
|
+
...optional("sourceHandle"),
|
|
128
|
+
...optional("targetHandle"),
|
|
129
|
+
...optional("type"),
|
|
130
|
+
...optional("label"),
|
|
131
|
+
...typeof edge.animated === "boolean" ? { animated: edge.animated } : {},
|
|
132
|
+
...record("style"),
|
|
133
|
+
...record("data")
|
|
134
|
+
}
|
|
135
|
+
};
|
|
148
136
|
}
|
|
149
137
|
function patchEdgeOps(before, edge) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
}
|
|
163
|
-
];
|
|
138
|
+
const patch = {};
|
|
139
|
+
for (const field of EDGE_PATCH_FIELDS) {
|
|
140
|
+
if (edge[field] !== undefined && json(before[field]) !== json(edge[field])) {
|
|
141
|
+
patch[field] = edge[field];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (Object.keys(patch).length === 0) return [];
|
|
145
|
+
return [{
|
|
146
|
+
kind: "patchEdge",
|
|
147
|
+
edgeId: edge.id,
|
|
148
|
+
patch
|
|
149
|
+
}];
|
|
164
150
|
}
|
|
165
151
|
function edgeOps(base, current) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return before ? patchEdgeOps(before, edge) : [
|
|
178
|
-
addEdgeOp(edge)
|
|
179
|
-
];
|
|
180
|
-
});
|
|
181
|
-
return [
|
|
182
|
-
...removed,
|
|
183
|
-
...upserted
|
|
184
|
-
];
|
|
152
|
+
const baseEdges = new Map(base.edges.map((e) => [e.id, e]));
|
|
153
|
+
const currentIds = new Set(current.edges.map((e) => e.id));
|
|
154
|
+
const removed = base.edges.filter((e) => !currentIds.has(e.id)).map((e) => ({
|
|
155
|
+
kind: "removeEdge",
|
|
156
|
+
edgeId: e.id
|
|
157
|
+
}));
|
|
158
|
+
const upserted = current.edges.flatMap((edge) => {
|
|
159
|
+
const before = baseEdges.get(edge.id);
|
|
160
|
+
return before ? patchEdgeOps(before, edge) : [addEdgeOp(edge)];
|
|
161
|
+
});
|
|
162
|
+
return [...removed, ...upserted];
|
|
185
163
|
}
|
|
164
|
+
/**
|
|
165
|
+
* Diff two persisted documents into ops. Data patches carry only changed keys. A deleted
|
|
166
|
+
* data key, and a node's `type` or `zIndex`, have no op: `nodeFieldPatches` carries those.
|
|
167
|
+
*/
|
|
186
168
|
export function diffToOps(base, current) {
|
|
187
|
-
|
|
188
|
-
...nodeOps(base, current),
|
|
189
|
-
...edgeOps(base, current)
|
|
190
|
-
];
|
|
169
|
+
return [...nodeOps(base, current), ...edgeOps(base, current)];
|
|
191
170
|
}
|
|
192
|
-
const NODE_FIELDS = [
|
|
193
|
-
|
|
194
|
-
"zIndex"
|
|
195
|
-
];
|
|
171
|
+
const NODE_FIELDS = ["type", "zIndex"];
|
|
172
|
+
/** What changed on a node that `patchNode` can't say: `type`, `zIndex` and deleted data keys. */
|
|
196
173
|
function nodeFieldPatches(base, current) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return patches;
|
|
174
|
+
const baseNodes = new Map(base.nodes.map((n) => [n.id, n]));
|
|
175
|
+
const patches = [];
|
|
176
|
+
for (const node of current.nodes) {
|
|
177
|
+
const before = baseNodes.get(node.id);
|
|
178
|
+
if (!before) continue;
|
|
179
|
+
const set = {};
|
|
180
|
+
const unset = [];
|
|
181
|
+
for (const field of NODE_FIELDS) {
|
|
182
|
+
if (json(before[field]) === json(node[field])) continue;
|
|
183
|
+
if (node[field] === undefined) unset.push(field);
|
|
184
|
+
else set[field] = node[field];
|
|
185
|
+
}
|
|
186
|
+
const unsetData = Object.keys(before.data ?? {}).filter((key) => !(key in (node.data ?? {})));
|
|
187
|
+
if (unset.length + unsetData.length + Object.keys(set).length > 0) {
|
|
188
|
+
patches.push({
|
|
189
|
+
nodeId: node.id,
|
|
190
|
+
set,
|
|
191
|
+
unset,
|
|
192
|
+
unsetData
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return patches;
|
|
223
197
|
}
|
|
224
198
|
function withoutKeys(record, keys) {
|
|
225
|
-
|
|
199
|
+
return Object.fromEntries(Object.entries(record).filter(([key]) => !keys.includes(key)));
|
|
226
200
|
}
|
|
201
|
+
/** Null when a patched node is gone, the same answer `applyOps` gives for an op on a missing node. */
|
|
227
202
|
function applyNodeFieldPatches(nodes, patches) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
};
|
|
248
|
-
});
|
|
249
|
-
return seen.size === byId.size ? next : null;
|
|
203
|
+
const byId = new Map(patches.map((patch) => [patch.nodeId, patch]));
|
|
204
|
+
const seen = new Set();
|
|
205
|
+
const next = nodes.map((node) => {
|
|
206
|
+
const patch = typeof node.id === "string" ? byId.get(node.id) : undefined;
|
|
207
|
+
if (!patch) return node;
|
|
208
|
+
seen.add(patch.nodeId);
|
|
209
|
+
const patched = withoutKeys({
|
|
210
|
+
...node,
|
|
211
|
+
...patch.set
|
|
212
|
+
}, patch.unset);
|
|
213
|
+
if (patch.unsetData.length === 0 || typeof node.data !== "object" || node.data === null) {
|
|
214
|
+
return patched;
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
...patched,
|
|
218
|
+
data: withoutKeys(node.data, patch.unsetData)
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
return seen.size === byId.size ? next : null;
|
|
250
222
|
}
|
|
223
|
+
/** A local reparent can name a container that sits later in the server's order. */
|
|
251
224
|
function parentsFirst(nodes) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
225
|
+
const ids = new Set(nodes.map((node) => node.id));
|
|
226
|
+
const seen = new Set();
|
|
227
|
+
const misordered = nodes.some((node) => {
|
|
228
|
+
const early = ids.has(node.parentId) && !seen.has(node.parentId);
|
|
229
|
+
seen.add(node.id);
|
|
230
|
+
return early;
|
|
231
|
+
});
|
|
232
|
+
if (!misordered) return nodes;
|
|
233
|
+
const wrapped = nodes.map((node) => ({
|
|
234
|
+
id: String(node.id),
|
|
235
|
+
parentId: typeof node.parentId === "string" ? node.parentId : undefined,
|
|
236
|
+
node
|
|
237
|
+
}));
|
|
238
|
+
return sortParentsFirst(wrapped).map(({ node }) => node);
|
|
266
239
|
}
|
|
240
|
+
/**
|
|
241
|
+
* Replay local changes (lastSaved -> local) on the server document. Returns
|
|
242
|
+
* null when impossible, like a patched node deleted, so the save error shows.
|
|
243
|
+
*/
|
|
267
244
|
export function rebaseDiagram(serverDoc, lastSaved, local) {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
245
|
+
const ops = diffToOps(lastSaved, local);
|
|
246
|
+
const res = tryCatch(() => applyOps({
|
|
247
|
+
nodes: serverDoc.nodes,
|
|
248
|
+
edges: serverDoc.edges
|
|
249
|
+
}, ops));
|
|
250
|
+
if (!res.ok) return null;
|
|
251
|
+
const nodes = applyNodeFieldPatches(res.value.nodes, nodeFieldPatches(lastSaved, local));
|
|
252
|
+
return nodes ? {
|
|
253
|
+
...res.value,
|
|
254
|
+
nodes: parentsFirst(nodes)
|
|
255
|
+
} : null;
|
|
279
256
|
}
|