@liveroom-tech/react-immersive 1.3.3 → 1.3.5
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/README.md +27 -1
- package/dist/index.d.mts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +1023 -366
- package/dist/index.mjs +975 -316
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -242,7 +242,7 @@ function styleInject(css, { insertAt } = {}) {
|
|
|
242
242
|
}
|
|
243
243
|
|
|
244
244
|
// src/styles/ModelViewer.css
|
|
245
|
-
styleInject('.ri-viewer--license-checking,\n.ri-viewer--license-invalid {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background-color: #f9f9f9;\n}\n.ri-viewer {\n --ri-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-viewer-bg: #1c1c1d;\n --ri-viewer-text: #111827;\n --ri-panel-bg: #ffffff;\n --ri-panel-border: #e5e7eb;\n --ri-panel-title: #111827;\n --ri-panel-muted-text: #4b5563;\n --ri-panel-subtle-text: #9ca3af;\n --ri-panel-surface: #f9fafb;\n --ri-panel-surface-hover: #f3f4f6;\n --ri-panel-surface-border: #e5e7eb;\n --ri-panel-button-text: #1f2937;\n --ri-panel-accent: #2563eb;\n --ri-panel-accent-contrast: #ffffff;\n --ri-license-overlay-bg: rgba(0, 0, 0, 0.04);\n --ri-license-overlay-border: rgba(0, 0, 0, 0.08);\n --ri-license-overlay-text: #888;\n --ri-license-overlay-error-bg: rgba(192, 57, 43, 0.06);\n --ri-license-overlay-error-border: rgba(192, 57, 43, 0.2);\n --ri-license-overlay-error-text: #c0392b;\n --ri-sidepanel-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-sidepanel-bg: var(--ri-databinding-bg, #111318);\n --ri-sidepanel-open-btn-bg: rgba(20, 20, 20, 0.82);\n --ri-sidepanel-open-btn-bg-hover: #27282b7e;\n --ri-sidepanel-border: rgba(255, 255, 255, 0.06);\n --ri-sidepanel-divider: rgba(255, 255, 255, 0.04);\n --ri-sidepanel-border-strong: rgba(255, 255, 255, 0.08);\n --ri-sidepanel-border-active: rgba(255, 255, 255, 0.18);\n --ri-sidepanel-shadow-left: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-shadow-right: -4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-text: var(--ri-databinding-text, #f1f5f9);\n --ri-sidepanel-text-emphasis: var(--ri-databinding-text-hover, #cbd5e1);\n --ri-sidepanel-text-muted: var(--ri-databinding-text-muted, #94a3b8);\n --ri-sidepanel-text-subtle: var(--ri-databinding-text-subtle, #64748b);\n --ri-sidepanel-placeholder: var(--ri-databinding-text-faint, #475569);\n --ri-sidepanel-surface: rgba(255, 255, 255, 0.05);\n --ri-sidepanel-surface-hover: rgba(255, 255, 255, 0.1);\n --ri-sidepanel-accent: #3b82f6;\n --ri-sidepanel-accent-soft: rgba(59, 130, 246, 0.12);\n --ri-databinding-bg: #111318;\n --ri-databinding-border: rgba(255, 255, 255, 0.06);\n --ri-databinding-border-strong: rgba(255, 255, 255, 0.08);\n --ri-databinding-border-hover: rgba(255, 255, 255, 0.15);\n --ri-databinding-shadow: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-databinding-font-family:\n "Inter",\n "Segoe UI",\n sans-serif;\n --ri-databinding-mono-font-family:\n "JetBrains Mono",\n "Fira Code",\n "Menlo",\n monospace;\n --ri-databinding-text: #f1f5f9;\n --ri-databinding-text-muted: #94a3b8;\n --ri-databinding-text-subtle: #64748b;\n --ri-databinding-text-faint: #475569;\n --ri-databinding-text-hover: #cbd5e1;\n --ri-databinding-surface: rgba(255, 255, 255, 0.03);\n --ri-databinding-surface-hover: rgba(255, 255, 255, 0.06);\n --ri-databinding-accent: #93c5fd;\n --ri-databinding-accent-glow: rgba(147, 197, 253, 0.8);\n --ri-databinding-accent-border: rgba(96, 165, 250, 0.55);\n --ri-databinding-accent-gradient:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1d4ed8 60%,\n #2563eb 100% );\n --ri-databinding-accent-gradient-active:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1e40af 100% );\n display: flex;\n flex-direction: row;\n width: 100%;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n background: var(--ri-viewer-bg);\n position: relative;\n color: var(--ri-viewer-text);\n font-family: var(--ri-font-family);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n font-synthesis: none;\n}\n.ri-viewer * {\n box-sizing: border-box;\n}\n.ri-viewerLeft {\n display: flex;\n flex: 1;\n min-height: 0;\n min-width: 0;\n position: relative;\n}\n.ri-viewerRight {\n display: flex;\n flex-shrink: 0;\n min-width: 0;\n}\n.ri-panel {\n width: 280px;\n min-height: 0;\n background: var(--ri-panel-bg);\n border-right: 1px solid var(--ri-panel-border);\n display: flex;\n flex-direction: column;\n padding: 20px;\n overflow-y: auto;\n flex-shrink: 0;\n transition: margin-left 0.3s ease, transform 0.3s ease;\n}\n.ri-panel--open {\n transform: translateX(0);\n margin-left: 0;\n}\n.ri-panel--closed {\n transform: translateX(-100%);\n margin-left: -280px;\n}\n.ri-panelHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 12px;\n}\n.ri-panelTitle {\n margin: 0;\n font-size: 15px;\n font-weight: 700;\n color: var(--ri-panel-title);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ri-closeButton {\n border: 0;\n background: transparent;\n color: var(--ri-panel-subtle-text);\n font-size: 22px;\n line-height: 1;\n padding: 0;\n cursor: pointer;\n flex-shrink: 0;\n transition: color 0.2s ease;\n}\n.ri-closeButton:hover {\n color: var(--ri-panel-muted-text);\n}\n.ri-json {\n margin: 0 0 16px;\n padding: 12px;\n background: var(--ri-panel-surface);\n border: 1px solid var(--ri-panel-surface-border);\n border-radius: 12px;\n font-size: 12px;\n line-height: 1.55;\n color: var(--ri-panel-button-text);\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.ri-divider {\n border-top: 1px solid var(--ri-panel-border);\n margin-bottom: 12px;\n}\n.ri-sectionLabel {\n margin-bottom: 8px;\n font-size: 11px;\n font-weight: 700;\n color: var(--ri-panel-subtle-text);\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-actionList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.ri-actionButton {\n width: 100%;\n border-radius: 8px;\n border: 1px solid var(--ri-panel-surface-border);\n background: var(--ri-panel-surface);\n color: var(--ri-panel-button-text);\n padding: 10px 12px;\n text-align: left;\n font-size: 14px;\n line-height: 1.4;\n cursor: pointer;\n transition:\n background-color 0.2s ease,\n border-color 0.2s ease,\n color 0.2s ease;\n}\n.ri-actionButton:hover {\n background: var(--ri-panel-surface-hover);\n border-color: var(--ri-panel-border);\n}\n.ri-actionButton--active {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n color: var(--ri-panel-accent-contrast);\n}\n.ri-actionButton--active:hover {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n}\n.ri-canvasArea {\n flex: 1 1 auto;\n position: relative;\n min-width: 0;\n}\n.ri-canvasArea--measuring {\n cursor: crosshair;\n}\n.ri-canvasArea canvas {\n opacity: 1;\n transition: opacity 0.18s ease;\n}\n.ri-canvasArea--positioning canvas {\n opacity: 0;\n transition: none;\n}\n.ri-bindingsMismatchBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n white-space: nowrap;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-bindingsMismatchBanner__icon {\n flex-shrink: 0;\n}\n.ri-bindingsMismatchBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-bindingsMismatchBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n.ri-measureTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureButton {\n width: 34px;\n height: 34px;\n padding: 0;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease;\n}\n.ri-measureButton:hover {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-measureButton:active {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-measureButton--active {\n background: #22d3ee;\n border-color: #22d3ee;\n color: #06343c;\n}\n.ri-measureButton--active:hover {\n background: #38dbf0;\n color: #06343c;\n}\n.ri-measureHint {\n padding: 6px 10px;\n border-radius: 8px;\n font-size: 12px;\n line-height: 1;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n white-space: nowrap;\n}\n.ri-explodeTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-explodeSlider {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 12px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-explodeSlider input[type=range] {\n width: 120px;\n accent-color: #22d3ee;\n cursor: pointer;\n}\n.ri-cinematicTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureDot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: #22d3ee;\n border: 2px solid #fff;\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4);\n pointer-events: none;\n}\n.ri-measureLabel {\n padding: 3px 7px;\n border-radius: 6px;\n font-size: 12px;\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n color: #06343c;\n background: #22d3ee;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n pointer-events: none;\n user-select: none;\n}\n.ri-xrTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-xrButton {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 10px;\n font-size: 13px;\n font-weight: 500;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 8px;\n cursor: pointer;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n.ri-xrButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-mobileHandoffBackdrop {\n position: absolute;\n z-index: 30;\n inset: 0;\n display: grid;\n place-items: center;\n padding: 20px;\n background: rgba(2, 6, 23, 0.68);\n backdrop-filter: blur(5px);\n -webkit-backdrop-filter: blur(5px);\n}\n.ri-mobileHandoffDialog {\n position: relative;\n display: flex;\n width: min(100%, 300px);\n flex-direction: column;\n align-items: center;\n padding: 16px;\n text-align: center;\n color: #e2e8f0;\n background: #0f172a;\n border: 1px solid rgba(148, 163, 184, 0.3);\n border-radius: 16px;\n box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);\n}\n.ri-mobileHandoffDialog > svg {\n color: #67e8f9;\n}\n.ri-mobileHandoffDialog h2 {\n margin: 12px 0 0;\n font-size: 18px;\n line-height: 1.25;\n color: #fff;\n}\n.ri-mobileHandoffDialog p {\n margin: 8px 0 16px;\n font-size: 13px;\n line-height: 1.5;\n color: #cbd5e1;\n}\n.ri-mobileHandoffQr {\n display: grid;\n place-items: center;\n padding: 10px;\n background: #fff;\n border-radius: 10px;\n}\n.ri-mobileHandoffDialog a {\n margin-top: 16px;\n font-size: 13px;\n font-weight: 600;\n color: #a5f3fc;\n text-decoration: underline;\n text-underline-offset: 3px;\n}\n.ri-mobileHandoffClose {\n position: absolute;\n top: 10px;\n right: 10px;\n display: grid;\n width: 30px;\n height: 30px;\n place-items: center;\n padding: 0;\n color: #cbd5e1;\n background: transparent;\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n}\n.ri-mobileHandoffClose:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-arHintOverlay {\n position: absolute;\n top: 18px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 360px);\n pointer-events: none;\n}\n.ri-arHint {\n margin: 0 auto;\n width: fit-content;\n max-width: 100%;\n padding: 6px 12px;\n font-size: 11px;\n font-weight: 500;\n line-height: 1.2;\n text-align: center;\n color: rgba(255, 255, 255, 0.92);\n background: rgba(20, 20, 20, 0.58);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 999px;\n user-select: none;\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n}\n.ri-arOverlay {\n position: absolute;\n bottom: 24px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 420px);\n pointer-events: none;\n}\n.ri-arControls {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n}\n.ri-arStatus,\n.ri-arActionButton {\n max-width: 100%;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.3;\n text-align: center;\n white-space: nowrap;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 19px;\n user-select: none;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-arStatus {\n pointer-events: none;\n}\n.ri-arActionButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n cursor: pointer;\n pointer-events: auto;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n transition:\n background 0.15s ease,\n border-color 0.15s ease,\n color 0.15s ease,\n transform 0.15s ease;\n}\n.ri-arActionButton svg {\n flex-shrink: 0;\n}\n.ri-arActionButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-arActionButton:active {\n transform: scale(0.98);\n}\n.ri-arActionButton--primary {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-arActionButton--primary:hover {\n background: #67e8f9;\n border-color: transparent;\n}\n.ri-arActionButton.ri-arActionButton--active,\n.ri-arActionButton[aria-pressed=true],\n.ri-arActionButton.ri-arActionButton--active:hover,\n.ri-arActionButton[aria-pressed=true]:hover,\n.ri-arActionButton.ri-arActionButton--active:active,\n.ri-arActionButton[aria-pressed=true]:active {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-hiddenInput {\n display: none;\n}\n.ri-popover {\n position: absolute;\n z-index: 50;\n background: #ffffff;\n border: 1px solid #e5e7eb;\n border-radius: 16px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n color: #111827;\n}\n.ri-popover--inline {\n position: static;\n z-index: auto;\n width: 100%;\n border-radius: 10px;\n box-shadow: none;\n}\n.ri-popover--inline .react-colorful {\n width: 100%;\n}\n.ri-popover--inline {\n background: var(--ri-databinding-surface, rgba(255, 255, 255, 0.03));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-popoverHeader {\n border-bottom-color: var( --ri-databinding-border-strong, rgba(255, 255, 255, 0.08) );\n}\n.ri-popover--inline .ri-popoverLabel {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-colorCode {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-secondaryButton {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-secondaryButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.ri-popover--inline .ri-previewFrame {\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n}\n.ri-popover--inline .ri-emptyState {\n border-color: var(--ri-databinding-border-hover, rgba(255, 255, 255, 0.15));\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popover--inline .ri-uploadButton {\n background: var(--ri-databinding-accent-gradient, #2563eb);\n color: #ffffff;\n}\n.ri-popover--inline .ri-uploadButton:hover {\n background: var(--ri-databinding-accent-gradient-active, #1d4ed8);\n}\n.ri-popover--inline .ri-removeButton {\n background: transparent;\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-removeButton:hover {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n}\n.ri-popover--inline .ri-formatHint {\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popoverHeader {\n padding: 12px 16px;\n}\n.ri-popoverLabel {\n margin: 0 0 8px;\n font-size: 11px;\n font-weight: 700;\n color: #6b7280;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-inlineRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n margin-top: 8px;\n}\n.ri-colorCode {\n font-size: 12px;\n color: #9ca3af;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n text-transform: uppercase;\n}\n.ri-secondaryButton {\n border: 1px solid #e5e7eb;\n background: #f3f4f6;\n color: #374151;\n border-radius: 8px;\n padding: 6px 10px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-secondaryButton:hover {\n background: #e5e7eb;\n}\n.ri-materialBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding-left: 12px;\n padding-right: 12px;\n padding-bottom: 12px;\n}\n.ri-previewFrame {\n position: relative;\n height: 112px;\n border-radius: 12px;\n overflow: hidden;\n border: 1px solid #e5e7eb;\n}\n.ri-previewImage {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n.ri-previewCaption {\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n padding: 4px 8px;\n color: #ffffff;\n font-size: 10px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ri-emptyState {\n height: 112px;\n border-radius: 12px;\n border: 2px dashed #e5e7eb;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 4px;\n color: #d1d5db;\n text-align: center;\n}\n.ri-uploadButton {\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #111827;\n color: #ffffff;\n padding: 10px 12px;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-uploadButton:hover {\n background: #374151;\n}\n.ri-removeButton {\n width: 100%;\n border-radius: 10px;\n border: 1px solid #e5e7eb;\n background: #ffffff;\n color: #6b7280;\n padding: 8px 12px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-removeButton:hover {\n background: #f9fafb;\n}\n.ri-formatHint {\n margin: 0;\n text-align: center;\n font-size: 10px;\n color: #d1d5db;\n}\n.ri-buttonContainer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n position: absolute;\n bottom: 12px;\n left: 12px;\n right: 12px;\n z-index: 10;\n}\n.ri-tourControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n}\n.ri-rightControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n margin-left: auto;\n}\n.ri-rightControls:only-child {\n margin-left: auto;\n}\n.ri-navButtonSingle,\n.ri-tourButton,\n.ri-resetButton,\n.ri-downloadButton {\n height: 34px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n cursor: pointer;\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease,\n transform 0.15s ease;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n}\n.ri-navButtonSingle:hover:not(:disabled),\n.ri-tourButton:hover:not(:disabled),\n.ri-resetButton:hover:not(:disabled) {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-navButtonSingle:active:not(:disabled),\n.ri-tourButton:active:not(:disabled),\n.ri-resetButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-navButtonSingle:disabled,\n.ri-tourButton:disabled,\n.ri-resetButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-navButtonSingle {\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: nowrap;\n}\n.ri-tourButton {\n padding: 0 12px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 4px;\n white-space: nowrap;\n}\n.ri-tourButton svg {\n width: 15px;\n height: 15px;\n flex-shrink: 0;\n}\n.ri-tourButton--stop {\n border-color: rgba(255, 68, 68, 0.4);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:hover:not(:disabled) {\n background: rgba(255, 68, 68, 0.15);\n border-color: rgba(255, 68, 68, 0.6);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:active:not(:disabled) {\n background: #ff4444;\n color: #fff;\n border-color: #ff4444;\n}\n.ri-tourButton--next {\n border-color: rgba(56, 219, 240, 0.3);\n color: #38dbf0;\n}\n.ri-tourButton--next:hover:not(:disabled) {\n background: rgba(56, 219, 240, 0.12);\n border-color: rgba(56, 219, 240, 0.5);\n color: #38dbf0;\n}\n.ri-tourButton--next:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-resetButton {\n width: 34px;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.ri-resetButton svg {\n width: 15px;\n height: 15px;\n pointer-events: none;\n flex-shrink: 0;\n}\n.ri-resetButton:focus-visible {\n outline: 2px solid rgba(255, 255, 255, 0.5);\n outline-offset: 2px;\n}\n.ri-downloadWrapper {\n position: relative;\n display: inline-flex;\n flex-direction: column;\n align-items: stretch;\n}\n.ri-downloadButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n}\n.ri-downloadButton:hover:not(:disabled) {\n background: rgba(0, 0, 0, 0.72);\n border-color: rgba(255, 255, 255, 0.3);\n color: #fff;\n}\n.ri-downloadButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-downloadButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-downloadButton__spinner {\n display: inline-block;\n width: 13px;\n height: 13px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n border-top-color: #fff;\n border-radius: 50%;\n animation: ri-spin 0.7s linear infinite;\n flex-shrink: 0;\n}\n.ri-downloadMenu {\n position: absolute;\n bottom: calc(100% + 6px);\n right: 0;\n min-width: 168px;\n background: rgba(20, 20, 20, 0.92);\n border: 1px solid rgba(255, 255, 255, 0.14);\n border-radius: 10px;\n overflow: hidden;\n z-index: 100;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-menuReveal 0.12s ease;\n}\n@keyframes ri-menuReveal {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.ri-downloadMenuItem {\n display: flex;\n align-items: center;\n gap: 9px;\n width: 100%;\n padding: 10px 14px;\n font-size: 13px;\n font-weight: 400;\n color: #fff;\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n transition: background 0.1s ease;\n}\n.ri-downloadMenuItem:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-downloadMenuItem:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.ri-downloadMenuDivider {\n height: 1px;\n background: rgba(255, 255, 255, 0.1);\n margin: 0 10px;\n}\n@keyframes ri-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-annotationMarker {\n position: relative;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: rgba(20, 20, 20, 0.82);\n border: 2px solid rgba(255, 255, 255, 0.9);\n color: #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n font-weight: 700;\n cursor: pointer;\n z-index: 20;\n user-select: none;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);\n pointer-events: auto;\n transition: opacity 0.3s ease, transform 0.3s ease;\n}\n.ri-annotationMarker:hover {\n transform: scale(1.08);\n box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);\n background: rgba(40, 40, 40, 0.92);\n}\n.ri-annotationPopup {\n position: absolute;\n width: min(270px, calc(100vw - 32px));\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 16px;\n padding: 16px;\n z-index: 30;\n display: flex;\n flex-direction: column;\n gap: 12px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n}\n.ri-annotationInput,\n.ri-annotationTextarea {\n width: 100%;\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: rgba(255, 255, 255, 0.05);\n color: #e5e7eb;\n border-radius: 10px;\n padding: 10px 12px;\n outline: none;\n font-family: inherit;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n}\n.ri-annotationInput {\n font-size: 14px;\n font-weight: 500;\n}\n.ri-annotationTextarea {\n font-size: 13px;\n resize: vertical;\n min-height: 110px;\n}\n.ri-annotationInput:focus,\n.ri-annotationTextarea:focus {\n border-color: #38dbf0;\n box-shadow: 0 0 0 3px rgba(56, 219, 240, 0.12);\n}\n.ri-annotationActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.ri-annotationOkBtn {\n border: 0;\n border-radius: 8px;\n background: rgba(56, 219, 240, 0.8);\n color: #06343c;\n padding: 8px 18px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease, opacity 0.2s ease;\n}\n.ri-annotationOkBtn:hover:not(:disabled) {\n background: #38dbf0;\n}\n.ri-annotationOkBtn:disabled,\n.ri-annotationOkBtn.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.ri-annotationCancelBtn {\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: transparent;\n color: #9ca3af;\n border-radius: 8px;\n padding: 8px 12px;\n font-size: 13px;\n cursor: pointer;\n transition: background-color 0.2s ease, border-color 0.2s ease;\n}\n.ri-annotationCancelBtn:hover {\n background: rgba(255, 255, 255, 0.05);\n border-color: rgba(255, 255, 255, 0.2);\n}\n.ri-annotationIcons {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 12px;\n color: #9ca3af;\n}\n.ri-annotationIcons svg {\n flex-shrink: 0;\n}\n.ri-viewAnnotationPopup {\n position: relative;\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n padding: 12px 16px;\n min-width: 200px;\n max-width: min(280px, calc(100vw - 48px));\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);\n pointer-events: auto;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-viewAnnotationFadeIn 0.15s ease-out;\n margin-left: 24px;\n margin-top: -20px;\n isolation: isolate;\n z-index: 1000;\n}\n.ri-viewAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n.ri-viewAnnotationTitle {\n margin: 0;\n color: #e2e8f0;\n font-size: 14px;\n font-weight: 600;\n}\n.ri-viewAnnotationClose {\n background: transparent;\n border: none;\n color: #b7bdc6;\n cursor: pointer;\n font-size: 18px;\n padding: 0 4px;\n transition: color 0.2s;\n line-height: 1;\n}\n.ri-viewAnnotationClose:hover {\n color: #e2e8f0;\n}\n.ri-viewAnnotationDescription {\n margin: 0;\n color: #cbd5e1;\n font-size: 13px;\n line-height: 1.4;\n word-wrap: break-word;\n}\n@keyframes ri-viewAnnotationFadeIn {\n from {\n opacity: 0;\n transform: translateY(-6px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n.ri-annotationNavSingle {\n position: absolute;\n bottom: 120px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n pointer-events: none;\n}\n.ri-annotationNavSingle > * {\n pointer-events: auto;\n}\n.ri-navBadge {\n background: rgba(255, 255, 255, 0.1);\n padding: 2px 10px;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 400;\n color: rgba(255, 255, 255, 0.7);\n}\n.ri-annotationTitleHint {\n background: rgba(0, 0, 0, 0.7);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n color: white;\n padding: 8px 20px;\n border-radius: 8px;\n font-size: 13px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n text-align: center;\n max-width: 350px;\n animation: slideUp 0.3s ease;\n}\n.ri-annotationHintTitle {\n font-weight: 600;\n color: #e5e7eb;\n}\n.ri-annotationHintDesc {\n font-size: 12px;\n opacity: 0.6;\n margin-top: 2px;\n color: #d1d5db;\n}\n@keyframes slideUp {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n@media (max-width: 768px) {\n .ri-xrTools {\n gap: 6px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 6px;\n padding: 0 8px;\n font-size: 11px;\n }\n .ri-xrButton svg {\n width: 14px;\n height: 14px;\n }\n .ri-xrButtonPrefix {\n display: none;\n }\n .ri-annotationNavSingle {\n bottom: 100px;\n }\n .ri-navButtonSingle {\n padding: 8px 20px;\n font-size: 13px;\n min-width: 110px;\n }\n .ri-annotationTitleHint {\n padding: 6px 14px;\n font-size: 12px;\n max-width: 250px;\n }\n .ri-viewAnnotationPopup {\n min-width: 160px;\n max-width: 220px;\n padding: 10px 12px;\n }\n .ri-viewAnnotationTitle {\n font-size: 13px;\n }\n .ri-viewAnnotationDescription {\n font-size: 12px;\n }\n .ri-buttonContainer {\n flex-wrap: wrap;\n gap: 6px;\n }\n .ri-tourControls {\n flex-wrap: wrap;\n }\n .ri-rightControls {\n flex-wrap: wrap;\n }\n}\n@media (max-width: 480px) {\n .ri-xrTools {\n gap: 5px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 5px;\n font-size: 10px;\n }\n .ri-xrButton svg {\n width: 13px;\n height: 13px;\n }\n .ri-annotationNavSingle {\n bottom: 80px;\n }\n .ri-navButtonSingle {\n padding: 6px 16px;\n font-size: 12px;\n min-width: 90px;\n gap: 6px;\n }\n .ri-annotationTitleHint {\n padding: 4px 10px;\n font-size: 11px;\n max-width: 200px;\n }\n .ri-navBadge {\n font-size: 10px;\n padding: 1px 6px;\n }\n .ri-tourButton {\n padding: 0 8px;\n font-size: 12px;\n }\n .ri-tourButton span {\n display: none;\n }\n .ri-downloadButton {\n padding: 0 10px;\n font-size: 12px;\n }\n .ri-downloadButton span {\n display: none;\n }\n}\n.ri-canvasArea .bb-loadingOverlay {\n position: absolute;\n inset: 0;\n z-index: 12;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 16px;\n padding: 24px;\n text-align: center;\n border-radius: 14px;\n background: rgba(15, 23, 42, 0.46);\n backdrop-filter: blur(1px);\n}\n.ri-canvasArea .bb-loadingSpinner {\n width: 40px;\n height: 40px;\n border-radius: 999px;\n border: 3px solid rgba(226, 232, 240, 0.24);\n border-top-color: #93c5fd;\n animation: bb-spin 0.8s linear infinite;\n}\n.ri-canvasArea .bb-loadingMessage {\n max-width: 320px;\n color: #f7f8f9;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.5;\n}\n@keyframes bb-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-largeModelBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-largeModelBanner--offset {\n top: 64px;\n}\n.ri-largeModelBanner__icon {\n flex-shrink: 0;\n}\n.ri-largeModelBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-largeModelBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n');
|
|
245
|
+
styleInject('.ri-viewer--license-checking,\n.ri-viewer--license-invalid {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n height: 100%;\n background-color: #f9f9f9;\n}\n.ri-viewer {\n --ri-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-viewer-bg: #1c1c1d;\n --ri-viewer-text: #111827;\n --ri-panel-bg: #ffffff;\n --ri-panel-border: #e5e7eb;\n --ri-panel-title: #111827;\n --ri-panel-muted-text: #4b5563;\n --ri-panel-subtle-text: #9ca3af;\n --ri-panel-surface: #f9fafb;\n --ri-panel-surface-hover: #f3f4f6;\n --ri-panel-surface-border: #e5e7eb;\n --ri-panel-button-text: #1f2937;\n --ri-panel-accent: #2563eb;\n --ri-panel-accent-contrast: #ffffff;\n --ri-license-overlay-bg: rgba(0, 0, 0, 0.04);\n --ri-license-overlay-border: rgba(0, 0, 0, 0.08);\n --ri-license-overlay-text: #888;\n --ri-license-overlay-error-bg: rgba(192, 57, 43, 0.06);\n --ri-license-overlay-error-border: rgba(192, 57, 43, 0.2);\n --ri-license-overlay-error-text: #c0392b;\n --ri-sidepanel-font-family:\n Inter,\n "Segoe UI",\n Helvetica,\n Arial,\n sans-serif;\n --ri-sidepanel-bg: var(--ri-databinding-bg, #111318);\n --ri-sidepanel-open-btn-bg: rgba(20, 20, 20, 0.82);\n --ri-sidepanel-open-btn-bg-hover: #27282b7e;\n --ri-sidepanel-border: rgba(255, 255, 255, 0.06);\n --ri-sidepanel-divider: rgba(255, 255, 255, 0.04);\n --ri-sidepanel-border-strong: rgba(255, 255, 255, 0.08);\n --ri-sidepanel-border-active: rgba(255, 255, 255, 0.18);\n --ri-sidepanel-shadow-left: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-shadow-right: -4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-sidepanel-text: var(--ri-databinding-text, #f1f5f9);\n --ri-sidepanel-text-emphasis: var(--ri-databinding-text-hover, #cbd5e1);\n --ri-sidepanel-text-muted: var(--ri-databinding-text-muted, #94a3b8);\n --ri-sidepanel-text-subtle: var(--ri-databinding-text-subtle, #64748b);\n --ri-sidepanel-placeholder: var(--ri-databinding-text-faint, #475569);\n --ri-sidepanel-surface: rgba(255, 255, 255, 0.05);\n --ri-sidepanel-surface-hover: rgba(255, 255, 255, 0.1);\n --ri-sidepanel-accent: #3b82f6;\n --ri-sidepanel-accent-soft: rgba(59, 130, 246, 0.12);\n --ri-databinding-bg: #111318;\n --ri-databinding-border: rgba(255, 255, 255, 0.06);\n --ri-databinding-border-strong: rgba(255, 255, 255, 0.08);\n --ri-databinding-border-hover: rgba(255, 255, 255, 0.15);\n --ri-databinding-shadow: 4px 0 24px rgba(0, 0, 0, 0.35);\n --ri-databinding-font-family:\n "Inter",\n "Segoe UI",\n sans-serif;\n --ri-databinding-mono-font-family:\n "JetBrains Mono",\n "Fira Code",\n "Menlo",\n monospace;\n --ri-databinding-text: #f1f5f9;\n --ri-databinding-text-muted: #94a3b8;\n --ri-databinding-text-subtle: #64748b;\n --ri-databinding-text-faint: #475569;\n --ri-databinding-text-hover: #cbd5e1;\n --ri-databinding-surface: rgba(255, 255, 255, 0.03);\n --ri-databinding-surface-hover: rgba(255, 255, 255, 0.06);\n --ri-databinding-accent: #93c5fd;\n --ri-databinding-accent-glow: rgba(147, 197, 253, 0.8);\n --ri-databinding-accent-border: rgba(96, 165, 250, 0.55);\n --ri-databinding-accent-gradient:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1d4ed8 60%,\n #2563eb 100% );\n --ri-databinding-accent-gradient-active:\n linear-gradient(\n \n 135deg,\n #1e3a8a 0%,\n #1e40af 100% );\n display: flex;\n flex-direction: row;\n width: 100%;\n height: 100%;\n min-height: 0;\n overflow: hidden;\n background: var(--ri-viewer-bg);\n position: relative;\n color: var(--ri-viewer-text);\n font-family: var(--ri-font-family);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n font-synthesis: none;\n}\n.ri-viewer * {\n box-sizing: border-box;\n}\n.ri-viewerLeft {\n display: flex;\n flex: 1;\n min-height: 0;\n min-width: 0;\n position: relative;\n}\n.ri-viewerRight {\n display: flex;\n flex-shrink: 0;\n min-width: 0;\n}\n.ri-panel {\n width: 280px;\n min-height: 0;\n background: var(--ri-panel-bg);\n border-right: 1px solid var(--ri-panel-border);\n display: flex;\n flex-direction: column;\n padding: 20px;\n overflow-y: auto;\n flex-shrink: 0;\n transition: margin-left 0.3s ease, transform 0.3s ease;\n}\n.ri-panel--open {\n transform: translateX(0);\n margin-left: 0;\n}\n.ri-panel--closed {\n transform: translateX(-100%);\n margin-left: -280px;\n}\n.ri-panelHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n margin-bottom: 12px;\n}\n.ri-panelTitle {\n margin: 0;\n font-size: 15px;\n font-weight: 700;\n color: var(--ri-panel-title);\n line-height: 1.4;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.ri-closeButton {\n border: 0;\n background: transparent;\n color: var(--ri-panel-subtle-text);\n font-size: 22px;\n line-height: 1;\n padding: 0;\n cursor: pointer;\n flex-shrink: 0;\n transition: color 0.2s ease;\n}\n.ri-closeButton:hover {\n color: var(--ri-panel-muted-text);\n}\n.ri-json {\n margin: 0 0 16px;\n padding: 12px;\n background: var(--ri-panel-surface);\n border: 1px solid var(--ri-panel-surface-border);\n border-radius: 12px;\n font-size: 12px;\n line-height: 1.55;\n color: var(--ri-panel-button-text);\n overflow-x: auto;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.ri-divider {\n border-top: 1px solid var(--ri-panel-border);\n margin-bottom: 12px;\n}\n.ri-sectionLabel {\n margin-bottom: 8px;\n font-size: 11px;\n font-weight: 700;\n color: var(--ri-panel-subtle-text);\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-actionList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n.ri-actionButton {\n width: 100%;\n border-radius: 8px;\n border: 1px solid var(--ri-panel-surface-border);\n background: var(--ri-panel-surface);\n color: var(--ri-panel-button-text);\n padding: 10px 12px;\n text-align: left;\n font-size: 14px;\n line-height: 1.4;\n cursor: pointer;\n transition:\n background-color 0.2s ease,\n border-color 0.2s ease,\n color 0.2s ease;\n}\n.ri-actionButton:hover {\n background: var(--ri-panel-surface-hover);\n border-color: var(--ri-panel-border);\n}\n.ri-actionButton--active {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n color: var(--ri-panel-accent-contrast);\n}\n.ri-actionButton--active:hover {\n background: var(--ri-panel-accent);\n border-color: var(--ri-panel-accent);\n}\n.ri-canvasArea {\n flex: 1 1 auto;\n position: relative;\n min-width: 0;\n}\n.ri-canvasArea--measuring {\n cursor: crosshair;\n}\n.ri-canvasArea canvas {\n opacity: 1;\n transition: opacity 0.18s ease;\n}\n.ri-canvasArea--positioning canvas {\n opacity: 0;\n transition: none;\n}\n.ri-bindingsMismatchBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n white-space: nowrap;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-bindingsMismatchBanner__icon {\n flex-shrink: 0;\n}\n.ri-bindingsMismatchBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-bindingsMismatchBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n.ri-measureTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureButton {\n width: 34px;\n height: 34px;\n padding: 0;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease;\n}\n.ri-measureButton:hover {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-measureButton:active {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-measureButton--active {\n background: #22d3ee;\n border-color: #22d3ee;\n color: #06343c;\n}\n.ri-measureButton--active:hover {\n background: #38dbf0;\n color: #06343c;\n}\n.ri-measureHint {\n padding: 6px 10px;\n border-radius: 8px;\n font-size: 12px;\n line-height: 1;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n white-space: nowrap;\n}\n.ri-explodeTools {\n position: absolute;\n top: 16px;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-explodeSlider {\n display: flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 12px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-explodeSlider input[type=range] {\n width: 120px;\n accent-color: #22d3ee;\n cursor: pointer;\n}\n.ri-cinematicTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-measureDot {\n width: 10px;\n height: 10px;\n border-radius: 50%;\n background: #22d3ee;\n border: 2px solid #fff;\n box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4);\n pointer-events: none;\n}\n.ri-measureLabel {\n padding: 3px 7px;\n border-radius: 6px;\n font-size: 12px;\n font-weight: 600;\n line-height: 1;\n white-space: nowrap;\n color: #06343c;\n background: #22d3ee;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n pointer-events: none;\n user-select: none;\n}\n.ri-xrTools {\n position: absolute;\n left: 16px;\n z-index: 10;\n display: flex;\n gap: 8px;\n align-items: center;\n}\n.ri-xrButton {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n height: 34px;\n padding: 0 10px;\n font-size: 13px;\n font-weight: 500;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 8px;\n cursor: pointer;\n touch-action: manipulation;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n transition: background 0.15s ease, border-color 0.15s ease;\n}\n.ri-xrButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-mobileHandoffBackdrop {\n position: absolute;\n z-index: 30;\n inset: 0;\n display: grid;\n place-items: center;\n padding: 20px;\n background: rgba(2, 6, 23, 0.68);\n backdrop-filter: blur(5px);\n -webkit-backdrop-filter: blur(5px);\n}\n.ri-mobileHandoffDialog {\n position: relative;\n display: flex;\n width: min(100%, 300px);\n flex-direction: column;\n align-items: center;\n padding: 16px;\n text-align: center;\n color: #e2e8f0;\n background: #0f172a;\n border: 1px solid rgba(148, 163, 184, 0.3);\n border-radius: 16px;\n box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);\n}\n.ri-mobileHandoffDialog > svg {\n color: #67e8f9;\n}\n.ri-mobileHandoffDialog h2 {\n margin: 12px 0 0;\n font-size: 18px;\n line-height: 1.25;\n color: #fff;\n}\n.ri-mobileHandoffDialog p {\n margin: 8px 0 16px;\n font-size: 13px;\n line-height: 1.5;\n color: #cbd5e1;\n}\n.ri-mobileHandoffQr {\n display: grid;\n place-items: center;\n padding: 10px;\n background: #fff;\n border-radius: 10px;\n}\n.ri-mobileHandoffDialog a {\n margin-top: 16px;\n font-size: 13px;\n font-weight: 600;\n color: #a5f3fc;\n text-decoration: underline;\n text-underline-offset: 3px;\n}\n.ri-mobileHandoffClose {\n position: absolute;\n top: 10px;\n right: 10px;\n display: grid;\n width: 30px;\n height: 30px;\n place-items: center;\n padding: 0;\n color: #cbd5e1;\n background: transparent;\n border: 0;\n border-radius: 6px;\n cursor: pointer;\n}\n.ri-mobileHandoffClose:hover {\n color: #fff;\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-arHintOverlay {\n position: absolute;\n top: 18px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 360px);\n pointer-events: none;\n}\n.ri-arHint {\n margin: 0 auto;\n width: fit-content;\n max-width: 100%;\n padding: 6px 12px;\n font-size: 11px;\n font-weight: 500;\n line-height: 1.2;\n text-align: center;\n color: rgba(255, 255, 255, 0.92);\n background: rgba(20, 20, 20, 0.58);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 999px;\n user-select: none;\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n}\n.ri-arOverlay {\n position: absolute;\n bottom: 24px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n width: min(calc(100% - 32px), 420px);\n pointer-events: none;\n}\n.ri-arControls {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n}\n.ri-arStatus,\n.ri-arActionButton {\n max-width: 100%;\n padding: 8px 16px;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.3;\n text-align: center;\n white-space: nowrap;\n color: #fff;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.16);\n border-radius: 19px;\n user-select: none;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n}\n.ri-arStatus {\n pointer-events: none;\n}\n.ri-arActionButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n cursor: pointer;\n pointer-events: auto;\n touch-action: manipulation;\n -webkit-tap-highlight-color: transparent;\n transition:\n background 0.15s ease,\n border-color 0.15s ease,\n color 0.15s ease,\n transform 0.15s ease;\n}\n.ri-arActionButton svg {\n flex-shrink: 0;\n}\n.ri-arActionButton:hover {\n background: rgba(40, 40, 40, 0.92);\n border-color: rgba(255, 255, 255, 0.3);\n}\n.ri-arActionButton:active {\n transform: scale(0.98);\n}\n.ri-arActionButton--primary {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-arActionButton--primary:hover {\n background: #67e8f9;\n border-color: transparent;\n}\n.ri-arActionButton.ri-arActionButton--active,\n.ri-arActionButton[aria-pressed=true],\n.ri-arActionButton.ri-arActionButton--active:hover,\n.ri-arActionButton[aria-pressed=true]:hover,\n.ri-arActionButton.ri-arActionButton--active:active,\n.ri-arActionButton[aria-pressed=true]:active {\n color: #06343c;\n background: #22d3ee;\n border-color: transparent;\n box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);\n}\n.ri-hiddenInput {\n display: none;\n}\n.ri-popover {\n position: absolute;\n z-index: 50;\n background: #ffffff;\n border: 1px solid #e5e7eb;\n border-radius: 16px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n color: #111827;\n}\n.ri-popover--inline {\n position: static;\n z-index: auto;\n width: 100%;\n border-radius: 10px;\n box-shadow: none;\n}\n.ri-popover--inline .react-colorful {\n width: 100%;\n}\n.ri-popover--inline {\n background: var(--ri-databinding-surface, rgba(255, 255, 255, 0.03));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-popoverHeader {\n border-bottom-color: var( --ri-databinding-border-strong, rgba(255, 255, 255, 0.08) );\n}\n.ri-popover--inline .ri-popoverLabel {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-colorCode {\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-secondaryButton {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text, #f1f5f9);\n}\n.ri-popover--inline .ri-secondaryButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.ri-popover--inline .ri-previewFrame {\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n}\n.ri-popover--inline .ri-emptyState {\n border-color: var(--ri-databinding-border-hover, rgba(255, 255, 255, 0.15));\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popover--inline .ri-uploadButton {\n background: var(--ri-databinding-accent-gradient, #2563eb);\n color: #ffffff;\n}\n.ri-popover--inline .ri-uploadButton:hover {\n background: var(--ri-databinding-accent-gradient-active, #1d4ed8);\n}\n.ri-popover--inline .ri-removeButton {\n background: transparent;\n border-color: var(--ri-databinding-border-strong, rgba(255, 255, 255, 0.08));\n color: var(--ri-databinding-text-muted, #94a3b8);\n}\n.ri-popover--inline .ri-removeButton:hover {\n background: var(--ri-databinding-surface-hover, rgba(255, 255, 255, 0.06));\n}\n.ri-popover--inline .ri-formatHint {\n color: var(--ri-databinding-text-subtle, #64748b);\n}\n.ri-popoverHeader {\n padding: 12px 16px;\n}\n.ri-popoverLabel {\n margin: 0 0 8px;\n font-size: 11px;\n font-weight: 700;\n color: #6b7280;\n text-transform: uppercase;\n letter-spacing: 0.16em;\n}\n.ri-inlineRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n margin-top: 8px;\n}\n.ri-colorCode {\n font-size: 12px;\n color: #9ca3af;\n font-family:\n ui-monospace,\n SFMono-Regular,\n Menlo,\n Monaco,\n Consolas,\n monospace;\n text-transform: uppercase;\n}\n.ri-secondaryButton {\n border: 1px solid #e5e7eb;\n background: #f3f4f6;\n color: #374151;\n border-radius: 8px;\n padding: 6px 10px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-secondaryButton:hover {\n background: #e5e7eb;\n}\n.ri-materialBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding-left: 12px;\n padding-right: 12px;\n padding-bottom: 12px;\n}\n.ri-previewFrame {\n position: relative;\n height: 112px;\n border-radius: 12px;\n overflow: hidden;\n border: 1px solid #e5e7eb;\n}\n.ri-previewImage {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n.ri-previewCaption {\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.4);\n padding: 4px 8px;\n color: #ffffff;\n font-size: 10px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.ri-emptyState {\n height: 112px;\n border-radius: 12px;\n border: 2px dashed #e5e7eb;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 4px;\n color: #d1d5db;\n text-align: center;\n}\n.ri-uploadButton {\n width: 100%;\n border: 0;\n border-radius: 10px;\n background: #111827;\n color: #ffffff;\n padding: 10px 12px;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-uploadButton:hover {\n background: #374151;\n}\n.ri-removeButton {\n width: 100%;\n border-radius: 10px;\n border: 1px solid #e5e7eb;\n background: #ffffff;\n color: #6b7280;\n padding: 8px 12px;\n font-size: 12px;\n cursor: pointer;\n transition: background-color 0.2s ease;\n}\n.ri-removeButton:hover {\n background: #f9fafb;\n}\n.ri-formatHint {\n margin: 0;\n text-align: center;\n font-size: 10px;\n color: #d1d5db;\n}\n.ri-buttonContainer {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n position: absolute;\n bottom: 12px;\n left: 12px;\n right: 12px;\n z-index: 10;\n}\n.ri-tourControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n}\n.ri-rightControls {\n display: flex;\n gap: 6px;\n align-items: center;\n flex-shrink: 0;\n margin-left: auto;\n}\n.ri-rightControls:only-child {\n margin-left: auto;\n}\n.ri-navButtonSingle,\n.ri-tourButton,\n.ri-resetButton,\n.ri-downloadButton {\n height: 34px;\n background: rgba(20, 20, 20, 0.82);\n border: 1px solid rgba(255, 255, 255, 0.12);\n color: rgba(255, 255, 255, 0.75);\n border-radius: 8px;\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n cursor: pointer;\n transition:\n background 0.15s ease,\n color 0.15s ease,\n border-color 0.15s ease,\n transform 0.15s ease;\n user-select: none;\n -webkit-tap-highlight-color: transparent;\n}\n.ri-navButtonSingle:hover:not(:disabled),\n.ri-tourButton:hover:not(:disabled),\n.ri-resetButton:hover:not(:disabled) {\n background: rgba(40, 40, 40, 0.92);\n color: #fff;\n border-color: rgba(255, 255, 255, 0.25);\n}\n.ri-navButtonSingle:active:not(:disabled),\n.ri-tourButton:active:not(:disabled),\n.ri-resetButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-navButtonSingle:disabled,\n.ri-tourButton:disabled,\n.ri-resetButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-navButtonSingle {\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 8px;\n white-space: nowrap;\n}\n.ri-tourButton {\n padding: 0 12px;\n font-size: 13px;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 4px;\n white-space: nowrap;\n}\n.ri-tourButton svg {\n width: 15px;\n height: 15px;\n flex-shrink: 0;\n}\n.ri-tourButton--stop {\n border-color: rgba(255, 68, 68, 0.4);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:hover:not(:disabled) {\n background: rgba(255, 68, 68, 0.15);\n border-color: rgba(255, 68, 68, 0.6);\n color: #ff6b6b;\n}\n.ri-tourButton--stop:active:not(:disabled) {\n background: #ff4444;\n color: #fff;\n border-color: #ff4444;\n}\n.ri-tourButton--next {\n border-color: rgba(56, 219, 240, 0.3);\n color: #38dbf0;\n}\n.ri-tourButton--next:hover:not(:disabled) {\n background: rgba(56, 219, 240, 0.12);\n border-color: rgba(56, 219, 240, 0.5);\n color: #38dbf0;\n}\n.ri-tourButton--next:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-resetButton {\n width: 34px;\n padding: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.ri-resetButton svg {\n width: 15px;\n height: 15px;\n pointer-events: none;\n flex-shrink: 0;\n}\n.ri-resetButton:focus-visible {\n outline: 2px solid rgba(255, 255, 255, 0.5);\n outline-offset: 2px;\n}\n.ri-downloadWrapper {\n position: relative;\n display: inline-flex;\n flex-direction: column;\n align-items: stretch;\n}\n.ri-downloadButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 0 16px;\n font-size: 13px;\n font-weight: 500;\n white-space: nowrap;\n}\n.ri-downloadButton:hover:not(:disabled) {\n background: rgba(0, 0, 0, 0.72);\n border-color: rgba(255, 255, 255, 0.3);\n color: #fff;\n}\n.ri-downloadButton:active:not(:disabled) {\n background: #38dbf0;\n color: #06343c;\n border-color: #38dbf0;\n}\n.ri-downloadButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.ri-downloadButton__spinner {\n display: inline-block;\n width: 13px;\n height: 13px;\n border: 2px solid rgba(255, 255, 255, 0.3);\n border-top-color: #fff;\n border-radius: 50%;\n animation: ri-spin 0.7s linear infinite;\n flex-shrink: 0;\n}\n.ri-downloadMenu {\n position: absolute;\n bottom: calc(100% + 6px);\n right: 0;\n min-width: 168px;\n background: rgba(20, 20, 20, 0.92);\n border: 1px solid rgba(255, 255, 255, 0.14);\n border-radius: 10px;\n overflow: hidden;\n z-index: 100;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-menuReveal 0.12s ease;\n}\n@keyframes ri-menuReveal {\n from {\n opacity: 0;\n transform: translateY(4px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.ri-downloadMenuItem {\n display: flex;\n align-items: center;\n gap: 9px;\n width: 100%;\n padding: 10px 14px;\n font-size: 13px;\n font-weight: 400;\n color: #fff;\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n transition: background 0.1s ease;\n text-wrap: nowrap;\n}\n.ri-downloadMenuItem:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.1);\n}\n.ri-downloadMenuItem:disabled {\n opacity: 0.4;\n cursor: not-allowed;\n}\n.ri-downloadMenuDivider {\n height: 1px;\n background: rgba(255, 255, 255, 0.1);\n margin: 0 10px;\n}\n.ri-downloadMenuNotice {\n max-width: 220px;\n padding: 8px 14px 11px;\n color: #fca5a5;\n font-size: 11px;\n line-height: 1.4;\n}\n@keyframes ri-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-annotationMarker {\n position: relative;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: rgba(20, 20, 20, 0.82);\n border: 2px solid rgba(255, 255, 255, 0.9);\n color: #fff;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 14px;\n font-weight: 700;\n cursor: pointer;\n z-index: 20;\n user-select: none;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);\n pointer-events: auto;\n transition: opacity 0.3s ease, transform 0.3s ease;\n}\n.ri-annotationMarker:hover {\n transform: scale(1.08);\n box-shadow: 0 12px 28px rgba(15, 23, 42, 0.18);\n background: rgba(40, 40, 40, 0.92);\n}\n.ri-annotationPopup {\n position: absolute;\n width: min(270px, calc(100vw - 32px));\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 16px;\n padding: 16px;\n z-index: 30;\n display: flex;\n flex-direction: column;\n gap: 12px;\n box-shadow: 0 24px 48px rgba(15, 23, 42, 0.18);\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n}\n.ri-annotationInput,\n.ri-annotationTextarea {\n width: 100%;\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: rgba(255, 255, 255, 0.05);\n color: #e5e7eb;\n border-radius: 10px;\n padding: 10px 12px;\n outline: none;\n font-family: inherit;\n transition: border-color 0.2s ease, box-shadow 0.2s ease;\n}\n.ri-annotationInput {\n font-size: 14px;\n font-weight: 500;\n}\n.ri-annotationTextarea {\n font-size: 13px;\n resize: vertical;\n min-height: 110px;\n}\n.ri-annotationInput:focus,\n.ri-annotationTextarea:focus {\n border-color: #38dbf0;\n box-shadow: 0 0 0 3px rgba(56, 219, 240, 0.12);\n}\n.ri-annotationActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.ri-annotationOkBtn {\n border: 0;\n border-radius: 8px;\n background: rgba(56, 219, 240, 0.8);\n color: #06343c;\n padding: 8px 18px;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n transition: background-color 0.2s ease, opacity 0.2s ease;\n}\n.ri-annotationOkBtn:hover:not(:disabled) {\n background: #38dbf0;\n}\n.ri-annotationOkBtn:disabled,\n.ri-annotationOkBtn.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n.ri-annotationCancelBtn {\n border: 1px solid rgba(255, 255, 255, 0.12);\n background: transparent;\n color: #9ca3af;\n border-radius: 8px;\n padding: 8px 12px;\n font-size: 13px;\n cursor: pointer;\n transition: background-color 0.2s ease, border-color 0.2s ease;\n}\n.ri-annotationCancelBtn:hover {\n background: rgba(255, 255, 255, 0.05);\n border-color: rgba(255, 255, 255, 0.2);\n}\n.ri-annotationIcons {\n margin-left: auto;\n display: flex;\n align-items: center;\n gap: 12px;\n color: #9ca3af;\n}\n.ri-annotationIcons svg {\n flex-shrink: 0;\n}\n.ri-viewAnnotationPopup {\n position: relative;\n background: rgba(20, 20, 20, 0.85);\n border: 1px solid rgba(255, 255, 255, 0.12);\n border-radius: 8px;\n padding: 12px 16px;\n min-width: 200px;\n max-width: min(280px, calc(100vw - 48px));\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);\n pointer-events: auto;\n backdrop-filter: blur(10px);\n -webkit-backdrop-filter: blur(10px);\n animation: ri-viewAnnotationFadeIn 0.15s ease-out;\n margin-left: 24px;\n margin-top: -20px;\n isolation: isolate;\n z-index: 1000;\n}\n.ri-viewAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n.ri-viewAnnotationTitle {\n margin: 0;\n color: #e2e8f0;\n font-size: 14px;\n font-weight: 600;\n}\n.ri-viewAnnotationClose {\n background: transparent;\n border: none;\n color: #b7bdc6;\n cursor: pointer;\n font-size: 18px;\n padding: 0 4px;\n transition: color 0.2s;\n line-height: 1;\n}\n.ri-viewAnnotationClose:hover {\n color: #e2e8f0;\n}\n.ri-viewAnnotationDescription {\n margin: 0;\n color: #cbd5e1;\n font-size: 13px;\n line-height: 1.4;\n word-wrap: break-word;\n}\n@keyframes ri-viewAnnotationFadeIn {\n from {\n opacity: 0;\n transform: translateY(-6px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n.ri-annotationNavSingle {\n position: absolute;\n bottom: 120px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 10;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 10px;\n pointer-events: none;\n}\n.ri-annotationNavSingle > * {\n pointer-events: auto;\n}\n.ri-navBadge {\n background: rgba(255, 255, 255, 0.1);\n padding: 2px 10px;\n border-radius: 4px;\n font-size: 12px;\n font-weight: 400;\n color: rgba(255, 255, 255, 0.7);\n}\n.ri-annotationTitleHint {\n background: rgba(0, 0, 0, 0.7);\n backdrop-filter: blur(8px);\n -webkit-backdrop-filter: blur(8px);\n color: white;\n padding: 8px 20px;\n border-radius: 8px;\n font-size: 13px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n text-align: center;\n max-width: 350px;\n animation: slideUp 0.3s ease;\n}\n.ri-annotationHintTitle {\n font-weight: 600;\n color: #e5e7eb;\n}\n.ri-annotationHintDesc {\n font-size: 12px;\n opacity: 0.6;\n margin-top: 2px;\n color: #d1d5db;\n}\n@keyframes slideUp {\n from {\n opacity: 0;\n transform: translateY(10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.ri-canvasArea .bb-loadingOverlay {\n position: absolute;\n inset: 0;\n z-index: 12;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 16px;\n padding: 24px;\n text-align: center;\n border-radius: 14px;\n background: rgba(15, 23, 42, 0.46);\n backdrop-filter: blur(1px);\n}\n.ri-canvasArea .bb-loadingSpinner {\n width: 40px;\n height: 40px;\n border-radius: 999px;\n border: 3px solid rgba(226, 232, 240, 0.24);\n border-top-color: #93c5fd;\n animation: bb-spin 0.8s linear infinite;\n}\n.ri-canvasArea .bb-loadingMessage {\n max-width: 320px;\n color: #f7f8f9;\n font-size: 13px;\n font-weight: 600;\n line-height: 1.5;\n}\n@keyframes bb-spin {\n to {\n transform: rotate(360deg);\n }\n}\n.ri-largeModelBanner {\n position: absolute;\n top: 12px;\n left: 50%;\n transform: translateX(-50%);\n z-index: 20;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 7px 12px 7px 10px;\n border-radius: 8px;\n background: rgba(30, 20, 0, 0.88);\n border: 1px solid rgba(251, 191, 36, 0.35);\n backdrop-filter: blur(6px);\n -webkit-backdrop-filter: blur(6px);\n color: rgb(251, 191, 36);\n font-size: 12px;\n font-weight: 500;\n line-height: 1.4;\n pointer-events: auto;\n max-width: calc(100% - 32px);\n white-space: normal;\n text-align: center;\n}\n.ri-largeModelBanner--offset {\n top: 64px;\n}\n.ri-largeModelBanner__icon {\n flex-shrink: 0;\n}\n.ri-largeModelBanner__dismiss {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n margin-left: 4px;\n padding: 2px;\n background: none;\n border: none;\n border-radius: 4px;\n color: rgba(251, 191, 36, 0.7);\n cursor: pointer;\n transition: color 0.15s ease, background 0.15s ease;\n}\n.ri-largeModelBanner__dismiss:hover {\n color: rgb(251, 191, 36);\n background: rgba(251, 191, 36, 0.12);\n}\n@media (max-width: 768px) {\n .ri-xrTools {\n gap: 6px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 6px;\n padding: 0 8px;\n font-size: 11px;\n }\n .ri-xrButton svg {\n width: 14px;\n height: 14px;\n }\n .ri-xrButtonPrefix {\n display: none;\n }\n .ri-annotationNavSingle {\n bottom: 100px;\n }\n .ri-navButtonSingle {\n padding: 8px 20px;\n font-size: 13px;\n min-width: 110px;\n }\n .ri-annotationTitleHint {\n padding: 6px 14px;\n font-size: 12px;\n max-width: 250px;\n }\n .ri-viewAnnotationPopup {\n min-width: 160px;\n max-width: 220px;\n padding: 10px 12px;\n }\n .ri-viewAnnotationTitle {\n font-size: 13px;\n }\n .ri-viewAnnotationDescription {\n font-size: 12px;\n }\n .ri-buttonContainer {\n flex-wrap: wrap;\n gap: 6px;\n }\n .ri-tourControls {\n flex-wrap: wrap;\n }\n .ri-rightControls {\n flex-wrap: wrap;\n }\n}\n@media (max-width: 480px) {\n .ri-xrTools {\n gap: 5px;\n }\n .ri-xrButton {\n height: 34px;\n gap: 5px;\n font-size: 10px;\n }\n .ri-xrButton svg {\n width: 13px;\n height: 13px;\n }\n .ri-annotationNavSingle {\n bottom: 80px;\n }\n .ri-navButtonSingle {\n padding: 6px 16px;\n font-size: 12px;\n min-width: 90px;\n gap: 6px;\n }\n .ri-annotationTitleHint {\n padding: 4px 10px;\n font-size: 11px;\n max-width: 200px;\n }\n .ri-navBadge {\n font-size: 10px;\n padding: 1px 6px;\n }\n .ri-tourButton {\n padding: 0 8px;\n font-size: 12px;\n }\n .ri-tourButton span {\n display: none;\n }\n .ri-downloadButton {\n padding: 0 10px;\n font-size: 12px;\n }\n .ri-downloadButton span {\n display: none;\n }\n}\n');
|
|
246
246
|
|
|
247
247
|
// src/components/LicenseOverlay.tsx
|
|
248
248
|
var import_lucide_react = require("lucide-react");
|
|
@@ -367,6 +367,208 @@ function exportSceneMesh({
|
|
|
367
367
|
});
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
+
// src/components/ModelViewer/internal/exportUvLayout.ts
|
|
371
|
+
var import_fflate = require("fflate");
|
|
372
|
+
var SVG_SIZE = 2048;
|
|
373
|
+
var MESH_COLORS = [
|
|
374
|
+
"#00e5ff",
|
|
375
|
+
"#ff4fd8",
|
|
376
|
+
"#ffe45e",
|
|
377
|
+
"#7cff6b",
|
|
378
|
+
"#ff784f",
|
|
379
|
+
"#a78bfa",
|
|
380
|
+
"#38bdf8",
|
|
381
|
+
"#fb7185"
|
|
382
|
+
];
|
|
383
|
+
function escapeXml(value) {
|
|
384
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
385
|
+
}
|
|
386
|
+
function slugify(value, fallback) {
|
|
387
|
+
const slug = value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
388
|
+
return slug || fallback;
|
|
389
|
+
}
|
|
390
|
+
function safeFilenameStem(value) {
|
|
391
|
+
return slugify(value, "model");
|
|
392
|
+
}
|
|
393
|
+
function getVertexIndex(geometry, elementIndex) {
|
|
394
|
+
return geometry.index ? Math.round(geometry.index.getX(elementIndex)) : elementIndex;
|
|
395
|
+
}
|
|
396
|
+
function readTriangle(geometry, uv, elementIndex) {
|
|
397
|
+
const a = getVertexIndex(geometry, elementIndex);
|
|
398
|
+
const b = getVertexIndex(geometry, elementIndex + 1);
|
|
399
|
+
const c = getVertexIndex(geometry, elementIndex + 2);
|
|
400
|
+
if (a >= uv.count || b >= uv.count || c >= uv.count) return null;
|
|
401
|
+
const values = [
|
|
402
|
+
{ index: a, u: uv.getX(a), v: uv.getY(a) },
|
|
403
|
+
{ index: b, u: uv.getX(b), v: uv.getY(b) },
|
|
404
|
+
{ index: c, u: uv.getX(c), v: uv.getY(c) }
|
|
405
|
+
];
|
|
406
|
+
return values.every(
|
|
407
|
+
(vertex) => Number.isFinite(vertex.u) && Number.isFinite(vertex.v)
|
|
408
|
+
) ? values : null;
|
|
409
|
+
}
|
|
410
|
+
function uvPointKey(vertex) {
|
|
411
|
+
return `${vertex.u.toFixed(6)},${vertex.v.toFixed(6)}`;
|
|
412
|
+
}
|
|
413
|
+
function addEdge(edges, start, end, indexed) {
|
|
414
|
+
let startKey;
|
|
415
|
+
let endKey;
|
|
416
|
+
if (indexed) {
|
|
417
|
+
startKey = start.index;
|
|
418
|
+
endKey = end.index;
|
|
419
|
+
} else {
|
|
420
|
+
startKey = uvPointKey(start);
|
|
421
|
+
endKey = uvPointKey(end);
|
|
422
|
+
}
|
|
423
|
+
const key = startKey < endKey ? `${startKey}|${endKey}` : `${endKey}|${startKey}`;
|
|
424
|
+
const existing = edges.get(key);
|
|
425
|
+
if (existing) {
|
|
426
|
+
existing.count += 1;
|
|
427
|
+
} else {
|
|
428
|
+
edges.set(key, { count: 1, start, end });
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function addTriangleEdges(edges, triangle, indexed) {
|
|
432
|
+
addEdge(edges, triangle[0], triangle[1], indexed);
|
|
433
|
+
addEdge(edges, triangle[1], triangle[2], indexed);
|
|
434
|
+
addEdge(edges, triangle[2], triangle[0], indexed);
|
|
435
|
+
}
|
|
436
|
+
function materialForIndex(material, materialIndex) {
|
|
437
|
+
if (!Array.isArray(material)) return material;
|
|
438
|
+
return material[materialIndex] ?? null;
|
|
439
|
+
}
|
|
440
|
+
function collectUvLayouts(scene) {
|
|
441
|
+
const layouts = /* @__PURE__ */ new Map();
|
|
442
|
+
let unnamedMeshIndex = 0;
|
|
443
|
+
let unnamedMaterialIndex = 0;
|
|
444
|
+
let meshColorIndex = 0;
|
|
445
|
+
scene.traverse((node) => {
|
|
446
|
+
const mesh = node;
|
|
447
|
+
if (!mesh.isMesh || !mesh.geometry || !mesh.material) return;
|
|
448
|
+
const geometry = mesh.geometry;
|
|
449
|
+
const uv = geometry.getAttribute("uv");
|
|
450
|
+
if (!uv || uv.itemSize < 2 || uv.count < 3) return;
|
|
451
|
+
unnamedMeshIndex += 1;
|
|
452
|
+
const meshName = mesh.name || `Mesh ${unnamedMeshIndex}`;
|
|
453
|
+
const elementCount = geometry.index?.count ?? uv.count;
|
|
454
|
+
const groups = geometry.groups.length > 0 ? geometry.groups : [{ start: 0, count: elementCount, materialIndex: 0 }];
|
|
455
|
+
for (const group of groups) {
|
|
456
|
+
const materialIndex = group.materialIndex ?? 0;
|
|
457
|
+
const material = materialForIndex(mesh.material, materialIndex);
|
|
458
|
+
if (!material) continue;
|
|
459
|
+
let layout = layouts.get(material.uuid);
|
|
460
|
+
if (!layout) {
|
|
461
|
+
unnamedMaterialIndex += 1;
|
|
462
|
+
layout = {
|
|
463
|
+
name: material.name || `Material ${unnamedMaterialIndex}`,
|
|
464
|
+
layers: /* @__PURE__ */ new Map()
|
|
465
|
+
};
|
|
466
|
+
layouts.set(material.uuid, layout);
|
|
467
|
+
}
|
|
468
|
+
let layer = layout.layers.get(mesh.uuid);
|
|
469
|
+
if (!layer) {
|
|
470
|
+
layer = {
|
|
471
|
+
name: meshName,
|
|
472
|
+
color: MESH_COLORS[meshColorIndex % MESH_COLORS.length],
|
|
473
|
+
edges: /* @__PURE__ */ new Map()
|
|
474
|
+
};
|
|
475
|
+
meshColorIndex += 1;
|
|
476
|
+
layout.layers.set(mesh.uuid, layer);
|
|
477
|
+
}
|
|
478
|
+
const start = Math.max(0, group.start);
|
|
479
|
+
const end = Math.min(elementCount, start + group.count);
|
|
480
|
+
for (let elementIndex = start; elementIndex + 2 < end; elementIndex += 3) {
|
|
481
|
+
const triangle = readTriangle(geometry, uv, elementIndex);
|
|
482
|
+
if (triangle) {
|
|
483
|
+
addTriangleEdges(layer.edges, triangle, Boolean(geometry.index));
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
return [...layouts.values()].filter(
|
|
489
|
+
(layout) => [...layout.layers.values()].some((layer) => layer.edges.size > 0)
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
function coordinate(value) {
|
|
493
|
+
return (value * SVG_SIZE).toFixed(2);
|
|
494
|
+
}
|
|
495
|
+
function edgePath(edge) {
|
|
496
|
+
return `M${coordinate(edge.start.u)} ${coordinate(1 - edge.start.v)}L${coordinate(edge.end.u)} ${coordinate(1 - edge.end.v)}`;
|
|
497
|
+
}
|
|
498
|
+
function renderSvg(layout) {
|
|
499
|
+
const materialName = escapeXml(layout.name);
|
|
500
|
+
const layers = [...layout.layers.values()].filter((layer) => layer.edges.size > 0).map((layer, index) => {
|
|
501
|
+
const name = escapeXml(layer.name);
|
|
502
|
+
const id = `${slugify(layer.name, "mesh")}-${index + 1}`;
|
|
503
|
+
const allEdges = [...layer.edges.values()];
|
|
504
|
+
const boundaryEdges = allEdges.filter((edge) => edge.count === 1);
|
|
505
|
+
const visibleEdges = boundaryEdges.length > 0 ? boundaryEdges : allEdges;
|
|
506
|
+
const paths = visibleEdges.map(edgePath).join("");
|
|
507
|
+
return [
|
|
508
|
+
` <g id="${id}" data-mesh="${name}" fill="none" stroke="${layer.color}"`,
|
|
509
|
+
' stroke-width="1" stroke-linejoin="round" vector-effect="non-scaling-stroke">',
|
|
510
|
+
` <title>${name}</title>`,
|
|
511
|
+
` <path d="${paths}"/>`,
|
|
512
|
+
" </g>"
|
|
513
|
+
].join("\n");
|
|
514
|
+
}).join("\n");
|
|
515
|
+
return [
|
|
516
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
517
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${SVG_SIZE}" height="${SVG_SIZE}" viewBox="0 0 ${SVG_SIZE} ${SVG_SIZE}">`,
|
|
518
|
+
` <title>${materialName} UV layout</title>`,
|
|
519
|
+
" <desc>UV0 island-outline template exported by React Immersive. The SVG canvas maps to the complete 0\u20131 texture area.</desc>",
|
|
520
|
+
' <rect width="2048" height="2048" fill="none" stroke="#94a3b8" stroke-width="2"/>',
|
|
521
|
+
layers,
|
|
522
|
+
"</svg>"
|
|
523
|
+
].join("\n");
|
|
524
|
+
}
|
|
525
|
+
function buildUvLayoutFiles(scene, downloadFilename = "model") {
|
|
526
|
+
const layouts = collectUvLayouts(scene);
|
|
527
|
+
const stem = safeFilenameStem(downloadFilename);
|
|
528
|
+
return layouts.map((layout, index) => {
|
|
529
|
+
const materialSlug = slugify(layout.name, `material-${index + 1}`);
|
|
530
|
+
const filename = layouts.length === 1 ? `${stem}-uv-layout.svg` : `${stem}-uv-${materialSlug}-${index + 1}.svg`;
|
|
531
|
+
return {
|
|
532
|
+
filename,
|
|
533
|
+
materialName: layout.name,
|
|
534
|
+
meshCount: [...layout.layers.values()].filter(
|
|
535
|
+
(layer) => layer.edges.size > 0
|
|
536
|
+
).length,
|
|
537
|
+
svg: renderSvg(layout)
|
|
538
|
+
};
|
|
539
|
+
});
|
|
540
|
+
}
|
|
541
|
+
function downloadBlob(blob, filename) {
|
|
542
|
+
const url = URL.createObjectURL(blob);
|
|
543
|
+
const anchor = document.createElement("a");
|
|
544
|
+
anchor.href = url;
|
|
545
|
+
anchor.download = filename;
|
|
546
|
+
document.body.appendChild(anchor);
|
|
547
|
+
anchor.click();
|
|
548
|
+
document.body.removeChild(anchor);
|
|
549
|
+
URL.revokeObjectURL(url);
|
|
550
|
+
}
|
|
551
|
+
function downloadUvLayouts(scene, downloadFilename = "model") {
|
|
552
|
+
const files = buildUvLayoutFiles(scene, downloadFilename);
|
|
553
|
+
if (files.length === 0) return files;
|
|
554
|
+
if (files.length === 1) {
|
|
555
|
+
downloadBlob(
|
|
556
|
+
new Blob([files[0].svg], { type: "image/svg+xml;charset=utf-8" }),
|
|
557
|
+
files[0].filename
|
|
558
|
+
);
|
|
559
|
+
return files;
|
|
560
|
+
}
|
|
561
|
+
const archive = Object.fromEntries(
|
|
562
|
+
files.map((file) => [file.filename, (0, import_fflate.strToU8)(file.svg)])
|
|
563
|
+
);
|
|
564
|
+
const zipped = (0, import_fflate.zipSync)(archive, { level: 6 });
|
|
565
|
+
downloadBlob(
|
|
566
|
+
new Blob([zipped], { type: "application/zip" }),
|
|
567
|
+
`${safeFilenameStem(downloadFilename)}-uv-layouts.zip`
|
|
568
|
+
);
|
|
569
|
+
return files;
|
|
570
|
+
}
|
|
571
|
+
|
|
370
572
|
// src/components/ModelViewer/internal/captureImage.ts
|
|
371
573
|
var import_three2 = require("three");
|
|
372
574
|
function captureImage(capture, options = {}) {
|
|
@@ -3438,19 +3640,31 @@ function useCameraBiasedAnchor(annotation, worldPositionsRef) {
|
|
|
3438
3640
|
});
|
|
3439
3641
|
return anchorRef;
|
|
3440
3642
|
}
|
|
3441
|
-
function AnnotationMarker3D({
|
|
3643
|
+
function AnnotationMarker3D({
|
|
3644
|
+
annotation,
|
|
3645
|
+
occluders,
|
|
3646
|
+
worldPositionsRef,
|
|
3647
|
+
onClick,
|
|
3648
|
+
onHover,
|
|
3649
|
+
showAnnotationOnHover
|
|
3650
|
+
}) {
|
|
3442
3651
|
const [occluded, setOccluded] = (0, import_react12.useState)(false);
|
|
3443
3652
|
const anchorRef = useCameraBiasedAnchor(annotation, worldPositionsRef);
|
|
3444
3653
|
const [isHovered, setIsHovered] = (0, import_react12.useState)(false);
|
|
3445
3654
|
const [zIndex, setZIndex] = (0, import_react12.useState)(1);
|
|
3446
3655
|
const clickTimeoutRef = (0, import_react12.useRef)(null);
|
|
3447
3656
|
const markerRef = (0, import_react12.useRef)(null);
|
|
3657
|
+
const hoverTimeoutRef = (0, import_react12.useRef)(null);
|
|
3448
3658
|
(0, import_react12.useEffect)(() => {
|
|
3449
3659
|
return () => {
|
|
3450
3660
|
if (clickTimeoutRef.current) {
|
|
3451
3661
|
clearTimeout(clickTimeoutRef.current);
|
|
3452
3662
|
clickTimeoutRef.current = null;
|
|
3453
3663
|
}
|
|
3664
|
+
if (hoverTimeoutRef.current) {
|
|
3665
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
3666
|
+
hoverTimeoutRef.current = null;
|
|
3667
|
+
}
|
|
3454
3668
|
};
|
|
3455
3669
|
}, []);
|
|
3456
3670
|
const handleClick = (event) => {
|
|
@@ -3469,13 +3683,29 @@ function AnnotationMarker3D({ annotation, occluders, worldPositionsRef, onClick,
|
|
|
3469
3683
|
event.stopPropagation();
|
|
3470
3684
|
setIsHovered(true);
|
|
3471
3685
|
setZIndex(100);
|
|
3686
|
+
if (hoverTimeoutRef.current) {
|
|
3687
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
3688
|
+
hoverTimeoutRef.current = null;
|
|
3689
|
+
}
|
|
3472
3690
|
onHover?.(annotation);
|
|
3691
|
+
if (showAnnotationOnHover) {
|
|
3692
|
+
onClick?.(annotation);
|
|
3693
|
+
}
|
|
3473
3694
|
};
|
|
3474
3695
|
const handlePointerLeave = (event) => {
|
|
3475
3696
|
event.stopPropagation();
|
|
3476
3697
|
setIsHovered(false);
|
|
3477
3698
|
setZIndex(1);
|
|
3478
|
-
|
|
3699
|
+
if (hoverTimeoutRef.current) {
|
|
3700
|
+
clearTimeout(hoverTimeoutRef.current);
|
|
3701
|
+
}
|
|
3702
|
+
hoverTimeoutRef.current = window.setTimeout(() => {
|
|
3703
|
+
onHover?.(null);
|
|
3704
|
+
if (showAnnotationOnHover) {
|
|
3705
|
+
onHover?.(null);
|
|
3706
|
+
}
|
|
3707
|
+
hoverTimeoutRef.current = null;
|
|
3708
|
+
}, 50);
|
|
3479
3709
|
};
|
|
3480
3710
|
const handlePointerDown = (event) => {
|
|
3481
3711
|
event.stopPropagation();
|
|
@@ -3600,7 +3830,8 @@ function AnnotationsLayer({
|
|
|
3600
3830
|
onAnnotationClick,
|
|
3601
3831
|
onAnnotationHover,
|
|
3602
3832
|
onCloseAnnotation,
|
|
3603
|
-
hideCloseButton = false
|
|
3833
|
+
hideCloseButton = false,
|
|
3834
|
+
showAnnotationOnHover = false
|
|
3604
3835
|
}) {
|
|
3605
3836
|
const invalidate = (0, import_fiber8.useThree)((state) => state.invalidate);
|
|
3606
3837
|
const scene = (0, import_fiber8.useThree)((state) => state.scene);
|
|
@@ -3608,6 +3839,7 @@ function AnnotationsLayer({
|
|
|
3608
3839
|
const [isLocked, setIsLocked] = (0, import_react12.useState)(false);
|
|
3609
3840
|
const groupRef = (0, import_react12.useRef)(null);
|
|
3610
3841
|
const invalidateFrameRef = (0, import_react12.useRef)(null);
|
|
3842
|
+
const hoverCloseTimeoutRef = (0, import_react12.useRef)(null);
|
|
3611
3843
|
const occluders = (0, import_react12.useMemo)(() => {
|
|
3612
3844
|
const list = [];
|
|
3613
3845
|
scene.traverse((object) => {
|
|
@@ -3638,6 +3870,9 @@ function AnnotationsLayer({
|
|
|
3638
3870
|
if (invalidateFrameRef.current !== null) {
|
|
3639
3871
|
window.cancelAnimationFrame(invalidateFrameRef.current);
|
|
3640
3872
|
}
|
|
3873
|
+
if (hoverCloseTimeoutRef.current) {
|
|
3874
|
+
clearTimeout(hoverCloseTimeoutRef.current);
|
|
3875
|
+
}
|
|
3641
3876
|
};
|
|
3642
3877
|
}, []);
|
|
3643
3878
|
const activeAnnotationToShow = isLocked ? localActiveAnnotation : activeAnnotation || localActiveAnnotation;
|
|
@@ -3649,53 +3884,71 @@ function AnnotationsLayer({
|
|
|
3649
3884
|
(annotation) => annotation.id === activeAnnotationToShow.id
|
|
3650
3885
|
) ?? activeAnnotationToShow;
|
|
3651
3886
|
}, [activeAnnotationToShow, annotations]);
|
|
3652
|
-
const handleAnnotationClick = (0, import_react12.useCallback)(
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3887
|
+
const handleAnnotationClick = (0, import_react12.useCallback)(
|
|
3888
|
+
(annotation) => {
|
|
3889
|
+
if (isLocked && localActiveAnnotation?.id === annotation.id) {
|
|
3890
|
+
setIsLocked(false);
|
|
3891
|
+
setLocalActiveAnnotation(null);
|
|
3892
|
+
onCloseAnnotation?.();
|
|
3893
|
+
return;
|
|
3894
|
+
}
|
|
3895
|
+
setIsLocked(true);
|
|
3896
|
+
setLocalActiveAnnotation(annotation);
|
|
3897
|
+
onAnnotationClick?.(annotation);
|
|
3898
|
+
},
|
|
3899
|
+
[isLocked, localActiveAnnotation, onAnnotationClick, onCloseAnnotation]
|
|
3900
|
+
);
|
|
3901
|
+
const handleAnnotationHover = (0, import_react12.useCallback)(
|
|
3902
|
+
(annotation) => {
|
|
3665
3903
|
onAnnotationHover?.(annotation);
|
|
3666
|
-
if (
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3904
|
+
if (showAnnotationOnHover && !isLocked) {
|
|
3905
|
+
if (hoverCloseTimeoutRef.current) {
|
|
3906
|
+
clearTimeout(hoverCloseTimeoutRef.current);
|
|
3907
|
+
hoverCloseTimeoutRef.current = null;
|
|
3908
|
+
}
|
|
3909
|
+
if (annotation) {
|
|
3910
|
+
setLocalActiveAnnotation(annotation);
|
|
3911
|
+
} else {
|
|
3912
|
+
hoverCloseTimeoutRef.current = window.setTimeout(() => {
|
|
3913
|
+
setLocalActiveAnnotation(null);
|
|
3914
|
+
onCloseAnnotation?.();
|
|
3915
|
+
hoverCloseTimeoutRef.current = null;
|
|
3916
|
+
}, 100);
|
|
3671
3917
|
}
|
|
3672
3918
|
}
|
|
3673
|
-
}
|
|
3674
|
-
|
|
3919
|
+
},
|
|
3920
|
+
[isLocked, onAnnotationHover, showAnnotationOnHover, onCloseAnnotation]
|
|
3921
|
+
);
|
|
3675
3922
|
const handleClose = (0, import_react12.useCallback)(() => {
|
|
3676
3923
|
setIsLocked(false);
|
|
3677
3924
|
setLocalActiveAnnotation(null);
|
|
3678
3925
|
onCloseAnnotation?.();
|
|
3679
3926
|
}, [onCloseAnnotation]);
|
|
3680
|
-
const handleCanvasClick = (0, import_react12.useCallback)(
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3927
|
+
const handleCanvasClick = (0, import_react12.useCallback)(
|
|
3928
|
+
(event) => {
|
|
3929
|
+
const target = event.target;
|
|
3930
|
+
if (target.closest?.(".ri-annotationMarker") || target.closest?.(".ri-viewAnnotationPopup") || target.closest?.(".annotation-html-wrapper")) {
|
|
3931
|
+
return;
|
|
3932
|
+
}
|
|
3933
|
+
const canvas = canvasRootRef.current;
|
|
3934
|
+
if (canvas && canvas.contains(target)) {
|
|
3935
|
+
setIsLocked(false);
|
|
3936
|
+
setLocalActiveAnnotation(null);
|
|
3937
|
+
onCloseAnnotation?.();
|
|
3938
|
+
}
|
|
3939
|
+
},
|
|
3940
|
+
[canvasRootRef, onCloseAnnotation]
|
|
3941
|
+
);
|
|
3942
|
+
const handleEscapeKey = (0, import_react12.useCallback)(
|
|
3943
|
+
(event) => {
|
|
3944
|
+
if (event.key === "Escape" && isLocked) {
|
|
3945
|
+
setIsLocked(false);
|
|
3946
|
+
setLocalActiveAnnotation(null);
|
|
3947
|
+
onCloseAnnotation?.();
|
|
3948
|
+
}
|
|
3949
|
+
},
|
|
3950
|
+
[isLocked, onCloseAnnotation]
|
|
3951
|
+
);
|
|
3699
3952
|
(0, import_react12.useEffect)(() => {
|
|
3700
3953
|
const canvas = canvasRootRef.current;
|
|
3701
3954
|
if (canvas) {
|
|
@@ -3745,7 +3998,8 @@ function AnnotationsLayer({
|
|
|
3745
3998
|
occluders,
|
|
3746
3999
|
worldPositionsRef,
|
|
3747
4000
|
onClick: handleAnnotationClick,
|
|
3748
|
-
onHover: handleAnnotationHover
|
|
4001
|
+
onHover: handleAnnotationHover,
|
|
4002
|
+
showAnnotationOnHover
|
|
3749
4003
|
},
|
|
3750
4004
|
stableKey
|
|
3751
4005
|
);
|
|
@@ -4099,18 +4353,75 @@ function CinematicCamera({
|
|
|
4099
4353
|
sceneRef,
|
|
4100
4354
|
config,
|
|
4101
4355
|
playing,
|
|
4356
|
+
resetOnPlay = false,
|
|
4102
4357
|
onFinished,
|
|
4103
|
-
onUserInteract
|
|
4358
|
+
onUserInteract,
|
|
4359
|
+
onStop
|
|
4104
4360
|
}) {
|
|
4105
4361
|
const invalidate = (0, import_fiber10.useThree)((state) => state.invalidate);
|
|
4106
4362
|
const waypoints = (0, import_react15.useMemo)(() => config.waypoints ?? [], [config.waypoints]);
|
|
4107
4363
|
const hasPath = waypoints.length >= 2;
|
|
4108
4364
|
const loop = config.loop ?? true;
|
|
4109
|
-
const
|
|
4365
|
+
const calculatedDuration = (0, import_react15.useMemo)(() => {
|
|
4366
|
+
if (waypoints.length < 2) return 24;
|
|
4367
|
+
let total = 0;
|
|
4368
|
+
for (let i = 0; i < waypoints.length - 1; i++) {
|
|
4369
|
+
total += waypoints[i].segmentDuration ?? 3.5;
|
|
4370
|
+
}
|
|
4371
|
+
if (loop && waypoints.length > 1) {
|
|
4372
|
+
total += waypoints[waypoints.length - 1].segmentDuration ?? 3.5;
|
|
4373
|
+
}
|
|
4374
|
+
return Math.max(total, 4);
|
|
4375
|
+
}, [waypoints, loop]);
|
|
4376
|
+
const duration = config.duration ?? calculatedDuration;
|
|
4110
4377
|
const signature = (0, import_react15.useMemo)(
|
|
4111
|
-
() => waypoints.map(
|
|
4378
|
+
() => waypoints.map(
|
|
4379
|
+
(waypoint) => [
|
|
4380
|
+
...waypoint.position,
|
|
4381
|
+
...waypoint.target,
|
|
4382
|
+
waypoint.azimuthAngle ?? "",
|
|
4383
|
+
waypoint.polarAngle ?? "",
|
|
4384
|
+
waypoint.segmentDuration ?? ""
|
|
4385
|
+
].join(",")
|
|
4386
|
+
).join(";"),
|
|
4112
4387
|
[waypoints]
|
|
4113
4388
|
);
|
|
4389
|
+
const segmentTimes = (0, import_react15.useMemo)(() => {
|
|
4390
|
+
const times = [];
|
|
4391
|
+
if (waypoints.length < 2) return times;
|
|
4392
|
+
let accumulated = 0;
|
|
4393
|
+
for (let i = 0; i < waypoints.length - 1; i++) {
|
|
4394
|
+
accumulated += waypoints[i].segmentDuration ?? 3.5;
|
|
4395
|
+
times.push(accumulated);
|
|
4396
|
+
}
|
|
4397
|
+
if (loop && waypoints.length > 1) {
|
|
4398
|
+
accumulated += waypoints[waypoints.length - 1].segmentDuration ?? 3.5;
|
|
4399
|
+
times.push(accumulated);
|
|
4400
|
+
}
|
|
4401
|
+
return times;
|
|
4402
|
+
}, [waypoints, loop]);
|
|
4403
|
+
const getCurveParameter = (0, import_react15.useMemo)(() => {
|
|
4404
|
+
return (elapsed) => {
|
|
4405
|
+
if (waypoints.length < 2 || segmentTimes.length === 0) {
|
|
4406
|
+
return elapsed / duration;
|
|
4407
|
+
}
|
|
4408
|
+
const segmentTotal = segmentTimes[segmentTimes.length - 1];
|
|
4409
|
+
const playbackElapsed = loop ? elapsed % duration : Math.min(elapsed, duration);
|
|
4410
|
+
const normalizedElapsed = duration > 0 ? playbackElapsed / duration * segmentTotal : 0;
|
|
4411
|
+
for (let i = 0; i < segmentTimes.length; i++) {
|
|
4412
|
+
if (normalizedElapsed <= segmentTimes[i]) {
|
|
4413
|
+
const prevTime = i === 0 ? 0 : segmentTimes[i - 1];
|
|
4414
|
+
const segmentDuration = segmentTimes[i] - prevTime;
|
|
4415
|
+
const progress = segmentDuration > 0 ? (normalizedElapsed - prevTime) / segmentDuration : 0;
|
|
4416
|
+
const totalSegments = loop ? waypoints.length : waypoints.length - 1;
|
|
4417
|
+
const baseU = i / totalSegments;
|
|
4418
|
+
const segmentU = 1 / totalSegments;
|
|
4419
|
+
return baseU + progress * segmentU;
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
return 1;
|
|
4423
|
+
};
|
|
4424
|
+
}, [waypoints.length, segmentTimes, duration, loop]);
|
|
4114
4425
|
const curves = (0, import_react15.useMemo)(() => {
|
|
4115
4426
|
if (!hasPath) return null;
|
|
4116
4427
|
return {
|
|
@@ -4128,29 +4439,83 @@ function CinematicCamera({
|
|
|
4128
4439
|
)
|
|
4129
4440
|
};
|
|
4130
4441
|
}, [signature, hasPath, loop]);
|
|
4442
|
+
const orbitPath = (0, import_react15.useMemo)(() => {
|
|
4443
|
+
if (!hasPath || !waypoints.every(
|
|
4444
|
+
(waypoint) => Number.isFinite(waypoint.azimuthAngle) && Number.isFinite(waypoint.polarAngle)
|
|
4445
|
+
)) {
|
|
4446
|
+
return null;
|
|
4447
|
+
}
|
|
4448
|
+
const points = waypoints.map((waypoint) => ({
|
|
4449
|
+
azimuth: waypoint.azimuthAngle,
|
|
4450
|
+
polar: waypoint.polarAngle,
|
|
4451
|
+
distance: new import_three18.Vector3(...waypoint.position).distanceTo(
|
|
4452
|
+
new import_three18.Vector3(...waypoint.target)
|
|
4453
|
+
)
|
|
4454
|
+
}));
|
|
4455
|
+
for (let index = 1; index < points.length; index += 1) {
|
|
4456
|
+
const previous = points[index - 1].azimuth;
|
|
4457
|
+
const current = points[index].azimuth;
|
|
4458
|
+
points[index].azimuth = current + Math.round((previous - current) / (Math.PI * 2)) * Math.PI * 2;
|
|
4459
|
+
}
|
|
4460
|
+
return points;
|
|
4461
|
+
}, [signature, hasPath]);
|
|
4131
4462
|
const elapsedRef = (0, import_react15.useRef)(0);
|
|
4132
4463
|
const enterElapsedRef = (0, import_react15.useRef)(0);
|
|
4133
4464
|
const enterPos = (0, import_react15.useRef)(new import_three18.Vector3());
|
|
4134
4465
|
const enterTarget = (0, import_react15.useRef)(new import_three18.Vector3());
|
|
4135
4466
|
const startedRef = (0, import_react15.useRef)(false);
|
|
4136
4467
|
const pausedElapsedRef = (0, import_react15.useRef)(0);
|
|
4468
|
+
const hasFinishedRef = (0, import_react15.useRef)(false);
|
|
4137
4469
|
const orbitRef = (0, import_react15.useRef)(null);
|
|
4138
4470
|
const tmpPos = (0, import_react15.useMemo)(() => new import_three18.Vector3(), []);
|
|
4139
4471
|
const tmpTarget = (0, import_react15.useMemo)(() => new import_three18.Vector3(), []);
|
|
4472
|
+
const tmpSpherical = (0, import_react15.useMemo)(() => new import_three18.Spherical(), []);
|
|
4473
|
+
(0, import_react15.useEffect)(() => {
|
|
4474
|
+
elapsedRef.current = 0;
|
|
4475
|
+
pausedElapsedRef.current = 0;
|
|
4476
|
+
enterElapsedRef.current = 0;
|
|
4477
|
+
hasFinishedRef.current = false;
|
|
4478
|
+
}, [signature, loop, duration]);
|
|
4479
|
+
(0, import_react15.useEffect)(() => {
|
|
4480
|
+
if (resetOnPlay) {
|
|
4481
|
+
pausedElapsedRef.current = 0;
|
|
4482
|
+
elapsedRef.current = 0;
|
|
4483
|
+
hasFinishedRef.current = false;
|
|
4484
|
+
}
|
|
4485
|
+
}, [resetOnPlay]);
|
|
4140
4486
|
(0, import_react15.useEffect)(() => {
|
|
4141
4487
|
if (!playing) {
|
|
4142
4488
|
startedRef.current = false;
|
|
4143
|
-
if (elapsedRef.current > 0) {
|
|
4489
|
+
if (elapsedRef.current > 0 && !resetOnPlay) {
|
|
4144
4490
|
pausedElapsedRef.current = elapsedRef.current;
|
|
4491
|
+
} else if (resetOnPlay) {
|
|
4492
|
+
pausedElapsedRef.current = 0;
|
|
4493
|
+
}
|
|
4494
|
+
if (elapsedRef.current > 0 || hasFinishedRef.current) {
|
|
4495
|
+
onStop?.();
|
|
4145
4496
|
}
|
|
4146
4497
|
return;
|
|
4147
4498
|
}
|
|
4499
|
+
if (elapsedRef.current >= duration && !loop) {
|
|
4500
|
+
elapsedRef.current = 0;
|
|
4501
|
+
pausedElapsedRef.current = 0;
|
|
4502
|
+
hasFinishedRef.current = false;
|
|
4503
|
+
}
|
|
4148
4504
|
const controls = controlsRef.current;
|
|
4149
4505
|
if (controls) {
|
|
4150
4506
|
controls.getPosition(enterPos.current);
|
|
4151
4507
|
controls.getTarget(enterTarget.current);
|
|
4152
4508
|
}
|
|
4153
|
-
|
|
4509
|
+
if (resetOnPlay || hasFinishedRef.current || pausedElapsedRef.current === 0) {
|
|
4510
|
+
elapsedRef.current = 0;
|
|
4511
|
+
pausedElapsedRef.current = 0;
|
|
4512
|
+
hasFinishedRef.current = false;
|
|
4513
|
+
} else if (pausedElapsedRef.current > 0 && pausedElapsedRef.current < duration) {
|
|
4514
|
+
elapsedRef.current = pausedElapsedRef.current;
|
|
4515
|
+
} else {
|
|
4516
|
+
elapsedRef.current = 0;
|
|
4517
|
+
pausedElapsedRef.current = 0;
|
|
4518
|
+
}
|
|
4154
4519
|
enterElapsedRef.current = 0;
|
|
4155
4520
|
if (!hasPath) {
|
|
4156
4521
|
const scene = sceneRef.current;
|
|
@@ -4164,12 +4529,26 @@ function CinematicCamera({
|
|
|
4164
4529
|
}
|
|
4165
4530
|
startedRef.current = true;
|
|
4166
4531
|
invalidate();
|
|
4167
|
-
}, [
|
|
4532
|
+
}, [
|
|
4533
|
+
playing,
|
|
4534
|
+
hasPath,
|
|
4535
|
+
controlsRef,
|
|
4536
|
+
sceneRef,
|
|
4537
|
+
invalidate,
|
|
4538
|
+
duration,
|
|
4539
|
+
loop,
|
|
4540
|
+
signature,
|
|
4541
|
+
resetOnPlay,
|
|
4542
|
+
onStop
|
|
4543
|
+
]);
|
|
4168
4544
|
(0, import_react15.useEffect)(() => {
|
|
4169
4545
|
if (!playing || !onUserInteract) return;
|
|
4170
4546
|
const controls = controlsRef.current;
|
|
4171
4547
|
if (!controls) return;
|
|
4172
|
-
const handle = () =>
|
|
4548
|
+
const handle = () => {
|
|
4549
|
+
if (hasFinishedRef.current) return;
|
|
4550
|
+
onUserInteract();
|
|
4551
|
+
};
|
|
4173
4552
|
controls.addEventListener("controlstart", handle);
|
|
4174
4553
|
return () => controls.removeEventListener("controlstart", handle);
|
|
4175
4554
|
}, [playing, controlsRef, onUserInteract]);
|
|
@@ -4179,12 +4558,33 @@ function CinematicCamera({
|
|
|
4179
4558
|
if (!controls) return;
|
|
4180
4559
|
elapsedRef.current += delta;
|
|
4181
4560
|
enterElapsedRef.current += delta;
|
|
4182
|
-
let u = elapsedRef.current
|
|
4183
|
-
const finished = !loop &&
|
|
4561
|
+
let u = getCurveParameter(elapsedRef.current);
|
|
4562
|
+
const finished = !loop && elapsedRef.current >= duration;
|
|
4184
4563
|
u = loop ? u % 1 : Math.min(u, 1);
|
|
4185
4564
|
if (curves) {
|
|
4186
|
-
curves.
|
|
4187
|
-
|
|
4565
|
+
curves.target.getPoint(u, tmpTarget);
|
|
4566
|
+
if (orbitPath) {
|
|
4567
|
+
const segmentCount = loop ? orbitPath.length : orbitPath.length - 1;
|
|
4568
|
+
const scaled = Math.min(u, 1) * segmentCount;
|
|
4569
|
+
const segment = Math.min(Math.floor(scaled), segmentCount - 1);
|
|
4570
|
+
const next = (segment + 1) % orbitPath.length;
|
|
4571
|
+
const localT = u >= 1 ? 1 : scaled - segment;
|
|
4572
|
+
const eased = localT * localT * (3 - 2 * localT);
|
|
4573
|
+
const start = orbitPath[segment];
|
|
4574
|
+
const end = orbitPath[next];
|
|
4575
|
+
const endAzimuth = end.azimuth + Math.round((start.azimuth - end.azimuth) / (Math.PI * 2)) * Math.PI * 2;
|
|
4576
|
+
const azimuth = start.azimuth + (endAzimuth - start.azimuth) * eased;
|
|
4577
|
+
const polar = start.polar + (end.polar - start.polar) * eased;
|
|
4578
|
+
const distance = start.distance + (end.distance - start.distance) * eased;
|
|
4579
|
+
tmpSpherical.set(
|
|
4580
|
+
Math.max(distance, 1e-6),
|
|
4581
|
+
Math.min(Math.max(polar, 1e-6), Math.PI - 1e-6),
|
|
4582
|
+
azimuth
|
|
4583
|
+
);
|
|
4584
|
+
tmpPos.setFromSpherical(tmpSpherical).add(tmpTarget);
|
|
4585
|
+
} else {
|
|
4586
|
+
curves.position.getPoint(u, tmpPos);
|
|
4587
|
+
}
|
|
4188
4588
|
} else {
|
|
4189
4589
|
const orbit = orbitRef.current;
|
|
4190
4590
|
if (!orbit) return;
|
|
@@ -4214,6 +4614,8 @@ function CinematicCamera({
|
|
|
4214
4614
|
);
|
|
4215
4615
|
if (finished) {
|
|
4216
4616
|
startedRef.current = false;
|
|
4617
|
+
hasFinishedRef.current = true;
|
|
4618
|
+
elapsedRef.current = 0;
|
|
4217
4619
|
pausedElapsedRef.current = 0;
|
|
4218
4620
|
onFinished?.();
|
|
4219
4621
|
return;
|
|
@@ -5470,7 +5872,8 @@ function ModelViewer({
|
|
|
5470
5872
|
usdzUrl,
|
|
5471
5873
|
arScaleMode = "normalized",
|
|
5472
5874
|
mobileHandoffUrl,
|
|
5473
|
-
showAnnotationNavigation = true
|
|
5875
|
+
showAnnotationNavigation = true,
|
|
5876
|
+
showAnnotationOnHover = false
|
|
5474
5877
|
}) {
|
|
5475
5878
|
const { activeTab, activeSubTab } = useBindingBuilderContext();
|
|
5476
5879
|
const license = useLicense(licenseKey);
|
|
@@ -5540,6 +5943,7 @@ function ModelViewer({
|
|
|
5540
5943
|
!modelUrl || Boolean(onAutoFit) || Boolean(camera)
|
|
5541
5944
|
);
|
|
5542
5945
|
const [downloadMenuOpen, setDownloadMenuOpen] = (0, import_react22.useState)(false);
|
|
5946
|
+
const [uvExportError, setUvExportError] = (0, import_react22.useState)(null);
|
|
5543
5947
|
const [modelLoaded, setModelLoaded] = (0, import_react22.useState)(false);
|
|
5544
5948
|
const [modelLoadFailed, setModelLoadFailed] = (0, import_react22.useState)(false);
|
|
5545
5949
|
const [bindingsMismatch, setBindingsMismatch] = (0, import_react22.useState)(false);
|
|
@@ -5594,9 +5998,11 @@ function ModelViewer({
|
|
|
5594
5998
|
return { ...base ?? {}, ...cinematic };
|
|
5595
5999
|
}, [cinematic, resolvedSceneConfig]);
|
|
5596
6000
|
const [cinematicPlaying, setCinematicPlaying] = (0, import_react22.useState)(false);
|
|
6001
|
+
const [cinematicReset, setCinematicReset] = (0, import_react22.useState)(false);
|
|
5597
6002
|
const cinematicAutoStartedRef = (0, import_react22.useRef)(false);
|
|
5598
6003
|
(0, import_react22.useEffect)(() => {
|
|
5599
6004
|
setCinematicPlaying(false);
|
|
6005
|
+
setCinematicReset(false);
|
|
5600
6006
|
cinematicAutoStartedRef.current = false;
|
|
5601
6007
|
}, [modelUrl]);
|
|
5602
6008
|
(0, import_react22.useEffect)(() => {
|
|
@@ -5604,6 +6010,7 @@ function ModelViewer({
|
|
|
5604
6010
|
if (modelLoaded && cameraPositioned && !isAutoFraming) {
|
|
5605
6011
|
cinematicAutoStartedRef.current = true;
|
|
5606
6012
|
setCinematicPlaying(true);
|
|
6013
|
+
setCinematicReset(false);
|
|
5607
6014
|
}
|
|
5608
6015
|
}, [cinematicConfig, modelLoaded, cameraPositioned, isAutoFraming]);
|
|
5609
6016
|
const xrStore = (0, import_react22.useMemo)(() => {
|
|
@@ -6728,6 +7135,17 @@ function ModelViewer({
|
|
|
6728
7135
|
console.error("Screenshot failed:", err);
|
|
6729
7136
|
});
|
|
6730
7137
|
}, [downloadFilename]);
|
|
7138
|
+
const handleDownloadUvLayout = (0, import_react22.useCallback)(() => {
|
|
7139
|
+
const scene = sceneRef.current;
|
|
7140
|
+
if (!scene) return;
|
|
7141
|
+
const files = downloadUvLayouts(scene, downloadFilename);
|
|
7142
|
+
if (files.length === 0) {
|
|
7143
|
+
setUvExportError("This model does not contain UV coordinates.");
|
|
7144
|
+
return;
|
|
7145
|
+
}
|
|
7146
|
+
setUvExportError(null);
|
|
7147
|
+
setDownloadMenuOpen(false);
|
|
7148
|
+
}, [downloadFilename]);
|
|
6731
7149
|
const handleReset = (0, import_react22.useCallback)(async () => {
|
|
6732
7150
|
if (onAutoFit) {
|
|
6733
7151
|
await onAutoFit();
|
|
@@ -6757,6 +7175,20 @@ function ModelViewer({
|
|
|
6757
7175
|
console.error("Reset camera error:", error);
|
|
6758
7176
|
}
|
|
6759
7177
|
}, [onAutoFit, fitScene]);
|
|
7178
|
+
const handleCinematicPlay = (0, import_react22.useCallback)(() => {
|
|
7179
|
+
setCinematicReset(false);
|
|
7180
|
+
setCinematicPlaying(true);
|
|
7181
|
+
}, []);
|
|
7182
|
+
const handleCinematicPause = (0, import_react22.useCallback)(() => {
|
|
7183
|
+
setCinematicPlaying(false);
|
|
7184
|
+
}, []);
|
|
7185
|
+
const handleCinematicStop = (0, import_react22.useCallback)(() => {
|
|
7186
|
+
setCinematicPlaying(false);
|
|
7187
|
+
setCinematicReset(true);
|
|
7188
|
+
setTimeout(() => {
|
|
7189
|
+
handleReset();
|
|
7190
|
+
}, 100);
|
|
7191
|
+
}, [handleReset]);
|
|
6760
7192
|
const handleCameraChangeWithAnnotations = (0, import_react22.useCallback)(
|
|
6761
7193
|
(cameraValue, controls) => {
|
|
6762
7194
|
handleCameraChangeInternal(cameraValue, controls);
|
|
@@ -7054,7 +7486,8 @@ function ModelViewer({
|
|
|
7054
7486
|
modelLoaded,
|
|
7055
7487
|
onAnnotationClick: handleAnnotationMarkerClick,
|
|
7056
7488
|
onCloseAnnotation: closeViewingAnnotation,
|
|
7057
|
-
hideCloseButton: isGuidedTourActive
|
|
7489
|
+
hideCloseButton: isGuidedTourActive,
|
|
7490
|
+
showAnnotationOnHover
|
|
7058
7491
|
}
|
|
7059
7492
|
),
|
|
7060
7493
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
@@ -7073,8 +7506,14 @@ function ModelViewer({
|
|
|
7073
7506
|
sceneRef,
|
|
7074
7507
|
config: cinematicConfig,
|
|
7075
7508
|
playing: cinematicPlaying,
|
|
7076
|
-
|
|
7077
|
-
|
|
7509
|
+
resetOnPlay: cinematicReset,
|
|
7510
|
+
onFinished: () => {
|
|
7511
|
+
setCinematicPlaying(false);
|
|
7512
|
+
setCinematicReset(false);
|
|
7513
|
+
},
|
|
7514
|
+
onUserInteract: () => {
|
|
7515
|
+
setCinematicPlaying(false);
|
|
7516
|
+
}
|
|
7078
7517
|
}
|
|
7079
7518
|
),
|
|
7080
7519
|
showMeasureTools && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
@@ -7302,24 +7741,43 @@ function ModelViewer({
|
|
|
7302
7741
|
]
|
|
7303
7742
|
}
|
|
7304
7743
|
),
|
|
7305
|
-
cinematicConfig && /* @__PURE__ */ (0, import_jsx_runtime19.
|
|
7744
|
+
cinematicConfig && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
7306
7745
|
"div",
|
|
7307
7746
|
{
|
|
7308
7747
|
className: "ri-cinematicTools",
|
|
7309
7748
|
style: {
|
|
7310
7749
|
top: `${topLeftRow((xrToolsVisible ? 1 : 0) + (showMeasureTools ? 1 : 0) + (showExplodeControls ? 1 : 0))}px`
|
|
7311
7750
|
},
|
|
7312
|
-
children:
|
|
7313
|
-
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
|
-
|
|
7317
|
-
|
|
7318
|
-
|
|
7319
|
-
|
|
7320
|
-
|
|
7321
|
-
|
|
7322
|
-
|
|
7751
|
+
children: [
|
|
7752
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
7753
|
+
"button",
|
|
7754
|
+
{
|
|
7755
|
+
type: "button",
|
|
7756
|
+
className: `ri-measureButton ${cinematicPlaying ? "ri-measureButton--active" : ""}`,
|
|
7757
|
+
onClick: () => {
|
|
7758
|
+
if (cinematicPlaying) {
|
|
7759
|
+
setCinematicPlaying(false);
|
|
7760
|
+
} else {
|
|
7761
|
+
setCinematicReset(false);
|
|
7762
|
+
setCinematicPlaying(true);
|
|
7763
|
+
}
|
|
7764
|
+
},
|
|
7765
|
+
title: cinematicPlaying ? "Pause cinematic" : "Play cinematic",
|
|
7766
|
+
"aria-pressed": cinematicPlaying,
|
|
7767
|
+
children: cinematicPlaying ? /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react3.Pause, { size: 16 }) : /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react3.Play, { size: 16 })
|
|
7768
|
+
}
|
|
7769
|
+
),
|
|
7770
|
+
cinematicPlaying && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
7771
|
+
"button",
|
|
7772
|
+
{
|
|
7773
|
+
type: "button",
|
|
7774
|
+
className: "ri-measureButton ri-measureButton--stop",
|
|
7775
|
+
onClick: handleCinematicStop,
|
|
7776
|
+
title: "Stop cinematic and reset camera",
|
|
7777
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react3.Square, { size: 16 })
|
|
7778
|
+
}
|
|
7779
|
+
)
|
|
7780
|
+
]
|
|
7323
7781
|
}
|
|
7324
7782
|
),
|
|
7325
7783
|
(showResetButton || showDownloadButton || showAnnotationNavigation && sortedAnnotations.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "ri-buttonContainer", children: [
|
|
@@ -7422,13 +7880,38 @@ function ModelViewer({
|
|
|
7422
7880
|
"Download PNG"
|
|
7423
7881
|
]
|
|
7424
7882
|
}
|
|
7883
|
+
),
|
|
7884
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "ri-downloadMenuDivider" }),
|
|
7885
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(
|
|
7886
|
+
"button",
|
|
7887
|
+
{
|
|
7888
|
+
className: "ri-downloadMenuItem",
|
|
7889
|
+
onClick: handleDownloadUvLayout,
|
|
7890
|
+
disabled: !sceneRef.current,
|
|
7891
|
+
children: [
|
|
7892
|
+
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(import_lucide_react3.FileImage, { size: 14 }),
|
|
7893
|
+
"Download UV Layout"
|
|
7894
|
+
]
|
|
7895
|
+
}
|
|
7896
|
+
),
|
|
7897
|
+
uvExportError && /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
7898
|
+
"div",
|
|
7899
|
+
{
|
|
7900
|
+
className: "ri-downloadMenuNotice",
|
|
7901
|
+
role: "status",
|
|
7902
|
+
"aria-live": "polite",
|
|
7903
|
+
children: uvExportError
|
|
7904
|
+
}
|
|
7425
7905
|
)
|
|
7426
7906
|
] }),
|
|
7427
7907
|
/* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
|
|
7428
7908
|
"button",
|
|
7429
7909
|
{
|
|
7430
7910
|
className: "ri-downloadButton",
|
|
7431
|
-
onClick: () =>
|
|
7911
|
+
onClick: () => {
|
|
7912
|
+
setUvExportError(null);
|
|
7913
|
+
setDownloadMenuOpen((v) => !v);
|
|
7914
|
+
},
|
|
7432
7915
|
disabled: isDownloading || !sceneRef.current,
|
|
7433
7916
|
title: "Download options",
|
|
7434
7917
|
children: isDownloading ? /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)(import_jsx_runtime19.Fragment, { children: [
|
|
@@ -7559,7 +8042,7 @@ function ModelViewer({
|
|
|
7559
8042
|
}
|
|
7560
8043
|
|
|
7561
8044
|
// src/components/BindingBuilder/BindingBuilder.tsx
|
|
7562
|
-
var
|
|
8045
|
+
var import_react39 = require("react");
|
|
7563
8046
|
var import_lucide_react16 = require("lucide-react");
|
|
7564
8047
|
|
|
7565
8048
|
// src/styles/BindingBuilder.css
|
|
@@ -7599,14 +8082,16 @@ function buildCameraState(camera, controls) {
|
|
|
7599
8082
|
position: toTuple(position),
|
|
7600
8083
|
target: toTuple(target),
|
|
7601
8084
|
fov: "fov" in camera && typeof camera.fov === "number" ? camera.fov : null,
|
|
7602
|
-
zoom: "zoom" in camera && typeof camera.zoom === "number" ? camera.zoom : 1
|
|
8085
|
+
zoom: "zoom" in camera && typeof camera.zoom === "number" ? camera.zoom : 1,
|
|
8086
|
+
azimuthAngle: controls.azimuthAngle,
|
|
8087
|
+
polarAngle: controls.polarAngle
|
|
7603
8088
|
};
|
|
7604
8089
|
}
|
|
7605
8090
|
function isSameCameraState(left, right) {
|
|
7606
8091
|
if (!left) {
|
|
7607
8092
|
return false;
|
|
7608
8093
|
}
|
|
7609
|
-
return left.position[0] === right.position[0] && left.position[1] === right.position[1] && left.position[2] === right.position[2] && left.target[0] === right.target[0] && left.target[1] === right.target[1] && left.target[2] === right.target[2] && left.fov === right.fov && left.zoom === right.zoom;
|
|
8094
|
+
return left.position[0] === right.position[0] && left.position[1] === right.position[1] && left.position[2] === right.position[2] && left.target[0] === right.target[0] && left.target[1] === right.target[1] && left.target[2] === right.target[2] && left.fov === right.fov && left.zoom === right.zoom && left.azimuthAngle === right.azimuthAngle && left.polarAngle === right.polarAngle;
|
|
7610
8095
|
}
|
|
7611
8096
|
async function runSmoothCameraTransition2(controls, action, smoothTime = 0.6) {
|
|
7612
8097
|
const originalSmoothTime = controls.smoothTime;
|
|
@@ -7761,15 +8246,27 @@ function useViewerCamera() {
|
|
|
7761
8246
|
}
|
|
7762
8247
|
try {
|
|
7763
8248
|
markInitialAutoFitOverride(controls);
|
|
7764
|
-
|
|
7765
|
-
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
7769
|
-
|
|
7770
|
-
|
|
7771
|
-
|
|
7772
|
-
|
|
8249
|
+
const transitions = [
|
|
8250
|
+
controls.setLookAt(
|
|
8251
|
+
state.position[0],
|
|
8252
|
+
state.position[1],
|
|
8253
|
+
state.position[2],
|
|
8254
|
+
state.target[0],
|
|
8255
|
+
state.target[1],
|
|
8256
|
+
state.target[2],
|
|
8257
|
+
enableTransition
|
|
8258
|
+
)
|
|
8259
|
+
];
|
|
8260
|
+
if (state.azimuthAngle != null && state.polarAngle != null) {
|
|
8261
|
+
transitions.push(
|
|
8262
|
+
controls.rotateTo(
|
|
8263
|
+
state.azimuthAngle,
|
|
8264
|
+
state.polarAngle,
|
|
8265
|
+
enableTransition
|
|
8266
|
+
)
|
|
8267
|
+
);
|
|
8268
|
+
}
|
|
8269
|
+
await Promise.all(transitions);
|
|
7773
8270
|
const camera = controls.camera;
|
|
7774
8271
|
let projectionChanged = false;
|
|
7775
8272
|
if (state.fov !== null && "fov" in camera && typeof camera.fov === "number") {
|
|
@@ -7893,7 +8390,7 @@ function useViewerAnimations() {
|
|
|
7893
8390
|
}
|
|
7894
8391
|
|
|
7895
8392
|
// src/utils/modelFileResolver.ts
|
|
7896
|
-
var
|
|
8393
|
+
var import_fflate2 = require("fflate");
|
|
7897
8394
|
var SUPPORTED_MODEL_EXTENSIONS = [".glb", ".gltf", ".zip"];
|
|
7898
8395
|
var SUPPORTED_MODEL_MIME_TYPES = [
|
|
7899
8396
|
"model/gltf-binary",
|
|
@@ -7951,7 +8448,7 @@ async function resolveStandaloneGltfFile(file) {
|
|
|
7951
8448
|
};
|
|
7952
8449
|
}
|
|
7953
8450
|
async function resolveZippedGltfFile(file) {
|
|
7954
|
-
const zip = (0,
|
|
8451
|
+
const zip = (0, import_fflate2.unzipSync)(new Uint8Array(await file.arrayBuffer()));
|
|
7955
8452
|
const entries = /* @__PURE__ */ new Map();
|
|
7956
8453
|
for (const [rawPath, bytes] of Object.entries(zip)) {
|
|
7957
8454
|
const path = normalizeZipPath(rawPath);
|
|
@@ -7972,13 +8469,13 @@ async function resolveZippedGltfFile(file) {
|
|
|
7972
8469
|
if (!gltfBytes) {
|
|
7973
8470
|
throw new Error("Invalid GLTF zip: no .gltf found.");
|
|
7974
8471
|
}
|
|
7975
|
-
const gltf = parseGltfJson((0,
|
|
8472
|
+
const gltf = parseGltfJson((0, import_fflate2.strFromU8)(gltfBytes), gltfPath);
|
|
7976
8473
|
const rootDir = getDirname(gltfPath);
|
|
7977
8474
|
const urlsToRevoke = [];
|
|
7978
8475
|
try {
|
|
7979
8476
|
rewriteZipUris(gltf.buffers, rootDir, entries, urlsToRevoke);
|
|
7980
8477
|
rewriteZipUris(gltf.images, rootDir, entries, urlsToRevoke);
|
|
7981
|
-
const gltfBlob = new Blob([(0,
|
|
8478
|
+
const gltfBlob = new Blob([(0, import_fflate2.strToU8)(JSON.stringify(gltf))], {
|
|
7982
8479
|
type: "model/gltf+json"
|
|
7983
8480
|
});
|
|
7984
8481
|
const gltfUrl = URL.createObjectURL(gltfBlob);
|
|
@@ -8077,7 +8574,7 @@ function inferMimeType(path) {
|
|
|
8077
8574
|
}
|
|
8078
8575
|
|
|
8079
8576
|
// src/components/BindingBuilder/SceneSettings/SceneSettingsTab.tsx
|
|
8080
|
-
var
|
|
8577
|
+
var import_react33 = require("react");
|
|
8081
8578
|
|
|
8082
8579
|
// src/components/BindingBuilder/SceneSettings/GeneralSceneSettings.tsx
|
|
8083
8580
|
var import_react27 = require("react");
|
|
@@ -8258,9 +8755,10 @@ function SliderRow({
|
|
|
8258
8755
|
max,
|
|
8259
8756
|
step,
|
|
8260
8757
|
formatValue,
|
|
8261
|
-
onChange
|
|
8758
|
+
onChange,
|
|
8759
|
+
disabled = false
|
|
8262
8760
|
}) {
|
|
8263
|
-
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("label", { className: "se-field"
|
|
8761
|
+
return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("label", { className: `se-field ${disabled ? "se-field--disabled" : ""}`, children: [
|
|
8264
8762
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { className: "se-fieldLabel", children: [
|
|
8265
8763
|
label,
|
|
8266
8764
|
/* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { className: "se-fieldValue", children: formatValue ? formatValue(value) : value })
|
|
@@ -8274,7 +8772,8 @@ function SliderRow({
|
|
|
8274
8772
|
max,
|
|
8275
8773
|
step,
|
|
8276
8774
|
value,
|
|
8277
|
-
onChange: (e) => onChange(Number(e.target.value))
|
|
8775
|
+
onChange: (e) => onChange(Number(e.target.value)),
|
|
8776
|
+
disabled
|
|
8278
8777
|
}
|
|
8279
8778
|
)
|
|
8280
8779
|
] });
|
|
@@ -11690,6 +12189,7 @@ function AnnotationsTab({ config, onChange }) {
|
|
|
11690
12189
|
}
|
|
11691
12190
|
|
|
11692
12191
|
// src/components/BindingBuilder/SceneSettings/CinematicTab.tsx
|
|
12192
|
+
var import_react32 = require("react");
|
|
11693
12193
|
var import_lucide_react10 = require("lucide-react");
|
|
11694
12194
|
var import_jsx_runtime31 = require("react/jsx-runtime");
|
|
11695
12195
|
var DEFAULT_CINEMATIC = {
|
|
@@ -11698,6 +12198,7 @@ var DEFAULT_CINEMATIC = {
|
|
|
11698
12198
|
loop: true,
|
|
11699
12199
|
autoPlay: false
|
|
11700
12200
|
};
|
|
12201
|
+
var DEFAULT_SEGMENT_DURATION = 3.5;
|
|
11701
12202
|
function CinematicTab({
|
|
11702
12203
|
config,
|
|
11703
12204
|
onChange,
|
|
@@ -11706,31 +12207,76 @@ function CinematicTab({
|
|
|
11706
12207
|
}) {
|
|
11707
12208
|
const cinematic = config.cinematic ?? DEFAULT_CINEMATIC;
|
|
11708
12209
|
const { waypoints } = cinematic;
|
|
11709
|
-
const
|
|
12210
|
+
const calculateTotalDuration = (0, import_react32.useCallback)((wps, loop) => {
|
|
12211
|
+
if (wps.length < 2) return 0;
|
|
12212
|
+
let total = 0;
|
|
12213
|
+
for (let i = 0; i < wps.length - 1; i++) {
|
|
12214
|
+
const segDur = wps[i].segmentDuration ?? DEFAULT_SEGMENT_DURATION;
|
|
12215
|
+
total += segDur;
|
|
12216
|
+
}
|
|
12217
|
+
if (loop && wps.length > 1) {
|
|
12218
|
+
total += wps[wps.length - 1].segmentDuration ?? DEFAULT_SEGMENT_DURATION;
|
|
12219
|
+
}
|
|
12220
|
+
return Math.max(total, 0);
|
|
12221
|
+
}, []);
|
|
12222
|
+
const update = (0, import_react32.useCallback)((patch) => {
|
|
11710
12223
|
onChange({ ...config, cinematic: { ...cinematic, ...patch } });
|
|
11711
|
-
};
|
|
11712
|
-
const addWaypoint = () => {
|
|
12224
|
+
}, [config, cinematic, onChange]);
|
|
12225
|
+
const addWaypoint = (0, import_react32.useCallback)(() => {
|
|
11713
12226
|
if (!cameraState) return;
|
|
12227
|
+
const newWaypoint = {
|
|
12228
|
+
position: [...cameraState.position],
|
|
12229
|
+
target: [...cameraState.target],
|
|
12230
|
+
...cameraState.azimuthAngle != null ? { azimuthAngle: cameraState.azimuthAngle } : {},
|
|
12231
|
+
...cameraState.polarAngle != null ? { polarAngle: cameraState.polarAngle } : {},
|
|
12232
|
+
segmentDuration: DEFAULT_SEGMENT_DURATION
|
|
12233
|
+
};
|
|
12234
|
+
const newWaypoints = [...waypoints, newWaypoint];
|
|
12235
|
+
const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
|
|
11714
12236
|
update({
|
|
11715
|
-
waypoints:
|
|
11716
|
-
|
|
11717
|
-
{
|
|
11718
|
-
position: [...cameraState.position],
|
|
11719
|
-
target: [...cameraState.target]
|
|
11720
|
-
}
|
|
11721
|
-
]
|
|
12237
|
+
waypoints: newWaypoints,
|
|
12238
|
+
duration: newDuration
|
|
11722
12239
|
});
|
|
11723
|
-
};
|
|
11724
|
-
const deleteWaypoint = (index) => {
|
|
11725
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
12240
|
+
}, [cameraState, waypoints, cinematic.loop, calculateTotalDuration, update]);
|
|
12241
|
+
const deleteWaypoint = (0, import_react32.useCallback)((index) => {
|
|
12242
|
+
const newWaypoints = waypoints.filter((_, i) => i !== index);
|
|
12243
|
+
const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
|
|
12244
|
+
update({
|
|
12245
|
+
waypoints: newWaypoints,
|
|
12246
|
+
duration: newDuration
|
|
12247
|
+
});
|
|
12248
|
+
}, [waypoints, cinematic.loop, calculateTotalDuration, update]);
|
|
12249
|
+
const moveWaypoint = (0, import_react32.useCallback)((index, delta) => {
|
|
11728
12250
|
const next = index + delta;
|
|
11729
12251
|
if (next < 0 || next >= waypoints.length) return;
|
|
11730
12252
|
const reordered = [...waypoints];
|
|
11731
12253
|
[reordered[index], reordered[next]] = [reordered[next], reordered[index]];
|
|
11732
|
-
|
|
11733
|
-
|
|
12254
|
+
const newDuration = calculateTotalDuration(reordered, cinematic.loop);
|
|
12255
|
+
update({
|
|
12256
|
+
waypoints: reordered,
|
|
12257
|
+
duration: newDuration
|
|
12258
|
+
});
|
|
12259
|
+
}, [waypoints, cinematic.loop, calculateTotalDuration, update]);
|
|
12260
|
+
const updateWaypoint = (0, import_react32.useCallback)((index, updates) => {
|
|
12261
|
+
const newWaypoints = waypoints.map((wp, i) => {
|
|
12262
|
+
if (i === index) {
|
|
12263
|
+
return { ...wp, ...updates };
|
|
12264
|
+
}
|
|
12265
|
+
return wp;
|
|
12266
|
+
});
|
|
12267
|
+
const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
|
|
12268
|
+
update({
|
|
12269
|
+
waypoints: newWaypoints,
|
|
12270
|
+
duration: newDuration
|
|
12271
|
+
});
|
|
12272
|
+
}, [waypoints, cinematic.loop, calculateTotalDuration, update]);
|
|
12273
|
+
const [customizeDurations, setCustomizeDurations] = (0, import_react32.useState)(false);
|
|
12274
|
+
const hasSegmentDurations = waypoints.some((w) => w.segmentDuration !== void 0);
|
|
12275
|
+
const getDuration = (0, import_react32.useCallback)((index) => {
|
|
12276
|
+
if (index >= waypoints.length - 1) return DEFAULT_SEGMENT_DURATION;
|
|
12277
|
+
return waypoints[index]?.segmentDuration ?? DEFAULT_SEGMENT_DURATION;
|
|
12278
|
+
}, [waypoints]);
|
|
12279
|
+
const hasEnoughWaypoints = waypoints.length >= 2;
|
|
11734
12280
|
return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(Section, { title: "Cinematic Camera", children: [
|
|
11735
12281
|
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("p", { className: "se-cinematicHint", children: "Frame the model in the viewer, then capture it as a waypoint. The camera glides through the waypoints in order \u2014 enable and press play in the viewer to preview." }),
|
|
11736
12282
|
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
|
|
@@ -11747,62 +12293,165 @@ function CinematicTab({
|
|
|
11747
12293
|
]
|
|
11748
12294
|
}
|
|
11749
12295
|
),
|
|
11750
|
-
|
|
11751
|
-
/* @__PURE__ */ (0, import_jsx_runtime31.
|
|
11752
|
-
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointBody", children: [
|
|
11753
|
-
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointCoords", children: [
|
|
11754
|
-
"pos [",
|
|
11755
|
-
waypoint.position.map((n) => n.toFixed(1)).join(", "),
|
|
11756
|
-
"]"
|
|
11757
|
-
] }),
|
|
11758
|
-
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointCoords", children: [
|
|
11759
|
-
"look [",
|
|
11760
|
-
waypoint.target.map((n) => n.toFixed(1)).join(", "),
|
|
11761
|
-
"]"
|
|
11762
|
-
] })
|
|
11763
|
-
] }),
|
|
11764
|
-
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointActions", children: [
|
|
12296
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-customizeToggle", children: [
|
|
12297
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("label", { className: "se-toggleLabel", children: [
|
|
11765
12298
|
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
11766
|
-
"
|
|
11767
|
-
{
|
|
11768
|
-
className: "se-waypointAction",
|
|
11769
|
-
onClick: () => onPreviewWaypoint?.(waypoint),
|
|
11770
|
-
title: "Fly camera to this waypoint",
|
|
11771
|
-
disabled: !onPreviewWaypoint,
|
|
11772
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.MapPin, { size: 15 })
|
|
11773
|
-
}
|
|
11774
|
-
),
|
|
11775
|
-
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
11776
|
-
"button",
|
|
11777
|
-
{
|
|
11778
|
-
className: "se-waypointAction",
|
|
11779
|
-
onClick: () => moveWaypoint(index, -1),
|
|
11780
|
-
title: "Move up",
|
|
11781
|
-
disabled: index === 0,
|
|
11782
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.ArrowUp, { size: 15 })
|
|
11783
|
-
}
|
|
11784
|
-
),
|
|
11785
|
-
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
11786
|
-
"button",
|
|
12299
|
+
"input",
|
|
11787
12300
|
{
|
|
11788
|
-
|
|
11789
|
-
|
|
11790
|
-
|
|
11791
|
-
disabled: index === waypoints.length - 1,
|
|
11792
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.ArrowDown, { size: 15 })
|
|
12301
|
+
type: "checkbox",
|
|
12302
|
+
checked: customizeDurations,
|
|
12303
|
+
onChange: (e) => setCustomizeDurations(e.target.checked)
|
|
11793
12304
|
}
|
|
11794
12305
|
),
|
|
11795
|
-
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
11796
|
-
|
|
11797
|
-
|
|
11798
|
-
|
|
11799
|
-
|
|
11800
|
-
|
|
11801
|
-
|
|
11802
|
-
}
|
|
11803
|
-
)
|
|
12306
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.Settings, { size: 14 }),
|
|
12307
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { children: "Customize durations between waypoints" })
|
|
12308
|
+
] }),
|
|
12309
|
+
customizeDurations && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "se-toggleHint", children: [
|
|
12310
|
+
"Set duration for each segment between waypoints: 1\u21922, 2\u21923, etc. Default: ",
|
|
12311
|
+
DEFAULT_SEGMENT_DURATION,
|
|
12312
|
+
"s"
|
|
11804
12313
|
] })
|
|
11805
|
-
] }
|
|
12314
|
+
] }),
|
|
12315
|
+
waypoints.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "bb-emptyAnnotations", children: "No waypoints yet. Position the camera and add your first one." }) : /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(import_jsx_runtime31.Fragment, { children: [
|
|
12316
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "se-waypointList", children: waypoints.map((waypoint, index) => {
|
|
12317
|
+
const dur = getDuration(index);
|
|
12318
|
+
const isLast = index === waypoints.length - 1;
|
|
12319
|
+
const isCustom = waypoint.segmentDuration !== void 0 && waypoint.segmentDuration !== DEFAULT_SEGMENT_DURATION;
|
|
12320
|
+
return /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { children: [
|
|
12321
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointCard", children: [
|
|
12322
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("div", { className: "se-waypointBadge", children: index + 1 }),
|
|
12323
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointBody", children: [
|
|
12324
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointCoords", children: [
|
|
12325
|
+
"pos [",
|
|
12326
|
+
waypoint.position.map((n) => n.toFixed(1)).join(", "),
|
|
12327
|
+
"]"
|
|
12328
|
+
] }),
|
|
12329
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointCoords", children: [
|
|
12330
|
+
"look [",
|
|
12331
|
+
waypoint.target.map((n) => n.toFixed(1)).join(", "),
|
|
12332
|
+
"]"
|
|
12333
|
+
] }),
|
|
12334
|
+
customizeDurations && !isLast && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: `se-customBadge ${isCustom ? "se-customBadge--custom" : "se-customBadge--default"}`, children: [
|
|
12335
|
+
dur.toFixed(1),
|
|
12336
|
+
"s",
|
|
12337
|
+
!isCustom && " (default)"
|
|
12338
|
+
] })
|
|
12339
|
+
] }),
|
|
12340
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-waypointActions", children: [
|
|
12341
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
12342
|
+
"button",
|
|
12343
|
+
{
|
|
12344
|
+
className: "se-waypointAction",
|
|
12345
|
+
onClick: () => onPreviewWaypoint?.(waypoint),
|
|
12346
|
+
title: "Fly camera to this waypoint",
|
|
12347
|
+
disabled: !onPreviewWaypoint,
|
|
12348
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.MapPin, { size: 15 })
|
|
12349
|
+
}
|
|
12350
|
+
),
|
|
12351
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
12352
|
+
"button",
|
|
12353
|
+
{
|
|
12354
|
+
className: "se-waypointAction",
|
|
12355
|
+
onClick: () => moveWaypoint(index, -1),
|
|
12356
|
+
title: "Move up",
|
|
12357
|
+
disabled: index === 0,
|
|
12358
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.ArrowUp, { size: 15 })
|
|
12359
|
+
}
|
|
12360
|
+
),
|
|
12361
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
12362
|
+
"button",
|
|
12363
|
+
{
|
|
12364
|
+
className: "se-waypointAction",
|
|
12365
|
+
onClick: () => moveWaypoint(index, 1),
|
|
12366
|
+
title: "Move down",
|
|
12367
|
+
disabled: isLast,
|
|
12368
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.ArrowDown, { size: 15 })
|
|
12369
|
+
}
|
|
12370
|
+
),
|
|
12371
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
12372
|
+
"button",
|
|
12373
|
+
{
|
|
12374
|
+
className: "se-waypointAction se-waypointAction--delete",
|
|
12375
|
+
onClick: () => deleteWaypoint(index),
|
|
12376
|
+
title: "Delete waypoint",
|
|
12377
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(import_lucide_react10.Trash2, { size: 15 })
|
|
12378
|
+
}
|
|
12379
|
+
)
|
|
12380
|
+
] })
|
|
12381
|
+
] }),
|
|
12382
|
+
customizeDurations && !isLast && /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-segmentBetween", children: [
|
|
12383
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-segmentLabel", children: [
|
|
12384
|
+
index + 1,
|
|
12385
|
+
" \u2192 ",
|
|
12386
|
+
index + 2,
|
|
12387
|
+
!waypoint.segmentDuration && " (default)"
|
|
12388
|
+
] }),
|
|
12389
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-segmentControls", children: [
|
|
12390
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "se-durationLabel", children: "Duration:" }),
|
|
12391
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
12392
|
+
"input",
|
|
12393
|
+
{
|
|
12394
|
+
type: "range",
|
|
12395
|
+
className: "se-segmentSlider",
|
|
12396
|
+
min: 1,
|
|
12397
|
+
max: 20,
|
|
12398
|
+
step: 0.5,
|
|
12399
|
+
value: dur,
|
|
12400
|
+
onChange: (e) => {
|
|
12401
|
+
const val = parseFloat(e.target.value);
|
|
12402
|
+
updateWaypoint(index, { segmentDuration: val });
|
|
12403
|
+
}
|
|
12404
|
+
}
|
|
12405
|
+
),
|
|
12406
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("span", { className: "se-segmentValue", children: [
|
|
12407
|
+
dur.toFixed(1),
|
|
12408
|
+
"s"
|
|
12409
|
+
] }),
|
|
12410
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-presetButtons", children: [
|
|
12411
|
+
[2, 4, 6, 8].map((preset) => /* @__PURE__ */ (0, import_jsx_runtime31.jsxs)(
|
|
12412
|
+
"button",
|
|
12413
|
+
{
|
|
12414
|
+
className: "se-presetButton",
|
|
12415
|
+
onClick: () => updateWaypoint(index, { segmentDuration: preset }),
|
|
12416
|
+
children: [
|
|
12417
|
+
preset,
|
|
12418
|
+
"s"
|
|
12419
|
+
]
|
|
12420
|
+
},
|
|
12421
|
+
`preset-${index}-${preset}`
|
|
12422
|
+
)),
|
|
12423
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
12424
|
+
"button",
|
|
12425
|
+
{
|
|
12426
|
+
className: "se-presetButton se-presetButton--default",
|
|
12427
|
+
onClick: () => {
|
|
12428
|
+
const newWaypoints = waypoints.map((wp, i) => {
|
|
12429
|
+
if (i === index) {
|
|
12430
|
+
const { segmentDuration, ...rest } = wp;
|
|
12431
|
+
return rest;
|
|
12432
|
+
}
|
|
12433
|
+
return wp;
|
|
12434
|
+
});
|
|
12435
|
+
const newDuration = calculateTotalDuration(newWaypoints, cinematic.loop);
|
|
12436
|
+
update({
|
|
12437
|
+
waypoints: newWaypoints,
|
|
12438
|
+
duration: newDuration
|
|
12439
|
+
});
|
|
12440
|
+
},
|
|
12441
|
+
children: "Reset"
|
|
12442
|
+
}
|
|
12443
|
+
)
|
|
12444
|
+
] })
|
|
12445
|
+
] })
|
|
12446
|
+
] })
|
|
12447
|
+
] }, `waypoint-wrapper-${index}`);
|
|
12448
|
+
}) }),
|
|
12449
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsxs)("div", { className: "se-totalDuration", children: [
|
|
12450
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "se-totalLabel", children: "Total duration:" }),
|
|
12451
|
+
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "se-totalValue", children: hasEnoughWaypoints ? (cinematic.duration ?? calculateTotalDuration(waypoints, cinematic.loop)).toFixed(1) + "s" : "-" }),
|
|
12452
|
+
!hasEnoughWaypoints && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)("span", { className: "se-totalHint", children: "(need at least 2 waypoints)" })
|
|
12453
|
+
] })
|
|
12454
|
+
] }),
|
|
11806
12455
|
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
11807
12456
|
ToggleRow,
|
|
11808
12457
|
{
|
|
@@ -11816,7 +12465,11 @@ function CinematicTab({
|
|
|
11816
12465
|
{
|
|
11817
12466
|
label: "Loop",
|
|
11818
12467
|
checked: cinematic.loop,
|
|
11819
|
-
onChange: (loop) =>
|
|
12468
|
+
onChange: (loop) => {
|
|
12469
|
+
const newLoop = loop;
|
|
12470
|
+
const newDuration = calculateTotalDuration(waypoints, newLoop);
|
|
12471
|
+
update({ loop: newLoop, duration: newDuration });
|
|
12472
|
+
}
|
|
11820
12473
|
}
|
|
11821
12474
|
),
|
|
11822
12475
|
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
@@ -11827,11 +12480,11 @@ function CinematicTab({
|
|
|
11827
12480
|
onChange: (autoPlay) => update({ autoPlay })
|
|
11828
12481
|
}
|
|
11829
12482
|
),
|
|
11830
|
-
/* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
12483
|
+
!customizeDurations && hasEnoughWaypoints && /* @__PURE__ */ (0, import_jsx_runtime31.jsx)(
|
|
11831
12484
|
SliderRow,
|
|
11832
12485
|
{
|
|
11833
12486
|
label: "Duration",
|
|
11834
|
-
value: cinematic.duration ??
|
|
12487
|
+
value: cinematic.duration ?? calculateTotalDuration(waypoints, cinematic.loop),
|
|
11835
12488
|
min: 4,
|
|
11836
12489
|
max: 120,
|
|
11837
12490
|
step: 1,
|
|
@@ -11843,7 +12496,7 @@ function CinematicTab({
|
|
|
11843
12496
|
}
|
|
11844
12497
|
|
|
11845
12498
|
// src/styles/SceneSettingsTab.css
|
|
11846
|
-
styleInject('.se-tabContent {\n display: flex;\n flex-direction: column;\n}\n.se-tabContent * {\n box-sizing: border-box;\n}\n.se-subTabRow {\n display: flex;\n justify-content: space-between;\n gap: 6px;\n padding-bottom: 16px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n margin-bottom: 4px;\n}\n.se-subTab {\n padding: 8px 12px;\n display: flex;\n justify-items: center;\n border-radius: 10px;\n border: 1px solid rgba(148, 163, 184, 0.18);\n background: rgba(255, 255, 255, 0.04);\n color: #b3bfd2;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n cursor: pointer;\n transition:\n background-color 0.18s ease,\n border-color 0.18s ease,\n color 0.18s ease;\n}\n.se-subTab:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.32);\n}\n.se-subTab--active {\n background: rgba(99, 102, 241, 0.18);\n border-color: rgba(99, 102, 241, 0.4);\n color: #f8fafc;\n}\n.se-generalContent {\n display: flex;\n flex-direction: column;\n padding-top: 0;\n}\n.se-placeholder {\n font-size: 13px;\n color: #93a4c0;\n}\n.se-section {\n margin-bottom: 10px;\n border-radius: 7px;\n background: rgba(255, 255, 255, 0.03);\n border: 1px solid rgba(148, 163, 184, 0.12);\n overflow: hidden;\n padding: 0;\n}\n.se-section:last-child {\n margin-bottom: 0;\n}\n.se-sectionTitle {\n margin: 0;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #c3cbdb;\n padding: 11px 14px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n}\n.se-sectionBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n}\n.se-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n}\n.se-fieldInline {\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n.se-fieldLabel {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n font-size: 12px;\n font-weight: 700;\n color: #d7dee9;\n}\n.se-fieldValue {\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: #93a4c0;\n}\n.se-slider {\n width: 100%;\n accent-color: #2aa8d8;\n cursor: pointer;\n}\n.se-checkbox {\n width: 18px;\n height: 18px;\n accent-color: #2aa8d8;\n cursor: pointer;\n}\n.se-select,\n.se-textInput,\n.se-numberInput {\n background: rgba(15, 23, 42, 0.28);\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 7px;\n color: #f8fafc;\n font-size: 13px;\n padding: 11px 13px;\n outline: none;\n}\n.se-select:focus,\n.se-textInput:focus,\n.se-numberInput:focus {\n outline: none;\n border-color: rgba(42, 168, 216, 0.65);\n box-shadow: 0 0 0 4px rgba(42, 168, 216, 0.12);\n}\n.se-select {\n min-width: 130px;\n text-transform: capitalize;\n}\n.se-textInput {\n width: 100%;\n}\n.se-numberInput {\n width: 100%;\n}\n.se-swatch {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n min-width: 96px;\n height: 30px;\n padding: 0 8px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.35);\n cursor: pointer;\n box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);\n}\n.se-swatchCode {\n font-size: 11px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n color: #fff;\n mix-blend-mode: difference;\n}\n.se-colorPicker {\n margin-top: 6px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n width: fit-content;\n}\n.se-colorPicker .react-colorful {\n width: 100%;\n height: 140px;\n}\n.se-inlineColorPicker .react-colorful {\n width: 100%;\n height: 240px;\n}\n.se-colorPickerPopover {\n z-index: 1000;\n padding: 10px;\n border-radius: 8px;\n background: #1b2333;\n border: 1px solid rgba(148, 163, 184, 0.25);\n box-shadow: 0 12px 28px rgba(0, 0, 0, 0.4);\n}\n.se-colorPickerPopover .react-colorful {\n width: 200px;\n height: 160px;\n}\n.se-vectorRow {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 8px;\n}\n.se-vectorAxis {\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 11px;\n color: #93a4c0;\n}\n.se-panel {\n margin-bottom: 10px;\n overflow: hidden;\n border: 1px solid rgba(148, 163, 184, 0.12);\n border-radius: 7px;\n background: rgba(255, 255, 255, 0.03);\n}\n.se-panel:last-child {\n margin-bottom: 0;\n}\n.se-panelHeader {\n display: flex;\n width: 100%;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border: none;\n background: rgba(255, 255, 255, 0.03);\n padding: 11px 14px;\n color: #e5e7eb;\n cursor: pointer;\n text-align: left;\n}\n.se-panelHeader:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.se-panelHeaderMain {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: 8px;\n}\n.se-panelChevron {\n flex-shrink: 0;\n color: #8b97ab;\n transform: rotate(-90deg);\n transition: transform 0.18s ease;\n}\n.se-panelChevron--open {\n transform: rotate(0deg);\n}\n.se-panelTitle {\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #c3cbdb;\n}\n.se-panelHelp {\n display: inline-flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n margin-left: 2px;\n border-radius: 999px;\n color: #b9c0ca;\n cursor: help;\n transition:\n background 0.18s ease,\n color 0.18s ease,\n transform 0.18s ease;\n}\n.se-panelHelp:hover {\n background: rgba(255, 255, 255, 0.08);\n color: #eef2ff;\n transform: translateY(-0.5px);\n}\n.se-panelTrailing {\n display: flex;\n align-items: center;\n}\n.se-panelBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n background: transparent;\n}\n.se-iconButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 30px;\n border: none;\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.08);\n color: #d7dde8;\n cursor: pointer;\n}\n.se-iconButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.se-axisStepperGrid {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 12px;\n}\n.se-axisStepper {\n display: grid;\n grid-template-columns: 40px 1fr 40px;\n align-items: center;\n min-height: 30px;\n border-radius: 10px;\n background: rgba(17, 24, 39, 0.62);\n border: 1px solid rgba(255, 255, 255, 0.05);\n}\n.se-axisStepperButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n border: none;\n background: transparent;\n color: #f3f4f6;\n cursor: pointer;\n}\n.se-axisStepperButton:hover {\n background: rgba(255, 255, 255, 0.04);\n}\n.se-axisStepperLabel {\n text-align: center;\n font-size: 15px;\n color: #f3f4f6;\n}\n.se-segmentedControl {\n display: grid;\n grid-auto-flow: column;\n overflow: hidden;\n border-radius: 10px;\n background: rgba(17, 24, 39, 0.62);\n}\n.se-segmentedOption {\n min-height: 35px;\n border: none;\n background: transparent;\n color: #d1d5db;\n font-size: 14px;\n text-transform: uppercase;\n cursor: pointer;\n}\n.se-segmentedOption--active {\n background: #2aa8d8;\n color: #fff;\n}\n.se-helperCard {\n padding: 32px;\n text-align: center;\n background: rgba(15, 23, 42, 0.44);\n border-radius: 10px;\n border: 1px solid rgba(148, 163, 184, 0.15);\n color: #94a3b8;\n font-size: 14px;\n}\n.se-helperCard strong {\n font-size: 14px;\n}\n.se-helperCard span {\n font-size: 13px;\n line-height: 1.4;\n color: #c7d0df;\n}\n.se-switch {\n position: relative;\n display: inline-flex;\n align-items: center;\n}\n.se-switch input[type=checkbox] {\n position: absolute;\n left: 0;\n top: 0;\n z-index: 10;\n width: 100%;\n height: 100%;\n cursor: pointer;\n opacity: 0;\n}\n.se-switchTrack {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.se-switchTrack::before {\n content: "";\n width: 36px;\n height: 20px;\n background: #4b5563;\n display: inline-block;\n border-radius: 20px;\n transition: background-color 0.2s ease-in;\n}\n.se-switchThumb {\n position: absolute;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n left: 2px;\n top: 2px;\n z-index: 2;\n background: #fff;\n box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);\n transition: 0.2s ease-in;\n}\n.se-switch input[type=checkbox]:hover + .se-switchTrack .se-switchThumb {\n box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.2), 0 3px 8px 0 rgba(0, 0, 0, 0.1);\n}\n.se-switch input[type=checkbox]:checked + .se-switchTrack::before {\n background: #376fcb;\n}\n.se-switch input[type=checkbox]:checked + .se-switchTrack .se-switchThumb {\n background: #4285f4;\n left: 18px;\n}\n.se-switchLabel {\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.06em;\n color: #e5e7eb;\n}\n.se-switch input[type=checkbox]:checked ~ .se-switchTrack .se-switchLabel {\n color: #2aa8d8;\n}\n.se-lightList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.se-lightRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.1);\n background: rgba(255, 255, 255, 0.03);\n cursor: pointer;\n transition: background-color 0.15s ease, border-color 0.15s ease;\n}\n.se-lightRow:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.se-lightRow--active {\n background: rgba(42, 168, 216, 0.12);\n border-color: rgba(42, 168, 216, 0.35);\n}\n.se-lightRowLabel {\n font-size: 13px;\n color: #d8e0ee;\n}\n.se-lightActions {\n display: flex;\n gap: 8px;\n}\n.se-lightProps {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding-top: 4px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n}\n.se-animationList {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.se-animationCard {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding: 16px;\n border-radius: 14px;\n border: 1px solid rgba(148, 163, 184, 0.16);\n background: rgba(15, 23, 42, 0.48);\n}\n.se-animationCard--active {\n border-color: rgba(42, 168, 216, 0.45);\n box-shadow: inset 0 0 0 1px rgba(42, 168, 216, 0.16);\n}\n.se-animationHeader {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n}\n.se-animationHeading {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: 6px;\n}\n.se-animationTitleRow {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n.se-animationStatus,\n.se-animationBadge {\n display: inline-flex;\n align-items: center;\n min-height: 22px;\n padding: 0 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n.se-animationStatus {\n background: rgba(42, 168, 216, 0.16);\n color: #9fe8ff;\n}\n.se-animationBadge {\n background: rgba(99, 102, 241, 0.18);\n color: #d9ddff;\n}\n.se-animationSource {\n font-size: 12px;\n color: #93a4c0;\n word-break: break-word;\n}\n.se-animationMeta {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n.se-animationMeta span {\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.06);\n padding: 4px 9px;\n font-size: 12px;\n color: #c6d0e2;\n}\n.se-animationDangerButton,\n.se-animationButton,\n.se-animationAutoplayButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 10px;\n background: rgba(255, 255, 255, 0.04);\n color: #e2e8f0;\n cursor: pointer;\n transition:\n background-color 0.18s ease,\n border-color 0.18s ease,\n color 0.18s ease;\n}\n.se-animationButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.se-animationDangerButton {\n width: 36px;\n height: 36px;\n color: #fca5a5;\n}\n.se-animationDangerButton:hover,\n.se-animationButton:hover,\n.se-animationAutoplayButton:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.34);\n}\n.se-animationButtonRow {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n.se-animationButton {\n min-height: 36px;\n padding: 0 14px;\n font-size: 13px;\n font-weight: 600;\n}\n.se-animationButton--icon {\n flex: 1;\n min-width: 0;\n padding: 0;\n height: 38px;\n}\n.se-animationSeekRow {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.se-animationSeek {\n flex: 1;\n min-width: 0;\n}\n.se-animationSeekTime {\n flex-shrink: 0;\n min-width: 86px;\n text-align: right;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: #97a3b6;\n}\n.se-animationGrid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n}\n.se-animationSpeedRow {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 88px;\n gap: 10px;\n align-items: center;\n}\n.se-animationSpeedInput {\n text-align: right;\n}\n.se-animationAutoplayButton {\n align-self: flex-start;\n min-height: 36px;\n padding: 0 14px;\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.se-animationAutoplayButton--active {\n background: rgba(42, 168, 216, 0.18);\n border-color: rgba(42, 168, 216, 0.42);\n color: #dff8ff;\n}\n.se-removedAnimations {\n overflow: hidden;\n border-radius: 12px;\n border: 1px solid rgba(148, 163, 184, 0.14);\n background: rgba(255, 255, 255, 0.03);\n}\n.se-removedAnimationList {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 14px;\n}\n.se-removedAnimationRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border-radius: 10px;\n background: rgba(15, 23, 42, 0.42);\n padding: 12px;\n}\n.se-removedAnimationName {\n font-size: 13px;\n font-weight: 600;\n color: #e2e8f0;\n}\n@media (max-width: 900px) {\n .se-animationGrid {\n grid-template-columns: 1fr;\n }\n .se-animationSpeedRow {\n grid-template-columns: 1fr;\n }\n}\n.bb-viewSpotOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n backdrop-filter: blur(4px);\n animation: bb-fadeIn 0.2s ease;\n}\n.bb-viewSpotPopup {\n background: #1e1e20;\n border-radius: 12px;\n max-width: 480px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n border: 1px solid #333335;\n animation: bb-slideUp 0.25s ease;\n}\n.bb-viewSpotHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px;\n border-bottom: 1px solid #2a2a2c;\n}\n.bb-viewSpotTitle {\n font-size: 18px;\n font-weight: 600;\n color: #e8e8ea;\n margin: 0;\n}\n.bb-viewSpotClose {\n background: none;\n border: none;\n color: #888;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n line-height: 1;\n transition: color 0.2s;\n}\n.bb-viewSpotClose:hover {\n color: #e8e8ea;\n}\n.bb-viewSpotBody {\n padding: 24px;\n}\n.bb-viewSpotDescription {\n color: #c8c8ca;\n font-size: 14px;\n line-height: 1.6;\n margin-bottom: 20px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.bb-viewSpotPosition {\n background: #141416;\n border-radius: 8px;\n padding: 12px 16px;\n margin-bottom: 24px;\n font-size: 13px;\n color: #a0a0a2;\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.bb-viewSpotLabel {\n font-weight: 500;\n color: #888;\n}\n.bb-viewSpotCode {\n background: #0a0a0c;\n padding: 4px 10px;\n border-radius: 4px;\n font-family:\n "Monaco",\n "Menlo",\n monospace;\n font-size: 12px;\n color: #8be9fd;\n}\n.bb-viewSpotActions {\n display: flex;\n justify-content: flex-end;\n gap: 10px;\n padding-top: 4px;\n}\n.bb-editAnnotationOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n}\n.bb-editAnnotationPopup {\n background: #0f141e;\n border-radius: 16px;\n padding: 24px;\n min-width: 200px;\n max-width: 400px;\n width: 90%;\n border: 1px solid rgba(148, 163, 184, 0.18);\n}\n.bb-editAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n padding-bottom: 12px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.12);\n}\n.bb-editAnnotationTitle {\n color: #f8fafc;\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n}\n.bb-editAnnotationClose {\n background: none;\n border: none;\n color: #94a3b8;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n}\n.bb-editAnnotationClose:hover {\n color: #f8fafc;\n}\n.bb-editAnnotationBody {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.bb-editAnnotationActions {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n}\n.se-cinematicHint {\n margin: 0 0 10px;\n font-size: 12px;\n line-height: 1.5;\n color: rgba(255, 255, 255, 0.55);\n}\n.bb-button.se-cinematicAdd {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n margin-bottom: 12px;\n background: #135bc0;\n color: white;\n}\n.bb-button.se-cinematicAdd:hover:not(:disabled) {\n background: #1a68d6;\n}\n.se-waypointList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 14px;\n}\n.se-waypointCard {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 10px;\n border: 1px solid rgba(255, 255, 255, 0.1);\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.03);\n}\n.se-waypointBadge {\n flex: none;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 600;\n color: #06343c;\n background: #22d3ee;\n}\n.se-waypointBody {\n flex: 1;\n min-width: 0;\n}\n.se-waypointCoords {\n font-size: 11px;\n line-height: 1.4;\n color: rgba(255, 255, 255, 0.6);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.se-waypointActions {\n flex: none;\n display: flex;\n gap: 2px;\n}\n.se-waypointAction {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: rgba(255, 255, 255, 0.6);\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n}\n.se-waypointAction:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.1);\n color: #fff;\n}\n.se-waypointAction:disabled {\n opacity: 0.35;\n cursor: default;\n}\n.se-waypointAction--delete:hover:not(:disabled) {\n background: rgba(248, 113, 113, 0.16);\n color: #f87171;\n}\n.bb-cameraWaypoint {\n align-items: flex-start;\n margin-bottom: 8px;\n}\n.bb-cameraWaypoint .bb-cameraLiveBadge,\n.bb-cameraWaypoint .bb-cameraSavedBadge {\n flex: none;\n}\n.bb-cameraWaypointLabel {\n font-size: 11px;\n font-weight: 600;\n color: rgba(255, 255, 255, 0.8);\n margin-bottom: 2px;\n}\n.bb-cameraControls--centered {\n justify-content: center;\n margin: 12px 0;\n}\n');
|
|
12499
|
+
styleInject('.se-tabContent {\n display: flex;\n flex-direction: column;\n}\n.se-tabContent * {\n box-sizing: border-box;\n}\n.se-subTabRow {\n display: flex;\n justify-content: space-between;\n gap: 6px;\n padding-bottom: 16px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n margin-bottom: 4px;\n}\n.se-subTab {\n padding: 8px 12px;\n display: flex;\n justify-items: center;\n border-radius: 10px;\n border: 1px solid rgba(148, 163, 184, 0.18);\n background: rgba(255, 255, 255, 0.04);\n color: #b3bfd2;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n cursor: pointer;\n transition:\n background-color 0.18s ease,\n border-color 0.18s ease,\n color 0.18s ease;\n}\n.se-subTab:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.32);\n}\n.se-subTab--active {\n background: rgba(99, 102, 241, 0.18);\n border-color: rgba(99, 102, 241, 0.4);\n color: #f8fafc;\n}\n.se-generalContent {\n display: flex;\n flex-direction: column;\n padding-top: 0;\n}\n.se-placeholder {\n font-size: 13px;\n color: #93a4c0;\n}\n.se-section {\n margin-bottom: 10px;\n border-radius: 7px;\n background: rgba(255, 255, 255, 0.03);\n border: 1px solid rgba(148, 163, 184, 0.12);\n overflow: hidden;\n padding: 0;\n}\n.se-section:last-child {\n margin-bottom: 0;\n}\n.se-sectionTitle {\n margin: 0;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #c3cbdb;\n padding: 11px 14px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.1);\n}\n.se-sectionBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n}\n.se-field {\n display: flex;\n flex-direction: column;\n gap: 5px;\n}\n.se-fieldInline {\n flex-direction: row;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n.se-fieldLabel {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 8px;\n font-size: 12px;\n font-weight: 700;\n color: #d7dee9;\n}\n.se-fieldValue {\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: #93a4c0;\n}\n.se-slider {\n width: 100%;\n accent-color: #2aa8d8;\n cursor: pointer;\n}\n.se-checkbox {\n width: 18px;\n height: 18px;\n accent-color: #2aa8d8;\n cursor: pointer;\n}\n.se-select,\n.se-textInput,\n.se-numberInput {\n background: rgba(15, 23, 42, 0.28);\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 7px;\n color: #f8fafc;\n font-size: 13px;\n padding: 11px 13px;\n outline: none;\n}\n.se-select:focus,\n.se-textInput:focus,\n.se-numberInput:focus {\n outline: none;\n border-color: rgba(42, 168, 216, 0.65);\n box-shadow: 0 0 0 4px rgba(42, 168, 216, 0.12);\n}\n.se-select {\n min-width: 130px;\n text-transform: capitalize;\n}\n.se-textInput {\n width: 100%;\n}\n.se-numberInput {\n width: 100%;\n}\n.se-swatch {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n min-width: 96px;\n height: 30px;\n padding: 0 8px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.35);\n cursor: pointer;\n box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);\n}\n.se-swatchCode {\n font-size: 11px;\n font-weight: 600;\n font-variant-numeric: tabular-nums;\n color: #fff;\n mix-blend-mode: difference;\n}\n.se-colorPicker {\n margin-top: 6px;\n display: flex;\n flex-direction: column;\n gap: 8px;\n width: fit-content;\n}\n.se-colorPicker .react-colorful {\n width: 100%;\n height: 140px;\n}\n.se-inlineColorPicker .react-colorful {\n width: 100%;\n height: 240px;\n}\n.se-colorPickerPopover {\n z-index: 1000;\n padding: 10px;\n border-radius: 8px;\n background: #1b2333;\n border: 1px solid rgba(148, 163, 184, 0.25);\n box-shadow: 0 12px 28px rgba(0, 0, 0, 0.4);\n}\n.se-colorPickerPopover .react-colorful {\n width: 200px;\n height: 160px;\n}\n.se-vectorRow {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 8px;\n}\n.se-vectorAxis {\n display: flex;\n flex-direction: column;\n gap: 4px;\n font-size: 11px;\n color: #93a4c0;\n}\n.se-panel {\n margin-bottom: 10px;\n overflow: hidden;\n border: 1px solid rgba(148, 163, 184, 0.12);\n border-radius: 7px;\n background: rgba(255, 255, 255, 0.03);\n}\n.se-panel:last-child {\n margin-bottom: 0;\n}\n.se-panelHeader {\n display: flex;\n width: 100%;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border: none;\n background: rgba(255, 255, 255, 0.03);\n padding: 11px 14px;\n color: #e5e7eb;\n cursor: pointer;\n text-align: left;\n}\n.se-panelHeader:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.se-panelHeaderMain {\n display: flex;\n min-width: 0;\n align-items: center;\n gap: 8px;\n}\n.se-panelChevron {\n flex-shrink: 0;\n color: #8b97ab;\n transform: rotate(-90deg);\n transition: transform 0.18s ease;\n}\n.se-panelChevron--open {\n transform: rotate(0deg);\n}\n.se-panelTitle {\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n color: #c3cbdb;\n}\n.se-panelHelp {\n display: inline-flex;\n flex-shrink: 0;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n margin-left: 2px;\n border-radius: 999px;\n color: #b9c0ca;\n cursor: help;\n transition:\n background 0.18s ease,\n color 0.18s ease,\n transform 0.18s ease;\n}\n.se-panelHelp:hover {\n background: rgba(255, 255, 255, 0.08);\n color: #eef2ff;\n transform: translateY(-0.5px);\n}\n.se-panelTrailing {\n display: flex;\n align-items: center;\n}\n.se-panelBody {\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 14px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n background: transparent;\n}\n.se-iconButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 30px;\n height: 30px;\n border: none;\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.08);\n color: #d7dde8;\n cursor: pointer;\n}\n.se-iconButton:hover {\n background: rgba(255, 255, 255, 0.12);\n}\n.se-axisStepperGrid {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 12px;\n}\n.se-axisStepper {\n display: grid;\n grid-template-columns: 40px 1fr 40px;\n align-items: center;\n min-height: 30px;\n border-radius: 10px;\n background: rgba(17, 24, 39, 0.62);\n border: 1px solid rgba(255, 255, 255, 0.05);\n}\n.se-axisStepperButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n border: none;\n background: transparent;\n color: #f3f4f6;\n cursor: pointer;\n}\n.se-axisStepperButton:hover {\n background: rgba(255, 255, 255, 0.04);\n}\n.se-axisStepperLabel {\n text-align: center;\n font-size: 15px;\n color: #f3f4f6;\n}\n.se-segmentedControl {\n display: grid;\n grid-auto-flow: column;\n overflow: hidden;\n border-radius: 10px;\n background: rgba(17, 24, 39, 0.62);\n}\n.se-segmentedOption {\n min-height: 35px;\n border: none;\n background: transparent;\n color: #d1d5db;\n font-size: 14px;\n text-transform: uppercase;\n cursor: pointer;\n}\n.se-segmentedOption--active {\n background: #2aa8d8;\n color: #fff;\n}\n.se-helperCard {\n padding: 32px;\n text-align: center;\n background: rgba(15, 23, 42, 0.44);\n border-radius: 10px;\n border: 1px solid rgba(148, 163, 184, 0.15);\n color: #94a3b8;\n font-size: 14px;\n}\n.se-helperCard strong {\n font-size: 14px;\n}\n.se-helperCard span {\n font-size: 13px;\n line-height: 1.4;\n color: #c7d0df;\n}\n.se-switch {\n position: relative;\n display: inline-flex;\n align-items: center;\n}\n.se-switch input[type=checkbox] {\n position: absolute;\n left: 0;\n top: 0;\n z-index: 10;\n width: 100%;\n height: 100%;\n cursor: pointer;\n opacity: 0;\n}\n.se-switchTrack {\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.se-switchTrack::before {\n content: "";\n width: 36px;\n height: 20px;\n background: #4b5563;\n display: inline-block;\n border-radius: 20px;\n transition: background-color 0.2s ease-in;\n}\n.se-switchThumb {\n position: absolute;\n width: 16px;\n height: 16px;\n border-radius: 50%;\n left: 2px;\n top: 2px;\n z-index: 2;\n background: #fff;\n box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);\n transition: 0.2s ease-in;\n}\n.se-switch input[type=checkbox]:hover + .se-switchTrack .se-switchThumb {\n box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.2), 0 3px 8px 0 rgba(0, 0, 0, 0.1);\n}\n.se-switch input[type=checkbox]:checked + .se-switchTrack::before {\n background: #376fcb;\n}\n.se-switch input[type=checkbox]:checked + .se-switchTrack .se-switchThumb {\n background: #4285f4;\n left: 18px;\n}\n.se-switchLabel {\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.06em;\n color: #e5e7eb;\n}\n.se-switch input[type=checkbox]:checked ~ .se-switchTrack .se-switchLabel {\n color: #2aa8d8;\n}\n.se-lightList {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.se-lightRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 10px;\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.1);\n background: rgba(255, 255, 255, 0.03);\n cursor: pointer;\n transition: background-color 0.15s ease, border-color 0.15s ease;\n}\n.se-lightRow:hover {\n background: rgba(255, 255, 255, 0.06);\n}\n.se-lightRow--active {\n background: rgba(42, 168, 216, 0.12);\n border-color: rgba(42, 168, 216, 0.35);\n}\n.se-lightRowLabel {\n font-size: 13px;\n color: #d8e0ee;\n}\n.se-lightActions {\n display: flex;\n gap: 8px;\n}\n.se-lightProps {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding-top: 4px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n}\n.se-animationList {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.se-animationCard {\n display: flex;\n flex-direction: column;\n gap: 14px;\n padding: 16px;\n border-radius: 14px;\n border: 1px solid rgba(148, 163, 184, 0.16);\n background: rgba(15, 23, 42, 0.48);\n}\n.se-animationCard--active {\n border-color: rgba(42, 168, 216, 0.45);\n box-shadow: inset 0 0 0 1px rgba(42, 168, 216, 0.16);\n}\n.se-animationHeader {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 12px;\n}\n.se-animationHeading {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: 6px;\n}\n.se-animationTitleRow {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n}\n.se-animationStatus,\n.se-animationBadge {\n display: inline-flex;\n align-items: center;\n min-height: 22px;\n padding: 0 8px;\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n.se-animationStatus {\n background: rgba(42, 168, 216, 0.16);\n color: #9fe8ff;\n}\n.se-animationBadge {\n background: rgba(99, 102, 241, 0.18);\n color: #d9ddff;\n}\n.se-animationSource {\n font-size: 12px;\n color: #93a4c0;\n word-break: break-word;\n}\n.se-animationMeta {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n.se-animationMeta span {\n border-radius: 999px;\n background: rgba(255, 255, 255, 0.06);\n padding: 4px 9px;\n font-size: 12px;\n color: #c6d0e2;\n}\n.se-animationDangerButton,\n.se-animationButton,\n.se-animationAutoplayButton {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n border: 1px solid rgba(148, 163, 184, 0.2);\n border-radius: 10px;\n background: rgba(255, 255, 255, 0.04);\n color: #e2e8f0;\n cursor: pointer;\n transition:\n background-color 0.18s ease,\n border-color 0.18s ease,\n color 0.18s ease;\n}\n.se-animationButton:disabled {\n opacity: 0.45;\n cursor: not-allowed;\n}\n.se-animationDangerButton {\n width: 36px;\n height: 36px;\n color: #fca5a5;\n}\n.se-animationDangerButton:hover,\n.se-animationButton:hover,\n.se-animationAutoplayButton:hover {\n background: rgba(255, 255, 255, 0.08);\n border-color: rgba(148, 163, 184, 0.34);\n}\n.se-animationButtonRow {\n display: flex;\n flex-wrap: wrap;\n gap: 10px;\n}\n.se-animationButton {\n min-height: 36px;\n padding: 0 14px;\n font-size: 13px;\n font-weight: 600;\n}\n.se-animationButton--icon {\n flex: 1;\n min-width: 0;\n padding: 0;\n height: 38px;\n}\n.se-animationSeekRow {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n.se-animationSeek {\n flex: 1;\n min-width: 0;\n}\n.se-animationSeekTime {\n flex-shrink: 0;\n min-width: 86px;\n text-align: right;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n color: #97a3b6;\n}\n.se-animationGrid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n}\n.se-animationSpeedRow {\n display: grid;\n grid-template-columns: minmax(0, 1fr) 88px;\n gap: 10px;\n align-items: center;\n}\n.se-animationSpeedInput {\n text-align: right;\n}\n.se-animationAutoplayButton {\n align-self: flex-start;\n min-height: 36px;\n padding: 0 14px;\n font-size: 12px;\n font-weight: 700;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n}\n.se-animationAutoplayButton--active {\n background: rgba(42, 168, 216, 0.18);\n border-color: rgba(42, 168, 216, 0.42);\n color: #dff8ff;\n}\n.se-removedAnimations {\n overflow: hidden;\n border-radius: 12px;\n border: 1px solid rgba(148, 163, 184, 0.14);\n background: rgba(255, 255, 255, 0.03);\n}\n.se-removedAnimationList {\n display: flex;\n flex-direction: column;\n gap: 10px;\n padding: 14px;\n}\n.se-removedAnimationRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n border-radius: 10px;\n background: rgba(15, 23, 42, 0.42);\n padding: 12px;\n}\n.se-removedAnimationName {\n font-size: 13px;\n font-weight: 600;\n color: #e2e8f0;\n}\n@media (max-width: 900px) {\n .se-animationGrid {\n grid-template-columns: 1fr;\n }\n .se-animationSpeedRow {\n grid-template-columns: 1fr;\n }\n}\n.bb-viewSpotOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n backdrop-filter: blur(4px);\n animation: bb-fadeIn 0.2s ease;\n}\n.bb-viewSpotPopup {\n background: #1e1e20;\n border-radius: 12px;\n max-width: 480px;\n width: 90%;\n max-height: 80vh;\n overflow-y: auto;\n box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);\n border: 1px solid #333335;\n animation: bb-slideUp 0.25s ease;\n}\n.bb-viewSpotHeader {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px 24px;\n border-bottom: 1px solid #2a2a2c;\n}\n.bb-viewSpotTitle {\n font-size: 18px;\n font-weight: 600;\n color: #e8e8ea;\n margin: 0;\n}\n.bb-viewSpotClose {\n background: none;\n border: none;\n color: #888;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n line-height: 1;\n transition: color 0.2s;\n}\n.bb-viewSpotClose:hover {\n color: #e8e8ea;\n}\n.bb-viewSpotBody {\n padding: 24px;\n}\n.bb-viewSpotDescription {\n color: #c8c8ca;\n font-size: 14px;\n line-height: 1.6;\n margin-bottom: 20px;\n white-space: pre-wrap;\n word-break: break-word;\n}\n.bb-viewSpotPosition {\n background: #141416;\n border-radius: 8px;\n padding: 12px 16px;\n margin-bottom: 24px;\n font-size: 13px;\n color: #a0a0a2;\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.bb-viewSpotLabel {\n font-weight: 500;\n color: #888;\n}\n.bb-viewSpotCode {\n background: #0a0a0c;\n padding: 4px 10px;\n border-radius: 4px;\n font-family:\n "Monaco",\n "Menlo",\n monospace;\n font-size: 12px;\n color: #8be9fd;\n}\n.bb-viewSpotActions {\n display: flex;\n justify-content: flex-end;\n gap: 10px;\n padding-top: 4px;\n}\n.bb-editAnnotationOverlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.6);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n}\n.bb-editAnnotationPopup {\n background: #0f141e;\n border-radius: 16px;\n padding: 24px;\n min-width: 200px;\n max-width: 400px;\n width: 90%;\n border: 1px solid rgba(148, 163, 184, 0.18);\n}\n.bb-editAnnotationHeader {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 16px;\n padding-bottom: 12px;\n border-bottom: 1px solid rgba(148, 163, 184, 0.12);\n}\n.bb-editAnnotationTitle {\n color: #f8fafc;\n font-size: 18px;\n font-weight: 600;\n margin: 0;\n}\n.bb-editAnnotationClose {\n background: none;\n border: none;\n color: #94a3b8;\n font-size: 24px;\n cursor: pointer;\n padding: 0 4px;\n}\n.bb-editAnnotationClose:hover {\n color: #f8fafc;\n}\n.bb-editAnnotationBody {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n.bb-editAnnotationActions {\n display: flex;\n gap: 8px;\n margin-top: 8px;\n}\n.se-cinematicHint {\n margin: 0 0 10px;\n font-size: 12px;\n line-height: 1.5;\n color: rgba(255, 255, 255, 0.55);\n}\n.bb-button.se-cinematicAdd {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n width: 100%;\n margin-bottom: 12px;\n background: #135bc0;\n color: white;\n}\n.bb-button.se-cinematicAdd:hover:not(:disabled) {\n background: #1a68d6;\n}\n.se-waypointList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 14px;\n}\n.se-waypointCard {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 8px 12px;\n border: 1px solid rgba(255, 255, 255, 0.08);\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.03);\n}\n.se-waypointBadge {\n flex: none;\n width: 24px;\n height: 24px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 600;\n color: #06343c;\n background: #22d3ee;\n}\n.se-waypointBody {\n flex: 1;\n min-width: 0;\n}\n.se-waypointCoords {\n font-size: 11px;\n line-height: 1.4;\n color: rgba(255, 255, 255, 0.5);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.se-waypointActions {\n flex: none;\n display: flex;\n gap: 2px;\n}\n.se-waypointAction {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: none;\n border-radius: 6px;\n background: transparent;\n color: rgba(255, 255, 255, 0.5);\n cursor: pointer;\n transition: background 0.15s ease, color 0.15s ease;\n}\n.se-waypointAction:hover:not(:disabled) {\n background: rgba(255, 255, 255, 0.08);\n color: #fff;\n}\n.se-waypointAction:disabled {\n opacity: 0.35;\n cursor: default;\n}\n.se-waypointAction--delete:hover:not(:disabled) {\n background: rgba(248, 113, 113, 0.16);\n color: #f87171;\n}\n.se-customBadge {\n display: inline-block;\n padding: 1px 8px;\n border-radius: 3px;\n background: rgba(34, 211, 238, 0.15);\n color: #22d3ee;\n font-size: 10px;\n font-weight: 600;\n margin-top: 4px;\n}\n.se-customizeToggle {\n display: flex;\n flex-direction: column;\n gap: 4px;\n padding: 8px 12px;\n background: rgba(255, 255, 255, 0.03);\n border-radius: 8px;\n border: 1px solid rgba(148, 163, 184, 0.08);\n margin: 8px 0 12px;\n}\n.se-toggleLabel {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n font-weight: 500;\n color: #f8fafc;\n cursor: pointer;\n user-select: none;\n}\n.se-toggleLabel input[type=checkbox] {\n width: 16px;\n height: 16px;\n accent-color: #22d3ee;\n cursor: pointer;\n}\n.se-toggleLabel svg {\n color: #22d3ee;\n width: 14px;\n height: 14px;\n}\n.se-toggleHint {\n font-size: 11px;\n color: rgba(255, 255, 255, 0.4);\n padding-left: 28px;\n}\n.se-segmentBetween {\n display: flex;\n flex-direction: column;\n gap: 6px;\n padding: 10px 14px;\n margin: 6px 0 6px 32px;\n background: rgba(255, 255, 255, 0.03);\n border-radius: 6px;\n border-left: 3px solid rgba(34, 211, 238, 0.25);\n}\n.se-segmentLabel {\n font-size: 11px;\n font-weight: 500;\n color: rgba(255, 255, 255, 0.45);\n}\n.se-segmentControls {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.se-durationLabel {\n font-size: 10px;\n color: rgba(255, 255, 255, 0.35);\n font-weight: 500;\n white-space: nowrap;\n}\n.se-segmentSlider {\n flex: 1;\n min-width: 80px;\n max-width: 150px;\n height: 4px;\n -webkit-appearance: none;\n appearance: none;\n background: rgba(255, 255, 255, 0.1);\n border-radius: 2px;\n outline: none;\n}\n.se-segmentSlider::-webkit-slider-thumb {\n -webkit-appearance: none;\n appearance: none;\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: #22d3ee;\n cursor: pointer;\n border: 2px solid rgba(255, 255, 255, 0.2);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);\n}\n.se-segmentSlider::-moz-range-thumb {\n width: 14px;\n height: 14px;\n border-radius: 50%;\n background: #22d3ee;\n cursor: pointer;\n border: 2px solid rgba(255, 255, 255, 0.2);\n}\n.se-segmentValue {\n font-size: 12px;\n font-weight: 600;\n color: #22d3ee;\n min-width: 36px;\n text-align: center;\n}\n.se-presetButtons {\n display: flex;\n gap: 4px;\n}\n.se-presetButton {\n padding: 2px 8px;\n background: rgba(255, 255, 255, 0.06);\n border: 1px solid rgba(148, 163, 184, 0.15);\n border-radius: 4px;\n font-size: 10px;\n color: rgba(255, 255, 255, 0.6);\n cursor: pointer;\n transition: all 0.2s ease;\n font-weight: 500;\n}\n.se-presetButton:hover {\n background: #22d3ee;\n color: #06343c;\n border-color: #22d3ee;\n}\n.se-totalDuration {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 8px 4px;\n margin: 8px 0 12px;\n border-top: 1px solid rgba(148, 163, 184, 0.1);\n padding-top: 12px;\n}\n.se-totalLabel {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.5);\n font-weight: 500;\n}\n.se-totalValue {\n font-size: 14px;\n font-weight: 600;\n color: #22d3ee;\n}\n.se-totalHint {\n font-size: 11px;\n color: rgba(255, 255, 255, 0.35);\n font-weight: 400;\n margin-left: 8px;\n}\n');
|
|
11847
12500
|
|
|
11848
12501
|
// src/components/BindingBuilder/SceneSettings/SceneSettingsTab.tsx
|
|
11849
12502
|
var import_lucide_react11 = require("lucide-react");
|
|
@@ -11882,13 +12535,13 @@ function SceneSettingsTab({
|
|
|
11882
12535
|
animationClipDetails = [],
|
|
11883
12536
|
animationPlayback = FALLBACK_ANIMATION_PLAYBACK
|
|
11884
12537
|
}) {
|
|
11885
|
-
const [internalActiveSubTab, setInternalActiveSubTab] = (0,
|
|
12538
|
+
const [internalActiveSubTab, setInternalActiveSubTab] = (0, import_react33.useState)("General");
|
|
11886
12539
|
const activeSubTab = controlledActiveSubTab ?? internalActiveSubTab;
|
|
11887
12540
|
const resolvedAnimationPlayback = animationPlayback ?? FALLBACK_ANIMATION_PLAYBACK;
|
|
11888
12541
|
const { tier } = useBindingBuilderContext();
|
|
11889
12542
|
const canUseStarterFeatures = hasTier(tier, "starter");
|
|
11890
12543
|
const canUseGrowthFeatures = hasTier(tier, "growth");
|
|
11891
|
-
(0,
|
|
12544
|
+
(0, import_react33.useEffect)(() => {
|
|
11892
12545
|
if (controlledActiveSubTab !== void 0) {
|
|
11893
12546
|
setInternalActiveSubTab(controlledActiveSubTab);
|
|
11894
12547
|
}
|
|
@@ -12068,7 +12721,7 @@ function EnableToggle2({
|
|
|
12068
12721
|
}
|
|
12069
12722
|
|
|
12070
12723
|
// src/components/BindingBuilder/components/MaterialSection.tsx
|
|
12071
|
-
var
|
|
12724
|
+
var import_react35 = require("react");
|
|
12072
12725
|
|
|
12073
12726
|
// src/components/ModelViewer/types/objectBinding.ts
|
|
12074
12727
|
var MATERIAL_SIDES = ["front", "back", "double"];
|
|
@@ -12115,7 +12768,7 @@ function MaterialPanelGroup({
|
|
|
12115
12768
|
}
|
|
12116
12769
|
|
|
12117
12770
|
// src/components/BindingBuilder/components/MaterialFields.tsx
|
|
12118
|
-
var
|
|
12771
|
+
var import_react34 = require("react");
|
|
12119
12772
|
var import_react_colorful4 = require("react-colorful");
|
|
12120
12773
|
var import_lucide_react13 = require("lucide-react");
|
|
12121
12774
|
var import_jsx_runtime35 = require("react/jsx-runtime");
|
|
@@ -12125,7 +12778,7 @@ function MaterialTexturePathField({
|
|
|
12125
12778
|
placeholder,
|
|
12126
12779
|
onChange
|
|
12127
12780
|
}) {
|
|
12128
|
-
const inputRef = (0,
|
|
12781
|
+
const inputRef = (0, import_react34.useRef)(null);
|
|
12129
12782
|
return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "bb-field bb-field--full", children: [
|
|
12130
12783
|
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("label", { className: "bb-label", children: label }),
|
|
12131
12784
|
/* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "bb-fileField", children: [
|
|
@@ -12182,9 +12835,9 @@ function MaterialColorField({
|
|
|
12182
12835
|
fallback = "#ffffff",
|
|
12183
12836
|
onChange
|
|
12184
12837
|
}) {
|
|
12185
|
-
const [open, setOpen] = (0,
|
|
12838
|
+
const [open, setOpen] = (0, import_react34.useState)(false);
|
|
12186
12839
|
const color = value || fallback;
|
|
12187
|
-
const anchorRef = (0,
|
|
12840
|
+
const anchorRef = (0, import_react34.useRef)(null);
|
|
12188
12841
|
return /* @__PURE__ */ (0, import_jsx_runtime35.jsxs)("div", { className: "bb-field", children: [
|
|
12189
12842
|
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { className: "bb-label", children: label }),
|
|
12190
12843
|
/* @__PURE__ */ (0, import_jsx_runtime35.jsx)("div", { className: "se-fieldInline", children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)("span", { ref: anchorRef, style: { display: "inline-flex" }, children: /* @__PURE__ */ (0, import_jsx_runtime35.jsx)(
|
|
@@ -12336,8 +12989,8 @@ function MaterialSection({
|
|
|
12336
12989
|
tier: "starter",
|
|
12337
12990
|
locked: !canUseStarterFeatures
|
|
12338
12991
|
};
|
|
12339
|
-
const [roughnessMode, setRoughnessMode] = (0,
|
|
12340
|
-
const [anisotropyMode, setAnisotropyMode] = (0,
|
|
12992
|
+
const [roughnessMode, setRoughnessMode] = (0, import_react35.useState)("roughness");
|
|
12993
|
+
const [anisotropyMode, setAnisotropyMode] = (0, import_react35.useState)(
|
|
12341
12994
|
"factor"
|
|
12342
12995
|
);
|
|
12343
12996
|
return /* @__PURE__ */ (0, import_jsx_runtime36.jsxs)(import_jsx_runtime36.Fragment, { children: [
|
|
@@ -13376,7 +14029,7 @@ function MaterialPresetGallery({
|
|
|
13376
14029
|
}
|
|
13377
14030
|
|
|
13378
14031
|
// src/components/BindingBuilder/components/ToolbarExportMenu.tsx
|
|
13379
|
-
var
|
|
14032
|
+
var import_react36 = require("react");
|
|
13380
14033
|
var import_lucide_react14 = require("lucide-react");
|
|
13381
14034
|
var import_jsx_runtime38 = require("react/jsx-runtime");
|
|
13382
14035
|
function ToolbarExportMenu({
|
|
@@ -13385,9 +14038,9 @@ function ToolbarExportMenu({
|
|
|
13385
14038
|
onExportJson,
|
|
13386
14039
|
onExportTs
|
|
13387
14040
|
}) {
|
|
13388
|
-
const [open, setOpen] = (0,
|
|
13389
|
-
const wrapperRef = (0,
|
|
13390
|
-
(0,
|
|
14041
|
+
const [open, setOpen] = (0, import_react36.useState)(false);
|
|
14042
|
+
const wrapperRef = (0, import_react36.useRef)(null);
|
|
14043
|
+
(0, import_react36.useEffect)(() => {
|
|
13391
14044
|
if (!open) return;
|
|
13392
14045
|
const close = (event) => {
|
|
13393
14046
|
if (!wrapperRef.current?.contains(event.target)) {
|
|
@@ -13824,10 +14477,10 @@ function CameraPositionPanel({
|
|
|
13824
14477
|
}
|
|
13825
14478
|
|
|
13826
14479
|
// src/components/BindingBuilder/components/SceneNodesPanel.tsx
|
|
13827
|
-
var
|
|
14480
|
+
var import_react37 = require("react");
|
|
13828
14481
|
|
|
13829
14482
|
// src/components/BindingBuilder/utils/bindingFactory.ts
|
|
13830
|
-
function
|
|
14483
|
+
function slugify2(value) {
|
|
13831
14484
|
return value.trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[_\s]+/g, "-").replace(/[^a-zA-Z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
13832
14485
|
}
|
|
13833
14486
|
function humanizeNodeName(value) {
|
|
@@ -13873,7 +14526,7 @@ function buildDefaultActions(type) {
|
|
|
13873
14526
|
function createDefaultBinding(nodeName) {
|
|
13874
14527
|
const type = inferType(nodeName);
|
|
13875
14528
|
return {
|
|
13876
|
-
id:
|
|
14529
|
+
id: slugify2(nodeName),
|
|
13877
14530
|
modelObjectId: nodeName,
|
|
13878
14531
|
type,
|
|
13879
14532
|
label: humanizeNodeName(nodeName),
|
|
@@ -13913,9 +14566,9 @@ function SceneNodesPanel({
|
|
|
13913
14566
|
isOpen = true,
|
|
13914
14567
|
onToggle
|
|
13915
14568
|
}) {
|
|
13916
|
-
const [search, setSearch] = (0,
|
|
13917
|
-
const nodeKeys = (0,
|
|
13918
|
-
const filteredNodeKeys = (0,
|
|
14569
|
+
const [search, setSearch] = (0, import_react37.useState)("");
|
|
14570
|
+
const nodeKeys = (0, import_react37.useMemo)(() => Object.keys(bindings), [bindings]);
|
|
14571
|
+
const filteredNodeKeys = (0, import_react37.useMemo)(() => {
|
|
13919
14572
|
const query = search.trim().toLowerCase();
|
|
13920
14573
|
if (!query) return nodeKeys;
|
|
13921
14574
|
return nodeKeys.filter((nodeKey) => {
|
|
@@ -14058,7 +14711,7 @@ function mergeAnimationConfig(sceneConfig, animationDetails) {
|
|
|
14058
14711
|
}
|
|
14059
14712
|
|
|
14060
14713
|
// src/components/BindingBuilder/hooks/useUndoableState.ts
|
|
14061
|
-
var
|
|
14714
|
+
var import_react38 = require("react");
|
|
14062
14715
|
var MAX_HISTORY = 100;
|
|
14063
14716
|
function historyReducer(state, action) {
|
|
14064
14717
|
switch (action.type) {
|
|
@@ -14097,13 +14750,13 @@ function historyReducer(state, action) {
|
|
|
14097
14750
|
}
|
|
14098
14751
|
}
|
|
14099
14752
|
function useUndoableState(initial, coalesceWindowMs = 600) {
|
|
14100
|
-
const [state, dispatch] = (0,
|
|
14753
|
+
const [state, dispatch] = (0, import_react38.useReducer)(historyReducer, {
|
|
14101
14754
|
past: [],
|
|
14102
14755
|
present: initial,
|
|
14103
14756
|
future: []
|
|
14104
14757
|
});
|
|
14105
|
-
const lastSetAt = (0,
|
|
14106
|
-
const setValue = (0,
|
|
14758
|
+
const lastSetAt = (0, import_react38.useRef)(0);
|
|
14759
|
+
const setValue = (0, import_react38.useCallback)(
|
|
14107
14760
|
(updater) => {
|
|
14108
14761
|
const now = Date.now();
|
|
14109
14762
|
const coalesce = now - lastSetAt.current < coalesceWindowMs;
|
|
@@ -14112,15 +14765,15 @@ function useUndoableState(initial, coalesceWindowMs = 600) {
|
|
|
14112
14765
|
},
|
|
14113
14766
|
[coalesceWindowMs]
|
|
14114
14767
|
);
|
|
14115
|
-
const undo = (0,
|
|
14768
|
+
const undo = (0, import_react38.useCallback)(() => {
|
|
14116
14769
|
lastSetAt.current = 0;
|
|
14117
14770
|
dispatch({ type: "undo" });
|
|
14118
14771
|
}, []);
|
|
14119
|
-
const redo = (0,
|
|
14772
|
+
const redo = (0, import_react38.useCallback)(() => {
|
|
14120
14773
|
lastSetAt.current = 0;
|
|
14121
14774
|
dispatch({ type: "redo" });
|
|
14122
14775
|
}, []);
|
|
14123
|
-
const reset = (0,
|
|
14776
|
+
const reset = (0, import_react38.useCallback)((value) => {
|
|
14124
14777
|
lastSetAt.current = 0;
|
|
14125
14778
|
dispatch({ type: "reset", value });
|
|
14126
14779
|
}, []);
|
|
@@ -14136,7 +14789,7 @@ function useUndoableState(initial, coalesceWindowMs = 600) {
|
|
|
14136
14789
|
}
|
|
14137
14790
|
|
|
14138
14791
|
// src/components/BindingBuilder/utils/exportAssets.ts
|
|
14139
|
-
var
|
|
14792
|
+
var import_fflate3 = require("fflate");
|
|
14140
14793
|
|
|
14141
14794
|
// src/components/BindingBuilder/utils/serialization.ts
|
|
14142
14795
|
function toBindingsJson(bindings) {
|
|
@@ -14212,11 +14865,11 @@ async function downloadExport(filename, content, mimeType, zipFilename, assets)
|
|
|
14212
14865
|
downloadTextFile(filename, content, mimeType);
|
|
14213
14866
|
return;
|
|
14214
14867
|
}
|
|
14215
|
-
const files = { [filename]: (0,
|
|
14868
|
+
const files = { [filename]: (0, import_fflate3.strToU8)(content) };
|
|
14216
14869
|
for (const asset of assets) {
|
|
14217
14870
|
files[asset.path] = new Uint8Array(await asset.blob.arrayBuffer());
|
|
14218
14871
|
}
|
|
14219
|
-
const zipped = (0,
|
|
14872
|
+
const zipped = (0, import_fflate3.zipSync)(files, { level: 6 });
|
|
14220
14873
|
const blob = new Blob([zipped], { type: "application/zip" });
|
|
14221
14874
|
const url = URL.createObjectURL(blob);
|
|
14222
14875
|
const link = document.createElement("a");
|
|
@@ -14366,30 +15019,30 @@ function mergeSceneConfigSection(current, incoming) {
|
|
|
14366
15019
|
return next;
|
|
14367
15020
|
}
|
|
14368
15021
|
function BindingBuilder({ licenseKey }) {
|
|
14369
|
-
const modelInputRef = (0,
|
|
14370
|
-
const bindingsInputRef = (0,
|
|
14371
|
-
const sceneConfigInputRef = (0,
|
|
14372
|
-
const [modelUrl, setModelUrl] = (0,
|
|
14373
|
-
const [modelName, setModelName] = (0,
|
|
14374
|
-
const [selectedItem, setSelectedItem] = (0,
|
|
15022
|
+
const modelInputRef = (0, import_react39.useRef)(null);
|
|
15023
|
+
const bindingsInputRef = (0, import_react39.useRef)(null);
|
|
15024
|
+
const sceneConfigInputRef = (0, import_react39.useRef)(null);
|
|
15025
|
+
const [modelUrl, setModelUrl] = (0, import_react39.useState)(null);
|
|
15026
|
+
const [modelName, setModelName] = (0, import_react39.useState)("");
|
|
15027
|
+
const [selectedItem, setSelectedItem] = (0, import_react39.useState)(null);
|
|
14375
15028
|
const bindingsHistory = useUndoableState({});
|
|
14376
15029
|
const bindings = bindingsHistory.value;
|
|
14377
15030
|
const setBindings = bindingsHistory.setValue;
|
|
14378
|
-
const [loading, setLoading] = (0,
|
|
14379
|
-
const [isExporting, setIsExporting] = (0,
|
|
14380
|
-
const [statusMessage, setStatusMessage] = (0,
|
|
15031
|
+
const [loading, setLoading] = (0, import_react39.useState)(false);
|
|
15032
|
+
const [isExporting, setIsExporting] = (0, import_react39.useState)(false);
|
|
15033
|
+
const [statusMessage, setStatusMessage] = (0, import_react39.useState)(
|
|
14381
15034
|
"Load the demo model or upload a GLB/GLTF model to generate bindings."
|
|
14382
15035
|
);
|
|
14383
|
-
const [statusTone, setStatusTone] = (0,
|
|
14384
|
-
const [statusBannerVisible, setStatusBannerVisible] = (0,
|
|
14385
|
-
const [metricsDraft, setMetricsDraft] = (0,
|
|
14386
|
-
const [metadataDraft, setMetadataDraft] = (0,
|
|
14387
|
-
const [metricsError, setMetricsError] = (0,
|
|
14388
|
-
const [metadataError, setMetadataError] = (0,
|
|
14389
|
-
const [isPreviewDragActive, setIsPreviewDragActive] = (0,
|
|
14390
|
-
const [isEditorCollapsed, setIsEditorCollapsed] = (0,
|
|
14391
|
-
const [isNodesCollapsed, setIsNodesCollapsed] = (0,
|
|
14392
|
-
const [openObjectSections, setOpenObjectSections] = (0,
|
|
15036
|
+
const [statusTone, setStatusTone] = (0, import_react39.useState)("default");
|
|
15037
|
+
const [statusBannerVisible, setStatusBannerVisible] = (0, import_react39.useState)(false);
|
|
15038
|
+
const [metricsDraft, setMetricsDraft] = (0, import_react39.useState)("{}");
|
|
15039
|
+
const [metadataDraft, setMetadataDraft] = (0, import_react39.useState)("{}");
|
|
15040
|
+
const [metricsError, setMetricsError] = (0, import_react39.useState)(null);
|
|
15041
|
+
const [metadataError, setMetadataError] = (0, import_react39.useState)(null);
|
|
15042
|
+
const [isPreviewDragActive, setIsPreviewDragActive] = (0, import_react39.useState)(false);
|
|
15043
|
+
const [isEditorCollapsed, setIsEditorCollapsed] = (0, import_react39.useState)(false);
|
|
15044
|
+
const [isNodesCollapsed, setIsNodesCollapsed] = (0, import_react39.useState)(false);
|
|
15045
|
+
const [openObjectSections, setOpenObjectSections] = (0, import_react39.useState)({
|
|
14393
15046
|
identity: true,
|
|
14394
15047
|
basics: true,
|
|
14395
15048
|
material: true,
|
|
@@ -14398,7 +15051,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14398
15051
|
metrics: false,
|
|
14399
15052
|
metadata: false
|
|
14400
15053
|
});
|
|
14401
|
-
const [openMaterialPanels, setOpenMaterialPanels] = (0,
|
|
15054
|
+
const [openMaterialPanels, setOpenMaterialPanels] = (0, import_react39.useState)({
|
|
14402
15055
|
pbr: true,
|
|
14403
15056
|
roughness: false,
|
|
14404
15057
|
emission: false,
|
|
@@ -14414,27 +15067,27 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14414
15067
|
});
|
|
14415
15068
|
const toggleObjectSection = (key) => setOpenObjectSections((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
14416
15069
|
const toggleMaterialPanel = (key) => setOpenMaterialPanels((prev) => ({ ...prev, [key]: !prev[key] }));
|
|
14417
|
-
const [activeEditorTab, setActiveEditorTab] = (0,
|
|
15070
|
+
const [activeEditorTab, setActiveEditorTab] = (0, import_react39.useState)(
|
|
14418
15071
|
"object"
|
|
14419
15072
|
);
|
|
14420
|
-
const [activeSubTab, setActiveSubTab] = (0,
|
|
14421
|
-
const [pbrWorkflow, setPbrWorkflow] = (0,
|
|
14422
|
-
const [materialPanelEnabled, setMaterialPanelEnabled] = (0,
|
|
14423
|
-
const materialPanelItemRef = (0,
|
|
14424
|
-
const [zoomOnSelected, setZoomOnSelected] = (0,
|
|
14425
|
-
const [highlightOnHover, setHighlightOnHover] = (0,
|
|
14426
|
-
const [resetDialogOpen, setResetDialogOpen] = (0,
|
|
14427
|
-
const [resetTarget, setResetTarget] = (0,
|
|
15073
|
+
const [activeSubTab, setActiveSubTab] = (0, import_react39.useState)("General");
|
|
15074
|
+
const [pbrWorkflow, setPbrWorkflow] = (0, import_react39.useState)("metalness");
|
|
15075
|
+
const [materialPanelEnabled, setMaterialPanelEnabled] = (0, import_react39.useState)(createMaterialPanelEnabledState());
|
|
15076
|
+
const materialPanelItemRef = (0, import_react39.useRef)(null);
|
|
15077
|
+
const [zoomOnSelected, setZoomOnSelected] = (0, import_react39.useState)(false);
|
|
15078
|
+
const [highlightOnHover, setHighlightOnHover] = (0, import_react39.useState)(true);
|
|
15079
|
+
const [resetDialogOpen, setResetDialogOpen] = (0, import_react39.useState)(false);
|
|
15080
|
+
const [resetTarget, setResetTarget] = (0, import_react39.useState)(
|
|
14428
15081
|
null
|
|
14429
15082
|
);
|
|
14430
15083
|
const sceneConfigHistory = useUndoableState(DEFAULT_SCENE_CONFIG);
|
|
14431
15084
|
const sceneConfig = sceneConfigHistory.value;
|
|
14432
15085
|
const setSceneConfig = sceneConfigHistory.setValue;
|
|
14433
|
-
const [activeAnnotation, setActiveAnnotation] = (0,
|
|
14434
|
-
const [panelHoveredBinding, setPanelHoveredBinding] = (0,
|
|
15086
|
+
const [activeAnnotation, setActiveAnnotation] = (0, import_react39.useState)(null);
|
|
15087
|
+
const [panelHoveredBinding, setPanelHoveredBinding] = (0, import_react39.useState)(null);
|
|
14435
15088
|
const license = useLicense(licenseKey);
|
|
14436
15089
|
const tier = normalizeTier(license.tier);
|
|
14437
|
-
const bindingBuilderContextValue = (0,
|
|
15090
|
+
const bindingBuilderContextValue = (0, import_react39.useMemo)(
|
|
14438
15091
|
() => ({
|
|
14439
15092
|
isInsideEditor: true,
|
|
14440
15093
|
activeTab: activeEditorTab,
|
|
@@ -14443,11 +15096,11 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14443
15096
|
}),
|
|
14444
15097
|
[activeEditorTab, activeSubTab, tier]
|
|
14445
15098
|
);
|
|
14446
|
-
const uploadedModelRef = (0,
|
|
14447
|
-
const previewDragDepthRef = (0,
|
|
14448
|
-
const loadRequestRef = (0,
|
|
14449
|
-
const pendingLoadRef = (0,
|
|
14450
|
-
const loadedSceneRef = (0,
|
|
15099
|
+
const uploadedModelRef = (0, import_react39.useRef)(null);
|
|
15100
|
+
const previewDragDepthRef = (0, import_react39.useRef)(0);
|
|
15101
|
+
const loadRequestRef = (0, import_react39.useRef)(0);
|
|
15102
|
+
const pendingLoadRef = (0, import_react39.useRef)(null);
|
|
15103
|
+
const loadedSceneRef = (0, import_react39.useRef)(null);
|
|
14451
15104
|
const {
|
|
14452
15105
|
cameraState,
|
|
14453
15106
|
resetView,
|
|
@@ -14457,14 +15110,16 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14457
15110
|
handleCameraChange,
|
|
14458
15111
|
handleViewerReady
|
|
14459
15112
|
} = useViewerCamera();
|
|
14460
|
-
const handlePreviewWaypoint = (0,
|
|
15113
|
+
const handlePreviewWaypoint = (0, import_react39.useCallback)(
|
|
14461
15114
|
(waypoint) => {
|
|
14462
15115
|
setCameraState(
|
|
14463
15116
|
{
|
|
14464
15117
|
position: waypoint.position,
|
|
14465
15118
|
target: waypoint.target,
|
|
14466
15119
|
fov: cameraState?.fov ?? null,
|
|
14467
|
-
zoom: cameraState?.zoom ?? 1
|
|
15120
|
+
zoom: cameraState?.zoom ?? 1,
|
|
15121
|
+
azimuthAngle: waypoint.azimuthAngle,
|
|
15122
|
+
polarAngle: waypoint.polarAngle
|
|
14468
15123
|
},
|
|
14469
15124
|
true
|
|
14470
15125
|
);
|
|
@@ -14479,7 +15134,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14479
15134
|
position: formatVector3(cameraState.position),
|
|
14480
15135
|
target: formatVector3(cameraState.target)
|
|
14481
15136
|
} : null;
|
|
14482
|
-
const handleAnnotationsChange = (0,
|
|
15137
|
+
const handleAnnotationsChange = (0, import_react39.useCallback)(
|
|
14483
15138
|
(newAnnotations) => {
|
|
14484
15139
|
setSceneConfig((prev) => {
|
|
14485
15140
|
const currentIds = prev.annotations.map((a) => a.id).join(",");
|
|
@@ -14495,7 +15150,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14495
15150
|
},
|
|
14496
15151
|
[]
|
|
14497
15152
|
);
|
|
14498
|
-
const handleActiveAnnotationChange = (0,
|
|
15153
|
+
const handleActiveAnnotationChange = (0, import_react39.useCallback)(
|
|
14499
15154
|
(annotation) => {
|
|
14500
15155
|
setActiveAnnotation(
|
|
14501
15156
|
annotation ? {
|
|
@@ -14505,7 +15160,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14505
15160
|
},
|
|
14506
15161
|
[]
|
|
14507
15162
|
);
|
|
14508
|
-
(0,
|
|
15163
|
+
(0, import_react39.useEffect)(() => {
|
|
14509
15164
|
if (!selectedBinding) {
|
|
14510
15165
|
setMetricsDraft("{}");
|
|
14511
15166
|
setMetadataDraft("{}");
|
|
@@ -14518,7 +15173,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14518
15173
|
setMetricsError(null);
|
|
14519
15174
|
setMetadataError(null);
|
|
14520
15175
|
}, [selectedBinding]);
|
|
14521
|
-
(0,
|
|
15176
|
+
(0, import_react39.useEffect)(() => {
|
|
14522
15177
|
const derived = createMaterialPanelEnabledState(selectedMaterial);
|
|
14523
15178
|
if (materialPanelItemRef.current !== selectedItem) {
|
|
14524
15179
|
materialPanelItemRef.current = selectedItem;
|
|
@@ -14537,12 +15192,12 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14537
15192
|
return changed ? next : prev;
|
|
14538
15193
|
});
|
|
14539
15194
|
}, [selectedItem, selectedMaterial]);
|
|
14540
|
-
(0,
|
|
15195
|
+
(0, import_react39.useEffect)(() => {
|
|
14541
15196
|
return () => {
|
|
14542
15197
|
uploadedModelRef.current?.cleanup();
|
|
14543
15198
|
};
|
|
14544
15199
|
}, []);
|
|
14545
|
-
(0,
|
|
15200
|
+
(0, import_react39.useEffect)(() => {
|
|
14546
15201
|
const handleKeyDown = (event) => {
|
|
14547
15202
|
if (event.key.toLowerCase() !== "z" || !(event.metaKey || event.ctrlKey)) {
|
|
14548
15203
|
return;
|
|
@@ -14563,7 +15218,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14563
15218
|
window.addEventListener("keydown", handleKeyDown);
|
|
14564
15219
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
14565
15220
|
}, [activeEditorTab, bindingsHistory, sceneConfigHistory]);
|
|
14566
|
-
(0,
|
|
15221
|
+
(0, import_react39.useEffect)(() => {
|
|
14567
15222
|
if (loading || statusTone === "default" || statusTone === "success") return;
|
|
14568
15223
|
setStatusBannerVisible(true);
|
|
14569
15224
|
const timeout = window.setTimeout(
|
|
@@ -14572,8 +15227,8 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14572
15227
|
);
|
|
14573
15228
|
return () => window.clearTimeout(timeout);
|
|
14574
15229
|
}, [statusMessage, statusTone, loading]);
|
|
14575
|
-
const nodeKeys = (0,
|
|
14576
|
-
const idError = (0,
|
|
15230
|
+
const nodeKeys = (0, import_react39.useMemo)(() => Object.keys(bindings), [bindings]);
|
|
15231
|
+
const idError = (0, import_react39.useMemo)(() => {
|
|
14577
15232
|
if (!selectedItem || !selectedBinding) return null;
|
|
14578
15233
|
const trimmed = selectedBinding.id.trim();
|
|
14579
15234
|
if (!trimmed) return "ID is required.";
|
|
@@ -14616,7 +15271,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14616
15271
|
});
|
|
14617
15272
|
}
|
|
14618
15273
|
};
|
|
14619
|
-
const applyBindingsFromScene = (0,
|
|
15274
|
+
const applyBindingsFromScene = (0, import_react39.useCallback)(
|
|
14620
15275
|
(scene, nextModelName) => {
|
|
14621
15276
|
const nodeNames = collectNodeNames(scene);
|
|
14622
15277
|
bindingsHistory.reset(buildBindingsFromNodes(nodeNames));
|
|
@@ -14655,7 +15310,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14655
15310
|
bindingsHistory.reset({});
|
|
14656
15311
|
setSelectedItem(null);
|
|
14657
15312
|
};
|
|
14658
|
-
const handleViewerModelLoaded = (0,
|
|
15313
|
+
const handleViewerModelLoaded = (0, import_react39.useCallback)(
|
|
14659
15314
|
(scene) => {
|
|
14660
15315
|
const pending = pendingLoadRef.current;
|
|
14661
15316
|
if (!pending || pending.requestId !== loadRequestRef.current) return;
|
|
@@ -14665,7 +15320,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14665
15320
|
},
|
|
14666
15321
|
[applyBindingsFromScene]
|
|
14667
15322
|
);
|
|
14668
|
-
const handleViewerLoadError = (0,
|
|
15323
|
+
const handleViewerLoadError = (0, import_react39.useCallback)((error) => {
|
|
14669
15324
|
const pending = pendingLoadRef.current;
|
|
14670
15325
|
if (!pending || pending.requestId !== loadRequestRef.current) return;
|
|
14671
15326
|
pendingLoadRef.current = null;
|
|
@@ -14676,7 +15331,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14676
15331
|
setStatusTone("error");
|
|
14677
15332
|
setLoading(false);
|
|
14678
15333
|
}, []);
|
|
14679
|
-
const handleAnimationsReady = (0,
|
|
15334
|
+
const handleAnimationsReady = (0, import_react39.useCallback)(
|
|
14680
15335
|
(controls) => {
|
|
14681
15336
|
viewerAnimations.handleAnimationsReady(controls);
|
|
14682
15337
|
const details = controls.clipDetails ?? controls.clips.map((sourceName) => ({ sourceName, duration: 0 }));
|
|
@@ -14977,7 +15632,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14977
15632
|
}));
|
|
14978
15633
|
setError(null);
|
|
14979
15634
|
};
|
|
14980
|
-
const handleViewerSelect = (0,
|
|
15635
|
+
const handleViewerSelect = (0, import_react39.useCallback)(
|
|
14981
15636
|
(binding) => {
|
|
14982
15637
|
if (!binding) {
|
|
14983
15638
|
setSelectedItem(null);
|
|
@@ -14990,7 +15645,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14990
15645
|
},
|
|
14991
15646
|
[bindings]
|
|
14992
15647
|
);
|
|
14993
|
-
const handleSceneNodeSelect = (0,
|
|
15648
|
+
const handleSceneNodeSelect = (0, import_react39.useCallback)(
|
|
14994
15649
|
async (nodeKey) => {
|
|
14995
15650
|
setSelectedItem(nodeKey);
|
|
14996
15651
|
setPanelHoveredBinding(null);
|
|
@@ -14998,7 +15653,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
14998
15653
|
},
|
|
14999
15654
|
[focusObject]
|
|
15000
15655
|
);
|
|
15001
|
-
const saveCameraPositionForSelectedObject = (0,
|
|
15656
|
+
const saveCameraPositionForSelectedObject = (0, import_react39.useCallback)(() => {
|
|
15002
15657
|
if (!selectedItem || !cameraState) {
|
|
15003
15658
|
return;
|
|
15004
15659
|
}
|
|
@@ -15013,7 +15668,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15013
15668
|
cameraState: currentCameraState
|
|
15014
15669
|
}));
|
|
15015
15670
|
}, [selectedItem, cameraState]);
|
|
15016
|
-
const goToSavedCameraPosition = (0,
|
|
15671
|
+
const goToSavedCameraPosition = (0, import_react39.useCallback)(() => {
|
|
15017
15672
|
if (!savedCameraPosition?.position || !savedCameraPosition?.target) return;
|
|
15018
15673
|
setCameraState(
|
|
15019
15674
|
{
|
|
@@ -15025,28 +15680,28 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15025
15680
|
true
|
|
15026
15681
|
);
|
|
15027
15682
|
}, [savedCameraPosition, setCameraState, cameraState]);
|
|
15028
|
-
const clearSavedCameraPosition = (0,
|
|
15683
|
+
const clearSavedCameraPosition = (0, import_react39.useCallback)(() => {
|
|
15029
15684
|
if (!selectedItem) return;
|
|
15030
15685
|
updateBinding(selectedItem, (binding) => ({
|
|
15031
15686
|
...binding,
|
|
15032
15687
|
cameraState: void 0
|
|
15033
15688
|
}));
|
|
15034
15689
|
}, [selectedItem]);
|
|
15035
|
-
const handleResetView = (0,
|
|
15690
|
+
const handleResetView = (0, import_react39.useCallback)(async () => {
|
|
15036
15691
|
await resetView();
|
|
15037
15692
|
}, [resetView]);
|
|
15038
|
-
const handleFitScene = (0,
|
|
15693
|
+
const handleFitScene = (0, import_react39.useCallback)(async () => {
|
|
15039
15694
|
await fitScene();
|
|
15040
15695
|
}, [fitScene]);
|
|
15041
|
-
const openResetDialog = (0,
|
|
15696
|
+
const openResetDialog = (0, import_react39.useCallback)((target) => {
|
|
15042
15697
|
setResetTarget(target);
|
|
15043
15698
|
setResetDialogOpen(true);
|
|
15044
15699
|
}, []);
|
|
15045
|
-
const closeResetDialog = (0,
|
|
15700
|
+
const closeResetDialog = (0, import_react39.useCallback)(() => {
|
|
15046
15701
|
setResetDialogOpen(false);
|
|
15047
15702
|
setResetTarget(null);
|
|
15048
15703
|
}, []);
|
|
15049
|
-
const executeReset = (0,
|
|
15704
|
+
const executeReset = (0, import_react39.useCallback)(() => {
|
|
15050
15705
|
if (resetTarget === "bindings") {
|
|
15051
15706
|
if (!nodeKeys.length) {
|
|
15052
15707
|
closeResetDialog();
|
|
@@ -15177,7 +15832,9 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15177
15832
|
// the whole settings panel for no reason.
|
|
15178
15833
|
activeSubTab === "Cinematic" && cameraState ? {
|
|
15179
15834
|
position: cameraState.position,
|
|
15180
|
-
target: cameraState.target
|
|
15835
|
+
target: cameraState.target,
|
|
15836
|
+
azimuthAngle: cameraState.azimuthAngle,
|
|
15837
|
+
polarAngle: cameraState.polarAngle
|
|
15181
15838
|
} : null
|
|
15182
15839
|
),
|
|
15183
15840
|
onPreviewWaypoint: handlePreviewWaypoint,
|
|
@@ -15602,7 +16259,7 @@ function BindingBuilder({ licenseKey }) {
|
|
|
15602
16259
|
}
|
|
15603
16260
|
|
|
15604
16261
|
// src/components/SimpleModelViewer/SimpleModelViewer.tsx
|
|
15605
|
-
var
|
|
16262
|
+
var import_react40 = require("react");
|
|
15606
16263
|
var import_drei6 = require("@react-three/drei");
|
|
15607
16264
|
var import_fiber12 = require("@react-three/fiber");
|
|
15608
16265
|
var import_fiber13 = require("@react-three/fiber");
|
|
@@ -15642,7 +16299,7 @@ function applyClippingPlanes(scene, controls) {
|
|
|
15642
16299
|
camera.updateProjectionMatrix();
|
|
15643
16300
|
controls.maxDistance = size * 10;
|
|
15644
16301
|
}
|
|
15645
|
-
var ModelErrorBoundary2 = class extends
|
|
16302
|
+
var ModelErrorBoundary2 = class extends import_react40.Component {
|
|
15646
16303
|
constructor() {
|
|
15647
16304
|
super(...arguments);
|
|
15648
16305
|
this.state = { hasError: false };
|
|
@@ -15674,12 +16331,12 @@ function InnerModel({
|
|
|
15674
16331
|
meshopt: true
|
|
15675
16332
|
});
|
|
15676
16333
|
const invalidate = (0, import_fiber12.useThree)((state) => state.invalidate);
|
|
15677
|
-
const [hoveredKey, setHoveredKey] = (0,
|
|
15678
|
-
const scene = (0,
|
|
16334
|
+
const [hoveredKey, setHoveredKey] = (0, import_react40.useState)(null);
|
|
16335
|
+
const scene = (0, import_react40.useMemo)(
|
|
15679
16336
|
() => import_three_stdlib4.SkeletonUtils.clone(rawScene),
|
|
15680
16337
|
[rawScene]
|
|
15681
16338
|
);
|
|
15682
|
-
const meshMap = (0,
|
|
16339
|
+
const meshMap = (0, import_react40.useMemo)(() => {
|
|
15683
16340
|
const map = {};
|
|
15684
16341
|
scene.traverse((obj) => {
|
|
15685
16342
|
if (!isMeshNode2(obj)) return;
|
|
@@ -15688,7 +16345,7 @@ function InnerModel({
|
|
|
15688
16345
|
});
|
|
15689
16346
|
return map;
|
|
15690
16347
|
}, [scene]);
|
|
15691
|
-
const perKeyMaterials = (0,
|
|
16348
|
+
const perKeyMaterials = (0, import_react40.useMemo)(() => {
|
|
15692
16349
|
const map = {};
|
|
15693
16350
|
for (const [key, { ref }] of Object.entries(meshMap)) {
|
|
15694
16351
|
const original = Array.isArray(ref.material) ? ref.material[0] : ref.material;
|
|
@@ -15704,14 +16361,14 @@ function InnerModel({
|
|
|
15704
16361
|
}
|
|
15705
16362
|
return map;
|
|
15706
16363
|
}, [meshMap]);
|
|
15707
|
-
(0,
|
|
16364
|
+
(0, import_react40.useEffect)(
|
|
15708
16365
|
() => () => {
|
|
15709
16366
|
Object.values(perKeyMaterials).forEach((m) => m.dispose());
|
|
15710
16367
|
},
|
|
15711
16368
|
[perKeyMaterials]
|
|
15712
16369
|
);
|
|
15713
|
-
const notifiedUrl = (0,
|
|
15714
|
-
(0,
|
|
16370
|
+
const notifiedUrl = (0, import_react40.useRef)(null);
|
|
16371
|
+
(0, import_react40.useEffect)(() => {
|
|
15715
16372
|
if (notifiedUrl.current === url) return;
|
|
15716
16373
|
notifiedUrl.current = url;
|
|
15717
16374
|
scene.updateWorldMatrix(true, true);
|
|
@@ -15719,7 +16376,7 @@ function InnerModel({
|
|
|
15719
16376
|
onMeshesReady(meshMap);
|
|
15720
16377
|
onModelLoaded(scene);
|
|
15721
16378
|
}, [url, scene, meshMap, onMeshesReady, onModelLoaded]);
|
|
15722
|
-
(0,
|
|
16379
|
+
(0, import_react40.useEffect)(() => {
|
|
15723
16380
|
const controller = new AbortController();
|
|
15724
16381
|
void buildSceneBVH(scene, controller.signal);
|
|
15725
16382
|
return () => {
|
|
@@ -15727,26 +16384,26 @@ function InnerModel({
|
|
|
15727
16384
|
disposeSceneBVH(scene);
|
|
15728
16385
|
};
|
|
15729
16386
|
}, [scene]);
|
|
15730
|
-
const pointerRaycastEnabled = (0,
|
|
16387
|
+
const pointerRaycastEnabled = (0, import_react40.useMemo)(
|
|
15731
16388
|
() => !exceedsBvhBudget(scene),
|
|
15732
16389
|
[scene]
|
|
15733
16390
|
);
|
|
15734
|
-
(0,
|
|
16391
|
+
(0, import_react40.useEffect)(() => {
|
|
15735
16392
|
for (const [key, { ref }] of Object.entries(meshMap)) {
|
|
15736
16393
|
nodeRefsOut.current[key] = ref;
|
|
15737
16394
|
}
|
|
15738
16395
|
}, [meshMap, nodeRefsOut]);
|
|
15739
|
-
(0,
|
|
16396
|
+
(0, import_react40.useEffect)(() => {
|
|
15740
16397
|
if (!highlightOnHover && hoveredKey !== null) {
|
|
15741
16398
|
setHoveredKey(null);
|
|
15742
16399
|
}
|
|
15743
16400
|
}, [highlightOnHover, hoveredKey]);
|
|
15744
|
-
(0,
|
|
16401
|
+
(0, import_react40.useEffect)(() => {
|
|
15745
16402
|
return () => {
|
|
15746
16403
|
document.body.style.cursor = "default";
|
|
15747
16404
|
};
|
|
15748
16405
|
}, []);
|
|
15749
|
-
(0,
|
|
16406
|
+
(0, import_react40.useEffect)(() => {
|
|
15750
16407
|
for (const [key, { ref }] of Object.entries(meshMap)) {
|
|
15751
16408
|
ref.visible = !hiddenKeys.has(key);
|
|
15752
16409
|
const mat = perKeyMaterials[key];
|
|
@@ -15816,11 +16473,11 @@ function CameraFitter({
|
|
|
15816
16473
|
refitOnResize,
|
|
15817
16474
|
onInitialFitComplete
|
|
15818
16475
|
}) {
|
|
15819
|
-
const fitted = (0,
|
|
16476
|
+
const fitted = (0, import_react40.useRef)(false);
|
|
15820
16477
|
const invalidate = (0, import_fiber12.useThree)((state) => state.invalidate);
|
|
15821
16478
|
const size = (0, import_fiber12.useThree)((state) => state.size);
|
|
15822
|
-
const resizeTimerRef = (0,
|
|
15823
|
-
(0,
|
|
16479
|
+
const resizeTimerRef = (0, import_react40.useRef)(null);
|
|
16480
|
+
(0, import_react40.useEffect)(() => {
|
|
15824
16481
|
fitted.current = false;
|
|
15825
16482
|
invalidate();
|
|
15826
16483
|
}, [invalidate, scene]);
|
|
@@ -15840,7 +16497,7 @@ function CameraFitter({
|
|
|
15840
16497
|
}).catch(() => {
|
|
15841
16498
|
}).finally(onInitialFitComplete);
|
|
15842
16499
|
});
|
|
15843
|
-
(0,
|
|
16500
|
+
(0, import_react40.useEffect)(() => {
|
|
15844
16501
|
if (!refitOnResize || !fitted.current || !scene || !controlsRef.current) {
|
|
15845
16502
|
return;
|
|
15846
16503
|
}
|
|
@@ -15868,7 +16525,7 @@ function CameraFitter({
|
|
|
15868
16525
|
function ExposureController({ exposure }) {
|
|
15869
16526
|
const gl = (0, import_fiber12.useThree)((state) => state.gl);
|
|
15870
16527
|
const invalidate = (0, import_fiber12.useThree)((state) => state.invalidate);
|
|
15871
|
-
(0,
|
|
16528
|
+
(0, import_react40.useEffect)(() => {
|
|
15872
16529
|
gl.toneMappingExposure = exposure;
|
|
15873
16530
|
invalidate();
|
|
15874
16531
|
}, [gl, exposure, invalidate]);
|
|
@@ -15976,7 +16633,7 @@ function EnvironmentSection({
|
|
|
15976
16633
|
title,
|
|
15977
16634
|
children
|
|
15978
16635
|
}) {
|
|
15979
|
-
const [collapsed, setCollapsed] = (0,
|
|
16636
|
+
const [collapsed, setCollapsed] = (0, import_react40.useState)(false);
|
|
15980
16637
|
return /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)("div", { className: "env-section", children: [
|
|
15981
16638
|
/* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
|
|
15982
16639
|
"button",
|
|
@@ -16141,8 +16798,8 @@ function SceneObjectsPanel2({
|
|
|
16141
16798
|
onHover,
|
|
16142
16799
|
onToggleVisibility
|
|
16143
16800
|
}) {
|
|
16144
|
-
const [search, setSearch] = (0,
|
|
16145
|
-
const [rowHovered, setRowHovered] = (0,
|
|
16801
|
+
const [search, setSearch] = (0, import_react40.useState)("");
|
|
16802
|
+
const [rowHovered, setRowHovered] = (0, import_react40.useState)(null);
|
|
16146
16803
|
const entries = Object.values(meshKeys).filter(
|
|
16147
16804
|
(m) => m.label.toLowerCase().includes(search.toLowerCase())
|
|
16148
16805
|
);
|
|
@@ -16266,35 +16923,35 @@ function SimpleModelViewer({
|
|
|
16266
16923
|
onModelLoaded,
|
|
16267
16924
|
onLoadError
|
|
16268
16925
|
}) {
|
|
16269
|
-
const cameraControlsRef = (0,
|
|
16270
|
-
const nodeRefsOut = (0,
|
|
16271
|
-
const fileInputRef = (0,
|
|
16272
|
-
const uploadedModelRef = (0,
|
|
16273
|
-
const dragDepthRef = (0,
|
|
16274
|
-
const [loadedScene, setLoadedScene] = (0,
|
|
16275
|
-
const [largeModelTriangles, setLargeModelTriangles] = (0,
|
|
16926
|
+
const cameraControlsRef = (0, import_react40.useRef)(null);
|
|
16927
|
+
const nodeRefsOut = (0, import_react40.useRef)({});
|
|
16928
|
+
const fileInputRef = (0, import_react40.useRef)(null);
|
|
16929
|
+
const uploadedModelRef = (0, import_react40.useRef)(null);
|
|
16930
|
+
const dragDepthRef = (0, import_react40.useRef)(0);
|
|
16931
|
+
const [loadedScene, setLoadedScene] = (0, import_react40.useState)(null);
|
|
16932
|
+
const [largeModelTriangles, setLargeModelTriangles] = (0, import_react40.useState)(
|
|
16276
16933
|
null
|
|
16277
16934
|
);
|
|
16278
|
-
const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = (0,
|
|
16279
|
-
const [meshKeys, setMeshKeys] = (0,
|
|
16280
|
-
const [hiddenKeys, setHiddenKeys] = (0,
|
|
16281
|
-
const [selectedKey, setSelectedKey] = (0,
|
|
16282
|
-
const [panelHoveredKey, setPanelHoveredKey] = (0,
|
|
16283
|
-
const [uploadedModelUrl, setUploadedModelUrl] = (0,
|
|
16284
|
-
const [uploadedModelName, setUploadedModelName] = (0,
|
|
16935
|
+
const [largeModelNoticeDismissed, setLargeModelNoticeDismissed] = (0, import_react40.useState)(false);
|
|
16936
|
+
const [meshKeys, setMeshKeys] = (0, import_react40.useState)({});
|
|
16937
|
+
const [hiddenKeys, setHiddenKeys] = (0, import_react40.useState)(/* @__PURE__ */ new Set());
|
|
16938
|
+
const [selectedKey, setSelectedKey] = (0, import_react40.useState)(null);
|
|
16939
|
+
const [panelHoveredKey, setPanelHoveredKey] = (0, import_react40.useState)(null);
|
|
16940
|
+
const [uploadedModelUrl, setUploadedModelUrl] = (0, import_react40.useState)(null);
|
|
16941
|
+
const [uploadedModelName, setUploadedModelName] = (0, import_react40.useState)(
|
|
16285
16942
|
null
|
|
16286
16943
|
);
|
|
16287
|
-
const [modelLoadFailed, setModelLoadFailed] = (0,
|
|
16288
|
-
const [isResolvingUpload, setIsResolvingUpload] = (0,
|
|
16289
|
-
const [initialFitComplete, setInitialFitComplete] = (0,
|
|
16290
|
-
const [isDragActive, setIsDragActive] = (0,
|
|
16291
|
-
const [isPanelOpen, setIsPanelOpen] = (0,
|
|
16944
|
+
const [modelLoadFailed, setModelLoadFailed] = (0, import_react40.useState)(false);
|
|
16945
|
+
const [isResolvingUpload, setIsResolvingUpload] = (0, import_react40.useState)(false);
|
|
16946
|
+
const [initialFitComplete, setInitialFitComplete] = (0, import_react40.useState)(false);
|
|
16947
|
+
const [isDragActive, setIsDragActive] = (0, import_react40.useState)(false);
|
|
16948
|
+
const [isPanelOpen, setIsPanelOpen] = (0, import_react40.useState)(
|
|
16292
16949
|
() => typeof window === "undefined" ? true : window.innerWidth > 900
|
|
16293
16950
|
);
|
|
16294
|
-
const [isEnvPanelOpen, setIsEnvPanelOpen] = (0,
|
|
16951
|
+
const [isEnvPanelOpen, setIsEnvPanelOpen] = (0, import_react40.useState)(
|
|
16295
16952
|
() => typeof window === "undefined" ? true : window.innerWidth > 900
|
|
16296
16953
|
);
|
|
16297
|
-
const [envSettings, setEnvSettings] = (0,
|
|
16954
|
+
const [envSettings, setEnvSettings] = (0, import_react40.useState)({
|
|
16298
16955
|
backgroundEnabled,
|
|
16299
16956
|
backgroundColor,
|
|
16300
16957
|
autoRotate,
|
|
@@ -16306,7 +16963,7 @@ function SimpleModelViewer({
|
|
|
16306
16963
|
directionalColor
|
|
16307
16964
|
});
|
|
16308
16965
|
const activeModelUrl = enableModelUpload ? uploadedModelUrl : modelUrl;
|
|
16309
|
-
const resetModelState = (0,
|
|
16966
|
+
const resetModelState = (0, import_react40.useCallback)(() => {
|
|
16310
16967
|
setLoadedScene(null);
|
|
16311
16968
|
setLargeModelTriangles(null);
|
|
16312
16969
|
setLargeModelNoticeDismissed(false);
|
|
@@ -16317,11 +16974,11 @@ function SimpleModelViewer({
|
|
|
16317
16974
|
setModelLoadFailed(false);
|
|
16318
16975
|
setInitialFitComplete(false);
|
|
16319
16976
|
}, []);
|
|
16320
|
-
const revokeUploadedModelUrl = (0,
|
|
16977
|
+
const revokeUploadedModelUrl = (0, import_react40.useCallback)(() => {
|
|
16321
16978
|
uploadedModelRef.current?.cleanup();
|
|
16322
16979
|
uploadedModelRef.current = null;
|
|
16323
16980
|
}, []);
|
|
16324
|
-
const clearUploadedModel = (0,
|
|
16981
|
+
const clearUploadedModel = (0, import_react40.useCallback)(() => {
|
|
16325
16982
|
revokeUploadedModelUrl();
|
|
16326
16983
|
setUploadedModelUrl(null);
|
|
16327
16984
|
setUploadedModelName(null);
|
|
@@ -16350,7 +17007,7 @@ function SimpleModelViewer({
|
|
|
16350
17007
|
resetModelState,
|
|
16351
17008
|
revokeUploadedModelUrl
|
|
16352
17009
|
]);
|
|
16353
|
-
const handleUploadedModel = (0,
|
|
17010
|
+
const handleUploadedModel = (0, import_react40.useCallback)(
|
|
16354
17011
|
async (file) => {
|
|
16355
17012
|
setIsResolvingUpload(true);
|
|
16356
17013
|
try {
|
|
@@ -16369,7 +17026,7 @@ function SimpleModelViewer({
|
|
|
16369
17026
|
},
|
|
16370
17027
|
[onLoadError, resetModelState, revokeUploadedModelUrl]
|
|
16371
17028
|
);
|
|
16372
|
-
const handleFileSelection = (0,
|
|
17029
|
+
const handleFileSelection = (0, import_react40.useCallback)(
|
|
16373
17030
|
(files) => {
|
|
16374
17031
|
const file = files?.[0];
|
|
16375
17032
|
if (!file) return;
|
|
@@ -16377,29 +17034,29 @@ function SimpleModelViewer({
|
|
|
16377
17034
|
},
|
|
16378
17035
|
[handleUploadedModel]
|
|
16379
17036
|
);
|
|
16380
|
-
(0,
|
|
17037
|
+
(0, import_react40.useEffect)(() => {
|
|
16381
17038
|
return () => {
|
|
16382
17039
|
revokeUploadedModelUrl();
|
|
16383
17040
|
};
|
|
16384
17041
|
}, [revokeUploadedModelUrl]);
|
|
16385
|
-
const handleEnvSettingChange = (0,
|
|
17042
|
+
const handleEnvSettingChange = (0, import_react40.useCallback)(
|
|
16386
17043
|
(key, value) => {
|
|
16387
17044
|
setEnvSettings((prev) => ({ ...prev, [key]: value }));
|
|
16388
17045
|
},
|
|
16389
17046
|
[]
|
|
16390
17047
|
);
|
|
16391
|
-
(0,
|
|
17048
|
+
(0, import_react40.useEffect)(() => {
|
|
16392
17049
|
setEnvSettings((prev) => ({ ...prev, backgroundColor }));
|
|
16393
17050
|
}, [backgroundColor]);
|
|
16394
|
-
(0,
|
|
17051
|
+
(0, import_react40.useEffect)(() => {
|
|
16395
17052
|
resetModelState();
|
|
16396
17053
|
}, [activeModelUrl, resetModelState]);
|
|
16397
|
-
const handleMeshesReady = (0,
|
|
17054
|
+
const handleMeshesReady = (0, import_react40.useCallback)((meshes) => {
|
|
16398
17055
|
setMeshKeys(meshes);
|
|
16399
17056
|
setHiddenKeys(/* @__PURE__ */ new Set());
|
|
16400
17057
|
setSelectedKey(null);
|
|
16401
17058
|
}, []);
|
|
16402
|
-
const handleModelLoaded = (0,
|
|
17059
|
+
const handleModelLoaded = (0, import_react40.useCallback)(
|
|
16403
17060
|
(scene) => {
|
|
16404
17061
|
setLoadedScene(scene);
|
|
16405
17062
|
setModelLoadFailed(false);
|
|
@@ -16410,14 +17067,14 @@ function SimpleModelViewer({
|
|
|
16410
17067
|
},
|
|
16411
17068
|
[onModelLoaded]
|
|
16412
17069
|
);
|
|
16413
|
-
const handleLoadError = (0,
|
|
17070
|
+
const handleLoadError = (0, import_react40.useCallback)(
|
|
16414
17071
|
(error) => {
|
|
16415
17072
|
setModelLoadFailed(true);
|
|
16416
17073
|
onLoadError?.(error);
|
|
16417
17074
|
},
|
|
16418
17075
|
[onLoadError]
|
|
16419
17076
|
);
|
|
16420
|
-
const focusMesh = (0,
|
|
17077
|
+
const focusMesh = (0, import_react40.useCallback)(
|
|
16421
17078
|
(key) => {
|
|
16422
17079
|
setSelectedKey(key);
|
|
16423
17080
|
if (!zoomOnSelected) return;
|
|
@@ -16433,14 +17090,14 @@ function SimpleModelViewer({
|
|
|
16433
17090
|
},
|
|
16434
17091
|
[zoomOnSelected]
|
|
16435
17092
|
);
|
|
16436
|
-
const handleToggleVisibility = (0,
|
|
17093
|
+
const handleToggleVisibility = (0, import_react40.useCallback)((key) => {
|
|
16437
17094
|
setHiddenKeys((prev) => {
|
|
16438
17095
|
const next = new Set(prev);
|
|
16439
17096
|
next.has(key) ? next.delete(key) : next.add(key);
|
|
16440
17097
|
return next;
|
|
16441
17098
|
});
|
|
16442
17099
|
}, []);
|
|
16443
|
-
const handleDragEnter = (0,
|
|
17100
|
+
const handleDragEnter = (0, import_react40.useCallback)(
|
|
16444
17101
|
(event) => {
|
|
16445
17102
|
if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
|
|
16446
17103
|
return;
|
|
@@ -16452,7 +17109,7 @@ function SimpleModelViewer({
|
|
|
16452
17109
|
},
|
|
16453
17110
|
[enableModelUpload]
|
|
16454
17111
|
);
|
|
16455
|
-
const handleDragOver = (0,
|
|
17112
|
+
const handleDragOver = (0, import_react40.useCallback)(
|
|
16456
17113
|
(event) => {
|
|
16457
17114
|
if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
|
|
16458
17115
|
return;
|
|
@@ -16464,7 +17121,7 @@ function SimpleModelViewer({
|
|
|
16464
17121
|
},
|
|
16465
17122
|
[enableModelUpload]
|
|
16466
17123
|
);
|
|
16467
|
-
const handleDragLeave = (0,
|
|
17124
|
+
const handleDragLeave = (0, import_react40.useCallback)(
|
|
16468
17125
|
(event) => {
|
|
16469
17126
|
if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
|
|
16470
17127
|
return;
|
|
@@ -16478,7 +17135,7 @@ function SimpleModelViewer({
|
|
|
16478
17135
|
},
|
|
16479
17136
|
[enableModelUpload]
|
|
16480
17137
|
);
|
|
16481
|
-
const handleDrop = (0,
|
|
17138
|
+
const handleDrop = (0, import_react40.useCallback)(
|
|
16482
17139
|
(event) => {
|
|
16483
17140
|
if (!enableModelUpload || !supportsDraggedFiles(event.dataTransfer)) {
|
|
16484
17141
|
return;
|
|
@@ -16571,7 +17228,7 @@ function SimpleModelViewer({
|
|
|
16571
17228
|
raycaster: { firstHitOnly: true },
|
|
16572
17229
|
style: { background: envSettings.backgroundColor },
|
|
16573
17230
|
camera: { position: [0, 0, 5], fov: 45, near: 0.1, far: 1e3 },
|
|
16574
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(
|
|
17231
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime46.jsxs)(import_react40.Suspense, { fallback: null, children: [
|
|
16575
17232
|
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
16576
17233
|
"ambientLight",
|
|
16577
17234
|
{
|
|
@@ -16589,7 +17246,7 @@ function SimpleModelViewer({
|
|
|
16589
17246
|
}
|
|
16590
17247
|
),
|
|
16591
17248
|
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)("directionalLight", { position: [-5, 5, 5], intensity: 0.5 }),
|
|
16592
|
-
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
17249
|
+
/* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_react40.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
16593
17250
|
import_drei6.Environment,
|
|
16594
17251
|
{
|
|
16595
17252
|
preset: envSettings.envPreset,
|
|
@@ -16608,7 +17265,7 @@ function SimpleModelViewer({
|
|
|
16608
17265
|
ModelErrorBoundary2,
|
|
16609
17266
|
{
|
|
16610
17267
|
onLoadError: handleLoadError,
|
|
16611
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
17268
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(import_react40.Suspense, { fallback: null, children: /* @__PURE__ */ (0, import_jsx_runtime46.jsx)(
|
|
16612
17269
|
InnerModel,
|
|
16613
17270
|
{
|
|
16614
17271
|
url: activeModelUrl,
|
|
@@ -16707,18 +17364,18 @@ function SimpleModelViewer({
|
|
|
16707
17364
|
}
|
|
16708
17365
|
|
|
16709
17366
|
// src/hooks/useViewerSelection.ts
|
|
16710
|
-
var
|
|
17367
|
+
var import_react41 = require("react");
|
|
16711
17368
|
function useViewerSelection(initialSelectedObjectBinding = null) {
|
|
16712
|
-
const [selectedObjectBinding, setSelectedObjectBinding] = (0,
|
|
17369
|
+
const [selectedObjectBinding, setSelectedObjectBinding] = (0, import_react41.useState)(
|
|
16713
17370
|
initialSelectedObjectBinding
|
|
16714
17371
|
);
|
|
16715
|
-
const handleObjectSelect = (0,
|
|
17372
|
+
const handleObjectSelect = (0, import_react41.useCallback)(
|
|
16716
17373
|
(binding) => {
|
|
16717
17374
|
setSelectedObjectBinding(binding);
|
|
16718
17375
|
},
|
|
16719
17376
|
[setSelectedObjectBinding]
|
|
16720
17377
|
);
|
|
16721
|
-
const clearSelection = (0,
|
|
17378
|
+
const clearSelection = (0, import_react41.useCallback)(() => {
|
|
16722
17379
|
setSelectedObjectBinding(null);
|
|
16723
17380
|
}, []);
|
|
16724
17381
|
return {
|
|
@@ -16730,9 +17387,9 @@ function useViewerSelection(initialSelectedObjectBinding = null) {
|
|
|
16730
17387
|
}
|
|
16731
17388
|
|
|
16732
17389
|
// src/hooks/useObjectBinding.ts
|
|
16733
|
-
var
|
|
17390
|
+
var import_react42 = require("react");
|
|
16734
17391
|
function useObjectBinding(objectBindings, objectId) {
|
|
16735
|
-
return (0,
|
|
17392
|
+
return (0, import_react42.useMemo)(() => {
|
|
16736
17393
|
if (!objectId) {
|
|
16737
17394
|
return null;
|
|
16738
17395
|
}
|
|
@@ -16747,23 +17404,23 @@ function useObjectBinding(objectBindings, objectId) {
|
|
|
16747
17404
|
}
|
|
16748
17405
|
|
|
16749
17406
|
// src/hooks/useObjectBindingIds.ts
|
|
16750
|
-
var
|
|
17407
|
+
var import_react43 = require("react");
|
|
16751
17408
|
function useObjectBindingIds(objectBindings) {
|
|
16752
|
-
return (0,
|
|
17409
|
+
return (0, import_react43.useMemo)(
|
|
16753
17410
|
() => Object.values(objectBindings).map((binding) => binding.modelObjectId),
|
|
16754
17411
|
[objectBindings]
|
|
16755
17412
|
);
|
|
16756
17413
|
}
|
|
16757
17414
|
|
|
16758
17415
|
// src/hooks/useObjectVisibility.ts
|
|
16759
|
-
var
|
|
17416
|
+
var import_react44 = require("react");
|
|
16760
17417
|
function getHiddenObjects(objectBindings) {
|
|
16761
17418
|
return Object.fromEntries(
|
|
16762
17419
|
Object.entries(objectBindings).filter(([, binding]) => binding.visible === false).map(([bindingKey]) => [bindingKey, true])
|
|
16763
17420
|
);
|
|
16764
17421
|
}
|
|
16765
17422
|
function useObjectVisibility(objectBindings, onObjectBindingsChange) {
|
|
16766
|
-
const updateVisibility = (0,
|
|
17423
|
+
const updateVisibility = (0, import_react44.useCallback)(
|
|
16767
17424
|
(objectId, visible) => {
|
|
16768
17425
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
16769
17426
|
if (!bindingKey) return;
|
|
@@ -16779,15 +17436,15 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
|
|
|
16779
17436
|
},
|
|
16780
17437
|
[objectBindings, onObjectBindingsChange]
|
|
16781
17438
|
);
|
|
16782
|
-
const hideObject = (0,
|
|
17439
|
+
const hideObject = (0, import_react44.useCallback)(
|
|
16783
17440
|
(objectId) => updateVisibility(objectId, false),
|
|
16784
17441
|
[updateVisibility]
|
|
16785
17442
|
);
|
|
16786
|
-
const showObject = (0,
|
|
17443
|
+
const showObject = (0, import_react44.useCallback)(
|
|
16787
17444
|
(objectId) => updateVisibility(objectId, true),
|
|
16788
17445
|
[updateVisibility]
|
|
16789
17446
|
);
|
|
16790
|
-
const toggleObjectVisibility = (0,
|
|
17447
|
+
const toggleObjectVisibility = (0, import_react44.useCallback)(
|
|
16791
17448
|
(objectId) => {
|
|
16792
17449
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
16793
17450
|
if (!bindingKey) return;
|
|
@@ -16803,7 +17460,7 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
|
|
|
16803
17460
|
},
|
|
16804
17461
|
[objectBindings, onObjectBindingsChange]
|
|
16805
17462
|
);
|
|
16806
|
-
const clearHiddenObjects = (0,
|
|
17463
|
+
const clearHiddenObjects = (0, import_react44.useCallback)(() => {
|
|
16807
17464
|
onObjectBindingsChange(
|
|
16808
17465
|
Object.fromEntries(
|
|
16809
17466
|
Object.entries(objectBindings).map(([bindingKey, binding]) => [
|
|
@@ -16813,17 +17470,17 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
|
|
|
16813
17470
|
)
|
|
16814
17471
|
);
|
|
16815
17472
|
}, [objectBindings, onObjectBindingsChange]);
|
|
16816
|
-
const hiddenObjects = (0,
|
|
17473
|
+
const hiddenObjects = (0, import_react44.useMemo)(
|
|
16817
17474
|
() => getHiddenObjects(objectBindings),
|
|
16818
17475
|
[objectBindings]
|
|
16819
17476
|
);
|
|
16820
|
-
const hiddenObjectIds = (0,
|
|
17477
|
+
const hiddenObjectIds = (0, import_react44.useMemo)(
|
|
16821
17478
|
() => Object.entries(hiddenObjects).map(
|
|
16822
17479
|
([bindingKey]) => objectBindings[bindingKey]?.id ?? bindingKey
|
|
16823
17480
|
),
|
|
16824
17481
|
[hiddenObjects, objectBindings]
|
|
16825
17482
|
);
|
|
16826
|
-
const isObjectHidden = (0,
|
|
17483
|
+
const isObjectHidden = (0, import_react44.useCallback)(
|
|
16827
17484
|
(objectId) => {
|
|
16828
17485
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
16829
17486
|
return bindingKey ? objectBindings[bindingKey]?.visible === false : false;
|
|
@@ -16842,10 +17499,10 @@ function useObjectVisibility(objectBindings, onObjectBindingsChange) {
|
|
|
16842
17499
|
}
|
|
16843
17500
|
|
|
16844
17501
|
// src/hooks/useViewerHover.ts
|
|
16845
|
-
var
|
|
17502
|
+
var import_react45 = require("react");
|
|
16846
17503
|
function useViewerHover() {
|
|
16847
|
-
const [hoveredObjectBinding, setHoveredObjectBinding] = (0,
|
|
16848
|
-
const handleHoveredObject = (0,
|
|
17504
|
+
const [hoveredObjectBinding, setHoveredObjectBinding] = (0, import_react45.useState)(null);
|
|
17505
|
+
const handleHoveredObject = (0, import_react45.useCallback)(
|
|
16849
17506
|
(binding) => {
|
|
16850
17507
|
setHoveredObjectBinding(binding);
|
|
16851
17508
|
},
|
|
@@ -16858,18 +17515,18 @@ function useViewerHover() {
|
|
|
16858
17515
|
}
|
|
16859
17516
|
|
|
16860
17517
|
// src/hooks/useViewerModel.ts
|
|
16861
|
-
var
|
|
17518
|
+
var import_react46 = require("react");
|
|
16862
17519
|
var import_three22 = require("three");
|
|
16863
17520
|
function toTuple2(vector) {
|
|
16864
17521
|
return [vector.x, vector.y, vector.z];
|
|
16865
17522
|
}
|
|
16866
17523
|
function useViewerModel() {
|
|
16867
|
-
const [isLoading, setIsLoading] = (0,
|
|
16868
|
-
const [isReady, setIsReady] = (0,
|
|
16869
|
-
const [error, setError] = (0,
|
|
16870
|
-
const [bounds, setBounds] = (0,
|
|
16871
|
-
const [objectCount, setObjectCount] = (0,
|
|
16872
|
-
const handleModelLoaded = (0,
|
|
17524
|
+
const [isLoading, setIsLoading] = (0, import_react46.useState)(true);
|
|
17525
|
+
const [isReady, setIsReady] = (0, import_react46.useState)(false);
|
|
17526
|
+
const [error, setError] = (0, import_react46.useState)(null);
|
|
17527
|
+
const [bounds, setBounds] = (0, import_react46.useState)(null);
|
|
17528
|
+
const [objectCount, setObjectCount] = (0, import_react46.useState)(0);
|
|
17529
|
+
const handleModelLoaded = (0, import_react46.useCallback)((scene) => {
|
|
16873
17530
|
const box = new import_three22.Box3().setFromObject(scene);
|
|
16874
17531
|
const min = box.min.clone();
|
|
16875
17532
|
const max = box.max.clone();
|
|
@@ -16894,14 +17551,14 @@ function useViewerModel() {
|
|
|
16894
17551
|
setIsLoading(false);
|
|
16895
17552
|
setIsReady(true);
|
|
16896
17553
|
}, []);
|
|
16897
|
-
const handleLoadError = (0,
|
|
17554
|
+
const handleLoadError = (0, import_react46.useCallback)((nextError) => {
|
|
16898
17555
|
setError(nextError);
|
|
16899
17556
|
setBounds(null);
|
|
16900
17557
|
setObjectCount(0);
|
|
16901
17558
|
setIsLoading(false);
|
|
16902
17559
|
setIsReady(false);
|
|
16903
17560
|
}, []);
|
|
16904
|
-
const resetModelState = (0,
|
|
17561
|
+
const resetModelState = (0, import_react46.useCallback)(() => {
|
|
16905
17562
|
setIsLoading(true);
|
|
16906
17563
|
setIsReady(false);
|
|
16907
17564
|
setError(null);
|
|
@@ -16921,10 +17578,10 @@ function useViewerModel() {
|
|
|
16921
17578
|
}
|
|
16922
17579
|
|
|
16923
17580
|
// src/hooks/useViewerActions.ts
|
|
16924
|
-
var
|
|
17581
|
+
var import_react47 = require("react");
|
|
16925
17582
|
function useViewerActions(objectBindings, options = {}) {
|
|
16926
17583
|
const { onAction, onObjectBindingsChange } = options;
|
|
16927
|
-
const getActionsForObject = (0,
|
|
17584
|
+
const getActionsForObject = (0, import_react47.useCallback)(
|
|
16928
17585
|
(objectId) => {
|
|
16929
17586
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
16930
17587
|
if (!bindingKey) {
|
|
@@ -16934,7 +17591,7 @@ function useViewerActions(objectBindings, options = {}) {
|
|
|
16934
17591
|
},
|
|
16935
17592
|
[objectBindings]
|
|
16936
17593
|
);
|
|
16937
|
-
const runAction = (0,
|
|
17594
|
+
const runAction = (0, import_react47.useCallback)(
|
|
16938
17595
|
(objectId, actionId) => {
|
|
16939
17596
|
const bindingKey = resolveBindingKey(objectBindings, objectId);
|
|
16940
17597
|
if (!bindingKey) {
|
|
@@ -16976,7 +17633,7 @@ function useViewerActions(objectBindings, options = {}) {
|
|
|
16976
17633
|
}
|
|
16977
17634
|
|
|
16978
17635
|
// src/hooks/useShareableViewerState.ts
|
|
16979
|
-
var
|
|
17636
|
+
var import_react48 = require("react");
|
|
16980
17637
|
function serializeState(cameraState, objectBindings, selectedObjectBinding) {
|
|
16981
17638
|
const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
|
|
16982
17639
|
return {
|
|
@@ -16996,8 +17653,8 @@ function useShareableViewerState({
|
|
|
16996
17653
|
debounceMs = 400,
|
|
16997
17654
|
autoSync = true
|
|
16998
17655
|
}) {
|
|
16999
|
-
const lastWrittenRawRef = (0,
|
|
17000
|
-
const buildHash = (0,
|
|
17656
|
+
const lastWrittenRawRef = (0, import_react48.useRef)(null);
|
|
17657
|
+
const buildHash = (0, import_react48.useCallback)(() => {
|
|
17001
17658
|
const state = serializeState(
|
|
17002
17659
|
cameraState,
|
|
17003
17660
|
objectBindings,
|
|
@@ -17005,12 +17662,12 @@ function useShareableViewerState({
|
|
|
17005
17662
|
);
|
|
17006
17663
|
return `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
|
|
17007
17664
|
}, [cameraState, objectBindings, selectedObjectBinding, paramKey]);
|
|
17008
|
-
const getShareUrl = (0,
|
|
17665
|
+
const getShareUrl = (0, import_react48.useCallback)(() => {
|
|
17009
17666
|
const url = new URL(window.location.href);
|
|
17010
17667
|
url.hash = buildHash();
|
|
17011
17668
|
return url.toString();
|
|
17012
17669
|
}, [buildHash]);
|
|
17013
|
-
const syncUrl = (0,
|
|
17670
|
+
const syncUrl = (0, import_react48.useCallback)(() => {
|
|
17014
17671
|
const url = new URL(window.location.href);
|
|
17015
17672
|
url.hash = buildHash();
|
|
17016
17673
|
window.history.replaceState(null, "", url.toString());
|
|
@@ -17018,7 +17675,7 @@ function useShareableViewerState({
|
|
|
17018
17675
|
paramKey
|
|
17019
17676
|
);
|
|
17020
17677
|
}, [buildHash, paramKey]);
|
|
17021
|
-
const latestStateRef = (0,
|
|
17678
|
+
const latestStateRef = (0, import_react48.useRef)({
|
|
17022
17679
|
cameraState,
|
|
17023
17680
|
objectBindings,
|
|
17024
17681
|
selectedObjectBinding
|
|
@@ -17028,9 +17685,9 @@ function useShareableViewerState({
|
|
|
17028
17685
|
objectBindings,
|
|
17029
17686
|
selectedObjectBinding
|
|
17030
17687
|
};
|
|
17031
|
-
const [restoreAttempted, setRestoreAttempted] = (0,
|
|
17032
|
-
const lastSyncedHashRef = (0,
|
|
17033
|
-
(0,
|
|
17688
|
+
const [restoreAttempted, setRestoreAttempted] = (0, import_react48.useState)(false);
|
|
17689
|
+
const lastSyncedHashRef = (0, import_react48.useRef)(null);
|
|
17690
|
+
(0, import_react48.useEffect)(() => {
|
|
17034
17691
|
if (!autoSync || !restoreAttempted) return;
|
|
17035
17692
|
const tick = () => {
|
|
17036
17693
|
const current = latestStateRef.current;
|
|
@@ -17052,9 +17709,9 @@ function useShareableViewerState({
|
|
|
17052
17709
|
const id = window.setInterval(tick, debounceMs);
|
|
17053
17710
|
return () => window.clearInterval(id);
|
|
17054
17711
|
}, [autoSync, restoreAttempted, debounceMs, paramKey]);
|
|
17055
|
-
const lastRestoredRawRef = (0,
|
|
17056
|
-
const restoringRawRef = (0,
|
|
17057
|
-
const restoreFromUrl = (0,
|
|
17712
|
+
const lastRestoredRawRef = (0, import_react48.useRef)(null);
|
|
17713
|
+
const restoringRawRef = (0, import_react48.useRef)(null);
|
|
17714
|
+
const restoreFromUrl = (0, import_react48.useCallback)(async () => {
|
|
17058
17715
|
setRestoreAttempted(true);
|
|
17059
17716
|
const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
|
|
17060
17717
|
const raw = new URLSearchParams(rawHash).get(paramKey);
|
|
@@ -17107,7 +17764,7 @@ function useShareableViewerState({
|
|
|
17107
17764
|
setCameraState,
|
|
17108
17765
|
setSelectedObjectBinding
|
|
17109
17766
|
]);
|
|
17110
|
-
(0,
|
|
17767
|
+
(0, import_react48.useEffect)(() => {
|
|
17111
17768
|
const handleHashChange = () => {
|
|
17112
17769
|
void restoreFromUrl();
|
|
17113
17770
|
};
|