1+ // app/assets/javascript/mammogram-viewer.js
2+
3+ // Single global variable to store the window reference
4+ let mammogramWindow = null ;
5+
6+ // Track what was the last event ID that had a viewer open
7+ let currentEventId = '' ;
8+
9+ // Global flag to indicate if we're in a reading context
10+ window . inReadingContext = false ;
11+
12+ // Storage key for viewer state
13+ const VIEWER_STORAGE_KEY = 'mammogramViewerOpen' ;
14+
15+ // Flag to track if we're in the process of a page navigation/refresh
16+ let isNavigating = false ;
17+
18+ // Check for viewer flag on page load
19+ document . addEventListener ( 'DOMContentLoaded' , function ( ) {
20+ // Reset navigation flag
21+ isNavigating = false ;
22+
23+ // Check if this page should show the viewer
24+ const viewerMeta = document . querySelector ( 'meta[name="mammogram-viewer"]' ) ;
25+ const shouldShowViewer = viewerMeta ?. getAttribute ( 'content' ) === 'show' ;
26+ const participantName = document . querySelector ( 'meta[name="participant-name"]' ) ?. getAttribute ( 'content' ) ;
27+ const eventId = document . querySelector ( 'meta[name="event-id"]' ) ?. getAttribute ( 'content' ) ;
28+
29+ console . log ( "Mammogram viewer check:" , shouldShowViewer ? "SHOW" : "HIDE" , "Event ID:" , eventId ) ;
30+
31+ // Update our reading context flag
32+ window . inReadingContext = shouldShowViewer ;
33+
34+ // Check for stored state (helps with page refreshes)
35+ try {
36+ const storedViewerState = localStorage . getItem ( VIEWER_STORAGE_KEY ) ;
37+ if ( storedViewerState && ! mammogramWindow ) {
38+ // We have stored state but no window - this is likely a page refresh
39+ console . log ( "Found stored viewer state during page load" ) ;
40+ }
41+ } catch ( e ) {
42+ console . error ( "Error checking stored viewer state:" , e ) ;
43+ }
44+
45+ // If we're in a reading context with metadata present
46+ if ( shouldShowViewer && participantName ) {
47+ // Check if we need to update the viewer
48+ const isSameEvent = eventId && eventId === currentEventId ;
49+
50+ if ( ! isSameEvent || ! MammogramViewer . isOpen ( ) ) {
51+ // Update tracking variables
52+ currentEventId = eventId || '' ;
53+
54+ // Open or update viewer
55+ MammogramViewer . open ( participantName ) ;
56+ } else {
57+ console . log ( "Same event, not updating viewer" ) ;
58+ }
59+ } else if ( ! shouldShowViewer ) {
60+ // We're on a page that should NOT show the viewer
61+ console . log ( "Page lacks viewer meta tag or has it set to hide - closing viewer" ) ;
62+ MammogramViewer . close ( ) ;
63+ currentEventId = '' ;
64+ }
65+ } ) ;
66+
67+ // Message handler to track viewer status
68+ window . addEventListener ( 'message' , function ( event ) {
69+ if ( event . data === 'viewer-alive' ) {
70+ console . log ( "Received alive signal from mammogram viewer" ) ;
71+ }
72+ } ) ;
73+
74+ // Simple viewer manager object
75+ const MammogramViewer = {
76+ // Open or update the mammogram viewer window
77+ open : function ( participantName ) {
78+ // Check if we already have a window reference that's valid
79+ try {
80+ if ( mammogramWindow && mammogramWindow . closed ) {
81+ mammogramWindow = null ;
82+ localStorage . removeItem ( VIEWER_STORAGE_KEY ) ;
83+ }
84+ } catch ( e ) {
85+ mammogramWindow = null ;
86+ localStorage . removeItem ( VIEWER_STORAGE_KEY ) ;
87+ }
88+
89+ // Create window if it doesn't exist
90+ if ( ! mammogramWindow ) {
91+ this . openNew ( participantName ) ;
92+ } else {
93+ // Update existing window without reopening it
94+ this . updateExisting ( participantName ) ;
95+ }
96+
97+ // Force focus back to parent window to prevent focus stealing
98+ setTimeout ( function ( ) {
99+ window . focus ( ) ;
100+ } , 100 ) ;
101+
102+ return mammogramWindow ;
103+ } ,
104+
105+ // Open a new viewer window
106+ openNew : function ( participantName ) {
107+ const url = `/reading/mammogram-viewer?name=${ encodeURIComponent ( participantName ) } &ts=${ Date . now ( ) } ` ;
108+ console . log ( "Opening new mammogram viewer" ) ;
109+
110+ // Force external window behavior with specific window features
111+ // Note: Using 'alwaysRaised=yes' and 'popup=yes' helps ensure it opens as a window
112+ mammogramWindow = window . open (
113+ url ,
114+ 'mammogramViewer' ,
115+ 'width=700,height=900,resizable=yes,scrollbars=no,status=no,location=no,toolbar=no,menubar=no,alwaysRaised=yes,popup=yes'
116+ ) ;
117+
118+ // Store window information
119+ if ( mammogramWindow ) {
120+ try {
121+ localStorage . setItem ( VIEWER_STORAGE_KEY , 'true' ) ;
122+ console . log ( "Mammogram viewer opened" ) ;
123+
124+ // Ensure parent window keeps focus
125+ setTimeout ( function ( ) {
126+ window . focus ( ) ;
127+ } , 50 ) ;
128+ } catch ( e ) {
129+ console . error ( 'Error marking viewer as open:' , e ) ;
130+ }
131+ }
132+ } ,
133+
134+ // Update existing window without reopening it
135+ updateExisting : function ( participantName ) {
136+ let updatedSuccessfully = false ;
137+
138+ try {
139+ // First try postMessage approach as it doesn't steal focus
140+ try {
141+ mammogramWindow . postMessage ( {
142+ type : 'update-participant' ,
143+ name : participantName
144+ } , '*' ) ;
145+ console . log ( "Sent postMessage update to viewer" ) ;
146+ updatedSuccessfully = true ;
147+ } catch ( postErr ) {
148+ console . log ( "PostMessage update failed:" , postErr ) ;
149+ }
150+
151+ // Fall back to direct DOM manipulation only if postMessage failed
152+ if ( ! updatedSuccessfully ) {
153+ console . log ( "Falling back to DOM update" ) ;
154+ // Remember current focus state - document.activeElement
155+ const previousFocus = document . activeElement ;
156+
157+ const nameElement = mammogramWindow . document . getElementById ( 'participant-name' ) ;
158+ if ( nameElement ) {
159+ nameElement . textContent = participantName ;
160+ mammogramWindow . document . title = 'Mammogram: ' + participantName ;
161+ console . log ( "Mammogram viewer updated for:" , participantName ) ;
162+
163+ // Re-randomize the images when switching participants
164+ if ( typeof mammogramWindow . randomizeAllImages === 'function' ) {
165+ // Set a new random seed based on participant name
166+ const nameSeed = participantName . split ( '' ) . reduce ( ( sum , char , i ) => sum + char . charCodeAt ( 0 ) * ( i + 1 ) , 0 ) ;
167+ mammogramWindow . Math . random = mammogramWindow . Math . seedrandom ( nameSeed ) ;
168+
169+ // Apply randomization
170+ mammogramWindow . randomizeAllImages ( ) ;
171+ }
172+
173+ // Restore focus to the previous element
174+ if ( previousFocus ) {
175+ previousFocus . focus ( ) ;
176+ } else {
177+ window . focus ( ) ;
178+ }
179+
180+ updatedSuccessfully = true ;
181+ }
182+ }
183+ } catch ( e ) {
184+ console . error ( 'Could not update viewer window, opening new one' , e ) ;
185+ updatedSuccessfully = false ;
186+ }
187+
188+ // If we couldn't update, create a new window
189+ if ( ! updatedSuccessfully ) {
190+ try {
191+ if ( mammogramWindow ) {
192+ mammogramWindow . close ( ) ;
193+ }
194+ } catch ( err ) { }
195+
196+ localStorage . removeItem ( VIEWER_STORAGE_KEY ) ;
197+ this . openNew ( participantName ) ;
198+ }
199+
200+ // Ensure parent window keeps focus
201+ window . focus ( ) ;
202+
203+ return updatedSuccessfully ;
204+ } ,
205+
206+ // Close the viewer window - but only if it actually exists
207+ close : function ( ) {
208+ console . log ( "Attempting to close mammogram viewer" ) ;
209+
210+ // Only close if we're not in the middle of navigation
211+ if ( isNavigating ) {
212+ console . log ( "Navigation in progress, not closing viewer" ) ;
213+ return ;
214+ }
215+
216+ // Only close from non-reading pages
217+ if ( window . inReadingContext ) {
218+ console . log ( "Still in reading context, not closing viewer" ) ;
219+ return ;
220+ }
221+
222+ // Only try to close if we have a valid reference - don't search for windows
223+ if ( mammogramWindow && ! mammogramWindow . closed ) {
224+ console . log ( "Closing viewer window" ) ;
225+
226+ // First try to signal a graceful close via message
227+ try {
228+ mammogramWindow . postMessage ( 'please-close' , '*' ) ;
229+ } catch ( e ) {
230+ console . error ( "Error sending close message:" , e ) ;
231+ }
232+
233+ // As a fallback, force close after a short delay
234+ setTimeout ( function ( ) {
235+ try {
236+ if ( mammogramWindow && ! mammogramWindow . closed ) {
237+ mammogramWindow . close ( ) ;
238+ }
239+ } catch ( e ) {
240+ console . error ( "Error force closing viewer:" , e ) ;
241+ }
242+ mammogramWindow = null ;
243+ } , 300 ) ;
244+ }
245+
246+ // Clean up local storage
247+ try {
248+ localStorage . removeItem ( VIEWER_STORAGE_KEY ) ;
249+ } catch ( e ) {
250+ console . error ( "Error removing stored state:" , e ) ;
251+ }
252+ } ,
253+
254+ // Check if viewer is open
255+ isOpen : function ( ) {
256+ // Only check our direct reference, don't try to find windows
257+ try {
258+ return mammogramWindow && ! mammogramWindow . closed ;
259+ } catch ( e ) {
260+ // If there's an error accessing the window property, it's likely closed
261+ return false ;
262+ }
263+ }
264+ } ;
265+
266+ // Make it globally available
267+ window . MammogramViewer = MammogramViewer ;
268+
269+ // Set the navigating flag on popstate events
270+ window . addEventListener ( 'popstate' , function ( ) {
271+ isNavigating = true ;
272+ console . log ( "Navigation detected, setting isNavigating flag" ) ;
273+
274+ // If we're leaving reading context, close the viewer
275+ if ( window . inReadingContext && ! window . location . pathname . includes ( '/reading/batch/' ) ) {
276+ console . log ( "Navigation away from reading detected - flagging for closure" ) ;
277+ window . inReadingContext = false ;
278+ }
279+ } ) ;
280+
281+ // Before unload handler - set navigating flag
282+ window . addEventListener ( 'beforeunload' , function ( ) {
283+ isNavigating = true ;
284+ console . log ( "Page unloading, setting isNavigating flag" ) ;
285+
286+ // Store the current reading context state for potential page refresh
287+ if ( window . inReadingContext && mammogramWindow && ! mammogramWindow . closed ) {
288+ try {
289+ localStorage . setItem ( 'wasInReadingContext' , 'true' ) ;
290+ } catch ( e ) { }
291+ }
292+ } ) ;
0 commit comments