@@ -7,6 +7,7 @@ import Vue, {
77 ref , shallowRef , reactive , provide , toRef , Ref , UnwrapRef , computed , watch ,
88} from 'vue' ;
99import { map , over } from 'lodash' ;
10+ import createViewLink from './viewLink' ;
1011
1112import { use } from '../../provides' ;
1213import type {
@@ -113,10 +114,27 @@ export function useMediaController() {
113114 let state : Record < string , UnwrapRef < MediaControllerReactiveData > > = { } ;
114115 let cameraControllerSymbols : Record < string , symbol > = { } ;
115116 const synchronizeCameras : Ref < boolean > = ref ( false ) ;
117+
118+ // Installed by the viewer while stereo auto-compute is on; otherwise
119+ // synchronised panes only copy each other's screen motion.
120+ const viewLink = createViewLink ( {
121+ center : ( key ) => geoViewers [ key ] ?. value ?. center ( ) ,
122+ cameraName : ( key ) => state [ key ] ?. cameraName ,
123+ synced : ( ) => synchronizeCameras . value ,
124+ recenter : ( sourceKey , point ) => {
125+ allowCameraTrigger = false ;
126+ Object . entries ( geoViewers ) . forEach ( ( [ camera , geoViewer ] ) => {
127+ if ( geoViewer . value && camera !== sourceKey ) {
128+ geoViewer . value . center ( { x : point [ 0 ] , y : point [ 1 ] } ) ;
129+ }
130+ } ) ;
131+ allowCameraTrigger = true ;
132+ } ,
133+ } ) ;
116134 const resizeTrigger : Ref < number > = ref ( 0 ) ;
117135 // Raised only while onResize applies its programmatic resetZoom, so the
118- // linked-viewer navigation ignores the resulting pan/zoom events (see
119- // AggregateMediaController.resizing).
136+ // linked-viewer navigation and stereo view-link ignore the resulting
137+ // pan/zoom events (see AggregateMediaController.resizing).
120138 const resizing : Ref < boolean > = ref ( false ) ;
121139 // shallowRef: an AlignedFrameResolver carries nested Refs (slotCount, frameRate)
122140 // that must NOT be deep-reactive-converted/auto-unwrapped by a plain ref().
@@ -150,6 +168,7 @@ export function useMediaController() {
150168 currentTime : emptyControllerCurrentTime ,
151169 getController,
152170 toggleSynchronizeCameras,
171+ setViewLinkResolver,
153172 cameraSync : synchronizeCameras ,
154173 resizeTrigger,
155174 resizing,
@@ -287,6 +306,12 @@ export function useMediaController() {
287306 synchronizeCameras . value = val ;
288307 }
289308
309+ function setViewLinkResolver (
310+ resolver : Parameters < typeof viewLink . setResolver > [ 0 ] ,
311+ ) {
312+ viewLink . setResolver ( resolver ) ;
313+ }
314+
290315 /**
291316 * Optional replacement for the aggregate "reset pan and zoom" behavior,
292317 * installed by the aligned-view navigation link (useAlignedNavigation).
@@ -320,6 +345,12 @@ export function useMediaController() {
320345 }
321346 } ) ;
322347 allowCameraTrigger = true ;
348+ // onResize's resetZoom emits pan/zoom in native space; skip the stereo
349+ // lookup so a pane's native center isn't warped onto the other camera
350+ // after the resize settles (same guard as aligned/registration nav).
351+ if ( ! resizing . value ) {
352+ viewLink . schedule ( camEvent . camera ) ;
353+ }
323354 }
324355 } ) ;
325356
@@ -333,6 +364,9 @@ export function useMediaController() {
333364 }
334365 } ) ;
335366 allowCameraTrigger = true ;
367+ if ( ! resizing . value ) {
368+ viewLink . schedule ( camEvent . camera ) ;
369+ }
336370 }
337371 } ) ;
338372 /**
@@ -654,6 +688,7 @@ export function useMediaController() {
654688 getController,
655689 resetMapDimensions,
656690 toggleSynchronizeCameras,
691+ setViewLinkResolver,
657692 cameraSync : synchronizeCameras ,
658693 resizeTrigger,
659694 resizing,
@@ -820,6 +855,7 @@ export function useMediaController() {
820855 currentTime : defaultController . currentTime ,
821856 getController,
822857 toggleSynchronizeCameras,
858+ setViewLinkResolver,
823859 cameraSync : synchronizeCameras ,
824860 resizeTrigger,
825861 resizing,
0 commit comments