pxengine 0.1.75 → 0.1.76

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
@@ -38864,23 +38864,26 @@ var CloseIcon = () => /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("svg", { w
38864
38864
  ] });
38865
38865
  var ChevronLeft2 = () => /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("polyline", { points: "15 18 9 12 15 6" }) });
38866
38866
  var ChevronRight2 = () => /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("polyline", { points: "9 18 15 12 9 6" }) });
38867
+ var ExpandIcon = () => /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
38868
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("polyline", { points: "15 3 21 3 21 9" }),
38869
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("polyline", { points: "9 21 3 21 3 15" }),
38870
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("line", { x1: "21", y1: "3", x2: "14", y2: "10" }),
38871
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("line", { x1: "3", y1: "21", x2: "10", y2: "14" })
38872
+ ] });
38873
+ var ShareIcon = () => /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.2", strokeLinecap: "round", strokeLinejoin: "round", children: [
38874
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("path", { d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71" }),
38875
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("path", { d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71" })
38876
+ ] });
38877
+ var CheckIcon = () => /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("polyline", { points: "20 6 9 17 4 12" }) });
38867
38878
  var Skel = ({ className, style }) => /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: cn("animate-pulse rounded-md bg-zinc-800/60", className), style });
38868
38879
  var FORMATS = [
38869
- {
38870
- key: "pptx_url",
38871
- label: "PowerPoint",
38872
- ext: ".pptx",
38873
- emoji: "\u{1F4CA}",
38874
- desc: "Editable slides \u2014 open in Microsoft PowerPoint or Google Slides",
38875
- accent: { text: "text-orange-400", bg: "bg-orange-500/10 border-orange-500/20", pill: "bg-orange-500/15 text-orange-400 border-orange-500/25" }
38876
- },
38877
38880
  {
38878
38881
  key: "pdf_url",
38879
38882
  label: "PDF",
38880
38883
  ext: ".pdf",
38881
38884
  emoji: "\u{1F4C4}",
38882
38885
  desc: "Print-ready, fixed layout \u2014 open in any PDF viewer",
38883
- accent: { text: "text-red-400", bg: "bg-red-500/10 border-red-500/20", pill: "bg-red-500/15 text-red-400 border-red-500/25" }
38886
+ accent: { text: "text-red-400", bg: "bg-red-500/10 border-red-500/20" }
38884
38887
  },
38885
38888
  {
38886
38889
  key: "html_url",
@@ -38888,29 +38891,21 @@ var FORMATS = [
38888
38891
  ext: ".html",
38889
38892
  emoji: "\u{1F310}",
38890
38893
  desc: "Interactive web presentation \u2014 download and open in browser",
38891
- accent: { text: "text-indigo-400", bg: "bg-indigo-500/10 border-indigo-500/20", pill: "bg-indigo-500/15 text-indigo-400 border-indigo-500/25" }
38894
+ accent: { text: "text-indigo-400", bg: "bg-indigo-500/10 border-indigo-500/20" }
38892
38895
  }
38893
38896
  ];
38894
38897
  var ExportModal = ({ formats, title, onClose }) => {
38895
38898
  const available = FORMATS.filter((f) => formats[f.key]);
38896
38899
  const filename = title.replace(/[^a-z0-9]/gi, "-").toLowerCase();
38897
38900
  const [downloadingKey, setDownloadingKey] = (0, import_react74.useState)(null);
38898
- const buildDownloadHref = (url, ext) => {
38899
- const downloadName = `${filename}${ext}`;
38900
- return `/api/download?url=${encodeURIComponent(url)}&filename=${encodeURIComponent(downloadName)}`;
38901
- };
38902
38901
  const handleDownload = async (fmtKey, url, ext) => {
38903
- if (downloadingKey) {
38904
- return;
38905
- }
38906
- const downloadHref = buildDownloadHref(url, ext);
38902
+ if (downloadingKey) return;
38907
38903
  const downloadName = `${filename}${ext}`;
38904
+ const href = `/api/download?url=${encodeURIComponent(url)}&filename=${encodeURIComponent(downloadName)}`;
38908
38905
  try {
38909
38906
  setDownloadingKey(fmtKey);
38910
- const response = await fetch(downloadHref);
38911
- if (!response.ok) {
38912
- throw new Error(`Download failed with status ${response.status}`);
38913
- }
38907
+ const response = await fetch(href);
38908
+ if (!response.ok) throw new Error(`status ${response.status}`);
38914
38909
  const blob = await response.blob();
38915
38910
  const objectUrl = window.URL.createObjectURL(blob);
38916
38911
  const anchor = document.createElement("a");
@@ -38921,7 +38916,7 @@ var ExportModal = ({ formats, title, onClose }) => {
38921
38916
  anchor.remove();
38922
38917
  window.URL.revokeObjectURL(objectUrl);
38923
38918
  } catch {
38924
- window.open(downloadHref, "_blank", "noopener,noreferrer");
38919
+ window.open(href, "_blank", "noopener,noreferrer");
38925
38920
  } finally {
38926
38921
  setDownloadingKey(null);
38927
38922
  }
@@ -38934,15 +38929,7 @@ var ExportModal = ({ formats, title, onClose }) => {
38934
38929
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("h3", { className: "text-sm font-semibold text-zinc-100", children: "Export Presentation" }),
38935
38930
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("p", { className: "text-xs text-zinc-500 mt-0.5 truncate", children: title })
38936
38931
  ] }),
38937
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
38938
- "button",
38939
- {
38940
- onClick: onClose,
38941
- className: "text-zinc-500 hover:text-zinc-200 transition-colors flex-shrink-0 mt-0.5",
38942
- "aria-label": "Close",
38943
- children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(CloseIcon, {})
38944
- }
38945
- )
38932
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("button", { onClick: onClose, className: "text-zinc-500 hover:text-zinc-200 transition-colors flex-shrink-0 mt-0.5", children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(CloseIcon, {}) })
38946
38933
  ] }),
