pxengine 0.1.73 → 0.1.75

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
@@ -38894,6 +38894,38 @@ var FORMATS = [
38894
38894
  var ExportModal = ({ formats, title, onClose }) => {
38895
38895
  const available = FORMATS.filter((f) => formats[f.key]);
38896
38896
  const filename = title.replace(/[^a-z0-9]/gi, "-").toLowerCase();
38897
+ 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
+ const handleDownload = async (fmtKey, url, ext) => {
38903
+ if (downloadingKey) {
38904
+ return;
38905
+ }
38906
+ const downloadHref = buildDownloadHref(url, ext);
38907
+ const downloadName = `${filename}${ext}`;
38908
+ try {
38909
+ setDownloadingKey(fmtKey);
38910
+ const response = await fetch(downloadHref);
38911
+ if (!response.ok) {
38912
+ throw new Error(`Download failed with status ${response.status}`);
38913
+ }
38914
+ const blob = await response.blob();
38915
+ const objectUrl = window.URL.createObjectURL(blob);
38916
+ const anchor = document.createElement("a");
38917
+ anchor.href = objectUrl;
38918
+ anchor.download = downloadName;
38919
+ document.body.appendChild(anchor);
38920
+ anchor.click();
38921
+ anchor.remove();
38922
+ window.URL.revokeObjectURL(objectUrl);
38923
+ } catch {
38924
+ window.open(downloadHref, "_blank", "noopener,noreferrer");
38925
+ } finally {
38926
+ setDownloadingKey(null);
38927
+ }
38928
+ };
38897
38929
  return /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4", children: [
38898
38930
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("div", { className: "absolute inset-0 bg-black/70 backdrop-blur-sm", onClick: onClose }),
38899
38931
  /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "relative z-10 bg-zinc-900 border border-zinc-800 rounded-2xl w-full max-w-sm p-6 shadow-2xl", children: [
@@ -38916,22 +38948,23 @@ var ExportModal = ({ formats, title, onClose }) => {
38916
38948
  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" }),
38917
38949
  available.map((fmt) => {
38918
38950
  const url = formats[fmt.key];
38919
- const dlName = `${filename}${fmt.ext}`;
38951
+ const isDownloading = downloadingKey === fmt.key;
38920
38952
  return /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)(
38921
- "a",
38953
+ "button",
38922
38954
  {
38923
- href: url,
38924
- download: dlName,
38955
+ type: "button",
38956
+ disabled: Boolean(downloadingKey),
38957
+ onClick: () => handleDownload(fmt.key, url, fmt.ext),
38925
38958
  className: cn(
38926
- "flex items-center gap-3.5 p-3.5 rounded-xl border transition-all",
38927
- "hover:brightness-110 cursor-pointer no-underline group",
38959
+ "flex w-full items-center gap-3.5 p-3.5 rounded-xl border transition-all text-left",
38960
+ "hover:brightness-110 disabled:opacity-60 disabled:cursor-not-allowed",
38928
38961
  fmt.accent.bg
38929
38962
  ),
38930
38963
  children: [
38931
38964
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("span", { className: "text-xl flex-shrink-0", children: fmt.emoji }),
38932
38965
  /* @__PURE__ */ (0, import_jsx_runtime146.jsxs)("div", { className: "flex-1 min-w-0", children: [
38933
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("p", { className: cn("text-sm font-semibold", fmt.accent.text), children: fmt.label }),
38934
- /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("p", { className: "text-xs text-zinc-500 mt-0.5 leading-relaxed", children: fmt.desc })
38966
+ /* @__PURE__ */ (0, import_jsx_runtime146.jsx)("p", { className: cn("text-sm font-semibold", fmt.accent.text), children: isDownloading ? `Downloading ${fmt.label}...` : fmt.label }),
38967
+ /* @__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 })
38935
38968
  ] }),
38936
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, {}) })
38937
38970
  ]
@@ -38962,6 +38995,8 @@ var PresentationJobCard = ({
38962
38995
  const [showModal, setShowModal] = (0, import_react74.useState)(false);
38963
38996
  const [currentSlide, setCurrentSlide] = (0, import_react74.useState)(1);
38964
38997
  const [previewScale, setPreviewScale] = (0, import_react74.useState)(1);
38998
+ const [iframeReady, setIframeReady] = (0, import_react74.useState)(false);
38999
+ const [pendingSlide, setPendingSlide] = (0, import_react74.useState)(null);
38965
39000
  const intervalRef = (0, import_react74.useRef)(null);
38966
39001
  const previewRef = (0, import_react74.useRef)(null);
38967
39002
  const iframeRef = (0, import_react74.useRef)(null);
@@ -38977,6 +39012,10 @@ var PresentationJobCard = ({
38977
39012
  if (previewRef.current) ro.observe(previewRef.current);
38978
39013
  return () => ro.disconnect();
38979
39014
  }, [updateScale, formats.html_url]);
39015
+ (0, import_react74.useEffect)(() => {
39016
+ setIframeReady(false);
39017
+ setPendingSlide(null);
39018
+ }, [formats.html_url]);
38980
39019
  (0, import_react74.useEffect)(() => {
38981
39020
  const handler = (e) => {
38982
39021
  if (e.data?.type === "slideChanged") {
@@ -38989,11 +39028,26 @@ var PresentationJobCard = ({
38989
39028
  window.addEventListener("message", handler);
38990
39029
  return () => window.removeEventListener("message", handler);
38991
39030
  }, [slideCount]);
39031
+ (0, import_react74.useEffect)(() => {
39032
+ if (!iframeReady || pendingSlide === null) return;
39033
+ iframeRef.current?.contentWindow?.postMessage({ type: "goToSlide", slide: pendingSlide }, "*");
39034
+ setPendingSlide(null);
39035
+ }, [iframeReady, pendingSlide]);
39036
+ const sendSlideCommand = (command) => {
39037
+ if (!iframeReady) {
39038
+ return;
39039
+ }
39040
+ iframeRef.current?.contentWindow?.postMessage({ type: command }, "*");
39041
+ };
38992
39042
  const goToSlide = (n) => {
38993
39043
  const total2 = slideCount || 1;
38994
39044
  const target = (n - 1 + total2) % total2 + 1;
38995
39045
  setCurrentSlide(target);
38996
- iframeRef.current?.contentWindow?.postMessage({ type: "goToSlide", slide: target }, "*");
39046
+ if (iframeReady) {
39047
+ iframeRef.current?.contentWindow?.postMessage({ type: "goToSlide", slide: target }, "*");
39048
+ return;
39049
+ }
39050
+ setPendingSlide(target);
38997
39051
  };
38998
39052
  const isTerminal = status === "complete" || status === "failed";
38999
39053
  (0, import_react74.useEffect)(() => {
@@ -39093,6 +39147,7 @@ var PresentationJobCard = ({
39093
39147
  ref: iframeRef,
39094
39148
  src: formats.html_url,
39095
39149
  title,
39150
+ onLoad: () => setIframeReady(true),
39096
39151
  sandbox: "allow-same-origin allow-scripts",
39097
39152
  style: {
39098
39153
  width: 1280,
@@ -39123,8 +39178,14 @@ var PresentationJobCard = ({
39123
39178
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39124
39179
  "button",
39125
39180
  {
39126
- onClick: () => goToSlide(currentSlide - 1),
39127
- disabled: total <= 1,
39181
+ onClick: () => {
39182
+ if (slideCount > 1) {
39183
+ goToSlide(currentSlide - 1);
39184
+ return;
39185
+ }
39186
+ sendSlideCommand("prevSlide");
39187
+ },
39188
+ disabled: !formats.html_url,
39128
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",
39129
39190
  "aria-label": "Previous slide",
39130
39191
  children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ChevronLeft2, {})
@@ -39138,8 +39199,14 @@ var PresentationJobCard = ({
39138
39199
  /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(
39139
39200
  "button",
39140
39201
  {
39141
- onClick: () => goToSlide(currentSlide + 1),
39142
- disabled: total <= 1,
39202
+ onClick: () => {
39203
+ if (slideCount > 1) {
39204
+ goToSlide(currentSlide + 1);
39205
+ return;
39206
+ }
39207
+ sendSlideCommand("nextSlide");
39208
+ },
39209
+ disabled: !formats.html_url,
39143
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",
39144
39211
  "aria-label": "Next slide",
39145
39212
  children: /* @__PURE__ */ (0, import_jsx_runtime146.jsx)(ChevronRight2, {})