feat: improve collaboration feedback and previews

This commit is contained in:
Aymeric Rabot
2026-07-20 20:25:30 +02:00
parent 10c9c6ad27
commit e1a4dba740
27 changed files with 535 additions and 39 deletions
@@ -449,14 +449,17 @@ const PointerMissedHandler = ({
const OutlinerSync = () => {
const selection = useViewer((s) => s.selection)
const externalSelectedIds = useViewer((s) => s.externalSelectedIds)
const hoveredId = useViewer((s) => s.hoveredId)
const outliner = useViewer((s) => s.outliner)
const geometryRevision = useViewer((s) => s.geometryRevision)
const nodes = useScene((s) => s.nodes)
useEffect(() => {
void geometryRevision
// Sync selected objects
outliner.selectedObjects.length = 0
for (const id of selection.selectedIds) {
for (const id of new Set([...selection.selectedIds, ...externalSelectedIds])) {
const node = nodes[id as AnyNodeId]
if (node?.type === 'slab') continue
const obj = sceneRegistry.nodes.get(id)
@@ -471,7 +474,7 @@ const OutlinerSync = () => {
const obj = sceneRegistry.nodes.get(hoveredId)
if (obj) outliner.hoveredObjects.push(obj)
}
}, [selection, hoveredId, outliner, nodes])
}, [selection, externalSelectedIds, hoveredId, outliner, nodes, geometryRevision])
return null
}
+2
View File
@@ -15,6 +15,8 @@ type ViewerState = {
selection: SelectionPath
previewSelectedIds: BaseNode['id'][]
setPreviewSelectedIds: (ids: BaseNode['id'][]) => void
externalSelectedIds: BaseNode['id'][]
setExternalSelectedIds: (ids: BaseNode['id'][]) => void
hoverHighlightMode: string
setHoverHighlightMode: (mode: string) => void
hoveredId: AnyNode['id'] | ZoneNode['id'] | null
@@ -5,6 +5,7 @@ import useViewer from './use-viewer'
const resetMeasurementPreferences = () => {
useViewer.setState({
externalSelectedIds: [],
projectId: null,
projectPreferences: {},
showMeasurements: true,
@@ -49,3 +50,14 @@ describe('measurement display preferences', () => {
expect(useViewer.getState().showMeasurements).toBe(false)
})
})
describe('external selection highlights', () => {
test('tracks host-owned highlights without changing the local selection', () => {
const localSelection = useViewer.getState().selection
useViewer.getState().setExternalSelectedIds(['wall_remote'])
expect(useViewer.getState().externalSelectedIds).toEqual(['wall_remote'])
expect(useViewer.getState().selection).toBe(localSelection)
})
})
+15
View File
@@ -27,6 +27,10 @@ type ViewerState = {
selection: SelectionPath
previewSelectedIds: BaseNode['id'][]
setPreviewSelectedIds: (ids: BaseNode['id'][]) => void
/** Host-owned selection highlights rendered through the viewer's native
* selection paths without changing the local user's editable selection. */
externalSelectedIds: BaseNode['id'][]
setExternalSelectedIds: (ids: BaseNode['id'][]) => void
hoverHighlightMode: string
setHoverHighlightMode: (mode: string) => void
hoveredId: AnyNode['id'] | ZoneNode['id'] | null
@@ -314,6 +318,17 @@ const useViewer = create<ViewerState>()(
selection: { buildingId: null, levelId: null, zoneId: null, selectedIds: [] },
previewSelectedIds: [],
setPreviewSelectedIds: (ids) => set({ previewSelectedIds: ids }),
externalSelectedIds: [],
setExternalSelectedIds: (ids) =>
set((state) => {
if (
state.externalSelectedIds.length === ids.length &&
state.externalSelectedIds.every((id, index) => id === ids[index])
) {
return state
}
return { externalSelectedIds: ids }
}),
hoverHighlightMode: 'default',
setHoverHighlightMode: (mode) =>
set((state) => (state.hoverHighlightMode === mode ? state : { hoverHighlightMode: mode })),