38947
38934
  /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "space-y-2.5", children: [
38948
38935
  available.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("p", { className: "text-sm text-zinc-500 text-center py-6", children: "No formats available yet" }),
@@ -38966,7 +38953,7 @@ var ExportModal = ({ formats, title, onClose }) => {
38966
38953
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("p", { className: cn("text-sm font-semibold", fmt.accent.text), children: isDownloading ? `Downloading ${fmt.label}...` : fmt.label }),
38967
38954
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("p", { className: "text-xs text-zinc-500 mt-0.5 leading-relaxed", children: isDownloading ? "Please wait while the file is being prepared." : fmt.desc })
38968
38955
  ] }),
38969
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { className: cn("flex-shrink-0 transition-colors", fmt.accent.text), children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(DownloadIcon, {}) })
38956
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { className: cn("flex-shrink-0", fmt.accent.text), children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(DownloadIcon, {}) })
38970
38957
  ]
38971
38958
  },
38972
38959
  fmt.key
@@ -38977,6 +38964,87 @@ var ExportModal = ({ formats, title, onClose }) => {
38977
38964
  ] })
38978
38965
  ] });
38979
38966
  };
38967
+ var FullscreenModal = ({ url, title, slideCount, onClose }) => {
38968
+ const [currentSlide, setCurrentSlide] = (0, import_react74.useState)(1);
38969
+ const iframeRef = (0, import_react74.useRef)(null);
38970
+ (0, import_react74.useEffect)(() => {
38971
+ const onKey = (e) => {
38972
+ if (e.key === "Escape") onClose();
38973
+ };
38974
+ const onMsg = (e) => {
38975
+ if (e.data?.type === "presentationExit") onClose();
38976
+ if (e.data?.type === "slideChanged") setCurrentSlide(e.data.slide);
38977
+ };
38978
+ document.addEventListener("keydown", onKey);
38979
+ window.addEventListener("message", onMsg);
38980
+ return () => {
38981
+ document.removeEventListener("keydown", onKey);
38982
+ window.removeEventListener("message", onMsg);
38983
+ };
38984
+ }, [onClose]);
38985
+ (0, import_react74.useEffect)(() => {
38986
+ document.body.style.overflow = "hidden";
38987
+ return () => {
38988
+ document.body.style.overflow = "";
38989
+ };
38990
+ }, []);
38991
+ const send = (type) => iframeRef.current?.contentWindow?.postMessage({ type }, "*");
38992
+ return /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "fixed inset-0 z-50 bg-black flex flex-col", children: [
38993
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center justify-between gap-4 px-4 h-11 bg-zinc-950/90 border-b border-zinc-800/60 flex-shrink-0", children: [
38994
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center gap-3 min-w-0", children: [
38995
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { className: "text-indigo-400 flex-shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(SlidesIcon, {}) }),
38996
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { className: "text-sm font-medium text-zinc-300 truncate", children: title }),
38997
+ slideCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("span", { className: "text-xs text-zinc-600 flex-shrink-0", children: [
38998
+ currentSlide,
38999
+ " / ",
39000
+ slideCount
39001
+ ] })
39002
+ ] }),
39003
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
39004
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39005
+ "button",
39006
+ {
39007
+ onClick: () => send("prevSlide"),
39008
+ className: "w-7 h-7 rounded-full border border-zinc-700 bg-zinc-800 hover:border-indigo-500 hover:text-indigo-400 text-zinc-400 flex items-center justify-center transition-colors",
39009
+ "aria-label": "Previous slide",
39010
+ children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ChevronLeft2, {})
39011
+ }
39012
+ ),
39013
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39014
+ "button",
39015
+ {
39016
+ onClick: () => send("nextSlide"),
39017
+ className: "w-7 h-7 rounded-full border border-zinc-700 bg-zinc-800 hover:border-indigo-500 hover:text-indigo-400 text-zinc-400 flex items-center justify-center transition-colors",
39018
+ "aria-label": "Next slide",
39019
+ children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ChevronRight2, {})
39020
+ }
39021
+ ),
39022
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)(
39023
+ "button",
39024
+ {
39025
+ onClick: onClose,
39026
+ className: "flex items-center gap-1.5 px-3 h-7 rounded-lg border border-zinc-700 bg-zinc-800 hover:border-zinc-500 text-zinc-400 hover:text-zinc-200 text-xs transition-colors",
39027
+ children: [
39028
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(CloseIcon, {}),
39029
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { children: "ESC" })
39030
+ ]
39031
+ }
39032
+ )
39033
+ ] })
39034
+ ] }),
39035
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: "flex-1 relative", children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39036
+ "iframe",
39037
+ {
39038
+ ref: iframeRef,
39039
+ src: url,
39040
+ title,
39041
+ onLoad: () => iframeRef.current?.contentWindow?.postMessage({ type: "goToSlide", slide: currentSlide }, "*"),
39042
+ sandbox: "allow-same-origin allow-scripts allow-fullscreen",
39043
+ className: "absolute inset-0 w-full h-full border-0"
39044
+ }
39045
+ ) })
39046
+ ] });
39047
+ };
38980
39048
  var PresentationJobCard = ({
38981
39049
  job_id: _job_id,
38982
39050
  title: initialTitle,
@@ -38985,6 +39053,7 @@ var PresentationJobCard = ({
38985
39053
  formats: initialFormats = {},
38986
39054
  error: initialError,
38987
39055
  pollUrl,
39056
+ shareUrl,
38988
39057
  className
38989
39058
  }) => {
38990
39059
  const [status, setStatus] = (0, import_react74.useState)(initialStatus);
@@ -38992,7 +39061,9 @@ var PresentationJobCard = ({
38992
39061
  const [slideCount, setSlideCount] = (0, import_react74.useState)(initialSlideCount ?? 0);
38993
39062
  const [formats, setFormats] = (0, import_react74.useState)(initialFormats);
38994
39063
  const [error, setError] = (0, import_react74.useState)(initialError);
38995
- const [showModal, setShowModal] = (0, import_react74.useState)(false);
39064
+ const [showExport, setShowExport] = (0, import_react74.useState)(false);
39065
+ const [showFullscreen, setShowFullscreen] = (0, import_react74.useState)(false);
39066
+ const [copied, setCopied] = (0, import_react74.useState)(false);
38996
39067
  const [currentSlide, setCurrentSlide] = (0, import_react74.useState)(1);
38997
39068
  const [previewScale, setPreviewScale] = (0, import_react74.useState)(1);
38998
39069
  const [iframeReady, setIframeReady] = (0, import_react74.useState)(false);
@@ -39001,9 +39072,7 @@ var PresentationJobCard = ({
39001
39072
  const previewRef = (0, import_react74.useRef)(null);
39002
39073
  const iframeRef = (0, import_react74.useRef)(null);
39003
39074
  const updateScale = (0, import_react74.useCallback)(() => {
39004
- if (previewRef.current) {
39005
- setPreviewScale(previewRef.current.offsetWidth / 1280);
39006
- }
39075
+ if (previewRef.current) setPreviewScale(previewRef.current.offsetWidth / 1280);
39007
39076
  }, []);
39008
39077
  (0, import_react74.useEffect)(() => {
39009
39078
  updateScale();
@@ -39020,9 +39089,7 @@ var PresentationJobCard = ({
39020
39089
  const handler = (e) => {
39021
39090
  if (e.data?.type === "slideChanged") {
39022
39091
  setCurrentSlide(e.data.slide);
39023
- if (e.data.total && !slideCount) {
39024
- setSlideCount(e.data.total);
39025
- }
39092
+ if (e.data.total && !slideCount) setSlideCount(e.data.total);
39026
39093
  }
39027
39094
  };
39028
39095
  window.addEventListener("message", handler);
@@ -39034,9 +39101,7 @@ var PresentationJobCard = ({
39034
39101
  setPendingSlide(null);
39035
39102
  }, [iframeReady, pendingSlide]);
39036
39103
  const sendSlideCommand = (command) => {
39037
- if (!iframeReady) {
39038
- return;
39039
- }
39104
+ if (!iframeReady) return;
39040
39105
  iframeRef.current?.contentWindow?.postMessage({ type: command }, "*");
39041
39106
  };
39042
39107
  const goToSlide = (n) => {
@@ -39064,9 +39129,7 @@ var PresentationJobCard = ({
39064
39129
  if (data.output.slide_count) setSlideCount(data.output.slide_count);
39065
39130
  if (data.output.formats) setFormats(data.output.formats);
39066
39131
  }
39067
- if (newStatus === "failed" && data.error) {
39068
- setError(data.error);
39069
- }
39132
+ if (newStatus === "failed" && data.error) setError(data.error);
39070
39133
  } catch {
39071
39134
  }
39072
39135
  };
@@ -39082,6 +39145,17 @@ var PresentationJobCard = ({
39082
39145
  intervalRef.current = null;
39083
39146
  }
39084
39147
  }, [isTerminal]);
39148
+ const handleShare = async () => {
39149
+ const link = shareUrl ?? formats.html_url;
39150
+ if (!link) return;
39151
+ try {
39152
+ await navigator.clipboard.writeText(link);
39153
+ setCopied(true);
39154
+ setTimeout(() => setCopied(false), 2e3);
39155
+ } catch {
39156
+ window.open(link, "_blank", "noopener,noreferrer");
39157
+ }
39158
+ };
39085
39159
  if (status === "pending" || status === "running") {
39086
39160
  return /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: cn("w-full", className), children: /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl p-5", children: [
39087
39161
  /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center gap-3 mb-5", children: [
@@ -39107,8 +39181,8 @@ var PresentationJobCard = ({
39107
39181
  ] })
39108
39182
  ] }) }) });
39109
39183
  }
39110
- const availablePills = FORMATS.filter((f) => formats[f.key]);
39111
39184
  const total = slideCount || 1;
39185
+ const hasHtml = Boolean(formats.html_url);
39112
39186
  return /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)(import_jsx_runtime146.Fragment, { children: [
39113
39187
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: cn("w-full", className), children: /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "bg-zinc-900 border border-zinc-800 rounded-2xl overflow-hidden", children: [
39114
39188
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: "h-[3px] bg-gradient-to-r from-indigo-600 via-violet-500 to-purple-600" }),
@@ -39122,118 +39196,127 @@ var PresentationJobCard = ({
39122
39196
  " \xB7 Ready to export"
39123
39197
  ] })
39124
39198
  ] }),
