@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,324 +1,331 @@
|
|
|
1
|
+
// Break apart, the reading half: which cards, shapes, frames and step numbers a figure holds.
|
|
1
2
|
import { areaOf, boxOfEl, centerOf, contains, fontSize, hasText, isBold, isEl, num, plus, solid, strokeEnds, textLines, translateOf, visible, within } from "./figure-geometry.js";
|
|
3
|
+
/** A group with more text than this is a panel, not a card. */
|
|
2
4
|
const MAX_CARD_TEXTS = 8;
|
|
3
|
-
|
|
5
|
+
/** Smaller than this share of the drawing, or it is a panel. */
|
|
6
|
+
const MAX_CARD_AREA = .4;
|
|
7
|
+
/** How far an arrow end may sit from a card edge and still land on it, in drawing units. */
|
|
4
8
|
export const LAND_TOLERANCE = 8;
|
|
5
9
|
export const ARROWHEAD_MAX = 20;
|
|
10
|
+
/** A dot's number is at most this long. */
|
|
6
11
|
const BADGE_MAX_CHARS = 2;
|
|
7
12
|
const BADGE_MAX_R = 30;
|
|
13
|
+
/** Nothing under this on a side is a card or a shape: a rule or a speck. */
|
|
8
14
|
const MIN_SIDE = 4;
|
|
9
15
|
const ORIGIN = {
|
|
10
|
-
|
|
11
|
-
|
|
16
|
+
x: 0,
|
|
17
|
+
y: 0
|
|
12
18
|
};
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
// =========================
|
|
20
|
+
// CARDS
|
|
21
|
+
// =========================
|
|
22
|
+
const anchorOf = (el, offset) => ({
|
|
23
|
+
x: num(el.attrs.x) + offset.x,
|
|
24
|
+
y: num(el.attrs.y) + offset.y
|
|
25
|
+
});
|
|
26
|
+
/** A solid rect that has text standing inside it. */
|
|
17
27
|
function holdsText(rect, texts, offset) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
28
|
+
if (rect.tag !== "rect" || !visible(rect) || rect.attrs.fill === "none") return null;
|
|
29
|
+
const box = boxOfEl(rect, offset);
|
|
30
|
+
if (!box) return null;
|
|
31
|
+
return texts.some((t) => contains(box, anchorOf(t, offset), 3)) ? box : null;
|
|
22
32
|
}
|
|
33
|
+
/** A number in a dot is a pin marker, not copy: the ring that stays in the figure carries it. */
|
|
23
34
|
function splitBadge(inside, kids) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
let badge;
|
|
36
|
+
const words = [];
|
|
37
|
+
const badgeParts = [];
|
|
38
|
+
for (const t of inside) {
|
|
39
|
+
const line = textLines(t).join(" ");
|
|
40
|
+
const dot = kids.find((k) => k.tag === "circle" && Math.hypot(num(k.attrs.cx) - num(t.attrs.x), num(k.attrs.cy) - num(t.attrs.y)) <= num(k.attrs.r) + 2);
|
|
41
|
+
if (dot && line.length <= 2) {
|
|
42
|
+
badge = line;
|
|
43
|
+
badgeParts.push(dot, t);
|
|
44
|
+
} else words.push(t);
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
badge,
|
|
48
|
+
words,
|
|
49
|
+
badgeParts
|
|
50
|
+
};
|
|
40
51
|
}
|
|
52
|
+
/** The title is the top bold line, not an end-pinned tag or a big count further down. */
|
|
41
53
|
function headlineOf(ordered) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
tag,
|
|
58
|
-
bodyEls
|
|
59
|
-
};
|
|
54
|
+
const titleEl = ordered.find((t) => isBold(t) && t.attrs["text-anchor"] !== "end") ?? [...ordered].sort((a, b) => Number(isBold(b)) - Number(isBold(a)) || fontSize(b) - fontSize(a))[0];
|
|
55
|
+
const tag = ordered.find((t) => t !== titleEl && t.attrs["text-anchor"] === "end" && Math.abs(num(t.attrs.y) - num(titleEl.attrs.y)) < 8);
|
|
56
|
+
// A title set over two lines (CLAUDE, then CODE) is one title: the same face, directly after.
|
|
57
|
+
const titleEls = [titleEl];
|
|
58
|
+
const sameFace = (t) => fontSize(t) === fontSize(titleEl) && isBold(t) === isBold(titleEl) && t.attrs["text-anchor"] === titleEl.attrs["text-anchor"];
|
|
59
|
+
for (const t of ordered.slice(ordered.indexOf(titleEl) + 1)) {
|
|
60
|
+
if (t === tag || !sameFace(t)) break;
|
|
61
|
+
titleEls.push(t);
|
|
62
|
+
}
|
|
63
|
+
const bodyEls = ordered.filter((t) => !titleEls.includes(t) && t !== tag);
|
|
64
|
+
return {
|
|
65
|
+
titleEls,
|
|
66
|
+
tag,
|
|
67
|
+
bodyEls
|
|
68
|
+
};
|
|
60
69
|
}
|
|
70
|
+
/** Same-line fragments read as one line, left to right. */
|
|
61
71
|
function bodyLines(bodyEls) {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
const body = [];
|
|
73
|
+
let lastY = Number.NaN;
|
|
74
|
+
for (const t of bodyEls) {
|
|
75
|
+
const text = textLines(t).join(" ");
|
|
76
|
+
if (Math.abs(num(t.attrs.y) - lastY) < 3 && body.length > 0) body[body.length - 1] += ` ${text}`;
|
|
77
|
+
else body.push(text);
|
|
78
|
+
lastY = num(t.attrs.y);
|
|
79
|
+
}
|
|
80
|
+
return body;
|
|
71
81
|
}
|
|
82
|
+
/** Anything else drawn inside the card's own rect is its chrome and goes with it. */
|
|
72
83
|
function chromeOf(req, claimed) {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
const { kids, box, offset, taken } = req;
|
|
85
|
+
return kids.filter((k) => {
|
|
86
|
+
if (claimed.has(k) || taken.has(k) || k.attrs.fill === "none") return false;
|
|
87
|
+
const kb = boxOfEl(k, offset);
|
|
88
|
+
return kb !== null && within(kb, box);
|
|
89
|
+
});
|
|
79
90
|
}
|
|
80
|
-
const readingOrder = (a, b)=>num(a.attrs.y) - num(b.attrs.y) || num(a.attrs.x) - num(b.attrs.x);
|
|
91
|
+
const readingOrder = (a, b) => num(a.attrs.y) - num(b.attrs.y) || num(a.attrs.x) - num(b.attrs.x);
|
|
92
|
+
/** One card: `rect` and whatever of its group's other children stands inside it. */
|
|
81
93
|
function cardFromRect(req) {
|
|
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
|
-
titleSize: fontSize(titleEls[0]),
|
|
108
|
-
kicker: [
|
|
109
|
-
badge,
|
|
110
|
-
tag ? textLines(tag).join(" ") : undefined
|
|
111
|
-
].filter(Boolean).join(" · ") || undefined,
|
|
112
|
-
body: bodyLines(bodyEls)
|
|
113
|
-
};
|
|
94
|
+
const { group, kids, rect, box, offset, taken } = req;
|
|
95
|
+
const inside = kids.filter((k) => hasText(k) && !taken.has(k) && contains(box, anchorOf(k, offset), 3));
|
|
96
|
+
if (inside.length === 0 || inside.length > MAX_CARD_TEXTS) return null;
|
|
97
|
+
const { badge, words, badgeParts } = splitBadge(inside, kids);
|
|
98
|
+
if (words.length === 0) return null;
|
|
99
|
+
const { titleEls, tag, bodyEls } = headlineOf([...words].sort(readingOrder));
|
|
100
|
+
const claimed = new Set([
|
|
101
|
+
rect,
|
|
102
|
+
...badgeParts,
|
|
103
|
+
...titleEls,
|
|
104
|
+
...tag ? [tag] : [],
|
|
105
|
+
...bodyEls
|
|
106
|
+
]);
|
|
107
|
+
for (const k of chromeOf(req, claimed)) claimed.add(k);
|
|
108
|
+
return {
|
|
109
|
+
group,
|
|
110
|
+
offset,
|
|
111
|
+
rect,
|
|
112
|
+
box,
|
|
113
|
+
claimed,
|
|
114
|
+
title: titleEls.map((t) => textLines(t).join(" ")).join(" "),
|
|
115
|
+
titleSize: fontSize(titleEls[0]),
|
|
116
|
+
kicker: [badge, tag ? textLines(tag).join(" ") : undefined].filter(Boolean).join(" · ") || undefined,
|
|
117
|
+
body: bodyLines(bodyEls)
|
|
118
|
+
};
|
|
114
119
|
}
|
|
120
|
+
/** A `data-pins` group holds a pin per card rect. */
|
|
115
121
|
function readPinCards({ group, kids, texts, offset }) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
122
|
+
const taken = new Set();
|
|
123
|
+
const out = [];
|
|
124
|
+
for (const rect of kids) {
|
|
125
|
+
const box = holdsText(rect, texts, offset);
|
|
126
|
+
const found = box && cardFromRect({
|
|
127
|
+
group,
|
|
128
|
+
kids,
|
|
129
|
+
rect,
|
|
130
|
+
box,
|
|
131
|
+
offset,
|
|
132
|
+
taken
|
|
133
|
+
});
|
|
134
|
+
if (!found) continue;
|
|
135
|
+
for (const part of found.claimed) taken.add(part);
|
|
136
|
+
out.push(found);
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
133
139
|
}
|
|
140
|
+
/** Any other group is one card, unless it is a panel: too much text, a subgroup or too much of the drawing. */
|
|
134
141
|
function readOneCard({ group, kids, texts, offset }, drawing) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
152
|
-
return [];
|
|
142
|
+
if (texts.length > MAX_CARD_TEXTS || kids.some((k) => k.tag === "g")) return [];
|
|
143
|
+
for (const rect of kids) {
|
|
144
|
+
const box = holdsText(rect, texts, offset);
|
|
145
|
+
if (!box) continue;
|
|
146
|
+
if (areaOf(box) > areaOf(drawing) * MAX_CARD_AREA) return [];
|
|
147
|
+
const found = cardFromRect({
|
|
148
|
+
group,
|
|
149
|
+
kids,
|
|
150
|
+
rect,
|
|
151
|
+
box,
|
|
152
|
+
offset,
|
|
153
|
+
taken: new Set()
|
|
154
|
+
});
|
|
155
|
+
return found ? [found] : [];
|
|
156
|
+
}
|
|
157
|
+
return [];
|
|
153
158
|
}
|
|
159
|
+
/** Cards in one group. */
|
|
154
160
|
function readCards(group, offset, drawing) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
161
|
+
const kids = group.children.filter(isEl);
|
|
162
|
+
const texts = kids.filter(hasText);
|
|
163
|
+
if (texts.length === 0) return [];
|
|
164
|
+
const read = {
|
|
165
|
+
group,
|
|
166
|
+
kids,
|
|
167
|
+
texts,
|
|
168
|
+
offset
|
|
169
|
+
};
|
|
170
|
+
return "data-pins" in group.attrs ? readPinCards(read) : readOneCard(read, drawing);
|
|
165
171
|
}
|
|
166
172
|
export function findCards(el, offset, drawing, out) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
for (const child of el.children) {
|
|
174
|
+
if (!isEl(child) || child.tag !== "g") continue;
|
|
175
|
+
const shift = translateOf(child);
|
|
176
|
+
if (!shift) continue;
|
|
177
|
+
const at = plus(offset, shift);
|
|
178
|
+
const found = readCards(child, at, drawing);
|
|
179
|
+
if (found.length > 0) out.push(...found);
|
|
180
|
+
else findCards(child, at, drawing, out);
|
|
181
|
+
}
|
|
176
182
|
}
|
|
183
|
+
// =========================
|
|
184
|
+
// LOOSE SHAPES
|
|
185
|
+
// =========================
|
|
177
186
|
function flatten(container, offset, pins, out) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
187
|
+
const kids = container.children.filter(isEl);
|
|
188
|
+
for (const el of kids) {
|
|
189
|
+
if (el.tag === "g") {
|
|
190
|
+
const shift = translateOf(el);
|
|
191
|
+
if (shift) flatten(el, plus(offset, shift), pins || "data-pins" in el.attrs, out);
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (el.tag === "defs" || el.tag === "title" || el.tag === "desc") continue;
|
|
195
|
+
out.push({
|
|
196
|
+
el,
|
|
197
|
+
box: boxOfEl(el, offset),
|
|
198
|
+
offset,
|
|
199
|
+
kids,
|
|
200
|
+
container,
|
|
201
|
+
pins
|
|
202
|
+
});
|
|
203
|
+
}
|
|
195
204
|
}
|
|
205
|
+
/** Whether a text sits on a dot and is short enough to be its number. */
|
|
196
206
|
function numbersDot(a, center, r) {
|
|
197
|
-
|
|
198
|
-
|
|
207
|
+
const at = anchorOf(a.el, a.offset);
|
|
208
|
+
return textLines(a.el).join(" ").length <= BADGE_MAX_CHARS && Math.hypot(at.x - center.x, at.y - center.y) <= r + 2;
|
|
199
209
|
}
|
|
210
|
+
/** A number in a dot is a step number. */
|
|
200
211
|
function findBadges(free, texts) {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
212
|
+
const parts = new Set();
|
|
213
|
+
const badges = [];
|
|
214
|
+
for (const c of free.filter((a) => a.el.tag === "circle")) {
|
|
215
|
+
const r = num(c.el.attrs.r);
|
|
216
|
+
if (!c.box || r <= 0 || r > BADGE_MAX_R) continue;
|
|
217
|
+
const center = centerOf(c.box);
|
|
218
|
+
const t = texts.find((a) => !parts.has(a.el) && numbersDot(a, center, r));
|
|
219
|
+
if (!t) continue;
|
|
220
|
+
parts.add(c.el);
|
|
221
|
+
parts.add(t.el);
|
|
222
|
+
badges.push({
|
|
223
|
+
circle: c.el,
|
|
224
|
+
text: t.el,
|
|
225
|
+
center,
|
|
226
|
+
r,
|
|
227
|
+
label: textLines(t.el).join(" ")
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
badges,
|
|
232
|
+
parts
|
|
233
|
+
};
|
|
223
234
|
}
|
|
224
|
-
const isTip = (box)=>box !== null && box.x2 - box.x1 <= ARROWHEAD_MAX && box.y2 - box.y1 <= ARROWHEAD_MAX;
|
|
235
|
+
const isTip = (box) => box !== null && box.x2 - box.x1 <= ARROWHEAD_MAX && box.y2 - box.y1 <= ARROWHEAD_MAX;
|
|
236
|
+
/** A line that ends on two items joins them, and a small filled tip beside it is its arrowhead. */
|
|
225
237
|
function isConnector(a, itemBoxes) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
238
|
+
if (![
|
|
239
|
+
"line",
|
|
240
|
+
"polyline",
|
|
241
|
+
"path"
|
|
242
|
+
].includes(a.el.tag)) return false;
|
|
243
|
+
if (solid(a.el.attrs.fill)) return isTip(a.box);
|
|
244
|
+
const ends = strokeEnds(a.el, a.offset);
|
|
245
|
+
return ends !== null && ends.every((p) => itemBoxes.some((b) => contains(b, p, LAND_TOLERANCE)));
|
|
234
246
|
}
|
|
247
|
+
/** A rect with no rect inside it: a bare shape, or a card when it holds its own text. */
|
|
235
248
|
function leafRect(sweep, x, hasWords, out) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
249
|
+
if (!hasWords) {
|
|
250
|
+
out.shapes.push({
|
|
251
|
+
el: x.el,
|
|
252
|
+
box: x.box
|
|
253
|
+
});
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
if (areaOf(x.box) > areaOf(sweep.drawing) * MAX_CARD_AREA) return;
|
|
257
|
+
const found = cardFromRect({
|
|
258
|
+
group: x.container,
|
|
259
|
+
kids: x.kids,
|
|
260
|
+
rect: x.el,
|
|
261
|
+
box: x.box,
|
|
262
|
+
offset: x.offset,
|
|
263
|
+
taken: sweep.taken
|
|
264
|
+
});
|
|
265
|
+
if (!found) return;
|
|
266
|
+
for (const part of found.claimed) sweep.taken.add(part);
|
|
267
|
+
out.cards.push(found);
|
|
255
268
|
}
|
|
269
|
+
/** A rect round other items is a panel, a frame or a shape, by what it holds. */
|
|
256
270
|
function classifyRect(sweep, x, dropped, out) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
el: x.el,
|
|
281
|
-
box
|
|
282
|
-
});
|
|
271
|
+
const { box } = x;
|
|
272
|
+
const inner = sweep.rects.filter((o) => o !== x && !dropped.has(o) && within(o.box, box, 1) && areaOf(o.box) < areaOf(box));
|
|
273
|
+
const innerBoxes = [...inner.map((o) => o.box), ...sweep.groupCards.filter((c) => within(c.box, box, 1)).map((c) => c.box)];
|
|
274
|
+
const held = (point) => innerBoxes.some((b) => contains(b, point, 3)) ? false : contains(box, point, 3);
|
|
275
|
+
const own = sweep.words.filter((w) => held(anchorOf(w.el, w.offset)));
|
|
276
|
+
if (innerBoxes.length === 0) {
|
|
277
|
+
leafRect(sweep, x, own.length > 0, out);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const stray = sweep.strays.some(({ box: sb }) => sb !== null && within(sb, box, 1) && !innerBoxes.some((b) => within(sb, b, 1)));
|
|
281
|
+
if (own.length > 0 || stray) {
|
|
282
|
+
out.panels.push(box);
|
|
283
|
+
for (const o of inner) dropped.add(o);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (innerBoxes.length >= 2) out.frames.push({
|
|
287
|
+
el: x.el,
|
|
288
|
+
box
|
|
289
|
+
});
|
|
290
|
+
else out.shapes.push({
|
|
291
|
+
el: x.el,
|
|
292
|
+
box
|
|
293
|
+
});
|
|
283
294
|
}
|
|
284
295
|
function sweepRects(sweep, out) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
296
|
+
const dropped = new Set();
|
|
297
|
+
for (const x of sweep.rects) {
|
|
298
|
+
if (!dropped.has(x)) classifyRect(sweep, x, dropped, out);
|
|
299
|
+
}
|
|
289
300
|
}
|
|
301
|
+
/** Loose cards, bare rectangles, frames and step numbers. A rectangle with its own text and shapes inside is a panel, so its chrome stays. */
|
|
290
302
|
export function findLoose(tree, drawing, taken, groupCards) {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
taken
|
|
320
|
-
}, out);
|
|
321
|
-
out.badges = out.badges.filter((b)=>!taken.has(b.circle) && !taken.has(b.text));
|
|
322
|
-
out.badges = out.badges.filter((b)=>!out.panels.some((p)=>contains(p, b.center, 0)));
|
|
323
|
-
return out;
|
|
303
|
+
const atoms = [];
|
|
304
|
+
flatten(tree, ORIGIN, false, atoms);
|
|
305
|
+
const free = atoms.filter((a) => !a.pins && a.box && !taken.has(a.el));
|
|
306
|
+
const texts = free.filter((a) => hasText(a.el));
|
|
307
|
+
const { badges, parts } = findBadges(free, texts);
|
|
308
|
+
const rects = free.filter((a) => a.el.tag === "rect" && visible(a.el) && a.box !== null).filter((a) => a.box.x2 - a.box.x1 >= MIN_SIDE && a.box.y2 - a.box.y1 >= MIN_SIDE).sort((a, b) => areaOf(b.box) - areaOf(a.box));
|
|
309
|
+
const itemBoxes = [...rects.map((a) => a.box), ...groupCards.map((c) => c.box)];
|
|
310
|
+
const strays = free.filter((a) => !["rect", "text"].includes(a.el.tag) && !parts.has(a.el) && !isConnector(a, itemBoxes));
|
|
311
|
+
const out = {
|
|
312
|
+
cards: [],
|
|
313
|
+
shapes: [],
|
|
314
|
+
frames: [],
|
|
315
|
+
badges,
|
|
316
|
+
panels: []
|
|
317
|
+
};
|
|
318
|
+
const words = texts.filter((a) => !parts.has(a.el));
|
|
319
|
+
sweepRects({
|
|
320
|
+
rects,
|
|
321
|
+
words,
|
|
322
|
+
strays,
|
|
323
|
+
groupCards,
|
|
324
|
+
drawing,
|
|
325
|
+
taken
|
|
326
|
+
}, out);
|
|
327
|
+
// A number a card already took is not a mark of its own.
|
|
328
|
+
out.badges = out.badges.filter((b) => !taken.has(b.circle) && !taken.has(b.text));
|
|
329
|
+
out.badges = out.badges.filter((b) => !out.panels.some((p) => contains(p, b.center, 0)));
|
|
330
|
+
return out;
|
|
324
331
|
}
|