@usetheo/ui 0.1.0-next.0 → 0.1.0-next.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/CHANGELOG.md +14 -0
- package/README.md +116 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/plugin-Atb0VKtr.d.ts +172 -0
- package/dist/slide/index.d.ts +212 -0
- package/dist/slide/index.js +714 -0
- package/dist/slide/index.js.map +1 -0
- package/dist/slide/plugins/emoji/index.d.ts +29 -0
- package/dist/slide/plugins/emoji/index.js +157 -0
- package/dist/slide/plugins/emoji/index.js.map +1 -0
- package/dist/slide/plugins/math/index.d.ts +13 -0
- package/dist/slide/plugins/math/index.js +145 -0
- package/dist/slide/plugins/math/index.js.map +1 -0
- package/dist/slide/plugins/mermaid/index.d.ts +55 -0
- package/dist/slide/plugins/mermaid/index.js +218 -0
- package/dist/slide/plugins/mermaid/index.js.map +1 -0
- package/dist/slide/plugins/shiki/index.d.ts +18 -0
- package/dist/slide/plugins/shiki/index.js +87 -0
- package/dist/slide/plugins/shiki/index.js.map +1 -0
- package/dist/slide/themes/default.css +256 -0
- package/dist/slide/themes/layouts.css +143 -0
- package/dist/slide/themes/violet-forge.css +256 -0
- package/dist/slide-deck/index.css +52 -0
- package/dist/slide-deck/index.css.map +1 -0
- package/dist/slide-deck/index.d.ts +377 -0
- package/dist/slide-deck/index.js +1797 -0
- package/dist/slide-deck/index.js.map +1 -0
- package/dist/whiteboard/index.d.ts +258 -0
- package/dist/whiteboard/index.js +738 -0
- package/dist/whiteboard/index.js.map +1 -0
- package/package.json +141 -9
- package/registry/r/agent-composer.json +4 -4
- package/registry/r/agent-editor.json +9 -9
- package/registry/r/agent-error-card.json +2 -2
- package/registry/r/agent-event.json +4 -4
- package/registry/r/agent-handoff.json +2 -2
- package/registry/r/agent-profile.json +2 -2
- package/registry/r/agent-starting-state.json +2 -2
- package/registry/r/agent-stream.json +9 -9
- package/registry/r/agent-streaming.json +2 -2
- package/registry/r/agent-timeline.json +4 -4
- package/registry/r/approval-card.json +4 -4
- package/registry/r/artifact-preview.json +2 -2
- package/registry/r/attachment-chip.json +4 -4
- package/registry/r/audit-log-entry.json +3 -3
- package/registry/r/auto-compact-notice.json +2 -2
- package/registry/r/avatar.json +2 -2
- package/registry/r/badge.json +2 -2
- package/registry/r/browser-controls.json +2 -2
- package/registry/r/build-log-stream.json +2 -2
- package/registry/r/button.json +2 -2
- package/registry/r/capability-indicator.json +3 -3
- package/registry/r/card.json +2 -2
- package/registry/r/chat-composer.json +3 -3
- package/registry/r/chat-message.json +3 -3
- package/registry/r/chat-thread.json +2 -2
- package/registry/r/checkbox.json +2 -2
- package/registry/r/command-palette.json +4 -4
- package/registry/r/context-card.json +3 -3
- package/registry/r/context-window-bar.json +2 -2
- package/registry/r/cost-meter.json +2 -2
- package/registry/r/created-files-card.json +3 -3
- package/registry/r/cron-job-card.json +2 -2
- package/registry/r/cron-jobs-list.json +3 -3
- package/registry/r/deployment-row.json +3 -3
- package/registry/r/dialog.json +2 -2
- package/registry/r/diff-viewer.json +2 -2
- package/registry/r/domain-config.json +6 -6
- package/registry/r/empty-state.json +3 -3
- package/registry/r/env-var-editor.json +5 -5
- package/registry/r/folder-context-card.json +3 -3
- package/registry/r/folder-selector.json +2 -2
- package/registry/r/form-field.json +2 -2
- package/registry/r/hook-config.json +2 -2
- package/registry/r/hook-event-log.json +2 -2
- package/registry/r/input.json +2 -2
- package/registry/r/intent-selector.json +3 -3
- package/registry/r/label.json +2 -2
- package/registry/r/lane-board.json +2 -2
- package/registry/r/login-split.json +2 -2
- package/registry/r/mcp-server-card.json +2 -2
- package/registry/r/mcp-server-list.json +3 -3
- package/registry/r/memory-editor.json +3 -3
- package/registry/r/mention-menu.json +3 -3
- package/registry/r/metrics-panel.json +2 -2
- package/registry/r/model-card.json +3 -3
- package/registry/r/model-selector.json +2 -2
- package/registry/r/permission-matrix.json +2 -2
- package/registry/r/permission-modal.json +4 -4
- package/registry/r/preview-env-card.json +5 -5
- package/registry/r/preview-panel.json +3 -3
- package/registry/r/progress-checklist.json +3 -3
- package/registry/r/project-card.json +5 -5
- package/registry/r/project-switcher.json +2 -2
- package/registry/r/quick-action-chips.json +3 -3
- package/registry/r/radio-group.json +2 -2
- package/registry/r/recent-folders-list.json +2 -2
- package/registry/r/rollback-ui.json +4 -4
- package/registry/r/rule-card.json +3 -3
- package/registry/r/rule-editor.json +10 -10
- package/registry/r/rule-types.json +1 -1
- package/registry/r/run-stats.json +2 -2
- package/registry/r/running-tasks-panel.json +2 -2
- package/registry/r/scroll-area.json +2 -2
- package/registry/r/select.json +2 -2
- package/registry/r/session-list-item.json +2 -2
- package/registry/r/session-timeline.json +2 -2
- package/registry/r/sheet.json +2 -2
- package/registry/r/sidebar.json +2 -2
- package/registry/r/skeleton.json +2 -2
- package/registry/r/skill-card.json +4 -4
- package/registry/r/skill-editor.json +10 -10
- package/registry/r/skills-list.json +3 -3
- package/registry/r/social-auth-row.json +3 -3
- package/registry/r/steps-rail.json +2 -2
- package/registry/r/sub-agent-dispatch.json +2 -2
- package/registry/r/switch.json +2 -2
- package/registry/r/system-prompt-editor.json +2 -2
- package/registry/r/tabs.json +2 -2
- package/registry/r/task-header.json +4 -4
- package/registry/r/task-plan.json +2 -2
- package/registry/r/terminal-panel.json +2 -2
- package/registry/r/textarea.json +2 -2
- package/registry/r/theme-provider.json +2 -2
- package/registry/r/theme-script.json +1 -1
- package/registry/r/theo-ui-provider.json +2 -2
- package/registry/r/toast.json +2 -2
- package/registry/r/token-usage-chart.json +2 -2
- package/registry/r/tool-call-card.json +3 -3
- package/registry/r/tool-call.json +2 -2
- package/registry/r/tool-result.json +2 -2
- package/registry/r/tools-list.json +3 -3
- package/registry/r/tooltip.json +2 -2
- package/registry/r/topnav.json +2 -2
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
import { useMemo, useEffect, useRef, useState, useCallback } from 'react';
|
|
2
|
+
import rough from 'roughjs';
|
|
3
|
+
import { getStroke } from 'perfect-freehand';
|
|
4
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
|
|
7
|
+
// src/components/primitives/whiteboard/whiteboard.tsx
|
|
8
|
+
|
|
9
|
+
// src/components/primitives/whiteboard/seed.ts
|
|
10
|
+
var FNV_OFFSET_BASIS_32 = 2166136261;
|
|
11
|
+
var FNV_PRIME_32 = 16777619;
|
|
12
|
+
function fnv1a32(input) {
|
|
13
|
+
let hash = FNV_OFFSET_BASIS_32;
|
|
14
|
+
for (let i = 0; i < input.length; i++) {
|
|
15
|
+
hash ^= input.charCodeAt(i);
|
|
16
|
+
hash = Math.imul(hash, FNV_PRIME_32);
|
|
17
|
+
}
|
|
18
|
+
return hash | 0;
|
|
19
|
+
}
|
|
20
|
+
function deriveSeed(el) {
|
|
21
|
+
if (typeof el.seed === "number" && Number.isFinite(el.seed)) {
|
|
22
|
+
return el.seed | 0;
|
|
23
|
+
}
|
|
24
|
+
const key = `${el.type}|${el.x}|${el.y}|${el.w ?? ""}|${el.h ?? ""}|${el.label ?? ""}`;
|
|
25
|
+
return fnv1a32(key);
|
|
26
|
+
}
|
|
27
|
+
var DEFAULT_STROKE_OPTIONS = {
|
|
28
|
+
size: 8,
|
|
29
|
+
thinning: 0.5,
|
|
30
|
+
smoothing: 0.5,
|
|
31
|
+
streamline: 0.5
|
|
32
|
+
};
|
|
33
|
+
function fmt(n) {
|
|
34
|
+
return typeof n === "number" && Number.isFinite(n) ? n.toFixed(2) : "0";
|
|
35
|
+
}
|
|
36
|
+
function svgPathFromStroke(points) {
|
|
37
|
+
if (points.length === 0) return "";
|
|
38
|
+
const first = points[0] ?? [];
|
|
39
|
+
let d = `M ${fmt(first[0])} ${fmt(first[1])}`;
|
|
40
|
+
for (let i = 1; i < points.length; i++) {
|
|
41
|
+
const p = points[i] ?? [];
|
|
42
|
+
d += ` L ${fmt(p[0])} ${fmt(p[1])}`;
|
|
43
|
+
}
|
|
44
|
+
d += " Z";
|
|
45
|
+
return d;
|
|
46
|
+
}
|
|
47
|
+
function renderFreedraw(el) {
|
|
48
|
+
const size = (el.strokeWidth ?? 1.5) * 5;
|
|
49
|
+
const inputPoints = el.points.map(([x, y, pressure]) => {
|
|
50
|
+
const tx = el.x + x;
|
|
51
|
+
const ty = el.y + y;
|
|
52
|
+
return pressure === void 0 ? [tx, ty] : [tx, ty, pressure];
|
|
53
|
+
});
|
|
54
|
+
const stroke = getStroke(inputPoints, {
|
|
55
|
+
...DEFAULT_STROKE_OPTIONS,
|
|
56
|
+
size
|
|
57
|
+
});
|
|
58
|
+
const d = svgPathFromStroke(stroke);
|
|
59
|
+
return /* @__PURE__ */ jsx("g", { opacity: el.opacity ?? 1, children: /* @__PURE__ */ jsx("path", { d, fill: el.stroke ?? "currentColor", stroke: "none" }) });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/components/primitives/whiteboard/render/rough-paths.ts
|
|
63
|
+
function toRoughPaths(generator, drawable) {
|
|
64
|
+
const paths = generator.toPaths(drawable);
|
|
65
|
+
return paths.map((p) => ({
|
|
66
|
+
d: p.d,
|
|
67
|
+
stroke: p.stroke,
|
|
68
|
+
strokeWidth: p.strokeWidth,
|
|
69
|
+
fill: p.fill
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// src/components/primitives/whiteboard/render/style.ts
|
|
74
|
+
var DEFAULT_STROKE = "currentColor";
|
|
75
|
+
function buildOptions(src, seed) {
|
|
76
|
+
const o = {
|
|
77
|
+
seed,
|
|
78
|
+
stroke: src.stroke ?? DEFAULT_STROKE,
|
|
79
|
+
strokeWidth: src.strokeWidth ?? 1.5,
|
|
80
|
+
roughness: src.roughness ?? 1.2
|
|
81
|
+
};
|
|
82
|
+
if (src.strokeStyle === "dashed") o.strokeLineDash = [10, 6];
|
|
83
|
+
else if (src.strokeStyle === "dotted") o.strokeLineDash = [2, 4];
|
|
84
|
+
if (src.fill) o.fill = src.fill;
|
|
85
|
+
if (src.fillStyle) o.fillStyle = src.fillStyle;
|
|
86
|
+
return o;
|
|
87
|
+
}
|
|
88
|
+
function strokeDashArray(strokeStyle2, strokeWidth) {
|
|
89
|
+
if (strokeStyle2 === "dashed") {
|
|
90
|
+
const dash = Math.max(8, strokeWidth * 6);
|
|
91
|
+
const gap = Math.max(5, strokeWidth * 4);
|
|
92
|
+
return `${dash} ${gap}`;
|
|
93
|
+
}
|
|
94
|
+
if (strokeStyle2 === "dotted") {
|
|
95
|
+
return `${strokeWidth} ${strokeWidth * 2.5}`;
|
|
96
|
+
}
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
var HEAD_BASE_PX = 12;
|
|
100
|
+
var HEAD_ANGLE_RAD = Math.PI / 7;
|
|
101
|
+
function arrowHeadGeometry(fromX, fromY, toX, toY, strokeWidth) {
|
|
102
|
+
const dx = toX - fromX;
|
|
103
|
+
const dy = toY - fromY;
|
|
104
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
105
|
+
if (dist === 0) return null;
|
|
106
|
+
const angle = Math.atan2(dy, dx);
|
|
107
|
+
const headLen = Math.min(HEAD_BASE_PX + strokeWidth * 2, dist * 0.4);
|
|
108
|
+
const leftX = toX - headLen * Math.cos(angle - HEAD_ANGLE_RAD);
|
|
109
|
+
const leftY = toY - headLen * Math.sin(angle - HEAD_ANGLE_RAD);
|
|
110
|
+
const rightX = toX - headLen * Math.cos(angle + HEAD_ANGLE_RAD);
|
|
111
|
+
const rightY = toY - headLen * Math.sin(angle + HEAD_ANGLE_RAD);
|
|
112
|
+
return { apexX: toX, apexY: toY, leftX, leftY, rightX, rightY };
|
|
113
|
+
}
|
|
114
|
+
function lineBody(gen, el, seed) {
|
|
115
|
+
const opts = buildOptions(el, seed);
|
|
116
|
+
const drawable = gen.line(el.x, el.y, el.to[0], el.to[1], opts);
|
|
117
|
+
const dash = strokeDashArray(el.strokeStyle, el.strokeWidth ?? 1.5);
|
|
118
|
+
return toRoughPaths(gen, drawable).map((p, i) => /* @__PURE__ */ jsx(
|
|
119
|
+
"path",
|
|
120
|
+
{
|
|
121
|
+
d: p.d,
|
|
122
|
+
stroke: p.stroke,
|
|
123
|
+
strokeWidth: p.strokeWidth,
|
|
124
|
+
fill: "none",
|
|
125
|
+
strokeLinecap: "round",
|
|
126
|
+
strokeLinejoin: "round",
|
|
127
|
+
strokeDasharray: dash
|
|
128
|
+
},
|
|
129
|
+
`body-${i}`
|
|
130
|
+
));
|
|
131
|
+
}
|
|
132
|
+
function arrowHead(gen, fromX, fromY, toX, toY, seed, el, side) {
|
|
133
|
+
const geom = arrowHeadGeometry(fromX, fromY, toX, toY, el.strokeWidth ?? 1.5);
|
|
134
|
+
if (!geom) return [];
|
|
135
|
+
const opts = buildOptions(el, seed);
|
|
136
|
+
const leftDrawable = gen.line(geom.apexX, geom.apexY, geom.leftX, geom.leftY, opts);
|
|
137
|
+
const rightDrawable = gen.line(geom.apexX, geom.apexY, geom.rightX, geom.rightY, opts);
|
|
138
|
+
const leftPaths = toRoughPaths(gen, leftDrawable);
|
|
139
|
+
const rightPaths = toRoughPaths(gen, rightDrawable);
|
|
140
|
+
const nodes = [];
|
|
141
|
+
for (const [i, p] of leftPaths.entries()) {
|
|
142
|
+
nodes.push(
|
|
143
|
+
/* @__PURE__ */ jsx(
|
|
144
|
+
"path",
|
|
145
|
+
{
|
|
146
|
+
d: p.d,
|
|
147
|
+
stroke: p.stroke,
|
|
148
|
+
strokeWidth: p.strokeWidth,
|
|
149
|
+
fill: "none",
|
|
150
|
+
strokeLinecap: "round",
|
|
151
|
+
strokeLinejoin: "round",
|
|
152
|
+
"data-line-part": "arrowhead"
|
|
153
|
+
},
|
|
154
|
+
`${side}-l-${i}`
|
|
155
|
+
)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
for (const [i, p] of rightPaths.entries()) {
|
|
159
|
+
nodes.push(
|
|
160
|
+
/* @__PURE__ */ jsx(
|
|
161
|
+
"path",
|
|
162
|
+
{
|
|
163
|
+
d: p.d,
|
|
164
|
+
stroke: p.stroke,
|
|
165
|
+
strokeWidth: p.strokeWidth,
|
|
166
|
+
fill: "none",
|
|
167
|
+
strokeLinecap: "round",
|
|
168
|
+
strokeLinejoin: "round",
|
|
169
|
+
"data-line-part": "arrowhead"
|
|
170
|
+
},
|
|
171
|
+
`${side}-r-${i}`
|
|
172
|
+
)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
return nodes;
|
|
176
|
+
}
|
|
177
|
+
function renderLine(gen, el) {
|
|
178
|
+
return /* @__PURE__ */ jsx("g", { opacity: el.opacity ?? 1, children: lineBody(gen, el, el.seed ?? 0) });
|
|
179
|
+
}
|
|
180
|
+
function renderArrow(gen, el) {
|
|
181
|
+
const seed = el.seed ?? 0;
|
|
182
|
+
const nodes = [...lineBody(gen, el, seed)];
|
|
183
|
+
if (el.headEnd !== false) {
|
|
184
|
+
nodes.push(...arrowHead(gen, el.x, el.y, el.to[0], el.to[1], seed + 1, el, "end"));
|
|
185
|
+
}
|
|
186
|
+
if (el.headStart) {
|
|
187
|
+
nodes.push(...arrowHead(gen, el.to[0], el.to[1], el.x, el.y, seed + 2, el, "start"));
|
|
188
|
+
}
|
|
189
|
+
let label = null;
|
|
190
|
+
if (el.label) {
|
|
191
|
+
const midX = (el.x + el.to[0]) / 2;
|
|
192
|
+
const midY = (el.y + el.to[1]) / 2;
|
|
193
|
+
const dx = el.to[0] - el.x;
|
|
194
|
+
const dy = el.to[1] - el.y;
|
|
195
|
+
const dist = Math.sqrt(dx * dx + dy * dy) || 1;
|
|
196
|
+
const nx = -dy / dist;
|
|
197
|
+
const ny = dx / dist;
|
|
198
|
+
const offset = 12;
|
|
199
|
+
label = /* @__PURE__ */ jsx(
|
|
200
|
+
"text",
|
|
201
|
+
{
|
|
202
|
+
x: midX + nx * offset,
|
|
203
|
+
y: midY + ny * offset,
|
|
204
|
+
textAnchor: "middle",
|
|
205
|
+
dominantBaseline: "central",
|
|
206
|
+
fontSize: 14,
|
|
207
|
+
fontFamily: "ui-sans-serif, system-ui, sans-serif",
|
|
208
|
+
fill: el.stroke ?? "currentColor",
|
|
209
|
+
style: { pointerEvents: "none" },
|
|
210
|
+
children: el.label
|
|
211
|
+
}
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return /* @__PURE__ */ jsxs("g", { opacity: el.opacity ?? 1, children: [
|
|
215
|
+
nodes,
|
|
216
|
+
label
|
|
217
|
+
] });
|
|
218
|
+
}
|
|
219
|
+
function Label({ cx, cy, label, stroke }) {
|
|
220
|
+
return /* @__PURE__ */ jsx(
|
|
221
|
+
"text",
|
|
222
|
+
{
|
|
223
|
+
x: cx,
|
|
224
|
+
y: cy,
|
|
225
|
+
textAnchor: "middle",
|
|
226
|
+
dominantBaseline: "central",
|
|
227
|
+
fontSize: 16,
|
|
228
|
+
fontFamily: "ui-sans-serif, system-ui, sans-serif",
|
|
229
|
+
fill: stroke ?? "currentColor",
|
|
230
|
+
style: { pointerEvents: "none" },
|
|
231
|
+
children: label
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
function pathsToReactNodes(paths, keyPrefix, outlineStroke, dashArray) {
|
|
236
|
+
return paths.map((p, i) => {
|
|
237
|
+
const isOutline = p.stroke === outlineStroke;
|
|
238
|
+
return /* @__PURE__ */ jsx(
|
|
239
|
+
"path",
|
|
240
|
+
{
|
|
241
|
+
d: p.d,
|
|
242
|
+
stroke: p.stroke,
|
|
243
|
+
strokeWidth: p.strokeWidth,
|
|
244
|
+
fill: p.fill ?? "none",
|
|
245
|
+
strokeLinecap: "round",
|
|
246
|
+
strokeLinejoin: "round",
|
|
247
|
+
strokeDasharray: isOutline ? dashArray : void 0
|
|
248
|
+
},
|
|
249
|
+
`${keyPrefix}-${i}`
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function renderRect(gen, el) {
|
|
254
|
+
const opts = buildOptions(el, el.seed ?? 0);
|
|
255
|
+
const drawable = gen.rectangle(el.x, el.y, el.w, el.h, opts);
|
|
256
|
+
const paths = toRoughPaths(gen, drawable);
|
|
257
|
+
const dash = strokeDashArray(el.strokeStyle, el.strokeWidth ?? 1.5);
|
|
258
|
+
return /* @__PURE__ */ jsxs("g", { opacity: el.opacity ?? 1, children: [
|
|
259
|
+
pathsToReactNodes(paths, "rect", opts.stroke, dash),
|
|
260
|
+
el.label ? /* @__PURE__ */ jsx(Label, { cx: el.x + el.w / 2, cy: el.y + el.h / 2, label: el.label, stroke: el.stroke }) : null
|
|
261
|
+
] });
|
|
262
|
+
}
|
|
263
|
+
function renderEllipse(gen, el) {
|
|
264
|
+
const opts = buildOptions(el, el.seed ?? 0);
|
|
265
|
+
const drawable = gen.ellipse(el.x + el.w / 2, el.y + el.h / 2, el.w, el.h, opts);
|
|
266
|
+
const paths = toRoughPaths(gen, drawable);
|
|
267
|
+
const dash = strokeDashArray(el.strokeStyle, el.strokeWidth ?? 1.5);
|
|
268
|
+
return /* @__PURE__ */ jsxs("g", { opacity: el.opacity ?? 1, children: [
|
|
269
|
+
pathsToReactNodes(paths, "ellipse", opts.stroke, dash),
|
|
270
|
+
el.label ? /* @__PURE__ */ jsx(Label, { cx: el.x + el.w / 2, cy: el.y + el.h / 2, label: el.label, stroke: el.stroke }) : null
|
|
271
|
+
] });
|
|
272
|
+
}
|
|
273
|
+
function renderDiamond(gen, el) {
|
|
274
|
+
const opts = buildOptions(el, el.seed ?? 0);
|
|
275
|
+
const cx = el.x + el.w / 2;
|
|
276
|
+
const cy = el.y + el.h / 2;
|
|
277
|
+
const points = [
|
|
278
|
+
[cx, el.y],
|
|
279
|
+
// top
|
|
280
|
+
[el.x + el.w, cy],
|
|
281
|
+
// right
|
|
282
|
+
[cx, el.y + el.h],
|
|
283
|
+
// bottom
|
|
284
|
+
[el.x, cy]
|
|
285
|
+
// left
|
|
286
|
+
];
|
|
287
|
+
const drawable = gen.polygon(points, opts);
|
|
288
|
+
const paths = toRoughPaths(gen, drawable);
|
|
289
|
+
const dash = strokeDashArray(el.strokeStyle, el.strokeWidth ?? 1.5);
|
|
290
|
+
return /* @__PURE__ */ jsxs("g", { opacity: el.opacity ?? 1, children: [
|
|
291
|
+
pathsToReactNodes(paths, "diamond", opts.stroke, dash),
|
|
292
|
+
el.label ? /* @__PURE__ */ jsx(Label, { cx, cy, label: el.label, stroke: el.stroke }) : null
|
|
293
|
+
] });
|
|
294
|
+
}
|
|
295
|
+
var FONT_STACKS = {
|
|
296
|
+
sans: "ui-sans-serif, system-ui, sans-serif",
|
|
297
|
+
serif: "ui-serif, Georgia, serif",
|
|
298
|
+
mono: "ui-monospace, SFMono-Regular, Menlo, monospace",
|
|
299
|
+
hand: '"Virgil", "Caveat", "Comic Sans MS", cursive'
|
|
300
|
+
};
|
|
301
|
+
function textAnchor(align2) {
|
|
302
|
+
if (align2 === "center") return "middle";
|
|
303
|
+
if (align2 === "right") return "end";
|
|
304
|
+
return "start";
|
|
305
|
+
}
|
|
306
|
+
function renderText(el) {
|
|
307
|
+
const fontSize = el.fontSize ?? 18;
|
|
308
|
+
const fontFamily2 = FONT_STACKS[el.fontFamily ?? "hand"];
|
|
309
|
+
const anchor = textAnchor(el.align);
|
|
310
|
+
const lines = el.text.split("\n");
|
|
311
|
+
return /* @__PURE__ */ jsx("g", { opacity: el.opacity ?? 1, children: /* @__PURE__ */ jsx(
|
|
312
|
+
"text",
|
|
313
|
+
{
|
|
314
|
+
x: el.x,
|
|
315
|
+
y: el.y,
|
|
316
|
+
textAnchor: anchor,
|
|
317
|
+
dominantBaseline: "hanging",
|
|
318
|
+
fontSize,
|
|
319
|
+
fontFamily: fontFamily2,
|
|
320
|
+
fill: el.stroke ?? "currentColor",
|
|
321
|
+
children: lines.map((line, i) => (
|
|
322
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: text is split deterministically on '\n' so the line index is the most stable key.
|
|
323
|
+
/* @__PURE__ */ jsx("tspan", { x: el.x, dy: i === 0 ? 0 : fontSize * 1.2, children: line }, i)
|
|
324
|
+
))
|
|
325
|
+
}
|
|
326
|
+
) });
|
|
327
|
+
}
|
|
328
|
+
function elementWithSeed(el) {
|
|
329
|
+
if (typeof el.seed === "number") return el;
|
|
330
|
+
return { ...el, seed: deriveSeed(el) };
|
|
331
|
+
}
|
|
332
|
+
function renderElement(el, gen) {
|
|
333
|
+
switch (el.type) {
|
|
334
|
+
case "rect":
|
|
335
|
+
return renderRect(gen, elementWithSeed(el));
|
|
336
|
+
case "ellipse":
|
|
337
|
+
return renderEllipse(gen, elementWithSeed(el));
|
|
338
|
+
case "diamond":
|
|
339
|
+
return renderDiamond(gen, elementWithSeed(el));
|
|
340
|
+
case "line":
|
|
341
|
+
return renderLine(gen, elementWithSeed(el));
|
|
342
|
+
case "arrow":
|
|
343
|
+
return renderArrow(gen, elementWithSeed(el));
|
|
344
|
+
case "text":
|
|
345
|
+
return renderText(elementWithSeed(el));
|
|
346
|
+
case "freedraw":
|
|
347
|
+
return renderFreedraw(elementWithSeed(el));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function renderScene(scene, gen) {
|
|
351
|
+
return /* @__PURE__ */ jsx(Fragment, { children: scene.elements.map((el, i) => /* @__PURE__ */ jsx(
|
|
352
|
+
"g",
|
|
353
|
+
{
|
|
354
|
+
"data-element-id": el.id ?? String(i),
|
|
355
|
+
"data-element-type": el.type,
|
|
356
|
+
children: renderElement(el, gen)
|
|
357
|
+
},
|
|
358
|
+
el.id ?? `__idx-${i}`
|
|
359
|
+
)) });
|
|
360
|
+
}
|
|
361
|
+
var finiteNumber = z.number().finite();
|
|
362
|
+
var finitePositive = finiteNumber.positive();
|
|
363
|
+
var strokeStyle = z.enum(["solid", "dashed", "dotted"]);
|
|
364
|
+
var fillStyle = z.enum(["hachure", "solid", "cross-hatch", "zigzag"]);
|
|
365
|
+
var align = z.enum(["left", "center", "right"]);
|
|
366
|
+
var fontFamily = z.enum(["sans", "serif", "mono", "hand"]);
|
|
367
|
+
var roundness = z.enum(["sharp", "round"]);
|
|
368
|
+
var baseElement = z.object({
|
|
369
|
+
id: z.string().max(120).optional(),
|
|
370
|
+
x: finiteNumber,
|
|
371
|
+
y: finiteNumber,
|
|
372
|
+
stroke: z.string().max(64).optional(),
|
|
373
|
+
strokeWidth: finitePositive.max(50).optional(),
|
|
374
|
+
strokeStyle: strokeStyle.optional(),
|
|
375
|
+
fill: z.string().max(64).optional(),
|
|
376
|
+
fillStyle: fillStyle.optional(),
|
|
377
|
+
opacity: finiteNumber.min(0).max(1).optional(),
|
|
378
|
+
roughness: finiteNumber.min(0).max(3).optional(),
|
|
379
|
+
seed: z.number().int().finite().optional()
|
|
380
|
+
});
|
|
381
|
+
var dim = finitePositive.max(2e4);
|
|
382
|
+
var labelText = z.string().max(500);
|
|
383
|
+
var rectElement = baseElement.extend({
|
|
384
|
+
type: z.literal("rect"),
|
|
385
|
+
w: dim,
|
|
386
|
+
h: dim,
|
|
387
|
+
label: labelText.optional(),
|
|
388
|
+
roundness: roundness.optional()
|
|
389
|
+
});
|
|
390
|
+
var ellipseElement = baseElement.extend({
|
|
391
|
+
type: z.literal("ellipse"),
|
|
392
|
+
w: dim,
|
|
393
|
+
h: dim,
|
|
394
|
+
label: labelText.optional()
|
|
395
|
+
});
|
|
396
|
+
var diamondElement = baseElement.extend({
|
|
397
|
+
type: z.literal("diamond"),
|
|
398
|
+
w: dim,
|
|
399
|
+
h: dim,
|
|
400
|
+
label: labelText.optional()
|
|
401
|
+
});
|
|
402
|
+
var point2 = z.tuple([finiteNumber, finiteNumber]);
|
|
403
|
+
var lineElement = baseElement.extend({
|
|
404
|
+
type: z.literal("line"),
|
|
405
|
+
to: point2
|
|
406
|
+
});
|
|
407
|
+
var arrowElement = baseElement.extend({
|
|
408
|
+
type: z.literal("arrow"),
|
|
409
|
+
to: point2,
|
|
410
|
+
label: labelText.optional(),
|
|
411
|
+
headStart: z.boolean().optional(),
|
|
412
|
+
headEnd: z.boolean().default(true)
|
|
413
|
+
});
|
|
414
|
+
var textElement = baseElement.extend({
|
|
415
|
+
type: z.literal("text"),
|
|
416
|
+
text: z.string().max(5e3),
|
|
417
|
+
fontSize: finitePositive.max(500).optional(),
|
|
418
|
+
align: align.optional(),
|
|
419
|
+
fontFamily: fontFamily.optional()
|
|
420
|
+
});
|
|
421
|
+
var freedrawPoint = z.tuple([finiteNumber, finiteNumber, finiteNumber.optional()]);
|
|
422
|
+
var freedrawElement = baseElement.extend({
|
|
423
|
+
type: z.literal("freedraw"),
|
|
424
|
+
points: z.array(freedrawPoint).min(2).max(5e3)
|
|
425
|
+
});
|
|
426
|
+
var whiteboardElement = z.discriminatedUnion("type", [
|
|
427
|
+
rectElement,
|
|
428
|
+
ellipseElement,
|
|
429
|
+
diamondElement,
|
|
430
|
+
lineElement,
|
|
431
|
+
arrowElement,
|
|
432
|
+
textElement,
|
|
433
|
+
freedrawElement
|
|
434
|
+
]);
|
|
435
|
+
var whiteboardScene = z.object({
|
|
436
|
+
version: z.literal(1),
|
|
437
|
+
width: dim,
|
|
438
|
+
// EC-4: clamped 1..20000
|
|
439
|
+
height: dim,
|
|
440
|
+
// EC-4: clamped 1..20000
|
|
441
|
+
background: z.string().max(64).optional(),
|
|
442
|
+
elements: z.array(whiteboardElement).max(5e3)
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// src/components/primitives/whiteboard/validate.ts
|
|
446
|
+
function valueAtPath(input, path) {
|
|
447
|
+
let cursor = input;
|
|
448
|
+
for (const segment of path) {
|
|
449
|
+
if (cursor === null || typeof cursor !== "object") return void 0;
|
|
450
|
+
cursor = cursor[segment];
|
|
451
|
+
}
|
|
452
|
+
return cursor;
|
|
453
|
+
}
|
|
454
|
+
function formatIssue(issue, input) {
|
|
455
|
+
const error = {
|
|
456
|
+
path: issue.path.join("."),
|
|
457
|
+
message: issue.message,
|
|
458
|
+
code: issue.code
|
|
459
|
+
};
|
|
460
|
+
if ("received" in issue && issue.received !== void 0) {
|
|
461
|
+
error.got = issue.received;
|
|
462
|
+
} else if (issue.path.length > 0) {
|
|
463
|
+
const value = valueAtPath(input, issue.path);
|
|
464
|
+
if (value !== void 0) error.got = value;
|
|
465
|
+
}
|
|
466
|
+
return error;
|
|
467
|
+
}
|
|
468
|
+
function validateScene(input) {
|
|
469
|
+
const result = whiteboardScene.safeParse(input);
|
|
470
|
+
if (result.success) {
|
|
471
|
+
return { ok: true, scene: result.data };
|
|
472
|
+
}
|
|
473
|
+
const errors = result.error.issues.map(
|
|
474
|
+
(issue) => formatIssue(issue, input)
|
|
475
|
+
);
|
|
476
|
+
return { ok: false, errors };
|
|
477
|
+
}
|
|
478
|
+
function usePointerPan(ref, viewport, size) {
|
|
479
|
+
const dragRef = useRef(null);
|
|
480
|
+
const spaceHeldRef = useRef(false);
|
|
481
|
+
useEffect(() => {
|
|
482
|
+
const el = ref.current;
|
|
483
|
+
if (!el) return;
|
|
484
|
+
const handler = (e) => {
|
|
485
|
+
e.preventDefault();
|
|
486
|
+
const rect = el.getBoundingClientRect();
|
|
487
|
+
const localX = e.clientX - rect.left;
|
|
488
|
+
const localY = e.clientY - rect.top;
|
|
489
|
+
const scaleX = size.width / rect.width;
|
|
490
|
+
const scaleY = size.height / rect.height;
|
|
491
|
+
viewport.zoomAt(localX * scaleX, localY * scaleY, -e.deltaY * 1e-3, size);
|
|
492
|
+
};
|
|
493
|
+
el.addEventListener("wheel", handler, { passive: false });
|
|
494
|
+
return () => el.removeEventListener("wheel", handler);
|
|
495
|
+
}, [ref, viewport, size]);
|
|
496
|
+
useEffect(() => {
|
|
497
|
+
const down = (e) => {
|
|
498
|
+
if (e.code === "Space") spaceHeldRef.current = true;
|
|
499
|
+
};
|
|
500
|
+
const up = (e) => {
|
|
501
|
+
if (e.code === "Space") spaceHeldRef.current = false;
|
|
502
|
+
};
|
|
503
|
+
window.addEventListener("keydown", down);
|
|
504
|
+
window.addEventListener("keyup", up);
|
|
505
|
+
return () => {
|
|
506
|
+
window.removeEventListener("keydown", down);
|
|
507
|
+
window.removeEventListener("keyup", up);
|
|
508
|
+
};
|
|
509
|
+
}, []);
|
|
510
|
+
const onPointerDown = useCallback((e) => {
|
|
511
|
+
if (e.target instanceof Element && !e.currentTarget.contains(e.target)) return;
|
|
512
|
+
const isMouseButton = e.button === 0 || e.button === 1;
|
|
513
|
+
if (!isMouseButton && e.pointerType === "mouse") return;
|
|
514
|
+
if (e.pointerType === "mouse" && e.button !== 1 && !spaceHeldRef.current && e.button !== 0) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
dragRef.current = { pointerId: e.pointerId, lastX: e.clientX, lastY: e.clientY };
|
|
518
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
519
|
+
}, []);
|
|
520
|
+
const onPointerMove = useCallback(
|
|
521
|
+
(e) => {
|
|
522
|
+
const drag = dragRef.current;
|
|
523
|
+
if (!drag || drag.pointerId !== e.pointerId) return;
|
|
524
|
+
const dx = e.clientX - drag.lastX;
|
|
525
|
+
const dy = e.clientY - drag.lastY;
|
|
526
|
+
drag.lastX = e.clientX;
|
|
527
|
+
drag.lastY = e.clientY;
|
|
528
|
+
const el = ref.current;
|
|
529
|
+
if (!el) return;
|
|
530
|
+
const rect = el.getBoundingClientRect();
|
|
531
|
+
const scaleX = size.width / rect.width;
|
|
532
|
+
const scaleY = size.height / rect.height;
|
|
533
|
+
viewport.pan(dx * scaleX, dy * scaleY);
|
|
534
|
+
},
|
|
535
|
+
[ref, viewport, size]
|
|
536
|
+
);
|
|
537
|
+
const releaseDrag = useCallback((e) => {
|
|
538
|
+
const drag = dragRef.current;
|
|
539
|
+
if (!drag || drag.pointerId !== e.pointerId) return;
|
|
540
|
+
try {
|
|
541
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
542
|
+
} catch {
|
|
543
|
+
}
|
|
544
|
+
dragRef.current = null;
|
|
545
|
+
}, []);
|
|
546
|
+
return {
|
|
547
|
+
onPointerDown,
|
|
548
|
+
onPointerMove,
|
|
549
|
+
onPointerUp: releaseDrag,
|
|
550
|
+
onPointerCancel: releaseDrag
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
var MIN_ZOOM = 0.1;
|
|
554
|
+
var MAX_ZOOM = 8;
|
|
555
|
+
function clampZoom(z2) {
|
|
556
|
+
if (!Number.isFinite(z2)) return 1;
|
|
557
|
+
return Math.min(MAX_ZOOM, Math.max(MIN_ZOOM, z2));
|
|
558
|
+
}
|
|
559
|
+
function useViewport(opts) {
|
|
560
|
+
const initial = useMemo(() => {
|
|
561
|
+
const zoom = clampZoom(opts.initialZoom ?? 1);
|
|
562
|
+
if (opts.initialCenter) {
|
|
563
|
+
const [cx, cy] = opts.initialCenter;
|
|
564
|
+
return {
|
|
565
|
+
x: cx - opts.width / (2 * zoom),
|
|
566
|
+
y: cy - opts.height / (2 * zoom),
|
|
567
|
+
zoom
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
return { x: 0, y: 0, zoom };
|
|
571
|
+
}, [opts.initialCenter, opts.initialZoom, opts.width, opts.height]);
|
|
572
|
+
const [state, setState] = useState(initial);
|
|
573
|
+
const pan = useCallback((dx, dy) => {
|
|
574
|
+
setState((prev) => ({ ...prev, x: prev.x - dx / prev.zoom, y: prev.y - dy / prev.zoom }));
|
|
575
|
+
}, []);
|
|
576
|
+
const setZoom = useCallback((zoom) => {
|
|
577
|
+
setState((prev) => ({ ...prev, zoom: clampZoom(zoom) }));
|
|
578
|
+
}, []);
|
|
579
|
+
const zoomAt = useCallback(
|
|
580
|
+
(screenX, screenY, delta, _size) => {
|
|
581
|
+
setState((prev) => {
|
|
582
|
+
const oldZoom = prev.zoom;
|
|
583
|
+
const newZoom = clampZoom(prev.zoom * Math.exp(delta));
|
|
584
|
+
if (newZoom === oldZoom) return prev;
|
|
585
|
+
const worldX = prev.x + screenX / oldZoom;
|
|
586
|
+
const worldY = prev.y + screenY / oldZoom;
|
|
587
|
+
return {
|
|
588
|
+
x: worldX - screenX / newZoom,
|
|
589
|
+
y: worldY - screenY / newZoom,
|
|
590
|
+
zoom: newZoom
|
|
591
|
+
};
|
|
592
|
+
});
|
|
593
|
+
},
|
|
594
|
+
[]
|
|
595
|
+
);
|
|
596
|
+
const reset = useCallback(() => setState(initial), [initial]);
|
|
597
|
+
const fitTo = useCallback((bounds, size) => {
|
|
598
|
+
const bboxWidth = Math.max(1, bounds.maxX - bounds.minX);
|
|
599
|
+
const bboxHeight = Math.max(1, bounds.maxY - bounds.minY);
|
|
600
|
+
const zoomX = size.width / bboxWidth;
|
|
601
|
+
const zoomY = size.height / bboxHeight;
|
|
602
|
+
const zoom = clampZoom(Math.min(zoomX, zoomY));
|
|
603
|
+
const cx = (bounds.minX + bounds.maxX) / 2;
|
|
604
|
+
const cy = (bounds.minY + bounds.maxY) / 2;
|
|
605
|
+
setState({
|
|
606
|
+
x: cx - size.width / (2 * zoom),
|
|
607
|
+
y: cy - size.height / (2 * zoom),
|
|
608
|
+
zoom
|
|
609
|
+
});
|
|
610
|
+
}, []);
|
|
611
|
+
const viewBox = useCallback(
|
|
612
|
+
(size) => `${state.x} ${state.y} ${size.width / state.zoom} ${size.height / state.zoom}`,
|
|
613
|
+
[state]
|
|
614
|
+
);
|
|
615
|
+
return { state, pan, setZoom, zoomAt, reset, fitTo, viewBox };
|
|
616
|
+
}
|
|
617
|
+
function computeBounds(scene) {
|
|
618
|
+
if (scene.elements.length === 0) return null;
|
|
619
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
620
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
621
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
622
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
623
|
+
for (const el of scene.elements) {
|
|
624
|
+
const x1 = el.x;
|
|
625
|
+
const y1 = el.y;
|
|
626
|
+
let x2 = el.x;
|
|
627
|
+
let y2 = el.y;
|
|
628
|
+
if (el.type === "rect" || el.type === "ellipse" || el.type === "diamond") {
|
|
629
|
+
x2 = el.x + el.w;
|
|
630
|
+
y2 = el.y + el.h;
|
|
631
|
+
} else if (el.type === "line" || el.type === "arrow") {
|
|
632
|
+
x2 = el.to[0];
|
|
633
|
+
y2 = el.to[1];
|
|
634
|
+
} else if (el.type === "freedraw") {
|
|
635
|
+
for (const [px, py] of el.points) {
|
|
636
|
+
const tx = el.x + px;
|
|
637
|
+
const ty = el.y + py;
|
|
638
|
+
if (tx < minX) minX = tx;
|
|
639
|
+
if (ty < minY) minY = ty;
|
|
640
|
+
if (tx > maxX) maxX = tx;
|
|
641
|
+
if (ty > maxY) maxY = ty;
|
|
642
|
+
}
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
if (x1 < minX) minX = x1;
|
|
646
|
+
if (y1 < minY) minY = y1;
|
|
647
|
+
if (x2 < minX) minX = x2;
|
|
648
|
+
if (y2 < minY) minY = y2;
|
|
649
|
+
if (x1 > maxX) maxX = x1;
|
|
650
|
+
if (y1 > maxY) maxY = y1;
|
|
651
|
+
if (x2 > maxX) maxX = x2;
|
|
652
|
+
if (y2 > maxY) maxY = y2;
|
|
653
|
+
}
|
|
654
|
+
if (!Number.isFinite(minX)) return null;
|
|
655
|
+
return { minX, minY, maxX, maxY };
|
|
656
|
+
}
|
|
657
|
+
function Whiteboard({
|
|
658
|
+
data,
|
|
659
|
+
className,
|
|
660
|
+
initialZoom,
|
|
661
|
+
initialCenter,
|
|
662
|
+
fitOnLoad,
|
|
663
|
+
onValidationError,
|
|
664
|
+
"aria-label": ariaLabel
|
|
665
|
+
}) {
|
|
666
|
+
const validation = useMemo(() => validateScene(data), [data]);
|
|
667
|
+
const scene = validation.ok ? validation.scene : null;
|
|
668
|
+
useEffect(() => {
|
|
669
|
+
if (!validation.ok && onValidationError) {
|
|
670
|
+
onValidationError(validation.errors);
|
|
671
|
+
}
|
|
672
|
+
}, [validation, onValidationError]);
|
|
673
|
+
const generator = useMemo(() => {
|
|
674
|
+
if (typeof globalThis === "undefined") return null;
|
|
675
|
+
try {
|
|
676
|
+
return rough.generator();
|
|
677
|
+
} catch {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
}, []);
|
|
681
|
+
const sceneWidth = scene?.width ?? 400;
|
|
682
|
+
const sceneHeight = scene?.height ?? 300;
|
|
683
|
+
const sceneBackground = scene?.background;
|
|
684
|
+
const viewport = useViewport({
|
|
685
|
+
width: sceneWidth,
|
|
686
|
+
height: sceneHeight,
|
|
687
|
+
initialZoom,
|
|
688
|
+
initialCenter
|
|
689
|
+
});
|
|
690
|
+
const svgRef = useRef(null);
|
|
691
|
+
const fitTo = viewport.fitTo;
|
|
692
|
+
useEffect(() => {
|
|
693
|
+
if (!fitOnLoad || !scene) return;
|
|
694
|
+
const bounds = computeBounds(scene);
|
|
695
|
+
if (!bounds) return;
|
|
696
|
+
fitTo(bounds, { width: sceneWidth, height: sceneHeight });
|
|
697
|
+
}, [fitOnLoad, scene, sceneWidth, sceneHeight, fitTo]);
|
|
698
|
+
const handlers = usePointerPan(svgRef, viewport, {
|
|
699
|
+
width: sceneWidth,
|
|
700
|
+
height: sceneHeight
|
|
701
|
+
});
|
|
702
|
+
const label = ariaLabel ?? "Whiteboard diagram";
|
|
703
|
+
return /* @__PURE__ */ jsxs(
|
|
704
|
+
"svg",
|
|
705
|
+
{
|
|
706
|
+
ref: svgRef,
|
|
707
|
+
viewBox: viewport.viewBox({ width: sceneWidth, height: sceneHeight }),
|
|
708
|
+
width: sceneWidth,
|
|
709
|
+
height: sceneHeight,
|
|
710
|
+
className,
|
|
711
|
+
role: "img",
|
|
712
|
+
"aria-label": label,
|
|
713
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
714
|
+
"data-whiteboard-state": scene ? "ok" : "invalid",
|
|
715
|
+
style: { touchAction: "none", userSelect: "none" },
|
|
716
|
+
...handlers,
|
|
717
|
+
children: [
|
|
718
|
+
/* @__PURE__ */ jsx("title", { children: label }),
|
|
719
|
+
sceneBackground ? /* @__PURE__ */ jsx(
|
|
720
|
+
"rect",
|
|
721
|
+
{
|
|
722
|
+
x: 0,
|
|
723
|
+
y: 0,
|
|
724
|
+
width: sceneWidth,
|
|
725
|
+
height: sceneHeight,
|
|
726
|
+
fill: sceneBackground,
|
|
727
|
+
"data-layer": "background"
|
|
728
|
+
}
|
|
729
|
+
) : null,
|
|
730
|
+
scene && generator ? renderScene(scene, generator) : null
|
|
731
|
+
]
|
|
732
|
+
}
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
export { Whiteboard, validateScene };
|
|
737
|
+
//# sourceMappingURL=index.js.map
|
|
738
|
+
//# sourceMappingURL=index.js.map
|