39125
- /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)(
39126
- "button",
39127
- {
39128
- onClick: () => setShowModal(true),
39129
- className: "flex items-center gap-1.5 px-3 py-1.5 bg-indigo-600 hover:bg-indigo-500 active:bg-indigo-700 text-white text-xs font-medium rounded-lg transition-colors flex-shrink-0",
39130
- children: [
39131
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(DownloadIcon, {}),
39132
- "Export"
39133
- ]
39134
- }
39135
- )
39199
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center gap-1.5 flex-shrink-0", children: [
39200
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39201
+ "button",
39202
+ {
39203
+ onClick: handleShare,
39204
+ disabled: !hasHtml,
39205
+ title: copied ? "Copied!" : "Copy shareable link",
39206
+ className: cn(
39207
+ "w-7 h-7 rounded-lg border flex items-center justify-center transition-all",
39208
+ copied ? "border-green-500/40 bg-green-500/10 text-green-400" : "border-zinc-700 bg-zinc-800 text-zinc-400 hover:border-zinc-500 hover:text-zinc-200",
39209
+ "disabled:opacity-30 disabled:cursor-not-allowed"
39210
+ ),
39211
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(CheckIcon, {}) : /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ShareIcon, {})
39212
+ }
39213
+ ),
39214
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)(
39215
+ "button",
39216
+ {
39217
+ onClick: () => setShowFullscreen(true),
39218
+ disabled: !hasHtml,
39219
+ title: "Open fullscreen preview",
39220
+ className: "flex items-center gap-1.5 px-2.5 h-7 rounded-lg border border-zinc-700 bg-zinc-800 hover:border-zinc-500 text-zinc-400 hover:text-zinc-200 text-xs font-medium transition-colors disabled:opacity-30 disabled:cursor-not-allowed",
39221
+ children: [
39222
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ExpandIcon, {}),
39223
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { children: "Preview" })
39224
+ ]
39225
+ }
39226
+ ),
39227
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)(
39228
+ "button",
39229
+ {
39230
+ onClick: () => setShowExport(true),
39231
+ className: "flex items-center gap-1.5 px-2.5 h-7 bg-indigo-600 hover:bg-indigo-500 active:bg-indigo-700 text-white text-xs font-medium rounded-lg transition-colors",
39232
+ children: [
39233
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(DownloadIcon, {}),
39234
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { children: "Export" })
39235
+ ]
39236
+ }
39237
+ )
39238
+ ] })
39136
39239
  ] }),
