react-os-shell 0.3.17 → 0.3.18

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.
@@ -513,8 +513,18 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
513
513
  const [showLayers, setShowLayers] = useState(false);
514
514
  const [showHint, setShowHint] = useState(true);
515
515
  const [measureEnabled, setMeasureEnabled] = useState(false);
516
- const [measureMode, setMeasureMode] = useState("point");
516
+ const [measureMode, setMeasureMode] = useState("horizontal");
517
+ const [measureAutocad, setMeasureAutocad] = useState(true);
517
518
  const [measureDistance, setMeasureDistance] = useState(null);
519
+ const measureModeRef = useRef(measureMode);
520
+ const measureAutocadRef = useRef(measureAutocad);
521
+ useEffect(() => {
522
+ measureModeRef.current = measureMode;
523
+ }, [measureMode]);
524
+ useEffect(() => {
525
+ measureAutocadRef.current = measureAutocad;
526
+ }, [measureAutocad]);
527
+ const measureRedrawRef = useRef(null);
518
528
  const measureRef = useRef(null);
519
529
  useEffect(() => {
520
530
  let cancelled = false;
@@ -641,6 +651,21 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
641
651
  containerRef.current.appendChild(overlay);
642
652
  const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
643
653
  svg.setAttribute("style", "position:absolute;inset:0;width:100%;height:100%;pointer-events:none;");
654
+ const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
655
+ const marker = document.createElementNS("http://www.w3.org/2000/svg", "marker");
656
+ marker.setAttribute("id", "dxf-measure-arrow");
657
+ marker.setAttribute("viewBox", "0 0 10 10");
658
+ marker.setAttribute("refX", "9");
659
+ marker.setAttribute("refY", "5");
660
+ marker.setAttribute("markerWidth", "6");
661
+ marker.setAttribute("markerHeight", "6");
662
+ marker.setAttribute("orient", "auto-start-reverse");
663
+ const arrowPath = document.createElementNS("http://www.w3.org/2000/svg", "path");
664
+ arrowPath.setAttribute("d", "M 0 0 L 10 5 L 0 10 z");
665
+ arrowPath.setAttribute("fill", "#ff8800");
666
+ marker.appendChild(arrowPath);
667
+ defs.appendChild(marker);
668
+ svg.appendChild(defs);
644
669
  overlay.appendChild(svg);
645
670
  const refLineEl = document.createElementNS("http://www.w3.org/2000/svg", "line");
646
671
  refLineEl.setAttribute("stroke", "#ff8800");
@@ -649,6 +674,18 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
649
674
  refLineEl.setAttribute("opacity", "0.55");
650
675
  refLineEl.style.display = "none";
651
676
  svg.appendChild(refLineEl);
677
+ const extLineA = document.createElementNS("http://www.w3.org/2000/svg", "line");
678
+ extLineA.setAttribute("stroke", "#ff8800");
679
+ extLineA.setAttribute("stroke-width", "1");
680
+ extLineA.setAttribute("opacity", "0.85");
681
+ extLineA.style.display = "none";
682
+ svg.appendChild(extLineA);
683
+ const extLineB = document.createElementNS("http://www.w3.org/2000/svg", "line");
684
+ extLineB.setAttribute("stroke", "#ff8800");
685
+ extLineB.setAttribute("stroke-width", "1");
686
+ extLineB.setAttribute("opacity", "0.85");
687
+ extLineB.style.display = "none";
688
+ svg.appendChild(extLineB);
652
689
  const lineEl = document.createElementNS("http://www.w3.org/2000/svg", "line");
653
690
  lineEl.setAttribute("stroke", "#ff8800");
654
691
  lineEl.setAttribute("stroke-width", "1.5");
@@ -660,11 +697,12 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
660
697
  overlay.appendChild(snapEl);
661
698
  measureRef.current = {
662
699
  picks: [],
663
- lineDir: null,
664
700
  overlay,
665
701
  svg,
666
702
  line: lineEl,
667
703
  refLine: refLineEl,
704
+ extLineA,
705
+ extLineB,
668
706
  markers: [],
669
707
  label: null,
670
708
  snap: snapEl,
@@ -773,18 +811,11 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
773
811
  const s = measureRef.current;
774
812
  const a = s.picks[0];
775
813
  const b = s.picks[1];
776
- if (measureMode === "perp" && s.lineDir) {
777
- const d = s.lineDir;
778
- const ax = a.x, ay = a.y;
779
- const t = (b.x - ax) * d.dx + (b.y - ay) * d.dy;
780
- const fx = ax + d.dx * t;
781
- const fy = ay + d.dy * t;
782
- return { from: { x: fx, y: fy }, to: { x: b.x, y: b.y } };
783
- }
784
- if (measureMode === "horizontal") {
814
+ const mode = measureModeRef.current;
815
+ if (mode === "horizontal") {
785
816
  return { from: a, to: { x: b.x, y: a.y } };
786
817
  }
787
- if (measureMode === "vertical") {
818
+ if (mode === "vertical") {
788
819
  return { from: a, to: { x: a.x, y: b.y } };
789
820
  }
790
821
  return { from: a, to: b };
@@ -792,9 +823,11 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
792
823
  const updateOverlay = () => {
793
824
  const s = measureRef.current;
794
825
  if (!s) return;
826
+ const mode = measureModeRef.current;
827
+ const autocad = measureAutocadRef.current;
795
828
  if (s.markers[0]) positionMarker(s.markers[0], s.picks[0].x, s.picks[0].y);
796
829
  if (s.markers[1]) positionMarker(s.markers[1], s.picks[1].x, s.picks[1].y);
797
- const refDir = measureMode === "horizontal" ? { dx: 1, dy: 0 } : measureMode === "vertical" ? { dx: 0, dy: 1 } : measureMode === "perp" ? s.lineDir : null;
830
+ const refDir = mode === "horizontal" ? { dx: 1, dy: 0 } : mode === "vertical" ? { dx: 0, dy: 1 } : null;
798
831
  if (refDir && s.picks.length >= 1 && s.refLine) {
799
832
  const a = s.picks[0];
800
833
  const w = canvas.clientWidth, h = canvas.clientHeight;
@@ -818,6 +851,7 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
818
851
  s.refLine.style.display = "none";
819
852
  }
820
853
  if (s.picks.length === 2) {
854
+ const a = s.picks[0], b = s.picks[1];
821
855
  const ends = computeRenderedEnds();
822
856
  const fp = pxFromScene(ends.from.x, ends.from.y);
823
857
  const tp = pxFromScene(ends.to.x, ends.to.y);
@@ -826,6 +860,37 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
826
860
  s.line.setAttribute("x2", String(tp.x));
827
861
  s.line.setAttribute("y2", String(tp.y));
828
862
  s.line.style.display = "";
863
+ if (autocad) {
864
+ s.line.setAttribute("marker-start", "url(#dxf-measure-arrow)");
865
+ s.line.setAttribute("marker-end", "url(#dxf-measure-arrow)");
866
+ } else {
867
+ s.line.removeAttribute("marker-start");
868
+ s.line.removeAttribute("marker-end");
869
+ }
870
+ const extA = s.extLineA;
871
+ const extB = s.extLineB;
872
+ if (autocad && mode === "horizontal" && Math.abs(b.y - a.y) > 1e-9) {
873
+ const p = pxFromScene(b.x, a.y);
874
+ const q = pxFromScene(b.x, b.y);
875
+ extB.setAttribute("x1", String(p.x));
876
+ extB.setAttribute("y1", String(p.y));
877
+ extB.setAttribute("x2", String(q.x));
878
+ extB.setAttribute("y2", String(q.y));
879
+ extB.style.display = "";
880
+ extA.style.display = "none";
881
+ } else if (autocad && mode === "vertical" && Math.abs(b.x - a.x) > 1e-9) {
882
+ const p = pxFromScene(a.x, b.y);
883
+ const q = pxFromScene(b.x, b.y);
884
+ extB.setAttribute("x1", String(p.x));
885
+ extB.setAttribute("y1", String(p.y));
886
+ extB.setAttribute("x2", String(q.x));
887
+ extB.setAttribute("y2", String(q.y));
888
+ extB.style.display = "";
889
+ extA.style.display = "none";
890
+ } else {
891
+ extA.style.display = "none";
892
+ extB.style.display = "none";
893
+ }
829
894
  if (s.label) {
830
895
  const cx = (fp.x + tp.x) / 2;
831
896
  const cy = (fp.y + tp.y) / 2;
@@ -836,6 +901,8 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
836
901
  }
837
902
  } else {
838
903
  s.line.style.display = "none";
904
+ if (s.extLineA) s.extLineA.style.display = "none";
905
+ if (s.extLineB) s.extLineB.style.display = "none";
839
906
  }
840
907
  };
841
908
  const DRAG_TOL = 4;
@@ -881,10 +948,35 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
881
948
  if (dragging || elapsed > DRAG_TIME) return;
882
949
  const raw = ev?.detail?.position;
883
950
  if (!raw) return;
884
- const useSnap = lastSnap && Math.hypot(downX - canvas.getBoundingClientRect().width, 0) >= 0;
885
- const picked = useSnap && lastSnap ? { x: lastSnap.sx, y: lastSnap.sy, snapType: lastSnap.type, lineDir: lastSnap.dir } : { x: raw.x, y: raw.y, lineDir: void 0 };
951
+ const picked = lastSnap ? { x: lastSnap.sx, y: lastSnap.sy } : { x: raw.x, y: raw.y };
886
952
  doPick(picked);
887
953
  };
954
+ const recomputeLabel = () => {
955
+ const s = measureRef.current;
956
+ if (!s || s.picks.length !== 2) return;
957
+ const a = s.picks[0], b = s.picks[1];
958
+ const mode = measureModeRef.current;
959
+ let dist;
960
+ let suffix = "";
961
+ if (mode === "horizontal") {
962
+ dist = Math.abs(b.x - a.x);
963
+ suffix = " \u2194";
964
+ } else if (mode === "vertical") {
965
+ dist = Math.abs(b.y - a.y);
966
+ suffix = " \u2195";
967
+ } else {
968
+ const dx = b.x - a.x, dy = b.y - a.y;
969
+ dist = Math.sqrt(dx * dx + dy * dy);
970
+ }
971
+ setMeasureDistance(dist);
972
+ const label = ensureLabel();
973
+ label.style.opacity = "1";
974
+ label.textContent = `${formatMeasureDistance(dist)}${suffix}`;
975
+ };
976
+ measureRedrawRef.current = () => {
977
+ recomputeLabel();
978
+ updateOverlay();
979
+ };
888
980
  const doPick = (p) => {
889
981
  const s = measureRef.current;
890
982
  if (!s) return;
@@ -892,51 +984,17 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
892
984
  for (const m of s.markers) m.parentElement?.removeChild(m);
893
985
  s.markers = [];
894
986
  s.picks = [];
895
- s.lineDir = null;
896
987
  s.line.style.display = "none";
897
988
  if (s.refLine) s.refLine.style.display = "none";
989
+ if (s.extLineA) s.extLineA.style.display = "none";
990
+ if (s.extLineB) s.extLineB.style.display = "none";
898
991
  if (s.label) s.label.style.opacity = "0";
899
992
  setMeasureDistance(null);
900
993
  }
901
- if (measureMode === "perp" && s.picks.length === 0) {
902
- if (!p.lineDir) {
903
- const label = ensureLabel();
904
- label.style.display = "";
905
- label.style.opacity = "1";
906
- label.style.left = `${pxFromScene(p.x, p.y).x}px`;
907
- label.style.top = `${pxFromScene(p.x, p.y).y - 18}px`;
908
- label.textContent = "\u22A5: snap to a line or corner first";
909
- setTimeout(() => {
910
- if (s.label && s.picks.length === 0) s.label.style.opacity = "0";
911
- }, 1500);
912
- return;
913
- }
914
- s.lineDir = p.lineDir;
915
- }
916
994
  s.picks.push({ x: p.x, y: p.y });
917
995
  s.markers.push(makeMarker());
918
996
  if (s.picks.length === 2) {
919
- const a = s.picks[0], b = s.picks[1];
920
- let dist;
921
- let suffix = "";
922
- if (measureMode === "perp" && s.lineDir) {
923
- const dx = b.x - a.x, dy = b.y - a.y;
924
- dist = Math.abs(dx * s.lineDir.dy - dy * s.lineDir.dx);
925
- suffix = " \u22A5";
926
- } else if (measureMode === "horizontal") {
927
- dist = Math.abs(b.x - a.x);
928
- suffix = " \u2194";
929
- } else if (measureMode === "vertical") {
930
- dist = Math.abs(b.y - a.y);
931
- suffix = " \u2195";
932
- } else {
933
- const dx = b.x - a.x, dy = b.y - a.y;
934
- dist = Math.sqrt(dx * dx + dy * dy);
935
- }
936
- setMeasureDistance(dist);
937
- const label = ensureLabel();
938
- label.style.opacity = "1";
939
- label.textContent = `${formatMeasureDistance(dist)}${suffix}`;
997
+ recomputeLabel();
940
998
  }
941
999
  updateOverlay();
942
1000
  };
@@ -974,10 +1032,14 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
974
1032
  }
975
1033
  canvas.removeEventListener("pointermove", handlePointerMove);
976
1034
  window.removeEventListener("keydown", onKeyDown);
1035
+ measureRedrawRef.current = null;
977
1036
  teardown();
978
1037
  setMeasureDistance(null);
979
1038
  };
980
- }, [measureEnabled, measureMode, loading, error]);
1039
+ }, [measureEnabled, loading, error]);
1040
+ useEffect(() => {
1041
+ measureRedrawRef.current?.();
1042
+ }, [measureMode, measureAutocad]);
981
1043
  const toggleLayer = (name) => {
982
1044
  setLayers((prev) => prev.map((l) => {
983
1045
  if (l.name !== name) return l;
@@ -1067,52 +1129,60 @@ function DxfPanel({ url, filename, onDownload, onEmail }) {
1067
1129
  ]
1068
1130
  }
1069
1131
  ),
1070
- measureEnabled && /* @__PURE__ */ jsxs("div", { className: "flex items-stretch h-7 rounded border border-gray-200 overflow-hidden text-[11px] font-semibold", children: [
1132
+ measureEnabled && /* @__PURE__ */ jsxs(Fragment, { children: [
1071
1133
  /* @__PURE__ */ jsx(
1072
1134
  "button",
1073
1135
  {
1074
- onClick: () => setMeasureMode("point"),
1075
- className: `px-2 transition-colors ${measureMode === "point" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
1076
- title: "Point \u2014 straight-line distance between two picks",
1077
- children: "Point"
1078
- }
1079
- ),
1080
- /* @__PURE__ */ jsx(
1081
- "button",
1082
- {
1083
- onClick: () => setMeasureMode("perp"),
1084
- className: `px-2 transition-colors ${measureMode === "perp" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
1085
- title: "Perpendicular \u2014 click on a line first, then pick a point. Reports the perpendicular distance.",
1136
+ onClick: () => {
1137
+ setMeasureAutocad((prev) => {
1138
+ const next = !prev;
1139
+ if (next && measureMode === "point") setMeasureMode("horizontal");
1140
+ return next;
1141
+ });
1142
+ },
1143
+ className: btn + (measureAutocad ? " bg-orange-100 text-orange-700" : ""),
1144
+ title: measureAutocad ? "AutoCAD dim style is ON \u2014 extension lines + arrows" : "AutoCAD dim style is OFF \u2014 plain line. Click to turn on (also switches to H if you're in Point).",
1086
1145
  children: "\u22A5"
1087
1146
  }
1088
1147
  ),
1089
- /* @__PURE__ */ jsx(
1090
- "button",
1091
- {
1092
- onClick: () => setMeasureMode("horizontal"),
1093
- className: `px-2 transition-colors ${measureMode === "horizontal" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
1094
- title: "Horizontal \u2014 distance along the X axis between two picks (AutoCAD DIMLINEAR horizontal)",
1095
- children: "H"
1096
- }
1097
- ),
1098
- /* @__PURE__ */ jsx(
1099
- "button",
1100
- {
1101
- onClick: () => setMeasureMode("vertical"),
1102
- className: `px-2 transition-colors ${measureMode === "vertical" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
1103
- title: "Vertical \u2014 distance along the Y axis between two picks (AutoCAD DIMLINEAR vertical)",
1104
- children: "V"
1105
- }
1106
- )
1148
+ /* @__PURE__ */ jsxs("div", { className: "flex items-stretch h-7 rounded border border-gray-200 overflow-hidden text-[11px] font-semibold", children: [
1149
+ /* @__PURE__ */ jsx(
1150
+ "button",
1151
+ {
1152
+ onClick: () => setMeasureMode("point"),
1153
+ className: `px-2 transition-colors ${measureMode === "point" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
1154
+ title: "Point \u2014 straight-line (Euclidean) distance between two picks",
1155
+ children: "Point"
1156
+ }
1157
+ ),
1158
+ /* @__PURE__ */ jsx(
1159
+ "button",
1160
+ {
1161
+ onClick: () => setMeasureMode("horizontal"),
1162
+ className: `px-2 transition-colors ${measureMode === "horizontal" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
1163
+ title: "Horizontal \u2014 distance along the X axis between two picks (AutoCAD DIMLINEAR horizontal)",
1164
+ children: "H"
1165
+ }
1166
+ ),
1167
+ /* @__PURE__ */ jsx(
1168
+ "button",
1169
+ {
1170
+ onClick: () => setMeasureMode("vertical"),
1171
+ className: `px-2 transition-colors ${measureMode === "vertical" ? "bg-orange-500 text-white" : "bg-white text-gray-600 hover:bg-gray-50"}`,
1172
+ title: "Vertical \u2014 distance along the Y axis between two picks (AutoCAD DIMLINEAR vertical)",
1173
+ children: "V"
1174
+ }
1175
+ )
1176
+ ] })
1107
1177
  ] }),
1108
1178
  measureEnabled && measureDistance !== null && /* @__PURE__ */ jsxs(
1109
1179
  "div",
1110
1180
  {
1111
1181
  className: "px-2 py-1 text-[11px] font-mono font-semibold text-orange-600 bg-orange-50 border border-orange-200 rounded whitespace-nowrap",
1112
- title: measureMode === "perp" ? "Perpendicular distance from second pick to first line" : 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",
1182
+ 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",
1113
1183
  children: [
1114
1184
  formatMeasureDistance(measureDistance),
1115
- measureMode === "perp" ? " \u22A5" : measureMode === "horizontal" ? " \u2194" : measureMode === "vertical" ? " \u2195" : ""
1185
+ measureMode === "horizontal" ? " \u2194" : measureMode === "vertical" ? " \u2195" : ""
1116
1186
  ]
1117
1187
  }
1118
1188
  ),
@@ -2476,5 +2546,5 @@ function ImagePanel({ url, filename, onDownload, onEmail }) {
2476
2546
  }
2477
2547
 
2478
2548
  export { Preview, setPdfPreview };
2479
- //# sourceMappingURL=chunk-TB72OMUU.js.map
2480
- //# sourceMappingURL=chunk-TB72OMUU.js.map
2549
+ //# sourceMappingURL=chunk-HADXZNYO.js.map
2550
+ //# sourceMappingURL=chunk-HADXZNYO.js.map