@braincrew-lab/langchain-canvas 0.1.8 → 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.
|
@@ -4,6 +4,12 @@ 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
|
" / ",
|
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). */
|
|
@@ -369,14 +373,16 @@ interface ChatMessage {
|
|
|
369
373
|
/** A command the editing UI forwards to the active html artifact's iframe. */
|
|
370
374
|
interface IframeCommand {
|
|
371
375
|
artifactId: string;
|
|
372
|
-
/** style · structure (duplicate/delete/move/insert/insert_html) · group/ungroup · set_src · clear. */
|
|
373
|
-
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";
|
|
374
378
|
/** Target element (omitted for document-level inserts with no selection). */
|
|
375
379
|
cid?: string;
|
|
376
380
|
/** Members to wrap for `group`. */
|
|
377
381
|
cids?: string[];
|
|
378
382
|
prop?: string;
|
|
379
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>;
|
|
380
386
|
/** Tag/block to insert for `insert` (e.g. "h2", "p", "button", "img", "hr", "section"). */
|
|
381
387
|
block?: string;
|
|
382
388
|
/** HTML fragment to insert for `insert_html` (a built-in section template). */
|
package/dist/index.js
CHANGED
|
@@ -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" }),
|
|
@@ -1741,7 +1941,7 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
1741
1941
|
var ChartRenderer = lazy(() => import('./ChartRenderer-4MWKP2J4.js').then((m) => ({ default: m.ChartRenderer })));
|
|
1742
1942
|
var DocumentRenderer = lazy(() => import('./DocumentRenderer-LJGP6N7E.js').then((m) => ({ default: m.DocumentRenderer })));
|
|
1743
1943
|
var TableRenderer = lazy(() => import('./TableRenderer-ZF7MVA7I.js').then((m) => ({ default: m.TableRenderer })));
|
|
1744
|
-
var SlidesRenderer = lazy(() => import('./SlidesRenderer-
|
|
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;
|
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