@heyramzi/ui 2.11.0 → 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/components/tags-input/tags-input.svelte +2 -0
- package/src/editor/markdown.ts +30 -35
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
// Break apart, the look: which sketch accent or pen colour a drawn colour maps to, and how big a note sets.
|
|
1
2
|
import { SKETCH_ACCENTS, sketchColors } from "./sketch.js";
|
|
2
3
|
import { PEN_HEX } from "../pen/colors.js";
|
|
4
|
+
/** A note's type and spacing in px (sketch-note.svelte), for estimating how tall a card sets. */
|
|
3
5
|
export const NOTE_TITLE_PX = 19;
|
|
4
6
|
export const NOTE_BODY_PX = 15;
|
|
5
7
|
const NOTE_TITLE_LINE = 26;
|
|
@@ -7,87 +9,90 @@ const NOTE_BODY_LINE = 21.75;
|
|
|
7
9
|
const NOTE_PAD_X = 40;
|
|
8
10
|
const NOTE_PAD_Y = 32;
|
|
9
11
|
const NOTE_GAP = 10;
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
const
|
|
12
|
+
/** Average glyph width as a share of the type size. */
|
|
13
|
+
const NOTE_GLYPH = .55;
|
|
14
|
+
const NOTE_GLYPH_CAPS = .7;
|
|
15
|
+
/** Aim a little under the drawn height, since the estimate wraps by character count. */
|
|
16
|
+
const FIT_MARGIN = .92;
|
|
13
17
|
function channels(hex) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
4
|
|
23
|
-
].map((i)=>Number.parseInt(full.slice(i, i + 2), 16));
|
|
18
|
+
const m = hex?.trim().match(/^#([\da-f]{3}|[\da-f]{6})$/i);
|
|
19
|
+
if (!m) return null;
|
|
20
|
+
const full = m[1].length === 3 ? [...m[1]].map((c) => c + c).join("") : m[1];
|
|
21
|
+
return [
|
|
22
|
+
0,
|
|
23
|
+
2,
|
|
24
|
+
4
|
|
25
|
+
].map((i) => Number.parseInt(full.slice(i, i + 2), 16));
|
|
24
26
|
}
|
|
27
|
+
/** The sketch accent nearest the rect's stroke. A grey or an unknown colour reads as ink, the default. */
|
|
25
28
|
export function accentOf(stroke) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
const rgb = channels(stroke);
|
|
30
|
+
if (!rgb) return undefined;
|
|
31
|
+
const spread = Math.max(...rgb) - Math.min(...rgb);
|
|
32
|
+
if (spread < 40) return undefined;
|
|
33
|
+
let best = null;
|
|
34
|
+
for (const name of SKETCH_ACCENTS) {
|
|
35
|
+
const target = channels(sketchColors(name).stroke);
|
|
36
|
+
if (!target) continue;
|
|
37
|
+
const distance = Math.hypot(...rgb.map((v, i) => v - target[i]));
|
|
38
|
+
if (!best || distance < best.distance) best = {
|
|
39
|
+
name,
|
|
40
|
+
distance
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return best && best.name !== "ink" && best.distance < 90 ? best.name : undefined;
|
|
41
44
|
}
|
|
45
|
+
/** Card text is markdown: a stray `*` or a leading `-` would turn into formatting. */
|
|
42
46
|
export function escapeMarkdown(line) {
|
|
43
|
-
|
|
47
|
+
return line.replace(/([\\`*_~[\]])/g, "\\$1").replace(/^([#>+-]|\d+\.)/, "\\$1");
|
|
44
48
|
}
|
|
45
49
|
function hueOf([r, g, b]) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
const max = Math.max(r, g, b);
|
|
51
|
+
const d = max - Math.min(r, g, b);
|
|
52
|
+
if (d === 0) return 0;
|
|
53
|
+
const h = max === r ? (g - b) / d % 6 : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
|
|
54
|
+
return (h * 60 + 360) % 360;
|
|
51
55
|
}
|
|
56
|
+
/** The pen colour whose hue is nearest, or undefined for a grey, a white or a black. */
|
|
52
57
|
export function penColorOf(color) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
const rgb = channels(color);
|
|
59
|
+
if (!rgb || Math.max(...rgb) - Math.min(...rgb) < 40) return undefined;
|
|
60
|
+
const hue = hueOf(rgb);
|
|
61
|
+
let best = null;
|
|
62
|
+
for (const name of Object.keys(PEN_HEX)) {
|
|
63
|
+
const target = channels(PEN_HEX[name].light);
|
|
64
|
+
if (!target || name === "ink") continue;
|
|
65
|
+
const diff = Math.abs(hue - hueOf(target));
|
|
66
|
+
const gap = Math.min(diff, 360 - diff);
|
|
67
|
+
if (!best || gap < best.gap) best = {
|
|
68
|
+
name,
|
|
69
|
+
gap
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return best?.name;
|
|
68
73
|
}
|
|
74
|
+
/** A solid colour, not white or paper. */
|
|
69
75
|
export function inked(color) {
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
const rgb = channels(color);
|
|
77
|
+
return Boolean(rgb) && Math.min(...rgb) < 240;
|
|
72
78
|
}
|
|
73
|
-
export const round1 = (n)=>Math.round(n * 10) / 10;
|
|
79
|
+
export const round1 = (n) => Math.round(n * 10) / 10;
|
|
80
|
+
/** The share a note shows at: its title at the drawn size, or smaller until the copy fits the drawn box. */
|
|
74
81
|
export function fitScale(card, matched, boxW, boxH) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
for(let i = 0; i < 16 && z > 0.2 && heightAt(z) > boxH * FIT_MARGIN; i += 1)z *= 0.92;
|
|
92
|
-
return Math.round(z * 1000) / 1000;
|
|
82
|
+
// Capitals run wide, and a word wider than the card breaks mid-word, so that counts as too tall.
|
|
83
|
+
const glyph = (text) => text === text.toUpperCase() ? NOTE_GLYPH_CAPS : NOTE_GLYPH;
|
|
84
|
+
const lines = (text, px, inner) => {
|
|
85
|
+
const widest = Math.max(...text.split(/\s+/).map((w) => w.length)) * px * glyph(text);
|
|
86
|
+
if (widest > inner) return 99;
|
|
87
|
+
return Math.max(1, Math.ceil(text.length * px * glyph(text) / inner));
|
|
88
|
+
};
|
|
89
|
+
const heightAt = (z) => {
|
|
90
|
+
const inner = Math.max(boxW / z - NOTE_PAD_X, 40);
|
|
91
|
+
const title = lines([card.kicker, card.title].filter(Boolean).join(" "), NOTE_TITLE_PX, inner);
|
|
92
|
+
const body = card.body.reduce((n, line) => n + lines(line, NOTE_BODY_PX, inner), 0);
|
|
93
|
+
return (NOTE_PAD_Y + title * NOTE_TITLE_LINE + (body ? NOTE_GAP + body * NOTE_BODY_LINE : 0)) * z;
|
|
94
|
+
};
|
|
95
|
+
let z = matched;
|
|
96
|
+
for (let i = 0; i < 16 && z > .2 && heightAt(z) > boxH * FIT_MARGIN; i += 1) z *= .92;
|
|
97
|
+
return Math.round(z * 1e3) / 1e3;
|
|
93
98
|
}
|
|
@@ -6,201 +6,208 @@ import { NOTE_BODY_PX, NOTE_TITLE_PX, round1 } from "./break-apart-style.js";
|
|
|
6
6
|
import { addCards, addEdges, addFrames, addShapes, freshId, linksOf, placeBadges, startPlan } from "./break-apart-plan.js";
|
|
7
7
|
const TEXT_NODE = 3;
|
|
8
8
|
const ELEMENT_NODE = 1;
|
|
9
|
+
/** Space between the drawing and the caption set under it. */
|
|
9
10
|
const CAPTION_GAP = 8;
|
|
11
|
+
// =========================
|
|
12
|
+
// TREE
|
|
13
|
+
// =========================
|
|
14
|
+
/** Read a DOM element (or any node shaped like one) into a tree. */
|
|
10
15
|
export function treeFromElement(el) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
const attrs = {};
|
|
17
|
+
for (const attr of Array.from(el.attributes ?? [])) attrs[attr.name] = attr.value;
|
|
18
|
+
const children = [];
|
|
19
|
+
for (const child of Array.from(el.childNodes ?? [])) {
|
|
20
|
+
if (child.nodeType === TEXT_NODE) children.push(child.textContent ?? "");
|
|
21
|
+
else if (child.nodeType === ELEMENT_NODE) children.push(treeFromElement(child));
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
tag: el.localName ?? "",
|
|
25
|
+
attrs,
|
|
26
|
+
children
|
|
27
|
+
};
|
|
23
28
|
}
|
|
24
29
|
function escapeText(value) {
|
|
25
|
-
|
|
30
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
26
31
|
}
|
|
27
32
|
function serialize(child) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
33
|
+
if (typeof child === "string") return escapeText(child);
|
|
34
|
+
const attrs = Object.entries(child.attrs).map(([name, value]) => ` ${name}="${value.replace(/&/g, "&").replace(/"/g, """)}"`).join("");
|
|
35
|
+
return `<${child.tag}${attrs}>${child.children.map(serialize).join("")}</${child.tag}>`;
|
|
31
36
|
}
|
|
37
|
+
// =========================
|
|
38
|
+
// WHAT IS LEFT
|
|
39
|
+
// =========================
|
|
32
40
|
function prune(el, removed) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
const children = [];
|
|
42
|
+
for (const child of el.children) {
|
|
43
|
+
if (removed.has(child)) continue;
|
|
44
|
+
if (isEl(child) && child.tag === "g") {
|
|
45
|
+
const kept = prune(child, removed);
|
|
46
|
+
// A group left with no shape, only whitespace, is gone.
|
|
47
|
+
if (kept.children.some((c) => isEl(c) ? true : c.trim() !== "")) children.push(kept);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
children.push(child);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
...el,
|
|
54
|
+
children
|
|
55
|
+
};
|
|
47
56
|
}
|
|
57
|
+
/** The ink left once the cards are out: drawable shapes and text, ignoring defs. */
|
|
48
58
|
function leftover(el) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
59
|
+
let box = null;
|
|
60
|
+
for (const child of el.children) {
|
|
61
|
+
if (!isEl(child) || child.tag === "defs" || child.tag === "title" || child.tag === "desc") continue;
|
|
62
|
+
if (child.tag === "g") {
|
|
63
|
+
const shift = translateOf(child) ?? {
|
|
64
|
+
x: 0,
|
|
65
|
+
y: 0
|
|
66
|
+
};
|
|
67
|
+
const inner = leftover(child);
|
|
68
|
+
if (inner) {
|
|
69
|
+
box = union(box, {
|
|
70
|
+
x1: inner.x1 + shift.x,
|
|
71
|
+
y1: inner.y1 + shift.y,
|
|
72
|
+
x2: inner.x2 + shift.x,
|
|
73
|
+
y2: inner.y2 + shift.y
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
box = union(box, boxOfEl(child, {
|
|
79
|
+
x: 0,
|
|
80
|
+
y: 0
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
return box;
|
|
74
84
|
}
|
|
85
|
+
/** Nothing is left to draw: the figure goes, and its title and caption stay as text above and below the pieces. */
|
|
75
86
|
function replaceWithText(plan) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
parentId: node.parentId
|
|
117
|
-
} : {}
|
|
118
|
-
}
|
|
119
|
-
});
|
|
120
|
-
}
|
|
87
|
+
const { node, taken, ops, drawing, scale, artTop } = plan;
|
|
88
|
+
ops.push({
|
|
89
|
+
kind: "removeNode",
|
|
90
|
+
nodeId: node.id
|
|
91
|
+
});
|
|
92
|
+
const accent = node.data.accent;
|
|
93
|
+
const color = Object.keys(PEN_HEX).find((c) => c === accent) ?? "ink";
|
|
94
|
+
const textWidth = Math.round(plan.artWidth);
|
|
95
|
+
const words = [[
|
|
96
|
+
String(node.data.label ?? ""),
|
|
97
|
+
NOTE_TITLE_PX,
|
|
98
|
+
node.position.y
|
|
99
|
+
], [
|
|
100
|
+
String(node.data.caption ?? ""),
|
|
101
|
+
NOTE_BODY_PX,
|
|
102
|
+
Math.round(node.position.y + artTop + (drawing.y2 - drawing.y1) * scale + CAPTION_GAP)
|
|
103
|
+
]];
|
|
104
|
+
for (const [i, [text, fontSize, y]] of words.entries()) {
|
|
105
|
+
if (!text) continue;
|
|
106
|
+
ops.push({
|
|
107
|
+
kind: "addNode",
|
|
108
|
+
node: {
|
|
109
|
+
id: freshId(`${node.id}-text-${i === 0 ? "title" : "caption"}`, taken),
|
|
110
|
+
type: "text",
|
|
111
|
+
position: {
|
|
112
|
+
x: node.position.x,
|
|
113
|
+
y
|
|
114
|
+
},
|
|
115
|
+
data: {
|
|
116
|
+
text,
|
|
117
|
+
color,
|
|
118
|
+
fontSize,
|
|
119
|
+
fixedWidth: true
|
|
120
|
+
},
|
|
121
|
+
width: textWidth,
|
|
122
|
+
height: Math.round(fontSize * 1.4),
|
|
123
|
+
...node.parentId ? { parentId: node.parentId } : {}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
121
127
|
}
|
|
128
|
+
/** The figure keeps its height, so its caption stays under the pieces; only the sides shrink. */
|
|
122
129
|
function shrinkFigure(plan, rest, ink) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
nodeId: node.id,
|
|
166
|
-
position: shift
|
|
167
|
-
});
|
|
168
|
-
}
|
|
130
|
+
const { node, drawing, scale, ops } = plan;
|
|
131
|
+
const crop = {
|
|
132
|
+
x1: Math.max(ink.x1, drawing.x1),
|
|
133
|
+
y1: drawing.y1,
|
|
134
|
+
x2: Math.min(ink.x2, drawing.x2),
|
|
135
|
+
y2: drawing.y2
|
|
136
|
+
};
|
|
137
|
+
const cropW = crop.x2 - crop.x1;
|
|
138
|
+
const cropH = crop.y2 - crop.y1;
|
|
139
|
+
const markup = sanitizeFigureSvg(serialize({
|
|
140
|
+
...rest,
|
|
141
|
+
attrs: {
|
|
142
|
+
...rest.attrs,
|
|
143
|
+
viewBox: `${round1(crop.x1)} ${round1(crop.y1)} ${round1(cropW)} ${round1(cropH)}`
|
|
144
|
+
}
|
|
145
|
+
}));
|
|
146
|
+
const width = Math.round(cropW * scale);
|
|
147
|
+
const board = node.data.board;
|
|
148
|
+
ops.push({
|
|
149
|
+
kind: "patchNode",
|
|
150
|
+
nodeId: node.id,
|
|
151
|
+
data: {
|
|
152
|
+
svg: markup ?? "",
|
|
153
|
+
...board?.w ? { board: {
|
|
154
|
+
...board,
|
|
155
|
+
w: width
|
|
156
|
+
} } : {}
|
|
157
|
+
},
|
|
158
|
+
width,
|
|
159
|
+
style: { width }
|
|
160
|
+
});
|
|
161
|
+
const shift = {
|
|
162
|
+
x: Math.round(node.position.x + (crop.x1 - drawing.x1) * scale),
|
|
163
|
+
y: Math.round(node.position.y + (crop.y1 - drawing.y1) * scale)
|
|
164
|
+
};
|
|
165
|
+
if (shift.x !== node.position.x || shift.y !== node.position.y) {
|
|
166
|
+
ops.push({
|
|
167
|
+
kind: "moveNode",
|
|
168
|
+
nodeId: node.id,
|
|
169
|
+
position: shift
|
|
170
|
+
});
|
|
171
|
+
}
|
|
169
172
|
}
|
|
173
|
+
// =========================
|
|
174
|
+
// BREAK APART
|
|
175
|
+
// =========================
|
|
176
|
+
/** Ops that swap a figure for cards, links, shapes and frames, or null with none in it. The rest stays as a smaller figure. */
|
|
170
177
|
export function breakApartFigure(input) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
178
|
+
const { node, tree } = input;
|
|
179
|
+
const drawing = viewBoxOf(tree);
|
|
180
|
+
const artWidth = input.art?.width ?? node.width;
|
|
181
|
+
if (!drawing || !artWidth) return null;
|
|
182
|
+
const pieces = findPieces(tree, drawing);
|
|
183
|
+
if (!pieces) return null;
|
|
184
|
+
const plan = startPlan(input, pieces, drawing, artWidth);
|
|
185
|
+
addFrames(plan);
|
|
186
|
+
addShapes(plan);
|
|
187
|
+
const links = linksOf(plan);
|
|
188
|
+
const linkLabels = placeBadges(plan, links);
|
|
189
|
+
addCards(plan);
|
|
190
|
+
addEdges(plan, links, linkLabels);
|
|
191
|
+
const counts = {
|
|
192
|
+
cards: plan.cards.length,
|
|
193
|
+
edges: links.length,
|
|
194
|
+
shapes: plan.shapes,
|
|
195
|
+
frames: plan.loose.frames.length
|
|
196
|
+
};
|
|
197
|
+
const rest = prune(tree, plan.removed);
|
|
198
|
+
const ink = leftover(rest);
|
|
199
|
+
if (!ink) {
|
|
200
|
+
replaceWithText(plan);
|
|
201
|
+
return {
|
|
202
|
+
ops: plan.ops,
|
|
203
|
+
...counts,
|
|
204
|
+
remains: false
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
shrinkFigure(plan, rest, ink);
|
|
208
|
+
return {
|
|
209
|
+
ops: plan.ops,
|
|
210
|
+
...counts,
|
|
211
|
+
remains: true
|
|
212
|
+
};
|
|
206
213
|
}
|