import { LSAClient } from '@lenovo-software/lsa-clients-common'; import { WebViewWindow, ChatWindowBounds } from './webViewWindow'; import { ConferenceState } from './conferenceState'; import { Logger } from '../common/logger'; import PubSub from 'pubsub-js'; import { Executor } from './executor'; interface ChatWindowState { waitingForFullScreenSelect: boolean; isInChatMode: boolean; } const chatWindowBounds: ChatWindowBounds = { width: 500, height: 460, left: Math.round(screen.width / 2 - 500 / 2), top: Math.round(screen.height / 2 - 460 / 2) }; // TODO: Can this class be genericized to match up with what Electron does? Make it a library common between the two apps? export class WebViewWindowController { private chatWindow: WebViewWindow; private conferenceState: ConferenceState; private chatWindowState: ChatWindowState; private lastChatWindowBounds: ChatWindowBounds; private ignoreRestoreBecauseWeAreMinimalAudio: boolean; private logger = Logger.getInstance(); private isInAudioConference = false; private isInVideoConference = false; constructor() { this.chatWindow = new WebViewWindow(); this.conferenceState = new ConferenceState(); this.chatWindowState = { waitingForFullScreenSelect: false, isInChatMode: false }; this.lastChatWindowBounds = chatWindowBounds; this.ignoreRestoreBecauseWeAreMinimalAudio = false; chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => { switch (request.message) { case 'ChatWebViewState': { this.chatWindowState = request.data.state; this.logger.logMessage('Got ChatWebViewState: ' + JSON.stringify(this.chatWindowState)); PubSub.publish('onChatWebViewState', this.chatWindowState); break; } case 'DownloadLogs': { const logExporter = LSAClient.getInstance().logExporter; const obfuscatedLog = await logExporter.retrieveObfuscatedLogBuffer(); //download log prompt must show from a webview window, not the background page chrome.runtime.sendMessage({ message: 'DownloadLogsPrompt', data: obfuscatedLog }); break; } case 'LearnMoreAboutStatus': { if (request.launchURL && request.launchURL.length > 0) { new Executor().execute({ path: request.launchURL, teacherInfo: null }); } else { this.logger.logWarning('LearnMoreAboutStatus: No valid url found.'); } break; } } }); PubSub.subscribe('onChatWebViewReady', () => { this.setChatWindowBounds(chatWindowBounds); }); PubSub.subscribe('onChatWindowMinimized', (msg, data) => { this.processOnChatWindowMinimized(); }); PubSub.subscribe('onChatWindowRestored', (msg, data) => { if (this.ignoreRestoreBecauseWeAreMinimalAudio) return; if (this.isInAudioConference || this.isInVideoConference) { chrome.runtime.sendMessage({ message: 'ChatRoute' }); } chrome.runtime.sendMessage({ message: 'ToolbarButton', toolbarButtonMessageToBackground: '' }); }); PubSub.subscribe('onConferenceStateChange', (msg, data) => { this.processConferenceStateChange(data); }); } // TODO: any processConferenceStateChange(notification: any) { this.processVideoConferenceNotification(notification); this.processAudioConferenceNotification(notification); this.processFullScreenNotification(notification); } // TODO: any processVideoConferenceNotification(notification: any) { if (!notification.hasOwnProperty('isInVideoConference')) return; this.restoreChatWindow(); setTimeout(() => { if (notification.isInVideoConference) { if (this.conferenceState.isVideoConferenceFullScreen) { this.setShowTeacherWindowFullScreen(); } else { this.autoPositionChatWindow(Math.round(screen.width * 0.66), Math.round(screen.height * 0.66)); this.chatWindow.setTopmostBriefly(); } } else { this.lastChatWindowBounds = chatWindowBounds; this.setWindowBounds(this.lastChatWindowBounds); } }, 200); } // TODO: any processAudioConferenceNotification(notification: any) { if (!notification.hasOwnProperty('isInAudioConference')) return; if (notification.isInAudioConference) { if (this.isChatWindowMinimized()) { this.processOnChatWindowMinimized(); } } else { this.restoreChatWindow(); } } // TODO: any processFullScreenNotification(notification: any) { if (!notification.hasOwnProperty('isVideoConferenceFullScreen')) return; if (notification.isVideoConferenceFullScreen) { this.setShowTeacherWindowFullScreen(); } else { this.revertWindowFromFullScreen(); } } processOnChatWindowMinimized() { if (!this.conferenceState.isInAudioConference) return; this.ignoreRestoreBecauseWeAreMinimalAudio = true; chrome.runtime.sendMessage({ message: 'AudioRoute' }); chrome.runtime.sendMessage({ message: 'ToolbarButton', toolbarButtonMessageToBackground: 'FromWebview_ToolbarButtonPressed' }); let windowSizeX = 250; let windowSizeY = 62; let minimalWindowBounds = { left: screen.availWidth - windowSizeX, top: screen.availHeight - windowSizeY, width: windowSizeX, height: windowSizeY }; this.restoreWindow(() => { this.setWindowBounds(minimalWindowBounds); }); } restoreChatWindow() { this.ignoreRestoreBecauseWeAreMinimalAudio = false; chrome.runtime.sendMessage({ message: 'ChatRoute' }); chrome.runtime.sendMessage({ message: 'ToolbarButton', toolbarButtonMessageToBackground: '' }); this.restoreWindow(() => { this.setWindowBounds(this.lastChatWindowBounds); }); } // TODO: any setChatWindowBounds(bounds: any) { chrome.runtime.sendMessage({ message: 'ChatWindowBounds', data: bounds }); } public loadChatWindow(): Promise { return new Promise((resolve, reject) => { if (this.chatWindow.hasBoundWindow()) { resolve(); return; } this.logger.logMessage('Launching main window...'); chrome.app.window.create( 'webview.html', { hidden: true, id: 'react', resizable: true, frame: 'none' }, (win: chrome.app.window.AppWindow) => { this.logger.logMessage('loadChatWindow(): Created window'); if (win) { this.chatWindow.setAppWindow(win); this.chatWindow.onWindowLoad(() => { this.chatWindow.setSizeAndPosition(chatWindowBounds); this.chatWindow.minimizeWindow(() => { PubSub.publish('onChatWebViewReady'); }); }); this.chatWindow.onClosed(() => { this.logger.logMessage('chat window closed!'); chrome.runtime.sendMessage({ message: 'CloseWindows' }); PubSub.publish('onChatWindowClosed'); this.chatWindow.resetWindow(); this.loadChatWindow(); }); this.chatWindow.onWindowMinimized(() => { PubSub.publish('onChatWindowMinimized'); }); this.chatWindow.onWindowRestored(() => { PubSub.publish('onChatWindowRestored'); }); resolve(); } } ); }); } setShowTeacherWindowFullScreen() { this.chatWindow.setAlwaysOnTopAndFullScreen(() => { // If another screen is supposed to be on top, we'll return false return !this.chatWindowState.waitingForFullScreenSelect; }); } revertWindowFromFullScreen() { this.chatWindow.revertWindow(); } autoPositionChatWindow(desiredWidth: number, desiredHeight: number) { // Objective is to keep the right-hand frame as close as possible to where it is now let currentBounds = this.chatWindow.getBounds(); let newLeft = currentBounds.left - desiredWidth + currentBounds.width; if (newLeft < 0) { newLeft = 0; } let newTop = currentBounds.top - desiredHeight + currentBounds.height; if (newTop < 0) { newTop = 0; } console.log('WebViewWindow: autoPosition (+)'); this.lastChatWindowBounds = { left: newLeft, top: newTop, width: desiredWidth, height: desiredHeight }; this.chatWindow.setSizeAndPosition(this.lastChatWindowBounds); console.log('WebViewWindow: autoPosition (-)'); } restoreWindow(onRestoreCompleted: Function) { this.chatWindow.restoreWindow(onRestoreCompleted); } minimizeWindow(onMinimizeCompleted: Function) { this.chatWindow.minimizeWindow(onMinimizeCompleted); } isStudentInShowTeacherMode() { return this.isInVideoConference; } isStudentInAudioConference() { return this.isInAudioConference; } isStudentWaitingForFullScreenPermission() { return this.chatWindowState.waitingForFullScreenSelect; } isChatWindowMinimized() { return this.chatWindow.isMinimized(); } getWindowBounds() { let boundsObj = this.chatWindow.getInnerBounds(); return { left: boundsObj.left, top: boundsObj.top, width: boundsObj.width, height: boundsObj.height }; } getNominalWindowBounds() { return chatWindowBounds; } setWindowBounds(boundsObj: ChatWindowBounds) { this.chatWindow.setSizeAndPosition(boundsObj); } setResizeListener(onResizeFunction: Function) { this.chatWindow.onWindowResized(onResizeFunction); } }