import ExtensionMode from '@src/common/enums/ExtensionMode.enum'; import { ClientMenu } from './ClientMenu'; import EventBus from '../EventBus'; import PlayerData from '../video-data/PlayerData'; import { SiteSettings } from '../settings/SiteSettings'; import Settings from '../settings/Settings'; import { MenuPosition as MenuPosition } from '@src/common/interfaces/ClientUiMenu.interface'; import { CommandInterface } from '@src/common/interfaces/SettingsInterface'; if (process.env.CHANNEL !== 'stable'){ console.info("Loading: UI"); } const csuiVersions = { 'normal': 'csui', // csui-overlay-normal.html, maps to csui.html 'light': 'csui-light', // csui-overlay-light.html, maps to csui-light.html 'dark': 'csui-dark' // csui-overlay-dark.html, maps to csui-dark.html }; const MAX_IFRAME_ERROR_COUNT = 5; class UI { isGlobal: boolean; isIframe: boolean; private eventBus: EventBus; private playerData: PlayerData; private uiSettings: any; private settings: Settings; private siteSettings: SiteSettings; private csuiScheme; // These can prolly be removed: lastProbeResponseTs: number; private extensionMenu: ClientMenu; constructor( public interfaceId, public uiConfig, // {parentElement?, eventBus?, isGlobal?, playerData} ) { this.lastProbeResponseTs = null; this.isGlobal = uiConfig.isGlobal ?? false; this.isIframe = window.self !== window.top; this.eventBus = uiConfig.eventBus; this.playerData = uiConfig.playerData; this.uiSettings = uiConfig.uiSettings; this.siteSettings = uiConfig.siteSettings; this.settings = uiConfig.settings; // TODO: at some point, UI should be different for global popup and in-player UI this.csuiScheme = this.getCsuiScheme(); const csuiVersion = this.getCsuiVersion(this.csuiScheme.contentScheme); // this.uiURI = chrome.runtime.getURL(`/csui/${csuiVersion}.html`); // this.extensionBase = chrome.runtime.getURL('').replace(/\/$/, ""); // UI will be initialized when setUiVisibility is called if (!this.isGlobal) { this.init(); } } async init() { this.destroy() this.createExtensionMenu(); // this.initUIContainer(); // this.initMessaging(); } executeCommand(x: CommandInterface) { this.eventBus.send(x.action, x.arguments); } createExtensionMenu() { console.log('—>—— creating ext menu:. is enabled?', this.siteSettings?.data.enableUI, ExtensionMode[this.siteSettings?.data.enableUI], this.siteSettings?.data.enableUI === ExtensionMode.Disabled, 'global?', this.isGlobal) if (+this.siteSettings?.data.enableUI === ExtensionMode.Disabled || this.isGlobal) { console.log('we said no to UI') return; // don't } if (this.extensionMenu) { this.extensionMenu.destroy(); } if (this.uiConfig.parentElement) { const menuConfig = { isGlobal: this.isGlobal, menuPosition: MenuPosition.Left, items: [ { label: "todo: site + site compatibility info" }, { label: 'Crop', subitems: this.settings.active.commands.crop.map((x: CommandInterface) => { return { label: x.label, action: () => this.executeCommand(x) } }) }, { label: 'Zoom (presets)', subitems: [ ... this.settings.active.commands.zoom.map((x: CommandInterface) => { return { label: x.label, action: () => this.executeCommand(x) } }), ] }, { label: 'Stretch', subitems: this.settings.active.commands.stretch.map((x: CommandInterface) => { return { label: x.label, action: () => this.executeCommand(x) } }) }, { label: 'Zoom (free-form)', subitems: [ {label: 'todo sliders'} ] }, { label: 'Align', subitems: [{label: 'todo pls'}] }, { label: 'todo: open settings' }, { label: 'todo: first-time "customize/hide menu" option' } ] } this.extensionMenu = new ClientMenu(menuConfig); this.extensionMenu.mount(this.uiConfig.parentElement); } } /** * Returns color scheme we need to use. * * contentScheme is used to select the correct HTML template. * iframeScheme gets applied to the iframe as style * @returns {contentScheme: string, iframeScheme: string} */ getCsuiScheme() { return { contentScheme: window.getComputedStyle( document.body ,null).getPropertyValue('color-scheme'), iframeScheme: document.documentElement.style.colorScheme || document.body.style.colorScheme || undefined }; } /** * Returns correct template for given preferredScheme parameter * @param {*} preferredScheme * @returns */ getCsuiVersion(preferredScheme) { return csuiVersions[preferredScheme] ?? csuiVersions.normal; } initUIContainer() { const random = Math.round(Math.random() * 69420); const uwid = `uw-ultrawidify-${this.interfaceId}-root-${random}` return; } // initMessaging() { // // subscribe to events coming back to us. Unsubscribe if iframe vanishes. // window.addEventListener('message', this.messageHandlerFn); // /* set up event bus tunnel from content script to UI — necessary if we want to receive // * like current zoom levels & current aspect ratio & stuff. Some of these things are // * necessary for UI display in the popup. // */ // this.eventBus.subscribeMulti( // { // 'uw-config-broadcast': { // function: (config, routingData) => { // this.sendToIframe('uw-config-broadcast', config, routingData); // } // }, // 'uw-set-ui-state': { // function: (config, routingData) => { // if (config.globalUiVisible !== undefined && !this.isIframe) { // if (this.isGlobal) { // this.setUiVisibility(config.globalUiVisible); // } else { // this.setUiVisibility(!config.globalUiVisible); // } // } // this.sendToIframe('uw-set-ui-state', {...config, isGlobal: this.isGlobal}, routingData); // } // }, // 'uw-get-page-stats': { // function: (config, routingData) => { // this.eventBus.send( // 'uw-page-stats', // { // pcsDark: window.matchMedia('(prefers-color-scheme: dark)').matches, // pcsLight: window.matchMedia('(prefers-color-scheme: light)').matches, // colorScheme: window.getComputedStyle( document.body ,null).getPropertyValue('color-scheme') // }, // { // comms: { // forwardTo: 'popup' // } // } // ); // } // }, // 'uw-restore-ui-state': { // function: (config, routingData) => { // if (!this.isGlobal) { // this.setUiVisibility(true); // this.sendToIframe('uw-restore-ui-state', config, routingData); // } // } // } // }, // this // ); // } // messageHandlerFn = (message) => { // if (!this.uiIframe?.contentWindow) { // window.removeEventListener('message', this.messageHandlerFn); // return; // } // this.handleMessage(message); // } setUiVisibility(visible) { return; } async enable() { // if root element is not present, we need to init the UI. // if (!this.rootDiv) { // await this.init(); // } // otherwise, we don't have to do anything } disable() { // if (this.rootDiv) { // this.destroy(); // } } /** * Checks whether mouse is moving over either: * *