@treasuryspatial/viewer-react 0.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AccessAssetsMenu.d.ts +15 -0
- package/dist/AccessAssetsMenu.d.ts.map +1 -0
- package/dist/AccessAssetsMenu.js +56 -0
- package/dist/AssetUploadCard.d.ts +19 -0
- package/dist/AssetUploadCard.d.ts.map +1 -0
- package/dist/AssetUploadCard.js +157 -0
- package/dist/ViewControls.d.ts +33 -0
- package/dist/ViewControls.d.ts.map +1 -0
- package/dist/ViewControls.js +250 -0
- package/dist/ViewerCanvas.d.ts +13 -0
- package/dist/ViewerCanvas.d.ts.map +1 -0
- package/dist/ViewerCanvas.js +135 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/package.json +35 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type AccessAssetsItem = {
|
|
2
|
+
key: string;
|
|
3
|
+
label: string;
|
|
4
|
+
onClick: () => void | Promise<void>;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export type AccessAssetsMenuProps = {
|
|
8
|
+
label?: string;
|
|
9
|
+
items: AccessAssetsItem[];
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
className?: string;
|
|
12
|
+
fullWidth?: boolean;
|
|
13
|
+
};
|
|
14
|
+
export default function AccessAssetsMenu({ label, items, disabled, className, fullWidth, }: AccessAssetsMenuProps): import("react/jsx-runtime").JSX.Element | null;
|
|
15
|
+
//# sourceMappingURL=AccessAssetsMenu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AccessAssetsMenu.d.ts","sourceRoot":"","sources":["../src/AccessAssetsMenu.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,KAAuB,EACvB,KAAK,EACL,QAAgB,EAChB,SAAS,EACT,SAAiB,GAClB,EAAE,qBAAqB,kDA0FvB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
export default function AccessAssetsMenu({ label = "access assets", items, disabled = false, className, fullWidth = false, }) {
|
|
4
|
+
const containerRef = useRef(null);
|
|
5
|
+
const [open, setOpen] = useState(false);
|
|
6
|
+
const resolvedItems = useMemo(() => items.filter((item) => item && item.label && typeof item.onClick === "function"), [items]);
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!open)
|
|
9
|
+
return;
|
|
10
|
+
const handler = (event) => {
|
|
11
|
+
if (!containerRef.current)
|
|
12
|
+
return;
|
|
13
|
+
if (!containerRef.current.contains(event.target)) {
|
|
14
|
+
setOpen(false);
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
window.addEventListener("mousedown", handler);
|
|
18
|
+
return () => window.removeEventListener("mousedown", handler);
|
|
19
|
+
}, [open]);
|
|
20
|
+
if (!resolvedItems.length)
|
|
21
|
+
return null;
|
|
22
|
+
return (_jsxs("div", { ref: containerRef, className: className, style: { width: "100%", maxWidth: fullWidth ? "none" : "220px" }, children: [_jsxs("button", { type: "button", onClick: () => setOpen((prev) => !prev), disabled: disabled, style: {
|
|
23
|
+
width: "100%",
|
|
24
|
+
borderRadius: "8px",
|
|
25
|
+
border: "1px solid #e2e8f0",
|
|
26
|
+
background: "#ffffff",
|
|
27
|
+
padding: "10px 12px",
|
|
28
|
+
fontSize: "12px",
|
|
29
|
+
textTransform: "lowercase",
|
|
30
|
+
color: "#475569",
|
|
31
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
32
|
+
opacity: disabled ? 0.6 : 1,
|
|
33
|
+
display: "flex",
|
|
34
|
+
alignItems: "center",
|
|
35
|
+
justifyContent: "space-between",
|
|
36
|
+
gap: "8px",
|
|
37
|
+
}, children: [_jsx("span", { children: label }), _jsx("span", { style: { fontSize: "12px", transform: open ? "rotate(180deg)" : "rotate(0deg)", transition: "transform 0.2s ease" }, children: "\u25BE" })] }), open ? (_jsx("div", { style: { marginTop: "10px", display: "flex", flexDirection: "column", gap: "8px" }, children: resolvedItems.map((item) => (_jsx("button", { type: "button", onClick: async () => {
|
|
38
|
+
try {
|
|
39
|
+
await item.onClick();
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
setOpen(false);
|
|
43
|
+
}
|
|
44
|
+
}, disabled: disabled || item.disabled, style: {
|
|
45
|
+
width: "100%",
|
|
46
|
+
borderRadius: "8px",
|
|
47
|
+
border: "1px dashed #e2e8f0",
|
|
48
|
+
padding: "10px 12px",
|
|
49
|
+
fontSize: "12px",
|
|
50
|
+
textTransform: "lowercase",
|
|
51
|
+
color: "#64748b",
|
|
52
|
+
background: "transparent",
|
|
53
|
+
cursor: disabled || item.disabled ? "not-allowed" : "pointer",
|
|
54
|
+
opacity: disabled || item.disabled ? 0.5 : 1,
|
|
55
|
+
}, children: item.label }, item.key))) })) : null] }));
|
|
56
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type UploadResult } from "@treasuryspatial/viewer-kit";
|
|
2
|
+
export type AssetUploadCardProps = {
|
|
3
|
+
disabled?: boolean;
|
|
4
|
+
apiRoute?: string;
|
|
5
|
+
rhinoApiRoute?: string;
|
|
6
|
+
maxBytes?: number;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
preview?: Record<string, unknown>;
|
|
9
|
+
allowEmptyGeometry?: boolean;
|
|
10
|
+
accept?: string;
|
|
11
|
+
onSolved: (result: UploadResult) => void | Promise<void>;
|
|
12
|
+
onClear?: () => void;
|
|
13
|
+
className?: string;
|
|
14
|
+
title?: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
minimal?: boolean;
|
|
17
|
+
};
|
|
18
|
+
export default function AssetUploadCard({ disabled, apiRoute, rhinoApiRoute, maxBytes, timeoutMs, preview, allowEmptyGeometry, accept, onSolved, onClear, className, title, description, minimal, }: AssetUploadCardProps): import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
//# sourceMappingURL=AssetUploadCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AssetUploadCard.d.ts","sourceRoot":"","sources":["../src/AssetUploadCard.tsx"],"names":[],"mappings":"AAEA,OAAO,EAML,KAAK,YAAY,EAClB,MAAM,6BAA6B,CAAC;AAErC,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAUF,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,EACtC,QAAQ,EACR,QAA0B,EAC1B,aAAgC,EAChC,QAAyC,EACzC,SAA2C,EAC3C,OAAO,EACP,kBAAkB,EAClB,MAAuB,EACvB,QAAQ,EACR,OAAO,EACP,SAAS,EACT,KAAsB,EACtB,WAAsF,EACtF,OAAe,GAChB,EAAE,oBAAoB,2CA6NtB"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { DEFAULT_UPLOAD_LIMITS, detectUploadKind, loadMeshFile, solveGrasshopperFile, solveRhino3dmFile, } from "@treasuryspatial/viewer-kit";
|
|
4
|
+
const DEFAULT_ACCEPT = ".gh,.ghx,.3dm,.obj,.fbx,.gltf,.glb,.stl,.ply";
|
|
5
|
+
const formatBytes = (bytes) => {
|
|
6
|
+
if (!Number.isFinite(bytes))
|
|
7
|
+
return "";
|
|
8
|
+
const mb = bytes / (1024 * 1024);
|
|
9
|
+
return `${mb.toFixed(0)}MB`;
|
|
10
|
+
};
|
|
11
|
+
export default function AssetUploadCard({ disabled, apiRoute = "/api/gh-solve", rhinoApiRoute = "/api/rhino-3dm", maxBytes = DEFAULT_UPLOAD_LIMITS.maxBytes, timeoutMs = DEFAULT_UPLOAD_LIMITS.timeoutMs, preview, allowEmptyGeometry, accept = DEFAULT_ACCEPT, onSolved, onClear, className, title = "Asset Upload", description = "Upload .gh/.ghx/.3dm or mesh assets (OBJ/FBX/GLTF/STL/PLY) to preview.", minimal = false, }) {
|
|
12
|
+
const [busy, setBusy] = useState(false);
|
|
13
|
+
const [lastFileName, setLastFileName] = useState(null);
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const [dragActive, setDragActive] = useState(false);
|
|
16
|
+
const [statusLine, setStatusLine] = useState("ready");
|
|
17
|
+
const [statusTone, setStatusTone] = useState("muted");
|
|
18
|
+
const [hovered, setHovered] = useState(false);
|
|
19
|
+
const statusText = useMemo(() => (busy ? "processing..." : statusLine), [busy, statusLine]);
|
|
20
|
+
const updateStatus = (fileName, detail, tone = "muted") => {
|
|
21
|
+
setStatusLine(`${fileName} · ${detail}`);
|
|
22
|
+
setStatusTone(tone);
|
|
23
|
+
};
|
|
24
|
+
const uploadAndSolve = async (file) => {
|
|
25
|
+
if (disabled || busy)
|
|
26
|
+
return;
|
|
27
|
+
const kind = detectUploadKind(file);
|
|
28
|
+
if (kind === "unknown") {
|
|
29
|
+
setError("unsupported file type");
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (typeof maxBytes === "number" && maxBytes > 0 && file.size > maxBytes) {
|
|
33
|
+
setError(`file exceeds ${formatBytes(maxBytes)} limit`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
setBusy(true);
|
|
37
|
+
setError(null);
|
|
38
|
+
setLastFileName(file.name);
|
|
39
|
+
updateStatus(file.name, "starting");
|
|
40
|
+
try {
|
|
41
|
+
let result;
|
|
42
|
+
if (kind === "gh" || kind === "ghx") {
|
|
43
|
+
const solved = await solveGrasshopperFile(file, {
|
|
44
|
+
apiRoute,
|
|
45
|
+
preview,
|
|
46
|
+
allowEmptyGeometry,
|
|
47
|
+
onStatus: (detail) => updateStatus(file.name, detail),
|
|
48
|
+
});
|
|
49
|
+
result = { ...solved, file };
|
|
50
|
+
}
|
|
51
|
+
else if (kind === "3dm") {
|
|
52
|
+
const solved = await solveRhino3dmFile(file, {
|
|
53
|
+
apiRoute: rhinoApiRoute,
|
|
54
|
+
onStatus: (detail) => updateStatus(file.name, detail),
|
|
55
|
+
});
|
|
56
|
+
result = { ...solved, file };
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
const solved = await loadMeshFile(file, {
|
|
60
|
+
maxBytes,
|
|
61
|
+
timeoutMs,
|
|
62
|
+
onStatus: (detail) => updateStatus(file.name, detail),
|
|
63
|
+
});
|
|
64
|
+
result = { ...solved, file };
|
|
65
|
+
}
|
|
66
|
+
await onSolved(result);
|
|
67
|
+
updateStatus(file.name, "completed", "success");
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
const message = e?.message ?? "failed to process upload";
|
|
71
|
+
setError(message);
|
|
72
|
+
updateStatus(file.name, `error: ${message}`, "error");
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
setBusy(false);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
const inputId = "asset-upload-input";
|
|
79
|
+
const typesLabel = accept.replace(/\./g, "").toUpperCase().replace(/,/g, ", ");
|
|
80
|
+
const showStatusLine = minimal && (busy || statusLine !== "ready" || error);
|
|
81
|
+
return (_jsxs("section", { className: className, style: { display: "flex", flexDirection: "column", gap: "12px" }, children: [!minimal ? (_jsxs("div", { style: { display: "flex", justifyContent: "space-between", gap: "12px" }, children: [_jsxs("div", { children: [_jsx("h3", { style: { fontSize: "12px", fontWeight: 600, letterSpacing: "0.08em", textTransform: "uppercase" }, children: title }), _jsx("p", { style: { marginTop: "6px", fontSize: "12px", lineHeight: 1.5 }, children: description })] }), onClear ? (_jsx("button", { type: "button", onClick: onClear, disabled: disabled || busy, style: { height: "32px" }, children: "clear" })) : null] })) : null, _jsxs("div", { role: "button", tabIndex: 0, onDragEnter: (event) => {
|
|
82
|
+
event.preventDefault();
|
|
83
|
+
event.stopPropagation();
|
|
84
|
+
if (disabled || busy)
|
|
85
|
+
return;
|
|
86
|
+
setDragActive(true);
|
|
87
|
+
}, onDragOver: (event) => {
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
event.stopPropagation();
|
|
90
|
+
if (disabled || busy)
|
|
91
|
+
return;
|
|
92
|
+
setDragActive(true);
|
|
93
|
+
}, onDragLeave: (event) => {
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
event.stopPropagation();
|
|
96
|
+
setDragActive(false);
|
|
97
|
+
}, onDrop: (event) => {
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
event.stopPropagation();
|
|
100
|
+
setDragActive(false);
|
|
101
|
+
const dropped = event.dataTransfer.files?.[0];
|
|
102
|
+
if (!dropped)
|
|
103
|
+
return;
|
|
104
|
+
void uploadAndSolve(dropped);
|
|
105
|
+
}, onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), onClick: () => {
|
|
106
|
+
if (disabled || busy)
|
|
107
|
+
return;
|
|
108
|
+
const input = document.getElementById(inputId);
|
|
109
|
+
input?.click();
|
|
110
|
+
}, style: {
|
|
111
|
+
borderRadius: "18px",
|
|
112
|
+
border: "1px dashed rgba(49, 143, 78, 0.45)",
|
|
113
|
+
padding: "18px 18px",
|
|
114
|
+
minHeight: "92px",
|
|
115
|
+
background: dragActive ? "rgba(49, 143, 78, 0.08)" : "rgba(255,255,255,0.9)",
|
|
116
|
+
cursor: disabled || busy ? "not-allowed" : "pointer",
|
|
117
|
+
opacity: disabled || busy ? 0.6 : 1,
|
|
118
|
+
boxShadow: "0 10px 24px rgba(15, 23, 42, 0.06)",
|
|
119
|
+
}, children: [minimal ? (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: [_jsx("div", { style: {
|
|
120
|
+
fontSize: "11px",
|
|
121
|
+
fontWeight: 500,
|
|
122
|
+
letterSpacing: "0.04em",
|
|
123
|
+
color: hovered ? "rgb(49, 143, 78)" : "#7b8794",
|
|
124
|
+
opacity: hovered ? 0.95 : 0.65,
|
|
125
|
+
}, children: typesLabel }), showStatusLine ? (_jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px", fontSize: "11px", opacity: 0.7 }, children: [_jsxs("span", { style: { display: "inline-flex", gap: "4px" }, children: [_jsx("span", { className: "upload-dot" }), _jsx("span", { className: "upload-dot" }), _jsx("span", { className: "upload-dot" })] }), _jsx("span", { style: { textTransform: "lowercase" }, children: error ? `error: ${error}` : statusText })] })) : null] })) : (_jsxs(_Fragment, { children: [_jsx("div", { style: { fontSize: "13px", fontWeight: 600, color: "#0f172a" }, children: lastFileName ? `Loaded ${lastFileName}` : "Drop a file to upload" }), _jsx("div", { style: { marginTop: "6px", fontSize: "12px", color: "#64748b" }, children: ".gh \u00B7 .ghx \u00B7 .3dm \u00B7 OBJ \u00B7 FBX \u00B7 GLTF \u00B7 STL \u00B7 PLY" }), _jsx("div", { style: { marginTop: "8px", fontSize: "11px", opacity: 0.7, textTransform: "lowercase" }, children: statusText })] })), error ? (_jsx("div", { style: { marginTop: "6px", fontSize: "11px", color: "#b91c1c" }, children: error })) : null] }), _jsx("style", { children: `
|
|
126
|
+
.upload-dot {
|
|
127
|
+
width: 5px;
|
|
128
|
+
height: 5px;
|
|
129
|
+
border-radius: 999px;
|
|
130
|
+
background: rgb(49, 143, 78);
|
|
131
|
+
opacity: 0.35;
|
|
132
|
+
animation: upload-dot-bounce 1s ease-in-out infinite;
|
|
133
|
+
}
|
|
134
|
+
.upload-dot:nth-child(2) {
|
|
135
|
+
animation-delay: 0.15s;
|
|
136
|
+
}
|
|
137
|
+
.upload-dot:nth-child(3) {
|
|
138
|
+
animation-delay: 0.3s;
|
|
139
|
+
}
|
|
140
|
+
@keyframes upload-dot-bounce {
|
|
141
|
+
0%, 100% {
|
|
142
|
+
transform: translateY(0);
|
|
143
|
+
opacity: 0.35;
|
|
144
|
+
}
|
|
145
|
+
50% {
|
|
146
|
+
transform: translateY(-3px);
|
|
147
|
+
opacity: 0.85;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
` }), _jsx("input", { id: inputId, type: "file", accept: accept, style: { display: "none" }, onChange: (event) => {
|
|
151
|
+
const file = event.target.files?.[0];
|
|
152
|
+
if (!file)
|
|
153
|
+
return;
|
|
154
|
+
void uploadAndSolve(file);
|
|
155
|
+
event.currentTarget.value = "";
|
|
156
|
+
} })] }));
|
|
157
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type LightingOption = {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
summary?: string;
|
|
5
|
+
};
|
|
6
|
+
export type MaterialOption = {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
summary?: string;
|
|
10
|
+
swatches?: string[];
|
|
11
|
+
};
|
|
12
|
+
export type SkyOption = {
|
|
13
|
+
id: string;
|
|
14
|
+
label: string;
|
|
15
|
+
description?: string;
|
|
16
|
+
};
|
|
17
|
+
export type ViewControlsProps = {
|
|
18
|
+
activeLightingPreset: string;
|
|
19
|
+
onLightingChange: (preset: string) => void;
|
|
20
|
+
materialOptions?: MaterialOption[];
|
|
21
|
+
activeMaterialPreset?: string;
|
|
22
|
+
onMaterialChange?: (preset: string) => void;
|
|
23
|
+
onCameraViewChange: (view: string) => void;
|
|
24
|
+
skyOptions: SkyOption[];
|
|
25
|
+
activeSkyId: string;
|
|
26
|
+
onSkyChange: (skyId: string) => void;
|
|
27
|
+
lightingOptions: LightingOption[];
|
|
28
|
+
veilsEnabled?: boolean;
|
|
29
|
+
onToggleVeils?: () => void;
|
|
30
|
+
onResetRender?: () => void;
|
|
31
|
+
};
|
|
32
|
+
export default function ViewControls({ activeLightingPreset, onLightingChange, materialOptions, activeMaterialPreset, onMaterialChange, onCameraViewChange, skyOptions, activeSkyId, onSkyChange, lightingOptions, veilsEnabled, onToggleVeils, onResetRender, }: ViewControlsProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
//# sourceMappingURL=ViewControls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ViewControls.d.ts","sourceRoot":"","sources":["../src/ViewControls.tsx"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gBAAgB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,eAAe,CAAC,EAAE,cAAc,EAAE,CAAC;IACnC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,kBAAkB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B,CAAC;AAyDF,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,oBAAoB,EACpB,gBAAgB,EAChB,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,WAAW,EACX,WAAW,EACX,eAAe,EACf,YAAmB,EACnB,aAAa,EACb,aAAa,GACd,EAAE,iBAAiB,2CAwYnB"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
const cardStyle = {
|
|
4
|
+
position: "relative",
|
|
5
|
+
background: "rgba(255,255,255,0.92)",
|
|
6
|
+
border: "1px solid #e2e8f0",
|
|
7
|
+
borderRadius: "10px",
|
|
8
|
+
boxShadow: "0 12px 24px rgba(15,23,42,0.12)",
|
|
9
|
+
overflow: "visible",
|
|
10
|
+
};
|
|
11
|
+
const labelStyle = {
|
|
12
|
+
fontSize: "12px",
|
|
13
|
+
textTransform: "lowercase",
|
|
14
|
+
color: "#475569",
|
|
15
|
+
};
|
|
16
|
+
const veilStyle = {
|
|
17
|
+
pointerEvents: "none",
|
|
18
|
+
position: "absolute",
|
|
19
|
+
inset: 0,
|
|
20
|
+
display: "flex",
|
|
21
|
+
alignItems: "center",
|
|
22
|
+
justifyContent: "center",
|
|
23
|
+
fontSize: "11px",
|
|
24
|
+
textTransform: "uppercase",
|
|
25
|
+
letterSpacing: "0.1em",
|
|
26
|
+
color: "rgba(100,116,139,0.7)",
|
|
27
|
+
};
|
|
28
|
+
const dropdownStyle = {
|
|
29
|
+
position: "absolute",
|
|
30
|
+
bottom: "calc(100% + 8px)",
|
|
31
|
+
left: 0,
|
|
32
|
+
background: "rgba(255,255,255,0.98)",
|
|
33
|
+
border: "1px solid #e2e8f0",
|
|
34
|
+
borderRadius: "10px",
|
|
35
|
+
padding: "8px",
|
|
36
|
+
boxShadow: "0 16px 24px rgba(15,23,42,0.14)",
|
|
37
|
+
minWidth: "220px",
|
|
38
|
+
zIndex: 60,
|
|
39
|
+
};
|
|
40
|
+
const swatchRowStyle = {
|
|
41
|
+
display: "flex",
|
|
42
|
+
gap: "6px",
|
|
43
|
+
marginTop: "6px",
|
|
44
|
+
};
|
|
45
|
+
const swatchStyle = (color) => ({
|
|
46
|
+
width: "12px",
|
|
47
|
+
height: "12px",
|
|
48
|
+
borderRadius: "999px",
|
|
49
|
+
background: color,
|
|
50
|
+
border: "1px solid rgba(15,23,42,0.1)",
|
|
51
|
+
});
|
|
52
|
+
export default function ViewControls({ activeLightingPreset, onLightingChange, materialOptions, activeMaterialPreset, onMaterialChange, onCameraViewChange, skyOptions, activeSkyId, onSkyChange, lightingOptions, veilsEnabled = true, onToggleVeils, onResetRender, }) {
|
|
53
|
+
const lightingMenuRef = useRef(null);
|
|
54
|
+
const materialMenuRef = useRef(null);
|
|
55
|
+
const skyMenuRef = useRef(null);
|
|
56
|
+
const [lightingExpanded, setLightingExpanded] = useState(false);
|
|
57
|
+
const [materialExpanded, setMaterialExpanded] = useState(false);
|
|
58
|
+
const [skyExpanded, setSkyExpanded] = useState(false);
|
|
59
|
+
const [lightingHover, setLightingHover] = useState(false);
|
|
60
|
+
const [materialHover, setMaterialHover] = useState(false);
|
|
61
|
+
const [skyHover, setSkyHover] = useState(false);
|
|
62
|
+
const [viewHover, setViewHover] = useState(false);
|
|
63
|
+
const activeLighting = useMemo(() => lightingOptions.find((option) => option.id === activeLightingPreset) ?? lightingOptions[0], [lightingOptions, activeLightingPreset]);
|
|
64
|
+
const activeSky = useMemo(() => skyOptions.find((option) => option.id === activeSkyId) ?? skyOptions[0], [skyOptions, activeSkyId]);
|
|
65
|
+
const activeMaterial = useMemo(() => materialOptions?.find((option) => option.id === activeMaterialPreset) ??
|
|
66
|
+
materialOptions?.[0], [materialOptions, activeMaterialPreset]);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (!lightingExpanded && !skyExpanded && !materialExpanded)
|
|
69
|
+
return;
|
|
70
|
+
const onMouseDown = (event) => {
|
|
71
|
+
const target = event.target;
|
|
72
|
+
if (!target)
|
|
73
|
+
return;
|
|
74
|
+
if (lightingMenuRef.current?.contains(target))
|
|
75
|
+
return;
|
|
76
|
+
if (materialMenuRef.current?.contains(target))
|
|
77
|
+
return;
|
|
78
|
+
if (skyMenuRef.current?.contains(target))
|
|
79
|
+
return;
|
|
80
|
+
setLightingExpanded(false);
|
|
81
|
+
setMaterialExpanded(false);
|
|
82
|
+
setSkyExpanded(false);
|
|
83
|
+
};
|
|
84
|
+
const onKeyDown = (event) => {
|
|
85
|
+
if (event.key !== "Escape")
|
|
86
|
+
return;
|
|
87
|
+
setLightingExpanded(false);
|
|
88
|
+
setMaterialExpanded(false);
|
|
89
|
+
setSkyExpanded(false);
|
|
90
|
+
};
|
|
91
|
+
window.addEventListener("mousedown", onMouseDown);
|
|
92
|
+
window.addEventListener("keydown", onKeyDown);
|
|
93
|
+
return () => {
|
|
94
|
+
window.removeEventListener("mousedown", onMouseDown);
|
|
95
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
96
|
+
};
|
|
97
|
+
}, [lightingExpanded, materialExpanded, skyExpanded]);
|
|
98
|
+
return (_jsxs("div", { style: { display: "flex", gap: "10px", alignItems: "flex-end" }, children: [_jsx("div", { ref: lightingMenuRef, children: _jsxs("div", { style: cardStyle, onMouseEnter: () => setLightingHover(true), onMouseLeave: () => setLightingHover(false), children: [lightingExpanded ? (_jsx("div", { style: dropdownStyle, children: lightingOptions.map((option) => (_jsxs("button", { type: "button", onClick: () => {
|
|
99
|
+
onLightingChange(option.id);
|
|
100
|
+
setLightingExpanded(false);
|
|
101
|
+
}, style: {
|
|
102
|
+
width: "100%",
|
|
103
|
+
border: "none",
|
|
104
|
+
borderRadius: "8px",
|
|
105
|
+
padding: "8px 10px",
|
|
106
|
+
textAlign: "left",
|
|
107
|
+
background: option.id === activeLightingPreset ? "rgba(49,67,144,0.12)" : "transparent",
|
|
108
|
+
color: option.id === activeLightingPreset ? "#314390" : "#475569",
|
|
109
|
+
cursor: "pointer",
|
|
110
|
+
display: "flex",
|
|
111
|
+
flexDirection: "column",
|
|
112
|
+
gap: "2px",
|
|
113
|
+
fontSize: "12px",
|
|
114
|
+
textTransform: "lowercase",
|
|
115
|
+
}, children: [_jsx("span", { children: option.label }), option.summary ? (_jsx("span", { style: { fontSize: "10px", opacity: 0.7 }, children: option.summary })) : null] }, option.id))) })) : null, _jsxs("button", { type: "button", onClick: () => {
|
|
116
|
+
setLightingExpanded((prev) => !prev);
|
|
117
|
+
setSkyExpanded(false);
|
|
118
|
+
}, style: {
|
|
119
|
+
display: "flex",
|
|
120
|
+
alignItems: "center",
|
|
121
|
+
justifyContent: "space-between",
|
|
122
|
+
gap: "10px",
|
|
123
|
+
padding: "8px 12px",
|
|
124
|
+
border: "none",
|
|
125
|
+
background: "transparent",
|
|
126
|
+
width: "100%",
|
|
127
|
+
cursor: "pointer",
|
|
128
|
+
}, children: [_jsx("span", { style: {
|
|
129
|
+
...labelStyle,
|
|
130
|
+
opacity: !veilsEnabled || lightingExpanded || lightingHover ? 1 : 0,
|
|
131
|
+
transition: "opacity 0.2s ease",
|
|
132
|
+
}, children: activeLighting?.label ?? activeLightingPreset }), _jsx("span", { style: {
|
|
133
|
+
fontSize: "11px",
|
|
134
|
+
color: "#64748b",
|
|
135
|
+
opacity: !veilsEnabled || lightingExpanded || lightingHover ? 1 : 0,
|
|
136
|
+
transition: "opacity 0.2s ease",
|
|
137
|
+
}, children: lightingExpanded ? "▼" : "▲" })] }), veilsEnabled && !lightingExpanded && !lightingHover ? _jsx("div", { style: veilStyle, children: "LIGHTING" }) : null] }) }), materialOptions && materialOptions.length && onMaterialChange ? (_jsx("div", { ref: materialMenuRef, children: _jsxs("div", { style: cardStyle, onMouseEnter: () => setMaterialHover(true), onMouseLeave: () => setMaterialHover(false), children: [materialExpanded ? (_jsx("div", { style: dropdownStyle, children: materialOptions.map((option) => (_jsxs("button", { type: "button", onClick: () => {
|
|
138
|
+
onMaterialChange(option.id);
|
|
139
|
+
setMaterialExpanded(false);
|
|
140
|
+
}, style: {
|
|
141
|
+
width: "100%",
|
|
142
|
+
border: "none",
|
|
143
|
+
borderRadius: "8px",
|
|
144
|
+
padding: "8px 10px",
|
|
145
|
+
textAlign: "left",
|
|
146
|
+
background: option.id === activeMaterialPreset ? "rgba(49,67,144,0.12)" : "transparent",
|
|
147
|
+
color: option.id === activeMaterialPreset ? "#314390" : "#475569",
|
|
148
|
+
cursor: "pointer",
|
|
149
|
+
display: "flex",
|
|
150
|
+
flexDirection: "column",
|
|
151
|
+
gap: "2px",
|
|
152
|
+
fontSize: "12px",
|
|
153
|
+
textTransform: "lowercase",
|
|
154
|
+
}, children: [_jsx("span", { children: option.label }), option.summary ? (_jsx("span", { style: { fontSize: "10px", opacity: 0.7 }, children: option.summary })) : null, option.swatches && option.swatches.length ? (_jsx("div", { style: swatchRowStyle, children: option.swatches.map((color, index) => (_jsx("span", { style: swatchStyle(color) }, `${option.id}-${index}`))) })) : null] }, option.id))) })) : null, _jsxs("button", { type: "button", onClick: () => {
|
|
155
|
+
setMaterialExpanded((prev) => !prev);
|
|
156
|
+
setLightingExpanded(false);
|
|
157
|
+
setSkyExpanded(false);
|
|
158
|
+
}, style: {
|
|
159
|
+
display: "flex",
|
|
160
|
+
alignItems: "center",
|
|
161
|
+
justifyContent: "space-between",
|
|
162
|
+
gap: "10px",
|
|
163
|
+
padding: "8px 12px",
|
|
164
|
+
border: "none",
|
|
165
|
+
background: "transparent",
|
|
166
|
+
width: "100%",
|
|
167
|
+
cursor: "pointer",
|
|
168
|
+
}, children: [_jsx("span", { style: {
|
|
169
|
+
...labelStyle,
|
|
170
|
+
opacity: !veilsEnabled || materialExpanded || materialHover ? 1 : 0,
|
|
171
|
+
transition: "opacity 0.2s ease",
|
|
172
|
+
}, children: activeMaterial?.label ?? activeMaterialPreset ?? "materials" }), _jsx("span", { style: {
|
|
173
|
+
fontSize: "11px",
|
|
174
|
+
color: "#64748b",
|
|
175
|
+
opacity: !veilsEnabled || materialExpanded || materialHover ? 1 : 0,
|
|
176
|
+
transition: "opacity 0.2s ease",
|
|
177
|
+
}, children: materialExpanded ? "▼" : "▲" })] }), veilsEnabled && !materialExpanded && !materialHover ? _jsx("div", { style: veilStyle, children: "MATERIALS" }) : null] }) })) : null, _jsxs("div", { style: { ...cardStyle, display: "flex" }, onMouseEnter: () => setViewHover(true), onMouseLeave: () => setViewHover(false), children: [_jsx("button", { type: "button", onClick: () => onCameraViewChange("interior"), style: {
|
|
178
|
+
padding: "8px 12px",
|
|
179
|
+
border: "none",
|
|
180
|
+
borderRight: "1px solid #e2e8f0",
|
|
181
|
+
background: "transparent",
|
|
182
|
+
cursor: "pointer",
|
|
183
|
+
minWidth: "84px",
|
|
184
|
+
...labelStyle,
|
|
185
|
+
color: !veilsEnabled || viewHover ? "#475569" : "transparent",
|
|
186
|
+
}, children: "interior" }), _jsx("button", { type: "button", onClick: () => onCameraViewChange("iso"), style: {
|
|
187
|
+
padding: "8px 12px",
|
|
188
|
+
border: "none",
|
|
189
|
+
background: "transparent",
|
|
190
|
+
cursor: "pointer",
|
|
191
|
+
minWidth: "84px",
|
|
192
|
+
...labelStyle,
|
|
193
|
+
color: !veilsEnabled || viewHover ? "#475569" : "transparent",
|
|
194
|
+
}, children: "perspective" }), veilsEnabled && !viewHover ? _jsx("div", { style: veilStyle, children: "VIEW" }) : null] }), skyOptions.length ? (_jsx("div", { ref: skyMenuRef, children: _jsxs("div", { style: cardStyle, onMouseEnter: () => setSkyHover(true), onMouseLeave: () => setSkyHover(false), children: [skyExpanded ? (_jsx("div", { style: dropdownStyle, children: skyOptions.map((option) => (_jsxs("button", { type: "button", onClick: () => {
|
|
195
|
+
onSkyChange(option.id);
|
|
196
|
+
setSkyExpanded(false);
|
|
197
|
+
}, style: {
|
|
198
|
+
width: "100%",
|
|
199
|
+
border: "none",
|
|
200
|
+
borderRadius: "8px",
|
|
201
|
+
padding: "8px 10px",
|
|
202
|
+
textAlign: "left",
|
|
203
|
+
background: option.id === activeSkyId ? "rgba(49,67,144,0.12)" : "transparent",
|
|
204
|
+
color: option.id === activeSkyId ? "#314390" : "#475569",
|
|
205
|
+
cursor: "pointer",
|
|
206
|
+
display: "flex",
|
|
207
|
+
flexDirection: "column",
|
|
208
|
+
gap: "2px",
|
|
209
|
+
fontSize: "12px",
|
|
210
|
+
textTransform: "lowercase",
|
|
211
|
+
}, children: [_jsx("span", { children: option.label }), option.description ? (_jsx("span", { style: { fontSize: "10px", opacity: 0.7 }, children: option.description })) : null] }, option.id))) })) : null, _jsxs("button", { type: "button", onClick: () => {
|
|
212
|
+
setSkyExpanded((prev) => !prev);
|
|
213
|
+
setLightingExpanded(false);
|
|
214
|
+
}, style: {
|
|
215
|
+
display: "flex",
|
|
216
|
+
alignItems: "center",
|
|
217
|
+
justifyContent: "space-between",
|
|
218
|
+
gap: "10px",
|
|
219
|
+
padding: "8px 12px",
|
|
220
|
+
border: "none",
|
|
221
|
+
background: "transparent",
|
|
222
|
+
width: "100%",
|
|
223
|
+
cursor: "pointer",
|
|
224
|
+
}, children: [_jsx("span", { style: {
|
|
225
|
+
...labelStyle,
|
|
226
|
+
opacity: !veilsEnabled || skyExpanded || skyHover ? 1 : 0,
|
|
227
|
+
transition: "opacity 0.2s ease",
|
|
228
|
+
}, children: activeSky?.label ?? "preset dome" }), _jsx("span", { style: {
|
|
229
|
+
fontSize: "11px",
|
|
230
|
+
color: "#64748b",
|
|
231
|
+
opacity: !veilsEnabled || skyExpanded || skyHover ? 1 : 0,
|
|
232
|
+
transition: "opacity 0.2s ease",
|
|
233
|
+
}, children: skyExpanded ? "▼" : "▲" })] }), veilsEnabled && !skyExpanded && !skyHover ? _jsx("div", { style: veilStyle, children: "BACKGROUND" }) : null] }) })) : null, onToggleVeils ? (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "6px", alignSelf: "stretch" }, children: [onResetRender ? (_jsx("button", { type: "button", onClick: onResetRender, "aria-label": "Reset render settings", style: {
|
|
234
|
+
border: "none",
|
|
235
|
+
background: "transparent",
|
|
236
|
+
fontSize: "11px",
|
|
237
|
+
textTransform: "uppercase",
|
|
238
|
+
letterSpacing: "0.12em",
|
|
239
|
+
color: "#64748b",
|
|
240
|
+
cursor: "pointer",
|
|
241
|
+
}, children: "RESET" })) : null, _jsx("button", { type: "button", onClick: onToggleVeils, "aria-label": "Toggle veils", style: {
|
|
242
|
+
border: "none",
|
|
243
|
+
background: "transparent",
|
|
244
|
+
fontSize: "11px",
|
|
245
|
+
textTransform: "uppercase",
|
|
246
|
+
letterSpacing: "0.14em",
|
|
247
|
+
color: "#64748b",
|
|
248
|
+
cursor: "pointer",
|
|
249
|
+
}, children: "VEILS" })] })) : null] }));
|
|
250
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PresetCatalog, ViewerCreateOptions, ViewerHandle } from "@treasuryspatial/viewer-kit";
|
|
2
|
+
export type ViewerCanvasProps = {
|
|
3
|
+
presetId?: string;
|
|
4
|
+
presets?: PresetCatalog;
|
|
5
|
+
assetResolver?: ViewerCreateOptions["assetResolver"];
|
|
6
|
+
camera?: ViewerCreateOptions["camera"];
|
|
7
|
+
renderer?: ViewerCreateOptions["renderer"];
|
|
8
|
+
usePostprocessing?: ViewerCreateOptions["usePostprocessing"];
|
|
9
|
+
className?: string;
|
|
10
|
+
onReady?: (handle: ViewerHandle | null) => void;
|
|
11
|
+
};
|
|
12
|
+
export default function ViewerCanvas({ presetId, presets, assetResolver, camera, renderer, usePostprocessing, className, onReady, }: ViewerCanvasProps): import("react/jsx-runtime").JSX.Element;
|
|
13
|
+
//# sourceMappingURL=ViewerCanvas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ViewerCanvas.d.ts","sourceRoot":"","sources":["../src/ViewerCanvas.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAEpG,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,aAAa,CAAC;IACxB,aAAa,CAAC,EAAE,mBAAmB,CAAC,eAAe,CAAC,CAAC;IACrD,MAAM,CAAC,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC;IACvC,QAAQ,CAAC,EAAE,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAC3C,iBAAiB,CAAC,EAAE,mBAAmB,CAAC,mBAAmB,CAAC,CAAC;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,KAAK,IAAI,CAAC;CACjD,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,QAAQ,EACR,OAAO,EACP,aAAa,EACb,MAAM,EACN,QAAQ,EACR,iBAAiB,EACjB,SAAS,EACT,OAAO,GACR,EAAE,iBAAiB,2CAqInB"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { createViewer } from "@treasuryspatial/viewer-kit";
|
|
4
|
+
export default function ViewerCanvas({ presetId, presets, assetResolver, camera, renderer, usePostprocessing, className, onReady, }) {
|
|
5
|
+
const [container, setContainer] = useState(null);
|
|
6
|
+
const handleRef = useRef(null);
|
|
7
|
+
const presetIdRef = useRef(presetId);
|
|
8
|
+
const initialPresetRef = useRef(presetId);
|
|
9
|
+
const instanceRef = useRef(0);
|
|
10
|
+
const createOptions = useMemo(() => {
|
|
11
|
+
return {
|
|
12
|
+
presetId: initialPresetRef.current,
|
|
13
|
+
presets,
|
|
14
|
+
assetResolver,
|
|
15
|
+
camera,
|
|
16
|
+
renderer,
|
|
17
|
+
usePostprocessing,
|
|
18
|
+
};
|
|
19
|
+
}, [assetResolver, camera, presets, renderer, usePostprocessing]);
|
|
20
|
+
const handleContainerRef = useCallback((node) => {
|
|
21
|
+
setContainer(node);
|
|
22
|
+
}, []);
|
|
23
|
+
useLayoutEffect(() => {
|
|
24
|
+
if (!container)
|
|
25
|
+
return;
|
|
26
|
+
instanceRef.current += 1;
|
|
27
|
+
const instanceId = instanceRef.current;
|
|
28
|
+
let disposed = false;
|
|
29
|
+
let initialized = false;
|
|
30
|
+
let resizeObserver = null;
|
|
31
|
+
let sizeObserver = null;
|
|
32
|
+
let rafId = null;
|
|
33
|
+
let handle = null;
|
|
34
|
+
const cleanup = () => {
|
|
35
|
+
disposed = true;
|
|
36
|
+
if (rafId !== null) {
|
|
37
|
+
cancelAnimationFrame(rafId);
|
|
38
|
+
rafId = null;
|
|
39
|
+
}
|
|
40
|
+
sizeObserver?.disconnect();
|
|
41
|
+
sizeObserver = null;
|
|
42
|
+
resizeObserver?.disconnect();
|
|
43
|
+
resizeObserver = null;
|
|
44
|
+
if (handle) {
|
|
45
|
+
handle.stop();
|
|
46
|
+
handle.dispose();
|
|
47
|
+
}
|
|
48
|
+
handleRef.current = null;
|
|
49
|
+
if (instanceRef.current === instanceId) {
|
|
50
|
+
onReady?.(null);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
const initViewer = () => {
|
|
54
|
+
if (disposed || initialized)
|
|
55
|
+
return;
|
|
56
|
+
if (container.clientWidth === 0 || container.clientHeight === 0)
|
|
57
|
+
return;
|
|
58
|
+
initialized = true;
|
|
59
|
+
container.innerHTML = "";
|
|
60
|
+
handle = createViewer({
|
|
61
|
+
...createOptions,
|
|
62
|
+
container,
|
|
63
|
+
hooks: {
|
|
64
|
+
onReady: (viewer) => {
|
|
65
|
+
if (disposed || instanceRef.current !== instanceId)
|
|
66
|
+
return;
|
|
67
|
+
viewer.__viewerId = instanceId;
|
|
68
|
+
handleRef.current = viewer;
|
|
69
|
+
console.log("[viewer-react] ready", { viewerId: instanceId });
|
|
70
|
+
onReady?.(viewer);
|
|
71
|
+
},
|
|
72
|
+
onError: (error) => {
|
|
73
|
+
if (disposed || instanceRef.current !== instanceId)
|
|
74
|
+
return;
|
|
75
|
+
console.error("[viewer-react] Failed to initialise viewer", error);
|
|
76
|
+
onReady?.(null);
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
handleRef.current = handle;
|
|
81
|
+
handle.__viewerId = instanceId;
|
|
82
|
+
console.log("[viewer-react] create", { viewerId: instanceId });
|
|
83
|
+
handle.start();
|
|
84
|
+
handle.resize();
|
|
85
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
86
|
+
resizeObserver = new ResizeObserver(() => handle?.resize());
|
|
87
|
+
resizeObserver.observe(container);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
requestAnimationFrame(() => handle?.resize());
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
const waitForSize = () => {
|
|
94
|
+
if (disposed || initialized)
|
|
95
|
+
return;
|
|
96
|
+
initViewer();
|
|
97
|
+
if (initialized)
|
|
98
|
+
return;
|
|
99
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
100
|
+
sizeObserver = new ResizeObserver(() => {
|
|
101
|
+
if (initialized || disposed)
|
|
102
|
+
return;
|
|
103
|
+
initViewer();
|
|
104
|
+
if (initialized) {
|
|
105
|
+
sizeObserver?.disconnect();
|
|
106
|
+
sizeObserver = null;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
sizeObserver.observe(container);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
const tick = () => {
|
|
113
|
+
if (disposed || initialized)
|
|
114
|
+
return;
|
|
115
|
+
initViewer();
|
|
116
|
+
if (!initialized) {
|
|
117
|
+
rafId = requestAnimationFrame(tick);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
rafId = requestAnimationFrame(tick);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
waitForSize();
|
|
124
|
+
return cleanup;
|
|
125
|
+
}, [container, createOptions, onReady]);
|
|
126
|
+
useEffect(() => {
|
|
127
|
+
if (!handleRef.current)
|
|
128
|
+
return;
|
|
129
|
+
if (presetId && presetIdRef.current !== presetId) {
|
|
130
|
+
presetIdRef.current = presetId;
|
|
131
|
+
handleRef.current.setPreset(presetId);
|
|
132
|
+
}
|
|
133
|
+
}, [presetId]);
|
|
134
|
+
return _jsx("div", { ref: handleContainerRef, className: className });
|
|
135
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { default as ViewerCanvas } from "./ViewerCanvas";
|
|
2
|
+
export { default as AssetUploadCard } from "./AssetUploadCard";
|
|
3
|
+
export { default as AccessAssetsMenu } from "./AccessAssetsMenu";
|
|
4
|
+
export { default as ViewControls } from "./ViewControls";
|
|
5
|
+
export type { ViewerCanvasProps } from "./ViewerCanvas";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,OAAO,IAAI,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAC/D,OAAO,EAAE,OAAO,IAAI,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACzD,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@treasuryspatial/viewer-react",
|
|
3
|
+
"version": "0.1.11",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@treasuryspatial/viewer-kit": "^0.2.31"
|
|
22
|
+
},
|
|
23
|
+
"peerDependencies": {
|
|
24
|
+
"react": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"react": "19.1.0",
|
|
28
|
+
"@types/react": "^19",
|
|
29
|
+
"@types/react-dom": "^19"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -b",
|
|
33
|
+
"typecheck": "tsc -b --pretty false --noEmit"
|
|
34
|
+
}
|
|
35
|
+
}
|