react-os-shell 2.4.0 → 2.6.0

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.
Files changed (51) hide show
  1. package/dist/Browser-H55YZJT5.js +7 -0
  2. package/dist/{Browser-PNQ5TVRZ.js.map → Browser-H55YZJT5.js.map} +1 -1
  3. package/dist/{Calculator-XGXSRMF2.js → Calculator-H7HMALL3.js} +4 -4
  4. package/dist/{Calculator-XGXSRMF2.js.map → Calculator-H7HMALL3.js.map} +1 -1
  5. package/dist/{CurrencyConverter-SVYA7ZGT.js → CurrencyConverter-576WXG7B.js} +4 -4
  6. package/dist/{CurrencyConverter-SVYA7ZGT.js.map → CurrencyConverter-576WXG7B.js.map} +1 -1
  7. package/dist/{Documents-L67KOICK.js → Documents-XBOFBIIZ.js} +4 -4
  8. package/dist/{Documents-L67KOICK.js.map → Documents-XBOFBIIZ.js.map} +1 -1
  9. package/dist/Files-KX3OMH6V.js +13 -0
  10. package/dist/{Files-4D6XQRZB.js.map → Files-KX3OMH6V.js.map} +1 -1
  11. package/dist/{Notepad-L6QXDCAY.js → Notepad-PWLIOQR5.js} +4 -4
  12. package/dist/{Notepad-L6QXDCAY.js.map → Notepad-PWLIOQR5.js.map} +1 -1
  13. package/dist/{PomodoroTimer-L62PEYPP.js → PomodoroTimer-PQ7FXSKY.js} +4 -4
  14. package/dist/{PomodoroTimer-L62PEYPP.js.map → PomodoroTimer-PQ7FXSKY.js.map} +1 -1
  15. package/dist/Preview-YOTSKJNV.js +9 -0
  16. package/dist/{Preview-XWX24RJD.js.map → Preview-YOTSKJNV.js.map} +1 -1
  17. package/dist/Spreadsheet-LQOJPR4G.js +7 -0
  18. package/dist/{Spreadsheet-2SWMHJQY.js.map → Spreadsheet-LQOJPR4G.js.map} +1 -1
  19. package/dist/{Stock-EDXEPZFF.js → Stock-NQFKY52J.js} +4 -4
  20. package/dist/{Stock-EDXEPZFF.js.map → Stock-NQFKY52J.js.map} +1 -1
  21. package/dist/{Weather-T2EVJ5NE.js → Weather-KH5A7AZ3.js} +4 -4
  22. package/dist/{Weather-T2EVJ5NE.js.map → Weather-KH5A7AZ3.js.map} +1 -1
  23. package/dist/{WorldClock-JN7YJN3B.js → WorldClock-VUMFYV5V.js} +4 -4
  24. package/dist/{WorldClock-JN7YJN3B.js.map → WorldClock-VUMFYV5V.js.map} +1 -1
  25. package/dist/apps/index.js +19 -19
  26. package/dist/{chunk-G4H4FQN3.js → chunk-2NHG2V2R.js} +5 -5
  27. package/dist/{chunk-G4H4FQN3.js.map → chunk-2NHG2V2R.js.map} +1 -1
  28. package/dist/{chunk-CGSN2MCD.js → chunk-7CT2Y2MB.js} +587 -126
  29. package/dist/chunk-7CT2Y2MB.js.map +1 -0
  30. package/dist/{chunk-PXCTWRVI.js → chunk-BE53IFLK.js} +4 -4
  31. package/dist/{chunk-PXCTWRVI.js.map → chunk-BE53IFLK.js.map} +1 -1
  32. package/dist/{chunk-A7MAQOF3.js → chunk-FIOHSNGM.js} +6 -6
  33. package/dist/{chunk-A7MAQOF3.js.map → chunk-FIOHSNGM.js.map} +1 -1
  34. package/dist/{chunk-EH6MIG5E.js → chunk-QOH6MNLW.js} +4 -4
  35. package/dist/{chunk-EH6MIG5E.js.map → chunk-QOH6MNLW.js.map} +1 -1
  36. package/dist/{chunk-BZLRZPLP.js → chunk-R3CZB3O2.js} +4 -4
  37. package/dist/{chunk-BZLRZPLP.js.map → chunk-R3CZB3O2.js.map} +1 -1
  38. package/dist/{chunk-QAHF22KW.js → chunk-TJ6N7SI5.js} +24 -3
  39. package/dist/chunk-TJ6N7SI5.js.map +1 -0
  40. package/dist/{chunk-5V6OCGI6.js → chunk-YVIW5GPB.js} +3 -3
  41. package/dist/{chunk-5V6OCGI6.js.map → chunk-YVIW5GPB.js.map} +1 -1
  42. package/dist/index.d.ts +13 -3
  43. package/dist/index.js +39 -24
  44. package/dist/index.js.map +1 -1
  45. package/package.json +1 -1
  46. package/dist/Browser-PNQ5TVRZ.js +0 -7
  47. package/dist/Files-4D6XQRZB.js +0 -13
  48. package/dist/Preview-XWX24RJD.js +0 -9
  49. package/dist/Spreadsheet-2SWMHJQY.js +0 -7
  50. package/dist/chunk-CGSN2MCD.js.map +0 -1
  51. package/dist/chunk-QAHF22KW.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { ImageAnnotator_default } from './chunk-KUIPWCTJ.js';
2
2
  import { toast_default } from './chunk-WIJ45SYD.js';
3
- import { AboutApp } from './chunk-A7MAQOF3.js';
4
- import { WindowTitle, getActiveModalId } from './chunk-QAHF22KW.js';
3
+ import { AboutApp } from './chunk-FIOHSNGM.js';
4
+ import { WindowTitle, registerModalEscapeInterceptor, getActiveModalId } from './chunk-TJ6N7SI5.js';
5
5
  import { createContext, useRef, useEffect, useState, useContext } from 'react';
6
6
  import { createPortal } from 'react-dom';
7
7
  import * as pdfjsLib from 'pdfjs-dist';
@@ -604,6 +604,61 @@ var DEFAULT_DXF_FONTS = [
604
604
  "https://cdn.jsdelivr.net/gh/vagran/dxf-viewer-example-src@master/src/assets/fonts/NotoSansDisplay-SemiCondensedLightItalic.ttf",
605
605
  "https://cdn.jsdelivr.net/gh/vagran/dxf-viewer-example-src@master/src/assets/fonts/NanumGothic-Regular.ttf"
606
606
  ];
