@treasuryspatial/viewer-react 0.1.44 → 0.1.47

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 (58) hide show
  1. package/dist/AccessAssetsMenu.d.ts.map +1 -1
  2. package/dist/AccessAssetsMenu.js +53 -45
  3. package/dist/AssetUploadCard.d.ts +2 -1
  4. package/dist/AssetUploadCard.d.ts.map +1 -1
  5. package/dist/AssetUploadCard.js +3 -2
  6. package/dist/ViewControls.d.ts +19 -2
  7. package/dist/ViewControls.d.ts.map +1 -1
  8. package/dist/ViewControls.js +65 -16
  9. package/dist/ViewerCanvas.d.ts.map +1 -1
  10. package/dist/ViewerCanvas.js +15 -40
  11. package/dist/composerRouting.d.ts +6 -0
  12. package/dist/composerRouting.d.ts.map +1 -1
  13. package/dist/composerRouting.js +18 -9
  14. package/dist/index.d.ts +13 -6
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +6 -4
  17. package/dist/tsconfig.tsbuildinfo +1 -1
  18. package/dist/useAdminGallery.d.ts +2 -30
  19. package/dist/useAdminGallery.d.ts.map +1 -1
  20. package/dist/useAdminGallery.js +15 -6
  21. package/dist/useAdminImageStream.d.ts +3 -31
  22. package/dist/useAdminImageStream.d.ts.map +1 -1
  23. package/dist/useAdminImageStream.js +22 -10
  24. package/dist/useComposerAccessGate.js +4 -4
  25. package/dist/useComposerAuthState.d.ts +1 -0
  26. package/dist/useComposerAuthState.d.ts.map +1 -1
  27. package/dist/useComposerAuthState.js +4 -1
  28. package/dist/useComposerModeDraftState.d.ts +21 -0
  29. package/dist/useComposerModeDraftState.d.ts.map +1 -0
  30. package/dist/useComposerModeDraftState.js +89 -0
  31. package/dist/useComposerUiState.d.ts +11 -4
  32. package/dist/useComposerUiState.d.ts.map +1 -1
  33. package/dist/useComposerUiState.js +103 -79
  34. package/dist/useComposerWorkspaceSession.d.ts +21 -0
  35. package/dist/useComposerWorkspaceSession.d.ts.map +1 -0
  36. package/dist/useComposerWorkspaceSession.js +291 -0
  37. package/dist/useGrasshopperSession.d.ts.map +1 -1
  38. package/dist/useGrasshopperSession.js +10 -4
  39. package/dist/useModeShellState.d.ts.map +1 -1
  40. package/dist/useModeShellState.js +52 -6
  41. package/dist/usePromptPackSelection.d.ts +20 -0
  42. package/dist/usePromptPackSelection.d.ts.map +1 -0
  43. package/dist/usePromptPackSelection.js +81 -0
  44. package/dist/usePromptPackSelectionByMode.d.ts +2 -1
  45. package/dist/usePromptPackSelectionByMode.d.ts.map +1 -1
  46. package/dist/usePromptPackSelectionByMode.js +35 -22
  47. package/dist/useRightRailTabs.js +1 -1
  48. package/dist/useSessionLogout.d.ts.map +1 -1
  49. package/dist/useSessionLogout.js +12 -1
  50. package/dist/useSessionRevocationPing.d.ts.map +1 -1
  51. package/dist/useSessionRevocationPing.js +15 -2
  52. package/dist/useShellPanelToggle.d.ts +7 -1
  53. package/dist/useShellPanelToggle.d.ts.map +1 -1
  54. package/dist/useShellPanelToggle.js +14 -6
  55. package/dist/workspaceSession.d.ts +36 -0
  56. package/dist/workspaceSession.d.ts.map +1 -0
  57. package/dist/workspaceSession.js +141 -0
  58. package/package.json +8 -8
