react-os-shell 0.1.27 → 0.1.29

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.
@@ -0,0 +1,6 @@
1
+ export { Preview as default, setPdfPreview } from './chunk-CP5X55CA.js';
2
+ import './chunk-WIJ45SYD.js';
3
+ import './chunk-AKZTZLKP.js';
4
+ import './chunk-RFTLYCSF.js';
5
+ //# sourceMappingURL=Preview-KRBYR47K.js.map
6
+ //# sourceMappingURL=Preview-KRBYR47K.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"Preview-RQPIGKTJ.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"Preview-KRBYR47K.js"}
@@ -13,8 +13,10 @@ interface PdfPreviewData {
13
13
  filename: string;
14
14
  /** Renderer to use. Defaults to `'pdf'`. `'dxf'` requires the consumer to
15
15
  * have `dxf-viewer` installed (it's an optional peer dep). `'image'`
16
- * renders an `<img>` for raster screenshots / photos. */
17
- kind?: 'pdf' | 'dxf' | 'image';
16
+ * renders an `<img>` for raster screenshots / photos. `'3d'` covers
17
+ * STEP / STL / OBJ / GLTF / 3MF / IGES via the optional
18
+ * `online-3d-viewer` peer dep. */
19
+ kind?: 'pdf' | 'dxf' | 'image' | '3d';
18
20
  /** Optional download handler — replaces the built-in "save URL as filename" if supplied. */
19
21
  onDownload?: () => void;
20
22
  /** Optional email handler — only shown when supplied. */
@@ -1,4 +1,4 @@
1
- export { setPdfPreview } from '../chunk-4XBIXMZC.js';
1
+ export { setPdfPreview } from '../chunk-CP5X55CA.js';
2
2
  import '../chunk-WIJ45SYD.js';
3
3
  import '../chunk-AKZTZLKP.js';
4
4
  import '../chunk-RFTLYCSF.js';
@@ -19,7 +19,7 @@ var Minesweeper = lazy(() => import('../Minesweeper-KAOD327F.js'));
19
19
  var Email = lazy(() => import('../Email-UCNJ53MV.js'));
20
20
  var GeminiChat = lazy(() => import('../GeminiChat-BXLBJFT4.js'));
21
21
  var Calendar = lazy(() => import('../Calendar-RQVSPJAJ.js'));
22
- var Preview = lazy(() => import('../Preview-RQPIGKTJ.js'));
22
+ var Preview = lazy(() => import('../Preview-KRBYR47K.js'));
23
23
  var Documents = lazy(() => import('../Documents-3P6JKOLE.js'));
24
24
  var utilityApps = {
25
25
  "/calculator": { component: Calculator, label: "Calculator", size: "sm", allowPinOnTop: true, utility: true, widget: true, autoHeight: true, dimensions: [280, 420] },
@@ -48,7 +48,7 @@ function Preview() {
48
48
  const ingestFile = (file) => {
49
49
  const url = URL.createObjectURL(file);
50
50
  const ext = (file.name.split(".").pop() || "").toLowerCase();
51
- const kind = ext === "pdf" ? "pdf" : ext === "dxf" ? "dxf" : ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif", "bmp"].includes(ext) ? "image" : void 0;
51
+ const kind = ext === "pdf" ? "pdf" : ext === "dxf" ? "dxf" : ["jpg", "jpeg", "png", "gif", "webp", "svg", "avif", "bmp"].includes(ext) ? "image" : ["stp", "step", "stl", "obj", "gltf", "glb", "3mf", "iges", "igs", "ply", "fbx"].includes(ext) ? "3d" : void 0;
52
52
  if (!kind) {
53
53
  URL.revokeObjectURL(url);
54
54
  if (ext === "dwg") toast_default.error("DWG files need server-side conversion. Convert to PDF or DXF first.");
@@ -74,7 +74,7 @@ function Preview() {
74
74
  {
75
75
  ref: fileRef,
76
76
  type: "file",
77
- accept: ".pdf,.dxf,.jpg,.jpeg,.png,.gif,.webp,.svg,.avif,.bmp",
77
+ accept: ".pdf,.dxf,.jpg,.jpeg,.png,.gif,.webp,.svg,.avif,.bmp,.stp,.step,.stl,.obj,.gltf,.glb,.3mf,.iges,.igs,.ply,.fbx",
78
78
  onChange: handleFile,
79
79
  className: "hidden"
80
80
  }
@@ -90,7 +90,7 @@ function Preview() {
90
90
  ]
91
91
  }
92
92
  ),
93
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-400 ml-1", children: "PDF \xB7 DXF \xB7 Images" }),
93
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-400 ml-1", children: "PDF \xB7 DXF \xB7 3D \xB7 Images" }),
94
94
  data?.filename && /* @__PURE__ */ jsxs(Fragment, { children: [
95
95
  /* @__PURE__ */ jsx("div", { className: "h-4 w-px bg-gray-300 mx-1" }),
96
96
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-700 truncate max-w-[200px]", title: data.filename, children: data.filename })
@@ -114,10 +114,16 @@ function Preview() {
114
114
  ] }),
115
115
  /* @__PURE__ */ jsxs("li", { children: [
116
116
  /* @__PURE__ */ jsx("span", { className: "font-mono text-gray-700", children: ".dxf" }),
117
- " \u2014 vector CAD drawings (requires the optional ",
117
+ " \u2014 vector CAD drawings (optional ",
118
118
  /* @__PURE__ */ jsx("span", { className: "font-mono", children: "dxf-viewer" }),
119
119
  " peer dep)"
120
120
  ] }),
121
+ /* @__PURE__ */ jsxs("li", { children: [
122
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-gray-700", children: ".stp .step .stl .obj .gltf .glb .3mf .iges" }),
123
+ " \u2014 3D models (optional ",
124
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: "online-3d-viewer" }),
125
+ " peer dep)"
126
+ ] }),
121
127
  /* @__PURE__ */ jsxs("li", { children: [
122
128
  /* @__PURE__ */ jsx("span", { className: "font-mono text-gray-700", children: ".jpg .jpeg .png .gif .webp .svg .avif .bmp" }),
123
129
  " \u2014 raster images"
@@ -130,6 +136,8 @@ function Preview() {
130
136
  body = /* @__PURE__ */ jsx(ConvertingPanel, { filename: data.filename, message: data.convertingMessage });
131
137
  } else if (data.kind === "dxf") {
132
138
  body = /* @__PURE__ */ jsx(DxfPanel, { url: data.url, filename: data.filename, onDownload: data.onDownload, onEmail: data.onEmail }, data.url);
139
+ } else if (data.kind === "3d") {
140
+ body = /* @__PURE__ */ jsx(StepPanel, { url: data.url, filename: data.filename, onDownload: data.onDownload, onEmail: data.onEmail }, data.url);
133
141
  } else if (data.kind === "image") {
134
142
  body = /* @__PURE__ */ jsx(ImagePanel, { url: data.url, filename: data.filename, onDownload: data.onDownload, onEmail: data.onEmail }, data.url);
135
143
  } else {
@@ -303,16 +311,25 @@ function PdfPanel({ url, filename, onDownload, onEmail }) {
303
311
  /* @__PURE__ */ jsx("div", { ref: containerRef, className: "flex-1 overflow-auto bg-gray-100 flex justify-center p-4", children: loading ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-20 text-gray-400 text-sm", children: "Loading PDF..." }) : /* @__PURE__ */ jsx("canvas", { ref: canvasRef, className: "shadow-lg rounded" }) })
304
312
  ] });
305
313
  }
314
+ var DEFAULT_DXF_FONTS = [
315
+ "https://cdn.jsdelivr.net/gh/vagran/dxf-viewer-example-src@master/src/assets/fonts/Roboto-LightItalic.ttf",
316
+ "https://cdn.jsdelivr.net/gh/vagran/dxf-viewer-example-src@master/src/assets/fonts/NotoSansDisplay-SemiCondensedLightItalic.ttf",
317
+ "https://cdn.jsdelivr.net/gh/vagran/dxf-viewer-example-src@master/src/assets/fonts/NanumGothic-Regular.ttf"
318
+ ];
306
319
  function DxfPanel({ url, filename, onDownload, onEmail }) {
307
320
  const containerRef = useRef(null);
308
321
  const viewerRef = useRef(null);
309
322
  const [loading, setLoading] = useState(true);
310
323
  const [error, setError] = useState(null);
324
+ const [layers, setLayers] = useState([]);
325
+ const [showLayers, setShowLayers] = useState(false);
326
+ const [showHint, setShowHint] = useState(true);
311
327
  useEffect(() => {
312
328
  let cancelled = false;
313
329
  let viewer = null;
314
330
  setLoading(true);
315
331
  setError(null);
332
+ setLayers([]);
316
333
  (async () => {
317
334
  let DxfViewer;
318
335
  try {
@@ -349,8 +366,21 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
349
366
  if (ClearColor) viewerOpts.clearColor = new ClearColor(16777215);
350
367
  viewer = new DxfViewer(containerRef.current, viewerOpts);
351
368
  viewerRef.current = viewer;
352
- await viewer.Load({ url, fonts: null, workerFactory: null });
369
+ const fontUrls = typeof window !== "undefined" && window.__REACT_OS_SHELL_DXF_FONTS__ || DEFAULT_DXF_FONTS;
370
+ await viewer.Load({ url, fonts: fontUrls, workerFactory: null });
353
371
  if (cancelled) return;
372
+ try {
373
+ const list = viewer.GetLayers?.() ?? [];
374
+ if (Array.isArray(list)) {
375
+ setLayers(list.map((l) => ({
376
+ name: l.name,
377
+ displayName: l.displayName ?? l.name,
378
+ color: typeof l.color === "number" ? l.color : void 0,
379
+ visible: true
380
+ })));
381
+ }
382
+ } catch {
383
+ }
354
384
  const refit = () => {
355
385
  try {
356
386
  const rect = containerRef.current?.getBoundingClientRect();
@@ -391,6 +421,32 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
391
421
  viewerRef.current = null;
392
422
  };
393
423
  }, [url]);
424
+ useEffect(() => {
425
+ if (!showHint || loading) return;
426
+ const t = setTimeout(() => setShowHint(false), 5e3);
427
+ return () => clearTimeout(t);
428
+ }, [showHint, loading]);
429
+ const toggleLayer = (name) => {
430
+ setLayers((prev) => prev.map((l) => {
431
+ if (l.name !== name) return l;
432
+ const next = !l.visible;
433
+ try {
434
+ viewerRef.current?.ShowLayer?.(name, next);
435
+ } catch {
436
+ }
437
+ return { ...l, visible: next };
438
+ }));
439
+ };
440
+ const setAllLayers = (visible) => {
441
+ setLayers((prev) => prev.map((l) => {
442
+ if (l.visible === visible) return l;
443
+ try {
444
+ viewerRef.current?.ShowLayer?.(l.name, visible);
445
+ } catch {
446
+ }
447
+ return { ...l, visible };
448
+ }));
449
+ };
394
450
  const handleDefaultDownload = () => {
395
451
  const a = document.createElement("a");
396
452
  a.href = url;
@@ -399,18 +455,56 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
399
455
  };
400
456
  const handleResetView = () => {
401
457
  try {
402
- viewerRef.current?.FitView?.();
458
+ const v = viewerRef.current;
459
+ const bounds = v?.GetBounds?.();
460
+ const origin = v?.GetOrigin?.();
461
+ if (bounds && origin) {
462
+ v.FitView(
463
+ bounds.minX - origin.x,
464
+ bounds.maxX - origin.x,
465
+ bounds.minY - origin.y,
466
+ bounds.maxY - origin.y
467
+ );
468
+ } else {
469
+ v?.FitView?.();
470
+ }
471
+ v?.Render?.();
403
472
  } catch {
404
473
  }
405
474
  };
406
475
  const btn = "px-2 py-1 rounded hover:bg-gray-200 transition-colors text-gray-600 flex items-center gap-1";
476
+ const colorHex = (n) => {
477
+ if (typeof n !== "number") return "#999";
478
+ return "#" + n.toString(16).padStart(6, "0");
479
+ };
407
480
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
408
481
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0 text-xs", children: [
409
482
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
410
483
  /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-600", children: "DXF" }),
411
484
  /* @__PURE__ */ jsx("span", { className: "text-gray-400 truncate max-w-xs", children: filename })
412
485
  ] }),
413
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
486
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 relative", children: [
487
+ /* @__PURE__ */ jsxs(
488
+ "button",
489
+ {
490
+ onClick: () => setShowLayers((s) => !s),
491
+ className: btn + (showLayers ? " bg-gray-200" : ""),
492
+ title: "Toggle layer visibility",
493
+ disabled: layers.length === 0,
494
+ children: [
495
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6.429 9.75L2.25 12l4.179 2.25m0-4.5l5.571 3 5.571-3m-11.142 0L2.25 7.5 12 2.25l9.75 5.25-4.179 2.25m0 0L21.75 12l-4.179 2.25m0 0l4.179 2.25L12 21.75 2.25 16.5l4.179-2.25m11.142 0l-5.571 3-5.571-3" }) }),
496
+ "Layers ",
497
+ layers.length > 0 && /* @__PURE__ */ jsxs("span", { className: "text-gray-400", children: [
498
+ "(",
499
+ layers.filter((l) => l.visible).length,
500
+ "/",
501
+ layers.length,
502
+ ")"
503
+ ] })
504
+ ]
505
+ }
506
+ ),
507
+ /* @__PURE__ */ jsx("button", { onClick: () => setShowHint((s) => !s), className: btn, title: "How to navigate", children: /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" }) }) }),
414
508
  /* @__PURE__ */ jsx("button", { onClick: handleResetView, className: btn, title: "Fit drawing to view", children: "Fit" }),
415
509
  /* @__PURE__ */ jsxs("button", { onClick: onDownload ?? handleDefaultDownload, className: btn, children: [
416
510
  /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" }) }),
@@ -424,11 +518,182 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
424
518
  ] }),
425
519
  /* @__PURE__ */ jsxs("div", { className: "relative flex-1 bg-white min-h-0", children: [
426
520
  /* @__PURE__ */ jsx("div", { ref: containerRef, style: { width: "100%", height: "100%" } }),
521
+ showLayers && layers.length > 0 && /* @__PURE__ */ jsxs("div", { className: "absolute top-2 right-2 w-64 max-h-[70%] flex flex-col bg-white/95 backdrop-blur border border-gray-200 rounded-md shadow-xl z-10 text-xs", children: [
522
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-2 py-1.5 border-b border-gray-200 bg-gray-50", children: [
523
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-700", children: "Layers" }),
524
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
525
+ /* @__PURE__ */ jsx("button", { onClick: () => setAllLayers(true), className: "px-1.5 py-0.5 rounded hover:bg-gray-200 text-gray-600", children: "All" }),
526
+ /* @__PURE__ */ jsx("button", { onClick: () => setAllLayers(false), className: "px-1.5 py-0.5 rounded hover:bg-gray-200 text-gray-600", children: "None" }),
527
+ /* @__PURE__ */ jsx("button", { onClick: () => setShowLayers(false), className: "px-1.5 py-0.5 rounded hover:bg-gray-200 text-gray-600", title: "Close", children: "\xD7" })
528
+ ] })
529
+ ] }),
530
+ /* @__PURE__ */ jsx("div", { className: "overflow-y-auto py-1", children: layers.map((l) => /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 px-2 py-1 hover:bg-gray-100 cursor-pointer", children: [
531
+ /* @__PURE__ */ jsx(
532
+ "input",
533
+ {
534
+ type: "checkbox",
535
+ checked: l.visible,
536
+ onChange: () => toggleLayer(l.name),
537
+ className: "h-3.5 w-3.5"
538
+ }
539
+ ),
540
+ /* @__PURE__ */ jsx(
541
+ "span",
542
+ {
543
+ className: "inline-block h-3 w-3 rounded-sm border border-gray-300 shrink-0",
544
+ style: { background: colorHex(l.color) }
545
+ }
546
+ ),
547
+ /* @__PURE__ */ jsx("span", { className: "truncate text-gray-700", title: l.displayName, children: l.displayName || l.name })
548
+ ] }, l.name)) })
549
+ ] }),
550
+ showHint && !loading && !error && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-3 left-1/2 -translate-x-1/2 bg-gray-900/85 text-white text-[11px] px-3 py-1.5 rounded-full shadow-lg flex items-center gap-3 z-10 pointer-events-none", children: [
551
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
552
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122" }) }),
553
+ "Drag to pan"
554
+ ] }),
555
+ /* @__PURE__ */ jsx("span", { className: "text-white/40", children: "\u2022" }),
556
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
557
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.8, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607zM10.5 7.5v6m3-3h-6" }) }),
558
+ "Scroll to zoom"
559
+ ] }),
560
+ /* @__PURE__ */ jsx("span", { className: "text-white/40", children: "\u2022" }),
561
+ /* @__PURE__ */ jsx("span", { children: "Fit to reset" })
562
+ ] }),
427
563
  loading && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center bg-white/80 text-sm text-gray-500", children: "Loading drawing\u2026" }),
