@braincrew-lab/langchain-canvas 0.1.7 → 0.1.9
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/{ChartRenderer-CKCJ2LQX.js → ChartRenderer-4MWKP2J4.js} +2 -2
- package/dist/{DocumentRenderer-7HP3YQUK.js → DocumentRenderer-LJGP6N7E.js} +2 -2
- package/dist/{SlidesRenderer-KFSPD63I.js → SlidesRenderer-7ICJQHYT.js} +98 -10
- package/dist/{TableRenderer-TRYSMQA7.js → TableRenderer-ZF7MVA7I.js} +2 -2
- package/dist/{chunk-PSIT32I5.js → chunk-KKLWKR5G.js} +27 -6
- package/dist/{chunk-HHJWIO2C.js → chunk-TERWLUW3.js} +1 -1
- package/dist/index.d.ts +27 -5
- package/dist/index.js +244 -16
- package/dist/styles.css +10 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useArtifactPatch } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
import { useArtifactPatch } from './chunk-TERWLUW3.js';
|
|
2
|
+
import './chunk-KKLWKR5G.js';
|
|
3
3
|
import { useState, useMemo, useRef, useEffect } from 'react';
|
|
4
4
|
import * as echarts from 'echarts/core';
|
|
5
5
|
import { BarChart, LineChart, PieChart } from 'echarts/charts';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useArtifactPatch } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
import { useArtifactPatch } from './chunk-TERWLUW3.js';
|
|
2
|
+
import './chunk-KKLWKR5G.js';
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import ReactMarkdown from 'react-markdown';
|
|
5
5
|
import remarkGfm from 'remark-gfm';
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { resolveElements } from './chunk-6L3AL6W4.js';
|
|
2
|
-
import { useArtifactPatch } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
import { useArtifactPatch } from './chunk-TERWLUW3.js';
|
|
3
|
+
import './chunk-KKLWKR5G.js';
|
|
4
4
|
import { useState, useRef, useEffect } from 'react';
|
|
5
5
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
6
|
|
|
7
|
+
function shapeStyle(el) {
|
|
8
|
+
const fill = el.fill ?? "currentColor";
|
|
9
|
+
if (el.shape === "ellipse") return { width: "100%", height: "100%", background: fill, borderRadius: "50%" };
|
|
10
|
+
if (el.shape === "line") return { width: "100%", height: "100%", background: fill, borderRadius: 2 };
|
|
11
|
+
return { width: "100%", height: "100%", background: fill, borderRadius: 8 };
|
|
12
|
+
}
|
|
7
13
|
var clamp = (v, min, max) => Math.max(min, Math.min(max, v));
|
|
8
14
|
var dupSeq = 0;
|
|
9
15
|
var dupId = (base) => `${base}_c${Date.now().toString(36)}${dupSeq++}`;
|
|
@@ -175,7 +181,7 @@ function FreeSlide({ elements, onChange }) {
|
|
|
175
181
|
},
|
|
176
182
|
children: el.text
|
|
177
183
|
}
|
|
178
|
-
) : /* @__PURE__ */ jsx("img", { className: "cv-free__img", src: el.src, alt: "", draggable: false }),
|
|
184
|
+
) : el.type === "shape" ? /* @__PURE__ */ jsx("div", { style: shapeStyle(el) }) : /* @__PURE__ */ jsx("img", { className: "cv-free__img", src: el.src, alt: "", draggable: false }),
|
|
179
185
|
selected === el.id && el.type === "text" && /* @__PURE__ */ jsxs("div", { className: `cv-free__fmt ${el.y < 16 ? "cv-free__fmt--below" : ""}`, onPointerDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation(), children: [
|
|
180
186
|
/* @__PURE__ */ jsx("button", { className: el.bold ? "is-on" : "", onClick: () => updateEl(el.id, { bold: !el.bold }), title: "Bold", children: /* @__PURE__ */ jsx("b", { children: "B" }) }),
|
|
181
187
|
/* @__PURE__ */ jsx(
|
|
@@ -194,6 +200,7 @@ function FreeSlide({ elements, onChange }) {
|
|
|
194
200
|
/* @__PURE__ */ jsx("button", { onClick: () => updateEl(el.id, { align: "center" }), title: "Align center", children: "\u2261" }),
|
|
195
201
|
/* @__PURE__ */ jsx("button", { onClick: () => updateEl(el.id, { align: "right" }), title: "Align right", children: "\u27F9" })
|
|
196
202
|
] }),
|
|
203
|
+
selected === el.id && el.type === "shape" && /* @__PURE__ */ jsx("div", { className: `cv-free__fmt ${el.y < 16 ? "cv-free__fmt--below" : ""}`, onPointerDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx("input", { type: "color", value: el.fill ?? "#5b5bd6", onChange: (e) => updateEl(el.id, { fill: e.target.value }), title: "Fill color" }) }),
|
|
197
204
|
selected === el.id && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
198
205
|
/* @__PURE__ */ jsx("span", { className: "cv-free__resize", onPointerDown: (e) => onDown(e, el, "resize") }),
|
|
199
206
|
/* @__PURE__ */ jsxs("div", { className: `cv-free__ctl ${el.y < 16 ? "cv-free__ctl--below" : ""}`, onPointerDown: (e) => e.stopPropagation(), children: [
|
|
@@ -225,11 +232,41 @@ function FreeSlide({ elements, onChange }) {
|
|
|
225
232
|
}
|
|
226
233
|
var THEMES = [
|
|
227
234
|
{ id: "light", label: "Light", bg: "#ffffff", text: "#1f2328" },
|
|
235
|
+
{ id: "paper", label: "Paper", bg: "#f7f5ef", text: "#2b2a26" },
|
|
228
236
|
{ id: "dark", label: "Dark", bg: "#14171f", text: "#f0f2f5" },
|
|
229
237
|
{ id: "midnight", label: "Midnight", bg: "#0b1020", text: "#e6e8ef" },
|
|
238
|
+
{ id: "navy", label: "Navy", bg: "#0d1b3e", text: "#eef2ff" },
|
|
239
|
+
{ id: "forest", label: "Forest", bg: "#0f2a22", text: "#e6f2ec" },
|
|
230
240
|
{ id: "sunset", label: "Sunset", bg: "#2b1a2e", text: "#ffe8d6" },
|
|
231
241
|
{ id: "mint", label: "Mint", bg: "#0f2a24", text: "#d7f5ec" }
|
|
232
242
|
];
|
|
243
|
+
var SHAPES = [
|
|
244
|
+
{ id: "rect", label: "\u25AD Rectangle", box: { w: 30, h: 20 } },
|
|
245
|
+
{ id: "ellipse", label: "\u25EF Ellipse", box: { w: 24, h: 24 } },
|
|
246
|
+
{ id: "line", label: "\u2014 Line", box: { w: 40, h: 2 } }
|
|
247
|
+
];
|
|
248
|
+
var LAYOUTS = {
|
|
249
|
+
title: { label: "Title", els: [
|
|
250
|
+
{ type: "text", x: 12, y: 34, w: 76, h: 18, text: "Presentation title", fontSize: 54, bold: true, align: "center" },
|
|
251
|
+
{ type: "text", x: 20, y: 56, w: 60, h: 10, text: "Subtitle or one-line summary", fontSize: 24, align: "center" }
|
|
252
|
+
] },
|
|
253
|
+
section: { label: "Section", els: [
|
|
254
|
+
{ type: "shape", shape: "rect", x: 10, y: 34, w: 8, h: 3, fill: "#5b5bd6" },
|
|
255
|
+
{ type: "text", x: 10, y: 40, w: 80, h: 16, text: "Section title", fontSize: 46, bold: true }
|
|
256
|
+
] },
|
|
257
|
+
bullets: { label: "Bullets", els: [
|
|
258
|
+
{ type: "text", x: 10, y: 12, w: 80, h: 12, text: "Heading", fontSize: 36, bold: true },
|
|
259
|
+
{ type: "text", x: 10, y: 30, w: 80, h: 50, text: "\u2022 First key point\n\u2022 Second key point\n\u2022 Third key point", fontSize: 26 }
|
|
260
|
+
] },
|
|
261
|
+
"two-column": { label: "Two column", els: [
|
|
262
|
+
{ type: "text", x: 8, y: 16, w: 40, h: 60, text: "Left column\n\n\u2022 point\n\u2022 point", fontSize: 24 },
|
|
263
|
+
{ type: "text", x: 52, y: 16, w: 40, h: 60, text: "Right column\n\n\u2022 point\n\u2022 point", fontSize: 24 }
|
|
264
|
+
] },
|
|
265
|
+
quote: { label: "Quote", els: [
|
|
266
|
+
{ type: "text", x: 12, y: 30, w: 76, h: 30, text: "\u201CA short, memorable quote.\u201D", fontSize: 40, bold: true },
|
|
267
|
+
{ type: "text", x: 12, y: 62, w: 50, h: 8, text: "\u2014 Attribution", fontSize: 22 }
|
|
268
|
+
] }
|
|
269
|
+
};
|
|
233
270
|
var elementSeq = 0;
|
|
234
271
|
var newElementId = () => `el_${Date.now().toString(36)}_${elementSeq++}`;
|
|
235
272
|
function SlidesRenderer({ artifact }) {
|
|
@@ -239,6 +276,7 @@ function SlidesRenderer({ artifact }) {
|
|
|
239
276
|
const [dragIndex, setDragIndex] = useState(null);
|
|
240
277
|
const [presenting, setPresenting] = useState(false);
|
|
241
278
|
const imgRef = useRef(null);
|
|
279
|
+
const bgRef = useRef(null);
|
|
242
280
|
useEffect(() => {
|
|
243
281
|
if (!presenting) return;
|
|
244
282
|
const onKey = (e) => {
|
|
@@ -301,6 +339,20 @@ function SlidesRenderer({ artifact }) {
|
|
|
301
339
|
reader.onload = () => addElement({ type: "image", x: 22, y: 22, w: 40, h: 34, src: String(reader.result) });
|
|
302
340
|
reader.readAsDataURL(file);
|
|
303
341
|
};
|
|
342
|
+
const addShapeEl = (shape) => {
|
|
343
|
+
const s = SHAPES.find((x) => x.id === shape);
|
|
344
|
+
if (s) addElement({ type: "shape", shape, x: 20, y: 20, w: s.box.w, h: s.box.h, fill: "#5b5bd6" });
|
|
345
|
+
};
|
|
346
|
+
const applyLayout = (key) => {
|
|
347
|
+
const l = LAYOUTS[key];
|
|
348
|
+
if (l) update({ elements: l.els.map((e) => ({ ...e, id: newElementId() })) });
|
|
349
|
+
};
|
|
350
|
+
const setSlideBgImage = (file) => {
|
|
351
|
+
if (!file) return;
|
|
352
|
+
const reader = new FileReader();
|
|
353
|
+
reader.onload = () => update({ background: `#000 url("${String(reader.result)}") center/cover no-repeat` });
|
|
354
|
+
reader.readAsDataURL(file);
|
|
355
|
+
};
|
|
304
356
|
return /* @__PURE__ */ jsxs("div", { className: "cv-deck", children: [
|
|
305
357
|
/* @__PURE__ */ jsxs("aside", { className: "cv-deck__rail cv-chrome", children: [
|
|
306
358
|
slides.map((s, i) => /* @__PURE__ */ jsx(
|
|
@@ -321,11 +373,11 @@ function SlidesRenderer({ artifact }) {
|
|
|
321
373
|
(el) => el.type === "text" ? /* @__PURE__ */ jsx(
|
|
322
374
|
"span",
|
|
323
375
|
{
|
|
324
|
-
style: { position: "absolute", left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, fontSize: (el.fontSize ?? 24) * 0.12, fontWeight: el.bold ? 700 : 400, color: el.color ?? s.textColor, overflow: "hidden" },
|
|
376
|
+
style: { position: "absolute", left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, fontSize: (el.fontSize ?? 24) * 0.12, fontWeight: el.bold ? 700 : 400, color: el.color ?? s.textColor, overflow: "hidden", whiteSpace: "pre-wrap" },
|
|
325
377
|
children: el.text
|
|
326
378
|
},
|
|
327
379
|
el.id
|
|
328
|
-
) : /* @__PURE__ */ jsx("img", { src: el.src, alt: "", style: { position: "absolute", left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%`, objectFit: "contain" } }, el.id)
|
|
380
|
+
) : el.type === "shape" ? /* @__PURE__ */ jsx("div", { style: { position: "absolute", left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%`, color: s.textColor, ...shapeStyle(el) } }, el.id) : /* @__PURE__ */ jsx("img", { src: el.src, alt: "", style: { position: "absolute", left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%`, objectFit: "contain" } }, el.id)
|
|
329
381
|
) })
|
|
330
382
|
] })
|
|
331
383
|
},
|
|
@@ -338,6 +390,39 @@ function SlidesRenderer({ artifact }) {
|
|
|
338
390
|
/* @__PURE__ */ jsx("button", { onClick: addTextEl, title: "Add text box", children: "+ Text" }),
|
|
339
391
|
/* @__PURE__ */ jsx("button", { onClick: () => imgRef.current?.click(), title: "Add image", children: "+ Image" }),
|
|
340
392
|
/* @__PURE__ */ jsx("input", { ref: imgRef, type: "file", accept: "image/*", hidden: true, onChange: (e) => addImageEl(e.target.files?.[0]) }),
|
|
393
|
+
/* @__PURE__ */ jsx("input", { ref: bgRef, type: "file", accept: "image/*", hidden: true, onChange: (e) => setSlideBgImage(e.target.files?.[0]) }),
|
|
394
|
+
/* @__PURE__ */ jsxs(
|
|
395
|
+
"select",
|
|
396
|
+
{
|
|
397
|
+
className: "cv-deck__theme",
|
|
398
|
+
value: "",
|
|
399
|
+
title: "Add a shape",
|
|
400
|
+
onChange: (e) => {
|
|
401
|
+
if (e.target.value) addShapeEl(e.target.value);
|
|
402
|
+
e.currentTarget.value = "";
|
|
403
|
+
},
|
|
404
|
+
children: [
|
|
405
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "+ Shape" }),
|
|
406
|
+
SHAPES.map((s) => /* @__PURE__ */ jsx("option", { value: s.id, children: s.label }, s.id))
|
|
407
|
+
]
|
|
408
|
+
}
|
|
409
|
+
),
|
|
410
|
+
/* @__PURE__ */ jsxs(
|
|
411
|
+
"select",
|
|
412
|
+
{
|
|
413
|
+
className: "cv-deck__theme",
|
|
414
|
+
value: "",
|
|
415
|
+
title: "Apply a layout",
|
|
416
|
+
onChange: (e) => {
|
|
417
|
+
if (e.target.value) applyLayout(e.target.value);
|
|
418
|
+
e.currentTarget.value = "";
|
|
419
|
+
},
|
|
420
|
+
children: [
|
|
421
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Layout\u2026" }),
|
|
422
|
+
Object.entries(LAYOUTS).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
423
|
+
]
|
|
424
|
+
}
|
|
425
|
+
),
|
|
341
426
|
/* @__PURE__ */ jsxs(
|
|
342
427
|
"select",
|
|
343
428
|
{
|
|
@@ -347,14 +432,16 @@ function SlidesRenderer({ artifact }) {
|
|
|
347
432
|
onChange: (e) => {
|
|
348
433
|
const t = THEMES.find((x) => x.id === e.target.value);
|
|
349
434
|
if (t) update({ background: t.bg, textColor: t.text });
|
|
435
|
+
e.currentTarget.value = "";
|
|
350
436
|
},
|
|
351
437
|
children: [
|
|
352
|
-
/* @__PURE__ */ jsx("option", { value: "",
|
|
438
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Theme\u2026" }),
|
|
353
439
|
THEMES.map((t) => /* @__PURE__ */ jsx("option", { value: t.id, children: t.label }, t.id))
|
|
354
440
|
]
|
|
355
441
|
}
|
|
356
442
|
),
|
|
357
|
-
/* @__PURE__ */ jsx("label", { className: "cv-deck__bg", title: "Background color", children: /* @__PURE__ */ jsx("input", { type: "color", value: slide.background ?? "#ffffff", onChange: (e) => update({ background: e.target.value }) }) }),
|
|
443
|
+
/* @__PURE__ */ jsx("label", { className: "cv-deck__bg", title: "Background color", children: /* @__PURE__ */ jsx("input", { type: "color", value: /^#/.test(slide.background ?? "") ? slide.background : "#ffffff", onChange: (e) => update({ background: e.target.value }) }) }),
|
|
444
|
+
/* @__PURE__ */ jsx("button", { onClick: () => bgRef.current?.click(), title: "Background image", children: "\u{1F5BC} BG" }),
|
|
358
445
|
/* @__PURE__ */ jsx("span", { className: "cv-deck__spacer" }),
|
|
359
446
|
/* @__PURE__ */ jsx("button", { className: "cv-deck__present", onClick: () => setPresenting(true), title: "Present (full screen)", children: "\u25B6 Present" }),
|
|
360
447
|
/* @__PURE__ */ jsx("button", { onClick: () => moveSlide(-1), title: "Move up", disabled: at === 0, children: "\u25B2" }),
|
|
@@ -383,9 +470,10 @@ function SlidesRenderer({ artifact }) {
|
|
|
383
470
|
)
|
|
384
471
|
] }),
|
|
385
472
|
presenting && /* @__PURE__ */ jsxs("div", { className: "cv-present", onClick: () => setIndex(Math.min(at + 1, slides.length - 1)), children: [
|
|
386
|
-
/* @__PURE__ */ jsx("div", { className: "cv-present__slide cv-slide cv-slide--blank", style: slideStyle, children: /* @__PURE__ */ jsx("div", { className: "cv-free", children: resolveElements(slide).map(
|
|
387
|
-
(el) => el.type === "text" ? /* @__PURE__ */ jsx("div", { className: "cv-free__el", style: { left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%` }, children: /* @__PURE__ */ jsx("div", { className: "cv-free__text", style: { fontSize: el.fontSize ?? 24, fontWeight: el.bold ? 700 : 400, color: el.color, textAlign: el.align ?? "left" }, children: el.text }) }, el.id) : /* @__PURE__ */ jsx("div", { className: "cv-free__el", style: { left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%` }, children: /* @__PURE__ */ jsx("img", { className: "cv-free__img", src: el.src, alt: "" }) }, el.id)
|
|
388
|
-
) }) }),
|
|
473
|
+
/* @__PURE__ */ jsx("div", { className: "cv-present__slide cv-present__fade cv-slide cv-slide--blank", style: slideStyle, children: /* @__PURE__ */ jsx("div", { className: "cv-free", children: resolveElements(slide).map(
|
|
474
|
+
(el) => el.type === "text" ? /* @__PURE__ */ jsx("div", { className: "cv-free__el", style: { left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%` }, children: /* @__PURE__ */ jsx("div", { className: "cv-free__text", style: { fontSize: el.fontSize ?? 24, fontWeight: el.bold ? 700 : 400, color: el.color, textAlign: el.align ?? "left", whiteSpace: "pre-wrap" }, children: el.text }) }, el.id) : el.type === "shape" ? /* @__PURE__ */ jsx("div", { className: "cv-free__el", style: { left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%`, color: slide.textColor }, children: /* @__PURE__ */ jsx("div", { style: shapeStyle(el) }) }, el.id) : /* @__PURE__ */ jsx("div", { className: "cv-free__el", style: { left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, height: `${el.h}%` }, children: /* @__PURE__ */ jsx("img", { className: "cv-free__img", src: el.src, alt: "" }) }, el.id)
|
|
475
|
+
) }) }, at),
|
|
476
|
+
slide.notes ? /* @__PURE__ */ jsx("div", { className: "cv-present__notes", onClick: (e) => e.stopPropagation(), children: slide.notes }) : null,
|
|
389
477
|
/* @__PURE__ */ jsxs("div", { className: "cv-present__hint", children: [
|
|
390
478
|
at + 1,
|
|
391
479
|
" / ",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loadOptional } from './chunk-YZZSJJMQ.js';
|
|
2
|
-
import { useCanvasStore } from './chunk-
|
|
2
|
+
import { useCanvasStore } from './chunk-KKLWKR5G.js';
|
|
3
3
|
import { lazy, useMemo, useState, useRef, useEffect, useCallback, Suspense } from 'react';
|
|
4
4
|
import '@fortune-sheet/react/dist/index.css';
|
|
5
5
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
@@ -208,7 +208,7 @@ function TableRenderer({ artifact }) {
|
|
|
208
208
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
209
209
|
[dataKey, formulasReady]
|
|
210
210
|
);
|
|
211
|
-
const applyEvent = useCanvasStore((s) => s.
|
|
211
|
+
const applyEvent = useCanvasStore((s) => s.applyUserEvent);
|
|
212
212
|
const persistTimer = useRef(null);
|
|
213
213
|
const handleChange = useCallback(
|
|
214
214
|
(sheets) => {
|
|
@@ -129,6 +129,19 @@ function lastOf(order, excludeId) {
|
|
|
129
129
|
const remaining = order.filter((id) => id !== excludeId);
|
|
130
130
|
return remaining.length ? remaining[remaining.length - 1] : null;
|
|
131
131
|
}
|
|
132
|
+
function editedArtifactId(event) {
|
|
133
|
+
switch (event.type) {
|
|
134
|
+
case "canvas.create":
|
|
135
|
+
case "canvas.replace":
|
|
136
|
+
return event.artifact.id;
|
|
137
|
+
case "canvas.append":
|
|
138
|
+
case "canvas.patch":
|
|
139
|
+
case "canvas.node_patch":
|
|
140
|
+
return event.id;
|
|
141
|
+
default:
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
132
145
|
var initialState = () => ({
|
|
133
146
|
canvas: emptyCanvasState(),
|
|
134
147
|
messages: [],
|
|
@@ -137,17 +150,24 @@ var initialState = () => ({
|
|
|
137
150
|
selections: [],
|
|
138
151
|
iframeCommand: null,
|
|
139
152
|
undoStack: [],
|
|
140
|
-
redoStack: []
|
|
153
|
+
redoStack: [],
|
|
154
|
+
onUserEdit: null
|
|
141
155
|
});
|
|
142
156
|
function createCanvasStore() {
|
|
143
|
-
return createStore((set) => ({
|
|
157
|
+
return createStore((set, get) => ({
|
|
144
158
|
...initialState(),
|
|
145
159
|
applyEvent: (event) => set((state) => foldEvent(state, event)),
|
|
146
160
|
applyEvents: (events) => set((state) => events.reduce(foldEvent, state)),
|
|
147
|
-
applyUserEvent: (event) =>
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
161
|
+
applyUserEvent: (event) => {
|
|
162
|
+
set((state2) => {
|
|
163
|
+
const undoStack = [...state2.undoStack, state2.canvas].slice(-50);
|
|
164
|
+
return { ...foldEvent(state2, event), undoStack, redoStack: [] };
|
|
165
|
+
});
|
|
166
|
+
const state = get();
|
|
167
|
+
const id = editedArtifactId(event);
|
|
168
|
+
const artifact = id ? state.canvas.artifacts[id] : void 0;
|
|
169
|
+
if (artifact) state.onUserEdit?.(artifact);
|
|
170
|
+
},
|
|
151
171
|
undo: () => set((state) => {
|
|
152
172
|
if (!state.undoStack.length) return state;
|
|
153
173
|
const previous = state.undoStack[state.undoStack.length - 1];
|
|
@@ -176,6 +196,7 @@ function createCanvasStore() {
|
|
|
176
196
|
setActiveArtifact: (id) => set((state) => ({ canvas: { ...state.canvas, activeId: id } })),
|
|
177
197
|
setSelections: (selections) => set({ selections }),
|
|
178
198
|
sendIframeCommand: (command) => set((state) => ({ iframeCommand: { ...command, seq: (state.iframeCommand?.seq ?? 0) + 1 } })),
|
|
199
|
+
setOnUserEdit: (handler) => set({ onUserEdit: handler }),
|
|
179
200
|
reset: () => set(initialState())
|
|
180
201
|
}));
|
|
181
202
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -83,7 +83,7 @@ interface TableData {
|
|
|
83
83
|
/** A freely-positioned element on a "blank" slide (percent geometry, 0–100). */
|
|
84
84
|
interface SlideElement {
|
|
85
85
|
id: string;
|
|
86
|
-
type: "text" | "image";
|
|
86
|
+
type: "text" | "image" | "shape";
|
|
87
87
|
x: number;
|
|
88
88
|
y: number;
|
|
89
89
|
w: number;
|
|
@@ -94,6 +94,10 @@ interface SlideElement {
|
|
|
94
94
|
bold?: boolean;
|
|
95
95
|
color?: string;
|
|
96
96
|
align?: "left" | "center" | "right";
|
|
97
|
+
/** Shape kind for `type: "shape"`. */
|
|
98
|
+
shape?: "rect" | "ellipse" | "line";
|
|
99
|
+
/** Fill (rect/ellipse) or stroke (line) color for a shape. */
|
|
100
|
+
fill?: string;
|
|
97
101
|
}
|
|
98
102
|
interface Slide {
|
|
99
103
|
/** title · content (bullets) · section · image · two-column · blank (free canvas). */
|
|
@@ -355,6 +359,10 @@ declare function mockStream(events: StreamEvent[], options?: MockStreamOptions):
|
|
|
355
359
|
* patching, and versioning stay in one auditable place.
|
|
356
360
|
*/
|
|
357
361
|
|
|
362
|
+
/** Fired when the *user* edits an artifact in the canvas (a table cell, a chart
|
|
363
|
+
* value, document text, a slide/HTML element). The host wires this to sync the
|
|
364
|
+
* edit back to the agent/backend so the next turn sees it. */
|
|
365
|
+
type UserEditHandler = (artifact: Artifact) => void;
|
|
358
366
|
interface ChatMessage {
|
|
359
367
|
id: string;
|
|
360
368
|
role: "user" | "assistant";
|
|
@@ -365,14 +373,16 @@ interface ChatMessage {
|
|
|
365
373
|
/** A command the editing UI forwards to the active html artifact's iframe. */
|
|
366
374
|
interface IframeCommand {
|
|
367
375
|
artifactId: string;
|
|
368
|
-
/** style · structure (duplicate/delete/move/insert/insert_html) · group/ungroup · set_src · clear. */
|
|
369
|
-
type: "set_style" | "commit" | "clear" | "set_src" | "duplicate" | "delete" | "move_up" | "move_down" | "insert" | "insert_html" | "group" | "ungroup";
|
|
376
|
+
/** style · structure (duplicate/delete/move/insert/insert_html) · group/ungroup · set_src · set_slide_style · clear. */
|
|
377
|
+
type: "set_style" | "style_persist" | "commit" | "clear" | "set_src" | "set_slide_style" | "duplicate" | "delete" | "move_up" | "move_down" | "insert" | "insert_html" | "group" | "ungroup";
|
|
370
378
|
/** Target element (omitted for document-level inserts with no selection). */
|
|
371
379
|
cid?: string;
|
|
372
380
|
/** Members to wrap for `group`. */
|
|
373
381
|
cids?: string[];
|
|
374
382
|
prop?: string;
|
|
375
383
|
value?: string;
|
|
384
|
+
/** Style map to apply to the slide root for `set_slide_style` (e.g. background, color). */
|
|
385
|
+
style?: Record<string, string>;
|
|
376
386
|
/** Tag/block to insert for `insert` (e.g. "h2", "p", "button", "img", "hr", "section"). */
|
|
377
387
|
block?: string;
|
|
378
388
|
/** HTML fragment to insert for `insert_html` (a built-in section template). */
|
|
@@ -392,6 +402,8 @@ interface CanvasStore {
|
|
|
392
402
|
/** Snapshots for undo/redo — only user edits are recorded (not agent streaming). */
|
|
393
403
|
undoStack: CanvasState[];
|
|
394
404
|
redoStack: CanvasState[];
|
|
405
|
+
/** Host callback fired after a user edit reconciles — the write-back hook. */
|
|
406
|
+
onUserEdit: UserEditHandler | null;
|
|
395
407
|
applyEvent: (event: StreamEvent) => void;
|
|
396
408
|
/** Apply a batch of events in a single store write (one re-render per frame). */
|
|
397
409
|
applyEvents: (events: StreamEvent[]) => void;
|
|
@@ -404,6 +416,8 @@ interface CanvasStore {
|
|
|
404
416
|
setActiveArtifact: (id: string) => void;
|
|
405
417
|
setSelections: (selections: ElementSelection[]) => void;
|
|
406
418
|
sendIframeCommand: (command: Omit<IframeCommand, "seq">) => void;
|
|
419
|
+
/** Register (or clear) the user-edit write-back handler. */
|
|
420
|
+
setOnUserEdit: (handler: UserEditHandler | null) => void;
|
|
407
421
|
reset: () => void;
|
|
408
422
|
}
|
|
409
423
|
/** Create an isolated canvas store. */
|
|
@@ -508,8 +522,16 @@ interface CanvasProps {
|
|
|
508
522
|
* reveals a quick-edit bar.
|
|
509
523
|
*/
|
|
510
524
|
onEditElement?: (instruction: string) => void;
|
|
525
|
+
/**
|
|
526
|
+
* Fired after the *user* edits an artifact directly in the canvas — a table
|
|
527
|
+
* cell, a chart value, document text, a slide/HTML element — with the
|
|
528
|
+
* reconciled artifact. Wire this to sync the edit back to the agent/backend so
|
|
529
|
+
* the next turn sees it. Fires per committed edit (table edits are debounced);
|
|
530
|
+
* debounce further on the host before hitting the network.
|
|
531
|
+
*/
|
|
532
|
+
onUserEdit?: (artifact: Artifact) => void;
|
|
511
533
|
}
|
|
512
|
-
declare function Canvas({ registry, emptyState, onEditElement }: CanvasProps): react.JSX.Element;
|
|
534
|
+
declare function Canvas({ registry, emptyState, onEditElement, onUserEdit }: CanvasProps): react.JSX.Element;
|
|
513
535
|
|
|
514
536
|
interface ExportMenuProps {
|
|
515
537
|
artifact: Artifact;
|
|
@@ -666,4 +688,4 @@ declare function useCanvasImport(): {
|
|
|
666
688
|
canImport: (file: File) => boolean;
|
|
667
689
|
};
|
|
668
690
|
|
|
669
|
-
export { type Artifact, ArtifactCard, type ArtifactRegistry, type ArtifactRenderer, type ArtifactStatus, Canvas, type CanvasAppend, type CanvasClose, type CanvasCreate, type CanvasEvent, type CanvasNodePatch, type CanvasPatch, type CanvasProps, CanvasProvider, type CanvasProviderProps, CanvasRegistryProvider, type CanvasReplace, type CanvasState, type CanvasStatus, type CanvasStore, type ChartArtifact, type ChartData, type ChartOptions, ChartRenderer, type ChartSeries, type ChatEvent, type ChatMessage, type ChatRequest, type DocumentArtifact, type DocumentData, DocumentRenderer, type DoneEvent, type ElementSelection, type ErrorEvent, ExportMenu, type FileExport, type HtmlArtifact, type HtmlData, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK, type IframeCommand, type KnownArtifact, type MessageDelta, type MessageEnd, type MockStreamOptions, type RendererProps, STYLE_PROPS, type Scenario, SelectionBar, type Slide, type SlideElement, type SlidesArtifact, type SlidesData, SlidesRenderer, type StreamEvent, type StreamOptions, StylePanel, type TableArtifact, type TableColumn, type TableData, TableRenderer, type ToolEnd, type ToolStart, type UseCanvasStreamOptions, builtinRenderers, canImport, createCanvasStore, dataExporters, downloadBlob, emptyCanvasState, importFile, isCanvasEvent, isChatEvent, mergePatch, mergeRegistries, mockStream, parseCsv, parseSSE, printToPdf, reduceCanvas, scenarios, slidesToPrintHtml, slugify, streamChat, toStandaloneHtml, useArtifactPatch, useCanvasImport, useCanvasReplay, useCanvasStore, useCanvasStoreApi, useCanvasStream, useRenderer, withInspector };
|
|
691
|
+
export { type Artifact, ArtifactCard, type ArtifactRegistry, type ArtifactRenderer, type ArtifactStatus, Canvas, type CanvasAppend, type CanvasClose, type CanvasCreate, type CanvasEvent, type CanvasNodePatch, type CanvasPatch, type CanvasProps, CanvasProvider, type CanvasProviderProps, CanvasRegistryProvider, type CanvasReplace, type CanvasState, type CanvasStatus, type CanvasStore, type ChartArtifact, type ChartData, type ChartOptions, ChartRenderer, type ChartSeries, type ChatEvent, type ChatMessage, type ChatRequest, type DocumentArtifact, type DocumentData, DocumentRenderer, type DoneEvent, type ElementSelection, type ErrorEvent, ExportMenu, type FileExport, type HtmlArtifact, type HtmlData, HtmlRenderer, IMPORTABLE_EXTENSIONS, INSPECTOR_MARK, type IframeCommand, type KnownArtifact, type MessageDelta, type MessageEnd, type MockStreamOptions, type RendererProps, STYLE_PROPS, type Scenario, SelectionBar, type Slide, type SlideElement, type SlidesArtifact, type SlidesData, SlidesRenderer, type StreamEvent, type StreamOptions, StylePanel, type TableArtifact, type TableColumn, type TableData, TableRenderer, type ToolEnd, type ToolStart, type UseCanvasStreamOptions, type UserEditHandler, builtinRenderers, canImport, createCanvasStore, dataExporters, downloadBlob, emptyCanvasState, importFile, isCanvasEvent, isChatEvent, mergePatch, mergeRegistries, mockStream, parseCsv, parseSSE, printToPdf, reduceCanvas, scenarios, slidesToPrintHtml, slugify, streamChat, toStandaloneHtml, useArtifactPatch, useCanvasImport, useCanvasReplay, useCanvasStore, useCanvasStoreApi, useCanvasStream, useRenderer, withInspector };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { loadOptional } from './chunk-YZZSJJMQ.js';
|
|
3
3
|
import { resolveElements } from './chunk-6L3AL6W4.js';
|
|
4
|
-
export { useArtifactPatch } from './chunk-
|
|
5
|
-
import { useCanvasStoreApi, useCanvasStore } from './chunk-
|
|
6
|
-
export { CanvasProvider, createCanvasStore, emptyCanvasState, isCanvasEvent, isChatEvent, mergePatch, reduceCanvas, useCanvasStore, useCanvasStoreApi } from './chunk-
|
|
4
|
+
export { useArtifactPatch } from './chunk-TERWLUW3.js';
|
|
5
|
+
import { useCanvasStoreApi, useCanvasStore } from './chunk-KKLWKR5G.js';
|
|
6
|
+
export { CanvasProvider, createCanvasStore, emptyCanvasState, isCanvasEvent, isChatEvent, mergePatch, reduceCanvas, useCanvasStore, useCanvasStoreApi } from './chunk-KKLWKR5G.js';
|
|
7
7
|
import { createContext, lazy, useRef, useCallback, useEffect, useContext, useState, useMemo, Suspense, Component } from 'react';
|
|
8
8
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
9
9
|
|
|
@@ -569,6 +569,13 @@ var INSPECTOR_SCRIPT = `
|
|
|
569
569
|
if (!d || d.source !== MARK) return;
|
|
570
570
|
if (d.type === "clear") { clearSelected(); return; }
|
|
571
571
|
if (d.type === "set_style") { var el = byCid(d.cid); if (el) el.style[d.prop] = d.value; return; }
|
|
572
|
+
if (d.type === "style_persist") { var elp = byCid(d.cid); if (elp) { elp.style[d.prop] = d.value; emitEdit(elp); } return; }
|
|
573
|
+
if (d.type === "set_slide_style") {
|
|
574
|
+
// Theme the whole slide: apply to the .slide-container (fallback body).
|
|
575
|
+
var root = document.querySelector(".slide-container") || document.body;
|
|
576
|
+
if (root && d.style) { for (var sk in d.style) { try { root.style[sk] = d.style[sk]; } catch (_e) {} } emitDoc(); }
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
572
579
|
if (d.type === "set_src") { var ei = byCid(d.cid); if (ei) { ei.setAttribute("src", d.value); emitEdit(ei); } return; }
|
|
573
580
|
if (d.type === "commit") { var el2 = byCid(d.cid); if (el2) emitEdit(el2); return; }
|
|
574
581
|
|
|
@@ -1493,6 +1500,7 @@ var BLOCKS = [
|
|
|
1493
1500
|
{ tag: "img", label: "Image" },
|
|
1494
1501
|
{ tag: "hr", label: "Divider" }
|
|
1495
1502
|
];
|
|
1503
|
+
var SLIDE_BLOCK_TAGS = /* @__PURE__ */ new Set(["h2", "p", "img"]);
|
|
1496
1504
|
var TEMPLATES = {
|
|
1497
1505
|
hero: {
|
|
1498
1506
|
label: "Hero",
|
|
@@ -1521,6 +1529,105 @@ var TEMPLATES = {
|
|
|
1521
1529
|
</section>`
|
|
1522
1530
|
}
|
|
1523
1531
|
};
|
|
1532
|
+
var SLIDE_TEMPLATES = {
|
|
1533
|
+
title: {
|
|
1534
|
+
label: "Title",
|
|
1535
|
+
html: `<div style="height:100%;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;padding:0 8%">
|
|
1536
|
+
<h1 style="font-size:64px;font-weight:800;letter-spacing:-.02em;margin:0 0 20px;line-height:1.1">Presentation title</h1>
|
|
1537
|
+
<p style="font-size:26px;opacity:.7;margin:0">Subtitle or one-line summary</p>
|
|
1538
|
+
</div>`
|
|
1539
|
+
},
|
|
1540
|
+
section: {
|
|
1541
|
+
label: "Section",
|
|
1542
|
+
html: `<div style="height:100%;display:flex;flex-direction:column;justify-content:center;padding:0 9%">
|
|
1543
|
+
<div style="width:72px;height:6px;border-radius:3px;background:currentColor;opacity:.85;margin-bottom:28px"></div>
|
|
1544
|
+
<h2 style="font-size:52px;font-weight:800;margin:0;line-height:1.1">Section title</h2>
|
|
1545
|
+
</div>`
|
|
1546
|
+
},
|
|
1547
|
+
bullets: {
|
|
1548
|
+
label: "Bullets",
|
|
1549
|
+
html: `<div style="height:100%;display:flex;flex-direction:column;justify-content:center;padding:0 9%">
|
|
1550
|
+
<h2 style="font-size:40px;font-weight:750;margin:0 0 28px">Heading</h2>
|
|
1551
|
+
<ul style="font-size:26px;line-height:1.9;margin:0;padding-left:1.1em">
|
|
1552
|
+
<li>First key point</li><li>Second key point</li><li>Third key point</li>
|
|
1553
|
+
</ul>
|
|
1554
|
+
</div>`
|
|
1555
|
+
},
|
|
1556
|
+
"two-column": {
|
|
1557
|
+
label: "Two column",
|
|
1558
|
+
html: `<div style="height:100%;display:grid;grid-template-columns:1fr 1fr;gap:56px;align-content:center;padding:0 9%">
|
|
1559
|
+
<div><h3 style="font-size:28px;font-weight:700;margin:0 0 14px">Left heading</h3><p style="font-size:22px;line-height:1.6;margin:0;opacity:.85">Supporting copy for the left column.</p></div>
|
|
1560
|
+
<div><h3 style="font-size:28px;font-weight:700;margin:0 0 14px">Right heading</h3><p style="font-size:22px;line-height:1.6;margin:0;opacity:.85">Supporting copy for the right column.</p></div>
|
|
1561
|
+
</div>`
|
|
1562
|
+
},
|
|
1563
|
+
"image-text": {
|
|
1564
|
+
label: "Image + text",
|
|
1565
|
+
html: `<div style="height:100%;display:grid;grid-template-columns:1fr 1fr;gap:56px;align-items:center;padding:0 9%">
|
|
1566
|
+
<img src="https://placehold.co/720x480/eef/335?text=Image" alt="image" style="width:100%;border-radius:16px;display:block" />
|
|
1567
|
+
<div><h2 style="font-size:36px;font-weight:750;margin:0 0 16px">Heading</h2><p style="font-size:24px;line-height:1.65;margin:0;opacity:.85">Explain the visual in a sentence or two.</p></div>
|
|
1568
|
+
</div>`
|
|
1569
|
+
},
|
|
1570
|
+
quote: {
|
|
1571
|
+
label: "Quote",
|
|
1572
|
+
html: `<div style="height:100%;display:flex;flex-direction:column;justify-content:center;padding:0 12%">
|
|
1573
|
+
<p style="font-size:44px;font-weight:700;line-height:1.3;margin:0 0 24px">\u201CA short, memorable quote that anchors the point.\u201D</p>
|
|
1574
|
+
<p style="font-size:22px;opacity:.65;margin:0">\u2014 Attribution</p>
|
|
1575
|
+
</div>`
|
|
1576
|
+
},
|
|
1577
|
+
agenda: {
|
|
1578
|
+
label: "Agenda",
|
|
1579
|
+
html: `<div style="height:100%;display:flex;flex-direction:column;justify-content:center;padding:0 9%">
|
|
1580
|
+
<h2 style="font-size:40px;font-weight:750;margin:0 0 28px">Agenda</h2>
|
|
1581
|
+
<ol style="font-size:25px;line-height:2;margin:0;padding-left:1.2em">
|
|
1582
|
+
<li>Background & goals</li><li>Approach</li><li>Results</li><li>Next steps</li>
|
|
1583
|
+
</ol>
|
|
1584
|
+
</div>`
|
|
1585
|
+
},
|
|
1586
|
+
stat: {
|
|
1587
|
+
label: "Big stat",
|
|
1588
|
+
html: `<div style="height:100%;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;padding:0 8%">
|
|
1589
|
+
<div style="font-size:140px;font-weight:800;letter-spacing:-.03em;line-height:1">12\xD7</div>
|
|
1590
|
+
<p style="font-size:28px;opacity:.7;margin:16px 0 0">faster than the previous approach</p>
|
|
1591
|
+
</div>`
|
|
1592
|
+
},
|
|
1593
|
+
comparison: {
|
|
1594
|
+
label: "Comparison",
|
|
1595
|
+
html: `<div style="height:100%;display:grid;grid-template-columns:1fr 1fr;gap:40px;align-content:center;padding:0 9%">
|
|
1596
|
+
<div style="border:2px solid currentColor;opacity:.9;border-radius:16px;padding:28px"><h3 style="font-size:26px;margin:0 0 14px">Before</h3><ul style="font-size:21px;line-height:1.8;margin:0;padding-left:1.1em"><li>Point one</li><li>Point two</li></ul></div>
|
|
1597
|
+
<div style="border-radius:16px;padding:28px;background:currentColor"><div style="color:#fff;mix-blend-mode:difference"><h3 style="font-size:26px;margin:0 0 14px">After</h3><ul style="font-size:21px;line-height:1.8;margin:0;padding-left:1.1em"><li>Point one</li><li>Point two</li></ul></div></div>
|
|
1598
|
+
</div>`
|
|
1599
|
+
},
|
|
1600
|
+
closing: {
|
|
1601
|
+
label: "Closing",
|
|
1602
|
+
html: `<div style="height:100%;display:flex;flex-direction:column;justify-content:center;align-items:center;text-align:center;padding:0 8%">
|
|
1603
|
+
<h1 style="font-size:56px;font-weight:800;margin:0 0 16px">Thank you</h1>
|
|
1604
|
+
<p style="font-size:24px;opacity:.7;margin:0">Questions? \xB7 contact@example.com</p>
|
|
1605
|
+
</div>`
|
|
1606
|
+
}
|
|
1607
|
+
};
|
|
1608
|
+
var SLIDE_THEMES = {
|
|
1609
|
+
light: { label: "Light", style: { background: "#ffffff", color: "#141821" } },
|
|
1610
|
+
paper: { label: "Paper", style: { background: "#f7f5ef", color: "#2b2a26" } },
|
|
1611
|
+
ink: { label: "Ink", style: { background: "#14161d", color: "#f0f1f5" } },
|
|
1612
|
+
navy: { label: "Navy", style: { background: "#0d1b3e", color: "#eef2ff" } },
|
|
1613
|
+
forest: { label: "Forest", style: { background: "#0f2a22", color: "#e6f2ec" } },
|
|
1614
|
+
sunset: { label: "Sunset", style: { background: "#2a1533", color: "#ffe8d6" } },
|
|
1615
|
+
brand: { label: "Brand", style: { background: "#ffffff", color: "#b01722" } }
|
|
1616
|
+
};
|
|
1617
|
+
var SLIDE_SHAPES = {
|
|
1618
|
+
rect: { label: "Rectangle", html: `<div style="width:280px;height:160px;background:currentColor;opacity:.9;border-radius:10px;margin:24px"></div>` },
|
|
1619
|
+
circle: { label: "Circle", html: `<div style="width:200px;height:200px;background:currentColor;opacity:.9;border-radius:50%;margin:24px"></div>` },
|
|
1620
|
+
line: { label: "Line", html: `<div style="width:360px;height:4px;background:currentColor;opacity:.85;margin:24px"></div>` },
|
|
1621
|
+
arrow: { label: "Arrow", html: `<svg width="240" height="60" viewBox="0 0 240 60" style="margin:24px;color:currentColor"><path d="M0 30 H210 M188 12 L212 30 L188 48" fill="none" stroke="currentColor" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/></svg>` },
|
|
1622
|
+
badge: { label: "Pill", html: `<span style="display:inline-block;padding:10px 22px;border-radius:999px;background:currentColor;color:#fff;mix-blend-mode:normal;font-weight:700;margin:24px">Label</span>` }
|
|
1623
|
+
};
|
|
1624
|
+
var SLIDE_FONTS = {
|
|
1625
|
+
sans: { label: "Sans", stack: "'Segoe UI', system-ui, 'Apple SD Gothic Neo', 'Noto Sans KR', sans-serif" },
|
|
1626
|
+
serif: { label: "Serif", stack: "Georgia, 'Times New Roman', 'Apple SD Gothic Neo', serif" },
|
|
1627
|
+
mono: { label: "Mono", stack: "ui-monospace, 'SF Mono', Menlo, Consolas, monospace" },
|
|
1628
|
+
rounded: { label: "Rounded", stack: "'Trebuchet MS', 'Segoe UI', 'Apple SD Gothic Neo', sans-serif" },
|
|
1629
|
+
condensed: { label: "Condensed", stack: "'Arial Narrow', 'Helvetica Neue', 'Apple SD Gothic Neo', sans-serif" }
|
|
1630
|
+
};
|
|
1524
1631
|
var SLIDE_W = 1280;
|
|
1525
1632
|
var SCROLL_FIX = "<style>html,body{overflow:auto!important;height:auto!important;min-height:100%!important}</style>";
|
|
1526
1633
|
function withScrollableBody(html) {
|
|
@@ -1550,6 +1657,7 @@ function useSlideFit(ratio, boxRef) {
|
|
|
1550
1657
|
function HtmlRenderer({ artifact: artifact2 }) {
|
|
1551
1658
|
const iframeRef = useRef(null);
|
|
1552
1659
|
const imgFileRef = useRef(null);
|
|
1660
|
+
const bgFileRef = useRef(null);
|
|
1553
1661
|
const stageRef = useRef(null);
|
|
1554
1662
|
const api = useCanvasStoreApi();
|
|
1555
1663
|
const setSelections = useCanvasStore((s) => s.setSelections);
|
|
@@ -1625,6 +1733,17 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1625
1733
|
reader.onload = () => sendIframeCommand({ artifactId: artifact2.id, type: "set_src", cid: single.cid, value: String(reader.result) });
|
|
1626
1734
|
reader.readAsDataURL(file);
|
|
1627
1735
|
};
|
|
1736
|
+
const onSlideBg = (file) => {
|
|
1737
|
+
if (!file) return;
|
|
1738
|
+
const reader = new FileReader();
|
|
1739
|
+
reader.onload = () => sendIframeCommand({
|
|
1740
|
+
artifactId: artifact2.id,
|
|
1741
|
+
type: "set_slide_style",
|
|
1742
|
+
style: { backgroundImage: `url("${String(reader.result)}")`, backgroundSize: "cover", backgroundPosition: "center" }
|
|
1743
|
+
});
|
|
1744
|
+
reader.readAsDataURL(file);
|
|
1745
|
+
};
|
|
1746
|
+
const setSlideStyle = (style) => sendIframeCommand({ artifactId: artifact2.id, type: "set_slide_style", style });
|
|
1628
1747
|
const ratio = artifact2.meta?.ratio;
|
|
1629
1748
|
const slide = useSlideFit(ratio, stageRef);
|
|
1630
1749
|
return /* @__PURE__ */ jsxs("div", { className: "cv-html-wrap", children: [
|
|
@@ -1632,6 +1751,10 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1632
1751
|
onImgFile(e.target.files?.[0]);
|
|
1633
1752
|
e.target.value = "";
|
|
1634
1753
|
} }),
|
|
1754
|
+
/* @__PURE__ */ jsx("input", { ref: bgFileRef, type: "file", accept: "image/*", hidden: true, onChange: (e) => {
|
|
1755
|
+
onSlideBg(e.target.files?.[0]);
|
|
1756
|
+
e.target.value = "";
|
|
1757
|
+
} }),
|
|
1635
1758
|
/* @__PURE__ */ jsxs("div", { className: "cv-html-bar cv-chrome", children: [
|
|
1636
1759
|
mode === "design" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1637
1760
|
!ratio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -1639,8 +1762,8 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1639
1762
|
/* @__PURE__ */ jsx("span", { className: "cv-html-bar__sep" })
|
|
1640
1763
|
] }),
|
|
1641
1764
|
/* @__PURE__ */ jsx("span", { className: "cv-html-bar__label", children: "Add" }),
|
|
1642
|
-
BLOCKS.map((b) => /* @__PURE__ */ jsx("button", { className: "cv-html-add", onClick: () => command("insert", { block: b.tag }), children: b.label }, b.tag)),
|
|
1643
|
-
/* @__PURE__ */ jsxs(
|
|
1765
|
+
(ratio ? BLOCKS.filter((b) => SLIDE_BLOCK_TAGS.has(b.tag)) : BLOCKS).map((b) => /* @__PURE__ */ jsx("button", { className: "cv-html-add", onClick: () => command("insert", { block: b.tag }), children: b.label }, b.tag)),
|
|
1766
|
+
!ratio && /* @__PURE__ */ jsxs(
|
|
1644
1767
|
"select",
|
|
1645
1768
|
{
|
|
1646
1769
|
className: "cv-html-tpl",
|
|
@@ -1657,6 +1780,77 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1657
1780
|
]
|
|
1658
1781
|
}
|
|
1659
1782
|
),
|
|
1783
|
+
ratio && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1784
|
+
/* @__PURE__ */ jsxs(
|
|
1785
|
+
"select",
|
|
1786
|
+
{
|
|
1787
|
+
className: "cv-html-tpl",
|
|
1788
|
+
value: "",
|
|
1789
|
+
title: "Insert a slide layout",
|
|
1790
|
+
onChange: (e) => {
|
|
1791
|
+
const t = SLIDE_TEMPLATES[e.target.value];
|
|
1792
|
+
if (t) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t.html });
|
|
1793
|
+
e.currentTarget.value = "";
|
|
1794
|
+
},
|
|
1795
|
+
children: [
|
|
1796
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Layout\u2026" }),
|
|
1797
|
+
Object.entries(SLIDE_TEMPLATES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
1798
|
+
]
|
|
1799
|
+
}
|
|
1800
|
+
),
|
|
1801
|
+
/* @__PURE__ */ jsxs(
|
|
1802
|
+
"select",
|
|
1803
|
+
{
|
|
1804
|
+
className: "cv-html-tpl",
|
|
1805
|
+
value: "",
|
|
1806
|
+
title: "Apply a slide theme",
|
|
1807
|
+
onChange: (e) => {
|
|
1808
|
+
const t = SLIDE_THEMES[e.target.value];
|
|
1809
|
+
if (t) sendIframeCommand({ artifactId: artifact2.id, type: "set_slide_style", style: t.style });
|
|
1810
|
+
e.currentTarget.value = "";
|
|
1811
|
+
},
|
|
1812
|
+
children: [
|
|
1813
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Theme\u2026" }),
|
|
1814
|
+
Object.entries(SLIDE_THEMES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
1815
|
+
]
|
|
1816
|
+
}
|
|
1817
|
+
),
|
|
1818
|
+
/* @__PURE__ */ jsxs(
|
|
1819
|
+
"select",
|
|
1820
|
+
{
|
|
1821
|
+
className: "cv-html-tpl",
|
|
1822
|
+
value: "",
|
|
1823
|
+
title: "Insert a shape",
|
|
1824
|
+
onChange: (e) => {
|
|
1825
|
+
const t = SLIDE_SHAPES[e.target.value];
|
|
1826
|
+
if (t) sendIframeCommand({ artifactId: artifact2.id, type: "insert_html", cid: single?.cid, html: t.html });
|
|
1827
|
+
e.currentTarget.value = "";
|
|
1828
|
+
},
|
|
1829
|
+
children: [
|
|
1830
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Shape\u2026" }),
|
|
1831
|
+
Object.entries(SLIDE_SHAPES).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
1832
|
+
]
|
|
1833
|
+
}
|
|
1834
|
+
),
|
|
1835
|
+
/* @__PURE__ */ jsxs(
|
|
1836
|
+
"select",
|
|
1837
|
+
{
|
|
1838
|
+
className: "cv-html-tpl",
|
|
1839
|
+
value: "",
|
|
1840
|
+
title: "Slide font",
|
|
1841
|
+
onChange: (e) => {
|
|
1842
|
+
const f = SLIDE_FONTS[e.target.value];
|
|
1843
|
+
if (f) setSlideStyle({ fontFamily: f.stack });
|
|
1844
|
+
e.currentTarget.value = "";
|
|
1845
|
+
},
|
|
1846
|
+
children: [
|
|
1847
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Font\u2026" }),
|
|
1848
|
+
Object.entries(SLIDE_FONTS).map(([k, v]) => /* @__PURE__ */ jsx("option", { value: k, children: v.label }, k))
|
|
1849
|
+
]
|
|
1850
|
+
}
|
|
1851
|
+
),
|
|
1852
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-add", title: "Slide background image", onClick: () => bgFileRef.current?.click(), children: "\u{1F5BC} BG" })
|
|
1853
|
+
] }),
|
|
1660
1854
|
selected.length >= 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1661
1855
|
/* @__PURE__ */ jsx("span", { className: "cv-html-bar__sep" }),
|
|
1662
1856
|
/* @__PURE__ */ jsx("span", { className: "cv-html-bar__label", children: "Selection" }),
|
|
@@ -1684,6 +1878,12 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1684
1878
|
}
|
|
1685
1879
|
)
|
|
1686
1880
|
] }),
|
|
1881
|
+
single && single.tag !== "img" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1882
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Align left", onClick: () => command("style_persist", { prop: "textAlign", value: "left" }), children: "\u2B05" }),
|
|
1883
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Align center", onClick: () => command("style_persist", { prop: "textAlign", value: "center" }), children: "\u2B0C" }),
|
|
1884
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Align right", onClick: () => command("style_persist", { prop: "textAlign", value: "right" }), children: "\u27A1" }),
|
|
1885
|
+
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Bold", onClick: () => command("style_persist", { prop: "fontWeight", value: "800" }), children: "B" })
|
|
1886
|
+
] }),
|
|
1687
1887
|
single && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1688
1888
|
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Duplicate", onClick: () => command("duplicate"), children: "\u29C9" }),
|
|
1689
1889
|
/* @__PURE__ */ jsx("button", { className: "cv-html-act", title: "Move up", onClick: () => command("move_up"), children: "\u2191" }),
|
|
@@ -1738,10 +1938,10 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1738
1938
|
}
|
|
1739
1939
|
|
|
1740
1940
|
// src/components/renderers/index.ts
|
|
1741
|
-
var ChartRenderer = lazy(() => import('./ChartRenderer-
|
|
1742
|
-
var DocumentRenderer = lazy(() => import('./DocumentRenderer-
|
|
1743
|
-
var TableRenderer = lazy(() => import('./TableRenderer-
|
|
1744
|
-
var SlidesRenderer = lazy(() => import('./SlidesRenderer-
|
|
1941
|
+
var ChartRenderer = lazy(() => import('./ChartRenderer-4MWKP2J4.js').then((m) => ({ default: m.ChartRenderer })));
|
|
1942
|
+
var DocumentRenderer = lazy(() => import('./DocumentRenderer-LJGP6N7E.js').then((m) => ({ default: m.DocumentRenderer })));
|
|
1943
|
+
var TableRenderer = lazy(() => import('./TableRenderer-ZF7MVA7I.js').then((m) => ({ default: m.TableRenderer })));
|
|
1944
|
+
var SlidesRenderer = lazy(() => import('./SlidesRenderer-7ICJQHYT.js').then((m) => ({ default: m.SlidesRenderer })));
|
|
1745
1945
|
var builtinRenderers = {
|
|
1746
1946
|
html: HtmlRenderer,
|
|
1747
1947
|
document: DocumentRenderer,
|
|
@@ -1880,13 +2080,20 @@ async function slidesToPptx(data, _title) {
|
|
|
1880
2080
|
const H = 5.625;
|
|
1881
2081
|
for (const slide of data.slides) {
|
|
1882
2082
|
const s = pptx.addSlide();
|
|
1883
|
-
if (slide.background) s.background = { color: slide.background.replace("#", "") };
|
|
2083
|
+
if (slide.background && /^#[0-9a-f]{3,8}$/i.test(slide.background)) s.background = { color: slide.background.replace("#", "") };
|
|
1884
2084
|
const tc = slide.textColor ? slide.textColor.replace("#", "") : void 0;
|
|
1885
2085
|
for (const el of resolveElements(slide)) {
|
|
1886
2086
|
const box = { x: el.x / 100 * W, y: el.y / 100 * H, w: el.w / 100 * W, h: el.h / 100 * H };
|
|
1887
2087
|
if (el.type === "text") {
|
|
1888
2088
|
const color = el.color ? el.color.replace("#", "") : tc;
|
|
1889
2089
|
s.addText(el.text ?? "", { ...box, fontSize: (el.fontSize ?? 24) * 0.75, bold: !!el.bold, align: el.align ?? "left", ...color ? { color } : {} });
|
|
2090
|
+
} else if (el.type === "shape") {
|
|
2091
|
+
const fill = (el.fill ?? "#5b5bd6").replace("#", "");
|
|
2092
|
+
if (el.shape === "line") {
|
|
2093
|
+
s.addShape(pptx.ShapeType.line, { ...box, line: { color: fill, width: 2 } });
|
|
2094
|
+
} else {
|
|
2095
|
+
s.addShape(el.shape === "ellipse" ? pptx.ShapeType.ellipse : pptx.ShapeType.rect, { ...box, fill: { color: fill } });
|
|
2096
|
+
}
|
|
1890
2097
|
} else if (el.src) {
|
|
1891
2098
|
s.addImage({ data: el.src, ...box, sizing: { type: "contain", w: box.w, h: box.h } });
|
|
1892
2099
|
}
|
|
@@ -1903,9 +2110,14 @@ function slidesToPrintHtml(data, title) {
|
|
|
1903
2110
|
const els = resolveElements(slide).map((el) => {
|
|
1904
2111
|
const box = `left:${el.x}%;top:${el.y}%;width:${el.w}%;height:${el.h}%`;
|
|
1905
2112
|
if (el.type === "text") {
|
|
1906
|
-
const style = `${box};font-size:${(el.fontSize ?? 24) / 7.2}vw;font-weight:${el.bold ? 700 : 400};color:${escapeAttr(el.color ?? fg)};text-align:${escapeAttr(el.align ?? "left")}`;
|
|
2113
|
+
const style = `${box};font-size:${(el.fontSize ?? 24) / 7.2}vw;font-weight:${el.bold ? 700 : 400};color:${escapeAttr(el.color ?? fg)};text-align:${escapeAttr(el.align ?? "left")};white-space:pre-wrap`;
|
|
1907
2114
|
return `<div class="el" style="${style}">${escapeXml(el.text ?? "")}</div>`;
|
|
1908
2115
|
}
|
|
2116
|
+
if (el.type === "shape") {
|
|
2117
|
+
const fill = escapeAttr(el.fill ?? fg);
|
|
2118
|
+
const radius = el.shape === "ellipse" ? "50%" : el.shape === "line" ? "2px" : "8px";
|
|
2119
|
+
return `<div class="el" style="${box};background:${fill};border-radius:${radius}"></div>`;
|
|
2120
|
+
}
|
|
1909
2121
|
const src = safeSrc(el.src);
|
|
1910
2122
|
return src ? `<img class="el" style="${box}" src="${escapeAttr(src)}"/>` : "";
|
|
1911
2123
|
}).join("");
|
|
@@ -1920,6 +2132,13 @@ function slidesToPrintHtml(data, title) {
|
|
|
1920
2132
|
img.el { object-fit: contain; }
|
|
1921
2133
|
</style></head><body>${pages}</body></html>`;
|
|
1922
2134
|
}
|
|
2135
|
+
function htmlSlideToPrintHtml(html, ratio) {
|
|
2136
|
+
const w = ratio === "4:3" ? 960 : 1280;
|
|
2137
|
+
const h = 720;
|
|
2138
|
+
const style = `<style>@page{size:${w}px ${h}px;margin:0}html,body{margin:0!important;padding:0!important;background:#fff}.slide-container{width:${w}px!important;height:${h}px!important;box-shadow:none!important;border-radius:0!important;overflow:hidden!important;page-break-after:avoid}</style>`;
|
|
2139
|
+
const i = html.toLowerCase().lastIndexOf("</head>");
|
|
2140
|
+
return i === -1 ? style + html : html.slice(0, i) + style + html.slice(i);
|
|
2141
|
+
}
|
|
1923
2142
|
function escapeXml(value) {
|
|
1924
2143
|
return String(value ?? "").replace(/[&<>]/g, (c) => ({ "&": "&", "<": "<", ">": ">" })[c]);
|
|
1925
2144
|
}
|
|
@@ -1983,7 +2202,9 @@ function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
|
|
|
1983
2202
|
const dataOptions = dataExporters[artifact2.type] ?? [];
|
|
1984
2203
|
const exportHtml = () => {
|
|
1985
2204
|
if (artifact2.type === "html") {
|
|
1986
|
-
|
|
2205
|
+
const ratio = artifact2.meta?.ratio;
|
|
2206
|
+
const html = artifact2.data.html;
|
|
2207
|
+
downloadBlob(`${stem}.html`, "text/html", ratio ? htmlSlideToPrintHtml(html, ratio) : html);
|
|
1987
2208
|
} else {
|
|
1988
2209
|
const html = getRenderedHtml();
|
|
1989
2210
|
if (html == null) return;
|
|
@@ -2000,7 +2221,9 @@ function ExportMenu({ artifact: artifact2, getRenderedHtml }) {
|
|
|
2000
2221
|
if (artifact2.type === "slides") {
|
|
2001
2222
|
printToPdf(slidesToPrintHtml(artifact2.data, artifact2.title));
|
|
2002
2223
|
} else if (artifact2.type === "html") {
|
|
2003
|
-
|
|
2224
|
+
const ratio = artifact2.meta?.ratio;
|
|
2225
|
+
const html = artifact2.data.html;
|
|
2226
|
+
printToPdf(ratio ? htmlSlideToPrintHtml(html, ratio) : html);
|
|
2004
2227
|
} else {
|
|
2005
2228
|
const html = getRenderedHtml();
|
|
2006
2229
|
if (html == null) return;
|
|
@@ -2362,17 +2585,22 @@ function useCanvasImport() {
|
|
|
2362
2585
|
return { importFiles, canImport };
|
|
2363
2586
|
}
|
|
2364
2587
|
var ACCEPT = IMPORTABLE_EXTENSIONS.join(",");
|
|
2365
|
-
function Canvas({ registry = builtinRenderers, emptyState, onEditElement }) {
|
|
2366
|
-
return /* @__PURE__ */ jsx(CanvasRegistryProvider, { registry, children: /* @__PURE__ */ jsx(CanvasPanel, { emptyState, onEditElement }) });
|
|
2588
|
+
function Canvas({ registry = builtinRenderers, emptyState, onEditElement, onUserEdit }) {
|
|
2589
|
+
return /* @__PURE__ */ jsx(CanvasRegistryProvider, { registry, children: /* @__PURE__ */ jsx(CanvasPanel, { emptyState, onEditElement, onUserEdit }) });
|
|
2367
2590
|
}
|
|
2368
|
-
function CanvasPanel({ emptyState, onEditElement }) {
|
|
2591
|
+
function CanvasPanel({ emptyState, onEditElement, onUserEdit }) {
|
|
2369
2592
|
const { artifacts, order, activeId } = useCanvasStore((s) => s.canvas);
|
|
2370
2593
|
const history = useCanvasStore((s) => s.canvas.history);
|
|
2371
2594
|
const setActive = useCanvasStore((s) => s.setActiveArtifact);
|
|
2372
2595
|
const selections = useCanvasStore((s) => s.selections);
|
|
2373
2596
|
const setSelections = useCanvasStore((s) => s.setSelections);
|
|
2597
|
+
const setOnUserEdit = useCanvasStore((s) => s.setOnUserEdit);
|
|
2374
2598
|
const { importFiles } = useCanvasImport();
|
|
2375
2599
|
const [dropping, setDropping] = useState(false);
|
|
2600
|
+
useEffect(() => {
|
|
2601
|
+
setOnUserEdit(onUserEdit ?? null);
|
|
2602
|
+
return () => setOnUserEdit(null);
|
|
2603
|
+
}, [onUserEdit, setOnUserEdit]);
|
|
2376
2604
|
useEffect(() => {
|
|
2377
2605
|
if (!selections.length) return;
|
|
2378
2606
|
const onKey = (e) => {
|
package/dist/styles.css
CHANGED
|
@@ -617,6 +617,16 @@
|
|
|
617
617
|
.cv-present__slide .cv-slide__subtitle { font-size: clamp(16px, 2.6vw, 30px); }
|
|
618
618
|
.cv-present__slide .cv-slide__bullets li { font-size: clamp(16px, 2.4vw, 30px); }
|
|
619
619
|
.cv-present__hint { color: #9aa4b2; font-size: 13px; }
|
|
620
|
+
/* Fade each slide in as the presenter advances. */
|
|
621
|
+
.cv-present__fade { animation: cv-present-fade 0.32s ease; }
|
|
622
|
+
@keyframes cv-present-fade { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
|
|
623
|
+
@media (prefers-reduced-motion: reduce) { .cv-present__fade { animation: none; } }
|
|
624
|
+
/* Speaker notes shown to the presenter, below the slide. */
|
|
625
|
+
.cv-present__notes {
|
|
626
|
+
max-width: min(92vw, 1280px); color: #cdd3de; font-size: 15px; line-height: 1.5;
|
|
627
|
+
background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.08);
|
|
628
|
+
border-radius: 10px; padding: 12px 16px; cursor: default; white-space: pre-wrap;
|
|
629
|
+
}
|
|
620
630
|
|
|
621
631
|
/* in-place editing cues */
|
|
622
632
|
.cv-word .cv-doc { cursor: text; }
|
package/package.json
CHANGED