@@ -1 +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,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAC9B,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,kDA8IvB"}
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,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;CAC9B,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,kDAkKvB"}
@@ -3,6 +3,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
3
3
  export default function AccessAssetsMenu({ label = "access assets", items, disabled = false, className, fullWidth = false, }) {
4
4
  const containerRef = useRef(null);
5
5
  const [open, setOpen] = useState(false);
6
+ const [openGroups, setOpenGroups] = useState({});
6
7
  const resolvedItems = useMemo(() => items.filter((item) => item &&
7
8
  item.label &&
8
9
  (typeof item.onClick === "function" || (Array.isArray(item.actions) && item.actions.length > 0))), [items]);
@@ -21,6 +22,14 @@ export default function AccessAssetsMenu({ label = "access assets", items, disab
21
22
  }, [open]);
22
23
  if (!resolvedItems.length)
23
24
  return null;
25
+ const runAction = async (action) => {
26
+ try {
27
+ await action.onClick?.();
28
+ }
29
+ finally {
30
+ setOpen(false);
31
+ }
32
+ };
24
33
  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: {
25
34
  width: "100%",
26
35
  borderRadius: "8px",
@@ -36,49 +45,48 @@ export default function AccessAssetsMenu({ label = "access assets", items, disab
36
45
  alignItems: "center",
37
46
  justifyContent: "space-between",
38
47
  gap: "8px",
39
- }, 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) => (item.actions && item.actions.length > 0 ? (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [_jsx("div", { style: {
40
- fontSize: "11px",
41
- textTransform: "uppercase",
42
- letterSpacing: "0.14em",
43
- color: "#94a3b8",
44
- padding: "0 2px",
45
- }, children: item.label }), item.actions
46
- .filter((action) => action && action.label && typeof action.onClick === "function")
47
- .map((action) => (_jsx("button", { type: "button", onClick: async () => {
48
- try {
49
- await action.onClick?.();
50
- }
51
- finally {
52
- setOpen(false);
53
- }
54
- }, disabled: disabled || item.disabled || action.disabled, style: {
55
- width: "100%",
56
- borderRadius: "8px",
57
- border: "1px dashed #e2e8f0",
58
- padding: "10px 12px",
59
- fontSize: "12px",
60
- textTransform: "lowercase",
61
- color: "#64748b",
62
- background: "transparent",
63
- cursor: disabled || item.disabled || action.disabled ? "not-allowed" : "pointer",
64
- opacity: disabled || item.disabled || action.disabled ? 0.5 : 1,
65
- }, children: action.label }, action.key)))] }, item.key)) : (_jsx("button", { type: "button", onClick: async () => {
66
- try {
67
- await item.onClick?.();
68
- }
69
- finally {
70
- setOpen(false);
71
- }
72
- }, disabled: disabled || item.disabled, style: {
73
- width: "100%",
74
- borderRadius: "8px",
75
- border: "1px dashed #e2e8f0",
76
- padding: "10px 12px",
77
- fontSize: "12px",
78
- textTransform: "lowercase",
79
- color: "#64748b",
80
- background: "transparent",
81
- cursor: disabled || item.disabled ? "not-allowed" : "pointer",
82
- opacity: disabled || item.disabled ? 0.5 : 1,
83
- }, children: item.label }, item.key)))) })) : null] }));
48
+ }, 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) => {
49
+ const actions = item.actions?.filter((action) => action && action.label && typeof action.onClick === "function") ?? [];
50
+ const groupOpen = openGroups[item.key] ?? false;
51
+ const itemDisabled = disabled || item.disabled;
52
+ return actions.length > 0 ? (_jsxs("div", { style: { display: "flex", flexDirection: "column", gap: "8px" }, children: [_jsxs("button", { type: "button", onClick: () => setOpenGroups((prev) => ({ ...prev, [item.key]: !groupOpen })), disabled: itemDisabled, style: {
53
+ width: "100%",
54
+ borderRadius: "8px",
55
+ border: "1px dashed #e2e8f0",
56
+ padding: "10px 12px",
57
+ fontSize: "12px",
58
+ color: "#475569",
59
+ background: "transparent",
60
+ cursor: itemDisabled ? "not-allowed" : "pointer",
61
+ opacity: itemDisabled ? 0.5 : 1,
62
+ display: "flex",
63
+ alignItems: "center",
64
+ justifyContent: "space-between",
65
+ gap: "8px",
66
+ }, children: [_jsx("span", { children: item.label }), _jsx("span", { style: {
67
+ fontSize: "12px",
68
+ transform: groupOpen ? "rotate(180deg)" : "rotate(0deg)",
69
+ transition: "transform 0.2s ease",
70
+ }, children: "\u25BE" })] }), groupOpen ? (_jsx("div", { style: { display: "flex", flexDirection: "column", gap: "8px", paddingLeft: "12px" }, children: actions.map((action) => (_jsx("button", { type: "button", onClick: () => void runAction(action), disabled: itemDisabled || action.disabled, style: {
71
+ width: "100%",
72
+ borderRadius: "8px",
73
+ border: "1px dashed #e2e8f0",
74
+ padding: "10px 12px",
75
+ fontSize: "12px",
76
+ color: "#64748b",
77
+ background: "transparent",
78
+ cursor: itemDisabled || action.disabled ? "not-allowed" : "pointer",
79
+ opacity: itemDisabled || action.disabled ? 0.5 : 1,
80
+ }, children: action.label }, action.key))) })) : null] }, item.key)) : (_jsx("button", { type: "button", onClick: () => void runAction(item), disabled: itemDisabled, style: {
81
+ width: "100%",
82
+ borderRadius: "8px",
83
+ border: "1px dashed #e2e8f0",
84
+ padding: "10px 12px",
85
+ fontSize: "12px",
86
+ color: "#64748b",
87
+ background: "transparent",
88
+ cursor: itemDisabled ? "not-allowed" : "pointer",
89
+ opacity: itemDisabled ? 0.5 : 1,
90
+ }, children: item.label }, item.key));
91
+ }) })) : null] }));
84
92
  }