428
564
  error && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center text-sm text-red-600 px-6 text-center", children: error })
429
565
  ] })
430
566
  ] });
431
567
  }
568
+ var DEFAULT_O3DV_LIBS = "https://cdn.jsdelivr.net/npm/online-3d-viewer@0.18.0/libs/";
569
+ function StepPanel({ url, filename, onDownload, onEmail }) {
570
+ const containerRef = useRef(null);
571
+ const viewerRef = useRef(null);
572
+ const ovRef = useRef(null);
573
+ const [loading, setLoading] = useState(true);
574
+ const [error, setError] = useState(null);
575
+ const [showHint, setShowHint] = useState(true);
576
+ useEffect(() => {
577
+ let cancelled = false;
578
+ let viewer = null;
579
+ setLoading(true);
580
+ setError(null);
581
+ (async () => {
582
+ let OV;
583
+ try {
584
+ OV = await import('online-3d-viewer');
585
+ } catch {
586
+ if (!cancelled) {
587
+ setError("online-3d-viewer is not installed in this app. Add it to enable 3D file viewing.");
588
+ setLoading(false);
589
+ }
590
+ return;
591
+ }
592
+ ovRef.current = OV;
593
+ if (cancelled || !containerRef.current) return;
594
+ await new Promise((resolve) => {
595
+ const tryStart = () => {
596
+ const r = containerRef.current?.getBoundingClientRect();
597
+ if (r && r.width > 4 && r.height > 4) resolve();
598
+ else requestAnimationFrame(tryStart);
599
+ };
600
+ tryStart();
601
+ });
602
+ if (cancelled || !containerRef.current) return;
603
+ try {
604
+ const libsBase = typeof window !== "undefined" && window.__REACT_OS_SHELL_O3DV_LIBS__ || DEFAULT_O3DV_LIBS;
605
+ OV.SetExternalLibLocation?.(libsBase);
606
+ viewer = new OV.EmbeddedViewer(containerRef.current, {
607
+ backgroundColor: new OV.RGBAColor(245, 246, 248, 255),
608
+ defaultColor: new OV.RGBColor(180, 188, 200),
609
+ edgeSettings: new OV.EdgeSettings(false, new OV.RGBColor(0, 0, 0), 1),
610
+ onModelLoaded: () => {
611
+ if (!cancelled) setLoading(false);
612
+ }
613
+ });
614
+ viewerRef.current = viewer;
615
+ const inputFile = new OV.InputFile(filename, OV.FileSource.Url, url);
616
+ viewer.LoadModelFromInputFiles([inputFile]);
617
+ setTimeout(() => {
618
+ if (!cancelled) setLoading(false);
619
+ }, 4e3);
620
+ } catch (e) {
621
+ if (!cancelled) {
622
+ setError(e?.message || "Failed to load 3D model.");
623
+ setLoading(false);
624
+ }
625
+ }
626
+ })();
627
+ return () => {
628
+ cancelled = true;
629
+ try {
630
+ viewer?.Destroy?.();
631
+ } catch {
632
+ }
633
+ viewerRef.current = null;
634
+ };
635
+ }, [url, filename]);
636
+ useEffect(() => {
637
+ if (!showHint || loading) return;
638
+ const t = setTimeout(() => setShowHint(false), 5e3);
639
+ return () => clearTimeout(t);
640
+ }, [showHint, loading]);
641
+ const handleDefaultDownload = () => {
642
+ const a = document.createElement("a");
643
+ a.href = url;
644
+ a.download = filename;
645
+ a.click();
646
+ };
647
+ const handleResetView = () => {
648
+ try {
649
+ const v = viewerRef.current;
650
+ v?.viewer?.FitSphereToWindow?.(v?.GetBoundingSphere?.((m) => true), false);
651
+ v?.Render?.();
652
+ } catch {
653
+ }
654
+ };
655
+ const ext = (filename.split(".").pop() || "").toUpperCase();
656
+ const btn = "px-2 py-1 rounded hover:bg-gray-200 transition-colors text-gray-600 flex items-center gap-1";
657
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
658
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0 text-xs", children: [
659
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
660
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-gray-600", children: ext || "3D" }),
661
+ /* @__PURE__ */ jsx("span", { className: "text-gray-400 truncate max-w-xs", children: filename })
662
+ ] }),
663
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
664
+ /* @__PURE__ */ jsx("button", { onClick: () => setShowHint((s) => !s), className: btn, title: "How to navigate", children: /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z" }) }) }),
665
+ /* @__PURE__ */ jsx("button", { onClick: handleResetView, className: btn, title: "Fit model to view", children: "Fit" }),
666
+ /* @__PURE__ */ jsxs("button", { onClick: onDownload ?? handleDefaultDownload, className: btn, children: [
667
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3" }) }),
668
+ "Download"
669
+ ] }),
670
+ onEmail && /* @__PURE__ */ jsxs("button", { onClick: onEmail, className: btn, children: [
671
+ /* @__PURE__ */ jsx("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" }) }),
672
+ "Email"
673
+ ] })
674
+ ] })
675
+ ] }),
676
+ /* @__PURE__ */ jsxs("div", { className: "relative flex-1 bg-[#f5f6f8] min-h-0", children: [
677
+ /* @__PURE__ */ jsx("div", { ref: containerRef, style: { width: "100%", height: "100%" } }),
678
+ showHint && !loading && !error && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-3 left-1/2 -translate-x-1/2 bg-gray-900/85 text-white text-[11px] px-3 py-1.5 rounded-full shadow-lg flex items-center gap-3 z-10 pointer-events-none", children: [
679
+ /* @__PURE__ */ jsx("span", { children: "Drag to rotate" }),
680
+ /* @__PURE__ */ jsx("span", { className: "text-white/40", children: "\u2022" }),
681
+ /* @__PURE__ */ jsx("span", { children: "Right-click drag to pan" }),
682
+ /* @__PURE__ */ jsx("span", { className: "text-white/40", children: "\u2022" }),
683
+ /* @__PURE__ */ jsx("span", { children: "Scroll to zoom" })
684
+ ] }),
685
+ loading && /* @__PURE__ */ jsxs("div", { className: "absolute inset-0 flex flex-col items-center justify-center bg-white/80 text-sm text-gray-500 gap-2", children: [
686
+ /* @__PURE__ */ jsxs("svg", { className: "h-6 w-6 text-blue-500 animate-spin", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, children: [
687
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "10", strokeOpacity: "0.2" }),
688
+ /* @__PURE__ */ jsx("path", { d: "M22 12a10 10 0 0 1-10 10", strokeLinecap: "round" })
689
+ ] }),
690
+ /* @__PURE__ */ jsx("span", { children: "Loading 3D model\u2026" }),
691
+ ext === "STP" || ext === "STEP" ? /* @__PURE__ */ jsx("span", { className: "text-[10px] text-gray-400", children: "STEP files load OpenCascade WASM (~5 MB) on first use." }) : null
692
+ ] }),
693
+ error && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center text-sm text-red-600 px-6 text-center", children: error })
694
+ ] })
695
+ ] });
696
+ }
432
697
  function ImagePanel({ url, filename, onDownload, onEmail }) {
433
698
  const [zoom, setZoom] = useState(1);
434
699
  const [error, setError] = useState(false);
@@ -479,5 +744,5 @@ function ImagePanel({ url, filename, onDownload, onEmail }) {
479
744
  }
480
745
 
481
746
  export { Preview, setPdfPreview };
482
- //# sourceMappingURL=chunk-4XBIXMZC.js.map
483
- //# sourceMappingURL=chunk-4XBIXMZC.js.map
747
+ //# sourceMappingURL=chunk-CP5X55CA.js.map
748
+ //# sourceMappingURL=chunk-CP5X55CA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/apps/Preview.tsx"],"names":[],"mappings":"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","file":"chunk-CP5X55CA.js","sourcesContent":["/**\n * Preview — windowed PDF viewer app.\n *\n * Consumers stage a PDF via `setPdfPreview({ url, filename, ... })` and then\n * call `openPage('/preview')`. If the window is already open, it swaps to the\n * new PDF in-place via a custom event.\n */\nimport { useState, useEffect, useRef } from 'react';\nimport * as pdfjsLib from 'pdfjs-dist';\nimport toast from '../shell/toast';\nimport { WindowTitle } from '../shell/Modal';\n\nconst TITLE_DISPLAY_MAX = 24;\nfunction truncateForTitle(s: string) {\n return s.length > TITLE_DISPLAY_MAX ? `${s.slice(0, TITLE_DISPLAY_MAX - 1)}…` : s;\n}\n\n// Default the worker to the matching unpkg build (mirrors the consumer's\n// installed npm version exactly). Consumers can override by setting\n// pdfjsLib.GlobalWorkerOptions.workerSrc themselves before opening the\n// Preview window.\nif (typeof window !== 'undefined' && !pdfjsLib.GlobalWorkerOptions.workerSrc) {\n pdfjsLib.GlobalWorkerOptions.workerSrc =\n `https://unpkg.com/pdfjs-dist@${pdfjsLib.version}/build/pdf.worker.min.mjs`;\n}\n\nexport interface PdfPreviewData {\n /** Object URL or remote URL of the PDF. Blob URLs are revoked when the window unmounts.\n * Leave blank when staging a `converting: true` placeholder; call `setPdfPreview` again\n * with the resolved URL once conversion finishes. */\n url?: string;\n /** Display name (and download filename). */\n filename: string;\n /** Renderer to use. Defaults to `'pdf'`. `'dxf'` requires the consumer to\n * have `dxf-viewer` installed (it's an optional peer dep). `'image'`\n * renders an `<img>` for raster screenshots / photos. `'3d'` covers\n * STEP / STL / OBJ / GLTF / 3MF / IGES via the optional\n * `online-3d-viewer` peer dep. */\n kind?: 'pdf' | 'dxf' | 'image' | '3d';\n /** Optional download handler — replaces the built-in \"save URL as filename\" if supplied. */\n onDownload?: () => void;\n /** Optional email handler — only shown when supplied. */\n onEmail?: () => void;\n /** Show a progress placeholder while the consumer fetches/converts the file. */\n converting?: boolean;\n /** Headline shown on the converting placeholder (e.g. \"CONVERTING DWG FILE\"). */\n convertingMessage?: string;\n}\n\nconst EVENT_NAME = 'react-os-shell:pdf-preview';\n\nlet pendingData: PdfPreviewData | null = null;\n\n/** Stage a PDF for the next Preview window mount, or swap into an open one. */\nexport function setPdfPreview(data: PdfPreviewData) {\n pendingData = data;\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(EVENT_NAME, { detail: data }));\n }\n}\n\nexport default function Preview() {\n const [data, setData] = useState<PdfPreviewData | null>(() => {\n const d = pendingData;\n pendingData = null;\n return d;\n });\n\n // Swap to a new PDF if `setPdfPreview` is called while the window is open.\n useEffect(() => {\n const handler = (e: Event) => {\n const next = (e as CustomEvent<PdfPreviewData>).detail;\n setData(prev => {\n if (prev?.url && prev.url !== next.url && prev.url.startsWith('blob:')) {\n URL.revokeObjectURL(prev.url);\n }\n return next;\n });\n };\n window.addEventListener(EVENT_NAME, handler);\n return () => window.removeEventListener(EVENT_NAME, handler);\n }, []);\n\n // Revoke blob URL on unmount.\n useEffect(() => () => {\n if (data?.url?.startsWith('blob:')) URL.revokeObjectURL(data.url);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Window title reflects whatever is loaded — same pattern Spreadsheets uses.\n const titleName = data?.filename ? truncateForTitle(data.filename) : 'Untitled';\n\n const fileRef = useRef<HTMLInputElement>(null);\n const [isDragging, setIsDragging] = useState(false);\n const handlePick = () => fileRef.current?.click();\n const ingestFile = (file: File) => {\n const url = URL.createObjectURL(file);\n const ext = (file.name.split('.').pop() || '').toLowerCase();\n const kind: 'pdf' | 'image' | 'dxf' | '3d' | undefined =\n ext === 'pdf' ? 'pdf'\n : ext === 'dxf' ? 'dxf'\n : ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'avif', 'bmp'].includes(ext) ? 'image'\n : ['stp', 'step', 'stl', 'obj', 'gltf', 'glb', '3mf', 'iges', 'igs', 'ply', 'fbx'].includes(ext) ? '3d'\n : undefined;\n if (!kind) {\n URL.revokeObjectURL(url);\n if (ext === 'dwg') toast.error('DWG files need server-side conversion. Convert to PDF or DXF first.');\n else toast.error(`Unsupported file type: .${ext || 'unknown'}`);\n return;\n }\n setPdfPreview({ url, filename: file.name, kind });\n };\n const handleFile = (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n if (file) ingestFile(file);\n if (fileRef.current) fileRef.current.value = '';\n };\n const handleDrop = (e: React.DragEvent) => {\n e.preventDefault();\n setIsDragging(false);\n const file = e.dataTransfer.files?.[0];\n if (file) ingestFile(file);\n };\n\n const Toolbar = (\n <div className=\"flex items-center gap-2 px-3 py-2 border-b border-gray-200 bg-gray-50 shrink-0\">\n <input\n ref={fileRef}\n type=\"file\"\n accept=\".pdf,.dxf,.jpg,.jpeg,.png,.gif,.webp,.svg,.avif,.bmp,.stp,.step,.stl,.obj,.gltf,.glb,.3mf,.iges,.igs,.ply,.fbx\"\n onChange={handleFile}\n className=\"hidden\"\n />\n <button\n onClick={handlePick}\n className=\"text-xs text-gray-700 hover:text-gray-900 px-2 py-1 rounded hover:bg-gray-200 transition-colors flex items-center gap-1\"\n >\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776\" />\n </svg>\n Open\n </button>\n <span className=\"text-[10px] text-gray-400 ml-1\">PDF · DXF · 3D · Images</span>\n {data?.filename && (\n <>\n <div className=\"h-4 w-px bg-gray-300 mx-1\" />\n <span className=\"text-xs font-medium text-gray-700 truncate max-w-[200px]\" title={data.filename}>{data.filename}</span>\n </>\n )}\n </div>\n );\n\n let body: React.ReactNode;\n if (!data) {\n body = (\n <div className=\"flex flex-1 flex-col items-center justify-center text-gray-500 text-sm gap-3 p-8 text-center\">\n <svg className=\"h-12 w-12 text-gray-300\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z\" />\n </svg>\n <p className=\"font-medium text-gray-700\">Drop a file here, or click <button onClick={handlePick} className=\"text-blue-600 hover:underline\">Open</button>.</p>\n <div className=\"text-xs text-gray-500 max-w-sm\">\n <p className=\"font-semibold uppercase tracking-wide text-[10px] text-gray-400 mb-1\">Supported formats</p>\n <ul className=\"space-y-0.5\">\n <li><span className=\"font-mono text-gray-700\">.pdf</span> — multi-page document viewer</li>\n <li><span className=\"font-mono text-gray-700\">.dxf</span> — vector CAD drawings (optional <span className=\"font-mono\">dxf-viewer</span> peer dep)</li>\n <li><span className=\"font-mono text-gray-700\">.stp .step .stl .obj .gltf .glb .3mf .iges</span> — 3D models (optional <span className=\"font-mono\">online-3d-viewer</span> peer dep)</li>\n <li><span className=\"font-mono text-gray-700\">.jpg .jpeg .png .gif .webp .svg .avif .bmp</span> — raster images</li>\n </ul>\n <p className=\"mt-2 text-[11px] text-gray-400 italic\">DWG files need to be converted to PDF or DXF first (server-side).</p>\n </div>\n </div>\n );\n } else if (data.converting || !data.url) {\n body = <ConvertingPanel filename={data.filename} message={data.convertingMessage} />;\n } else if (data.kind === 'dxf') {\n body = <DxfPanel key={data.url} url={data.url} filename={data.filename} onDownload={data.onDownload} onEmail={data.onEmail} />;\n } else if (data.kind === '3d') {\n body = <StepPanel key={data.url} url={data.url} filename={data.filename} onDownload={data.onDownload} onEmail={data.onEmail} />;\n } else if (data.kind === 'image') {\n body = <ImagePanel key={data.url} url={data.url} filename={data.filename} onDownload={data.onDownload} onEmail={data.onEmail} />;\n } else {\n body = <PdfPanel key={data.url} url={data.url} filename={data.filename} onDownload={data.onDownload} onEmail={data.onEmail} />;\n }\n\n return (\n <div\n className=\"relative flex flex-col h-full\"\n onDragOver={(e) => { e.preventDefault(); if (!isDragging) setIsDragging(true); }}\n onDragLeave={(e) => {\n // Only clear when leaving the outer container, not transitioning between children.\n if (e.currentTarget === e.target) setIsDragging(false);\n }}\n onDrop={handleDrop}\n >\n <WindowTitle title={`${titleName} - Preview`} />\n {Toolbar}\n <div className=\"flex-1 min-h-0\">{body}</div>\n {isDragging && (\n <div className=\"absolute inset-0 bg-blue-500/15 border-4 border-dashed border-blue-500 pointer-events-none flex items-center justify-center z-20\">\n <div className=\"px-4 py-2 rounded-md bg-blue-600 text-white text-sm font-medium shadow-lg\">\n Drop to open\n </div>\n </div>\n )}\n </div>\n );\n}\n\nfunction ConvertingPanel({ filename, message }: { filename: string; message?: string }) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full bg-gray-100 gap-4 px-8\">\n <div className=\"flex flex-col items-center gap-3\">\n <svg className=\"h-12 w-12 text-blue-500 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2}>\n <circle cx=\"12\" cy=\"12\" r=\"10\" strokeOpacity=\"0.2\" />\n <path d=\"M22 12a10 10 0 0 1-10 10\" strokeLinecap=\"round\" />\n </svg>\n <div className=\"text-base font-semibold tracking-wide text-gray-700 uppercase\">{message || 'Converting file'}</div>\n <div className=\"text-xs text-gray-400 truncate max-w-md\">{filename}</div>\n </div>\n <div className=\"w-72 h-1.5 rounded-full bg-gray-200 overflow-hidden\">\n <div className=\"h-full w-1/3 bg-blue-500 rounded-full animate-pulse\" style={{ animation: 'preview-bar 1.4s ease-in-out infinite' }} />\n </div>\n <style>{`@keyframes preview-bar { 0% { transform: translateX(-110%); } 100% { transform: translateX(310%); } }`}</style>\n </div>\n );\n}\n\ninterface PdfPanelProps {\n url: string;\n filename: string;\n onDownload?: () => void;\n onEmail?: () => void;\n}\n\nfunction PdfPanel({ url, filename, onDownload, onEmail }: PdfPanelProps) {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const [pdf, setPdf] = useState<pdfjsLib.PDFDocumentProxy | null>(null);\n const [page, setPage] = useState(1);\n const [totalPages, setTotalPages] = useState(0);\n const [scale, setScale] = useState(1.5);\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n let cancelled = false;\n setLoading(true);\n pdfjsLib.getDocument(url).promise.then(doc => {\n if (cancelled) return;\n setPdf(doc);\n setTotalPages(doc.numPages);\n setLoading(false);\n }).catch(() => {\n if (!cancelled) { toast.error('Failed to load PDF'); setLoading(false); }\n });\n return () => { cancelled = true; };\n }, [url]);\n\n useEffect(() => {\n if (!pdf || !containerRef.current) return;\n pdf.getPage(1).then(p => {\n const containerW = containerRef.current?.clientWidth || 800;\n const viewport = p.getViewport({ scale: 1 });\n const fitScale = (containerW - 40) / viewport.width;\n setScale(Math.min(Math.max(fitScale, 0.5), 3));\n });\n }, [pdf]);\n\n useEffect(() => {\n if (!pdf || !canvasRef.current) return;\n let cancelled = false;\n pdf.getPage(page).then(p => {\n if (cancelled || !canvasRef.current) return;\n const viewport = p.getViewport({ scale });\n const canvas = canvasRef.current;\n canvas.width = viewport.width;\n canvas.height = viewport.height;\n const ctx = canvas.getContext('2d')!;\n p.render({ canvas, canvasContext: ctx, viewport }).promise.catch(() => {});\n });\n return () => { cancelled = true; };\n }, [pdf, page, scale]);\n\n const handlePrint = () => {\n if (!pdf) return;\n const win = window.open('', '_blank');\n if (!win) { toast.error('Allow popups to print'); return; }\n const promises: Promise<string>[] = [];\n for (let i = 1; i <= totalPages; i++) {\n promises.push(pdf.getPage(i).then(p => {\n const vp = p.getViewport({ scale: 2 });\n const c = document.createElement('canvas');\n c.width = vp.width; c.height = vp.height;\n return p.render({ canvas: c, canvasContext: c.getContext('2d')!, viewport: vp }).promise.then(() => c.toDataURL());\n }));\n }\n Promise.all(promises).then(images => {\n win.document.write(`<html><head><title>${filename}</title><style>@media print{body{margin:0}img{width:100%;page-break-after:always}}</style></head><body>`);\n win.document.write(images.map(src => `<img src=\"${src}\"/>`).join(''));\n win.document.write('</body></html>');\n win.document.close();\n setTimeout(() => { win.print(); win.close(); }, 300);\n });\n };\n\n const handleDefaultDownload = () => {\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n a.click();\n };\n\n const fitWidth = () => {\n if (!pdf || !containerRef.current) return;\n pdf.getPage(page).then(p => {\n const containerW = containerRef.current?.clientWidth || 800;\n const viewport = p.getViewport({ scale: 1 });\n setScale(Math.min(Math.max((containerW - 40) / viewport.width, 0.5), 3));\n });\n };\n\n const btn = 'px-2 py-1 rounded hover:bg-gray-200 transition-colors text-gray-600 flex items-center gap-1';\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex items-center justify-between px-3 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0 text-xs\">\n <div className=\"flex items-center gap-1\">\n <button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page <= 1} className=\"px-2 py-1 rounded hover:bg-gray-200 disabled:opacity-30\">\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M15.75 19.5L8.25 12l7.5-7.5\" /></svg>\n </button>\n <span className=\"text-gray-600 font-medium tabular-nums\">{page} / {totalPages}</span>\n <button onClick={() => setPage(p => Math.min(totalPages, p + 1))} disabled={page >= totalPages} className=\"px-2 py-1 rounded hover:bg-gray-200 disabled:opacity-30\">\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" /></svg>\n </button>\n </div>\n\n <div className=\"flex items-center gap-1\">\n <button onClick={() => setScale(s => Math.max(0.3, Math.round((s - 0.25) * 100) / 100))} className={btn}>−</button>\n <span className=\"text-gray-500 w-12 text-center tabular-nums\">{Math.round(scale * 100)}%</span>\n <button onClick={() => setScale(s => Math.min(4, Math.round((s + 0.25) * 100) / 100))} className={btn}>+</button>\n <button onClick={fitWidth} className={btn}>Fit</button>\n </div>\n\n <div className=\"flex items-center gap-1\">\n <button onClick={handlePrint} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6.72 13.829c-.24.03-.48.062-.72.096m.72-.096a42.415 42.415 0 0110.56 0m-10.56 0L6.34 18m10.94-4.171c.24.03.48.062.72.096m-.72-.096L17.66 18m0 0l.229 2.523a1.125 1.125 0 01-1.12 1.227H7.231c-.662 0-1.18-.568-1.12-1.227L6.34 18m11.318 0h1.091A2.25 2.25 0 0021 15.75V9.456c0-1.081-.768-2.015-1.837-2.175a48.055 48.055 0 00-1.913-.247M6.34 18H5.25A2.25 2.25 0 013 15.75V9.456c0-1.081.768-2.015 1.837-2.175a48.041 48.041 0 011.913-.247m10.5 0a48.536 48.536 0 00-10.5 0m10.5 0V3.375c0-.621-.504-1.125-1.125-1.125h-8.25c-.621 0-1.125.504-1.125 1.125v3.659M18 10.5h.008v.008H18V10.5zm-3 0h.008v.008H15V10.5z\" /></svg>\n Print\n </button>\n <button onClick={onDownload ?? handleDefaultDownload} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3\" /></svg>\n Download\n </button>\n {onEmail && (\n <button onClick={onEmail} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75\" /></svg>\n Email\n </button>\n )}\n </div>\n </div>\n\n <div ref={containerRef} className=\"flex-1 overflow-auto bg-gray-100 flex justify-center p-4\">\n {loading ? (\n <div className=\"flex items-center justify-center py-20 text-gray-400 text-sm\">Loading PDF...</div>\n ) : (\n <canvas ref={canvasRef} className=\"shadow-lg rounded\" />\n )}\n </div>\n </div>\n );\n}\n\ninterface DxfPanelProps {\n url: string;\n filename: string;\n onDownload?: () => void;\n onEmail?: () => void;\n}\n\n// Default font set covering Latin + CJK glyphs, served from jsdelivr's mirror\n// of the dxf-viewer example assets. Without these, any TEXT/MTEXT entities in\n// the drawing render as empty boxes. Consumers can override by setting\n// `window.__REACT_OS_SHELL_DXF_FONTS__` to a different array of TTF/OTF URLs.\nconst DEFAULT_DXF_FONTS: string[] = [\n 'https://cdn.jsdelivr.net/gh/vagran/dxf-viewer-example-src@master/src/assets/fonts/Roboto-LightItalic.ttf',\n 'https://cdn.jsdelivr.net/gh/vagran/dxf-viewer-example-src@master/src/assets/fonts/NotoSansDisplay-SemiCondensedLightItalic.ttf',\n 'https://cdn.jsdelivr.net/gh/vagran/dxf-viewer-example-src@master/src/assets/fonts/NanumGothic-Regular.ttf',\n];\n\ninterface DxfLayer {\n name: string;\n displayName?: string;\n color?: number;\n visible: boolean;\n}\n\nfunction DxfPanel({ url, filename, onDownload, onEmail }: DxfPanelProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const viewerRef = useRef<any>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [layers, setLayers] = useState<DxfLayer[]>([]);\n const [showLayers, setShowLayers] = useState(false);\n const [showHint, setShowHint] = useState(true);\n\n useEffect(() => {\n let cancelled = false;\n let viewer: any = null;\n setLoading(true);\n setError(null);\n setLayers([]);\n\n (async () => {\n let DxfViewer: any;\n try {\n const mod = await import('dxf-viewer');\n DxfViewer = (mod as any).DxfViewer;\n } catch (e) {\n if (!cancelled) {\n setError('dxf-viewer is not installed in this app.');\n setLoading(false);\n }\n return;\n }\n if (cancelled || !containerRef.current) return;\n try {\n // dxf-viewer reads container dimensions in its constructor (with\n // autoResize:true, those become the canvas size). If the Modal is\n // still settling we may be measured as 0×0, breaking aspect math\n // and the WebGL viewport. Wait until the container has a real\n // bounding box before constructing the viewer.\n await new Promise<void>(resolve => {\n const tryStart = () => {\n const r = containerRef.current?.getBoundingClientRect();\n if (r && r.width > 4 && r.height > 4) resolve();\n else requestAnimationFrame(tryStart);\n };\n tryStart();\n });\n if (cancelled || !containerRef.current) return;\n\n // clearColor must be a THREE.Color (Library calls .getHex()).\n let three: any = null;\n try { three = await import(/* @vite-ignore */ 'three' as any); } catch {}\n const ClearColor = three?.Color ?? null;\n const viewerOpts: any = { autoResize: true };\n if (ClearColor) viewerOpts.clearColor = new ClearColor(0xffffff);\n viewer = new DxfViewer(containerRef.current, viewerOpts);\n viewerRef.current = viewer;\n\n const fontUrls = (typeof window !== 'undefined' && (window as any).__REACT_OS_SHELL_DXF_FONTS__)\n || DEFAULT_DXF_FONTS;\n await viewer.Load({ url, fonts: fontUrls, workerFactory: null });\n if (cancelled) return;\n\n // Snapshot layer list — used to render the toggle panel.\n try {\n const list: any[] = viewer.GetLayers?.() ?? [];\n if (Array.isArray(list)) {\n setLayers(list.map(l => ({\n name: l.name,\n displayName: l.displayName ?? l.name,\n color: typeof l.color === 'number' ? l.color : undefined,\n visible: true,\n })));\n }\n } catch {}\n\n // Force-fit using the actual loaded bounds and refresh canvas size.\n const refit = () => {\n try {\n const rect = containerRef.current?.getBoundingClientRect();\n if (rect && rect.width > 0 && rect.height > 0) {\n viewer.SetSize?.(Math.round(rect.width), Math.round(rect.height));\n }\n const bounds = viewer.GetBounds?.();\n const origin = viewer.GetOrigin?.();\n if (bounds && origin) {\n viewer.FitView(\n bounds.minX - origin.x, bounds.maxX - origin.x,\n bounds.minY - origin.y, bounds.maxY - origin.y,\n );\n }\n viewer.Render?.();\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn('[Preview] DXF refit failed', err);\n }\n };\n refit();\n requestAnimationFrame(refit);\n setLoading(false);\n } catch (e: any) {\n if (!cancelled) {\n setError(e?.message || 'Failed to render DXF.');\n setLoading(false);\n }\n }\n })();\n\n return () => {\n cancelled = true;\n try { viewer?.Destroy?.(); } catch {}\n viewerRef.current = null;\n };\n }, [url]);\n\n // Auto-hide the usage hint after a few seconds.\n useEffect(() => {\n if (!showHint || loading) return;\n const t = setTimeout(() => setShowHint(false), 5000);\n return () => clearTimeout(t);\n }, [showHint, loading]);\n\n const toggleLayer = (name: string) => {\n setLayers(prev => prev.map(l => {\n if (l.name !== name) return l;\n const next = !l.visible;\n try { viewerRef.current?.ShowLayer?.(name, next); } catch {}\n return { ...l, visible: next };\n }));\n };\n\n const setAllLayers = (visible: boolean) => {\n setLayers(prev => prev.map(l => {\n if (l.visible === visible) return l;\n try { viewerRef.current?.ShowLayer?.(l.name, visible); } catch {}\n return { ...l, visible };\n }));\n };\n\n const handleDefaultDownload = () => {\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n a.click();\n };\n\n const handleResetView = () => {\n try {\n const v = viewerRef.current;\n const bounds = v?.GetBounds?.();\n const origin = v?.GetOrigin?.();\n if (bounds && origin) {\n v.FitView(\n bounds.minX - origin.x, bounds.maxX - origin.x,\n bounds.minY - origin.y, bounds.maxY - origin.y,\n );\n } else {\n v?.FitView?.();\n }\n v?.Render?.();\n } catch {}\n };\n\n const btn = 'px-2 py-1 rounded hover:bg-gray-200 transition-colors text-gray-600 flex items-center gap-1';\n const colorHex = (n?: number) => {\n if (typeof n !== 'number') return '#999';\n return '#' + n.toString(16).padStart(6, '0');\n };\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex items-center justify-between px-3 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0 text-xs\">\n <div className=\"flex items-center gap-1\">\n <span className=\"font-medium text-gray-600\">DXF</span>\n <span className=\"text-gray-400 truncate max-w-xs\">{filename}</span>\n </div>\n <div className=\"flex items-center gap-1 relative\">\n <button\n onClick={() => setShowLayers(s => !s)}\n className={btn + (showLayers ? ' bg-gray-200' : '')}\n title=\"Toggle layer visibility\"\n disabled={layers.length === 0}\n >\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6.429 9.75L2.25 12l4.179 2.25m0-4.5l5.571 3 5.571-3m-11.142 0L2.25 7.5 12 2.25l9.75 5.25-4.179 2.25m0 0L21.75 12l-4.179 2.25m0 0l4.179 2.25L12 21.75 2.25 16.5l4.179-2.25m11.142 0l-5.571 3-5.571-3\" /></svg>\n Layers {layers.length > 0 && <span className=\"text-gray-400\">({layers.filter(l => l.visible).length}/{layers.length})</span>}\n </button>\n <button onClick={() => setShowHint(s => !s)} className={btn} title=\"How to navigate\">\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z\" /></svg>\n </button>\n <button onClick={handleResetView} className={btn} title=\"Fit drawing to view\">Fit</button>\n <button onClick={onDownload ?? handleDefaultDownload} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3\" /></svg>\n Download\n </button>\n {onEmail && (\n <button onClick={onEmail} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75\" /></svg>\n Email\n </button>\n )}\n </div>\n </div>\n <div className=\"relative flex-1 bg-white min-h-0\">\n {/* dxf-viewer overrides container's `position` to `relative` in its\n * constructor, which kills any `inset: 0` sizing. Use explicit\n * width/height: 100% so the container always fills its flex parent. */}\n <div ref={containerRef} style={{ width: '100%', height: '100%' }} />\n\n {showLayers && layers.length > 0 && (\n <div className=\"absolute top-2 right-2 w-64 max-h-[70%] flex flex-col bg-white/95 backdrop-blur border border-gray-200 rounded-md shadow-xl z-10 text-xs\">\n <div className=\"flex items-center justify-between px-2 py-1.5 border-b border-gray-200 bg-gray-50\">\n <span className=\"font-medium text-gray-700\">Layers</span>\n <div className=\"flex items-center gap-1\">\n <button onClick={() => setAllLayers(true)} className=\"px-1.5 py-0.5 rounded hover:bg-gray-200 text-gray-600\">All</button>\n <button onClick={() => setAllLayers(false)} className=\"px-1.5 py-0.5 rounded hover:bg-gray-200 text-gray-600\">None</button>\n <button onClick={() => setShowLayers(false)} className=\"px-1.5 py-0.5 rounded hover:bg-gray-200 text-gray-600\" title=\"Close\">×</button>\n </div>\n </div>\n <div className=\"overflow-y-auto py-1\">\n {layers.map(l => (\n <label key={l.name} className=\"flex items-center gap-2 px-2 py-1 hover:bg-gray-100 cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={l.visible}\n onChange={() => toggleLayer(l.name)}\n className=\"h-3.5 w-3.5\"\n />\n <span\n className=\"inline-block h-3 w-3 rounded-sm border border-gray-300 shrink-0\"\n style={{ background: colorHex(l.color) }}\n />\n <span className=\"truncate text-gray-700\" title={l.displayName}>{l.displayName || l.name}</span>\n </label>\n ))}\n </div>\n </div>\n )}\n\n {showHint && !loading && !error && (\n <div className=\"absolute bottom-3 left-1/2 -translate-x-1/2 bg-gray-900/85 text-white text-[11px] px-3 py-1.5 rounded-full shadow-lg flex items-center gap-3 z-10 pointer-events-none\">\n <span className=\"flex items-center gap-1\">\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.8}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M15 15l-2 5L9 9l11 4-5 2zm0 0l5 5M7.188 2.239l.777 2.897M5.136 7.965l-2.898-.777M13.95 4.05l-2.122 2.122m-5.657 5.656l-2.12 2.122\" /></svg>\n Drag to pan\n </span>\n <span className=\"text-white/40\">•</span>\n <span className=\"flex items-center gap-1\">\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.8}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607zM10.5 7.5v6m3-3h-6\" /></svg>\n Scroll to zoom\n </span>\n <span className=\"text-white/40\">•</span>\n <span>Fit to reset</span>\n </div>\n )}\n\n {loading && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-white/80 text-sm text-gray-500\">Loading drawing…</div>\n )}\n {error && (\n <div className=\"absolute inset-0 flex items-center justify-center text-sm text-red-600 px-6 text-center\">{error}</div>\n )}\n </div>\n </div>\n );\n}\n\ninterface StepPanelProps {\n url: string;\n filename: string;\n onDownload?: () => void;\n onEmail?: () => void;\n}\n\n// online-3d-viewer expects an \"external libs\" base URL where it can find the\n// occt-import-js WASM (for STEP/IGES) plus draco/rhino3dm/web-ifc decoders.\n// jsdelivr mirrors the npm package at this path; consumers can override via\n// `window.__REACT_OS_SHELL_O3DV_LIBS__` to self-host (e.g. air-gapped).\nconst DEFAULT_O3DV_LIBS = 'https://cdn.jsdelivr.net/npm/online-3d-viewer@0.18.0/libs/';\n\nfunction StepPanel({ url, filename, onDownload, onEmail }: StepPanelProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const viewerRef = useRef<any>(null);\n const ovRef = useRef<any>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [showHint, setShowHint] = useState(true);\n\n useEffect(() => {\n let cancelled = false;\n let viewer: any = null;\n setLoading(true);\n setError(null);\n\n (async () => {\n let OV: any;\n try {\n OV = await import('online-3d-viewer');\n } catch {\n if (!cancelled) {\n setError('online-3d-viewer is not installed in this app. Add it to enable 3D file viewing.');\n setLoading(false);\n }\n return;\n }\n ovRef.current = OV;\n if (cancelled || !containerRef.current) return;\n\n // Wait for container to have a real layout before constructing the\n // viewer — same dance as the DXF panel.\n await new Promise<void>(resolve => {\n const tryStart = () => {\n const r = containerRef.current?.getBoundingClientRect();\n if (r && r.width > 4 && r.height > 4) resolve();\n else requestAnimationFrame(tryStart);\n };\n tryStart();\n });\n if (cancelled || !containerRef.current) return;\n\n try {\n const libsBase = (typeof window !== 'undefined' && (window as any).__REACT_OS_SHELL_O3DV_LIBS__)\n || DEFAULT_O3DV_LIBS;\n OV.SetExternalLibLocation?.(libsBase);\n\n viewer = new OV.EmbeddedViewer(containerRef.current, {\n backgroundColor: new OV.RGBAColor(245, 246, 248, 255),\n defaultColor: new OV.RGBColor(180, 188, 200),\n edgeSettings: new OV.EdgeSettings(false, new OV.RGBColor(0, 0, 0), 1),\n onModelLoaded: () => {\n if (!cancelled) setLoading(false);\n },\n });\n viewerRef.current = viewer;\n\n // Use InputFile so the blob: URL is paired with a real filename — the\n // loader picks the right importer (.stp/.step/.stl/etc.) from that\n // name, not the URL pathname.\n const inputFile = new OV.InputFile(filename, OV.FileSource.Url, url);\n viewer.LoadModelFromInputFiles([inputFile]);\n\n // Defensive fallback: if onModelLoaded never fires (e.g. version\n // mismatch), drop the spinner after a short delay so the canvas is\n // visible. Errors still flow through `setError`.\n setTimeout(() => { if (!cancelled) setLoading(false); }, 4000);\n } catch (e: any) {\n if (!cancelled) {\n setError(e?.message || 'Failed to load 3D model.');\n setLoading(false);\n }\n }\n })();\n\n return () => {\n cancelled = true;\n try { viewer?.Destroy?.(); } catch {}\n viewerRef.current = null;\n };\n }, [url, filename]);\n\n useEffect(() => {\n if (!showHint || loading) return;\n const t = setTimeout(() => setShowHint(false), 5000);\n return () => clearTimeout(t);\n }, [showHint, loading]);\n\n const handleDefaultDownload = () => {\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n a.click();\n };\n\n const handleResetView = () => {\n try {\n const v = viewerRef.current;\n // EmbeddedViewer's underlying viewer exposes FitSphereToWindow / FitToWindow.\n v?.viewer?.FitSphereToWindow?.(v?.GetBoundingSphere?.((m: any) => true), false);\n v?.Render?.();\n } catch {}\n };\n\n const ext = (filename.split('.').pop() || '').toUpperCase();\n const btn = 'px-2 py-1 rounded hover:bg-gray-200 transition-colors text-gray-600 flex items-center gap-1';\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex items-center justify-between px-3 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0 text-xs\">\n <div className=\"flex items-center gap-1\">\n <span className=\"font-medium text-gray-600\">{ext || '3D'}</span>\n <span className=\"text-gray-400 truncate max-w-xs\">{filename}</span>\n </div>\n <div className=\"flex items-center gap-1\">\n <button onClick={() => setShowHint(s => !s)} className={btn} title=\"How to navigate\">\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9 5.25h.008v.008H12v-.008z\" /></svg>\n </button>\n <button onClick={handleResetView} className={btn} title=\"Fit model to view\">Fit</button>\n <button onClick={onDownload ?? handleDefaultDownload} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3\" /></svg>\n Download\n </button>\n {onEmail && (\n <button onClick={onEmail} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75\" /></svg>\n Email\n </button>\n )}\n </div>\n </div>\n <div className=\"relative flex-1 bg-[#f5f6f8] min-h-0\">\n <div ref={containerRef} style={{ width: '100%', height: '100%' }} />\n\n {showHint && !loading && !error && (\n <div className=\"absolute bottom-3 left-1/2 -translate-x-1/2 bg-gray-900/85 text-white text-[11px] px-3 py-1.5 rounded-full shadow-lg flex items-center gap-3 z-10 pointer-events-none\">\n <span>Drag to rotate</span>\n <span className=\"text-white/40\">•</span>\n <span>Right-click drag to pan</span>\n <span className=\"text-white/40\">•</span>\n <span>Scroll to zoom</span>\n </div>\n )}\n\n {loading && (\n <div className=\"absolute inset-0 flex flex-col items-center justify-center bg-white/80 text-sm text-gray-500 gap-2\">\n <svg className=\"h-6 w-6 text-blue-500 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2}>\n <circle cx=\"12\" cy=\"12\" r=\"10\" strokeOpacity=\"0.2\" />\n <path d=\"M22 12a10 10 0 0 1-10 10\" strokeLinecap=\"round\" />\n </svg>\n <span>Loading 3D model…</span>\n {ext === 'STP' || ext === 'STEP' ? <span className=\"text-[10px] text-gray-400\">STEP files load OpenCascade WASM (~5 MB) on first use.</span> : null}\n </div>\n )}\n {error && (\n <div className=\"absolute inset-0 flex items-center justify-center text-sm text-red-600 px-6 text-center\">{error}</div>\n )}\n </div>\n </div>\n );\n}\n\ninterface ImagePanelProps {\n url: string;\n filename: string;\n onDownload?: () => void;\n onEmail?: () => void;\n}\n\nfunction ImagePanel({ url, filename, onDownload, onEmail }: ImagePanelProps) {\n const [zoom, setZoom] = useState(1);\n const [error, setError] = useState(false);\n\n const handleDefaultDownload = () => {\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n a.click();\n };\n\n const btn = 'px-2 py-1 rounded hover:bg-gray-200 transition-colors text-gray-600 flex items-center gap-1';\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex items-center justify-between px-3 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0 text-xs\">\n <div className=\"flex items-center gap-1\">\n <span className=\"font-medium text-gray-600\">Image</span>\n <span className=\"text-gray-400 truncate max-w-xs\">{filename}</span>\n </div>\n <div className=\"flex items-center gap-1\">\n <button onClick={() => setZoom(z => Math.max(0.1, Math.round((z - 0.25) * 100) / 100))} className={btn}>−</button>\n <span className=\"text-gray-500 w-12 text-center tabular-nums\">{Math.round(zoom * 100)}%</span>\n <button onClick={() => setZoom(z => Math.min(8, Math.round((z + 0.25) * 100) / 100))} className={btn}>+</button>\n <button onClick={() => setZoom(1)} className={btn}>1:1</button>\n </div>\n <div className=\"flex items-center gap-1\">\n <button onClick={onDownload ?? handleDefaultDownload} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3\" /></svg>\n Download\n </button>\n {onEmail && (\n <button onClick={onEmail} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75\" /></svg>\n Email\n </button>\n )}\n </div>\n </div>\n <div className=\"flex-1 overflow-auto bg-gray-100 flex items-center justify-center p-4\">\n {error ? (\n <div className=\"text-sm text-red-600\">Failed to load image.</div>\n ) : (\n <img\n src={url}\n alt={filename}\n onError={() => setError(true)}\n style={{ transform: `scale(${zoom})`, transformOrigin: 'center center', transition: 'transform 120ms ease' }}\n className=\"max-w-full max-h-full shadow-lg rounded bg-white\"\n />\n )}\n </div>\n </div>\n );\n}\n"]}
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { formatDate } from './chunk-NSU7OHPC.js';
3
3
  export { formatDate } from './chunk-NSU7OHPC.js';
4
4
  import { useGoogleAuth } from './chunk-5O2KEISQ.js';
5
5
  import { playStartup, soundsEnabled, getSoundConfig, SOUND_PACK_KEYS, SOUND_PACKS, SOUND_TYPES, SOUND_TYPE_LABELS, playLogout, setSoundForType, previewSound, setAllSounds } from './chunk-D7PYW2QS.js';
6
- import { setPdfPreview } from './chunk-4XBIXMZC.js';
6
+ import { setPdfPreview } from './chunk-CP5X55CA.js';
7
7
  import { toast_default } from './chunk-WIJ45SYD.js';
8
8
  export { toast_default as toast } from './chunk-WIJ45SYD.js';
9
9
  import { useShellPrefs } from './chunk-36VM54SC.js';
@@ -651,7 +651,7 @@ function StatusBadge({ status }) {
651
651
  }
652
652
 
653
653
  // src/version.ts
654
- var VERSION = "0.1.27" ;
654
+ var VERSION = "0.1.29" ;
655
655
  var APP_VERSION = VERSION;
656
656
 
657
657
  // src/changelog.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-os-shell",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "Desktop-style React UI shell — windows, taskbar, start menu, sticky notes, frosted glass theming, and 17 bundled apps including a PDF Preview viewer.",
5
5
  "license": "MIT",
6
6
  "author": "Victor Y. Mau",
@@ -39,6 +39,7 @@
39
39
  "@tanstack/react-query": ">=5",
40
40
  "dxf-viewer": "*",
41
41
  "mammoth": "*",
42
+ "online-3d-viewer": "*",
42
43
  "pdfjs-dist": "*",
43
44
  "react": ">=18",
44
45
  "react-dom": ">=18",
@@ -55,6 +56,7 @@
55
56
  "@types/react-dom": "^18.2.0",
56
57
  "dxf-viewer": "^1.0.47",
57
58
  "mammoth": "^1.8.0",
59
+ "online-3d-viewer": "^0.18.0",
58
60
  "pdfjs-dist": "^5.6.205",
59
61
  "playwright": "^1.48.0",
60
62
  "react": "^18.2.0",
@@ -94,6 +96,9 @@
94
96
  },
