@flytedan/flytebot-design-system 0.12.11 → 0.12.12

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/index.cjs CHANGED
@@ -73,6 +73,8 @@ __export(index_exports, {
73
73
  EmptyState: () => EmptyState,
74
74
  EntityRow: () => EntityRow,
75
75
  EntityRowMetrics: () => EntityRowMetrics,
76
+ FILE_VIEWER_CSV_ROW_LIMIT: () => FILE_VIEWER_CSV_ROW_LIMIT,
77
+ FILE_VIEWER_TEXT_LIMIT: () => FILE_VIEWER_TEXT_LIMIT,
76
78
  FeatureGate: () => FeatureGate,
77
79
  FileChip: () => FileChip,
78
80
  FileGrid: () => FileGrid,
@@ -80,6 +82,7 @@ __export(index_exports, {
80
82
  FilePickButton: () => FilePickButton,
81
83
  FileStrip: () => FileStrip,
82
84
  FileTile: () => FileTile,
85
+ FileViewer: () => FileViewer,
83
86
  FilterBar: () => FilterBar,
84
87
  Flag: () => Flag,
85
88
  FlytedeskBrand: () => FlytedeskBrand,
@@ -184,6 +187,7 @@ __export(index_exports, {
184
187
  markdownToText: () => markdownToText,
185
188
  meterFormats: () => meterFormats,
186
189
  modifierLabel: () => modifierLabel,
190
+ parseDelimited: () => parseDelimited,
187
191
  pastedTextName: () => pastedTextName,
188
192
  pluralizeUnit: () => pluralizeUnit,
189
193
  preferenceScore: () => preferenceScore,
@@ -4672,7 +4676,7 @@ function useStagedFiles(upload, opts) {
4672
4676
  patch(att.id, { progress: 0, error: void 0 });
4673
4677
  Promise.resolve().then(() => upload(att.file, (p) => patch(att.id, { progress: Math.max(0, Math.min(99, p)) }), ac ? ac.signal : void 0)).then((res) => {
4674
4678
  const r = res || {};
4675
- patch(att.id, { progress: 100, url: r.url || att.url, thumb: r.thumb || att.thumb, error: void 0, remote: true });
4679
+ patch(att.id, Object.assign({}, r, { id: r.id || att.id, progress: 100, error: void 0 }));
4676
4680
  o.onUploaded && o.onUploaded(att);
4677
4681
  }).catch((err) => {
4678
4682
  if (ac && ac.signal.aborted) return;
@@ -9668,7 +9672,9 @@ function ChatComposer({
9668
9672
  const submit = () => {
9669
9673
  if (!canSend) return;
9670
9674
  onSubmit(text.trim(), staged.items.map((f) => {
9671
- const { file: _file, ...rest } = f;
9675
+ const { file, blobUrl, ...rest } = f;
9676
+ void file;
9677
+ void blobUrl;
9672
9678
  return rest;
9673
9679
  }));
9674
9680
  change("");
@@ -10363,8 +10369,219 @@ function AgentChatPanel({
10363
10369
  );
10364
10370
  }
10365
10371
 
10366
- // src/kits/query.ts
10372
+ // src/components/files/FileViewer.tsx
10367
10373
  var React47 = __toESM(require("react"), 1);
10374
+ var import_jsx_runtime78 = require("react/jsx-runtime");
10375
+ function previewKindOf(file) {
10376
+ const mime = (file.mime || "").toLowerCase();
10377
+ const ext = extensionOf(file.name || "");
10378
+ if (isImage(mime, file.name)) return "image";
10379
+ if (mime === "application/pdf" || ext === "pdf") return "pdf";
10380
+ if (mime === "text/markdown" || ext === "md" || ext === "markdown") return "markdown";
10381
+ if (mime === "application/json" || ext === "json") return "json";
10382
+ if (mime === "text/csv" || mime === "text/tab-separated-values" || ext === "csv" || ext === "tsv") return "csv";
10383
+ if (mime.startsWith("text/") || ext === "txt" || ext === "log") return "text";
10384
+ return "none";
10385
+ }
10386
+ var FILE_VIEWER_TEXT_LIMIT = 5e5;
10387
+ var FILE_VIEWER_CSV_ROW_LIMIT = 500;
10388
+ function parseDelimited(text, delimiter) {
10389
+ const rows = [];
10390
+ let row = [];
10391
+ let field = "";
10392
+ let inQuotes = false;
10393
+ for (let i = 0; i < text.length; i++) {
10394
+ const c = text[i];
10395
+ if (inQuotes) {
10396
+ if (c === '"') {
10397
+ if (text[i + 1] === '"') {
10398
+ field += '"';
10399
+ i++;
10400
+ } else inQuotes = false;
10401
+ } else field += c;
10402
+ continue;
10403
+ }
10404
+ if (c === '"') {
10405
+ inQuotes = true;
10406
+ continue;
10407
+ }
10408
+ if (c === delimiter) {
10409
+ row.push(field);
10410
+ field = "";
10411
+ continue;
10412
+ }
10413
+ if (c === "\n") {
10414
+ row.push(field);
10415
+ rows.push(row);
10416
+ row = [];
10417
+ field = "";
10418
+ continue;
10419
+ }
10420
+ if (c === "\r") continue;
10421
+ field += c;
10422
+ }
10423
+ if (field.length || row.length) {
10424
+ row.push(field);
10425
+ rows.push(row);
10426
+ }
10427
+ return rows;
10428
+ }
10429
+ function usePreview(file, kind, resolveUrl) {
10430
+ const [state, setState] = React47.useState({ status: "loading" });
10431
+ const [attempt, setAttempt] = React47.useState(0);
10432
+ const resolveRef = React47.useRef(resolveUrl);
10433
+ React47.useEffect(() => {
10434
+ resolveRef.current = resolveUrl;
10435
+ }, [resolveUrl]);
10436
+ React47.useEffect(() => {
10437
+ if (kind === "none") {
10438
+ setState({ status: "ready" });
10439
+ return;
10440
+ }
10441
+ let dead = false;
10442
+ const ac = typeof AbortController !== "undefined" ? new AbortController() : void 0;
10443
+ setState({ status: "loading" });
10444
+ resolveRef.current("inline").then((url) => {
10445
+ if (dead) return void 0;
10446
+ if (kind === "image" || kind === "pdf") {
10447
+ setState({ status: "ready", url });
10448
+ return void 0;
10449
+ }
10450
+ return fetch(url, ac ? { signal: ac.signal } : void 0).then((res) => {
10451
+ if (!res.ok) throw new Error("The preview didn't load (" + res.status + ").");
10452
+ return res.text();
10453
+ }).then((body) => {
10454
+ if (dead) return;
10455
+ setState({ status: "ready", url, text: body.length > FILE_VIEWER_TEXT_LIMIT ? body.slice(0, FILE_VIEWER_TEXT_LIMIT) : body });
10456
+ });
10457
+ }).catch((err) => {
10458
+ if (dead || ac && ac.signal.aborted) return;
10459
+ setState({ status: "error", error: err && err.message || "The preview didn't load." });
10460
+ });
10461
+ return () => {
10462
+ dead = true;
10463
+ if (ac) ac.abort();
10464
+ };
10465
+ }, [file.id, kind, attempt]);
10466
+ return { ...state, retry: React47.useCallback(() => setAttempt((a) => a + 1), []) };
10467
+ }
10468
+ function PreviewBody({ file, kind, state }) {
10469
+ if (kind === "image") return /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("img", { className: "fd-fv-image", src: state.url, alt: file.name || "Preview" });
10470
+ if (kind === "pdf") return /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("iframe", { className: "fd-fv-pdf", src: state.url, title: file.name || "PDF preview" });
10471
+ if (kind === "markdown") return /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(Markdown, { className: "fd-fv-markdown", source: state.text || "" });
10472
+ if (kind === "json") {
10473
+ let pretty = state.text || "";
10474
+ try {
10475
+ pretty = JSON.stringify(JSON.parse(pretty), null, 2);
10476
+ } catch {
10477
+ }
10478
+ return /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(CodeBlock, { code: pretty, language: "json" });
10479
+ }
10480
+ if (kind === "csv") {
10481
+ const tsv = extensionOf(file.name || "") === "tsv" || (file.mime || "").toLowerCase().includes("tab-separated");
10482
+ const rows = parseDelimited(state.text || "", tsv ? " " : ",").filter((r) => r.length > 1 || r[0] !== "");
10483
+ const head = rows[0] || [];
10484
+ const dataRows = rows.slice(1);
10485
+ const shown = dataRows.slice(0, FILE_VIEWER_CSV_ROW_LIMIT);
10486
+ return /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("div", { className: "fd-fv-csv", children: [
10487
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("table", { className: "fd-fv-csv-table", children: [
10488
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("tr", { children: head.map((c, i) => /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("th", { children: c }, i)) }) }),
10489
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("tbody", { children: shown.map((r, ri) => /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("tr", { children: head.map((_, ci) => /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("td", { children: r[ci] ?? "" }, ci)) }, ri)) })
10490
+ ] }),
10491
+ dataRows.length > FILE_VIEWER_CSV_ROW_LIMIT ? /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("p", { className: "fd-fv-csv-note fd-body-sm fd-muted", children: [
10492
+ "Showing the first ",
10493
+ FILE_VIEWER_CSV_ROW_LIMIT.toLocaleString(),
10494
+ " of ",
10495
+ dataRows.length.toLocaleString(),
10496
+ " rows."
10497
+ ] }) : null
10498
+ ] });
10499
+ }
10500
+ if (kind === "text") return /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(CodeBlock, { code: state.text || "", language: extensionOf(file.name || "") || void 0 });
10501
+ return /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("div", { className: "fd-fv-fallback", children: [
10502
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "fd-fv-fallback-icon", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("i", { className: "ph ph-" + iconForMime(file.mime, file.name) }) }),
10503
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "fd-fv-fallback-title", children: "No preview available" }),
10504
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("span", { className: "fd-fv-fallback-meta", children: [
10505
+ file.mime || "Unknown type",
10506
+ file.size ? " \xB7 " + formatBytes(file.size) : ""
10507
+ ] })
10508
+ ] });
10509
+ }
10510
+ function FileViewer({ file, resolveUrl, sidePanel, sidePanelLabel = "Details", onClose, actions, className = "" }) {
10511
+ const kind = React47.useMemo(() => previewKindOf(file), [file]);
10512
+ const preview = usePreview(file, kind, resolveUrl);
10513
+ const [tab, setTab] = React47.useState("preview");
10514
+ const [downloading, setDownloading] = React47.useState(false);
10515
+ const [downloadError, setDownloadError] = React47.useState(null);
10516
+ const hasSide = sidePanel != null;
10517
+ React47.useEffect(() => {
10518
+ setTab("preview");
10519
+ }, [file.id]);
10520
+ const download = () => {
10521
+ setDownloading(true);
10522
+ setDownloadError(null);
10523
+ resolveUrl("attachment").then((url) => {
10524
+ const a = document.createElement("a");
10525
+ a.href = url;
10526
+ a.rel = "noopener";
10527
+ a.download = file.name || "";
10528
+ document.body.appendChild(a);
10529
+ a.click();
10530
+ a.remove();
10531
+ }).catch((err) => setDownloadError(err && err.message || "Download failed.")).finally(() => setDownloading(false));
10532
+ };
10533
+ return /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)(
10534
+ "section",
10535
+ {
10536
+ className: ["fd-fv", hasSide && tab === "side" ? "is-tab-side" : "is-tab-preview", className].filter(Boolean).join(" "),
10537
+ "aria-label": file.name || "File",
10538
+ children: [
10539
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("header", { className: "fd-fv-head", children: [
10540
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "fd-fv-head-icon", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("i", { className: "ph ph-" + iconForMime(file.mime, file.name) }) }),
10541
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("span", { className: "fd-fv-head-titles", children: [
10542
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("span", { className: "fd-fv-head-title", title: file.name, children: file.name }),
10543
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("span", { className: "fd-fv-head-meta", children: [
10544
+ file.mime || "Unknown type",
10545
+ file.size ? " \xB7 " + formatBytes(file.size) : ""
10546
+ ] })
10547
+ ] }),
10548
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("span", { className: "fd-fv-head-actions", children: [
10549
+ actions,
10550
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(Button, { variant: "secondary", size: "sm", icon: "download-simple", loading: downloading, onClick: download, children: "Download" }),
10551
+ onClose ? /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(IconButton, { icon: "x", label: "Close", onClick: onClose }) : null
10552
+ ] })
10553
+ ] }),
10554
+ downloadError ? /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("div", { className: "fd-fv-download-error", role: "alert", children: downloadError }) : null,
10555
+ hasSide ? /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(
10556
+ Tabs,
10557
+ {
10558
+ className: "fd-fv-tabs",
10559
+ tabs: [{ value: "preview", label: "Preview" }, { value: "side", label: sidePanelLabel }],
10560
+ value: tab,
10561
+ onChange: (v) => setTab(v)
10562
+ }
10563
+ ) : null,
10564
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsxs)("div", { className: "fd-fv-body", children: [
10565
+ /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("div", { className: "fd-fv-preview", role: "region", "aria-label": "Preview of " + (file.name || "file"), children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(
10566
+ LoadingRegion,
10567
+ {
10568
+ state: preview.status === "loading" ? "loading" : preview.status === "error" ? "error" : "ready",
10569
+ error: preview.error,
10570
+ label: "Loading preview of " + (file.name || "file"),
10571
+ onRetry: preview.status === "error" ? preview.retry : void 0,
10572
+ skeleton: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(Skeleton, { shape: "card", h: 220 }),
10573
+ children: /* @__PURE__ */ (0, import_jsx_runtime78.jsx)(PreviewBody, { file, kind, state: preview })
10574
+ }
10575
+ ) }),
10576
+ hasSide ? /* @__PURE__ */ (0, import_jsx_runtime78.jsx)("div", { className: "fd-fv-side", role: "region", "aria-label": sidePanelLabel, children: sidePanel }) : null
10577
+ ] })
10578
+ ]
10579
+ }
10580
+ );
10581
+ }
10582
+
10583
+ // src/kits/query.ts
10584
+ var React48 = __toESM(require("react"), 1);
10368
10585
  function eqFilter(get2) {
10369
10586
  return (row, value) => Array.isArray(value) ? value.includes(get2(row)) : get2(row) === value;
10370
10587
  }
@@ -10396,22 +10613,22 @@ function compare(a, b, dir) {
10396
10613
  var API = null;
10397
10614
  var PREFS = null;
10398
10615
  function useServerTable({ endpoint, params, defaults, deps, prefsKey }) {
10399
- const [query, setQuery] = React47.useState(() => {
10616
+ const [query, setQuery] = React48.useState(() => {
10400
10617
  const store = PREFS || window.PlannerPrefs;
10401
10618
  const saved = prefsKey && store ? store.getTable(prefsKey) : {};
10402
10619
  return { ...DEFAULTS, ...defaults || {}, ...saved.pageSize ? { pageSize: saved.pageSize } : {}, ...saved.sort ? { sort: saved.sort, dir: saved.dir || "desc" } : {} };
10403
10620
  });
10404
- const savePref = React47.useCallback((patch2) => {
10621
+ const savePref = React48.useCallback((patch2) => {
10405
10622
  const store = PREFS || window.PlannerPrefs;
10406
10623
  if (prefsKey && store) store.setTable(prefsKey, patch2);
10407
10624
  }, [prefsKey]);
10408
- const [res, setRes] = React47.useState(null);
10409
- const [loading, setLoading] = React47.useState(true);
10410
- const seq2 = React47.useRef(0);
10625
+ const [res, setRes] = React48.useState(null);
10626
+ const [loading, setLoading] = React48.useState(true);
10627
+ const seq2 = React48.useRef(0);
10411
10628
  const depKey = (deps || []).join("|");
10412
10629
  const paramKey = JSON.stringify(params || {});
10413
10630
  const queryKey = JSON.stringify(query);
10414
- React47.useEffect(() => {
10631
+ React48.useEffect(() => {
10415
10632
  const id = ++seq2.current;
10416
10633
  setLoading(true);
10417
10634
  const t = setTimeout(() => {
@@ -10753,6 +10970,8 @@ function createVersionStore(initialState, options) {
10753
10970
  EmptyState,
10754
10971
  EntityRow,
10755
10972
  EntityRowMetrics,
10973
+ FILE_VIEWER_CSV_ROW_LIMIT,
10974
+ FILE_VIEWER_TEXT_LIMIT,
10756
10975
  FeatureGate,
10757
10976
  FileChip,
10758
10977
  FileGrid,
@@ -10760,6 +10979,7 @@ function createVersionStore(initialState, options) {
10760
10979
  FilePickButton,
10761
10980
  FileStrip,
10762
10981
  FileTile,
10982
+ FileViewer,
10763
10983
  FilterBar,
10764
10984
  Flag,
10765
10985
  FlytedeskBrand,
@@ -10864,6 +11084,7 @@ function createVersionStore(initialState, options) {
10864
11084
  markdownToText,
10865
11085
  meterFormats,
10866
11086
  modifierLabel,
11087
+ parseDelimited,
10867
11088
  pastedTextName,
10868
11089
  pluralizeUnit,
10869
11090
  preferenceScore,