@@ -2,6 +2,7 @@ import { type UploadResult } from "@treasuryspatial/viewer-kit";
2
2
  export type AssetUploadCardProps = {
3
3
  disabled?: boolean;
4
4
  apiRoute?: string;
5
+ resolveApiRoute?: () => string;
5
6
  geometryApiRoute?: string;
6
7
  maxBytes?: number;
7
8
  timeoutMs?: number;
@@ -15,5 +16,5 @@ export type AssetUploadCardProps = {
15
16
  description?: string;
16
17
  minimal?: boolean;
17
18
  };
18
- export default function AssetUploadCard({ disabled, apiRoute, geometryApiRoute, maxBytes, timeoutMs, preview, allowEmptyGeometry, accept, onSolved, onClear, className, title, description, minimal, }: AssetUploadCardProps): import("react/jsx-runtime").JSX.Element;
19
+ export default function AssetUploadCard({ disabled, apiRoute, resolveApiRoute, geometryApiRoute, maxBytes, timeoutMs, preview, allowEmptyGeometry, accept, onSolved, onClear, className, title, description, minimal, }: AssetUploadCardProps): import("react/jsx-runtime").JSX.Element;
19
20
  //# sourceMappingURL=AssetUploadCard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AssetUploadCard.d.ts","sourceRoot":"","sources":["../src/AssetUploadCard.tsx"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,YAAY,EAClB,MAAM,6BAA6B,CAAC;AAErC,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,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,gBAAsC,EACtC,QAAyC,EACzC,SAA2C,EAC3C,OAAO,EACP,kBAAkB,EAClB,MAAuB,EACvB,QAAQ,EACR,OAAO,EACP,SAAS,EACT,KAAsB,EACtB,WAAsF,EACtF,OAAe,GAChB,EAAE,oBAAoB,2CAgOtB"}
1
+ {"version":3,"file":"AssetUploadCard.d.ts","sourceRoot":"","sources":["../src/AssetUploadCard.tsx"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,YAAY,EAClB,MAAM,6BAA6B,CAAC;AAErC,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;IAC/B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,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,eAAe,EACf,gBAAsC,EACtC,QAAyC,EACzC,SAA2C,EAC3C,OAAO,EACP,kBAAkB,EAClB,MAAuB,EACvB,QAAQ,EACR,OAAO,EACP,SAAS,EACT,KAAsB,EACtB,WAAsF,EACtF,OAAe,GAChB,EAAE,oBAAoB,2CAiOtB"}
@@ -8,7 +8,7 @@ const formatBytes = (bytes) => {
8
8
  const mb = bytes / (1024 * 1024);
9
9
  return `${mb.toFixed(0)}MB`;
10
10
  };
11
- export default function AssetUploadCard({ disabled, apiRoute = "/api/gh-solve", geometryApiRoute = "/api/geometry-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, }) {
11
+ export default function AssetUploadCard({ disabled, apiRoute = "/api/gh-solve", resolveApiRoute, geometryApiRoute = "/api/geometry-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
12
  const [busy, setBusy] = useState(false);
13
13
  const [lastFileName, setLastFileName] = useState(null);
14
14
  const [error, setError] = useState(null);
@@ -40,8 +40,9 @@ export default function AssetUploadCard({ disabled, apiRoute = "/api/gh-solve",
40
40
  try {
41
41
  let result;
42
42
  if (kind === "gh" || kind === "ghx") {
43
+ const resolvedApiRoute = resolveApiRoute?.() ?? apiRoute;
43
44
  const solved = await solveGrasshopperFile(file, {
44
- apiRoute,
45
+ apiRoute: resolvedApiRoute,
45
46
  preview,
46
47
  allowEmptyGeometry,
47
48
  onStatus: (detail) => updateStatus(file.name, detail),
@@ -1,18 +1,33 @@
1
+ import React from "react";
1
2
  export type LightingOption = {
2
3
  id: string;
3
4
  label: string;
4
5
  summary?: string;
6
+ group?: string;
5
7
  };
6
8
  export type MaterialOption = {
7
9
  id: string;
8
10
  label: string;
9
11
  summary?: string;
10
12
  swatches?: string[];
13
+ group?: string;
11
14
  };
12
15
  export type SkyOption = {
13
16
  id: string;
14
17
  label: string;
15
18
  description?: string;
19
+ group?: string;
20
+ };
21
+ export type ViewControlsAction = {
22
+ id: string;
23
+ label: string;
24
+ active?: boolean;
25
+ onClick: () => void;
26
+ };
27
+ export type ViewControlsWidget = {
28
+ id: string;
29
+ content: React.ReactNode;
30
+ minWidth?: number | string;
16
31
  };
17
32
  export type ViewControlsProps = {
18
33
  activeLightingPreset: string;
@@ -20,14 +35,16 @@ export type ViewControlsProps = {
20
35
  materialOptions?: MaterialOption[];
21
36
  activeMaterialPreset?: string;
22
37
  onMaterialChange?: (preset: string) => void;
23
- onCameraViewChange: (view: string) => void;
38
+ onCameraViewChange?: (view: string) => void;
24
39
  skyOptions: SkyOption[];
25
40
  activeSkyId: string;
26
41
  onSkyChange: (skyId: string) => void;
27
42
  lightingOptions: LightingOption[];
43
+ extraActions?: ViewControlsAction[];
44
+ extraWidgets?: ViewControlsWidget[];
28
45
  veilsEnabled?: boolean;
29
46
  onToggleVeils?: () => void;
30
47
  onResetRender?: () => void;
31
48
  };
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;
49
+ export default function ViewControls({ activeLightingPreset, onLightingChange, materialOptions, activeMaterialPreset, onMaterialChange, onCameraViewChange, skyOptions, activeSkyId, onSkyChange, lightingOptions, extraActions, extraWidgets, veilsEnabled, onToggleVeils, onResetRender, }: ViewControlsProps): import("react/jsx-runtime").JSX.Element;
33
50
  //# sourceMappingURL=ViewControls.d.ts.map
@@ -1 +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;AA6EF,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,2CA8VnB"}
1
+ {"version":3,"file":"ViewControls.d.ts","sourceRoot":"","sources":["../src/ViewControls.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+C,MAAM,OAAO,CAAC;AAEpE,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,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;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B,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,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,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,kBAAkB,EAAE,CAAC;IACpC,YAAY,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACpC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;CAC5B,CAAC;AA+FF,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,YAAiB,EACjB,YAAiB,EACjB,YAAmB,EACnB,aAAa,EACb,aAAa,GACd,EAAE,iBAAiB,2CA0anB"}
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useMemo, useRef, useState } from "react";
2
+ import React, { useEffect, useMemo, useRef, useState } from "react";
3
3
  const cardStyle = {
4
4
  position: "relative",
5
5
  background: "var(--viewer-ui-color-surface-strong, rgba(255,255,255,0.92))",
@@ -25,6 +25,7 @@ const veilStyle = {
25
25
  letterSpacing: "0.1em",
26
26
  color: "var(--viewer-ui-color-text-subtle, rgba(100,116,139,0.7))",
27
27
  };
28
+ const compactControlHeight = "32px";
28
29
  const dropdownStyle = {
29
30
  position: "absolute",
30
31
  bottom: "calc(100% + 8px)",
@@ -56,6 +57,14 @@ const menuButtonStyle = (active) => ({
56
57
  fontSize: "12px",
57
58
  textTransform: "lowercase",
58
59
  });
60
+ const menuGroupLabelStyle = {
61
+ padding: "8px 10px 4px",
62
+ fontSize: "10px",
63
+ fontWeight: 700,
64
+ letterSpacing: "0.12em",
65
+ textTransform: "uppercase",
66
+ color: "var(--viewer-ui-color-text-subtle, rgba(100,116,139,0.72))",
67
+ };
59
68
  const swatchRowStyle = {
60
69
  display: "flex",
61
70
  gap: "6px",
@@ -68,7 +77,15 @@ const swatchStyle = (color) => ({
68
77
  background: color,
69
78
  border: "1px solid var(--viewer-ui-color-panel-border, rgba(15,23,42,0.1))",
70
79
  });
71
- export default function ViewControls({ activeLightingPreset, onLightingChange, materialOptions, activeMaterialPreset, onMaterialChange, onCameraViewChange, skyOptions, activeSkyId, onSkyChange, lightingOptions, veilsEnabled = true, onToggleVeils, onResetRender, }) {
80
+ const shouldShowGroupLabel = (items, index) => {
81
+ const current = items[index]?.group;
82
+ if (!current)
83
+ return false;
84
+ if (index === 0)
85
+ return true;
86
+ return items[index - 1]?.group !== current;
87
+ };
88
+ export default function ViewControls({ activeLightingPreset, onLightingChange, materialOptions, activeMaterialPreset, onMaterialChange, onCameraViewChange, skyOptions, activeSkyId, onSkyChange, lightingOptions, extraActions = [], extraWidgets = [], veilsEnabled = true, onToggleVeils, onResetRender, }) {
72
89
  const lightingMenuRef = useRef(null);
73
90
  const materialMenuRef = useRef(null);
74
91
  const skyMenuRef = useRef(null);
@@ -114,10 +131,10 @@ export default function ViewControls({ activeLightingPreset, onLightingChange, m
114
131
  window.removeEventListener("keydown", onKeyDown);
115
132
  };
116
133
  }, [lightingExpanded, materialExpanded, skyExpanded]);
117
- 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: () => {
118
- onLightingChange(option.id);
119
- setLightingExpanded(false);
120
- }, style: menuButtonStyle(option.id === activeLightingPreset), 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: () => {
134
+ 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, index) => (_jsxs(React.Fragment, { children: [shouldShowGroupLabel(lightingOptions, index) ? (_jsx("div", { style: menuGroupLabelStyle, children: option.group })) : null, _jsxs("button", { type: "button", onClick: () => {
135
+ onLightingChange(option.id);
136
+ setLightingExpanded(false);
137
+ }, style: menuButtonStyle(option.id === activeLightingPreset), 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: () => {
121
138
  setLightingExpanded((prev) => !prev);
122
139
  setSkyExpanded(false);
123
140
  }, style: {
@@ -139,10 +156,10 @@ export default function ViewControls({ activeLightingPreset, onLightingChange, m
139
156
  color: "var(--viewer-ui-color-text-muted, #64748b)",
140
157
  opacity: !veilsEnabled || lightingExpanded || lightingHover ? 1 : 0,
141
158
  transition: "opacity 0.2s ease",
142
- }, 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: () => {
143
- onMaterialChange(option.id);
144
- setMaterialExpanded(false);
145
- }, style: menuButtonStyle(option.id === activeMaterialPreset), 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: () => {
159
+ }, 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, index) => (_jsxs(React.Fragment, { children: [shouldShowGroupLabel(materialOptions, index) ? (_jsx("div", { style: menuGroupLabelStyle, children: option.group })) : null, _jsxs("button", { type: "button", onClick: () => {
160
+ onMaterialChange(option.id);
161
+ setMaterialExpanded(false);
162
+ }, style: menuButtonStyle(option.id === activeMaterialPreset), 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, swatchIndex) => (_jsx("span", { style: swatchStyle(color) }, `${option.id}-${swatchIndex}`))) })) : null] })] }, option.id))) })) : null, _jsxs("button", { type: "button", onClick: () => {
146
163
  setMaterialExpanded((prev) => !prev);
147
164
  setLightingExpanded(false);
148
165
  setSkyExpanded(false);
@@ -165,7 +182,7 @@ export default function ViewControls({ activeLightingPreset, onLightingChange, m
165
182
  color: "var(--viewer-ui-color-text-muted, #64748b)",
166
183
  opacity: !veilsEnabled || materialExpanded || materialHover ? 1 : 0,
167
184
  transition: "opacity 0.2s ease",
168
- }, 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: {
185
+ }, children: materialExpanded ? "▼" : "▲" })] }), veilsEnabled && !materialExpanded && !materialHover ? _jsx("div", { style: veilStyle, children: "MATERIALS" }) : null] }) })) : null, onCameraViewChange ? (_jsxs("div", { style: { ...cardStyle, display: "flex" }, onMouseEnter: () => setViewHover(true), onMouseLeave: () => setViewHover(false), children: [_jsx("button", { type: "button", onClick: () => onCameraViewChange("interior"), style: {
169
186
  padding: "8px 12px",
170
187
  border: "none",
171
188
  borderRight: "1px solid var(--viewer-ui-color-panel-border, #e2e8f0)",
@@ -182,10 +199,10 @@ export default function ViewControls({ activeLightingPreset, onLightingChange, m
182
199
  minWidth: "84px",
183
200
  ...labelStyle,
184
201
  color: !veilsEnabled || viewHover ? "var(--viewer-ui-color-text-muted, #475569)" : "transparent",
185
- }, 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: () => {
186
- onSkyChange(option.id);
187
- setSkyExpanded(false);
188
- }, style: menuButtonStyle(option.id === activeSkyId), 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: () => {
202
+ }, children: "perspective" }), veilsEnabled && !viewHover ? _jsx("div", { style: veilStyle, children: "VIEW" }) : null] })) : 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, index) => (_jsxs(React.Fragment, { children: [shouldShowGroupLabel(skyOptions, index) ? (_jsx("div", { style: menuGroupLabelStyle, children: option.group })) : null, _jsxs("button", { type: "button", onClick: () => {
203
+ onSkyChange(option.id);
204
+ setSkyExpanded(false);
205
+ }, style: menuButtonStyle(option.id === activeSkyId), 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: () => {
189
206
  setSkyExpanded((prev) => !prev);
190
207
  setLightingExpanded(false);
191
208
  }, style: {
@@ -207,7 +224,39 @@ export default function ViewControls({ activeLightingPreset, onLightingChange, m
207
224
  color: "var(--viewer-ui-color-text-muted, #64748b)",
208
225
  opacity: !veilsEnabled || skyExpanded || skyHover ? 1 : 0,
209
226
  transition: "opacity 0.2s ease",
210
- }, 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: {
227
+ }, children: skyExpanded ? "▼" : "▲" })] }), veilsEnabled && !skyExpanded && !skyHover ? _jsx("div", { style: veilStyle, children: "BACKGROUND" }) : null] }) })) : null, extraActions.map((action) => (_jsx("div", { children: _jsx("div", { style: {
228
+ ...cardStyle,
229
+ border: action.active ? "1px solid transparent" : cardStyle.border,
230
+ }, children: _jsx("button", { type: "button", onClick: action.onClick, style: {
231
+ display: "flex",
232
+ alignItems: "center",
233
+ justifyContent: "space-between",
234
+ gap: "10px",
235
+ padding: "8px 12px",
236
+ minHeight: compactControlHeight,
237
+ border: "none",
238
+ background: action.active
239
+ ? "var(--viewer-ui-color-button-primary, var(--brand-primary, #318f4e))"
240
+ : "transparent",
241
+ borderRadius: "11px",
242
+ width: "100%",
243
+ cursor: "pointer",
244
+ }, children: _jsx("span", { style: {
245
+ ...labelStyle,
246
+ opacity: 1,
247
+ color: action.active
248
+ ? "var(--viewer-ui-color-button-primary-text, #ffffff)"
249
+ : labelStyle.color,
250
+ }, children: action.label }) }) }) }, action.id))), extraWidgets.map((widget) => (_jsx("div", { children: _jsx("div", { style: {
251
+ ...cardStyle,
252
+ minWidth: widget.minWidth ?? 280,
253
+ minHeight: compactControlHeight,
254
+ display: "flex",
255
+ alignItems: "stretch",
256
+ position: "relative",
257
+ zIndex: 1,
258
+ pointerEvents: "auto",
259
+ }, children: widget.content }) }, widget.id))), 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: {
211
260
  border: "none",
212
261
  background: "transparent",
213
262
  fontSize: "11px",
@@ -1 +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"}
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;AASF,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,2CA4GnB"}
@@ -1,12 +1,22 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
3
3
  import { createViewer } from "@treasuryspatial/viewer-kit";
4
+ const VIEWER_CANVAS_STYLE = {
5
+ position: "absolute",
6
+ inset: 0,
7
+ width: "100%",
8
+ height: "100%",
9
+ };
4
10
  export default function ViewerCanvas({ presetId, presets, assetResolver, camera, renderer, usePostprocessing, className, onReady, }) {
5
11
  const [container, setContainer] = useState(null);
6
12
  const handleRef = useRef(null);
13
+ const onReadyRef = useRef(onReady);
7
14
  const presetIdRef = useRef(presetId);
8
15
  const initialPresetRef = useRef(presetId);
9
16
  const instanceRef = useRef(0);
17
+ useEffect(() => {
18
+ onReadyRef.current = onReady;
19
+ }, [onReady]);
10
20
  const createOptions = useMemo(() => {
11
21
  return {
12
22
  presetId: initialPresetRef.current,
@@ -28,17 +38,9 @@ export default function ViewerCanvas({ presetId, presets, assetResolver, camera,
28
38
  let disposed = false;
29
39
  let initialized = false;
30
40
  let resizeObserver = null;
31
- let sizeObserver = null;
32
- let rafId = null;
33
41
  let handle = null;
34
42
  const cleanup = () => {
35
43
  disposed = true;
36
- if (rafId !== null) {
37
- cancelAnimationFrame(rafId);
38
- rafId = null;
39
- }
40
- sizeObserver?.disconnect();
41
- sizeObserver = null;
42
44
  resizeObserver?.disconnect();
43
45
  resizeObserver = null;
44
46
  if (handle) {
@@ -47,14 +49,12 @@ export default function ViewerCanvas({ presetId, presets, assetResolver, camera,
47
49
  }
48
50
  handleRef.current = null;
49
51
  if (instanceRef.current === instanceId) {
50
- onReady?.(null);
52
+ onReadyRef.current?.(null);
51
53
  }
52
54
  };
53
55
  const initViewer = () => {
54
56
  if (disposed || initialized)
55
57
  return;
56
- if (container.clientWidth === 0 || container.clientHeight === 0)
57
- return;
58
58
  initialized = true;
59
59
  container.innerHTML = "";
60
60
  handle = createViewer({
@@ -67,13 +67,13 @@ export default function ViewerCanvas({ presetId, presets, assetResolver, camera,
67
67
  viewer.__viewerId = instanceId;
68
68
  handleRef.current = viewer;
69
69
  console.log("[viewer-react] ready", { viewerId: instanceId });
70
- onReady?.(viewer);
70
+ onReadyRef.current?.(viewer);
71
71
  },
72
72
  onError: (error) => {
73
73
  if (disposed || instanceRef.current !== instanceId)
74
74
  return;
75
75
  console.error("[viewer-react] Failed to initialise viewer", error);
76
- onReady?.(null);
76
+ onReadyRef.current?.(null);
77
77
  },
78
78
  },
79
79
  });
@@ -94,35 +94,10 @@ export default function ViewerCanvas({ presetId, presets, assetResolver, camera,
94
94
  if (disposed || initialized)
95
95
  return;
96
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
97
  };
123
98
  waitForSize();
124
99
  return cleanup;
125
- }, [container, createOptions, onReady]);
100
+ }, [container, createOptions]);
126
101
  useEffect(() => {
127
102
  if (!handleRef.current)
128
103
  return;
@@ -131,5 +106,5 @@ export default function ViewerCanvas({ presetId, presets, assetResolver, camera,
131
106
  handleRef.current.setPreset(presetId);
132
107
  }
133
108
  }, [presetId]);
134
- return _jsx("div", { ref: handleContainerRef, className: className });
109
+ return _jsx("div", { ref: handleContainerRef, className: className, style: VIEWER_CANVAS_STYLE });
135
110
  }
@@ -1,5 +1,11 @@
1
1
  export type ComposerMode = string;
2
2
  export declare const DEFAULT_COMPOSER_MODE = "imagine";
3
+ export declare const IMAGE_STREAM_MODE = "images";
4
+ export declare const COMPOSER_PRODUCT_ROUTE = "/composer";
5
+ export declare const COMPOSER_RUNTIME_ROUTE = "/composer/compose";
6
+ export declare const COMPOSER_GALLERY_ROUTE = "/composer/gallery";
7
+ export declare const COMPOSER_IMAGE_STREAM_ROUTE = "/composer/images";
8
+ export declare const COMPOSER_ADMIN_ROUTE = "/composer/admin";
3
9
  export declare const resolveComposerMode: (value?: string | null, options?: {
4
10
  allowedModes?: string[];
5
11
  fallback?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"composerRouting.d.ts","sourceRoot":"","sources":["../src/composerRouting.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,eAAO,MAAM,qBAAqB,YAAY,CAAC;AAI/C,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,MAAM,GAAG,IAAI,EACrB,UAAS;IAAE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,KAC3D,YAUF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,MAAM,YAAY,KAAG,MAIvD,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,MAAM,YAAY,KAAG,MAI5D,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,2BAIpC;IACD,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,KAAG;IACF,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,iBAAiB,EAAE,OAAO,CAAC;CA0B5B,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,uBAG5B;IACD,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,KAAG,MAKH,CAAC"}
1
+ {"version":3,"file":"composerRouting.d.ts","sourceRoot":"","sources":["../src/composerRouting.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,eAAO,MAAM,qBAAqB,YAAY,CAAC;AAC/C,eAAO,MAAM,iBAAiB,WAAW,CAAC;AAC1C,eAAO,MAAM,sBAAsB,cAAc,CAAC;AAClD,eAAO,MAAM,sBAAsB,sBAAsB,CAAC;AAC1D,eAAO,MAAM,sBAAsB,sBAAsB,CAAC;AAC1D,eAAO,MAAM,2BAA2B,qBAAqB,CAAC;AAC9D,eAAO,MAAM,oBAAoB,oBAAoB,CAAC;AAItD,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,MAAM,GAAG,IAAI,EACrB,UAAS;IAAE,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,KAC3D,YAUF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,MAAM,YAAY,KAAG,MAKvD,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAAI,MAAM,YAAY,KAAG,MAI5D,CAAC;AAEF,eAAO,MAAM,sBAAsB,GAAI,2BAIpC;IACD,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,YAAY,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,KAAG;IACF,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,iBAAiB,EAAE,OAAO,CAAC;CA2B5B,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,uBAG5B;IACD,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,KAAG,MAKH,CAAC"}
@@ -1,4 +1,10 @@
1
1
  export const DEFAULT_COMPOSER_MODE = "imagine";
2
+ export const IMAGE_STREAM_MODE = "images";
3
+ export const COMPOSER_PRODUCT_ROUTE = "/composer";
4
+ export const COMPOSER_RUNTIME_ROUTE = "/composer/compose";
5
+ export const COMPOSER_GALLERY_ROUTE = "/composer/gallery";
6
+ export const COMPOSER_IMAGE_STREAM_ROUTE = "/composer/images";
7
+ export const COMPOSER_ADMIN_ROUTE = "/composer/admin";
2
8
  const normalizeMode = (value) => (typeof value === "string" ? value.trim() : "");
3
9
  export const resolveComposerMode = (value, options = {}) => {
4
10
  const requested = normalizeMode(value);
@@ -15,28 +21,31 @@ export const resolveComposerMode = (value, options = {}) => {
15
21
  };
16
22
  export const composeHrefForMode = (mode) => {
17
23
  const resolved = normalizeMode(mode);
24
+ if (resolved === IMAGE_STREAM_MODE)
25
+ return COMPOSER_IMAGE_STREAM_ROUTE;
18
26
  if (!resolved || resolved === DEFAULT_COMPOSER_MODE)
19
- return "/compose";
20
- return `/compose?mode=${encodeURIComponent(resolved)}`;
27
+ return COMPOSER_RUNTIME_ROUTE;
28
+ return `${COMPOSER_RUNTIME_ROUTE}?mode=${encodeURIComponent(resolved)}`;
21
29
  };
22
30
  export const composeGuideHrefForMode = (mode) => {
23
31
  const resolved = normalizeMode(mode);
24
32
  if (!resolved || resolved === DEFAULT_COMPOSER_MODE)
25
- return "/compose?view=guide";
26
- return `/compose?mode=${encodeURIComponent(resolved)}&view=guide`;
33
+ return `${COMPOSER_RUNTIME_ROUTE}?view=guide`;
34
+ return `${COMPOSER_RUNTIME_ROUTE}?mode=${encodeURIComponent(resolved)}&view=guide`;
27
35
  };
28
36
  export const resolveGuideNavigation = ({ pathname, mode, view, }) => {
29
37
  const currentPath = pathname || "/";
30
- const isAdminPage = currentPath.startsWith("/admin");
31
- const isGuideViewActive = currentPath === "/compose" && view === "guide";
38
+ const isAdminPage = currentPath.startsWith(COMPOSER_ADMIN_ROUTE) || currentPath.startsWith("/admin");
39
+ const isComposeRuntimePath = currentPath === COMPOSER_RUNTIME_ROUTE || currentPath === "/compose";
40
+ const isGuideViewActive = isComposeRuntimePath && view === "guide";
32
41
  const guideLabel = isAdminPage ? "user guide" : isGuideViewActive || currentPath === "/guide" ? "composer" : "user guide";
33
42
  const composerHref = composeHrefForMode(mode);
34
43
  const guideLinkBase = composeGuideHrefForMode(mode);
35
44
  const guideHref = isAdminPage
36
45
  ? "/guide"
37
46
  : currentPath === "/guide"
38
- ? "/compose"
39
- : currentPath === "/compose"
47
+ ? COMPOSER_RUNTIME_ROUTE
48
+ : isComposeRuntimePath
40
49
  ? isGuideViewActive
41
50
  ? composerHref
42
51
  : guideLinkBase
@@ -54,5 +63,5 @@ export const buildLoginHref = ({ pathname, search, }) => {
54
63
  const currentPath = pathname || "/";
55
64
  const searchString = search || "";
56
65
  const loginFrom = searchString ? `${currentPath}?${searchString}` : currentPath;
57
- return `/login?from=${encodeURIComponent(loginFrom)}`;
66
+ return `/composer/login?from=${encodeURIComponent(loginFrom)}`;
58
67
  };