95
97
  "mammoth": {
96
98
  "optional": true
99
+ },
100
+ "online-3d-viewer": {
101
+ "optional": true
97
102
  }
98
103
  }
99
104
  }
@@ -1,6 +0,0 @@
1
- export { Preview as default, setPdfPreview } from './chunk-4XBIXMZC.js';
2
- import './chunk-WIJ45SYD.js';
3
- import './chunk-AKZTZLKP.js';
4
- import './chunk-RFTLYCSF.js';
5
- //# sourceMappingURL=Preview-RQPIGKTJ.js.map
6
- //# sourceMappingURL=Preview-RQPIGKTJ.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/apps/Preview.tsx"],"names":[],"mappings":"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","file":"chunk-4XBIXMZC.js","sourcesContent":["/**\n * Preview — windowed PDF viewer app.\n *\n * Consumers stage a PDF via `setPdfPreview({ url, filename, ... })` and then\n * call `openPage('/preview')`. If the window is already open, it swaps to the\n * new PDF in-place via a custom event.\n */\nimport { useState, useEffect, useRef } from 'react';\nimport * as pdfjsLib from 'pdfjs-dist';\nimport toast from '../shell/toast';\nimport { WindowTitle } from '../shell/Modal';\n\nconst TITLE_DISPLAY_MAX = 24;\nfunction truncateForTitle(s: string) {\n return s.length > TITLE_DISPLAY_MAX ? `${s.slice(0, TITLE_DISPLAY_MAX - 1)}…` : s;\n}\n\n// Default the worker to the matching unpkg build (mirrors the consumer's\n// installed npm version exactly). Consumers can override by setting\n// pdfjsLib.GlobalWorkerOptions.workerSrc themselves before opening the\n// Preview window.\nif (typeof window !== 'undefined' && !pdfjsLib.GlobalWorkerOptions.workerSrc) {\n pdfjsLib.GlobalWorkerOptions.workerSrc =\n `https://unpkg.com/pdfjs-dist@${pdfjsLib.version}/build/pdf.worker.min.mjs`;\n}\n\nexport interface PdfPreviewData {\n /** Object URL or remote URL of the PDF. Blob URLs are revoked when the window unmounts.\n * Leave blank when staging a `converting: true` placeholder; call `setPdfPreview` again\n * with the resolved URL once conversion finishes. */\n url?: string;\n /** Display name (and download filename). */\n filename: string;\n /** Renderer to use. Defaults to `'pdf'`. `'dxf'` requires the consumer to\n * have `dxf-viewer` installed (it's an optional peer dep). `'image'`\n * renders an `<img>` for raster screenshots / photos. */\n kind?: 'pdf' | 'dxf' | 'image';\n /** Optional download handler — replaces the built-in \"save URL as filename\" if supplied. */\n onDownload?: () => void;\n /** Optional email handler — only shown when supplied. */\n onEmail?: () => void;\n /** Show a progress placeholder while the consumer fetches/converts the file. */\n converting?: boolean;\n /** Headline shown on the converting placeholder (e.g. \"CONVERTING DWG FILE\"). */\n convertingMessage?: string;\n}\n\nconst EVENT_NAME = 'react-os-shell:pdf-preview';\n\nlet pendingData: PdfPreviewData | null = null;\n\n/** Stage a PDF for the next Preview window mount, or swap into an open one. */\nexport function setPdfPreview(data: PdfPreviewData) {\n pendingData = data;\n if (typeof window !== 'undefined') {\n window.dispatchEvent(new CustomEvent(EVENT_NAME, { detail: data }));\n }\n}\n\nexport default function Preview() {\n const [data, setData] = useState<PdfPreviewData | null>(() => {\n const d = pendingData;\n pendingData = null;\n return d;\n });\n\n // Swap to a new PDF if `setPdfPreview` is called while the window is open.\n useEffect(() => {\n const handler = (e: Event) => {\n const next = (e as CustomEvent<PdfPreviewData>).detail;\n setData(prev => {\n if (prev?.url && prev.url !== next.url && prev.url.startsWith('blob:')) {\n URL.revokeObjectURL(prev.url);\n }\n return next;\n });\n };\n window.addEventListener(EVENT_NAME, handler);\n return () => window.removeEventListener(EVENT_NAME, handler);\n }, []);\n\n // Revoke blob URL on unmount.\n useEffect(() => () => {\n if (data?.url?.startsWith('blob:')) URL.revokeObjectURL(data.url);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Window title reflects whatever is loaded — same pattern Spreadsheets uses.\n const titleName = data?.filename ? truncateForTitle(data.filename) : 'Untitled';\n\n const fileRef = useRef<HTMLInputElement>(null);\n const [isDragging, setIsDragging] = useState(false);\n const handlePick = () => fileRef.current?.click();\n const ingestFile = (file: File) => {\n const url = URL.createObjectURL(file);\n const ext = (file.name.split('.').pop() || '').toLowerCase();\n const kind: 'pdf' | 'image' | 'dxf' | undefined =\n ext === 'pdf' ? 'pdf'\n : ext === 'dxf' ? 'dxf'\n : ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg', 'avif', 'bmp'].includes(ext) ? 'image'\n : undefined;\n if (!kind) {\n URL.revokeObjectURL(url);\n if (ext === 'dwg') toast.error('DWG files need server-side conversion. Convert to PDF or DXF first.');\n else toast.error(`Unsupported file type: .${ext || 'unknown'}`);\n return;\n }\n setPdfPreview({ url, filename: file.name, kind });\n };\n const handleFile = (e: React.ChangeEvent<HTMLInputElement>) => {\n const file = e.target.files?.[0];\n if (file) ingestFile(file);\n if (fileRef.current) fileRef.current.value = '';\n };\n const handleDrop = (e: React.DragEvent) => {\n e.preventDefault();\n setIsDragging(false);\n const file = e.dataTransfer.files?.[0];\n if (file) ingestFile(file);\n };\n\n const Toolbar = (\n <div className=\"flex items-center gap-2 px-3 py-2 border-b border-gray-200 bg-gray-50 shrink-0\">\n <input\n ref={fileRef}\n type=\"file\"\n accept=\".pdf,.dxf,.jpg,.jpeg,.png,.gif,.webp,.svg,.avif,.bmp\"\n onChange={handleFile}\n className=\"hidden\"\n />\n <button\n onClick={handlePick}\n className=\"text-xs text-gray-700 hover:text-gray-900 px-2 py-1 rounded hover:bg-gray-200 transition-colors flex items-center gap-1\"\n >\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776\" />\n </svg>\n Open\n </button>\n <span className=\"text-[10px] text-gray-400 ml-1\">PDF · DXF · Images</span>\n {data?.filename && (\n <>\n <div className=\"h-4 w-px bg-gray-300 mx-1\" />\n <span className=\"text-xs font-medium text-gray-700 truncate max-w-[200px]\" title={data.filename}>{data.filename}</span>\n </>\n )}\n </div>\n );\n\n let body: React.ReactNode;\n if (!data) {\n body = (\n <div className=\"flex flex-1 flex-col items-center justify-center text-gray-500 text-sm gap-3 p-8 text-center\">\n <svg className=\"h-12 w-12 text-gray-300\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z\" />\n </svg>\n <p className=\"font-medium text-gray-700\">Drop a file here, or click <button onClick={handlePick} className=\"text-blue-600 hover:underline\">Open</button>.</p>\n <div className=\"text-xs text-gray-500 max-w-sm\">\n <p className=\"font-semibold uppercase tracking-wide text-[10px] text-gray-400 mb-1\">Supported formats</p>\n <ul className=\"space-y-0.5\">\n <li><span className=\"font-mono text-gray-700\">.pdf</span> — multi-page document viewer</li>\n <li><span className=\"font-mono text-gray-700\">.dxf</span> — vector CAD drawings (requires the optional <span className=\"font-mono\">dxf-viewer</span> peer dep)</li>\n <li><span className=\"font-mono text-gray-700\">.jpg .jpeg .png .gif .webp .svg .avif .bmp</span> — raster images</li>\n </ul>\n <p className=\"mt-2 text-[11px] text-gray-400 italic\">DWG files need to be converted to PDF or DXF first (server-side).</p>\n </div>\n </div>\n );\n } else if (data.converting || !data.url) {\n body = <ConvertingPanel filename={data.filename} message={data.convertingMessage} />;\n } else if (data.kind === 'dxf') {\n body = <DxfPanel key={data.url} url={data.url} filename={data.filename} onDownload={data.onDownload} onEmail={data.onEmail} />;\n } else if (data.kind === 'image') {\n body = <ImagePanel key={data.url} url={data.url} filename={data.filename} onDownload={data.onDownload} onEmail={data.onEmail} />;\n } else {\n body = <PdfPanel key={data.url} url={data.url} filename={data.filename} onDownload={data.onDownload} onEmail={data.onEmail} />;\n }\n\n return (\n <div\n className=\"relative flex flex-col h-full\"\n onDragOver={(e) => { e.preventDefault(); if (!isDragging) setIsDragging(true); }}\n onDragLeave={(e) => {\n // Only clear when leaving the outer container, not transitioning between children.\n if (e.currentTarget === e.target) setIsDragging(false);\n }}\n onDrop={handleDrop}\n >\n <WindowTitle title={`${titleName} - Preview`} />\n {Toolbar}\n <div className=\"flex-1 min-h-0\">{body}</div>\n {isDragging && (\n <div className=\"absolute inset-0 bg-blue-500/15 border-4 border-dashed border-blue-500 pointer-events-none flex items-center justify-center z-20\">\n <div className=\"px-4 py-2 rounded-md bg-blue-600 text-white text-sm font-medium shadow-lg\">\n Drop to open\n </div>\n </div>\n )}\n </div>\n );\n}\n\nfunction ConvertingPanel({ filename, message }: { filename: string; message?: string }) {\n return (\n <div className=\"flex flex-col items-center justify-center h-full bg-gray-100 gap-4 px-8\">\n <div className=\"flex flex-col items-center gap-3\">\n <svg className=\"h-12 w-12 text-blue-500 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2}>\n <circle cx=\"12\" cy=\"12\" r=\"10\" strokeOpacity=\"0.2\" />\n <path d=\"M22 12a10 10 0 0 1-10 10\" strokeLinecap=\"round\" />\n </svg>\n <div className=\"text-base font-semibold tracking-wide text-gray-700 uppercase\">{message || 'Converting file'}</div>\n <div className=\"text-xs text-gray-400 truncate max-w-md\">{filename}</div>\n </div>\n <div className=\"w-72 h-1.5 rounded-full bg-gray-200 overflow-hidden\">\n <div className=\"h-full w-1/3 bg-blue-500 rounded-full animate-pulse\" style={{ animation: 'preview-bar 1.4s ease-in-out infinite' }} />\n </div>\n <style>{`@keyframes preview-bar { 0% { transform: translateX(-110%); } 100% { transform: translateX(310%); } }`}</style>\n </div>\n );\n}\n\ninterface PdfPanelProps {\n url: string;\n filename: string;\n onDownload?: () => void;\n onEmail?: () => void;\n}\n\nfunction PdfPanel({ url, filename, onDownload, onEmail }: PdfPanelProps) {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const [pdf, setPdf] = useState<pdfjsLib.PDFDocumentProxy | null>(null);\n const [page, setPage] = useState(1);\n const [totalPages, setTotalPages] = useState(0);\n const [scale, setScale] = useState(1.5);\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n let cancelled = false;\n setLoading(true);\n pdfjsLib.getDocument(url).promise.then(doc => {\n if (cancelled) return;\n setPdf(doc);\n setTotalPages(doc.numPages);\n setLoading(false);\n }).catch(() => {\n if (!cancelled) { toast.error('Failed to load PDF'); setLoading(false); }\n });\n return () => { cancelled = true; };\n }, [url]);\n\n useEffect(() => {\n if (!pdf || !containerRef.current) return;\n pdf.getPage(1).then(p => {\n const containerW = containerRef.current?.clientWidth || 800;\n const viewport = p.getViewport({ scale: 1 });\n const fitScale = (containerW - 40) / viewport.width;\n setScale(Math.min(Math.max(fitScale, 0.5), 3));\n });\n }, [pdf]);\n\n useEffect(() => {\n if (!pdf || !canvasRef.current) return;\n let cancelled = false;\n pdf.getPage(page).then(p => {\n if (cancelled || !canvasRef.current) return;\n const viewport = p.getViewport({ scale });\n const canvas = canvasRef.current;\n canvas.width = viewport.width;\n canvas.height = viewport.height;\n const ctx = canvas.getContext('2d')!;\n p.render({ canvas, canvasContext: ctx, viewport }).promise.catch(() => {});\n });\n return () => { cancelled = true; };\n }, [pdf, page, scale]);\n\n const handlePrint = () => {\n if (!pdf) return;\n const win = window.open('', '_blank');\n if (!win) { toast.error('Allow popups to print'); return; }\n const promises: Promise<string>[] = [];\n for (let i = 1; i <= totalPages; i++) {\n promises.push(pdf.getPage(i).then(p => {\n const vp = p.getViewport({ scale: 2 });\n const c = document.createElement('canvas');\n c.width = vp.width; c.height = vp.height;\n return p.render({ canvas: c, canvasContext: c.getContext('2d')!, viewport: vp }).promise.then(() => c.toDataURL());\n }));\n }\n Promise.all(promises).then(images => {\n win.document.write(`<html><head><title>${filename}</title><style>@media print{body{margin:0}img{width:100%;page-break-after:always}}</style></head><body>`);\n win.document.write(images.map(src => `<img src=\"${src}\"/>`).join(''));\n win.document.write('</body></html>');\n win.document.close();\n setTimeout(() => { win.print(); win.close(); }, 300);\n });\n };\n\n const handleDefaultDownload = () => {\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n a.click();\n };\n\n const fitWidth = () => {\n if (!pdf || !containerRef.current) return;\n pdf.getPage(page).then(p => {\n const containerW = containerRef.current?.clientWidth || 800;\n const viewport = p.getViewport({ scale: 1 });\n setScale(Math.min(Math.max((containerW - 40) / viewport.width, 0.5), 3));\n });\n };\n\n const btn = 'px-2 py-1 rounded hover:bg-gray-200 transition-colors text-gray-600 flex items-center gap-1';\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex items-center justify-between px-3 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0 text-xs\">\n <div className=\"flex items-center gap-1\">\n <button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page <= 1} className=\"px-2 py-1 rounded hover:bg-gray-200 disabled:opacity-30\">\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M15.75 19.5L8.25 12l7.5-7.5\" /></svg>\n </button>\n <span className=\"text-gray-600 font-medium tabular-nums\">{page} / {totalPages}</span>\n <button onClick={() => setPage(p => Math.min(totalPages, p + 1))} disabled={page >= totalPages} className=\"px-2 py-1 rounded hover:bg-gray-200 disabled:opacity-30\">\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" /></svg>\n </button>\n </div>\n\n <div className=\"flex items-center gap-1\">\n <button onClick={() => setScale(s => Math.max(0.3, Math.round((s - 0.25) * 100) / 100))} className={btn}>−</button>\n <span className=\"text-gray-500 w-12 text-center tabular-nums\">{Math.round(scale * 100)}%</span>\n <button onClick={() => setScale(s => Math.min(4, Math.round((s + 0.25) * 100) / 100))} className={btn}>+</button>\n <button onClick={fitWidth} className={btn}>Fit</button>\n </div>\n\n <div className=\"flex items-center gap-1\">\n <button onClick={handlePrint} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6.72 13.829c-.24.03-.48.062-.72.096m.72-.096a42.415 42.415 0 0110.56 0m-10.56 0L6.34 18m10.94-4.171c.24.03.48.062.72.096m-.72-.096L17.66 18m0 0l.229 2.523a1.125 1.125 0 01-1.12 1.227H7.231c-.662 0-1.18-.568-1.12-1.227L6.34 18m11.318 0h1.091A2.25 2.25 0 0021 15.75V9.456c0-1.081-.768-2.015-1.837-2.175a48.055 48.055 0 00-1.913-.247M6.34 18H5.25A2.25 2.25 0 013 15.75V9.456c0-1.081.768-2.015 1.837-2.175a48.041 48.041 0 011.913-.247m10.5 0a48.536 48.536 0 00-10.5 0m10.5 0V3.375c0-.621-.504-1.125-1.125-1.125h-8.25c-.621 0-1.125.504-1.125 1.125v3.659M18 10.5h.008v.008H18V10.5zm-3 0h.008v.008H15V10.5z\" /></svg>\n Print\n </button>\n <button onClick={onDownload ?? handleDefaultDownload} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3\" /></svg>\n Download\n </button>\n {onEmail && (\n <button onClick={onEmail} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75\" /></svg>\n Email\n </button>\n )}\n </div>\n </div>\n\n <div ref={containerRef} className=\"flex-1 overflow-auto bg-gray-100 flex justify-center p-4\">\n {loading ? (\n <div className=\"flex items-center justify-center py-20 text-gray-400 text-sm\">Loading PDF...</div>\n ) : (\n <canvas ref={canvasRef} className=\"shadow-lg rounded\" />\n )}\n </div>\n </div>\n );\n}\n\ninterface DxfPanelProps {\n url: string;\n filename: string;\n onDownload?: () => void;\n onEmail?: () => void;\n}\n\nfunction DxfPanel({ url, filename, onDownload, onEmail }: DxfPanelProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const viewerRef = useRef<any>(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n let viewer: any = null;\n setLoading(true);\n setError(null);\n\n (async () => {\n let DxfViewer: any;\n try {\n const mod = await import('dxf-viewer');\n DxfViewer = (mod as any).DxfViewer;\n } catch (e) {\n if (!cancelled) {\n setError('dxf-viewer is not installed in this app.');\n setLoading(false);\n }\n return;\n }\n if (cancelled || !containerRef.current) return;\n try {\n // dxf-viewer reads container dimensions in its constructor (with\n // autoResize:true, those become the canvas size). If the Modal is\n // still settling we may be measured as 0×0, breaking aspect math\n // and the WebGL viewport. Wait until the container has a real\n // bounding box before constructing the viewer.\n await new Promise<void>(resolve => {\n const tryStart = () => {\n const r = containerRef.current?.getBoundingClientRect();\n if (r && r.width > 4 && r.height > 4) resolve();\n else requestAnimationFrame(tryStart);\n };\n tryStart();\n });\n if (cancelled || !containerRef.current) return;\n\n // clearColor must be a THREE.Color (Library calls .getHex()).\n let three: any = null;\n try { three = await import(/* @vite-ignore */ 'three' as any); } catch {}\n const ClearColor = three?.Color ?? null;\n const viewerOpts: any = { autoResize: true };\n if (ClearColor) viewerOpts.clearColor = new ClearColor(0xffffff);\n viewer = new DxfViewer(containerRef.current, viewerOpts);\n viewerRef.current = viewer;\n await viewer.Load({ url, fonts: null, workerFactory: null });\n if (cancelled) return;\n // Force-fit using the actual loaded bounds and refresh canvas size.\n const refit = () => {\n try {\n const rect = containerRef.current?.getBoundingClientRect();\n if (rect && rect.width > 0 && rect.height > 0) {\n viewer.SetSize?.(Math.round(rect.width), Math.round(rect.height));\n }\n const bounds = viewer.GetBounds?.();\n const origin = viewer.GetOrigin?.();\n if (bounds && origin) {\n viewer.FitView(\n bounds.minX - origin.x, bounds.maxX - origin.x,\n bounds.minY - origin.y, bounds.maxY - origin.y,\n );\n }\n viewer.Render?.();\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn('[Preview] DXF refit failed', err);\n }\n };\n refit();\n requestAnimationFrame(refit);\n setLoading(false);\n } catch (e: any) {\n if (!cancelled) {\n setError(e?.message || 'Failed to render DXF.');\n setLoading(false);\n }\n }\n })();\n\n return () => {\n cancelled = true;\n try { viewer?.Destroy?.(); } catch {}\n viewerRef.current = null;\n };\n }, [url]);\n\n const handleDefaultDownload = () => {\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n a.click();\n };\n\n const handleResetView = () => {\n try { viewerRef.current?.FitView?.(); } catch {}\n };\n\n const btn = 'px-2 py-1 rounded hover:bg-gray-200 transition-colors text-gray-600 flex items-center gap-1';\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex items-center justify-between px-3 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0 text-xs\">\n <div className=\"flex items-center gap-1\">\n <span className=\"font-medium text-gray-600\">DXF</span>\n <span className=\"text-gray-400 truncate max-w-xs\">{filename}</span>\n </div>\n <div className=\"flex items-center gap-1\">\n <button onClick={handleResetView} className={btn} title=\"Fit drawing to view\">Fit</button>\n <button onClick={onDownload ?? handleDefaultDownload} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3\" /></svg>\n Download\n </button>\n {onEmail && (\n <button onClick={onEmail} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75\" /></svg>\n Email\n </button>\n )}\n </div>\n </div>\n <div className=\"relative flex-1 bg-white min-h-0\">\n {/* dxf-viewer overrides container's `position` to `relative` in its\n * constructor, which kills any `inset: 0` sizing. Use explicit\n * width/height: 100% so the container always fills its flex parent. */}\n <div ref={containerRef} style={{ width: '100%', height: '100%' }} />\n {loading && (\n <div className=\"absolute inset-0 flex items-center justify-center bg-white/80 text-sm text-gray-500\">Loading drawing…</div>\n )}\n {error && (\n <div className=\"absolute inset-0 flex items-center justify-center text-sm text-red-600 px-6 text-center\">{error}</div>\n )}\n </div>\n </div>\n );\n}\n\ninterface ImagePanelProps {\n url: string;\n filename: string;\n onDownload?: () => void;\n onEmail?: () => void;\n}\n\nfunction ImagePanel({ url, filename, onDownload, onEmail }: ImagePanelProps) {\n const [zoom, setZoom] = useState(1);\n const [error, setError] = useState(false);\n\n const handleDefaultDownload = () => {\n const a = document.createElement('a');\n a.href = url;\n a.download = filename;\n a.click();\n };\n\n const btn = 'px-2 py-1 rounded hover:bg-gray-200 transition-colors text-gray-600 flex items-center gap-1';\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex items-center justify-between px-3 py-1.5 border-b border-gray-200 bg-gray-50 shrink-0 text-xs\">\n <div className=\"flex items-center gap-1\">\n <span className=\"font-medium text-gray-600\">Image</span>\n <span className=\"text-gray-400 truncate max-w-xs\">{filename}</span>\n </div>\n <div className=\"flex items-center gap-1\">\n <button onClick={() => setZoom(z => Math.max(0.1, Math.round((z - 0.25) * 100) / 100))} className={btn}>−</button>\n <span className=\"text-gray-500 w-12 text-center tabular-nums\">{Math.round(zoom * 100)}%</span>\n <button onClick={() => setZoom(z => Math.min(8, Math.round((z + 0.25) * 100) / 100))} className={btn}>+</button>\n <button onClick={() => setZoom(1)} className={btn}>1:1</button>\n </div>\n <div className=\"flex items-center gap-1\">\n <button onClick={onDownload ?? handleDefaultDownload} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5M16.5 12L12 16.5m0 0L7.5 12m4.5 4.5V3\" /></svg>\n Download\n </button>\n {onEmail && (\n <button onClick={onEmail} className={btn}>\n <svg className=\"h-3.5 w-3.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={1.5}><path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75\" /></svg>\n Email\n </button>\n )}\n </div>\n </div>\n <div className=\"flex-1 overflow-auto bg-gray-100 flex items-center justify-center p-4\">\n {error ? (\n <div className=\"text-sm text-red-600\">Failed to load image.</div>\n ) : (\n <img\n src={url}\n alt={filename}\n onError={() => setError(true)}\n style={{ transform: `scale(${zoom})`, transformOrigin: 'center center', transition: 'transform 120ms ease' }}\n className=\"max-w-full max-h-full shadow-lg rounded bg-white\"\n />\n )}\n </div>\n </div>\n );\n}\n"]}