607
+ var DXF_EDIT_COMMANDS = {
608
+ l: "LINE",
609
+ line: "LINE",
610
+ pl: "PLINE",
611
+ pline: "PLINE",
612
+ ex: "EXTEND",
613
+ extend: "EXTEND",
614
+ tr: "TRIM",
615
+ trim: "TRIM",
616
+ e: "ERASE",
617
+ erase: "ERASE",
618
+ m: "MOVE",
619
+ move: "MOVE",
620
+ co: "COPY",
621
+ cp: "COPY",
622
+ copy: "COPY",
623
+ o: "OFFSET",
624
+ offset: "OFFSET",
625
+ f: "FILLET",
626
+ fillet: "FILLET",
627
+ cha: "CHAMFER",
628
+ chamfer: "CHAMFER",
629
+ x: "EXPLODE",
630
+ explode: "EXPLODE",
631
+ mi: "MIRROR",
632
+ mirror: "MIRROR",
633
+ ro: "ROTATE",
634
+ rotate: "ROTATE",
635
+ sc: "SCALE",
636
+ scale: "SCALE",
637
+ s: "STRETCH",
638
+ stretch: "STRETCH",
639
+ ar: "ARRAY",
640
+ array: "ARRAY",
641
+ rec: "RECTANG",
642
+ rectang: "RECTANG",
643
+ rectangle: "RECTANG",
644
+ c: "CIRCLE",
645
+ circle: "CIRCLE",
646
+ a: "ARC",
647
+ arc: "ARC",
648
+ el: "ELLIPSE",
649
+ ellipse: "ELLIPSE",
650
+ t: "MTEXT",
651
+ mt: "MTEXT",
652
+ mtext: "MTEXT",
653
+ text: "TEXT",
654
+ b: "BLOCK",
655
+ block: "BLOCK",
656
+ i: "INSERT",
657
+ insert: "INSERT",
658
+ ha: "HATCH",
659
+ hatch: "HATCH"
660
+ };
661
+ var DXF_CMD_HELP = "Commands: DI distance \xB7 DIM/DLI linear dim \xB7 H / V force axis \xB7 <number> lock \u0394 \xB7 U undo pick \xB7 Z fit \xB7 LA layers \xB7 Esc exit";
607
662
  function DxfPanel({ url, filename, onDownload, onEmail }) {
608
663
  const containerRef = useRef(null);
609
664
  const viewerRef = useRef(null);
@@ -613,8 +668,9 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
613
668
  const [showLayers, setShowLayers] = useState(false);
614
669
  const [showHint, setShowHint] = useState(true);
615
670
  const [measureEnabled, setMeasureEnabled] = useState(false);
616
- const [measureMode, setMeasureMode] = useState("horizontal");
671
+ const [measureMode, setMeasureMode] = useState("auto");
617
672
  const [measureDistance, setMeasureDistance] = useState(null);
673
+ const [measureResolved, setMeasureResolved] = useState(null);
618
674
  const [measureFixedDist, setMeasureFixedDist] = useState(null);
619
675
  const [measureFixedInput, setMeasureFixedInput] = useState("");
620
676
  const measureModeRef = useRef(measureMode);
@@ -626,6 +682,13 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
626
682
  measureFixedDistRef.current = measureFixedDist;
627
683
  }, [measureFixedDist]);
628
684
  const measureRedrawRef = useRef(null);
685
+ const measureResetRef = useRef(null);
686
+ const measureUndoRef = useRef(null);
687
+ const [cmdValue, setCmdValue] = useState("");
688
+ const [cmdEcho, setCmdEcho] = useState(null);
689
+ const lastCmdRef = useRef("");
690
+ const cmdInputRef = useRef(null);
691
+ const rootRef = useRef(null);
629
692
  const measureRef = useRef(null);
630
693
  useEffect(() => {
631
694
  let cancelled = false;
@@ -729,6 +792,7 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
729
792
  const t = setTimeout(() => setShowHint(false), 5e3);
730
793
  return () => clearTimeout(t);
731
794
  }, [showHint, loading]);
795
+ const layersKey = layers.map((l) => l.visible ? "1" : "0").join("");
732
796
  useEffect(() => {
733
797
  const v = viewerRef.current;
734
798
  if (loading || error || !v) return;
@@ -745,6 +809,7 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
745
809
  if (!measureEnabled) {
746
810
  teardown();
747
811
  setMeasureDistance(null);
812
+ setMeasureResolved(null);
748
813
  return;
749
814
  }
750
815
  const overlay = document.createElement("div");
@@ -768,13 +833,18 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
768
833
  defs.appendChild(marker);
769
834
  svg.appendChild(defs);
770
835
  overlay.appendChild(svg);
771
- const refLineEl = document.createElementNS("http://www.w3.org/2000/svg", "line");
772
- refLineEl.setAttribute("stroke", "#ff8800");
773
- refLineEl.setAttribute("stroke-width", "1");
774
- refLineEl.setAttribute("stroke-dasharray", "6,4");
775
- refLineEl.setAttribute("opacity", "0.55");
776
- refLineEl.style.display = "none";
777
- svg.appendChild(refLineEl);
836
+ const mkRefLine = () => {
837
+ const el = document.createElementNS("http://www.w3.org/2000/svg", "line");
838
+ el.setAttribute("stroke", "#ff8800");
839
+ el.setAttribute("stroke-width", "1");
840
+ el.setAttribute("stroke-dasharray", "6,4");
841
+ el.setAttribute("opacity", "0.55");
842
+ el.style.display = "none";
843
+ svg.appendChild(el);
844
+ return el;
845
+ };
846
+ const refLineEl = mkRefLine();
847
+ const refLineVEl = mkRefLine();
778
848
  const extLineA = document.createElementNS("http://www.w3.org/2000/svg", "line");
779
849
  extLineA.setAttribute("stroke", "#ff8800");
780
850
  extLineA.setAttribute("stroke-width", "1");
@@ -850,10 +920,44 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
850
920
  poly.setAttribute("stroke-linejoin", "round");
851
921
  g.appendChild(poly);
852
922
  });
923
+ const snapGlyphMidpoint = mkSnapGlyph((g) => {
924
+ const poly = document.createElementNS(SVGNS, "polygon");
925
+ poly.setAttribute("points", "11,3 19,18 3,18");
926
+ poly.setAttribute("fill", "rgba(255,255,255,0.9)");
927
+ poly.setAttribute("stroke", "#ff8800");
928
+ poly.setAttribute("stroke-width", "1.8");
929
+ poly.setAttribute("stroke-linejoin", "round");
930
+ g.appendChild(poly);
931
+ });
932
+ const snapGlyphNode = mkSnapGlyph((g) => {
933
+ const c = document.createElementNS(SVGNS, "circle");
934
+ c.setAttribute("cx", "11");
935
+ c.setAttribute("cy", "11");
936
+ c.setAttribute("r", "7");
937
+ c.setAttribute("fill", "rgba(255,255,255,0.9)");
938
+ c.setAttribute("stroke", "#ff8800");
939
+ c.setAttribute("stroke-width", "1.8");
940
+ g.appendChild(c);
941
+ const mkLn = (x1, y1, x2, y2) => {
942
+ const ln = document.createElementNS(SVGNS, "line");
943
+ ln.setAttribute("x1", String(x1));
944
+ ln.setAttribute("y1", String(y1));
945
+ ln.setAttribute("x2", String(x2));
946
+ ln.setAttribute("y2", String(y2));
947
+ ln.setAttribute("stroke", "#ff8800");
948
+ ln.setAttribute("stroke-width", "1.6");
949
+ ln.setAttribute("stroke-linecap", "round");
950
+ return ln;
951
+ };
952
+ g.appendChild(mkLn(6.5, 6.5, 15.5, 15.5));
953
+ g.appendChild(mkLn(15.5, 6.5, 6.5, 15.5));
954
+ });
853
955
  const setSnapGlyph = (type) => {
854
956
  snapGlyphEndpoint.style.display = type === "endpoint" ? "" : "none";
855
957
  snapGlyphIntersection.style.display = type === "intersection" ? "" : "none";
856
958
  snapGlyphLine.style.display = type === "line" ? "" : "none";
959
+ snapGlyphMidpoint.style.display = type === "midpoint" ? "" : "none";
960
+ snapGlyphNode.style.display = type === "node" ? "" : "none";
857
961
  };
