@braincrew-lab/langchain-canvas 0.1.11 → 0.1.13
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-4MWKP2J4.js → ChartRenderer-FX6ZN4NC.js} +39 -6
- package/dist/DocumentRenderer-KJ6FTGKH.js +123 -0
- package/dist/{SlidesRenderer-7ICJQHYT.js → SlidesRenderer-SK5VQDIL.js} +11 -6
- package/dist/{TableRenderer-ZF7MVA7I.js → TableRenderer-Y4JFRSLU.js} +55 -4
- package/dist/index.d.ts +5 -0
- package/dist/index.js +10 -6
- package/dist/styles.css +35 -0
- package/package.json +1 -1
- package/dist/DocumentRenderer-LJGP6N7E.js +0 -40
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
import { useArtifactPatch } from './chunk-TERWLUW3.js';
|
|
2
2
|
import './chunk-KKLWKR5G.js';
|
|
3
|
-
import { useState,
|
|
3
|
+
import { useState, useRef, useMemo, useEffect } from 'react';
|
|
4
4
|
import * as echarts from 'echarts/core';
|
|
5
5
|
import { BarChart, LineChart, PieChart } from 'echarts/charts';
|
|
6
6
|
import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
|
|
7
7
|
import { CanvasRenderer } from 'echarts/renderers';
|
|
8
|
-
import {
|
|
8
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
9
9
|
|
|
10
10
|
echarts.use([BarChart, LineChart, PieChart, GridComponent, TooltipComponent, LegendComponent, CanvasRenderer]);
|
|
11
11
|
var PALETTE = ["#6366f1", "#10b981", "#f59e0b", "#ef4444", "#0ea5e9", "#a855f7"];
|
|
12
12
|
var seriesColor = (series, index) => series.color ?? PALETTE[index % PALETTE.length];
|
|
13
13
|
var sliceColor = (options, index) => options?.colors?.[index] ?? PALETTE[index % PALETTE.length];
|
|
14
14
|
var CHART_TYPES = ["bar", "line", "area", "pie"];
|
|
15
|
-
function EChart({ option, height }) {
|
|
15
|
+
function EChart({ option, height, onInst }) {
|
|
16
16
|
const boxRef = useRef(null);
|
|
17
17
|
const instRef = useRef(null);
|
|
18
18
|
useEffect(() => {
|
|
19
19
|
if (!boxRef.current) return;
|
|
20
20
|
const inst = echarts.init(boxRef.current, void 0, { renderer: "canvas" });
|
|
21
21
|
instRef.current = inst;
|
|
22
|
+
onInst?.(inst);
|
|
22
23
|
const ro = new ResizeObserver(() => inst.resize());
|
|
23
24
|
ro.observe(boxRef.current);
|
|
24
25
|
return () => {
|
|
25
26
|
ro.disconnect();
|
|
26
27
|
inst.dispose();
|
|
27
28
|
instRef.current = null;
|
|
29
|
+
onInst?.(null);
|
|
28
30
|
};
|
|
29
31
|
}, []);
|
|
30
32
|
useEffect(() => {
|
|
@@ -36,12 +38,28 @@ function ChartRenderer({ artifact }) {
|
|
|
36
38
|
const { chart, rows, xKey, series, options, echartsOption } = artifact.data;
|
|
37
39
|
const patch = useArtifactPatch(artifact.id);
|
|
38
40
|
const [editing, setEditing] = useState(false);
|
|
41
|
+
const instRef = useRef(null);
|
|
42
|
+
const downloadPng = () => {
|
|
43
|
+
const inst = instRef.current;
|
|
44
|
+
if (!inst) return;
|
|
45
|
+
const url = inst.getDataURL({ type: "png", pixelRatio: 2, backgroundColor: "#ffffff" });
|
|
46
|
+
const a = document.createElement("a");
|
|
47
|
+
a.href = url;
|
|
48
|
+
a.download = `${(artifact.title || "chart").replace(/[^a-z0-9가-힣]+/gi, "-").replace(/^-+|-+$/g, "") || "chart"}.png`;
|
|
49
|
+
a.click();
|
|
50
|
+
};
|
|
39
51
|
const option = useMemo(
|
|
40
52
|
() => echartsOption ?? toEChartsOption(chart, rows, xKey, series, options),
|
|
41
53
|
[echartsOption, chart, rows, xKey, series, options]
|
|
42
54
|
);
|
|
43
55
|
if (echartsOption) {
|
|
44
|
-
return /* @__PURE__ */
|
|
56
|
+
return /* @__PURE__ */ jsxs("div", { className: "cv-chart", children: [
|
|
57
|
+
/* @__PURE__ */ jsxs("div", { className: "cv-chart__toolbar cv-chrome", children: [
|
|
58
|
+
/* @__PURE__ */ jsx("span", { className: "cv-chart__spacer" }),
|
|
59
|
+
/* @__PURE__ */ jsx("button", { className: "cv-edit-btn", onClick: downloadPng, title: "Download as PNG", children: "\u2913 PNG" })
|
|
60
|
+
] }),
|
|
61
|
+
/* @__PURE__ */ jsx(EChart, { option, height: 320, onInst: (i) => instRef.current = i })
|
|
62
|
+
] });
|
|
45
63
|
}
|
|
46
64
|
if (rows.length === 0) {
|
|
47
65
|
return /* @__PURE__ */ jsx("div", { className: "cv-chart cv-chart--empty", children: "Waiting for data\u2026" });
|
|
@@ -67,7 +85,18 @@ function ChartRenderer({ artifact }) {
|
|
|
67
85
|
/* @__PURE__ */ jsx("input", { type: "checkbox", checked: !!options?.stacked, onChange: (e) => patch({ options: { ...options, stacked: e.target.checked } }) }),
|
|
68
86
|
"Stacked"
|
|
69
87
|
] }),
|
|
88
|
+
/* @__PURE__ */ jsx(
|
|
89
|
+
"input",
|
|
90
|
+
{
|
|
91
|
+
className: "cv-chart__title-input",
|
|
92
|
+
value: options?.title ?? "",
|
|
93
|
+
placeholder: "Chart title\u2026",
|
|
94
|
+
onChange: (e) => patch({ options: { ...options, title: e.target.value || void 0 } }),
|
|
95
|
+
title: "Chart title"
|
|
96
|
+
}
|
|
97
|
+
),
|
|
70
98
|
/* @__PURE__ */ jsx("span", { className: "cv-chart__spacer" }),
|
|
99
|
+
/* @__PURE__ */ jsx("button", { className: "cv-edit-btn", onClick: downloadPng, title: "Download as PNG", children: "\u2913 PNG" }),
|
|
71
100
|
/* @__PURE__ */ jsx("button", { className: `cv-edit-btn ${editing ? "is-primary" : ""}`, onClick: () => setEditing((v) => !v), children: editing ? "Done" : "Edit data" })
|
|
72
101
|
] }),
|
|
73
102
|
editing && /* @__PURE__ */ jsxs("div", { className: "cv-chart__editor cv-chrome", children: [
|
|
@@ -92,7 +121,7 @@ function ChartRenderer({ artifact }) {
|
|
|
92
121
|
] }),
|
|
93
122
|
/* @__PURE__ */ jsx(DataGrid, { rows, xKey, series, onCell: setCell, onAddRow: addRow, onRemoveRow: removeRow })
|
|
94
123
|
] }),
|
|
95
|
-
/* @__PURE__ */ jsx(EChart, { option, height: editing ? 260 : 340 })
|
|
124
|
+
/* @__PURE__ */ jsx(EChart, { option, height: editing ? 260 : 340, onInst: (i) => instRef.current = i })
|
|
96
125
|
] });
|
|
97
126
|
}
|
|
98
127
|
function DataGrid({
|
|
@@ -128,9 +157,12 @@ function DataGrid({
|
|
|
128
157
|
}
|
|
129
158
|
function toEChartsOption(chart, rows, xKey, series, options) {
|
|
130
159
|
const AXIS = "#9aa4b2";
|
|
160
|
+
const titleBlock = options?.title ? { title: { text: options.title, left: "center", top: 2, textStyle: { color: AXIS, fontSize: 15, fontWeight: 600 } } } : {};
|
|
161
|
+
const hasTitle = !!options?.title;
|
|
131
162
|
if (chart === "pie") {
|
|
132
163
|
const valueKey = series[0]?.key ?? "value";
|
|
133
164
|
return {
|
|
165
|
+
...titleBlock,
|
|
134
166
|
tooltip: { trigger: "item" },
|
|
135
167
|
legend: { bottom: 0, textStyle: { color: AXIS } },
|
|
136
168
|
series: [
|
|
@@ -151,9 +183,10 @@ function toEChartsOption(chart, rows, xKey, series, options) {
|
|
|
151
183
|
}
|
|
152
184
|
const stacked = (chart === "bar" || chart === "area") && !!options?.stacked;
|
|
153
185
|
return {
|
|
186
|
+
...titleBlock,
|
|
154
187
|
tooltip: { trigger: "axis" },
|
|
155
188
|
legend: { bottom: 0, textStyle: { color: AXIS } },
|
|
156
|
-
grid: { left: 8, right: 16, top: 24, bottom: 40, containLabel: true },
|
|
189
|
+
grid: { left: 8, right: 16, top: hasTitle ? 44 : 24, bottom: 40, containLabel: true },
|
|
157
190
|
xAxis: {
|
|
158
191
|
type: "category",
|
|
159
192
|
data: rows.map((r) => String(r[xKey] ?? "")),
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useArtifactPatch } from './chunk-TERWLUW3.js';
|
|
2
|
+
import './chunk-KKLWKR5G.js';
|
|
3
|
+
import { useState, useRef } from 'react';
|
|
4
|
+
import ReactMarkdown from 'react-markdown';
|
|
5
|
+
import remarkGfm from 'remark-gfm';
|
|
6
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
function wordStats(text) {
|
|
9
|
+
const words = text.trim() ? text.trim().split(/\s+/).length : 0;
|
|
10
|
+
return { words, minutes: Math.max(1, Math.round(words / 200)) };
|
|
11
|
+
}
|
|
12
|
+
function DocumentRenderer({ artifact }) {
|
|
13
|
+
const { content } = artifact.data;
|
|
14
|
+
const patch = useArtifactPatch(artifact.id);
|
|
15
|
+
const [draft, setDraft] = useState(null);
|
|
16
|
+
const taRef = useRef(null);
|
|
17
|
+
const editing = draft !== null;
|
|
18
|
+
const commit = () => {
|
|
19
|
+
if (draft !== null && draft !== content) patch({ content: draft });
|
|
20
|
+
setDraft(null);
|
|
21
|
+
};
|
|
22
|
+
const apply = (fn) => {
|
|
23
|
+
const ta = taRef.current;
|
|
24
|
+
if (!ta || draft === null) return;
|
|
25
|
+
const { text, a, b } = fn(draft, ta.selectionStart, ta.selectionEnd);
|
|
26
|
+
setDraft(text);
|
|
27
|
+
requestAnimationFrame(() => {
|
|
28
|
+
ta.focus();
|
|
29
|
+
ta.setSelectionRange(a, b);
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
const wrap = (mark) => apply((full, s, e) => ({ text: full.slice(0, s) + mark + full.slice(s, e) + mark + full.slice(e), a: s + mark.length, b: e + mark.length }));
|
|
33
|
+
const link = () => apply((full, s, e) => {
|
|
34
|
+
const label = full.slice(s, e) || "link";
|
|
35
|
+
const ins = `[${label}](https://)`;
|
|
36
|
+
const urlAt = s + label.length + 3;
|
|
37
|
+
return { text: full.slice(0, s) + ins + full.slice(e), a: urlAt, b: urlAt + 8 };
|
|
38
|
+
});
|
|
39
|
+
const prefixLines = (prefix) => apply((full, s, e) => {
|
|
40
|
+
const start = full.lastIndexOf("\n", s - 1) + 1;
|
|
41
|
+
const nl = full.indexOf("\n", e);
|
|
42
|
+
const end = nl === -1 ? full.length : nl;
|
|
43
|
+
const block = full.slice(start, end).split("\n").map((l) => prefix + l).join("\n");
|
|
44
|
+
return { text: full.slice(0, start) + block + full.slice(end), a: start, b: start + block.length };
|
|
45
|
+
});
|
|
46
|
+
const { words, minutes } = wordStats(editing ? draft ?? "" : content);
|
|
47
|
+
return /* @__PURE__ */ jsxs("div", { className: "cv-word", children: [
|
|
48
|
+
editing && /* @__PURE__ */ jsxs("div", { className: "cv-doc-bar cv-chrome", onMouseDown: (e) => e.preventDefault(), children: [
|
|
49
|
+
/* @__PURE__ */ jsx("button", { title: "Bold", onMouseDown: (e) => {
|
|
50
|
+
e.preventDefault();
|
|
51
|
+
wrap("**");
|
|
52
|
+
}, children: /* @__PURE__ */ jsx("b", { children: "B" }) }),
|
|
53
|
+
/* @__PURE__ */ jsx("button", { title: "Italic", onMouseDown: (e) => {
|
|
54
|
+
e.preventDefault();
|
|
55
|
+
wrap("*");
|
|
56
|
+
}, children: /* @__PURE__ */ jsx("i", { children: "I" }) }),
|
|
57
|
+
/* @__PURE__ */ jsx("button", { title: "Inline code", onMouseDown: (e) => {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
wrap("`");
|
|
60
|
+
}, children: "<>" }),
|
|
61
|
+
/* @__PURE__ */ jsx("span", { className: "cv-doc-bar__sep" }),
|
|
62
|
+
/* @__PURE__ */ jsx("button", { title: "Heading 1", onMouseDown: (e) => {
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
prefixLines("# ");
|
|
65
|
+
}, children: "H1" }),
|
|
66
|
+
/* @__PURE__ */ jsx("button", { title: "Heading 2", onMouseDown: (e) => {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
prefixLines("## ");
|
|
69
|
+
}, children: "H2" }),
|
|
70
|
+
/* @__PURE__ */ jsx("button", { title: "Bullet list", onMouseDown: (e) => {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
prefixLines("- ");
|
|
73
|
+
}, children: "\u2022" }),
|
|
74
|
+
/* @__PURE__ */ jsx("button", { title: "Numbered list", onMouseDown: (e) => {
|
|
75
|
+
e.preventDefault();
|
|
76
|
+
prefixLines("1. ");
|
|
77
|
+
}, children: "1." }),
|
|
78
|
+
/* @__PURE__ */ jsx("button", { title: "Quote", onMouseDown: (e) => {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
prefixLines("> ");
|
|
81
|
+
}, children: "\u275D" }),
|
|
82
|
+
/* @__PURE__ */ jsx("button", { title: "Link", onMouseDown: (e) => {
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
link();
|
|
85
|
+
}, children: "\u{1F517}" }),
|
|
86
|
+
/* @__PURE__ */ jsx("span", { className: "cv-doc-bar__spacer" }),
|
|
87
|
+
/* @__PURE__ */ jsxs("span", { className: "cv-doc-bar__count", children: [
|
|
88
|
+
words,
|
|
89
|
+
" words \xB7 ",
|
|
90
|
+
minutes,
|
|
91
|
+
" min read"
|
|
92
|
+
] }),
|
|
93
|
+
/* @__PURE__ */ jsx("button", { className: "cv-doc-bar__done", onMouseDown: (e) => {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
commit();
|
|
96
|
+
}, children: "Done" })
|
|
97
|
+
] }),
|
|
98
|
+
/* @__PURE__ */ jsx("div", { className: "cv-word__page", children: editing ? /* @__PURE__ */ jsx(
|
|
99
|
+
"textarea",
|
|
100
|
+
{
|
|
101
|
+
ref: taRef,
|
|
102
|
+
className: "cv-doc-editor",
|
|
103
|
+
value: draft,
|
|
104
|
+
autoFocus: true,
|
|
105
|
+
onBlur: commit,
|
|
106
|
+
onChange: (e) => setDraft(e.target.value)
|
|
107
|
+
}
|
|
108
|
+
) : /* @__PURE__ */ jsxs(
|
|
109
|
+
"article",
|
|
110
|
+
{
|
|
111
|
+
className: "cv-doc",
|
|
112
|
+
title: "Click to edit",
|
|
113
|
+
onClick: () => artifact.status !== "streaming" && setDraft(content),
|
|
114
|
+
children: [
|
|
115
|
+
/* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: content }),
|
|
116
|
+
artifact.status === "streaming" && /* @__PURE__ */ jsx("span", { className: "cv-caret", "aria-hidden": true })
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
) })
|
|
120
|
+
] });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export { DocumentRenderer };
|
|
@@ -27,7 +27,7 @@ function snapAxis(pos, size, targets) {
|
|
|
27
27
|
}
|
|
28
28
|
return best ? { pos: pos + best.delta, guide: best.guide } : { pos, guide: null };
|
|
29
29
|
}
|
|
30
|
-
function FreeSlide({ elements, onChange }) {
|
|
30
|
+
function FreeSlide({ elements, onChange, padding }) {
|
|
31
31
|
const slideRef = useRef(null);
|
|
32
32
|
const [els, setEls] = useState(elements);
|
|
33
33
|
const [selected, setSelected] = useState(null);
|
|
@@ -136,6 +136,7 @@ function FreeSlide({ elements, onChange }) {
|
|
|
136
136
|
{
|
|
137
137
|
className: "cv-free",
|
|
138
138
|
ref: slideRef,
|
|
139
|
+
style: padding ? { inset: `${padding}%` } : void 0,
|
|
139
140
|
onPointerMove: onMove,
|
|
140
141
|
onPointerUp: onUp,
|
|
141
142
|
onPointerLeave: onUp,
|
|
@@ -200,10 +201,10 @@ function FreeSlide({ elements, onChange }) {
|
|
|
200
201
|
/* @__PURE__ */ jsx("button", { onClick: () => updateEl(el.id, { align: "center" }), title: "Align center", children: "\u2261" }),
|
|
201
202
|
/* @__PURE__ */ jsx("button", { onClick: () => updateEl(el.id, { align: "right" }), title: "Align right", children: "\u27F9" })
|
|
202
203
|
] }),
|
|
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" }) }),
|
|
204
204
|
selected === el.id && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
205
205
|
/* @__PURE__ */ jsx("span", { className: "cv-free__resize", onPointerDown: (e) => onDown(e, el, "resize") }),
|
|
206
206
|
/* @__PURE__ */ jsxs("div", { className: `cv-free__ctl ${el.y < 16 ? "cv-free__ctl--below" : ""}`, onPointerDown: (e) => e.stopPropagation(), children: [
|
|
207
|
+
el.type === "shape" && /* @__PURE__ */ jsx("input", { className: "cv-free__ctl-fill", type: "color", value: el.fill ?? "#5b5bd6", onChange: (e) => updateEl(el.id, { fill: e.target.value }), onClick: (e) => e.stopPropagation(), title: "Fill color" }),
|
|
207
208
|
/* @__PURE__ */ jsx("button", { onClick: (e) => {
|
|
208
209
|
e.stopPropagation();
|
|
209
210
|
duplicate(el);
|
|
@@ -369,7 +370,7 @@ function SlidesRenderer({ artifact }) {
|
|
|
369
370
|
onDragEnd: () => setDragIndex(null),
|
|
370
371
|
children: /* @__PURE__ */ jsxs("button", { className: "cv-deck__thumb", onClick: () => setIndex(i), children: [
|
|
371
372
|
/* @__PURE__ */ jsx("span", { className: "cv-deck__thumb-n", children: i + 1 }),
|
|
372
|
-
/* @__PURE__ */ jsx("div", { className: "cv-deck__thumb-slide", style: s.background ? { background: s.background } : void 0, children: resolveElements(s).map(
|
|
373
|
+
/* @__PURE__ */ jsx("div", { className: "cv-deck__thumb-slide", style: s.background ? { background: s.background } : void 0, children: /* @__PURE__ */ jsx("div", { style: { position: "absolute", inset: `${s.padding ?? 0}%` }, children: resolveElements(s).map(
|
|
373
374
|
(el) => el.type === "text" ? /* @__PURE__ */ jsx(
|
|
374
375
|
"span",
|
|
375
376
|
{
|
|
@@ -378,7 +379,7 @@ function SlidesRenderer({ artifact }) {
|
|
|
378
379
|
},
|
|
379
380
|
el.id
|
|
380
381
|
) : 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)
|
|
381
|
-
) })
|
|
382
|
+
) }) })
|
|
382
383
|
] })
|
|
383
384
|
},
|
|
384
385
|
i
|
|
@@ -442,6 +443,10 @@ function SlidesRenderer({ artifact }) {
|
|
|
442
443
|
),
|
|
443
444
|
/* @__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
445
|
/* @__PURE__ */ jsx("button", { onClick: () => bgRef.current?.click(), title: "Background image", children: "\u{1F5BC} BG" }),
|
|
446
|
+
/* @__PURE__ */ jsxs("label", { className: "cv-deck__pad", title: "Content padding (% of slide)", children: [
|
|
447
|
+
"Pad",
|
|
448
|
+
/* @__PURE__ */ jsx("input", { type: "number", min: 0, max: 20, value: slide.padding ?? 0, onChange: (e) => update({ padding: Number(e.target.value) || void 0 }) })
|
|
449
|
+
] }),
|
|
445
450
|
/* @__PURE__ */ jsx("span", { className: "cv-deck__spacer" }),
|
|
446
451
|
/* @__PURE__ */ jsx("button", { className: "cv-deck__present", onClick: () => setPresenting(true), title: "Present (full screen)", children: "\u25B6 Present" }),
|
|
447
452
|
/* @__PURE__ */ jsx("button", { onClick: () => moveSlide(-1), title: "Move up", disabled: at === 0, children: "\u25B2" }),
|
|
@@ -449,7 +454,7 @@ function SlidesRenderer({ artifact }) {
|
|
|
449
454
|
/* @__PURE__ */ jsx("button", { onClick: duplicateSlide, title: "Duplicate slide", children: "\u29C9" }),
|
|
450
455
|
/* @__PURE__ */ jsx("button", { onClick: deleteSlide, title: "Delete slide", disabled: slides.length === 1, children: "\u{1F5D1}" })
|
|
451
456
|
] }),
|
|
452
|
-
/* @__PURE__ */ jsx("div", { className: "cv-slide cv-slide--blank", style: slideStyle, children: /* @__PURE__ */ jsx(FreeSlide, { elements: resolveElements(slide), onChange: (elements) => update({ elements }) }) }),
|
|
457
|
+
/* @__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 }) }),
|
|
453
458
|
/* @__PURE__ */ jsxs("div", { className: "cv-deck__nav cv-chrome", children: [
|
|
454
459
|
/* @__PURE__ */ jsx("button", { disabled: at === 0, onClick: () => setIndex(at - 1), "aria-label": "Previous slide", children: "\u2039" }),
|
|
455
460
|
/* @__PURE__ */ jsxs("span", { children: [
|
|
@@ -470,7 +475,7 @@ function SlidesRenderer({ artifact }) {
|
|
|
470
475
|
)
|
|
471
476
|
] }),
|
|
472
477
|
presenting && /* @__PURE__ */ jsxs("div", { className: "cv-present", onClick: () => setIndex(Math.min(at + 1, slides.length - 1)), children: [
|
|
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(
|
|
478
|
+
/* @__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(
|
|
474
479
|
(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
480
|
) }) }, at),
|
|
476
481
|
slide.notes ? /* @__PURE__ */ jsx("div", { className: "cv-present__notes", onClick: (e) => e.stopPropagation(), children: slide.notes }) : null,
|
|
@@ -2,7 +2,7 @@ import { loadOptional } from './chunk-YZZSJJMQ.js';
|
|
|
2
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
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
6
6
|
|
|
7
7
|
// src/io/formula.ts
|
|
8
8
|
var EMPTY = /* @__PURE__ */ new Map();
|
|
@@ -203,10 +203,34 @@ function TableRenderer({ artifact }) {
|
|
|
203
203
|
};
|
|
204
204
|
}, [dataKey, hasFormulas]);
|
|
205
205
|
const hasSheet = !!artifact.data.sheet?.length;
|
|
206
|
+
const [sortCol, setSortCol] = useState("");
|
|
207
|
+
const [sortDir, setSortDir] = useState(1);
|
|
208
|
+
const [filter, setFilter] = useState("");
|
|
209
|
+
const [appliedFilter, setAppliedFilter] = useState("");
|
|
210
|
+
useEffect(() => {
|
|
211
|
+
const t = setTimeout(() => setAppliedFilter(filter), 300);
|
|
212
|
+
return () => clearTimeout(t);
|
|
213
|
+
}, [filter]);
|
|
214
|
+
const viewRows = useMemo(() => {
|
|
215
|
+
let r = rows;
|
|
216
|
+
const q = appliedFilter.trim().toLowerCase();
|
|
217
|
+
if (q) r = r.filter((row) => columns.some((c) => String(row[c.key] ?? "").toLowerCase().includes(q)));
|
|
218
|
+
if (sortCol) {
|
|
219
|
+
r = [...r].sort((a, b) => {
|
|
220
|
+
const av = a[sortCol];
|
|
221
|
+
const bv = b[sortCol];
|
|
222
|
+
const cmp = typeof av === "number" && typeof bv === "number" ? av - bv : String(av ?? "").localeCompare(String(bv ?? ""));
|
|
223
|
+
return cmp * sortDir;
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return r;
|
|
227
|
+
}, [rows, columns, appliedFilter, sortCol, sortDir]);
|
|
228
|
+
const viewActive = !!appliedFilter.trim() || !!sortCol;
|
|
229
|
+
const wbKey = `${dataKey}:${viewActive ? `view-s${sortCol}${sortDir}-f${appliedFilter}` : hasSheet ? "sheet" : "rows"}`;
|
|
206
230
|
const initialData = useMemo(
|
|
207
|
-
() =>
|
|
231
|
+
() => viewActive ? toWorkbook(columns, viewRows, formulas) : hasSheet ? artifact.data.sheet : toWorkbook(columns, rows, formulas),
|
|
208
232
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
209
|
-
[
|
|
233
|
+
[wbKey, formulasReady]
|
|
210
234
|
);
|
|
211
235
|
const applyEvent = useCanvasStore((s) => s.applyUserEvent);
|
|
212
236
|
const persistTimer = useRef(null);
|
|
@@ -241,9 +265,36 @@ function TableRenderer({ artifact }) {
|
|
|
241
265
|
/* @__PURE__ */ jsxs("div", { className: "cv-sheet-tools", children: [
|
|
242
266
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => insert("column"), children: "\uFF0B Column" }),
|
|
243
267
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => insert("row"), children: "\uFF0B Row" }),
|
|
268
|
+
columns.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
269
|
+
/* @__PURE__ */ jsx("span", { className: "cv-sheet-tools__sep" }),
|
|
270
|
+
/* @__PURE__ */ jsxs(
|
|
271
|
+
"select",
|
|
272
|
+
{
|
|
273
|
+
className: "cv-sheet-tools__sort",
|
|
274
|
+
value: sortCol,
|
|
275
|
+
title: "Sort by column",
|
|
276
|
+
onChange: (e) => setSortCol(e.target.value),
|
|
277
|
+
children: [
|
|
278
|
+
/* @__PURE__ */ jsx("option", { value: "", children: "Sort\u2026" }),
|
|
279
|
+
columns.map((c) => /* @__PURE__ */ jsx("option", { value: c.key, children: c.label ?? c.key }, c.key))
|
|
280
|
+
]
|
|
281
|
+
}
|
|
282
|
+
),
|
|
283
|
+
sortCol && /* @__PURE__ */ jsx("button", { type: "button", title: sortDir === 1 ? "Ascending" : "Descending", onClick: () => setSortDir((d) => d === 1 ? -1 : 1), children: sortDir === 1 ? "\u25B2" : "\u25BC" }),
|
|
284
|
+
/* @__PURE__ */ jsx(
|
|
285
|
+
"input",
|
|
286
|
+
{
|
|
287
|
+
className: "cv-sheet-tools__filter",
|
|
288
|
+
value: filter,
|
|
289
|
+
placeholder: "Filter\u2026",
|
|
290
|
+
onChange: (e) => setFilter(e.target.value),
|
|
291
|
+
title: "Filter rows"
|
|
292
|
+
}
|
|
293
|
+
)
|
|
294
|
+
] }),
|
|
244
295
|
/* @__PURE__ */ jsx("span", { className: "cv-sheet-tools__hint", children: "Right-click a header for more, or drag to edit" })
|
|
245
296
|
] }),
|
|
246
|
-
/* @__PURE__ */ jsx("div", { className: "cv-sheet", ref: rootRef, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "cv-sheet--empty", children: "Loading\u2026" }), children: /* @__PURE__ */ jsx(Workbook, { ref: wbRef, data: initialData, onChange: handleChange },
|
|
297
|
+
/* @__PURE__ */ jsx("div", { className: "cv-sheet", ref: rootRef, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "cv-sheet--empty", children: "Loading\u2026" }), children: /* @__PURE__ */ jsx(Workbook, { ref: wbRef, data: initialData, onChange: handleChange }, wbKey) }) })
|
|
247
298
|
] });
|
|
248
299
|
}
|
|
249
300
|
|
package/dist/index.d.ts
CHANGED
|
@@ -46,6 +46,8 @@ interface ChartSeries {
|
|
|
46
46
|
interface ChartOptions {
|
|
47
47
|
stacked?: boolean;
|
|
48
48
|
yLabel?: string;
|
|
49
|
+
/** Chart title shown above the plot. */
|
|
50
|
+
title?: string;
|
|
49
51
|
/** Per-slice colors for pie charts, index-aligned to `rows`. */
|
|
50
52
|
colors?: string[];
|
|
51
53
|
}
|
|
@@ -117,6 +119,9 @@ interface Slide {
|
|
|
117
119
|
textColor?: string;
|
|
118
120
|
/** Speaker notes (not shown on the slide; exported to the .pptx notes pane). */
|
|
119
121
|
notes?: string;
|
|
122
|
+
/** Content padding as a percent of the slide width (a safe margin around the
|
|
123
|
+
* free canvas). Applied in the editor, present view, thumbnails, and export. */
|
|
124
|
+
padding?: number;
|
|
120
125
|
}
|
|
121
126
|
interface SlidesData {
|
|
122
127
|
slides: Slide[];
|
package/dist/index.js
CHANGED
|
@@ -2111,10 +2111,10 @@ function HtmlRenderer({ artifact: artifact2 }) {
|
|
|
2111
2111
|
}
|
|
2112
2112
|
|
|
2113
2113
|
// src/components/renderers/index.ts
|
|
2114
|
-
var ChartRenderer = lazy(() => import('./ChartRenderer-
|
|
2115
|
-
var DocumentRenderer = lazy(() => import('./DocumentRenderer-
|
|
2116
|
-
var TableRenderer = lazy(() => import('./TableRenderer-
|
|
2117
|
-
var SlidesRenderer = lazy(() => import('./SlidesRenderer-
|
|
2114
|
+
var ChartRenderer = lazy(() => import('./ChartRenderer-FX6ZN4NC.js').then((m) => ({ default: m.ChartRenderer })));
|
|
2115
|
+
var DocumentRenderer = lazy(() => import('./DocumentRenderer-KJ6FTGKH.js').then((m) => ({ default: m.DocumentRenderer })));
|
|
2116
|
+
var TableRenderer = lazy(() => import('./TableRenderer-Y4JFRSLU.js').then((m) => ({ default: m.TableRenderer })));
|
|
2117
|
+
var SlidesRenderer = lazy(() => import('./SlidesRenderer-SK5VQDIL.js').then((m) => ({ default: m.SlidesRenderer })));
|
|
2118
2118
|
var builtinRenderers = {
|
|
2119
2119
|
html: HtmlRenderer,
|
|
2120
2120
|
document: DocumentRenderer,
|
|
@@ -2255,8 +2255,10 @@ async function slidesToPptx(data, _title) {
|
|
|
2255
2255
|
const s = pptx.addSlide();
|
|
2256
2256
|
if (slide.background && /^#[0-9a-f]{3,8}$/i.test(slide.background)) s.background = { color: slide.background.replace("#", "") };
|
|
2257
2257
|
const tc = slide.textColor ? slide.textColor.replace("#", "") : void 0;
|
|
2258
|
+
const pad = (slide.padding ?? 0) / 100;
|
|
2259
|
+
const inset = (v) => pad + v / 100 * (1 - 2 * pad);
|
|
2258
2260
|
for (const el of resolveElements(slide)) {
|
|
2259
|
-
const box = { x: el.x
|
|
2261
|
+
const box = { x: inset(el.x) * W, y: inset(el.y) * H, w: el.w / 100 * (1 - 2 * pad) * W, h: el.h / 100 * (1 - 2 * pad) * H };
|
|
2260
2262
|
if (el.type === "text") {
|
|
2261
2263
|
const color = el.color ? el.color.replace("#", "") : tc;
|
|
2262
2264
|
s.addText(el.text ?? "", { ...box, fontSize: (el.fontSize ?? 24) * 0.75, bold: !!el.bold, align: el.align ?? "left", ...color ? { color } : {} });
|
|
@@ -2294,7 +2296,9 @@ function slidesToPrintHtml(data, title) {
|
|
|
2294
2296
|
const src = safeSrc(el.src);
|
|
2295
2297
|
return src ? `<img class="el" style="${box}" src="${escapeAttr(src)}"/>` : "";
|
|
2296
2298
|
}).join("");
|
|
2297
|
-
|
|
2299
|
+
const pad = slide.padding ?? 0;
|
|
2300
|
+
const inner = pad ? `<div style="position:absolute;inset:${pad}%">${els}</div>` : els;
|
|
2301
|
+
return `<section class="slide" style="background:${escapeAttr(bg)}">${inner}</section>`;
|
|
2298
2302
|
}).join("");
|
|
2299
2303
|
return `<!doctype html><html><head><meta charset="utf-8"><title>${escapeXml(title)}</title><style>
|
|
2300
2304
|
@page { size: 1280px 720px; margin: 0; }
|
package/dist/styles.css
CHANGED
|
@@ -295,6 +295,21 @@
|
|
|
295
295
|
|
|
296
296
|
/* --- Word viewer (document page) ---------------------------------------------- */
|
|
297
297
|
|
|
298
|
+
.cv-doc-bar {
|
|
299
|
+
position: sticky; top: 0; z-index: 5; display: flex; align-items: center; gap: 4px;
|
|
300
|
+
max-width: 816px; margin: 0 auto 12px; padding: 6px 8px;
|
|
301
|
+
border: 1px solid var(--cv-border); border-radius: 10px; background: var(--cv-surface);
|
|
302
|
+
flex-wrap: wrap;
|
|
303
|
+
}
|
|
304
|
+
.cv-doc-bar button {
|
|
305
|
+
border: 0; background: transparent; color: var(--cv-text); font: inherit; font-size: 13px;
|
|
306
|
+
min-width: 28px; height: 28px; padding: 0 8px; border-radius: 6px; cursor: pointer;
|
|
307
|
+
}
|
|
308
|
+
.cv-doc-bar button:hover { background: var(--cv-surface-2, rgba(120,120,140,0.12)); }
|
|
309
|
+
.cv-doc-bar__sep { width: 1px; height: 18px; background: var(--cv-border); margin: 0 4px; }
|
|
310
|
+
.cv-doc-bar__spacer { flex: 1; }
|
|
311
|
+
.cv-doc-bar__count { font-size: 12px; color: var(--cv-muted); white-space: nowrap; }
|
|
312
|
+
.cv-doc-bar__done { background: var(--cv-accent) !important; color: #fff; font-weight: 600; }
|
|
298
313
|
.cv-word { background: #edeef1; padding: 28px 16px; min-height: 100%; }
|
|
299
314
|
.cv-word .cv-edit-toolbar { max-width: 816px; margin: 0 auto 12px; }
|
|
300
315
|
.cv-word__page {
|
|
@@ -381,6 +396,13 @@
|
|
|
381
396
|
border-radius: 6px; background: var(--cv-bg); color: var(--cv-text); cursor: pointer;
|
|
382
397
|
}
|
|
383
398
|
.cv-sheet-tools button:hover { border-color: var(--cv-accent, #6366f1); color: var(--cv-accent, #6366f1); }
|
|
399
|
+
.cv-sheet-tools__sep { width: 1px; height: 18px; background: var(--cv-border); margin: 0 2px; }
|
|
400
|
+
.cv-sheet-tools__sort, .cv-sheet-tools__filter {
|
|
401
|
+
font: inherit; font-size: 12.5px; padding: 4px 8px; border-radius: 6px;
|
|
402
|
+
border: 1px solid var(--cv-border); background: var(--cv-bg); color: var(--cv-text);
|
|
403
|
+
}
|
|
404
|
+
.cv-sheet-tools__filter { min-width: 96px; max-width: 160px; }
|
|
405
|
+
.cv-sheet-tools__sort:focus, .cv-sheet-tools__filter:focus { outline: none; border-color: var(--cv-accent); }
|
|
384
406
|
.cv-sheet-tools__hint { margin-left: auto; font-size: 11px; color: var(--cv-muted); }
|
|
385
407
|
.cv-sheet { position: relative; width: 100%; flex: 1; height: auto; min-height: 0; }
|
|
386
408
|
.cv-sheet .fortune-container { height: 100% !important; width: 100% !important; }
|
|
@@ -596,6 +618,13 @@
|
|
|
596
618
|
width: 30px; height: 30px; padding: 0;
|
|
597
619
|
border: 1px solid var(--cv-border); border-radius: 7px; background: none; cursor: pointer;
|
|
598
620
|
}
|
|
621
|
+
.cv-deck__pad {
|
|
622
|
+
display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--cv-muted);
|
|
623
|
+
}
|
|
624
|
+
.cv-deck__pad input {
|
|
625
|
+
width: 48px; font: inherit; font-size: 12.5px; padding: 4px 6px;
|
|
626
|
+
border: 1px solid var(--cv-border); border-radius: 6px; background: var(--cv-bg); color: var(--cv-text);
|
|
627
|
+
}
|
|
599
628
|
.cv-deck__present {
|
|
600
629
|
width: auto !important; padding: 0 12px !important;
|
|
601
630
|
background: var(--cv-accent) !important; color: #fff !important;
|
|
@@ -690,6 +719,12 @@
|
|
|
690
719
|
.cv-chart__types .cv-edit-btn { text-transform: capitalize; }
|
|
691
720
|
.cv-chart__stack { display: flex; align-items: center; gap: 6px; font-size: 13px; color: var(--cv-muted); cursor: pointer; }
|
|
692
721
|
.cv-chart__spacer { flex: 1; }
|
|
722
|
+
.cv-chart__title-input {
|
|
723
|
+
font: inherit; font-size: 13px; padding: 5px 10px; border-radius: 7px;
|
|
724
|
+
border: 1px solid var(--cv-border); background: var(--cv-bg); color: var(--cv-text);
|
|
725
|
+
min-width: 120px; max-width: 220px;
|
|
726
|
+
}
|
|
727
|
+
.cv-chart__title-input:focus { outline: none; border-color: var(--cv-accent); }
|
|
693
728
|
|
|
694
729
|
/* --- chart editor ------------------------------------------------------------- */
|
|
695
730
|
.cv-chart__editor {
|
package/package.json
CHANGED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { useArtifactPatch } from './chunk-TERWLUW3.js';
|
|
2
|
-
import './chunk-KKLWKR5G.js';
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import ReactMarkdown from 'react-markdown';
|
|
5
|
-
import remarkGfm from 'remark-gfm';
|
|
6
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
|
-
|
|
8
|
-
function DocumentRenderer({ artifact }) {
|
|
9
|
-
const { content } = artifact.data;
|
|
10
|
-
const patch = useArtifactPatch(artifact.id);
|
|
11
|
-
const [draft, setDraft] = useState(null);
|
|
12
|
-
const editing = draft !== null;
|
|
13
|
-
const commit = () => {
|
|
14
|
-
if (draft !== null && draft !== content) patch({ content: draft });
|
|
15
|
-
setDraft(null);
|
|
16
|
-
};
|
|
17
|
-
return /* @__PURE__ */ jsx("div", { className: "cv-word", children: /* @__PURE__ */ jsx("div", { className: "cv-word__page", children: editing ? /* @__PURE__ */ jsx(
|
|
18
|
-
"textarea",
|
|
19
|
-
{
|
|
20
|
-
className: "cv-doc-editor",
|
|
21
|
-
value: draft,
|
|
22
|
-
autoFocus: true,
|
|
23
|
-
onBlur: commit,
|
|
24
|
-
onChange: (e) => setDraft(e.target.value)
|
|
25
|
-
}
|
|
26
|
-
) : /* @__PURE__ */ jsxs(
|
|
27
|
-
"article",
|
|
28
|
-
{
|
|
29
|
-
className: "cv-doc",
|
|
30
|
-
title: "Click to edit",
|
|
31
|
-
onClick: () => artifact.status !== "streaming" && setDraft(content),
|
|
32
|
-
children: [
|
|
33
|
-
/* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], children: content }),
|
|
34
|
-
artifact.status === "streaming" && /* @__PURE__ */ jsx("span", { className: "cv-caret", "aria-hidden": true })
|
|
35
|
-
]
|
|
36
|
-
}
|
|
37
|
-
) }) });
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export { DocumentRenderer };
|