@1agh/maude 1.4.5 → 1.5.0

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.
Files changed (106) hide show
  1. package/apps/studio/annotations/ai-read.ts +288 -0
  2. package/apps/studio/annotations/ai-write.ts +533 -0
  3. package/apps/studio/annotations/board-io.ts +94 -0
  4. package/apps/studio/annotations/board-text.ts +45 -0
  5. package/apps/studio/annotations/constants.ts +58 -0
  6. package/apps/studio/annotations/elements/_shared.ts +125 -0
  7. package/apps/studio/annotations/elements/arrow.model.ts +196 -0
  8. package/apps/studio/annotations/elements/media.model.ts +79 -0
  9. package/apps/studio/annotations/elements/pen.model.ts +82 -0
  10. package/apps/studio/annotations/elements/section.model.ts +70 -0
  11. package/apps/studio/annotations/elements/shape.model.ts +141 -0
  12. package/apps/studio/annotations/elements/sticky.model.ts +48 -0
  13. package/apps/studio/annotations/elements/text.model.ts +52 -0
  14. package/apps/studio/annotations/fields.ts +347 -0
  15. package/apps/studio/annotations/fractional-index.ts +234 -0
  16. package/apps/studio/annotations/legacy/mini-dom.ts +207 -0
  17. package/apps/studio/annotations/migrate-boot.ts +172 -0
  18. package/apps/studio/annotations/migrate-cli.ts +37 -0
  19. package/apps/studio/annotations/migrate-v1.ts +383 -0
  20. package/apps/studio/annotations/ops.ts +474 -0
  21. package/apps/studio/annotations/registry.ts +159 -0
  22. package/apps/studio/annotations/replica.ts +264 -0
  23. package/apps/studio/annotations/scene.ts +235 -0
  24. package/apps/studio/annotations/schema.ts +188 -0
  25. package/apps/studio/annotations/types.ts +77 -0
  26. package/apps/studio/annotations/ui/board.ts +126 -0
  27. package/apps/studio/annotations/ui/containment.ts +118 -0
  28. package/apps/studio/annotations/ui/edit-actions.ts +551 -0
  29. package/apps/studio/annotations/ui/editor-channel.ts +62 -0
  30. package/apps/studio/annotations/ui/element-node.tsx +993 -0
  31. package/apps/studio/annotations/ui/pipeline-context.ts +17 -0
  32. package/apps/studio/annotations/ui/pointer-pipeline.ts +150 -0
  33. package/apps/studio/annotations/ui/render-model.ts +264 -0
  34. package/apps/studio/annotations/ui/scene.tsx +63 -0
  35. package/apps/studio/annotations/ui/text-editor.tsx +420 -0
  36. package/apps/studio/annotations/ui/text-session.ts +140 -0
  37. package/apps/studio/annotations/ui/text-style.ts +111 -0
  38. package/apps/studio/annotations/ui/world.ts +47 -0
  39. package/apps/studio/annotations/v1-adapter.ts +428 -0
  40. package/apps/studio/annotations-align.ts +21 -6
  41. package/apps/studio/annotations-bindings.ts +2 -0
  42. package/apps/studio/annotations-context-toolbar.tsx +9 -13
  43. package/apps/studio/annotations-groups.ts +3 -0
  44. package/apps/studio/annotations-layer.tsx +923 -1925
  45. package/apps/studio/annotations-model.ts +97 -4
  46. package/apps/studio/annotations-sync.ts +4 -47
  47. package/apps/studio/api.ts +262 -68
  48. package/apps/studio/bin/_import-figma.mjs +22 -12
  49. package/apps/studio/bin/annotate.mjs +331 -838
  50. package/apps/studio/bin/annotate.sh +4 -4
  51. package/apps/studio/bin/perf.sh +21 -7
  52. package/apps/studio/bin/read-annotations.mjs +184 -666
  53. package/apps/studio/bin/read-annotations.sh +9 -5
  54. package/apps/studio/canvas-artifacts.ts +9 -0
  55. package/apps/studio/canvas-comment-mount.tsx +35 -2
  56. package/apps/studio/canvas-lib.tsx +18 -1
  57. package/apps/studio/canvas-shell.tsx +54 -1
  58. package/apps/studio/client/app.jsx +101 -35
  59. package/apps/studio/client/comments-overlay.css +10 -0
  60. package/apps/studio/client/hmr.mjs +1 -1
  61. package/apps/studio/client/panels/git-grouping.js +2 -2
  62. package/apps/studio/client/tree-expansion.js +217 -0
  63. package/apps/studio/collab/index.ts +58 -5
  64. package/apps/studio/collab/persistence.ts +80 -11
  65. package/apps/studio/collab/registry.ts +63 -26
  66. package/apps/studio/commands/annotation-ops-command.ts +72 -0
  67. package/apps/studio/comment-anchor.ts +116 -0
  68. package/apps/studio/comments-overlay.tsx +78 -35
  69. package/apps/studio/context.ts +1 -0
  70. package/apps/studio/cursors-overlay.tsx +337 -109
  71. package/apps/studio/dist/client.bundle.js +850 -850
  72. package/apps/studio/dist/comment-mount.js +2 -2
  73. package/apps/studio/figma/to-strokes.ts +31 -10
  74. package/apps/studio/git/endpoints.ts +1 -1
  75. package/apps/studio/git/service.ts +1 -1
  76. package/apps/studio/git/watch.ts +1 -1
  77. package/apps/studio/http.ts +90 -15
  78. package/apps/studio/server.ts +16 -0
  79. package/apps/studio/sync/accepted-cold-start.ts +72 -8
  80. package/apps/studio/sync/agent.ts +36 -6
  81. package/apps/studio/sync/codec.ts +95 -40
  82. package/apps/studio/sync/comment-ledger.ts +229 -0
  83. package/apps/studio/sync/file-membership.ts +12 -2
  84. package/apps/studio/sync/file-plane.ts +78 -2
  85. package/apps/studio/sync/index.ts +75 -16
  86. package/apps/studio/sync/journal-client.ts +5 -0
  87. package/apps/studio/sync/limits.ts +7 -2
  88. package/apps/studio/sync/migrate-seed.ts +17 -7
  89. package/apps/studio/sync/projection.ts +7 -2
  90. package/apps/studio/sync/remote-docs.ts +34 -0
  91. package/apps/studio/sync/writer-registry.ts +10 -0
  92. package/apps/studio/text-caret.ts +11 -2
  93. package/apps/studio/tree-state.ts +45 -0
  94. package/apps/studio/undo-stack.ts +2 -2
  95. package/apps/studio/use-annotation-resize.tsx +48 -23
  96. package/apps/studio/use-annotation-selection.tsx +9 -2
  97. package/apps/studio/use-collab.tsx +104 -1
  98. package/apps/studio/use-selection-set.tsx +4 -0
  99. package/apps/studio/whats-new.json +63 -0
  100. package/cli/lib/design-link.mjs +5 -1
  101. package/cli/lib/gitignore-block.mjs +1 -1
  102. package/cli/lib/gitignore-drift.mjs +2 -1
  103. package/package.json +9 -8
  104. package/plugins/design/templates/brief-board.tsx.template +1 -1
  105. package/apps/studio/annotation-edit-base.ts +0 -36
  106. package/apps/studio/commands/annotation-strokes-command.ts +0 -137
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @file annotations/ui/pipeline-context.ts — the layer's pointer pipeline, for its chrome
3
+ * @scope apps/studio/annotations/ui/pipeline-context.ts
4
+ * @purpose Components the annotation layer renders (the resize overlay's
5
+ * handles) start their gestures on the layer's one pipeline
6
+ * (pointer-pipeline.ts) instead of adding document listeners of
7
+ * their own.
8
+ */
9
+
10
+ import { createContext, useContext } from 'react';
11
+ import type { PointerPipeline } from './pointer-pipeline.ts';
12
+
13
+ export const AnnotationPipelineContext = createContext<PointerPipeline | null>(null);
14
+
15
+ export function useAnnotationPipeline(): PointerPipeline | null {
16
+ return useContext(AnnotationPipelineContext);
17
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * @file annotations/ui/pointer-pipeline.ts — one pointer pipeline for the annotation layer
3
+ * @scope apps/studio/annotations/ui/pointer-pipeline.ts
4
+ * @purpose DDR-242 AD8 (Task 21). The layer used to install a document
5
+ * capture listener per interaction (select/drag/marquee, group
6
+ * resize, connector dots, right-click…), each with its own
7
+ * per-gesture pointermove/up listeners and its own copy of the
8
+ * "is this chrome?" skip-list. Which one won depended on the
9
+ * order effects happened to register, and a skip-list that missed
10
+ * one class turned a handle drag into a marquee (ce641b18) or an
11
+ * artboard drag into a lost selection (3db83a8a).
12
+ *
13
+ * Here there is ONE set of document listeners. Stages are tried
14
+ * in explicit priority order (lowest first); the first to claim a
15
+ * pointerdown owns the gesture, and only that gesture sees the
16
+ * following moves and the release. `state` is the tool state
17
+ * machine's current node: `idle`, or the kind of the gesture in
18
+ * flight (`dragging`, `marquee`, `resizing`, `connecting`, …).
19
+ *
20
+ * Pure DOM, React-free: the layer registers stages from effects.
21
+ */
22
+
23
+ export interface Gesture {
24
+ /** State-machine node while this gesture runs (`dragging`, `marquee`, …). */
25
+ kind: string;
26
+ move?(e: PointerEvent): void;
27
+ up?(e: PointerEvent): void;
28
+ /** Pointercancel, or a new pointerdown arriving before the release. */
29
+ cancel?(e: PointerEvent | null): void;
30
+ }
31
+
32
+ /**
33
+ * What a stage answers to a pointerdown:
34
+ * - a Gesture: claimed, the stage owns the moves and the release;
35
+ * - 'pass': claimed for someone else — the target's own handlers (a resize
36
+ * handle, a toolbar button, a text editor) run; no later stage does;
37
+ * - undefined / false: not mine, try the next stage.
38
+ */
39
+ export type Claim = Gesture | 'pass' | undefined | false | void;
40
+
41
+ export interface Stage {
42
+ /** Lower runs first. */
43
+ priority: number;
44
+ name: string;
45
+ down(e: PointerEvent): Claim;
46
+ }
47
+
48
+ export interface PipelineTrace {
49
+ stage: string;
50
+ claim: 'gesture' | 'pass';
51
+ kind?: string;
52
+ }
53
+
54
+ export class PointerPipeline {
55
+ private stages: Stage[] = [];
56
+ private active: { gesture: Gesture; pointerId: number } | null = null;
57
+ private listeners: Array<[string, EventListener]> = [];
58
+ private doc: Document | null = null;
59
+ /** Last claim, for tooling and tests. */
60
+ lastClaim: PipelineTrace | null = null;
61
+ onState: ((state: string) => void) | null = null;
62
+
63
+ get state(): string {
64
+ return this.active?.gesture.kind ?? 'idle';
65
+ }
66
+
67
+ /** Register a stage; returns its unregister function. */
68
+ add(stage: Stage): () => void {
69
+ this.stages = [...this.stages, stage].sort((a, b) => a.priority - b.priority);
70
+ return () => {
71
+ this.stages = this.stages.filter((s) => s !== stage);
72
+ };
73
+ }
74
+
75
+ attach(doc: Document): () => void {
76
+ this.detach();
77
+ this.doc = doc;
78
+ const on = (type: string, fn: (e: PointerEvent) => void) => {
79
+ const l = fn as unknown as EventListener;
80
+ doc.addEventListener(type, l, true);
81
+ this.listeners.push([type, l]);
82
+ };
83
+ on('pointerdown', (e) => this.down(e));
84
+ on('pointermove', (e) => this.move(e));
85
+ on('pointerup', (e) => this.up(e));
86
+ on('pointercancel', (e) => this.cancel(e));
87
+ return () => this.detach();
88
+ }
89
+
90
+ detach(): void {
91
+ if (this.doc) {
92
+ for (const [type, l] of this.listeners) this.doc.removeEventListener(type, l, true);
93
+ }
94
+ this.listeners = [];
95
+ this.doc = null;
96
+ this.end(null);
97
+ }
98
+
99
+ /** Start a gesture from outside a stage (a stage that claimed via 'pass' but still wants moves). */
100
+ begin(pointerId: number, gesture: Gesture): void {
101
+ this.end(null);
102
+ this.active = { gesture, pointerId };
103
+ this.onState?.(gesture.kind);
104
+ }
105
+
106
+ private end(e: PointerEvent | null): void {
107
+ const a = this.active;
108
+ if (!a) return;
109
+ this.active = null;
110
+ a.gesture.cancel?.(e);
111
+ this.onState?.('idle');
112
+ }
113
+
114
+ private down(e: PointerEvent): void {
115
+ // A second pointer (or a lost release) ends whatever was running.
116
+ if (this.active) this.end(e);
117
+ for (const stage of this.stages) {
118
+ const claim = stage.down(e);
119
+ if (!claim) continue;
120
+ if (claim === 'pass') {
121
+ this.lastClaim = { stage: stage.name, claim: 'pass' };
122
+ return;
123
+ }
124
+ this.lastClaim = { stage: stage.name, claim: 'gesture', kind: claim.kind };
125
+ this.active = { gesture: claim, pointerId: e.pointerId };
126
+ this.onState?.(claim.kind);
127
+ return;
128
+ }
129
+ this.lastClaim = null;
130
+ }
131
+
132
+ private move(e: PointerEvent): void {
133
+ const a = this.active;
134
+ if (a && e.pointerId === a.pointerId) a.gesture.move?.(e);
135
+ }
136
+
137
+ private up(e: PointerEvent): void {
138
+ const a = this.active;
139
+ if (!a || e.pointerId !== a.pointerId) return;
140
+ this.active = null;
141
+ a.gesture.up?.(e);
142
+ this.onState?.('idle');
143
+ }
144
+
145
+ private cancel(e: PointerEvent): void {
146
+ const a = this.active;
147
+ if (!a || e.pointerId !== a.pointerId) return;
148
+ this.end(e);
149
+ }
150
+ }
@@ -0,0 +1,264 @@
1
+ /**
2
+ * @file annotations/ui/render-model.ts — what the scene draws, per element
3
+ * @scope apps/studio/annotations/ui/render-model.ts
4
+ * @purpose The scene (element-node.tsx) draws v2 element records in WORLD
5
+ * space, one DOM node per element, in paint order. A `RenderItem`
6
+ * is that record plus what the scene resolved for it (arrow
7
+ * endpoints).
8
+ *
9
+ * `renderItemsFromStrokes` is the TRANSITIONAL source while the
10
+ * layer still edits v1 `Stroke[]` (removed with the v1 adapter,
11
+ * Task 26). It maps each stroke straight to a view record WITHOUT
12
+ * the validator: the UI also draws states that are never persisted
13
+ * (an image whose upload is still running carries a `blob:` href
14
+ * the validator would drop, making the picture blink out). One
15
+ * record per stroke object, cached, so an unchanged stroke keeps
16
+ * its record identity and its node skips re-rendering.
17
+ */
18
+
19
+ import type { Stroke } from '../../annotations-model.ts';
20
+ import type { AnnotationElement } from '../types.ts';
21
+
22
+ export interface ArrowAnchor {
23
+ el: string;
24
+ nx: number;
25
+ ny: number;
26
+ }
27
+
28
+ /** World endpoints of an arrow plus the magnet each bound end sits on. */
29
+ export interface ResolvedEnds {
30
+ x1: number;
31
+ y1: number;
32
+ x2: number;
33
+ y2: number;
34
+ startAnchor?: ArrowAnchor;
35
+ endAnchor?: ArrowAnchor;
36
+ }
37
+
38
+ export interface RenderItem {
39
+ /** World-space record (no `parent`: containment is already resolved). */
40
+ el: AnnotationElement;
41
+ ends?: ResolvedEnds;
42
+ }
43
+
44
+ type Label = Extract<Stroke, { tool: 'text' }>;
45
+
46
+ function fmt(s: {
47
+ bold?: boolean;
48
+ italic?: boolean;
49
+ strike?: boolean;
50
+ underline?: boolean;
51
+ listType?: string;
52
+ align?: string;
53
+ }): Record<string, unknown> {
54
+ const out: Record<string, unknown> = {};
55
+ if (s.bold) out.bold = true;
56
+ if (s.italic) out.italic = true;
57
+ if (s.strike) out.strike = true;
58
+ if (s.underline) out.underline = true;
59
+ if (s.listType) out.list = s.listType;
60
+ if (s.align) out.align = s.align;
61
+ return out;
62
+ }
63
+
64
+ function author(s: Stroke): Record<string, unknown> {
65
+ if (s.author === 'ai') return { author: { kind: 'ai' } };
66
+ if (s.authorName) return { author: { kind: 'human', name: s.authorName } };
67
+ return {};
68
+ }
69
+
70
+ function box(x: number, y: number, w: number, h: number) {
71
+ return {
72
+ x: Math.min(x, x + w),
73
+ y: Math.min(y, y + h),
74
+ w: Math.abs(w),
75
+ h: Math.abs(h),
76
+ };
77
+ }
78
+
79
+ /** One stroke (and a shape's anchored label) → its world-space view record. */
80
+ export function strokeToRenderItem(s: Stroke, label?: Label): RenderItem | null {
81
+ const head = { id: s.id, index: 'a0', ...author(s) };
82
+ const rot = s.rotation ? { rot: s.rotation } : {};
83
+ switch (s.tool) {
84
+ case 'element':
85
+ return { el: { ...s.el, id: s.id } };
86
+ case 'sticky':
87
+ return {
88
+ el: {
89
+ ...head,
90
+ type: 'sticky',
91
+ ...box(s.x, s.y, s.w, s.h),
92
+ ...rot,
93
+ fill: s.color,
94
+ text: s.text,
95
+ fontSize: s.fontSize,
96
+ radius: s.cornerRadius,
97
+ ...fmt(s),
98
+ },
99
+ };
100
+ case 'text':
101
+ if (s.anchorId) return null; // drawn inside its host
102
+ return {
103
+ el: {
104
+ ...head,
105
+ type: 'text',
106
+ x: s.x ?? 0,
107
+ y: s.y ?? 0,
108
+ ...rot,
109
+ text: s.text,
110
+ fontSize: s.fontSize,
111
+ color: s.color,
112
+ ...fmt(s),
113
+ },
114
+ };
115
+ case 'rect':
116
+ case 'ellipse':
117
+ case 'polygon': {
118
+ const b =
119
+ s.tool === 'ellipse'
120
+ ? { x: s.cx - s.rx, y: s.cy - s.ry, w: s.rx * 2, h: s.ry * 2 }
121
+ : box(s.x, s.y, s.w, s.h);
122
+ return {
123
+ el: {
124
+ ...head,
125
+ type: 'shape',
126
+ kind: s.tool === 'polygon' ? s.shape : s.tool,
127
+ ...b,
128
+ ...rot,
129
+ color: s.color,
130
+ width: s.width,
131
+ fill: s.fill ?? null,
132
+ ...(s.tool === 'rect' && s.cornerRadius ? { radius: s.cornerRadius } : {}),
133
+ ...(s.dashed ? { dashed: true } : {}),
134
+ ...(label
135
+ ? {
136
+ label: {
137
+ text: label.text,
138
+ fontSize: label.fontSize,
139
+ color: label.color,
140
+ ...fmt(label),
141
+ },
142
+ }
143
+ : {}),
144
+ },
145
+ };
146
+ }
147
+ case 'arrow':
148
+ return {
149
+ el: {
150
+ ...head,
151
+ type: 'arrow',
152
+ start: { x: s.x1, y: s.y1 },
153
+ end: { x: s.x2, y: s.y2 },
154
+ color: s.color,
155
+ width: s.width,
156
+ ...(s.startHead ? { startHead: s.startHead } : {}),
157
+ ...(s.endHead ? { endHead: s.endHead } : {}),
158
+ ...(s.dashed ? { dashed: true } : {}),
159
+ ...(s.lineType ? { line: s.lineType } : {}),
160
+ },
161
+ ends: {
162
+ x1: s.x1,
163
+ y1: s.y1,
164
+ x2: s.x2,
165
+ y2: s.y2,
166
+ ...(s.startBind
167
+ ? { startAnchor: { el: s.startBind.hostId, nx: s.startBind.nx, ny: s.startBind.ny } }
168
+ : {}),
169
+ ...(s.endBind
170
+ ? { endAnchor: { el: s.endBind.hostId, nx: s.endBind.nx, ny: s.endBind.ny } }
171
+ : {}),
172
+ },
173
+ };
174
+ case 'pen':
175
+ return {
176
+ el: {
177
+ ...head,
178
+ type: 'pen',
179
+ points: s.points.flat(),
180
+ color: s.color,
181
+ width: s.width,
182
+ ...(s.highlighter ? { highlighter: true } : {}),
183
+ },
184
+ };
185
+ case 'image':
186
+ return {
187
+ el: {
188
+ ...head,
189
+ type: 'image',
190
+ ...box(s.x, s.y, s.w, s.h),
191
+ ...rot,
192
+ href: s.href,
193
+ alt: s.alt ?? '',
194
+ },
195
+ };
196
+ case 'link':
197
+ return {
198
+ el: {
199
+ ...head,
200
+ type: 'link',
201
+ ...box(s.x, s.y, s.w, s.h),
202
+ ...rot,
203
+ url: s.url,
204
+ title: s.title,
205
+ domain: s.domain,
206
+ },
207
+ };
208
+ case 'mediaref':
209
+ return {
210
+ el: {
211
+ ...head,
212
+ type: 'mediaref',
213
+ ...box(s.x, s.y, s.w, s.h),
214
+ ...rot,
215
+ src: s.src,
216
+ media: s.mediaKind,
217
+ title: s.title,
218
+ },
219
+ };
220
+ case 'section':
221
+ return {
222
+ el: {
223
+ ...head,
224
+ type: 'section',
225
+ ...box(s.x, s.y, s.w, s.h),
226
+ label: s.label,
227
+ color: s.color,
228
+ },
229
+ };
230
+ default:
231
+ return null;
232
+ }
233
+ }
234
+
235
+ const cache = new WeakMap<Stroke, { label: Label | undefined; item: RenderItem | null }>();
236
+
237
+ /**
238
+ * The strokes in paint order as view records. `unknown` are elements of a type
239
+ * this build does not know (a newer peer's) — drawn as labelled placeholders,
240
+ * never dropped.
241
+ */
242
+ export function renderItemsFromStrokes(
243
+ strokes: readonly Stroke[],
244
+ unknown: readonly AnnotationElement[] = []
245
+ ): RenderItem[] {
246
+ const labels = new Map<string, Label>();
247
+ for (const s of strokes) {
248
+ if (s.tool === 'text' && s.anchorId && !labels.has(s.anchorId)) labels.set(s.anchorId, s);
249
+ }
250
+ const out: RenderItem[] = [];
251
+ for (const s of strokes) {
252
+ const label = labels.get(s.id);
253
+ const hit = cache.get(s);
254
+ let item: RenderItem | null;
255
+ if (hit && hit.label === label) item = hit.item;
256
+ else {
257
+ item = strokeToRenderItem(s, label);
258
+ cache.set(s, { label, item });
259
+ }
260
+ if (item) out.push(item);
261
+ }
262
+ for (const el of unknown) out.push({ el });
263
+ return out;
264
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * @file annotations/ui/scene.tsx — the annotation elements, in paint order
3
+ * @scope apps/studio/annotations/ui/scene.tsx
4
+ * @purpose Portaled into `.dc-world` (whose CSS transform does pan/zoom), one
5
+ * node per element (element-node.tsx). Selection chrome (halos,
6
+ * marquee, guides, handles) stays in the `.dc-annot-svg` overlay the
7
+ * layer renders after this, so it always paints on top.
8
+ *
9
+ * `data-mdcc-annotations` marks the scene for export capture, which
10
+ * hides annotations from exported artboards.
11
+ */
12
+
13
+ import { type EditRequest, ElementNode, PendingTextNode } from './element-node.tsx';
14
+ import type { RenderItem } from './render-model.ts';
15
+
16
+ export interface PendingText {
17
+ x: number;
18
+ y: number;
19
+ color: string;
20
+ fontSize: number;
21
+ }
22
+
23
+ export function AnnotationScene({
24
+ items,
25
+ interactive,
26
+ editingId,
27
+ edit,
28
+ pending,
29
+ resolveAsset,
30
+ }: {
31
+ items: readonly RenderItem[];
32
+ interactive: boolean;
33
+ /** Element whose text slot is open in the editor. */
34
+ editingId: string | null;
35
+ edit: EditRequest | null;
36
+ /** A text caret not yet backed by an element (born on commit). */
37
+ pending: PendingText | null;
38
+ resolveAsset: (href: string) => string;
39
+ }) {
40
+ return (
41
+ <div className="dc-annot-scene" data-mdcc-annotations="1">
42
+ {items.map((it) => (
43
+ <ElementNode
44
+ key={it.el.id}
45
+ el={it.el}
46
+ ends={it.ends}
47
+ interactive={interactive}
48
+ edit={it.el.id === editingId ? edit : null}
49
+ resolveAsset={resolveAsset}
50
+ />
51
+ ))}
52
+ {pending && edit ? (
53
+ <PendingTextNode
54
+ x={pending.x}
55
+ y={pending.y}
56
+ color={pending.color}
57
+ fontSize={pending.fontSize}
58
+ edit={edit}
59
+ />
60
+ ) : null}
61
+ </div>
62
+ );
63
+ }