39137
- formats.html_url && /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "mt-4", children: [
39138
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39240
+ hasHtml && /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "mt-4", children: [
39241
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)(
39139
39242
  "div",
39140
39243
  {
39141
39244
  ref: previewRef,
39142
- className: "relative overflow-hidden rounded-xl bg-zinc-950 border border-zinc-800/50",
39245
+ onClick: () => setShowFullscreen(true),
39246
+ className: "group relative overflow-hidden rounded-xl bg-zinc-950 border border-zinc-800/50 cursor-pointer",
39143
39247
  style: { height: Math.round(previewScale * 720) },
39144
- children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39145
- "iframe",
39146
- {
39147
- ref: iframeRef,
39148
- src: formats.html_url,
39149
- title,
39150
- onLoad: () => setIframeReady(true),
39151
- sandbox: "allow-same-origin allow-scripts",
39152
- style: {
39153
- width: 1280,
39154
- height: 720,
39155
- transform: `scale(${previewScale})`,
39156
- transformOrigin: "top left",
39157
- border: "none",
39158
- pointerEvents: "none",
39159
- display: "block"
39248
+ title: "Click to open fullscreen",
39249
+ children: [
39250
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39251
+ "iframe",
39252
+ {
39253
+ ref: iframeRef,
39254
+ src: formats.html_url,
39255
+ title,
39256
+ onLoad: () => setIframeReady(true),
39257
+ sandbox: "allow-same-origin allow-scripts",
39258
+ style: {
39259
+ width: 1280,
39260
+ height: 720,
39261
+ transform: `scale(${previewScale})`,
39262
+ transformOrigin: "top left",
39263
+ border: "none",
39264
+ pointerEvents: "none",
39265
+ display: "block"
39266
+ }
39160
39267
  }
39161
- }
39162
- )
39268
+ ),
39269
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: "absolute inset-0 flex items-center justify-center bg-black/0 group-hover:bg-black/30 transition-colors", children: /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("span", { className: "opacity-0 group-hover:opacity-100 transition-opacity flex items-center gap-2 bg-zinc-900/80 backdrop-blur-sm border border-zinc-700 rounded-lg px-3 py-1.5 text-xs text-zinc-200 font-medium", children: [
39270
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ExpandIcon, {}),
39271
+ "Open fullscreen"
39272
+ ] }) })
39273
+ ]
39163
39274
  }
