@aspicio/react
Advanced tools
+6
-0
@@ -29,2 +29,8 @@ import { CSSProperties, ReactElement } from "react"; | ||
| onHoverLayer?: (layer: string | null) => void; | ||
| /** | ||
| * Show the built-in Download control (SVG / PNG export). Default: true. | ||
| * Set false to hide it in embeds; `toSVG()`/`toPNG()` remain callable via | ||
| * the forwarded ref. | ||
| */ | ||
| showDownload?: boolean; | ||
| } | ||
@@ -31,0 +37,0 @@ /** |
+106
-2
@@ -463,3 +463,3 @@ import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from "react"; | ||
| */ | ||
| const DxfPreview = forwardRef(function DxfPreview({ src, srcUrl, options, className, style, onLoaded, onError, onViewer, onHoverLayer }, ref) { | ||
| const DxfPreview = forwardRef(function DxfPreview({ src, srcUrl, options, className, style, onLoaded, onError, onViewer, onHoverLayer, showDownload = true }, ref) { | ||
| const containerRef = useRef(null); | ||
@@ -551,5 +551,109 @@ const [viewer, setViewer] = useState(null); | ||
| ...style | ||
| } | ||
| }, | ||
| children: showDownload && viewer ? /* @__PURE__ */ jsx(DownloadControl, { | ||
| viewer, | ||
| filename: downloadName(srcUrl) | ||
| }) : null | ||
| }); | ||
| }); | ||
| function downloadName(srcUrl) { | ||
| return (srcUrl?.split(/[?#]/)[0].split("/").pop() ?? "").replace(/\.dxf$/i, "") || "drawing"; | ||
| } | ||
| function triggerDownload(href, filename) { | ||
| const a = document.createElement("a"); | ||
| a.href = href; | ||
| a.download = filename; | ||
| document.body.appendChild(a); | ||
| a.click(); | ||
| a.remove(); | ||
| } | ||
| /** A small overlay button that exports the drawing as SVG or PNG. */ | ||
| function DownloadControl({ viewer, filename }) { | ||
| const [open, setOpen] = useState(false); | ||
| const save = (format) => { | ||
| if (format === "svg") { | ||
| const blob = new Blob([viewer.toSVG({ background: aspicioTokens.canvas })], { type: "image/svg+xml" }); | ||
| const url = URL.createObjectURL(blob); | ||
| triggerDownload(url, `${filename}.svg`); | ||
| setTimeout(() => URL.revokeObjectURL(url), 1e3); | ||
| } else triggerDownload(viewer.toPNG({ background: 1447965 }), `${filename}.png`); | ||
| setOpen(false); | ||
| }; | ||
| const item = { | ||
| display: "block", | ||
| width: "100%", | ||
| padding: "6px 10px", | ||
| background: "transparent", | ||
| border: "none", | ||
| borderRadius: 3, | ||
| color: aspicioTokens.text, | ||
| font: `12px ${aspicioTokens.fontMono}`, | ||
| letterSpacing: "0.06em", | ||
| textAlign: "left", | ||
| cursor: "pointer" | ||
| }; | ||
| return /* @__PURE__ */ jsxs("div", { | ||
| style: { | ||
| position: "absolute", | ||
| top: 10, | ||
| right: 10, | ||
| zIndex: 5 | ||
| }, | ||
| children: [/* @__PURE__ */ jsx("button", { | ||
| type: "button", | ||
| "aria-label": "Download", | ||
| onClick: () => setOpen((o) => !o), | ||
| style: { | ||
| display: "inline-flex", | ||
| alignItems: "center", | ||
| justifyContent: "center", | ||
| width: 32, | ||
| height: 32, | ||
| background: "rgba(25,28,34,.9)", | ||
| border: `1px solid ${aspicioTokens.hairline2}`, | ||
| borderRadius: 4, | ||
| color: aspicioTokens.text2, | ||
| cursor: "pointer" | ||
| }, | ||
| children: /* @__PURE__ */ jsxs("svg", { | ||
| width: "15", | ||
| height: "15", | ||
| viewBox: "0 0 24 24", | ||
| fill: "none", | ||
| stroke: "currentColor", | ||
| strokeWidth: "1.8", | ||
| strokeLinecap: "round", | ||
| strokeLinejoin: "round", | ||
| children: [ | ||
| /* @__PURE__ */ jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), | ||
| /* @__PURE__ */ jsx("path", { d: "M7 10l5 5 5-5" }), | ||
| /* @__PURE__ */ jsx("path", { d: "M12 15V3" }) | ||
| ] | ||
| }) | ||
| }), open ? /* @__PURE__ */ jsxs("div", { | ||
| style: { | ||
| position: "absolute", | ||
| top: "calc(100% + 6px)", | ||
| right: 0, | ||
| minWidth: 116, | ||
| padding: 4, | ||
| background: aspicioTokens.panel, | ||
| border: `1px solid ${aspicioTokens.hairline2}`, | ||
| borderRadius: 5, | ||
| boxShadow: "0 12px 32px rgba(0,0,0,.5)" | ||
| }, | ||
| children: [/* @__PURE__ */ jsx("button", { | ||
| type: "button", | ||
| style: item, | ||
| onClick: () => save("svg"), | ||
| children: "SVG" | ||
| }), /* @__PURE__ */ jsx("button", { | ||
| type: "button", | ||
| style: item, | ||
| onClick: () => save("png"), | ||
| children: "PNG" | ||
| })] | ||
| }) : null] | ||
| }); | ||
| } | ||
| //#endregion | ||
@@ -556,0 +660,0 @@ //#region src/DxfEmbed.tsx |
+2
-2
| { | ||
| "name": "@aspicio/react", | ||
| "version": "0.2.0", | ||
| "version": "0.3.0", | ||
| "description": "React bindings for the Aspicio DXF viewer.", | ||
@@ -33,3 +33,3 @@ "homepage": "https://github.com/frontsail-ai/aspicio/tree/main/packages/react#readme", | ||
| "dependencies": { | ||
| "@aspicio/core": "^0.2.0" | ||
| "@aspicio/core": "^0.3.0" | ||
| }, | ||
@@ -36,0 +36,0 @@ "devDependencies": { |
+3
-0
@@ -78,2 +78,5 @@ # @aspicio/react | ||
| Changing either loads the new document; stale in-flight loads are ignored. | ||
| - A built-in **Download** control (SVG / PNG export) shows by default; pass | ||
| `showDownload={false}` to hide it. The viewer's `toSVG()` / `toPNG()` stay | ||
| callable via the ref regardless. | ||
| - Camera state is deliberately not React state — subscribe to the `render` | ||
@@ -80,0 +83,0 @@ event on the viewer if you need to display it. |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
30432
12.28%733
16.53%85
3.66%0
-100%+ Added
- Removed
Updated