@braincrew-lab/langchain-canvas 0.4.9 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveElements } from './chunk-
|
|
1
|
+
import { deckPage, pageAspect, DEFAULT_SLIDE_PAGE_IN, resolveElements, fontScaleFor } from './chunk-SE6AP3A7.js';
|
|
2
2
|
import { useAssetUrl } from './chunk-FTNRRJ3K.js';
|
|
3
3
|
import './chunk-7T5DRR3F.js';
|
|
4
4
|
import { useArtifactPatch } from './chunk-K2UZAYW2.js';
|
|
@@ -29,7 +29,7 @@ function snapAxis(pos, size, targets) {
|
|
|
29
29
|
}
|
|
30
30
|
return best ? { pos: pos + best.delta, guide: best.guide } : { pos, guide: null };
|
|
31
31
|
}
|
|
32
|
-
function FreeSlide({ elements, onChange, padding }) {
|
|
32
|
+
function FreeSlide({ elements, onChange, padding, fontScale = 1 }) {
|
|
33
33
|
const slideRef = useRef(null);
|
|
34
34
|
const assetUrl = useAssetUrl();
|
|
35
35
|
const [els, setEls] = useState(elements);
|
|
@@ -174,7 +174,7 @@ function FreeSlide({ elements, onChange, padding }) {
|
|
|
174
174
|
contentEditable: editingId === el.id,
|
|
175
175
|
suppressContentEditableWarning: true,
|
|
176
176
|
style: {
|
|
177
|
-
fontSize: el.fontSize ?? 24,
|
|
177
|
+
fontSize: (el.fontSize ?? 24) * fontScale,
|
|
178
178
|
fontWeight: el.bold ? 700 : 400,
|
|
179
179
|
color: el.color,
|
|
180
180
|
textAlign: el.align ?? "left"
|
|
@@ -234,6 +234,19 @@ function FreeSlide({ elements, onChange, padding }) {
|
|
|
234
234
|
}
|
|
235
235
|
);
|
|
236
236
|
}
|
|
237
|
+
function useFontScale(page) {
|
|
238
|
+
const [node, setNode] = useState(null);
|
|
239
|
+
const [scale, setScale] = useState(1);
|
|
240
|
+
useEffect(() => {
|
|
241
|
+
if (!node) return;
|
|
242
|
+
const measure = () => setScale(fontScaleFor(node.clientWidth, page));
|
|
243
|
+
measure();
|
|
244
|
+
const observer = new ResizeObserver(measure);
|
|
245
|
+
observer.observe(node);
|
|
246
|
+
return () => observer.disconnect();
|
|
247
|
+
}, [node, page.widthIn, page.heightIn]);
|
|
248
|
+
return { ref: setNode, scale };
|
|
249
|
+
}
|
|
237
250
|
var THEMES = [
|
|
238
251
|
{ id: "light", label: "Light", bg: "#ffffff", text: "#1f2328" },
|
|
239
252
|
{ id: "paper", label: "Paper", bg: "#f7f5ef", text: "#2b2a26" },
|
|
@@ -282,6 +295,11 @@ function SlidesRenderer({ artifact }) {
|
|
|
282
295
|
const [presenting, setPresenting] = useState(false);
|
|
283
296
|
const imgRef = useRef(null);
|
|
284
297
|
const bgRef = useRef(null);
|
|
298
|
+
const page = deckPage(artifact.data);
|
|
299
|
+
const aspect = pageAspect(page);
|
|
300
|
+
const editBox = useFontScale(page);
|
|
301
|
+
const presentBox = useFontScale(page);
|
|
302
|
+
const thumbFont = 0.12 * (DEFAULT_SLIDE_PAGE_IN.widthIn / page.widthIn);
|
|
285
303
|
useEffect(() => {
|
|
286
304
|
if (!presenting) return;
|
|
287
305
|
const onKey = (e) => {
|
|
@@ -374,11 +392,11 @@ function SlidesRenderer({ artifact }) {
|
|
|
374
392
|
onDragEnd: () => setDragIndex(null),
|
|
375
393
|
children: /* @__PURE__ */ jsxs("button", { className: "cv-deck__thumb", onClick: () => setIndex(i), children: [
|
|
376
394
|
/* @__PURE__ */ jsx("span", { className: "cv-deck__thumb-n", children: i + 1 }),
|
|
377
|
-
/* @__PURE__ */ jsx("div", { className: "cv-deck__thumb-slide", style: s.background ? { background: s.background } :
|
|
395
|
+
/* @__PURE__ */ jsx("div", { className: "cv-deck__thumb-slide", style: { aspectRatio: aspect, ...s.background ? { background: s.background } : {} }, children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: `${s.padding ?? 0}%` }, children: resolveElements(s).map(
|
|
378
396
|
(el) => el.type === "text" ? /* @__PURE__ */ jsx(
|
|
379
397
|
"span",
|
|
380
398
|
{
|
|
381
|
-
style: { position: "absolute", left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, fontSize: (el.fontSize ?? 24) *
|
|
399
|
+
style: { position: "absolute", left: `${el.x}%`, top: `${el.y}%`, width: `${el.w}%`, fontSize: (el.fontSize ?? 24) * thumbFont, fontWeight: el.bold ? 700 : 400, color: el.color ?? s.textColor, overflow: "hidden", whiteSpace: "pre-wrap" },
|
|
382
400
|
children: el.text
|
|
383
401
|
},
|
|
384
402
|
el.id
|
|
@@ -458,7 +476,7 @@ function SlidesRenderer({ artifact }) {
|
|
|
458
476
|
/* @__PURE__ */ jsx("button", { onClick: duplicateSlide, title: "Duplicate slide", children: "\u29C9" }),
|
|
459
477
|
/* @__PURE__ */ jsx("button", { onClick: deleteSlide, title: "Delete slide", disabled: slides.length === 1, children: "\u{1F5D1}" })
|
|
460
478
|
] }),
|
|
461
|
-
/* @__PURE__ */ jsx("div", { className: "cv-slide cv-slide--blank", style: slideStyle, children: /* @__PURE__ */ jsx(FreeSlide, { elements: resolveElements(slide), onChange: (elements) => update({ elements }), padding: slide.padding }) }),
|
|
479
|
+
/* @__PURE__ */ jsx("div", { className: "cv-slide cv-slide--blank", ref: editBox.ref, style: { aspectRatio: aspect, ...slideStyle }, children: /* @__PURE__ */ jsx(FreeSlide, { elements: resolveElements(slide), onChange: (elements) => update({ elements }), padding: slide.padding, fontScale: editBox.scale }) }),
|
|
462
480
|
/* @__PURE__ */ jsxs("div", { className: "cv-deck__nav cv-chrome", children: [
|
|
463
481
|
/* @__PURE__ */ jsx("button", { disabled: at === 0, onClick: () => setIndex(at - 1), "aria-label": "Previous slide", children: "\u2039" }),
|
|
464
482
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
@@ -479,8 +497,8 @@ function SlidesRenderer({ artifact }) {
|
|
|
479
497
|
)
|
|
480
498
|
] }),
|
|
481
499
|
presenting && /* @__PURE__ */ jsxs("div", { className: "cv-present", onClick: () => setIndex(Math.min(at + 1, slides.length - 1)), children: [
|
|
482
|
-
/* @__PURE__ */ jsx("div", { className: "cv-present__slide cv-present__fade cv-slide cv-slide--blank", style: slideStyle, children: /* @__PURE__ */ jsx("div", { className: "cv-free", style: slide.padding ? { inset: `${slide.padding}%` } : void 0, children: resolveElements(slide).map(
|
|
483
|
-
(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: assetUrl(el.src), alt: "" }) }, el.id)
|
|
500
|
+
/* @__PURE__ */ jsx("div", { ref: presentBox.ref, className: "cv-present__slide cv-present__fade cv-slide cv-slide--blank", style: { aspectRatio: aspect, ...slideStyle }, children: /* @__PURE__ */ jsx("div", { className: "cv-free", style: slide.padding ? { inset: `${slide.padding}%` } : void 0, children: resolveElements(slide).map(
|
|
501
|
+
(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) * presentBox.scale, 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: assetUrl(el.src), alt: "" }) }, el.id)
|
|
484
502
|
) }) }, at),
|
|
485
503
|
slide.notes ? /* @__PURE__ */ jsx("div", { className: "cv-present__notes", onClick: (e) => e.stopPropagation(), children: slide.notes }) : null,
|
|
486
504
|
/* @__PURE__ */ jsxs("div", { className: "cv-present__hint", children: [
|
|
@@ -21,4 +21,24 @@ function toElements(s) {
|
|
|
21
21
|
}
|
|
22
22
|
var resolveElements = (s) => s.elements?.length ? s.elements : toElements(s);
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
// src/client/slidePage.ts
|
|
25
|
+
var DEFAULT_SLIDE_PAGE_IN = { widthIn: 10, heightIn: 5.625 };
|
|
26
|
+
var PAGE_DPI = 96;
|
|
27
|
+
function deckPage(data) {
|
|
28
|
+
const page = data.page;
|
|
29
|
+
if (!page) return DEFAULT_SLIDE_PAGE_IN;
|
|
30
|
+
const { widthIn, heightIn } = page;
|
|
31
|
+
if (!Number.isFinite(widthIn) || !Number.isFinite(heightIn) || widthIn <= 0 || heightIn <= 0) {
|
|
32
|
+
return DEFAULT_SLIDE_PAGE_IN;
|
|
33
|
+
}
|
|
34
|
+
return { widthIn, heightIn };
|
|
35
|
+
}
|
|
36
|
+
function pageAspect(page) {
|
|
37
|
+
return `${page.widthIn} / ${page.heightIn}`;
|
|
38
|
+
}
|
|
39
|
+
function fontScaleFor(boxWidthPx, page) {
|
|
40
|
+
if (!Number.isFinite(boxWidthPx) || boxWidthPx <= 0) return 1;
|
|
41
|
+
return boxWidthPx / (page.widthIn * PAGE_DPI);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { DEFAULT_SLIDE_PAGE_IN, deckPage, fontScaleFor, pageAspect, resolveElements };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { projectSheetIntoRows, mergeRowsIntoSheet } from './chunk-IFNRLN4Y.js';
|
|
3
3
|
import { loadOptional } from './chunk-YZZSJJMQ.js';
|
|
4
|
-
import { resolveElements } from './chunk-
|
|
4
|
+
import { deckPage, resolveElements } from './chunk-SE6AP3A7.js';
|
|
5
5
|
export { useAssetUrl } from './chunk-FTNRRJ3K.js';
|
|
6
6
|
import { inlineArtifactAssets, inlineHtmlAssets } from './chunk-7T5DRR3F.js';
|
|
7
7
|
export { ASSET_REFERENCE_PREFIXES, fetchAssetDataUri, inlineArtifactAssets, inlineHtmlAssets, isAssetReference, normalizeAssetReference, resolveAssetUrl } from './chunk-7T5DRR3F.js';
|
|
@@ -2258,7 +2258,7 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
2258
2258
|
var ChartRenderer = lazy(() => import('./ChartRenderer-JGRJ23OB.js').then((m) => ({ default: m.ChartRenderer })));
|
|
2259
2259
|
var DocumentRenderer = lazy(() => import('./DocumentRenderer-ZQDQMX7X.js').then((m) => ({ default: m.DocumentRenderer })));
|
|
2260
2260
|
var TableRenderer = lazy(() => import('./TableRenderer-3ESF577F.js').then((m) => ({ default: m.TableRenderer })));
|
|
2261
|
-
var SlidesRenderer = lazy(() => import('./SlidesRenderer-
|
|
2261
|
+
var SlidesRenderer = lazy(() => import('./SlidesRenderer-4TZNTSEL.js').then((m) => ({ default: m.SlidesRenderer })));
|
|
2262
2262
|
var FileRenderer = lazy(() => import('./FileRenderer-3ZHNORZJ.js').then((m) => ({ default: m.FileRenderer })));
|
|
2263
2263
|
var builtinRenderers = {
|
|
2264
2264
|
html: HtmlRenderer,
|
|
@@ -2414,8 +2414,9 @@ async function documentToDocx(data) {
|
|
|
2414
2414
|
async function slidesToPptx(data, _title) {
|
|
2415
2415
|
const PptxGenJS = (await loadOptional("pptxgenjs", () => import('pptxgenjs'))).default;
|
|
2416
2416
|
const pptx = new PptxGenJS();
|
|
2417
|
-
const W =
|
|
2418
|
-
|
|
2417
|
+
const { widthIn: W, heightIn: H } = deckPage(data);
|
|
2418
|
+
pptx.defineLayout({ name: "CV_PAGE", width: W, height: H });
|
|
2419
|
+
pptx.layout = "CV_PAGE";
|
|
2419
2420
|
for (const slide of data.slides) {
|
|
2420
2421
|
const s = pptx.addSlide();
|
|
2421
2422
|
if (slide.background && /^#[0-9a-f]{3,8}$/i.test(slide.background)) s.background = { color: slide.background.replace("#", "") };
|
|
@@ -2444,6 +2445,9 @@ async function slidesToPptx(data, _title) {
|
|
|
2444
2445
|
}
|
|
2445
2446
|
function slidesToPrintHtml(data, title) {
|
|
2446
2447
|
const slides2 = data.slides.length ? data.slides : [{ title: "Empty deck" }];
|
|
2448
|
+
const page = deckPage(data);
|
|
2449
|
+
const pw = Math.round(page.widthIn * 128);
|
|
2450
|
+
const ph = Math.round(page.heightIn * 128);
|
|
2447
2451
|
const pages = slides2.map((slide) => {
|
|
2448
2452
|
const bg = slide.background ?? "#ffffff";
|
|
2449
2453
|
const fg = slide.textColor ?? "#1f2328";
|
|
@@ -2466,10 +2470,10 @@ function slidesToPrintHtml(data, title) {
|
|
|
2466
2470
|
return `<section class="slide" style="background:${escapeAttr(bg)}">${inner}</section>`;
|
|
2467
2471
|
}).join("");
|
|
2468
2472
|
return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeXml(title)}</title><style>
|
|
2469
|
-
@page { size:
|
|
2473
|
+
@page { size: ${pw}px ${ph}px; margin: 0; }
|
|
2470
2474
|
* { margin: 0; box-sizing: border-box; }
|
|
2471
2475
|
body { font-family: Inter, Arial, sans-serif; }
|
|
2472
|
-
.slide { position: relative; width:
|
|
2476
|
+
.slide { position: relative; width: ${pw}px; height: ${ph}px; overflow: hidden; page-break-after: always; }
|
|
2473
2477
|
.el { position: absolute; overflow: hidden; line-height: 1.25; }
|
|
2474
2478
|
img.el { object-fit: contain; }
|
|
2475
2479
|
</style></head><body>${pages}</body></html>`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@braincrew-lab/langchain-canvas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "A live canvas for LangChain agents — stream documents, charts, and rich artifacts into a React panel.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Brain Crew (https://github.com/braincrew-lab)",
|