39164
39275
  ),
39165
- /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center justify-between mt-3", children: [
39166
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: "flex items-center gap-2", children: availablePills.map((fmt) => /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39167
- "span",
39276
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center gap-2 mt-3", children: [
39277
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39278
+ "button",
39168
39279
  {
39169
- className: cn(
39170
- "text-xs px-2.5 py-0.5 rounded-md border font-medium",
39171
- fmt.accent.pill
39172
- ),
39173
- children: fmt.label
39174
- },
39175
- fmt.key
39176
- )) }),
39177
- /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex items-center gap-2", children: [
39178
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39179
- "button",
39180
- {
39181
- onClick: () => {
39182
- if (slideCount > 1) {
39183
- goToSlide(currentSlide - 1);
39184
- return;
39185
- }
39186
- sendSlideCommand("prevSlide");
39187
- },
39188
- disabled: !formats.html_url,
39189
- className: "w-7 h-7 rounded-full border border-zinc-700 bg-zinc-800 hover:border-indigo-500 hover:text-indigo-400 text-zinc-400 flex items-center justify-center transition-colors disabled:opacity-30 disabled:cursor-not-allowed",
39190
- "aria-label": "Previous slide",
39191
- children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ChevronLeft2, {})
39192
- }
39193
- ),
39194
- /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("span", { className: "text-xs text-zinc-500 font-medium tabular-nums min-w-[3rem] text-center", children: [
39195
- currentSlide,
39196
- " / ",
39197
- total
39198
- ] }),
39199
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39200
- "button",
39201
- {
39202
- onClick: () => {
39203
- if (slideCount > 1) {
39204
- goToSlide(currentSlide + 1);
39205
- return;
39206
- }
39207
- sendSlideCommand("nextSlide");
39208
- },
39209
- disabled: !formats.html_url,
39210
- className: "w-7 h-7 rounded-full border border-zinc-700 bg-zinc-800 hover:border-indigo-500 hover:text-indigo-400 text-zinc-400 flex items-center justify-center transition-colors disabled:opacity-30 disabled:cursor-not-allowed",
39211
- "aria-label": "Next slide",
39212
- children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ChevronRight2, {})
39213
- }
39214
- )
39215
- ] })
39216
- ] })
39217
- ] }),
39218
- !formats.html_url && availablePills.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: "flex items-center gap-2 mt-4", children: availablePills.map((fmt) => /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39219
- "span",
39220
- {
39221
- className: cn(
39222
- "text-xs px-2.5 py-0.5 rounded-md border font-medium",
39223
- fmt.accent.pill
39280
+ onClick: (e) => {
39281
+ e.stopPropagation();
39282
+ slideCount > 1 ? goToSlide(currentSlide - 1) : sendSlideCommand("prevSlide");
39283
+ },
39284
+ disabled: !hasHtml,
39285
+ className: "w-7 h-7 rounded-full border border-zinc-700 bg-zinc-800 hover:border-indigo-500 hover:text-indigo-400 text-zinc-400 flex items-center justify-center transition-colors disabled:opacity-30 disabled:cursor-not-allowed",
39286
+ "aria-label": "Previous slide",
39287
+ children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ChevronLeft2, {})
39288
+ }
39224
39289
  ),
