import { Logger } from '../common/logger'; export interface ChatWindowBounds { width: number; height: number; left: number; top: number; } // TODO: Can this class be genericized to match up with what Electron does? Make it a library common between the two apps? export class WebViewWindow { private w: chrome.app.window.AppWindow | null; private fullScreenTimer: any; private logger = Logger.getInstance(); private onResizeFunction: Function | undefined; constructor() { this.w = null; this.fullScreenTimer = null; } public setAppWindow(win: chrome.app.window.AppWindow) { this.w = win; this.w.onBoundsChanged.addListener(() => { this.onBoundsChanged(); }); this.w.contentWindow.document.addEventListener('visibilitychange', () => { this.visibilityChange(); }); } public isMinimized() { if (!this.w) { throw new Error('WebViewWindow.isMinimized(): No binding window.'); } return this.w.isMinimized(); } // Programmatic minimizing of the window public minimizeWindow(onMinimizedFunction?: Function) { if (!this.w) { throw new Error('WebViewWindow.minimizeWindow(): No binding window.'); } if (!this.w.isMinimized()) { if (onMinimizedFunction) { this.logger.logMessage('WebViewWindow.minimizeWindow(): Executing minimize.'); let that = this; this.w.onMinimized.addListener(function oneTimeOnly() { if (that.w) { that.w.onMinimized.removeListener(oneTimeOnly); that.logger.logMessage('WebViewWindow.minimizeWindow(): Executing minimize callback.'); onMinimizedFunction(); } }); } this.w.minimize(); } else { this.logger.logMessage('WebViewWindow.minimizeWindow(): Already minimized.'); if (onMinimizedFunction) { onMinimizedFunction(); } } } // Programmatic restoration of the window public restoreWindow(onRestoredFunction?: Function) { if (!this.w) { throw new Error('WebViewWindow.restoreWindow(): No binding window.'); } if (this.w.isMinimized() || this.w.isFullscreen() || this.w.isMaximized()) { if (onRestoredFunction) { this.logger.logMessage('WebViewWindow.restoreWindow(): Executing restore.'); let that = this; this.w.onRestored.addListener(function oneTimeOnly() { if (that.w) { that.logger.logMessage('WebViewWindow.restoreWindow(): Executing restore callback.'); that.w.onRestored.removeListener(oneTimeOnly); onRestoredFunction(); } }); } this.w.restore(); } else { this.logger.logMessage('WebViewWindow.restoreWindow(): Already restored.'); if (onRestoredFunction) { onRestoredFunction(); } } } public setSizeAndPosition(boundsObj: ChatWindowBounds) { if (!this.w) { throw new Error('WebViewWindow.setSizeAndPosition(): No binding window.'); } console.log('WebViewWindow.setSizeAndPosition(): Setting window to ' + JSON.stringify(boundsObj)); if (boundsObj.left < 0 || boundsObj.top < 0 || boundsObj.width < 0 || boundsObj.height < 0) { this.minimizeWindow(); } else { this.w.innerBounds.setPosition(boundsObj.left, boundsObj.top); this.w.innerBounds.setSize(boundsObj.width, boundsObj.height); } } public makeModal() { this.w?.show(); this.w?.focus(); this.w?.setAlwaysOnTop(true); } public getBounds() { if (!this.w) throw new Error('WebViewWindow.getBounds(): No binding window.'); return this.w.innerBounds; } public revertWindow() { this.restoreWindow(); this.w?.setAlwaysOnTop(false); this.clearFullScreenTimer(); } public setTopmostBriefly() { this.w?.setAlwaysOnTop(true); setTimeout(() => { this.w?.setAlwaysOnTop(false); }, 100); } public setAlwaysOnTopAndFullScreen(verifyCallback: Function) { if (!this.w) { throw new Error('WebViewWindow.setAlwaysOnTopAndFullScreen(): No binding window.'); } if (verifyCallback) { if (!verifyCallback()) { return; } } this.w.fullscreen(); this.w.show(); this.w.focus(); this.w.setAlwaysOnTop(true); if (!this.fullScreenTimer) { this.fullScreenTimer = setInterval(() => { this.setAlwaysOnTopAndFullScreen(verifyCallback); }, 500); } } public visibilityChange() { if (!this.fullScreenTimer) return; this.logger.logMessage( 'WebViewWindow.visibilityChange(): document.visibilitychange: ' + this.w?.contentWindow.document.visibilityState ); if (this.w?.contentWindow.document.visibilityState === 'hidden') { // If we are running full screen show teacher, losing visibility means the student // somehow minimized it *or* more likely moved to a different desktop. By causing the // window to restore, it does two things: // 1) Causes the handles in ConferenceWindowContrller to force the window back to fullscreen. // 2) Snaps the window into focus on whatever desktop is being viewed. this.restoreWindow(); } } public resetWindow() { this.w = null; this.clearFullScreenTimer(); } public hasBoundWindow() { return this.w !== null; } public onDOMContentLoaded(callback: Function) { if (!this.w) { throw new Error('WebViewWindow.onDOMContentLoaded(): No binding window.'); } this.w.contentWindow.addEventListener('DOMContentLoaded', () => { this.logger.logMessage('WebViewWindow.onDOMContentLoaded(): Running callback...'); if (callback) callback(); }); } public onWindowLoad(callback: Function) { if (!this.w) { throw new Error('WebViewWindow.onWindowLoad(): No binding window.'); } this.w.contentWindow.addEventListener('load', () => { this.logger.logMessage('WebViewWindow.onWindowLoad(): Running callback...'); if (callback) callback(); }); } public onClosed(callback: Function) { if (!this.w) { throw new Error('WebViewWindow.onClosed(): No binding window.'); } this.w.onClosed.addListener(() => { this.logger.logMessage('WebViewWindow.onClosed(): Running callback...'); callback(); }); } public onWindowMinimized(onMinimizeFunction: Function) { if (!this.w) { throw new Error('WebViewWindow.onClosed(): No binding window.'); } if (onMinimizeFunction) { this.w.onMinimized.addListener(() => { this.logger.logMessage('WebViewWindow.onWindowMinimize(+)'); onMinimizeFunction(); }); } } public onWindowRestored(onRestoredFunction: Function) { if (!this.w) { throw new Error('WebViewWindow.onClosed(): No binding window.'); } if (onRestoredFunction) { this.w.onRestored.addListener(() => { this.logger.logMessage('WebViewWindow.onWindowRestored(+)'); onRestoredFunction(); }); } } public onWindowResized(onResizeFunction: Function) { this.onResizeFunction = onResizeFunction; } public onBoundsChanged() { if (!this.w) { throw new Error('WebViewWindow.onBoundsChanged(): No binding window.'); } if (this.onResizeFunction) { this.onResizeFunction(); } } public getInnerBounds() { if (!this.w) { throw new Error('WebViewWindow.getInnerBounds(): No binding window.'); } return this.w.innerBounds; } public clearFullScreenTimer() { if (this.fullScreenTimer) { clearInterval(this.fullScreenTimer); this.fullScreenTimer = null; } } }