858
962
  overlay.appendChild(snapEl);
859
963
  measureRef.current = {
@@ -865,12 +969,14 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
865
969
  fixedLine: fixedLineEl,
866
970
  fixedLabel: null,
867
971
  refLine: refLineEl,
972
+ refLineV: refLineVEl,
868
973
  extLineA,
869
974
  extLineB,
870
975
  markers: [],
871
976
  label: null,
872
977
  snap: snapEl,
873
- segments: []
978
+ segs: null,
979
+ nodes: null
874
980
  };
875
981
  let THREE = null;
876
982
  let ready = false;
@@ -880,28 +986,82 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
880
986
  const w = canvas.clientWidth, h = canvas.clientHeight;
881
987
  return { x: (v3.x + 1) / 2 * w, y: (-v3.y + 1) / 2 * h };
882
988
  };
989
+ const MAX_SNAP_SEGS = 4e5;
883
990
  (async () => {
884
991
  try {
885
992
  THREE = await import(
886
993
  /* @vite-ignore */
887
994
  'three'
888
995
  );
889
- const segs = measureRef.current?.segments;
890
- if (!segs) return;
891
- const va = new THREE.Vector3(), vb = new THREE.Vector3();
892
- scene.traverse((obj) => {
893
- if (!obj?.isLineSegments) return;
894
- const pos = obj.geometry?.attributes?.position;
996
+ const st = measureRef.current;
997
+ if (!st) return;
998
+ const segXY = [];
999
+ const nodeXY = [];
1000
+ let truncated = false;
1001
+ const instanceXforms = (g) => {
1002
+ const t0 = g?.attributes?.instanceTransform0;
1003
+ const t1 = g?.attributes?.instanceTransform1;
1004
+ if (t0 && t1) {
1005
+ const out = [];
1006
+ const n = Math.min(t0.count, t1.count);
1007
+ for (let k = 0; k < n; k++) {
1008
+ out.push([t0.getX(k), t0.getY(k), t0.getZ(k), t1.getX(k), t1.getY(k), t1.getZ(k)]);
1009
+ }
1010
+ return out;
1011
+ }
1012
+ const tp = g?.attributes?.instanceTransform;
1013
+ if (tp) {
1014
+ const out = [];
1015
+ for (let k = 0; k < tp.count; k++) out.push([1, 0, tp.getX(k), 0, 1, tp.getY(k)]);
1016
+ return out;
1017
+ }
1018
+ return [[1, 0, 0, 0, 1, 0]];
1019
+ };
1020
+ const visit = (obj) => {
1021
+ const isSeg = !!obj?.isLineSegments;
1022
+ const isPts = !!obj?.isPoints;
1023
+ if (!isSeg && !isPts) return;
1024
+ const g = obj.geometry;
1025
+ const pos = g?.attributes?.position;
895
1026
  if (!pos) return;
896
- const arr = pos.array;
897
- obj.updateMatrixWorld?.();
898
- const m = obj.matrixWorld;
899
- for (let i = 0; i < arr.length; i += 6) {
900
- va.set(arr[i], arr[i + 1], arr[i + 2]).applyMatrix4(m);
901
- vb.set(arr[i + 3], arr[i + 4], arr[i + 5]).applyMatrix4(m);
902
- segs.push({ ax: va.x, ay: va.y, bx: vb.x, by: vb.y });
1027
+ const xfs = instanceXforms(g);
1028
+ if (isSeg) {
1029
+ const idx = g.index;
1030
+ const pairCount = Math.floor((idx ? idx.count : pos.count) / 2);
1031
+ for (let p = 0; p < pairCount; p++) {
1032
+ const ia = idx ? idx.getX(2 * p) : 2 * p;
1033
+ const ib = idx ? idx.getX(2 * p + 1) : 2 * p + 1;
1034
+ const ax = pos.getX(ia), ay = pos.getY(ia);
1035
+ const bx = pos.getX(ib), by = pos.getY(ib);
1036
+ if (!Number.isFinite(ax + ay + bx + by)) continue;
1037
+ for (const m of xfs) {
1038
+ if (segXY.length >= MAX_SNAP_SEGS * 4) {
1039
+ truncated = true;
1040
+ return;
1041
+ }
1042
+ const tax = m[0] * ax + m[1] * ay + m[2], tay = m[3] * ax + m[4] * ay + m[5];
1043
+ const tbx = m[0] * bx + m[1] * by + m[2], tby = m[3] * bx + m[4] * by + m[5];
1044
+ if (tax === tbx && tay === tby) continue;
1045
+ segXY.push(tax, tay, tbx, tby);
1046
+ }
1047
+ }
1048
+ } else {
1049
+ for (let i = 0; i < pos.count; i++) {
1050
+ const x = pos.getX(i), y = pos.getY(i);
1051
+ if (!Number.isFinite(x + y)) continue;
1052
+ for (const m of xfs) {
1053
+ nodeXY.push(m[0] * x + m[1] * y + m[2], m[3] * x + m[4] * y + m[5]);
1054
+ }
1055
+ }
903
1056
  }
904
- });
1057
+ };
1058
+ if (typeof scene.traverseVisible === "function") scene.traverseVisible(visit);
1059
+ else scene.traverse(visit);
1060
+ st.segs = new Float64Array(segXY);
1061
+ st.nodes = new Float64Array(nodeXY);
1062
+ if (truncated) {
1063
+ console.warn(`[Preview] DXF snap cache truncated at ${MAX_SNAP_SEGS} segments \u2014 snapping may miss some geometry.`);
1064
+ }
905
1065
  ready = true;
906
1066
  } catch {
907
1067
  }
@@ -918,54 +1078,92 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
918
1078
  };
919
1079
  const findSnap = (cx, cy) => {
920
1080
  const s = measureRef.current;
921
- if (!s || !ready) return null;
922
- let best = null;
923
- let bestD2 = SNAP_PX * SNAP_PX;
924
- const candidateD2 = SNAP_PX * SNAP_PX;
1081
+ if (!s || !ready || !s.segs) return null;
1082
+ const o = pxFromScene(0, 0);
1083
+ const ex = pxFromScene(1, 0);
1084
+ const ey = pxFromScene(0, 1);
1085
+ const mxx = ex.x - o.x, mxy = ex.y - o.y;
1086
+ const myx = ey.x - o.x, myy = ey.y - o.y;
1087
+ const det = mxx * myy - myx * mxy;
1088
+ if (!det) return null;
1089
+ const csx = ((cx - o.x) * myy - (cy - o.y) * myx) / det;
1090
+ const csy = ((cy - o.y) * mxx - (cx - o.x) * mxy) / det;
1091
+ const rx = SNAP_PX / (Math.hypot(mxx, mxy) || 1);
1092
+ const ry = SNAP_PX / (Math.hypot(myx, myy) || 1);
1093
+ const minX = csx - rx, maxX = csx + rx, minY = csy - ry, maxY = csy + ry;
1094
+ const T2 = SNAP_PX * SNAP_PX;
1095
+ let bestPt = null, dPt = T2;
1096
+ let bestMid = null, dMid = T2;
1097
+ let bestLine = null, dLine = T2;
1098
+ let bestX = null, dX = T2;
925
1099
  const cand = [];
926
- for (const seg of s.segments) {
927
- const ap = pxFromScene(seg.ax, seg.ay);
928
- const bp = pxFromScene(seg.bx, seg.by);
929
- const apx = ap.x, apy = ap.y, bpx = bp.x, bpy = bp.y;
930
- const rejectX = apx < cx - SNAP_PX && bpx < cx - SNAP_PX || apx > cx + SNAP_PX && bpx > cx + SNAP_PX;
931
- const rejectY = apy < cy - SNAP_PX && bpy < cy - SNAP_PX || apy > cy + SNAP_PX && bpy > cy + SNAP_PX;
932
- if (rejectX || rejectY) continue;
933
- const ldx = seg.bx - seg.ax, ldy = seg.by - seg.ay;
934
- const llen = Math.sqrt(ldx * ldx + ldy * ldy) || 1;
935
- const segDir = { dx: ldx / llen, dy: ldy / llen };
1100
+ const segs = s.segs;
1101
+ for (let i = 0; i < segs.length; i += 4) {
1102
+ const ax = segs[i], ay = segs[i + 1], bx = segs[i + 2], by = segs[i + 3];
1103
+ if (ax < minX && bx < minX || ax > maxX && bx > maxX || ay < minY && by < minY || ay > maxY && by > maxY) continue;
1104
+ const apx = o.x + mxx * ax + myx * ay, apy = o.y + mxy * ax + myy * ay;
1105
+ const bpx = o.x + mxx * bx + myx * by, bpy = o.y + mxy * bx + myy * by;
1106
+ let near = false;
936
1107
  let dx = cx - apx, dy = cy - apy;
937
- const dEpA2 = dx * dx + dy * dy;
938
- if (dEpA2 < bestD2) {
939
- best = { sx: seg.ax, sy: seg.ay, type: "endpoint", dir: segDir };
940
- bestD2 = dEpA2;
1108
+ let d2 = dx * dx + dy * dy;
1109
+ if (d2 < T2) {
1110
+ near = true;
1111
+ if (d2 < dPt) {
1112
+ dPt = d2;
1113
+ bestPt = { sx: ax, sy: ay, type: "endpoint" };
1114
+ }
941
1115
  }
942
1116
  dx = cx - bpx;
943
1117
  dy = cy - bpy;
944
- const dEpB2 = dx * dx + dy * dy;
945
- if (dEpB2 < bestD2) {
946
- best = { sx: seg.bx, sy: seg.by, type: "endpoint", dir: segDir };
947
- bestD2 = dEpB2;
1118
+ d2 = dx * dx + dy * dy;
1119
+ if (d2 < T2) {
1120
+ near = true;
1121
+ if (d2 < dPt) {
1122
+ dPt = d2;
1123
+ bestPt = { sx: bx, sy: by, type: "endpoint" };
1124
+ }
1125
+ }
1126
+ dx = cx - (apx + bpx) / 2;
1127
+ dy = cy - (apy + bpy) / 2;
1128
+ d2 = dx * dx + dy * dy;
1129
+ if (d2 < T2) {
1130
+ near = true;
1131
+ if (d2 < dMid) {
1132
+ dMid = d2;
1133
+ bestMid = { sx: (ax + bx) / 2, sy: (ay + by) / 2, type: "midpoint" };
1134
+ }
948
1135
  }
949
1136
  const sdx = bpx - apx, sdy = bpy - apy;
950
1137
  const len2 = sdx * sdx + sdy * sdy;
951
- let dLine2 = Infinity;
952
1138
  if (len2 > 0) {
953
1139
  const t = ((cx - apx) * sdx + (cy - apy) * sdy) / len2;
954
1140
  if (t > 0 && t < 1) {
955
- const px = apx + t * sdx, py = apy + t * sdy;
956
- dx = cx - px;
957
- dy = cy - py;
958
- dLine2 = dx * dx + dy * dy;
959
- if (dLine2 < bestD2) {
960
- const sx = seg.ax + t * (seg.bx - seg.ax);
961
- const sy = seg.ay + t * (seg.by - seg.ay);
962
- best = { sx, sy, type: "line", dir: segDir };
963
- bestD2 = dLine2;
1141
+ dx = cx - (apx + t * sdx);
1142
+ dy = cy - (apy + t * sdy);
1143
+ d2 = dx * dx + dy * dy;
1144
+ if (d2 < T2) {
1145
+ near = true;
1146
+ if (d2 < dLine) {
1147
+ dLine = d2;
1148
+ bestLine = { sx: ax + t * (bx - ax), sy: ay + t * (by - ay), type: "line" };
1149
+ }
964
1150
  }
965
1151
  }
966
1152
  }
967
- if (dEpA2 < candidateD2 || dEpB2 < candidateD2 || dLine2 < candidateD2) {
968
- cand.push({ seg, apx, apy, bpx, bpy });
1153
+ if (near) cand.push({ ax, ay, bx, by, apx, apy, bpx, bpy });
1154
+ }
1155
+ const nodes = s.nodes;
1156
+ if (nodes) {
1157
+ for (let i = 0; i < nodes.length; i += 2) {
1158
+ const x = nodes[i], y = nodes[i + 1];
1159
+ if (x < minX || x > maxX || y < minY || y > maxY) continue;
1160
+ const dx = cx - (o.x + mxx * x + myx * y);
1161
+ const dy = cy - (o.y + mxy * x + myy * y);
1162
+ const d2 = dx * dx + dy * dy;
1163
+ if (d2 < dPt) {
1164
+ dPt = d2;
1165
+ bestPt = { sx: x, sy: y, type: "node" };
1166
+ }
969
1167
  }
970
1168
  }
971
1169
  for (let i = 0; i < cand.length; i++) {
@@ -976,16 +1174,13 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
976
1174
  if (!ix) continue;
977
1175
  const dx = cx - ix.px, dy = cy - ix.py;
978
1176
  const d2 = dx * dx + dy * dy;
979
- if (d2 >= SNAP_PX * SNAP_PX) continue;
980
- if (!best || best.type !== "intersection" || d2 < bestD2) {
981
- const sx = A.seg.ax + ix.t * (A.seg.bx - A.seg.ax);
982
- const sy = A.seg.ay + ix.t * (A.seg.by - A.seg.ay);
983
- best = { sx, sy, type: "intersection" };
984
- bestD2 = d2;
985
- }
1177
+ if (d2 >= T2 || d2 >= dX) continue;
1178
+ dX = d2;
1179
+ bestX = { sx: A.ax + ix.t * (A.bx - A.ax), sy: A.ay + ix.t * (A.by - A.ay), type: "intersection" };
986
1180
  }
987
1181
  }
988
- return best;
1182
+ const top = bestX && bestPt ? dX < dPt - 1 ? bestX : bestPt : bestX ?? bestPt;
1183
+ return top ?? bestMid ?? bestLine;
989
1184
  };
990
1185
  const makeMarker = () => {
991
1186
  const el = document.createElement("div");
@@ -1026,16 +1221,23 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
1026
1221
  s.picks[1] = { x: raw.x, y: raw.y };
1027
1222
  }
1028
1223
  };
1224
+ const resolveMode = (a, b) => {
1225
+ const mode = measureModeRef.current;
1226
+ if (mode !== "auto") return mode;
1227
+ if (!a || !b) return "horizontal";
1228
+ return Math.abs(b.x - a.x) >= Math.abs(b.y - a.y) ? "horizontal" : "vertical";
1229
+ };
1029
1230
  const computeMainDimEnds = () => {
1030
1231
  const s = measureRef.current;
1031
1232
  const a = s.picks[0];
1032
1233
  const b = s.picks[1];
1033
- const mode = measureModeRef.current;
1234
+ const mode = resolveMode(a, b);
1235
+ const rawMode = measureModeRef.current;
1034
1236
  const fixed = measureFixedDistRef.current;
1035
- if (fixed !== null && Number.isFinite(fixed) && mode === "horizontal") {
1237
+ if (fixed !== null && Number.isFinite(fixed) && rawMode === "horizontal") {
1036
1238
  return { from: { x: b.x, y: a.y }, to: { x: b.x, y: b.y } };
1037
1239
  }
1038
- if (fixed !== null && Number.isFinite(fixed) && mode === "vertical") {
1240
+ if (fixed !== null && Number.isFinite(fixed) && rawMode === "vertical") {
1039
1241
  return { from: { x: a.x, y: b.y }, to: { x: b.x, y: b.y } };
1040
1242
  }
1041
1243
  if (mode === "horizontal") return { from: a, to: { x: b.x, y: a.y } };
@@ -1046,36 +1248,49 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
1046
1248
  const s = measureRef.current;
1047
1249
  if (!s) return;
1048
1250
  reconcileLockedPick();
1049
- const mode = measureModeRef.current;
1251
+ const rawMode = measureModeRef.current;
1050
1252
  const fixed = measureFixedDistRef.current;
1051
- const fixedActive = fixed !== null && Number.isFinite(fixed) && (mode === "horizontal" || mode === "vertical");
1253
+ const fixedActive = fixed !== null && Number.isFinite(fixed) && (rawMode === "horizontal" || rawMode === "vertical");
1052
1254
  if (s.markers[0]) positionMarker(s.markers[0], s.picks[0].x, s.picks[0].y);
1053
1255
  if (s.markers[1]) positionMarker(s.markers[1], s.picks[1].x, s.picks[1].y);
1054
- const refDir = mode === "horizontal" ? { dx: 1, dy: 0 } : mode === "vertical" ? { dx: 0, dy: 1 } : null;
1055
- if (refDir && s.picks.length >= 1 && s.refLine) {
1256
+ const drawRefLine = (el, dir, visible) => {
1257
+ if (!el) return;
1258
+ if (!visible || s.picks.length < 1) {
1259
+ el.style.display = "none";
1260
+ return;
1261
+ }
1056
1262
  const a = s.picks[0];
1057
1263
  const w = canvas.clientWidth, h = canvas.clientHeight;
1058
1264
  const screenSpan = Math.hypot(w, h) * 4;
1059
1265
  const ap = pxFromScene(a.x, a.y);
1060
- const probe = pxFromScene(a.x + refDir.dx, a.y + refDir.dy);
1266
+ const probe = pxFromScene(a.x + dir.dx, a.y + dir.dy);
1061
1267
  const pxLen = Math.hypot(probe.x - ap.x, probe.y - ap.y) || 1;
1062
1268
  const sceneStep = screenSpan / pxLen;
1063
- const x0 = a.x - refDir.dx * sceneStep;
1064
- const y0 = a.y - refDir.dy * sceneStep;
1065
- const x1 = a.x + refDir.dx * sceneStep;
1066
- const y1 = a.y + refDir.dy * sceneStep;
1067
- const p0 = pxFromScene(x0, y0);
1068
- const p1 = pxFromScene(x1, y1);
1069
- s.refLine.setAttribute("x1", String(p0.x));
1070
- s.refLine.setAttribute("y1", String(p0.y));
1071
- s.refLine.setAttribute("x2", String(p1.x));
1072
- s.refLine.setAttribute("y2", String(p1.y));
1073
- s.refLine.style.display = "";
1074
- } else if (s.refLine) {
1075
- s.refLine.style.display = "none";
1269
+ const p0 = pxFromScene(a.x - dir.dx * sceneStep, a.y - dir.dy * sceneStep);
1270
+ const p1 = pxFromScene(a.x + dir.dx * sceneStep, a.y + dir.dy * sceneStep);
1271
+ el.setAttribute("x1", String(p0.x));
1272
+ el.setAttribute("y1", String(p0.y));
1273
+ el.setAttribute("x2", String(p1.x));
1274
+ el.setAttribute("y2", String(p1.y));
1275
+ el.style.display = "";
1276
+ };
1277
+ let showH = rawMode === "horizontal";
1278
+ let showV = rawMode === "vertical";
1279
+ if (rawMode === "auto") {
1280
+ if (s.picks.length >= 2) {
1281
+ const r = resolveMode(s.picks[0], s.picks[1]);
1282
+ showH = r === "horizontal";
1283
+ showV = r === "vertical";
1284
+ } else {
1285
+ showH = true;
1286
+ showV = true;
1287
+ }
1076
1288
  }
1289
+ drawRefLine(s.refLine, { dx: 1, dy: 0 }, showH);
1290
+ drawRefLine(s.refLineV, { dx: 0, dy: 1 }, showV);
1077
1291
  if (s.picks.length === 2) {
1078
1292
  const a = s.picks[0], b = s.picks[1];
1293
+ const mode = resolveMode(a, b);
1079
1294
  const ends = computeMainDimEnds();
1080
1295
  const fp = pxFromScene(ends.from.x, ends.from.y);
1081
1296
  const tp = pxFromScene(ends.to.x, ends.to.y);
@@ -1212,28 +1427,46 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
1212
1427
  if (!s || s.picks.length !== 2) return;
1213
1428
  reconcileLockedPick();
1214
1429
  const a = s.picks[0], b = s.picks[1];
1215
- const mode = measureModeRef.current;
1430
+ const rawMode = measureModeRef.current;
1431
+ const mode = resolveMode(a, b);
1216
1432
  const fixed = measureFixedDistRef.current;
1217
- const fixedActive = fixed !== null && Number.isFinite(fixed) && (mode === "horizontal" || mode === "vertical");
1433
+ const fixedActive = fixed !== null && Number.isFinite(fixed) && (rawMode === "horizontal" || rawMode === "vertical");
1218
1434
  let dist;
1219
1435
  let suffix = "";
1220
- if (fixedActive && mode === "horizontal") {
1221
- dist = Math.abs(b.y - a.y);
1436
+ let echo;
1437
+ let shown = mode;
1438
+ const adx = Math.abs(b.x - a.x), ady = Math.abs(b.y - a.y);
1439
+ if (fixedActive && rawMode === "horizontal") {
1440
+ dist = ady;
1222
1441
  suffix = " \u2195";
1223
- } else if (fixedActive && mode === "vertical") {
1224
- dist = Math.abs(b.x - a.x);
1442
+ shown = "vertical";
1443
+ echo = `\u0394Y = ${formatMeasureDistance(ady)} with \u0394X locked to ${formatMeasureDistance(Math.abs(fixed))}`;
1444
+ } else if (fixedActive && rawMode === "vertical") {
1445
+ dist = adx;
1225
1446
  suffix = " \u2194";
1447
+ shown = "horizontal";
1448
+ echo = `\u0394X = ${formatMeasureDistance(adx)} with \u0394Y locked to ${formatMeasureDistance(Math.abs(fixed))}`;
1226
1449
  } else if (mode === "horizontal") {
1227
- dist = Math.abs(b.x - a.x);
1450
+ dist = adx;
1228
1451
  suffix = " \u2194";
1452
+ echo = `Linear dimension = ${formatMeasureDistance(adx)} (\u0394X)`;
1229
1453
  } else if (mode === "vertical") {
1230
- dist = Math.abs(b.y - a.y);
1454
+ dist = ady;
1231
1455
  suffix = " \u2195";
1456
+ echo = `Linear dimension = ${formatMeasureDistance(ady)} (\u0394Y)`;
1232
1457
  } else {
1233
- const dx = b.x - a.x, dy = b.y - a.y;
1234
- dist = Math.sqrt(dx * dx + dy * dy);
1458
+ dist = Math.hypot(b.x - a.x, b.y - a.y);
1459
+ echo = `Distance = ${formatMeasureDistance(dist)} \u0394X = ${formatMeasureDistance(adx)} \u0394Y = ${formatMeasureDistance(ady)}`;
1460
+ }
1461
+ if (!Number.isFinite(dist)) {
1462
+ setMeasureDistance(null);
1463
+ setMeasureResolved(null);
1464
+ if (s.label) s.label.style.opacity = "0";
1465
+ return;
1235
1466
  }
1236
1467
  setMeasureDistance(dist);
1468
+ setMeasureResolved(shown);
1469
+ setCmdEcho(echo);
1237
1470
  const label = ensureLabel();
1238
1471
  label.style.opacity = "1";
1239
1472
  label.textContent = `${formatMeasureDistance(dist)}${suffix}`;
@@ -1242,23 +1475,42 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
1242
1475
  recomputeLabel();
1243
1476
  updateOverlay();
1244
1477
  };
1478
+ const hideDimVisuals = (s) => {
1479
+ s.line.style.display = "none";
1480
+ if (s.fixedLine) s.fixedLine.style.display = "none";
1481
+ if (s.fixedLabel) s.fixedLabel.style.display = "none";
1482
+ if (s.extLineA) s.extLineA.style.display = "none";
1483
+ if (s.extLineB) s.extLineB.style.display = "none";
1484
+ if (s.label) s.label.style.opacity = "0";
1485
+ setMeasureDistance(null);
1486
+ setMeasureResolved(null);
1487
+ };
1488
+ const resetPicks = () => {
1489
+ const s = measureRef.current;
1490
+ if (!s) return;
1491
+ for (const m of s.markers) m.parentElement?.removeChild(m);
1492
+ s.markers = [];
1493
+ s.picks = [];
1494
+ s.rawSecondClick = null;
1495
+ if (s.refLine) s.refLine.style.display = "none";
1496
+ if (s.refLineV) s.refLineV.style.display = "none";
1497
+ hideDimVisuals(s);
1498
+ };
1499
+ measureResetRef.current = resetPicks;
1500
+ measureUndoRef.current = () => {
1501
+ const s = measureRef.current;
1502
+ if (!s || s.picks.length === 0) return;
1503
+ s.picks.pop();
1504
+ const m = s.markers.pop();
1505
+ m?.parentElement?.removeChild(m);
1506
+ s.rawSecondClick = null;
1507
+ hideDimVisuals(s);
1508
+ updateOverlay();
1509
+ };
1245
1510
  const doPick = (p) => {
1246
1511
  const s = measureRef.current;
1247
1512
  if (!s) return;
1248
- if (s.picks.length === 2) {
1249
- for (const m of s.markers) m.parentElement?.removeChild(m);
1250
- s.markers = [];
1251
- s.picks = [];
1252
- s.rawSecondClick = null;
1253
- s.line.style.display = "none";
1254
- if (s.fixedLine) s.fixedLine.style.display = "none";
1255
- if (s.fixedLabel) s.fixedLabel.style.display = "none";
1256
- if (s.refLine) s.refLine.style.display = "none";
1257
- if (s.extLineA) s.extLineA.style.display = "none";
1258
- if (s.extLineB) s.extLineB.style.display = "none";
1259
- if (s.label) s.label.style.opacity = "0";
1260
- setMeasureDistance(null);
1261
- }
1513
+ if (s.picks.length === 2) resetPicks();
1262
1514
  if (s.picks.length === 0) {
1263
1515
  s.picks.push({ x: p.x, y: p.y });
1264
1516
  s.markers.push(makeMarker());
@@ -1305,10 +1557,13 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
1305
1557
  canvas.removeEventListener("pointermove", handlePointerMove);
1306
1558
  window.removeEventListener("keydown", onKeyDown);
1307
1559
  measureRedrawRef.current = null;
1560
+ measureResetRef.current = null;
1561
+ measureUndoRef.current = null;
1308
1562
  teardown();
1309
1563
  setMeasureDistance(null);
1564
+ setMeasureResolved(null);
1310
1565
  };
1311
- }, [measureEnabled, loading, error]);
1566
+ }, [measureEnabled, loading, error, layersKey]);
1312
1567
  useEffect(() => {
1313
1568
  measureRedrawRef.current?.();
1314
1569
  }, [measureMode, measureFixedDist]);
@@ -1358,12 +1613,170 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
1358
1613
  } catch {
1359
1614
  }
1360
1615
  };
1616
+ const runCommand = (raw) => {
1617
+ const exec = raw.trim() || lastCmdRef.current;
1618
+ setCmdValue("");
1619
+ if (!exec) return;
1620
+ const cmd = exec.toLowerCase().split(/\s+/)[0];
1621
+ const remember = () => {
1622
+ lastCmdRef.current = exec;
1623
+ };
1624
+ const startMeasure = (mode, echo) => {
1625
+ setMeasureEnabled(true);
1626
+ setMeasureMode(mode);
1627
+ measureResetRef.current?.();
1628
+ setCmdEcho(echo);
1629
+ remember();
1630
+ };
1631
+ if (/^-?(\d+\.?\d*|\.\d+)$/.test(cmd)) {
1632
+ if (measureEnabled && (measureMode === "horizontal" || measureMode === "vertical")) {
1633
+ const n = parseFloat(cmd);
1634
+ if (n) {
1635
+ setMeasureFixedInput(cmd);
1636
+ setMeasureFixedDist(n);
1637
+ setCmdEcho(`\u0394${measureMode === "horizontal" ? "X" : "Y"} locked to ${formatMeasureDistance(Math.abs(n))} \u2014 the label shows the perpendicular distance`);
1638
+ } else {
1639
+ setMeasureFixedInput("");
1640
+ setMeasureFixedDist(null);
1641
+ setCmdEcho("Fixed distance cleared.");
1642
+ }
1643
+ } else {
1644
+ setCmdEcho("Fixed distances apply in H or V mode \u2014 type H or V first.");
1645
+ }
1646
+ return;
1647
+ }
1648
+ switch (cmd) {
1649
+ case "di":
1650
+ case "dist":
1651
+ case "mea":
1652
+ case "measuregeom":
1653
+ startMeasure("point", "DIST \u2014 click two points; straight-line distance (Esc to exit)");
1654
+ break;
1655
+ case "dim":
1656
+ case "dli":
1657
+ case "dimlin":
1658
+ case "dimlinear":
1659
+ startMeasure("auto", "DIMLINEAR \u2014 click two points; measures \u0394X or \u0394Y, whichever is larger (H / V to force)");
1660
+ break;
1661
+ // H / V / AUTO switch the axis without dropping existing picks —
1662
+ // same as clicking the pill.
1663
+ case "h":
1664
+ case "hor":
1665
+ case "horizontal":
1666
+ setMeasureEnabled(true);
1667
+ setMeasureMode("horizontal");
1668
+ setCmdEcho("Horizontal \u2014 \u0394X between the two picks");
1669
+ remember();
1670
+ break;
1671
+ case "v":
1672
+ case "ver":
1673
+ case "vertical":
1674
+ setMeasureEnabled(true);
1675
+ setMeasureMode("vertical");
1676
+ setCmdEcho("Vertical \u2014 \u0394Y between the two picks");
1677
+ remember();
1678
+ break;
1679
+ case "au":
1680
+ case "auto":
1681
+ setMeasureEnabled(true);
1682
+ setMeasureMode("auto");
1683
+ setCmdEcho("Auto (DIMLINEAR) \u2014 measures along the dominant axis of the two picks");
1684
+ remember();
1685
+ break;
1686
+ case "measure":
1687
+ setMeasureEnabled(!measureEnabled);
1688
+ setCmdEcho(measureEnabled ? "Measure off." : "Measure on \u2014 click two points.");
1689
+ remember();
1690
+ break;
1691
+ case "u":
1692
+ case "undo":
1693
+ measureUndoRef.current?.();
1694
+ setCmdEcho("Last pick removed.");
1695
+ remember();
1696
+ break;
1697
+ case "z":
1698
+ case "ze":
1699
+ case "zoom":
1700
+ case "fit":
1701
+ case "zoomextents":
1702
+ handleResetView();
1703
+ setCmdEcho("Zoom extents.");
1704
+ remember();
1705
+ break;
1706
+ case "la":
1707
+ case "layer":
1708
+ case "layers":
1709
+ setShowLayers((s) => !s);
1710
+ setCmdEcho("Layer panel toggled.");
1711
+ remember();
1712
+ break;
1713
+ case "off":
1714
+ case "clear":
1715
+ setMeasureFixedDist(null);
1716
+ setMeasureFixedInput("");
1717
+ measureResetRef.current?.();
1718
+ setCmdEcho("Measurement cleared.");
1719
+ remember();
1720
+ break;
1721
+ case "?":
1722
+ case "help":
1723
+ setCmdEcho(DXF_CMD_HELP);
1724
+ break;
1725
+ default: {
1726
+ const editCmd = DXF_EDIT_COMMANDS[cmd];
1727
+ if (editCmd) setCmdEcho(`${editCmd} is a drawing command \u2014 Preview is a read-only viewer. Measuring: DI, DIM, H, V (? for help)`);
1728
+ else setCmdEcho(`Unknown command "${cmd.toUpperCase()}" \u2014 try DI, DIM, H, V, Z, LA (? for help)`);
1729
+ }
1730
+ }
1731
+ };
1732
+ useEffect(() => {
1733
+ const onKey = (e) => {
1734
+ if (e.defaultPrevented || e.metaKey || e.ctrlKey || e.altKey) return;
1735
+ if (e.key.length !== 1 || e.key === " ") return;
1736
+ const t = e.target;
1737
+ if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable)) return;
1738
+ const root = rootRef.current;
1739
+ if (!root) return;
1740
+ const myModal = root.closest("[data-modal-id]");
1741
+ if (myModal && getActiveModalId() !== myModal.dataset.modalId) return;
1742
+ cmdInputRef.current?.focus();
1743
+ };
1744
+ window.addEventListener("keydown", onKey);
1745
+ return () => window.removeEventListener("keydown", onKey);
1746
+ }, []);
1747
+ const cmdValueRef = useRef(cmdValue);
1748
+ const measureEnabledRef = useRef(measureEnabled);
1749
+ useEffect(() => {
1750
+ cmdValueRef.current = cmdValue;
1751
+ }, [cmdValue]);
1752
+ useEffect(() => {
1753
+ measureEnabledRef.current = measureEnabled;
1754
+ }, [measureEnabled]);
1755
+ useEffect(() => {
1756
+ return registerModalEscapeInterceptor(() => {
1757
+ const root = rootRef.current;
1758
+ if (!root) return false;
1759
+ const myModal = root.closest("[data-modal-id]");
1760
+ if (!myModal || getActiveModalId() !== myModal.dataset.modalId) return false;
1761
+ if (cmdValueRef.current) {
1762
+ setCmdValue("");
1763
+ setCmdEcho("*Cancel*");
1764
+ return true;
1765
+ }
1766
+ if (measureEnabledRef.current) {
1767
+ setMeasureEnabled(false);
1768
+ setCmdEcho("Measure off.");
1769
+ return true;
1770
+ }
1771
+ return false;
1772
+ });
1773
+ }, []);
1361
1774
  const btn = "px-2 py-1 rounded hover:bg-gray-200 transition-colors text-gray-600 flex items-center gap-1";
1362
1775
  const colorHex = (n) => {
1363
1776
  if (typeof n !== "number") return "#999";
1364
1777
  return "#" + n.toString(16).padStart(6, "0");
1365
1778
  };
1366
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
1779
+ return /* @__PURE__ */ jsxs("div", { ref: rootRef, className: "flex flex-col h-full", children: [
1367
1780
  /* @__PURE__ */ jsxs(PanelActions, { children: [
1368
1781
  /* @__PURE__ */ jsxs(
1369
1782
  "button",
@@ -1391,7 +1804,7 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
1391
1804
  {
1392
1805
  onClick: () => setMeasureEnabled((m) => !m),
1393
1806
  className: btn + (measureEnabled ? " bg-gray-200" : ""),
1394
- title: measureEnabled ? "Stop measuring (Esc)" : "Measure distance \u2014 click two points on the drawing",
1807
+ title: measureEnabled ? "Stop measuring (Esc)" : "Measure distance \u2014 click two points on the drawing, or type DI / DIM below",
1395
1808
  children: [
1396
1809
  /* @__PURE__ */ jsxs("svg", { className: "h-3.5 w-3.5", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 1.5, children: [
1397
1810
  /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M3.75 14.25l6-6 6 6 4.5-4.5M9.75 8.25v3M12.75 11.25v3M15.75 14.25v3" }),
@@ -1406,10 +1819,10 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
1406
1819
  /* @__PURE__ */ jsx(
1407
1820
  "button",
1408
1821
  {
1409
- onClick: () => setMeasureMode("point"),
1410
- className: `px-2 transition-colors ${measureMode === "point" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
1411
- title: "Point \u2014 straight-line (Euclidean) distance between two picks",
1412
- children: "Point"
1822
+ onClick: () => setMeasureMode("auto"),
1823
+ className: `px-2 transition-colors ${measureMode === "auto" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
1824
+ title: "Auto \u2014 AutoCAD DIMLINEAR: measures \u0394X or \u0394Y, whichever is larger between the two picks",
1825
+ children: "Auto"
1413
1826
  }
1414
1827
  ),
1415
1828
  /* @__PURE__ */ jsx(
@@ -1429,6 +1842,15 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
1429
1842
  title: "Vertical \u2014 distance along the Y axis between two picks (AutoCAD DIMLINEAR vertical)",
1430
1843
  children: "V"
1431
1844
  }
1845
+ ),
1846
+ /* @__PURE__ */ jsx(
1847
+ "button",
1848
+ {
1849
+ onClick: () => setMeasureMode("point"),
1850
+ className: `px-2 transition-colors ${measureMode === "point" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
1851
+ title: "Point \u2014 straight-line (Euclidean) distance between two picks (AutoCAD DIST)",
1852
+ children: "Point"
1853
+ }
1432
1854
  )
1433
1855
  ] }),
1434
1856
  (measureMode === "horizontal" || measureMode === "vertical") && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
@@ -1471,10 +1893,10 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
1471
1893
  "div",
1472
1894
  {
1473
1895
  className: "px-2 py-1 text-[11px] font-mono font-semibold text-orange-600 bg-orange-50 border border-orange-200 rounded whitespace-nowrap",
1474
- title: measureMode === "horizontal" ? "Horizontal distance (\u0394x) between the two picked points" : measureMode === "vertical" ? "Vertical distance (\u0394y) between the two picked points" : "Straight-line distance between the two picked points",
1896
+ title: measureResolved === "horizontal" ? `Horizontal distance (\u0394x) between the two picked points${measureMode === "auto" ? " \u2014 Auto resolved to the X axis" : ""}` : measureResolved === "vertical" ? `Vertical distance (\u0394y) between the two picked points${measureMode === "auto" ? " \u2014 Auto resolved to the Y axis" : ""}` : "Straight-line distance between the two picked points",
1475
1897
  children: [
1476
1898
  formatMeasureDistance(measureDistance),
1477
- measureMode === "horizontal" ? " \u2194" : measureMode === "vertical" ? " \u2195" : ""
1899
+ measureResolved === "horizontal" ? " \u2194" : measureResolved === "vertical" ? " \u2195" : ""
1478
1900
  ]
1479
1901
  }
1480
1902
  ),
@@ -1530,10 +1952,50 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
1530
1952
  "Scroll to zoom"
1531
1953
  ] }),
1532
1954
  /* @__PURE__ */ jsx("span", { className: "text-white/40", children: "\u2022" }),
1533
- /* @__PURE__ */ jsx("span", { children: "Fit to reset" })
1955
+ /* @__PURE__ */ jsx("span", { children: "Fit to reset" }),
1956
+ /* @__PURE__ */ jsx("span", { className: "text-white/40", children: "\u2022" }),
1957
+ /* @__PURE__ */ jsxs("span", { children: [
1958
+ "Type ",
1959
+ /* @__PURE__ */ jsx("span", { className: "font-mono font-semibold", children: "DI" }),
1960
+ " / ",
1961
+ /* @__PURE__ */ jsx("span", { className: "font-mono font-semibold", children: "DIM" }),
1962
+ " to measure"
1963
+ ] })
1534
1964
  ] }),
1535
1965
  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" }),
1536
1966
  error && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center text-sm text-red-600 px-6 text-center", children: error })
1967
+ ] }),
1968
+ /* @__PURE__ */ jsxs("div", { className: "shrink-0 border-t border-gray-200 bg-gray-50", children: [
1969
+ cmdEcho && /* @__PURE__ */ jsx("div", { className: "px-2.5 pt-1 text-[11px] font-mono text-gray-500 truncate", title: cmdEcho, children: cmdEcho }),
1970
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 px-2.5 h-7", children: [
1971
+ /* @__PURE__ */ jsx("span", { className: "text-[11px] font-mono font-semibold text-gray-400 select-none", children: ">" }),
1972
+ /* @__PURE__ */ jsx(
1973
+ "input",
1974
+ {
1975
+ ref: cmdInputRef,
1976
+ value: cmdValue,
1977
+ onChange: (e) => setCmdValue(e.target.value),
1978
+ onKeyDown: (e) => {
1979
+ if (e.key === "Enter" || e.key === " ") {
1980
+ e.preventDefault();
1981
+ runCommand(cmdValue);
1982
+ } else if (e.key === "Escape") {
1983
+ if (cmdValue) {
1984
+ e.stopPropagation();
1985
+ setCmdValue("");
1986
+ setCmdEcho("*Cancel*");
1987
+ } else {
1988
+ e.target.blur();
1989
+ }
1990
+ }
1991
+ },
1992
+ placeholder: "Type a command \u2014 DI, DIM, H, V, Z, LA (? for help)",
1993
+ spellCheck: false,
1994
+ autoComplete: "off",
1995
+ className: "flex-1 min-w-0 bg-transparent text-[11px] font-mono text-gray-700 focus:outline-none placeholder:text-gray-300"
1996
+ }
1997
+ )
1998
+ ] })
1537
1999
  ] })
1538
2000
  ] });
1539
2001
  }
@@ -1557,8 +2019,7 @@ function hexToRgb(OV, hex) {
1557
2019
  return new OV.RGBColor(n >> 16 & 255, n >> 8 & 255, n & 255);
1558
2020
  }
1559
2021
  function formatMeasureDistance(mm) {
1560
- if (mm >= 1e3) return `${(mm / 1e3).toFixed(2)} m`;
1561
- if (mm >= 10) return `${mm.toFixed(1)} mm`;
2022
+ if (mm >= 1e3) return `${(mm / 1e3).toFixed(3)} m`;
1562
2023
  return `${mm.toFixed(2)} mm`;
1563
2024
  }
1564
2025
  function StepPanel({ url, filename, onDownload, onEmail }) {
@@ -2838,5 +3299,5 @@ function ImagePanel({ url, filename, onDownload, onEmail }) {
2838
3299
  }
2839
3300
 
2840
3301
  export { Preview, setPdfPreview };
2841
- //# sourceMappingURL=chunk-CGSN2MCD.js.map
2842
- //# sourceMappingURL=chunk-CGSN2MCD.js.map
3302
+ //# sourceMappingURL=chunk-7CT2Y2MB.js.map
3303
+ //# sourceMappingURL=chunk-7CT2Y2MB.js.map