39225
- children: fmt.label
39226
- },
39227
- fmt.key
39228
- )) })
39290
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("span", { className: "text-xs text-zinc-500 font-medium tabular-nums min-w-[3rem] text-center", children: [
39291
+ currentSlide,
39292
+ " / ",
39293
+ total
39294
+ ] }),
39295
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39296
+ "button",
39297
+ {
39298
+ onClick: (e) => {
39299
+ e.stopPropagation();
39300
+ slideCount > 1 ? goToSlide(currentSlide + 1) : sendSlideCommand("nextSlide");
39301
+ },
39302
+ disabled: !hasHtml,
39303
+ className: "w-7 h-7 rounded-full border border-zinc-700 bg-zinc-800 hover:border-indigo-500 hover:text-indigo-400 text-zinc-400 flex items-center justify-center transition-colors disabled:opacity-30 disabled:cursor-not-allowed",
39304
+ "aria-label": "Next slide",
39305
+ children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ChevronRight2, {})
39306
+ }
39307
+ )
39308
+ ] })
39309
+ ] })
39229
39310
  ] })
39230
39311
  ] }) }),
39231
- showModal && /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39232
- ExportModal,
39312
+ showExport && /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ExportModal, { formats, title, onClose: () => setShowExport(false) }),
39313
+ showFullscreen && hasHtml && /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39314
+ FullscreenModal,
39233
39315
  {
39234
- formats,
39316
+ url: formats.html_url,
39235
39317
  title,
39236
- onClose: () => setShowModal(false)
39318
+ slideCount,
39319
+ onClose: () => setShowFullscreen(false)
39237
39320
  }
39238
39321
  )
39239
39322
  ] });