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'; import { SiteSupportLevel } from '@src/common/enums/SiteSupportLevel.enum'; import { ComponentLogger } from '../logging/ComponentLogger'; import alignmentIndicatorSvg from '!!raw-loader!@ui/res/img/alignment-indicators.svg'; import lockBarIndicatorSvg from '!!raw-loader!@ui/res/img/lock-bar-indicators.svg'; if (process.env.CHANNEL !== 'stable'){ console.info("Loading: UI"); } class UI { isGlobal: boolean; isIframe: boolean; private eventBus: EventBus; private playerData: PlayerData; private uiSettings: any; private settings: Settings; private siteSettings: SiteSettings; private extensionMenu: ClientMenu; private logger: ComponentLogger; constructor( public interfaceId, public uiConfig, // {parentElement?, eventBus?, isGlobal?, playerData} ) { 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; // UI will be initialized when setUiVisibility is called if (!this.isGlobal) { this.init(); } } async init() { this.destroy(); if (!this.logger) { this.logger = new ComponentLogger(this.settings.logAggregator, 'UI'); } this.createExtensionMenu(); this.eventBus.subscribeMulti({ 'uw-config-broadcast': { function: (message) => { console.log('UI.ts: received uw-config-broadcast', message); if (message.type === 'aard-error') { console.log('received: warning element:', this.extensionMenu.root.querySelector('#uw-cors-warning')); this.updateMenuWarnings(message); } else if (message.type === 'drm-status') { this.updateMenuWarnings(message); } } } }); } executeCommand(x: CommandInterface) { this.eventBus.send(x.action, x.arguments); } updateMenuWarnings(errors: {aardErrors?: any, hasDrm?: boolean}) { try { if (errors.aardErrors) { if (errors.aardErrors.cors) { this.extensionMenu.root.querySelector('#uw-cors-warning').classList.remove('uw-hidden'); } if (errors.aardErrors.drm) { this.extensionMenu.root.querySelector('#uw-drm-warning').classList.remove('uw-hidden'); } if (errors.aardErrors.webglError) { this.extensionMenu.root.querySelector('#uw-webgl-warning').classList.remove('uw-hidden'); } } if (errors.hasDrm) { this.extensionMenu.root.querySelector('#uw-drm-warning').classList.remove('uw-hidden'); } } catch (e) { this.logger.error("UI.ts: failed to update menu warnings:", e); } } private getSiteSupportLevelForDisplay() { const siteSupportLevel = (this.siteSettings ? this.siteSettings.data.type ?? SiteSupportLevel.Unknown : 'waiting') switch (siteSupportLevel) { case SiteSupportLevel.OfficialSupport: return {cssClass: 'uw-official', text: 'Verified', svg: 'check-decagram'}; case SiteSupportLevel.CommunitySupport: return {cssClass: 'uw-community', text: 'Community', svg: 'account-group'}; case SiteSupportLevel.Unknown: return {cssClass: 'uw-no-support', text: 'Untested', svg: 'help-circle-outline'}; case SiteSupportLevel.UserDefined: return {cssClass: 'uw-user-added', text: 'Modified by you', svg: 'account'}; case SiteSupportLevel.UserModified: return {cssClass: 'uw-user-added', text: 'Modified by you', svg: 'account'}; default: return {cssClass: 'uw-unknown', text: 'Unknown', svg: 'help-circle-outline'}; } } createExtensionMenu() { if (+this.siteSettings?.data.enableUI === ExtensionMode.Disabled || this.isGlobal) { return; // don't } if (this.extensionMenu) { this.extensionMenu.destroy(); } const svgMap = { 'check-decagram': 'check-decagram', 'account-group': 'account-group', 'help-circle-outline': 'help-circle-outline', 'account': 'account' }; const supportLevelInfo = this.getSiteSupportLevelForDisplay(); if (this.uiConfig.parentElement) { const menuConfig = { isGlobal: this.isGlobal, menuPosition: MenuPosition.Left, items: [ { customClassList: 'uw-site-info', customHTML: `
${this.siteSettings?.site ?? ''}
${svgMap[supportLevelInfo.svg]} ${supportLevelInfo.text}
` }, { customId: 'uw-drm-warning', customClassList: `uw-menu-warning ${this.playerData?.videoData.hasDrm ? '' : 'uw-hidden'}`, customHTML: ` alert
Autodetection not available
due to DRM
` }, { customId: 'uw-cors-warning', customClassList: 'uw-menu-warning uw-hidden', customHTML: ` alert
Autodetection not available
due to CORS error
` }, { customId: 'uw-webgl-warning', customClassList: 'uw-menu-warning uw-hidden', customHTML: ` alert
Autodetection not available
due to WEBGL error
Check if hardware acceleration is
enabled and working.
` }, { label: 'Crop', subitems: this.settings.active.commands.crop.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 (presets)', subitems: [ ... this.settings.active.commands.zoom.map((x: CommandInterface) => { return { label: x.label, action: () => this.executeCommand(x) } }), ] }, { label: 'Zoom (free-form)', subitems: [ { customHTML: `
Width:
100%
Height:
100%
${lockBarIndicatorSvg}
restore
`, } ] }, { 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); } } 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(); // } } /** * Replaces ui config and re-inits the UI * @param {*} newUiConfig */ replace(newUiConfig) { this.uiConfig = newUiConfig; this.init(); } destroy() { if (this.extensionMenu) { this.extensionMenu.destroy(); } } } if (process.env.CHANNEL !== 'stable'){ console.info("UI.js loaded"); } export default UI;