@liveroom-tech/react-immersive 1.0.0 → 1.0.2
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 +3 -1
- package/dist/index.d.mts +19 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +151 -3
- package/dist/index.mjs +150 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1018,6 +1018,8 @@ Example:
|
|
|
1018
1018
|
/>
|
|
1019
1019
|
```
|
|
1020
1020
|
|
|
1021
|
+
Passing an explicit `camera` counts as choosing the initial view yourself, so the mount-time auto-fit (see [`onAutoFit`](#onautofit)) is skipped automatically and your `position`/`fov` stick. Provide `onAutoFit` as well only if you want to run your _own_ fit-to-scene logic instead.
|
|
1022
|
+
|
|
1021
1023
|
### `backgroundColor`
|
|
1022
1024
|
|
|
1023
1025
|
Optional background color for the viewer canvas.
|
|
@@ -1273,7 +1275,7 @@ true;
|
|
|
1273
1275
|
|
|
1274
1276
|
### `onAutoFit`
|
|
1275
1277
|
|
|
1276
|
-
Optional async callback fired once the model has loaded and the scene is ready. When provided, the viewer calls it so consumers can run their own fit-to-scene behavior (e.g. an animated fit); if omitted, the viewer falls back to its internal `fitScene
|
|
1278
|
+
Optional async callback fired once the model has loaded and the scene is ready. When provided, the viewer calls it so consumers can run their own fit-to-scene behavior (e.g. an animated fit); if omitted, the viewer falls back to its internal `fitScene` — unless an explicit [`camera`](#camera) prop was given, in which case that position is treated as the chosen initial view and `fitScene` is skipped.
|
|
1277
1279
|
|
|
1278
1280
|
Current callback shape:
|
|
1279
1281
|
|
package/dist/index.d.mts
CHANGED
|
@@ -669,4 +669,22 @@ type ViewerActionsProps = {
|
|
|
669
669
|
};
|
|
670
670
|
declare function useViewerActions(objectBindings: Record<string, ObjectBinding>, options?: ViewerActionsOptions): ViewerActionsProps;
|
|
671
671
|
|
|
672
|
-
|
|
672
|
+
type UseShareableViewerStateOptions = {
|
|
673
|
+
cameraState: ViewerCameraState | null;
|
|
674
|
+
setCameraState: (state: ViewerCameraState, enableTransition?: boolean) => Promise<boolean>;
|
|
675
|
+
objectBindings: Record<string, ObjectBinding>;
|
|
676
|
+
onObjectBindingsChange: (next: Record<string, ObjectBinding>) => void;
|
|
677
|
+
selectedObjectBinding: ObjectBinding | null;
|
|
678
|
+
setSelectedObjectBinding: (binding: ObjectBinding | null) => void;
|
|
679
|
+
paramKey?: string;
|
|
680
|
+
debounceMs?: number;
|
|
681
|
+
autoSync?: boolean;
|
|
682
|
+
};
|
|
683
|
+
type ShareableViewerStateProps = {
|
|
684
|
+
getShareUrl: () => string;
|
|
685
|
+
syncUrl: () => void;
|
|
686
|
+
restoreFromUrl: () => Promise<boolean>;
|
|
687
|
+
};
|
|
688
|
+
declare function useShareableViewerState({ cameraState, setCameraState, objectBindings, onObjectBindingsChange, selectedObjectBinding, setSelectedObjectBinding, paramKey, debounceMs, autoSync, }: UseShareableViewerStateOptions): ShareableViewerStateProps;
|
|
689
|
+
|
|
690
|
+
export { type AnimationClipInfo, type AnimationControls, type AnimationLoopMode, type AnimationPlaybackState, BindingBuilder, type CaptureImageOptions, type CustomObjectBindingDataPanelProps, type CustomSceneObjectsPanelProps, type HiddenObjectsState, type HomeCameraState, MATERIAL_BLENDING_MODES, MATERIAL_SIDES, ModelViewer, type ModelViewerProps, type ObjectActionEvent, type ObjectBinding, type ObjectBindingAction, type ObjectBindingMaterial, type ObjectBindingMaterialBlendingMode, type ObjectBindingMaterialSide, type ObjectBindingTextureSlot, type ObjectVisibilityProps, type SceneAnimationClipConfig, type SceneAnimationConfig, type SceneConfig, SimpleModelViewer, type SimpleModelViewerProps, type ViewerActionsOptions, type ViewerActionsProps, type ViewerAnimationsProps, type ViewerCameraProps, type ViewerCameraState, type ViewerModelBounds, type ViewerModelProps, type ViewerReadyState, type ViewerSelectionProps, useObjectBinding, useObjectBindingIds, useObjectVisibility, useShareableViewerState, useViewerActions, useViewerAnimations, useViewerCamera, useViewerHover, useViewerModel, useViewerSelection };
|
package/dist/index.d.ts
CHANGED
|
@@ -669,4 +669,22 @@ type ViewerActionsProps = {
|
|
|
669
669
|
};
|
|
670
670
|
declare function useViewerActions(objectBindings: Record<string, ObjectBinding>, options?: ViewerActionsOptions): ViewerActionsProps;
|
|
671
671
|
|
|
672
|
-
|
|
672
|
+
type UseShareableViewerStateOptions = {
|
|
673
|
+
cameraState: ViewerCameraState | null;
|
|
674
|
+
setCameraState: (state: ViewerCameraState, enableTransition?: boolean) => Promise<boolean>;
|
|
675
|
+
objectBindings: Record<string, ObjectBinding>;
|
|
676
|
+
onObjectBindingsChange: (next: Record<string, ObjectBinding>) => void;
|
|
677
|
+
selectedObjectBinding: ObjectBinding | null;
|
|
678
|
+
setSelectedObjectBinding: (binding: ObjectBinding | null) => void;
|
|
679
|
+
paramKey?: string;
|
|
680
|
+
debounceMs?: number;
|
|
681
|
+
autoSync?: boolean;
|
|
682
|
+
};
|
|
683
|
+
type ShareableViewerStateProps = {
|
|
684
|
+
getShareUrl: () => string;
|
|
685
|
+
syncUrl: () => void;
|
|
686
|
+
restoreFromUrl: () => Promise<boolean>;
|
|
687
|
+
};
|
|
688
|
+
declare function useShareableViewerState({ cameraState, setCameraState, objectBindings, onObjectBindingsChange, selectedObjectBinding, setSelectedObjectBinding, paramKey, debounceMs, autoSync, }: UseShareableViewerStateOptions): ShareableViewerStateProps;
|
|
689
|
+
|
|
690
|
+
export { type AnimationClipInfo, type AnimationControls, type AnimationLoopMode, type AnimationPlaybackState, BindingBuilder, type CaptureImageOptions, type CustomObjectBindingDataPanelProps, type CustomSceneObjectsPanelProps, type HiddenObjectsState, type HomeCameraState, MATERIAL_BLENDING_MODES, MATERIAL_SIDES, ModelViewer, type ModelViewerProps, type ObjectActionEvent, type ObjectBinding, type ObjectBindingAction, type ObjectBindingMaterial, type ObjectBindingMaterialBlendingMode, type ObjectBindingMaterialSide, type ObjectBindingTextureSlot, type ObjectVisibilityProps, type SceneAnimationClipConfig, type SceneAnimationConfig, type SceneConfig, SimpleModelViewer, type SimpleModelViewerProps, type ViewerActionsOptions, type ViewerActionsProps, type ViewerAnimationsProps, type ViewerCameraProps, type ViewerCameraState, type ViewerModelBounds, type ViewerModelProps, type ViewerReadyState, type ViewerSelectionProps, useObjectBinding, useObjectBindingIds, useObjectVisibility, useShareableViewerState, useViewerActions, useViewerAnimations, useViewerCamera, useViewerHover, useViewerModel, useViewerSelection };
|
package/dist/index.js
CHANGED
|
@@ -38,6 +38,7 @@ __export(index_exports, {
|
|
|
38
38
|
useObjectBinding: () => useObjectBinding,
|
|
39
39
|
useObjectBindingIds: () => useObjectBindingIds,
|
|
40
40
|
useObjectVisibility: () => useObjectVisibility,
|
|
41
|
+
useShareableViewerState: () => useShareableViewerState,
|
|
41
42
|
useViewerActions: () => useViewerActions,
|
|
42
43
|
useViewerAnimations: () => useViewerAnimations,
|
|
43
44
|
useViewerCamera: () => useViewerCamera,
|
|
@@ -5025,10 +5026,12 @@ function ModelViewer({
|
|
|
5025
5026
|
const sceneRef = (0, import_react21.useRef)(null);
|
|
5026
5027
|
const fitAttemptedRef = (0, import_react21.useRef)(false);
|
|
5027
5028
|
const fitInFlightRef = (0, import_react21.useRef)(false);
|
|
5029
|
+
const cameraProvidedRef = (0, import_react21.useRef)(Boolean(camera));
|
|
5030
|
+
cameraProvidedRef.current = Boolean(camera);
|
|
5028
5031
|
const initialCameraStateRef = (0, import_react21.useRef)(null);
|
|
5029
5032
|
const [isAutoFraming, setIsAutoFraming] = (0, import_react21.useState)(Boolean(modelUrl));
|
|
5030
5033
|
const [cameraPositioned, setCameraPositioned] = (0, import_react21.useState)(
|
|
5031
|
-
!modelUrl || Boolean(onAutoFit)
|
|
5034
|
+
!modelUrl || Boolean(onAutoFit) || Boolean(camera)
|
|
5032
5035
|
);
|
|
5033
5036
|
const [downloadMenuOpen, setDownloadMenuOpen] = (0, import_react21.useState)(false);
|
|
5034
5037
|
const [modelLoaded, setModelLoaded] = (0, import_react21.useState)(false);
|
|
@@ -5846,7 +5849,7 @@ function ModelViewer({
|
|
|
5846
5849
|
return;
|
|
5847
5850
|
}
|
|
5848
5851
|
setIsAutoFraming(true);
|
|
5849
|
-
setCameraPositioned(Boolean(onAutoFit));
|
|
5852
|
+
setCameraPositioned(Boolean(onAutoFit) || cameraProvidedRef.current);
|
|
5850
5853
|
let cancelled = false;
|
|
5851
5854
|
let timer = null;
|
|
5852
5855
|
const scheduleCheck = (delay) => {
|
|
@@ -5870,7 +5873,7 @@ function ModelViewer({
|
|
|
5870
5873
|
return;
|
|
5871
5874
|
}
|
|
5872
5875
|
fitInFlightRef.current = true;
|
|
5873
|
-
const fitPromise = onAutoFit ? onAutoFit() : fitScene(true);
|
|
5876
|
+
const fitPromise = onAutoFit ? onAutoFit() : cameraProvidedRef.current ? Promise.resolve(true) : fitScene(true);
|
|
5874
5877
|
fitPromise.then((didFit) => {
|
|
5875
5878
|
fitAttemptedRef.current = didFit !== false;
|
|
5876
5879
|
if (fitAttemptedRef.current) {
|
|
@@ -15893,6 +15896,150 @@ function useViewerActions(objectBindings, options = {}) {
|
|
|
15893
15896
|
runAction
|
|
15894
15897
|
};
|
|
15895
15898
|
}
|
|
15899
|
+
|
|
15900
|
+
// src/hooks/useShareableViewerState.ts
|
|
15901
|
+
var import_react46 = require("react");
|
|
15902
|
+
function serializeState(cameraState, objectBindings, selectedObjectBinding) {
|
|
15903
|
+
const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
|
|
15904
|
+
return {
|
|
15905
|
+
camera: cameraState ?? void 0,
|
|
15906
|
+
hidden,
|
|
15907
|
+
selected: selectedObjectBinding?.id ?? null
|
|
15908
|
+
};
|
|
15909
|
+
}
|
|
15910
|
+
function useShareableViewerState({
|
|
15911
|
+
cameraState,
|
|
15912
|
+
setCameraState,
|
|
15913
|
+
objectBindings,
|
|
15914
|
+
onObjectBindingsChange,
|
|
15915
|
+
selectedObjectBinding,
|
|
15916
|
+
setSelectedObjectBinding,
|
|
15917
|
+
paramKey = "view",
|
|
15918
|
+
debounceMs = 400,
|
|
15919
|
+
autoSync = true
|
|
15920
|
+
}) {
|
|
15921
|
+
const lastWrittenRawRef = (0, import_react46.useRef)(null);
|
|
15922
|
+
const buildHash = (0, import_react46.useCallback)(() => {
|
|
15923
|
+
const state = serializeState(
|
|
15924
|
+
cameraState,
|
|
15925
|
+
objectBindings,
|
|
15926
|
+
selectedObjectBinding
|
|
15927
|
+
);
|
|
15928
|
+
return `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
|
|
15929
|
+
}, [cameraState, objectBindings, selectedObjectBinding, paramKey]);
|
|
15930
|
+
const getShareUrl = (0, import_react46.useCallback)(() => {
|
|
15931
|
+
const url = new URL(window.location.href);
|
|
15932
|
+
url.hash = buildHash();
|
|
15933
|
+
return url.toString();
|
|
15934
|
+
}, [buildHash]);
|
|
15935
|
+
const syncUrl = (0, import_react46.useCallback)(() => {
|
|
15936
|
+
const url = new URL(window.location.href);
|
|
15937
|
+
url.hash = buildHash();
|
|
15938
|
+
window.history.replaceState(null, "", url.toString());
|
|
15939
|
+
lastWrittenRawRef.current = new URLSearchParams(url.hash.slice(1)).get(
|
|
15940
|
+
paramKey
|
|
15941
|
+
);
|
|
15942
|
+
}, [buildHash, paramKey]);
|
|
15943
|
+
const latestStateRef = (0, import_react46.useRef)({
|
|
15944
|
+
cameraState,
|
|
15945
|
+
objectBindings,
|
|
15946
|
+
selectedObjectBinding
|
|
15947
|
+
});
|
|
15948
|
+
latestStateRef.current = {
|
|
15949
|
+
cameraState,
|
|
15950
|
+
objectBindings,
|
|
15951
|
+
selectedObjectBinding
|
|
15952
|
+
};
|
|
15953
|
+
const [restoreAttempted, setRestoreAttempted] = (0, import_react46.useState)(false);
|
|
15954
|
+
const lastSyncedHashRef = (0, import_react46.useRef)(null);
|
|
15955
|
+
(0, import_react46.useEffect)(() => {
|
|
15956
|
+
if (!autoSync || !restoreAttempted) return;
|
|
15957
|
+
const tick = () => {
|
|
15958
|
+
const current = latestStateRef.current;
|
|
15959
|
+
const state = serializeState(
|
|
15960
|
+
current.cameraState,
|
|
15961
|
+
current.objectBindings,
|
|
15962
|
+
current.selectedObjectBinding
|
|
15963
|
+
);
|
|
15964
|
+
const hash = `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
|
|
15965
|
+
if (hash === lastSyncedHashRef.current) return;
|
|
15966
|
+
lastSyncedHashRef.current = hash;
|
|
15967
|
+
const url = new URL(window.location.href);
|
|
15968
|
+
url.hash = hash;
|
|
15969
|
+
window.history.replaceState(null, "", url.toString());
|
|
15970
|
+
lastWrittenRawRef.current = new URLSearchParams(url.hash.slice(1)).get(
|
|
15971
|
+
paramKey
|
|
15972
|
+
);
|
|
15973
|
+
};
|
|
15974
|
+
const id = window.setInterval(tick, debounceMs);
|
|
15975
|
+
return () => window.clearInterval(id);
|
|
15976
|
+
}, [autoSync, restoreAttempted, debounceMs, paramKey]);
|
|
15977
|
+
const lastRestoredRawRef = (0, import_react46.useRef)(null);
|
|
15978
|
+
const restoringRawRef = (0, import_react46.useRef)(null);
|
|
15979
|
+
const restoreFromUrl = (0, import_react46.useCallback)(async () => {
|
|
15980
|
+
setRestoreAttempted(true);
|
|
15981
|
+
const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
|
|
15982
|
+
const raw = new URLSearchParams(rawHash).get(paramKey);
|
|
15983
|
+
if (!raw) {
|
|
15984
|
+
lastRestoredRawRef.current = null;
|
|
15985
|
+
return false;
|
|
15986
|
+
}
|
|
15987
|
+
if (raw === lastWrittenRawRef.current || raw === lastRestoredRawRef.current || raw === restoringRawRef.current) {
|
|
15988
|
+
return false;
|
|
15989
|
+
}
|
|
15990
|
+
let state;
|
|
15991
|
+
try {
|
|
15992
|
+
state = JSON.parse(raw);
|
|
15993
|
+
} catch {
|
|
15994
|
+
return false;
|
|
15995
|
+
}
|
|
15996
|
+
restoringRawRef.current = raw;
|
|
15997
|
+
try {
|
|
15998
|
+
if (state.camera) {
|
|
15999
|
+
await setCameraState(state.camera, false);
|
|
16000
|
+
}
|
|
16001
|
+
if (state.hidden) {
|
|
16002
|
+
const hiddenIds = new Set(state.hidden);
|
|
16003
|
+
onObjectBindingsChange(
|
|
16004
|
+
Object.fromEntries(
|
|
16005
|
+
Object.entries(objectBindings).map(([bindingKey, binding]) => [
|
|
16006
|
+
bindingKey,
|
|
16007
|
+
{ ...binding, visible: !hiddenIds.has(binding.id) }
|
|
16008
|
+
])
|
|
16009
|
+
)
|
|
16010
|
+
);
|
|
16011
|
+
}
|
|
16012
|
+
if (state.selected !== void 0) {
|
|
16013
|
+
const bindingKey = state.selected ? resolveBindingKey(objectBindings, state.selected) : null;
|
|
16014
|
+
setSelectedObjectBinding(
|
|
16015
|
+
bindingKey ? objectBindings[bindingKey] : null
|
|
16016
|
+
);
|
|
16017
|
+
}
|
|
16018
|
+
lastRestoredRawRef.current = raw;
|
|
16019
|
+
return true;
|
|
16020
|
+
} finally {
|
|
16021
|
+
if (restoringRawRef.current === raw) {
|
|
16022
|
+
restoringRawRef.current = null;
|
|
16023
|
+
}
|
|
16024
|
+
}
|
|
16025
|
+
}, [
|
|
16026
|
+
objectBindings,
|
|
16027
|
+
onObjectBindingsChange,
|
|
16028
|
+
paramKey,
|
|
16029
|
+
setCameraState,
|
|
16030
|
+
setSelectedObjectBinding
|
|
16031
|
+
]);
|
|
16032
|
+
(0, import_react46.useEffect)(() => {
|
|
16033
|
+
const handleHashChange = () => {
|
|
16034
|
+
void restoreFromUrl();
|
|
16035
|
+
};
|
|
16036
|
+
window.addEventListener("hashchange", handleHashChange);
|
|
16037
|
+
return () => {
|
|
16038
|
+
window.removeEventListener("hashchange", handleHashChange);
|
|
16039
|
+
};
|
|
16040
|
+
}, [restoreFromUrl]);
|
|
16041
|
+
return { getShareUrl, syncUrl, restoreFromUrl };
|
|
16042
|
+
}
|
|
15896
16043
|
// Annotate the CommonJS export names for ESM import in node:
|
|
15897
16044
|
0 && (module.exports = {
|
|
15898
16045
|
BindingBuilder,
|
|
@@ -15903,6 +16050,7 @@ function useViewerActions(objectBindings, options = {}) {
|
|
|
15903
16050
|
useObjectBinding,
|
|
15904
16051
|
useObjectBindingIds,
|
|
15905
16052
|
useObjectVisibility,
|
|
16053
|
+
useShareableViewerState,
|
|
15906
16054
|
useViewerActions,
|
|
15907
16055
|
useViewerAnimations,
|
|
15908
16056
|
useViewerCamera,
|
package/dist/index.mjs
CHANGED
|
@@ -5083,10 +5083,12 @@ function ModelViewer({
|
|
|
5083
5083
|
const sceneRef = useRef9(null);
|
|
5084
5084
|
const fitAttemptedRef = useRef9(false);
|
|
5085
5085
|
const fitInFlightRef = useRef9(false);
|
|
5086
|
+
const cameraProvidedRef = useRef9(Boolean(camera));
|
|
5087
|
+
cameraProvidedRef.current = Boolean(camera);
|
|
5086
5088
|
const initialCameraStateRef = useRef9(null);
|
|
5087
5089
|
const [isAutoFraming, setIsAutoFraming] = useState10(Boolean(modelUrl));
|
|
5088
5090
|
const [cameraPositioned, setCameraPositioned] = useState10(
|
|
5089
|
-
!modelUrl || Boolean(onAutoFit)
|
|
5091
|
+
!modelUrl || Boolean(onAutoFit) || Boolean(camera)
|
|
5090
5092
|
);
|
|
5091
5093
|
const [downloadMenuOpen, setDownloadMenuOpen] = useState10(false);
|
|
5092
5094
|
const [modelLoaded, setModelLoaded] = useState10(false);
|
|
@@ -5904,7 +5906,7 @@ function ModelViewer({
|
|
|
5904
5906
|
return;
|
|
5905
5907
|
}
|
|
5906
5908
|
setIsAutoFraming(true);
|
|
5907
|
-
setCameraPositioned(Boolean(onAutoFit));
|
|
5909
|
+
setCameraPositioned(Boolean(onAutoFit) || cameraProvidedRef.current);
|
|
5908
5910
|
let cancelled = false;
|
|
5909
5911
|
let timer = null;
|
|
5910
5912
|
const scheduleCheck = (delay) => {
|
|
@@ -5928,7 +5930,7 @@ function ModelViewer({
|
|
|
5928
5930
|
return;
|
|
5929
5931
|
}
|
|
5930
5932
|
fitInFlightRef.current = true;
|
|
5931
|
-
const fitPromise = onAutoFit ? onAutoFit() : fitScene(true);
|
|
5933
|
+
const fitPromise = onAutoFit ? onAutoFit() : cameraProvidedRef.current ? Promise.resolve(true) : fitScene(true);
|
|
5932
5934
|
fitPromise.then((didFit) => {
|
|
5933
5935
|
fitAttemptedRef.current = didFit !== false;
|
|
5934
5936
|
if (fitAttemptedRef.current) {
|
|
@@ -15989,6 +15991,150 @@ function useViewerActions(objectBindings, options = {}) {
|
|
|
15989
15991
|
runAction
|
|
15990
15992
|
};
|
|
15991
15993
|
}
|
|
15994
|
+
|
|
15995
|
+
// src/hooks/useShareableViewerState.ts
|
|
15996
|
+
import { useCallback as useCallback15, useEffect as useEffect20, useRef as useRef18, useState as useState29 } from "react";
|
|
15997
|
+
function serializeState(cameraState, objectBindings, selectedObjectBinding) {
|
|
15998
|
+
const hidden = Object.values(objectBindings).filter((binding) => binding.visible === false).map((binding) => binding.id);
|
|
15999
|
+
return {
|
|
16000
|
+
camera: cameraState ?? void 0,
|
|
16001
|
+
hidden,
|
|
16002
|
+
selected: selectedObjectBinding?.id ?? null
|
|
16003
|
+
};
|
|
16004
|
+
}
|
|
16005
|
+
function useShareableViewerState({
|
|
16006
|
+
cameraState,
|
|
16007
|
+
setCameraState,
|
|
16008
|
+
objectBindings,
|
|
16009
|
+
onObjectBindingsChange,
|
|
16010
|
+
selectedObjectBinding,
|
|
16011
|
+
setSelectedObjectBinding,
|
|
16012
|
+
paramKey = "view",
|
|
16013
|
+
debounceMs = 400,
|
|
16014
|
+
autoSync = true
|
|
16015
|
+
}) {
|
|
16016
|
+
const lastWrittenRawRef = useRef18(null);
|
|
16017
|
+
const buildHash = useCallback15(() => {
|
|
16018
|
+
const state = serializeState(
|
|
16019
|
+
cameraState,
|
|
16020
|
+
objectBindings,
|
|
16021
|
+
selectedObjectBinding
|
|
16022
|
+
);
|
|
16023
|
+
return `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
|
|
16024
|
+
}, [cameraState, objectBindings, selectedObjectBinding, paramKey]);
|
|
16025
|
+
const getShareUrl = useCallback15(() => {
|
|
16026
|
+
const url = new URL(window.location.href);
|
|
16027
|
+
url.hash = buildHash();
|
|
16028
|
+
return url.toString();
|
|
16029
|
+
}, [buildHash]);
|
|
16030
|
+
const syncUrl = useCallback15(() => {
|
|
16031
|
+
const url = new URL(window.location.href);
|
|
16032
|
+
url.hash = buildHash();
|
|
16033
|
+
window.history.replaceState(null, "", url.toString());
|
|
16034
|
+
lastWrittenRawRef.current = new URLSearchParams(url.hash.slice(1)).get(
|
|
16035
|
+
paramKey
|
|
16036
|
+
);
|
|
16037
|
+
}, [buildHash, paramKey]);
|
|
16038
|
+
const latestStateRef = useRef18({
|
|
16039
|
+
cameraState,
|
|
16040
|
+
objectBindings,
|
|
16041
|
+
selectedObjectBinding
|
|
16042
|
+
});
|
|
16043
|
+
latestStateRef.current = {
|
|
16044
|
+
cameraState,
|
|
16045
|
+
objectBindings,
|
|
16046
|
+
selectedObjectBinding
|
|
16047
|
+
};
|
|
16048
|
+
const [restoreAttempted, setRestoreAttempted] = useState29(false);
|
|
16049
|
+
const lastSyncedHashRef = useRef18(null);
|
|
16050
|
+
useEffect20(() => {
|
|
16051
|
+
if (!autoSync || !restoreAttempted) return;
|
|
16052
|
+
const tick = () => {
|
|
16053
|
+
const current = latestStateRef.current;
|
|
16054
|
+
const state = serializeState(
|
|
16055
|
+
current.cameraState,
|
|
16056
|
+
current.objectBindings,
|
|
16057
|
+
current.selectedObjectBinding
|
|
16058
|
+
);
|
|
16059
|
+
const hash = `${paramKey}=${encodeURIComponent(JSON.stringify(state))}`;
|
|
16060
|
+
if (hash === lastSyncedHashRef.current) return;
|
|
16061
|
+
lastSyncedHashRef.current = hash;
|
|
16062
|
+
const url = new URL(window.location.href);
|
|
16063
|
+
url.hash = hash;
|
|
16064
|
+
window.history.replaceState(null, "", url.toString());
|
|
16065
|
+
lastWrittenRawRef.current = new URLSearchParams(url.hash.slice(1)).get(
|
|
16066
|
+
paramKey
|
|
16067
|
+
);
|
|
16068
|
+
};
|
|
16069
|
+
const id = window.setInterval(tick, debounceMs);
|
|
16070
|
+
return () => window.clearInterval(id);
|
|
16071
|
+
}, [autoSync, restoreAttempted, debounceMs, paramKey]);
|
|
16072
|
+
const lastRestoredRawRef = useRef18(null);
|
|
16073
|
+
const restoringRawRef = useRef18(null);
|
|
16074
|
+
const restoreFromUrl = useCallback15(async () => {
|
|
16075
|
+
setRestoreAttempted(true);
|
|
16076
|
+
const rawHash = window.location.hash.startsWith("#") ? window.location.hash.slice(1) : window.location.hash;
|
|
16077
|
+
const raw = new URLSearchParams(rawHash).get(paramKey);
|
|
16078
|
+
if (!raw) {
|
|
16079
|
+
lastRestoredRawRef.current = null;
|
|
16080
|
+
return false;
|
|
16081
|
+
}
|
|
16082
|
+
if (raw === lastWrittenRawRef.current || raw === lastRestoredRawRef.current || raw === restoringRawRef.current) {
|
|
16083
|
+
return false;
|
|
16084
|
+
}
|
|
16085
|
+
let state;
|
|
16086
|
+
try {
|
|
16087
|
+
state = JSON.parse(raw);
|
|
16088
|
+
} catch {
|
|
16089
|
+
return false;
|
|
16090
|
+
}
|
|
16091
|
+
restoringRawRef.current = raw;
|
|
16092
|
+
try {
|
|
16093
|
+
if (state.camera) {
|
|
16094
|
+
await setCameraState(state.camera, false);
|
|
16095
|
+
}
|
|
16096
|
+
if (state.hidden) {
|
|
16097
|
+
const hiddenIds = new Set(state.hidden);
|
|
16098
|
+
onObjectBindingsChange(
|
|
16099
|
+
Object.fromEntries(
|
|
16100
|
+
Object.entries(objectBindings).map(([bindingKey, binding]) => [
|
|
16101
|
+
bindingKey,
|
|
16102
|
+
{ ...binding, visible: !hiddenIds.has(binding.id) }
|
|
16103
|
+
])
|
|
16104
|
+
)
|
|
16105
|
+
);
|
|
16106
|
+
}
|
|
16107
|
+
if (state.selected !== void 0) {
|
|
16108
|
+
const bindingKey = state.selected ? resolveBindingKey(objectBindings, state.selected) : null;
|
|
16109
|
+
setSelectedObjectBinding(
|
|
16110
|
+
bindingKey ? objectBindings[bindingKey] : null
|
|
16111
|
+
);
|
|
16112
|
+
}
|
|
16113
|
+
lastRestoredRawRef.current = raw;
|
|
16114
|
+
return true;
|
|
16115
|
+
} finally {
|
|
16116
|
+
if (restoringRawRef.current === raw) {
|
|
16117
|
+
restoringRawRef.current = null;
|
|
16118
|
+
}
|
|
16119
|
+
}
|
|
16120
|
+
}, [
|
|
16121
|
+
objectBindings,
|
|
16122
|
+
onObjectBindingsChange,
|
|
16123
|
+
paramKey,
|
|
16124
|
+
setCameraState,
|
|
16125
|
+
setSelectedObjectBinding
|
|
16126
|
+
]);
|
|
16127
|
+
useEffect20(() => {
|
|
16128
|
+
const handleHashChange = () => {
|
|
16129
|
+
void restoreFromUrl();
|
|
16130
|
+
};
|
|
16131
|
+
window.addEventListener("hashchange", handleHashChange);
|
|
16132
|
+
return () => {
|
|
16133
|
+
window.removeEventListener("hashchange", handleHashChange);
|
|
16134
|
+
};
|
|
16135
|
+
}, [restoreFromUrl]);
|
|
16136
|
+
return { getShareUrl, syncUrl, restoreFromUrl };
|
|
16137
|
+
}
|
|
15992
16138
|
export {
|
|
15993
16139
|
BindingBuilder,
|
|
15994
16140
|
MATERIAL_BLENDING_MODES,
|
|
@@ -15998,6 +16144,7 @@ export {
|
|
|
15998
16144
|
useObjectBinding,
|
|
15999
16145
|
useObjectBindingIds,
|
|
16000
16146
|
useObjectVisibility,
|
|
16147
|
+
useShareableViewerState,
|
|
16001
16148
|
useViewerActions,
|
|
16002
16149
|
useViewerAnimations,
|
|
16003
16150
|
useViewerCamera,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@liveroom-tech/react-immersive",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.mjs",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"homepage": "https://react-immersive.liveroom.dev",
|
|
76
76
|
"repository": {
|
|
77
77
|
"type": "git",
|
|
78
|
-
"url": "https://
|
|
78
|
+
"url": "https://github.com/liveroom-technologies/react-immersive-docs.git"
|
|
79
79
|
},
|
|
80
80
|
"bugs": {
|
|
81
81
|
"url": "https://github.com/liveroom-technologies/react-immersive-docs/issues"
|