@heyramzi/ui 2.9.0 → 2.10.1
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 +52 -0
- package/dist/agents/index.js +3 -0
- package/dist/agents/page-state.js +65 -0
- package/dist/agents/types.js +1 -0
- package/dist/agents/vendor/color.js +149 -0
- package/dist/agents/vendor/draw.js +548 -0
- package/dist/agents/vendor/engine.js +632 -0
- package/dist/agents/vendor/mesh.js +727 -0
- package/dist/agents/vendor/plastic.js +3273 -0
- package/dist/agents/vendor/presets.js +171 -0
- package/dist/agents/vendor/shapes.js +24 -0
- package/dist/agents/vendor/ticker.js +56 -0
- package/dist/agents/vendor/types.js +0 -0
- package/dist/agents/vendor/wear.js +1525 -0
- package/dist/ai/ai-button/index.js +2 -0
- package/dist/ai/confirmation/confirmation-context.svelte.js +12 -0
- package/dist/ai/confirmation/index.js +9 -0
- package/dist/ai/generation-progress/index.js +2 -0
- package/dist/ai/index.js +11 -0
- package/dist/ai/loading-state/index.js +3 -0
- package/dist/ai/prompt-input/index.js +6 -0
- package/dist/ai/reasoning/index.js +4 -0
- package/dist/ai/reasoning/reasoning-context.svelte.js +12 -0
- package/dist/ai/response/index.js +3 -0
- package/dist/ai/suggestion/index.js +3 -0
- package/dist/ai/thinking-dots/index.js +3 -0
- package/dist/ai/typing-dots/index.js +2 -0
- package/dist/ai/voice-waveform/index.js +3 -0
- package/dist/animations/text-swap.js +30 -0
- package/dist/brand/index.js +65 -0
- package/dist/canvas/card/resize.js +10 -0
- package/dist/canvas/edges/edge-path.js +284 -0
- package/dist/canvas/editor/context-menu.js +42 -0
- package/dist/canvas/editor/dispatch.svelte.js +157 -0
- package/dist/canvas/editor/editor-context.js +8 -0
- package/dist/canvas/editor/editor-mode.js +0 -0
- package/dist/canvas/editor/frame.js +87 -0
- package/dist/canvas/editor/hand-context.js +8 -0
- package/dist/canvas/editor/history-core.js +221 -0
- package/dist/canvas/editor/keymap.js +193 -0
- package/dist/canvas/editor/layout-actions.js +170 -0
- package/dist/canvas/editor/lock.js +65 -0
- package/dist/canvas/editor/rebase.js +279 -0
- package/dist/canvas/editor/style.js +174 -0
- package/dist/canvas/editor/text-field-context.js +8 -0
- package/dist/canvas/editor/z-order.js +82 -0
- package/dist/canvas/headless/alignment.js +20 -0
- package/dist/canvas/headless/break-apart-find.js +324 -0
- package/dist/canvas/headless/break-apart-pieces.js +74 -0
- package/dist/canvas/headless/break-apart-plan.js +378 -0
- package/dist/canvas/headless/break-apart-style.js +93 -0
- package/dist/canvas/headless/break-apart.js +206 -0
- package/dist/canvas/headless/canvas-cleanup.js +70 -0
- package/dist/canvas/headless/edge-anchors.js +134 -0
- package/dist/canvas/headless/edge-memo.js +28 -0
- package/dist/canvas/headless/edge-serialization.js +31 -0
- package/dist/canvas/headless/figure-geometry.js +331 -0
- package/dist/canvas/headless/fit-viewport.js +82 -0
- package/dist/canvas/headless/grid.js +96 -0
- package/dist/canvas/headless/icon-node.js +45 -0
- package/dist/canvas/headless/index.js +1 -0
- package/dist/canvas/headless/linkify.js +25 -0
- package/dist/canvas/headless/pen-marks.js +115 -0
- package/dist/canvas/headless/sketch.js +250 -0
- package/dist/canvas/headless/snap-guides.js +100 -0
- package/dist/canvas/headless/svg-figure.js +137 -0
- package/dist/canvas/headless/table-layout.js +161 -0
- package/dist/canvas/index.js +25 -0
- package/dist/canvas/lod/lod-mode.svelte.js +22 -0
- package/dist/canvas/lod/render-all.svelte.js +45 -0
- package/dist/canvas/ops/arrange.js +83 -0
- package/dist/canvas/ops/ops.js +318 -0
- package/dist/canvas/pen/colors.js +41 -0
- package/dist/canvas/pen/eraser.js +35 -0
- package/dist/canvas/pen/geometry.js +143 -0
- package/dist/canvas/pen/index.js +9 -0
- package/dist/canvas/pen/ops.js +15 -0
- package/dist/canvas/pen/pen-session.js +171 -0
- package/dist/canvas/pen/plugin.js +129 -0
- package/dist/canvas/pen/shapes.js +80 -0
- package/dist/canvas/pen/stroke-path.js +37 -0
- package/dist/canvas/pen/types.js +30 -0
- package/dist/canvas/pen/ui.js +20 -0
- package/dist/canvas/plugin.js +51 -0
- package/dist/canvas/screen/glyphs.js +24 -0
- package/dist/canvas/screen/labels.js +24 -0
- package/dist/canvas/screen/parts.js +140 -0
- package/dist/canvas/screen/plugin.js +71 -0
- package/dist/canvas/screen/types.js +44 -0
- package/dist/canvas/screen/ui.js +13 -0
- package/dist/canvas/test-utils.js +18 -0
- package/dist/canvas/types.js +0 -0
- package/dist/canvas/ui/forward-wheel.js +27 -0
- package/dist/canvas/ui/icons.js +133 -0
- package/dist/canvas/ui/place-panel.js +57 -0
- package/dist/canvas/ui/shortcut-tip.js +18 -0
- package/dist/canvas/ui/tools.js +411 -0
- package/dist/charts/dashboard/chart.js +1 -0
- package/dist/charts/dashboard/data-table.js +7 -0
- package/dist/charts/dashboard/index.js +13 -0
- package/dist/charts/dashboard/windows.js +5 -0
- package/dist/charts/edge-fade.js +7 -0
- package/dist/charts/hover-band.js +13 -0
- package/dist/charts/index.js +19 -0
- package/dist/charts/motion.js +15 -0
- package/dist/charts/primitives/chart-utils.js +22 -0
- package/dist/charts/primitives/index.js +4 -0
- package/dist/components/copy/clipboard.js +38 -0
- package/dist/components/copy/copied.svelte.js +26 -0
- package/dist/components/copy/index.js +3 -0
- package/dist/components/data-table/data-table.js +55 -0
- package/dist/components/data-table/index.js +7 -0
- package/dist/components/docs/index.js +3 -0
- package/dist/components/docs/scroll-spy.svelte.js +71 -0
- package/dist/components/field-row/index.js +1 -0
- package/dist/components/hold-to-confirm/index.js +1 -0
- package/dist/components/icon/index.js +1 -0
- package/dist/components/language-flag/index.js +1 -0
- package/dist/components/list-page/filter-section.js +4 -0
- package/dist/components/list-page/index.js +25 -0
- package/dist/components/list-page/list-table.js +5 -0
- package/dist/components/list-page/queue-rail.js +3 -0
- package/dist/components/list-page/section-host.js +8 -0
- package/dist/components/list-page/step.js +5 -0
- package/dist/components/list-page/types.js +0 -0
- package/dist/components/locale-switch/index.js +1 -0
- package/dist/components/mark/index.js +1 -0
- package/dist/components/open-in/index.js +1 -0
- package/dist/components/picker/index.js +3 -0
- package/dist/components/picker/trigger.js +33 -0
- package/dist/components/picker/types.js +0 -0
- package/dist/components/section-card/index.js +1 -0
- package/dist/components/section-header/index.js +2 -0
- package/dist/components/segmented/collapse.js +32 -0
- package/dist/components/segmented/index.js +8 -0
- package/dist/components/segmented/segmented.js +50 -0
- package/dist/components/star-rating-input/index.js +2 -0
- package/dist/components/svg/index.js +1 -0
- package/dist/components/table-view/index.js +1 -0
- package/dist/components/table-view/types.js +0 -0
- package/dist/components/tags-input/index.js +2 -0
- package/dist/components/tags-input/tags.js +90 -0
- package/dist/components/theme/index.js +3 -0
- package/dist/components/toolbar/index.js +3 -0
- package/dist/components/toolbar/toolbar-trigger.js +4 -0
- package/dist/editor/anchors.js +17 -0
- package/dist/editor/block-id.js +62 -0
- package/dist/editor/block-ops.js +105 -0
- package/dist/editor/doc-blocks.js +181 -0
- package/dist/editor/extensions.js +200 -0
- package/dist/editor/index.js +4 -0
- package/dist/editor/markdown-test-helpers.js +6 -0
- package/dist/editor/markdown.js +486 -0
- package/dist/editor/serializer.js +268 -0
- package/dist/editor/slash-items.js +104 -0
- package/dist/editor/youtube-url.js +27 -0
- package/dist/effects/animated-beam/index.js +1 -0
- package/dist/effects/animated-beam/path-calculator.svelte.js +42 -0
- package/dist/effects/animated-beam/types.js +0 -0
- package/dist/effects/border-beam/css.js +420 -0
- package/dist/effects/border-beam/presets.js +1792 -0
- package/dist/effects/border-beam/register-properties.js +39 -0
- package/dist/effects/border-beam/types.js +0 -0
- package/dist/effects/image-comparison/compare.js +4 -0
- package/dist/effects/image-reveal/index.js +1 -0
- package/dist/effects/image-reveal/types.js +0 -0
- package/dist/effects/magnetic/magnetic.js +54 -0
- package/dist/effects/reveal/index.js +4 -0
- package/dist/effects/reveal/observe.js +43 -0
- package/dist/effects/reveal/types.js +0 -0
- package/dist/effects/shader-gradient/color.js +34 -0
- package/dist/effects/shader-gradient/index.js +1 -0
- package/dist/effects/shader-gradient/renderer.js +320 -0
- package/dist/effects/shader-gradient/shaders.js +280 -0
- package/dist/effects/shader-gradient/types.js +0 -0
- package/dist/icons/apple-symbol-exports.js +126 -0
- package/dist/icons/sf-symbols.js +465 -0
- package/dist/icons/trades.js +34 -0
- package/dist/inbox/index.js +4 -0
- package/dist/inbox/logic.js +156 -0
- package/dist/inbox/types.js +46 -0
- package/dist/index.js +18 -0
- package/dist/lib/arrive.js +13 -0
- package/dist/lib/icon-types.js +0 -0
- package/dist/lib/initials.js +17 -0
- package/dist/lib/layout.js +1 -0
- package/dist/lib/panel.js +3 -0
- package/dist/lib/result.js +26 -0
- package/dist/lib/sliding-pill.js +45 -0
- package/dist/lib/theme-transition.js +53 -0
- package/dist/lib/tone.js +32 -0
- package/dist/lib/utils.js +5 -0
- package/dist/onboarding/define-tours.js +28 -0
- package/dist/onboarding/index.js +4 -0
- package/dist/onboarding/onboarding.svelte.js +15 -0
- package/dist/onboarding/pick.js +16 -0
- package/dist/onboarding/stores.js +50 -0
- package/dist/onboarding/types.js +1 -0
- package/dist/onboarding/wait.js +25 -0
- package/dist/panel/index.js +1 -0
- package/dist/panel/side-panel-types.js +0 -0
- package/dist/record/demo-data.js +512 -0
- package/dist/record/fields.js +148 -0
- package/dist/record/format.js +97 -0
- package/dist/record/gutter.js +1 -0
- package/dist/record/index.js +15 -0
- package/dist/record/kinds.js +110 -0
- package/dist/record/logic.js +255 -0
- package/dist/record/types.js +52 -0
- package/dist/shadcn/accordion/index.js +5 -0
- package/dist/shadcn/alert/index.js +5 -0
- package/dist/shadcn/alert-dialog/index.js +12 -0
- package/dist/shadcn/attachment/index.js +10 -0
- package/dist/shadcn/avatar/index.js +7 -0
- package/dist/shadcn/badge/index.js +2 -0
- package/dist/shadcn/breadcrumb/index.js +8 -0
- package/dist/shadcn/bubble/index.js +5 -0
- package/dist/shadcn/button/index.js +2 -0
- package/dist/shadcn/calendar/index.js +19 -0
- package/dist/shadcn/card/index.js +8 -0
- package/dist/shadcn/checkbox/index.js +2 -0
- package/dist/shadcn/code-block/index.js +9 -0
- package/dist/shadcn/collapsible/index.js +4 -0
- package/dist/shadcn/command/index.js +13 -0
- package/dist/shadcn/context-menu/index.js +17 -0
- package/dist/shadcn/dialog/index.js +11 -0
- package/dist/shadcn/dropdown-menu/index.js +19 -0
- package/dist/shadcn/dropdown-menu/menu-item.js +23 -0
- package/dist/shadcn/empty/index.js +7 -0
- package/dist/shadcn/field/index.js +12 -0
- package/dist/shadcn/hooks/highlighter.js +92 -0
- package/dist/shadcn/hooks/is-mobile.svelte.js +7 -0
- package/dist/shadcn/hooks/use-highlighted-code.svelte.js +65 -0
- package/dist/shadcn/hover-card/index.js +5 -0
- package/dist/shadcn/input/index.js +3 -0
- package/dist/shadcn/input-otp/index.js +5 -0
- package/dist/shadcn/kbd/index.js +3 -0
- package/dist/shadcn/label/index.js +2 -0
- package/dist/shadcn/marker/index.js +4 -0
- package/dist/shadcn/message/index.js +7 -0
- package/dist/shadcn/message-scroller/index.js +8 -0
- package/dist/shadcn/popover/index.js +9 -0
- package/dist/shadcn/progress/index.js +2 -0
- package/dist/shadcn/progress/tone.js +11 -0
- package/dist/shadcn/radio-group/index.js +3 -0
- package/dist/shadcn/scroll-area/index.js +3 -0
- package/dist/shadcn/select/index.js +12 -0
- package/dist/shadcn/separator/index.js +2 -0
- package/dist/shadcn/sheet/index.js +11 -0
- package/dist/shadcn/sidebar/constants.js +6 -0
- package/dist/shadcn/sidebar/context.svelte.js +38 -0
- package/dist/shadcn/sidebar/index.js +25 -0
- package/dist/shadcn/skeleton/index.js +2 -0
- package/dist/shadcn/slider/index.js +2 -0
- package/dist/shadcn/sonner/index.js +1 -0
- package/dist/shadcn/spinner/index.js +1 -0
- package/dist/shadcn/switch/index.js +2 -0
- package/dist/shadcn/table/index.js +9 -0
- package/dist/shadcn/tabs/index.js +5 -0
- package/dist/shadcn/textarea/index.js +2 -0
- package/dist/shadcn/toggle/index.js +3 -0
- package/dist/shadcn/toggle-group/index.js +3 -0
- package/dist/shadcn/tooltip/context.js +8 -0
- package/dist/shadcn/tooltip/index.js +6 -0
- package/dist/shadcn/trace/index.js +9 -0
- package/dist/slides/bridges.js +66 -0
- package/dist/slides/deck-controller.svelte.js +221 -0
- package/dist/slides/export/capture.js +81 -0
- package/dist/slides/export/ffmpeg.js +85 -0
- package/dist/slides/export/index.js +4 -0
- package/dist/slides/export/launch.js +8 -0
- package/dist/slides/export/pdf.js +78 -0
- package/dist/slides/export/types.js +0 -0
- package/dist/slides/index.js +7 -0
- package/dist/slides/labels.js +26 -0
- package/dist/slides/layout.js +47 -0
- package/dist/slides/model.js +9 -0
- package/dist/slides/navigation.js +89 -0
- package/dist/slides/types.js +6 -0
- package/dist/status/define-statuses.js +16 -0
- package/dist/status/index.js +4 -0
- package/dist/status/status-types.js +0 -0
- package/dist/test-setup.js +26 -0
- package/dist/thread/index.js +3 -0
- package/dist/thread/logic.js +94 -0
- package/dist/thread/types.js +23 -0
- package/dist/work/board-order.js +109 -0
- package/dist/work/dates.js +19 -0
- package/dist/work/detail/detail-types.js +20 -0
- package/dist/work/docs/edited.js +39 -0
- package/dist/work/docs/index.js +7 -0
- package/dist/work/docs/labels.js +38 -0
- package/dist/work/docs/menu.js +121 -0
- package/dist/work/docs/outline.js +39 -0
- package/dist/work/gantt/logic.js +260 -0
- package/dist/work/inbox/inbox-logic.js +94 -0
- package/dist/work/inbox/inbox-statuses.js +86 -0
- package/dist/work/inbox/inbox-types.js +0 -0
- package/dist/work/inbox/index.js +1 -0
- package/dist/work/index.js +21 -0
- package/dist/work/kanban-drag.js +25 -0
- package/dist/work/kanban-types.js +0 -0
- package/dist/work/presets/sales.js +35 -0
- package/dist/work/reorder.js +10 -0
- package/dist/work/task-types.js +0 -0
- package/dist/work/time/duration.js +60 -0
- package/dist/work/time/time-types.js +25 -0
- package/dist/work/use-kanban-dnd.svelte.js +128 -0
- package/package.json +45 -3
- package/src/components/toolbar/toolbar-trigger.ts +8 -2
- package/src/record/demo-data.ts +4 -4
- package/src/record/record-demo.svelte +2 -2
- package/src/status/status-dot.svelte +0 -2
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { alignNodesHorizontally, alignNodesVertically } from "../headless/alignment.js";
|
|
2
|
+
const DEFAULT_GROUP_MIN = {
|
|
3
|
+
width: 250,
|
|
4
|
+
height: 150
|
|
5
|
+
};
|
|
6
|
+
function setX(nodes, x) {
|
|
7
|
+
for (const n of nodes)n.position.x = x;
|
|
8
|
+
}
|
|
9
|
+
function setY(nodes, y) {
|
|
10
|
+
for (const n of nodes)n.position.y = y;
|
|
11
|
+
}
|
|
12
|
+
const ALIGNERS = {
|
|
13
|
+
left: (nodes)=>setX(nodes, Math.min(...nodes.map((n)=>n.position.x))),
|
|
14
|
+
right: (nodes)=>setX(nodes, Math.max(...nodes.map((n)=>n.position.x))),
|
|
15
|
+
top: (nodes)=>alignNodesHorizontally(nodes),
|
|
16
|
+
bottom: (nodes)=>setY(nodes, Math.max(...nodes.map((n)=>n.position.y))),
|
|
17
|
+
"center-h": (nodes)=>alignNodesVertically(nodes),
|
|
18
|
+
"center-v": (nodes)=>setY(nodes, nodes.reduce((sum, n)=>sum + n.position.y, 0) / nodes.length)
|
|
19
|
+
};
|
|
20
|
+
export function alignNodes(selected, direction) {
|
|
21
|
+
if (selected.length < 2) return new Map();
|
|
22
|
+
const mutableNodes = selected.map((n)=>({
|
|
23
|
+
id: n.id,
|
|
24
|
+
position: {
|
|
25
|
+
...n.position
|
|
26
|
+
}
|
|
27
|
+
}));
|
|
28
|
+
ALIGNERS[direction](mutableNodes);
|
|
29
|
+
return new Map(mutableNodes.map((n)=>[
|
|
30
|
+
n.id,
|
|
31
|
+
n.position
|
|
32
|
+
]));
|
|
33
|
+
}
|
|
34
|
+
const TIDY_GAP = 40;
|
|
35
|
+
export function tidySelectedNodes(selected) {
|
|
36
|
+
if (selected.length < 2) return new Map();
|
|
37
|
+
const xs = selected.map((n)=>n.position.x);
|
|
38
|
+
const ys = selected.map((n)=>n.position.y);
|
|
39
|
+
const xSpread = Math.max(...xs) - Math.min(...xs);
|
|
40
|
+
const ySpread = Math.max(...ys) - Math.min(...ys);
|
|
41
|
+
const horizontal = xSpread >= ySpread;
|
|
42
|
+
const sorted = [
|
|
43
|
+
...selected
|
|
44
|
+
].sort((a, b)=>horizontal ? a.position.x - b.position.x : a.position.y - b.position.y);
|
|
45
|
+
return horizontal ? stackAlongX(sorted) : stackAlongY(sorted);
|
|
46
|
+
}
|
|
47
|
+
function stackAlongX(sorted) {
|
|
48
|
+
const result = new Map();
|
|
49
|
+
let cursorX = sorted[0].position.x;
|
|
50
|
+
for (const node of sorted){
|
|
51
|
+
result.set(node.id, {
|
|
52
|
+
x: cursorX,
|
|
53
|
+
y: node.position.y
|
|
54
|
+
});
|
|
55
|
+
cursorX += (node.measured?.width ?? node.width ?? 200) + TIDY_GAP;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
function stackAlongY(sorted) {
|
|
60
|
+
const result = new Map();
|
|
61
|
+
let cursorY = sorted[0].position.y;
|
|
62
|
+
for (const node of sorted){
|
|
63
|
+
result.set(node.id, {
|
|
64
|
+
x: node.position.x,
|
|
65
|
+
y: cursorY
|
|
66
|
+
});
|
|
67
|
+
cursorY += (node.measured?.height ?? node.height ?? 100) + TIDY_GAP;
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
const GROUP_PADDING = 32;
|
|
72
|
+
const GROUP_HEADER = 36;
|
|
73
|
+
const GROUP_GAP = 24;
|
|
74
|
+
const ROW_TOLERANCE = 8;
|
|
75
|
+
export function tidyGroup(children, min = DEFAULT_GROUP_MIN) {
|
|
76
|
+
if (children.length === 0) return null;
|
|
77
|
+
const width = (n)=>n.measured?.width ?? n.width ?? 200;
|
|
78
|
+
const height = (n)=>n.measured?.height ?? n.height ?? 100;
|
|
79
|
+
const rows = [];
|
|
80
|
+
for (const n of [
|
|
81
|
+
...children
|
|
82
|
+
].sort((a, b)=>a.position.y - b.position.y)){
|
|
83
|
+
const row = rows.at(-1);
|
|
84
|
+
if (row && n.position.y < row.bottom - ROW_TOLERANCE) {
|
|
85
|
+
row.items.push(n);
|
|
86
|
+
row.bottom = Math.max(row.bottom, n.position.y + height(n));
|
|
87
|
+
} else {
|
|
88
|
+
rows.push({
|
|
89
|
+
bottom: n.position.y + height(n),
|
|
90
|
+
items: [
|
|
91
|
+
n
|
|
92
|
+
]
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const positions = new Map();
|
|
97
|
+
let y = GROUP_PADDING + GROUP_HEADER;
|
|
98
|
+
let right = 0;
|
|
99
|
+
for (const row of rows){
|
|
100
|
+
let x = GROUP_PADDING;
|
|
101
|
+
for (const n of row.items.sort((a, b)=>a.position.x - b.position.x)){
|
|
102
|
+
positions.set(n.id, {
|
|
103
|
+
x,
|
|
104
|
+
y
|
|
105
|
+
});
|
|
106
|
+
x += width(n) + GROUP_GAP;
|
|
107
|
+
}
|
|
108
|
+
right = Math.max(right, x - GROUP_GAP);
|
|
109
|
+
y += Math.max(...row.items.map(height)) + GROUP_GAP;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
positions,
|
|
113
|
+
size: {
|
|
114
|
+
width: Math.max(min.width, right + GROUP_PADDING),
|
|
115
|
+
height: Math.max(min.height, y - GROUP_GAP + GROUP_PADDING)
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export function groupSelectionOps(selected, groupLabel, { type = "group", data = {} } = {}) {
|
|
120
|
+
if (selected.length < 2) return [];
|
|
121
|
+
const parentId = selected[0].parentId;
|
|
122
|
+
const members = selected.filter((n)=>n.parentId === parentId);
|
|
123
|
+
if (members.length < 2) return [];
|
|
124
|
+
const width = (n)=>n.measured?.width ?? n.width ?? 200;
|
|
125
|
+
const height = (n)=>n.measured?.height ?? n.height ?? 100;
|
|
126
|
+
const minX = Math.min(...members.map((n)=>n.position.x));
|
|
127
|
+
const minY = Math.min(...members.map((n)=>n.position.y));
|
|
128
|
+
const maxX = Math.max(...members.map((n)=>n.position.x + width(n)));
|
|
129
|
+
const maxY = Math.max(...members.map((n)=>n.position.y + height(n)));
|
|
130
|
+
const origin = {
|
|
131
|
+
x: minX - GROUP_PADDING,
|
|
132
|
+
y: minY - GROUP_PADDING - GROUP_HEADER
|
|
133
|
+
};
|
|
134
|
+
const groupId = crypto.randomUUID();
|
|
135
|
+
return [
|
|
136
|
+
{
|
|
137
|
+
kind: "addNode",
|
|
138
|
+
node: {
|
|
139
|
+
id: groupId,
|
|
140
|
+
type,
|
|
141
|
+
position: origin,
|
|
142
|
+
data: {
|
|
143
|
+
...data,
|
|
144
|
+
label: groupLabel
|
|
145
|
+
},
|
|
146
|
+
width: maxX - minX + GROUP_PADDING * 2,
|
|
147
|
+
height: maxY - minY + GROUP_PADDING * 2 + GROUP_HEADER,
|
|
148
|
+
...parentId ? {
|
|
149
|
+
parentId
|
|
150
|
+
} : {}
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
...members.flatMap((n)=>[
|
|
154
|
+
{
|
|
155
|
+
kind: "setParent",
|
|
156
|
+
nodeId: n.id,
|
|
157
|
+
parentId: groupId,
|
|
158
|
+
extent: "parent"
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
kind: "moveNode",
|
|
162
|
+
nodeId: n.id,
|
|
163
|
+
position: {
|
|
164
|
+
x: n.position.x - origin.x,
|
|
165
|
+
y: n.position.y - origin.y
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
])
|
|
169
|
+
];
|
|
170
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
export function isLocked(node) {
|
|
2
|
+
const data = node?.data;
|
|
3
|
+
return data?.locked === true;
|
|
4
|
+
}
|
|
5
|
+
export const LOCK_FLAGS = {
|
|
6
|
+
draggable: false,
|
|
7
|
+
deletable: false
|
|
8
|
+
};
|
|
9
|
+
export function withLockFlags(node) {
|
|
10
|
+
if (node.data?.syncedCopy) return node;
|
|
11
|
+
if (isLocked(node)) return {
|
|
12
|
+
...node,
|
|
13
|
+
...LOCK_FLAGS
|
|
14
|
+
};
|
|
15
|
+
if (node.draggable !== false && node.deletable !== false) return node;
|
|
16
|
+
const { draggable: _drag, deletable: _delete, ...rest } = node;
|
|
17
|
+
return rest;
|
|
18
|
+
}
|
|
19
|
+
function onlyUnlocks(op) {
|
|
20
|
+
if (op.kind !== "patchNode") return false;
|
|
21
|
+
const patch = op;
|
|
22
|
+
return patch.data !== undefined && patch.style === undefined && patch.width === undefined && patch.height === undefined && Object.keys(patch.data).every((key)=>key === "locked");
|
|
23
|
+
}
|
|
24
|
+
export function refuseLockedOps(nodes, ops) {
|
|
25
|
+
const locked = new Set(nodes.filter(isLocked).map((node)=>node.id));
|
|
26
|
+
if (locked.size === 0) return [
|
|
27
|
+
...ops
|
|
28
|
+
];
|
|
29
|
+
return ops.filter((op)=>{
|
|
30
|
+
const nodeId = op.nodeId;
|
|
31
|
+
return typeof nodeId !== "string" || !locked.has(nodeId) || onlyUnlocks(op);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
export function deletableIds(nodes, ids) {
|
|
35
|
+
const parentOf = new Map(nodes.map((node)=>[
|
|
36
|
+
node.id,
|
|
37
|
+
node.parentId
|
|
38
|
+
]));
|
|
39
|
+
const holdsLocked = new Set();
|
|
40
|
+
for (const node of nodes){
|
|
41
|
+
if (!isLocked(node)) continue;
|
|
42
|
+
for(let up = node.parentId; up; up = parentOf.get(up))holdsLocked.add(up);
|
|
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));
|
|
49
|
+
}
|
|
50
|
+
export function toggleLock(nodes, picked) {
|
|
51
|
+
const targets = nodes.filter((node)=>picked.has(node.id) && !node.data.syncedCopy);
|
|
52
|
+
if (targets.length === 0) return null;
|
|
53
|
+
const locked = !targets.every(isLocked);
|
|
54
|
+
const ids = new Set(targets.map((node)=>node.id));
|
|
55
|
+
return {
|
|
56
|
+
locked,
|
|
57
|
+
nodes: nodes.map((node)=>ids.has(node.id) ? withLockFlags({
|
|
58
|
+
...node,
|
|
59
|
+
data: {
|
|
60
|
+
...node.data,
|
|
61
|
+
locked
|
|
62
|
+
}
|
|
63
|
+
}) : node)
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { tryCatch } from "../../lib/result.js";
|
|
2
|
+
import { applyOps } from "../ops/ops.js";
|
|
3
|
+
import { sortParentsFirst } from "../headless/canvas-cleanup.js";
|
|
4
|
+
const EDGE_PATCH_FIELDS = [
|
|
5
|
+
"source",
|
|
6
|
+
"target",
|
|
7
|
+
"label",
|
|
8
|
+
"animated",
|
|
9
|
+
"type",
|
|
10
|
+
"style",
|
|
11
|
+
"sourceHandle",
|
|
12
|
+
"targetHandle"
|
|
13
|
+
];
|
|
14
|
+
function json(value) {
|
|
15
|
+
return JSON.stringify(value ?? null);
|
|
16
|
+
}
|
|
17
|
+
function objectStyle(node) {
|
|
18
|
+
return node.style && typeof node.style === "object" ? node.style : undefined;
|
|
19
|
+
}
|
|
20
|
+
function numberOrUndefined(value) {
|
|
21
|
+
return typeof value === "number" ? value : undefined;
|
|
22
|
+
}
|
|
23
|
+
function addNodeOp(node) {
|
|
24
|
+
const style = objectStyle(node);
|
|
25
|
+
const width = numberOrUndefined(node.width);
|
|
26
|
+
const height = numberOrUndefined(node.height);
|
|
27
|
+
return {
|
|
28
|
+
kind: "addNode",
|
|
29
|
+
node: {
|
|
30
|
+
id: node.id,
|
|
31
|
+
type: String(node.type ?? "node"),
|
|
32
|
+
position: node.position ?? {
|
|
33
|
+
x: 0,
|
|
34
|
+
y: 0
|
|
35
|
+
},
|
|
36
|
+
data: node.data ?? {},
|
|
37
|
+
...style ? {
|
|
38
|
+
style
|
|
39
|
+
} : {},
|
|
40
|
+
...width !== undefined ? {
|
|
41
|
+
width
|
|
42
|
+
} : {},
|
|
43
|
+
...height !== undefined ? {
|
|
44
|
+
height
|
|
45
|
+
} : {},
|
|
46
|
+
...node.parentId ? {
|
|
47
|
+
parentId: node.parentId
|
|
48
|
+
} : {}
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function sizePatchOf(before, node) {
|
|
53
|
+
const patch = {};
|
|
54
|
+
const style = objectStyle(node);
|
|
55
|
+
if (style && json(before.style) !== json(node.style)) patch.style = style;
|
|
56
|
+
const width = numberOrUndefined(node.width);
|
|
57
|
+
if (width !== undefined && before.width !== node.width) patch.width = width;
|
|
58
|
+
const height = numberOrUndefined(node.height);
|
|
59
|
+
if (height !== undefined && before.height !== node.height) patch.height = height;
|
|
60
|
+
return patch;
|
|
61
|
+
}
|
|
62
|
+
function changedData(before, data) {
|
|
63
|
+
if (!data) return undefined;
|
|
64
|
+
const changed = Object.entries(data).filter(([key, value])=>json(before?.[key]) !== json(value));
|
|
65
|
+
return changed.length > 0 ? Object.fromEntries(changed) : undefined;
|
|
66
|
+
}
|
|
67
|
+
function changedNodeOps(before, node) {
|
|
68
|
+
const ops = [];
|
|
69
|
+
if (node.position && json(before.position) !== json(node.position)) {
|
|
70
|
+
ops.push({
|
|
71
|
+
kind: "moveNode",
|
|
72
|
+
nodeId: node.id,
|
|
73
|
+
position: node.position
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const parentId = node.parentId ?? null;
|
|
77
|
+
if ((before.parentId ?? null) !== parentId) {
|
|
78
|
+
ops.push({
|
|
79
|
+
kind: "setParent",
|
|
80
|
+
nodeId: node.id,
|
|
81
|
+
parentId
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const data = changedData(before.data, node.data);
|
|
85
|
+
if (data) ops.push({
|
|
86
|
+
kind: "patchNode",
|
|
87
|
+
nodeId: node.id,
|
|
88
|
+
data
|
|
89
|
+
});
|
|
90
|
+
const sizePatch = sizePatchOf(before, node);
|
|
91
|
+
if (Object.keys(sizePatch).length > 0) {
|
|
92
|
+
ops.push({
|
|
93
|
+
kind: "patchNode",
|
|
94
|
+
nodeId: node.id,
|
|
95
|
+
...sizePatch
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return ops;
|
|
99
|
+
}
|
|
100
|
+
function nodeOps(base, current) {
|
|
101
|
+
const baseNodes = new Map(base.nodes.map((n)=>[
|
|
102
|
+
n.id,
|
|
103
|
+
n
|
|
104
|
+
]));
|
|
105
|
+
const currentIds = new Set(current.nodes.map((n)=>n.id));
|
|
106
|
+
const removed = base.nodes.filter((n)=>!currentIds.has(n.id)).map((n)=>({
|
|
107
|
+
kind: "removeNode",
|
|
108
|
+
nodeId: n.id
|
|
109
|
+
}));
|
|
110
|
+
const added = sortParentsFirst(current.nodes.filter((n)=>!baseNodes.has(n.id))).map(addNodeOp);
|
|
111
|
+
const changed = current.nodes.flatMap((node)=>{
|
|
112
|
+
const before = baseNodes.get(node.id);
|
|
113
|
+
return before ? changedNodeOps(before, node) : [];
|
|
114
|
+
});
|
|
115
|
+
return [
|
|
116
|
+
...removed,
|
|
117
|
+
...added,
|
|
118
|
+
...changed
|
|
119
|
+
];
|
|
120
|
+
}
|
|
121
|
+
function addEdgeOp(edge) {
|
|
122
|
+
const optional = (key)=>typeof edge[key] === "string" ? {
|
|
123
|
+
[key]: edge[key]
|
|
124
|
+
} : {};
|
|
125
|
+
const record = (key)=>{
|
|
126
|
+
const value = edge[key];
|
|
127
|
+
return value && typeof value === "object" && !Array.isArray(value) ? {
|
|
128
|
+
[key]: value
|
|
129
|
+
} : {};
|
|
130
|
+
};
|
|
131
|
+
return {
|
|
132
|
+
kind: "addEdge",
|
|
133
|
+
edge: {
|
|
134
|
+
id: edge.id,
|
|
135
|
+
source: String(edge.source),
|
|
136
|
+
target: String(edge.target),
|
|
137
|
+
...optional("sourceHandle"),
|
|
138
|
+
...optional("targetHandle"),
|
|
139
|
+
...optional("type"),
|
|
140
|
+
...optional("label"),
|
|
141
|
+
...typeof edge.animated === "boolean" ? {
|
|
142
|
+
animated: edge.animated
|
|
143
|
+
} : {},
|
|
144
|
+
...record("style"),
|
|
145
|
+
...record("data")
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function patchEdgeOps(before, edge) {
|
|
150
|
+
const patch = {};
|
|
151
|
+
for (const field of EDGE_PATCH_FIELDS){
|
|
152
|
+
if (edge[field] !== undefined && json(before[field]) !== json(edge[field])) {
|
|
153
|
+
patch[field] = edge[field];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (Object.keys(patch).length === 0) return [];
|
|
157
|
+
return [
|
|
158
|
+
{
|
|
159
|
+
kind: "patchEdge",
|
|
160
|
+
edgeId: edge.id,
|
|
161
|
+
patch
|
|
162
|
+
}
|
|
163
|
+
];
|
|
164
|
+
}
|
|
165
|
+
function edgeOps(base, current) {
|
|
166
|
+
const baseEdges = new Map(base.edges.map((e)=>[
|
|
167
|
+
e.id,
|
|
168
|
+
e
|
|
169
|
+
]));
|
|
170
|
+
const currentIds = new Set(current.edges.map((e)=>e.id));
|
|
171
|
+
const removed = base.edges.filter((e)=>!currentIds.has(e.id)).map((e)=>({
|
|
172
|
+
kind: "removeEdge",
|
|
173
|
+
edgeId: e.id
|
|
174
|
+
}));
|
|
175
|
+
const upserted = current.edges.flatMap((edge)=>{
|
|
176
|
+
const before = baseEdges.get(edge.id);
|
|
177
|
+
return before ? patchEdgeOps(before, edge) : [
|
|
178
|
+
addEdgeOp(edge)
|
|
179
|
+
];
|
|
180
|
+
});
|
|
181
|
+
return [
|
|
182
|
+
...removed,
|
|
183
|
+
...upserted
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
export function diffToOps(base, current) {
|
|
187
|
+
return [
|
|
188
|
+
...nodeOps(base, current),
|
|
189
|
+
...edgeOps(base, current)
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
const NODE_FIELDS = [
|
|
193
|
+
"type",
|
|
194
|
+
"zIndex"
|
|
195
|
+
];
|
|
196
|
+
function nodeFieldPatches(base, current) {
|
|
197
|
+
const baseNodes = new Map(base.nodes.map((n)=>[
|
|
198
|
+
n.id,
|
|
199
|
+
n
|
|
200
|
+
]));
|
|
201
|
+
const patches = [];
|
|
202
|
+
for (const node of current.nodes){
|
|
203
|
+
const before = baseNodes.get(node.id);
|
|
204
|
+
if (!before) continue;
|
|
205
|
+
const set = {};
|
|
206
|
+
const unset = [];
|
|
207
|
+
for (const field of NODE_FIELDS){
|
|
208
|
+
if (json(before[field]) === json(node[field])) continue;
|
|
209
|
+
if (node[field] === undefined) unset.push(field);
|
|
210
|
+
else set[field] = node[field];
|
|
211
|
+
}
|
|
212
|
+
const unsetData = Object.keys(before.data ?? {}).filter((key)=>!(key in (node.data ?? {})));
|
|
213
|
+
if (unset.length + unsetData.length + Object.keys(set).length > 0) {
|
|
214
|
+
patches.push({
|
|
215
|
+
nodeId: node.id,
|
|
216
|
+
set,
|
|
217
|
+
unset,
|
|
218
|
+
unsetData
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return patches;
|
|
223
|
+
}
|
|
224
|
+
function withoutKeys(record, keys) {
|
|
225
|
+
return Object.fromEntries(Object.entries(record).filter(([key])=>!keys.includes(key)));
|
|
226
|
+
}
|
|
227
|
+
function applyNodeFieldPatches(nodes, patches) {
|
|
228
|
+
const byId = new Map(patches.map((patch)=>[
|
|
229
|
+
patch.nodeId,
|
|
230
|
+
patch
|
|
231
|
+
]));
|
|
232
|
+
const seen = new Set();
|
|
233
|
+
const next = nodes.map((node)=>{
|
|
234
|
+
const patch = typeof node.id === "string" ? byId.get(node.id) : undefined;
|
|
235
|
+
if (!patch) return node;
|
|
236
|
+
seen.add(patch.nodeId);
|
|
237
|
+
const patched = withoutKeys({
|
|
238
|
+
...node,
|
|
239
|
+
...patch.set
|
|
240
|
+
}, patch.unset);
|
|
241
|
+
if (patch.unsetData.length === 0 || typeof node.data !== "object" || node.data === null) {
|
|
242
|
+
return patched;
|
|
243
|
+
}
|
|
244
|
+
return {
|
|
245
|
+
...patched,
|
|
246
|
+
data: withoutKeys(node.data, patch.unsetData)
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
return seen.size === byId.size ? next : null;
|
|
250
|
+
}
|
|
251
|
+
function parentsFirst(nodes) {
|
|
252
|
+
const ids = new Set(nodes.map((node)=>node.id));
|
|
253
|
+
const seen = new Set();
|
|
254
|
+
const misordered = nodes.some((node)=>{
|
|
255
|
+
const early = ids.has(node.parentId) && !seen.has(node.parentId);
|
|
256
|
+
seen.add(node.id);
|
|
257
|
+
return early;
|
|
258
|
+
});
|
|
259
|
+
if (!misordered) return nodes;
|
|
260
|
+
const wrapped = nodes.map((node)=>({
|
|
261
|
+
id: String(node.id),
|
|
262
|
+
parentId: typeof node.parentId === "string" ? node.parentId : undefined,
|
|
263
|
+
node
|
|
264
|
+
}));
|
|
265
|
+
return sortParentsFirst(wrapped).map(({ node })=>node);
|
|
266
|
+
}
|
|
267
|
+
export function rebaseDiagram(serverDoc, lastSaved, local) {
|
|
268
|
+
const ops = diffToOps(lastSaved, local);
|
|
269
|
+
const res = tryCatch(()=>applyOps({
|
|
270
|
+
nodes: serverDoc.nodes,
|
|
271
|
+
edges: serverDoc.edges
|
|
272
|
+
}, ops));
|
|
273
|
+
if (!res.ok) return null;
|
|
274
|
+
const nodes = applyNodeFieldPatches(res.value.nodes, nodeFieldPatches(lastSaved, local));
|
|
275
|
+
return nodes ? {
|
|
276
|
+
...res.value,
|
|
277
|
+
nodes: parentsFirst(nodes)
|
|
278
|
+
} : null;
|
|
279
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { PEN_COLORS, SHAPE_NODE, STROKE_NODE, TEXT_NODE } from "../pen/types.js";
|
|
2
|
+
export const FONT_SIZES = [
|
|
3
|
+
16,
|
|
4
|
+
20,
|
|
5
|
+
28,
|
|
6
|
+
40
|
|
7
|
+
];
|
|
8
|
+
const SHAPE_WIDTHS = [
|
|
9
|
+
2,
|
|
10
|
+
3,
|
|
11
|
+
5
|
|
12
|
+
];
|
|
13
|
+
const PEN_WIDTHS = [
|
|
14
|
+
2,
|
|
15
|
+
4,
|
|
16
|
+
8
|
|
17
|
+
];
|
|
18
|
+
const HIGHLIGHTER_WIDTHS = [
|
|
19
|
+
12,
|
|
20
|
+
20,
|
|
21
|
+
32
|
|
22
|
+
];
|
|
23
|
+
const FRAME_WIDTHS = [
|
|
24
|
+
1,
|
|
25
|
+
2,
|
|
26
|
+
4
|
|
27
|
+
];
|
|
28
|
+
const CONTROLS = {
|
|
29
|
+
shape: [
|
|
30
|
+
"fill",
|
|
31
|
+
"stroke",
|
|
32
|
+
"width"
|
|
33
|
+
],
|
|
34
|
+
text: [
|
|
35
|
+
"stroke",
|
|
36
|
+
"fontSize",
|
|
37
|
+
"fontWeight"
|
|
38
|
+
],
|
|
39
|
+
stroke: [
|
|
40
|
+
"stroke",
|
|
41
|
+
"width"
|
|
42
|
+
],
|
|
43
|
+
frame: [
|
|
44
|
+
"fill",
|
|
45
|
+
"stroke",
|
|
46
|
+
"width"
|
|
47
|
+
]
|
|
48
|
+
};
|
|
49
|
+
function kindOf(node, frameType) {
|
|
50
|
+
if (node.type === SHAPE_NODE) return "shape";
|
|
51
|
+
if (node.type === TEXT_NODE) return "text";
|
|
52
|
+
if (node.type === STROKE_NODE) return "stroke";
|
|
53
|
+
return node.type === frameType && node.data.frame === true ? "frame" : null;
|
|
54
|
+
}
|
|
55
|
+
function widthsOf(kind, node) {
|
|
56
|
+
if (kind === "shape") return SHAPE_WIDTHS;
|
|
57
|
+
if (kind === "frame") return FRAME_WIDTHS;
|
|
58
|
+
return node.data.tool === "highlighter" ? HIGHLIGHTER_WIDTHS : PEN_WIDTHS;
|
|
59
|
+
}
|
|
60
|
+
function widthKey(kind) {
|
|
61
|
+
if (kind === "stroke") return "size";
|
|
62
|
+
return "strokeWidth";
|
|
63
|
+
}
|
|
64
|
+
function colorOf(value) {
|
|
65
|
+
return PEN_COLORS.find((color)=>color === value);
|
|
66
|
+
}
|
|
67
|
+
function levelOf(kind, node) {
|
|
68
|
+
const widths = widthsOf(kind, node);
|
|
69
|
+
const stored = Number(node.data[widthKey(kind)]);
|
|
70
|
+
if (!Number.isFinite(stored)) return kind === "frame" ? 0 : 1;
|
|
71
|
+
let nearest = 0;
|
|
72
|
+
for(let at = 1; at < widths.length; at += 1){
|
|
73
|
+
if (Math.abs(widths[at] - stored) < Math.abs(widths[nearest] - stored)) nearest = at;
|
|
74
|
+
}
|
|
75
|
+
return nearest;
|
|
76
|
+
}
|
|
77
|
+
const VALUES = {
|
|
78
|
+
shape: (node, color)=>({
|
|
79
|
+
fill: node.data.filled ? colorOf(node.data.fill) ?? color : "none",
|
|
80
|
+
stroke: color,
|
|
81
|
+
width: levelOf("shape", node)
|
|
82
|
+
}),
|
|
83
|
+
text: (node, color)=>({
|
|
84
|
+
stroke: color,
|
|
85
|
+
fontSize: Number(node.data.fontSize) || 20,
|
|
86
|
+
fontWeight: node.data.fontWeight === "bold" ? "bold" : "regular"
|
|
87
|
+
}),
|
|
88
|
+
stroke: (node, color)=>({
|
|
89
|
+
stroke: color,
|
|
90
|
+
width: levelOf("stroke", node)
|
|
91
|
+
}),
|
|
92
|
+
frame: (node)=>({
|
|
93
|
+
fill: colorOf(node.data.fill) ?? "none",
|
|
94
|
+
stroke: colorOf(node.data.stroke) ?? "ink",
|
|
95
|
+
width: levelOf("frame", node)
|
|
96
|
+
})
|
|
97
|
+
};
|
|
98
|
+
function valuesOf(kind, node) {
|
|
99
|
+
return VALUES[kind](node, colorOf(node.data.color) ?? "ink");
|
|
100
|
+
}
|
|
101
|
+
export function summarizeStyle(nodes, frameType) {
|
|
102
|
+
const controls = new Set();
|
|
103
|
+
const seen = new Map();
|
|
104
|
+
let widths = [];
|
|
105
|
+
for (const node of nodes){
|
|
106
|
+
const kind = kindOf(node, frameType);
|
|
107
|
+
if (!kind) continue;
|
|
108
|
+
const values = valuesOf(kind, node);
|
|
109
|
+
for (const control of CONTROLS[kind]){
|
|
110
|
+
controls.add(control);
|
|
111
|
+
const set = seen.get(control) ?? new Set();
|
|
112
|
+
set.add(values[control]);
|
|
113
|
+
seen.set(control, set);
|
|
114
|
+
}
|
|
115
|
+
if (widths.length === 0 && CONTROLS[kind].includes("width")) widths = widthsOf(kind, node);
|
|
116
|
+
}
|
|
117
|
+
if (controls.size === 0) return null;
|
|
118
|
+
const values = {};
|
|
119
|
+
for (const [control, set] of seen){
|
|
120
|
+
if (set.size === 1) Object.assign(values, {
|
|
121
|
+
[control]: [
|
|
122
|
+
...set
|
|
123
|
+
][0]
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
controls,
|
|
128
|
+
values,
|
|
129
|
+
widths
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function fillData(kind, fill) {
|
|
133
|
+
if (kind === "frame") return {
|
|
134
|
+
fill
|
|
135
|
+
};
|
|
136
|
+
return fill === "none" ? {
|
|
137
|
+
filled: false
|
|
138
|
+
} : {
|
|
139
|
+
filled: true,
|
|
140
|
+
fill
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function textData(change) {
|
|
144
|
+
const data = {};
|
|
145
|
+
if (change.fontSize !== undefined) data.fontSize = change.fontSize;
|
|
146
|
+
if (change.fontWeight !== undefined) data.fontWeight = change.fontWeight;
|
|
147
|
+
return data;
|
|
148
|
+
}
|
|
149
|
+
function dataFor(kind, node, change) {
|
|
150
|
+
const data = {};
|
|
151
|
+
if (change.stroke) data[kind === "frame" ? "stroke" : "color"] = change.stroke;
|
|
152
|
+
if (change.fill !== undefined && (kind === "shape" || kind === "frame")) {
|
|
153
|
+
Object.assign(data, fillData(kind, change.fill));
|
|
154
|
+
}
|
|
155
|
+
if (change.width !== undefined && kind !== "text") {
|
|
156
|
+
data[widthKey(kind)] = widthsOf(kind, node)[change.width];
|
|
157
|
+
}
|
|
158
|
+
if (kind === "text") Object.assign(data, textData(change));
|
|
159
|
+
return data;
|
|
160
|
+
}
|
|
161
|
+
export function styleOps(nodes, change, frameType) {
|
|
162
|
+
const ops = [];
|
|
163
|
+
for (const node of nodes){
|
|
164
|
+
const kind = kindOf(node, frameType);
|
|
165
|
+
if (!kind) continue;
|
|
166
|
+
const data = dataFor(kind, node, change);
|
|
167
|
+
if (Object.keys(data).length > 0) ops.push({
|
|
168
|
+
kind: "patchNode",
|
|
169
|
+
nodeId: node.id,
|
|
170
|
+
data
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return ops;
|
|
174
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getContext, setContext } from "svelte";
|
|
2
|
+
export const TEXT_FIELD_KEY = Symbol("canvas-text-field");
|
|
3
|
+
export function setTextField(component) {
|
|
4
|
+
setContext(TEXT_FIELD_KEY, component);
|
|
5
|
+
}
|
|
6
|
+
export function useTextField() {
|
|
7
|
+
return getContext(TEXT_FIELD_KEY) ?? null;
|
|
8
|
+
}
|