From 3180063fe370cf3f47aad91d492646c96f91af8a Mon Sep 17 00:00:00 2001 From: Tamius Han Date: Sat, 15 Jul 2023 04:03:32 +0200 Subject: [PATCH] attempt to get player selector to work --- src/common/interfaces/SettingsInterface.ts | 11 ++- src/csui/PlayerOverlay.vue | 14 --- src/csui/src/PlayerUIWindow.vue | 1 - .../SiteExtensionSettings.vue | 7 -- .../PlayerUiPanels/PlayerDetectionPanel.vue | 29 ++++-- src/ext/lib/settings/SiteSettings.ts | 92 +++++++++++++++++-- src/ext/lib/video-data/PlayerData.ts | 53 ++++++++++- 7 files changed, 162 insertions(+), 45 deletions(-) diff --git a/src/common/interfaces/SettingsInterface.ts b/src/common/interfaces/SettingsInterface.ts index d9569cd..e6c6dd2 100644 --- a/src/common/interfaces/SettingsInterface.ts +++ b/src/common/interfaces/SettingsInterface.ts @@ -48,7 +48,15 @@ export interface CommandInterface { actionId?: string, } +export type SettingsReloadComponent = 'PlayerData' | 'VideoData'; +export type SettingsReloadFlags = true | SettingsReloadComponent; + interface SettingsInterface { + _updateFlags?: { + requireReload?: SettingsReloadFlags, + forSite?: string + } + arDetect: { disabledReason: string, // if automatic aspect ratio has been disabled, show reason allowedMisaligned: number, // top and bottom letterbox thickness can differ by this much. @@ -384,7 +392,7 @@ export interface SiteSettingsInterface { } export interface SiteDOMSettingsInterface { - type: 'official' | 'community' | 'user-defined' | undefined; + type: 'official' | 'community' | 'user-defined' | 'modified' | undefined; elements?: { player?: SiteDOMElementSettingsInterface, video?: SiteDOMElementSettingsInterface, @@ -403,6 +411,7 @@ export interface SiteDOMElementSettingsInterface { manual?: boolean; querySelectors?: string; index?: number; // previously: useRelativeAncestor + videoAncestor + mode?: 'index' | 'qs'; nodeCss?: {[x: string]: string}; } diff --git a/src/csui/PlayerOverlay.vue b/src/csui/PlayerOverlay.vue index a2bee02..a129451 100644 --- a/src/csui/PlayerOverlay.vue +++ b/src/csui/PlayerOverlay.vue @@ -34,13 +34,6 @@ diff --git a/src/ext/lib/settings/SiteSettings.ts b/src/ext/lib/settings/SiteSettings.ts index 275d845..1a93e9a 100644 --- a/src/ext/lib/settings/SiteSettings.ts +++ b/src/ext/lib/settings/SiteSettings.ts @@ -1,6 +1,6 @@ import CropModePersistence from '../../../common/enums/CropModePersistence.enum'; import ExtensionMode from '../../../common/enums/ExtensionMode.enum'; -import { SiteSettingsInterface } from '../../../common/interfaces/SettingsInterface'; +import { SettingsReloadComponent, SettingsReloadFlags, SiteSettingsInterface } from '../../../common/interfaces/SettingsInterface'; import { _cp } from '../../../common/js/utils'; import Settings from '../Settings'; import { browser } from 'webextension-polyfill-ts'; @@ -24,6 +24,8 @@ export class SiteSettings { sessionData: SiteSettingsInterface; readonly defaultSettings: SiteSettingsInterface; + storageChangeSubscriptions: {[x: string]: ((newSiteConf, changes, area) => void)[]} = {}; + //#region lifecycle constructor(settings: Settings, site: string) { this.settings = settings; @@ -114,12 +116,53 @@ export class SiteSettings { } const parsedSettings = JSON.parse(changes.uwSettings.newValue); - this.data = parsedSettings.active.sites[this.site]; + this.data = parsedSettings.sites[this.site]; // we ignore 'readonly' property this once - (this as any).defaultSettings = parsedSettings.active.sites['@global']; + (this as any).defaultSettings = parsedSettings.sites['@global']; this.compileSettingsObject(); + + console.log('changes:', parsedSettings); + + // trigger any subscriptions on change + if (parsedSettings._updateFlags) { + console.log('update flags yay!') + if (parsedSettings._updateFlags?.forSite === this.site) { + if (parsedSettings._updateFlags?.requireReload === true) { + for (const key in this.storageChangeSubscriptions) { + for (const fn of this.storageChangeSubscriptions[key]) { + fn(this, changes, area); + } + } + } + else if (parsedSettings._updateFlags?.requireReload) { + for (const key of parsedSettings._updateFlags?.requireReload) { + console.log('reload required for:', key, this.storageChangeSubscriptions) + for (const fn of this.storageChangeSubscriptions[key]) { + fn(this, changes, area); + } + } + } + } + + // we aren't stepping on any other toes by doing this, since everyone + // gets the first change + this.settings.active._updateFlags = undefined; + this.settings.saveWithoutReload(); + } + } + + subscribeToStorageChange(component: SettingsReloadComponent, fn: (newSiteConf, changes, area) => void) { + if (!this.storageChangeSubscriptions[component]) { + this.storageChangeSubscriptions[component] = []; + } + this.storageChangeSubscriptions[component].push(fn); + } + unsubscribeToStorageChange(component: SettingsReloadComponent, fn: (newSiteConf, changes, area) => void) { + if (this.storageChangeSubscriptions[component]) { + this.storageChangeSubscriptions[component] = this.storageChangeSubscriptions[component].filter(x => x != fn); + } } //#endregion @@ -206,6 +249,23 @@ export class SiteSettings { //#endregion + //#region get + /** + * Gets DOMConfig. If DOMConfig with given name doesn't exist, we create a new one. + * @param configName We want to fetch this DOM config + * @param copyFrom If DOMConfig data doesn't exist, we copy things from DOMConfig with + * this name. If DOMConfig with this name doesn't exist, we copy last + * active DOMConfig. If that fails, we copy original DOMConfig. + * @returns Current DOMConfig object for this site + */ + getDOMConfig(configName: string, copyFrom?: string) { + if (! this.data.DOMConfig[configName]) { + this.data.DOMConfig[configName] = _cp(this.data.DOMConfig[copyFrom ?? this.data.activeDOMConfig ?? 'original']); + } + return this.data.currentDOMConfig[configName]; + } + //#endregion + //#region set shit /** * Sets option value. @@ -213,7 +273,7 @@ export class SiteSettings { * @param optionValue new value of option * @param reload whether we should trigger a reload in components that require it */ - async set(optionPath: string, optionValue: any, reload: boolean = false) { + async set(optionPath: string, optionValue: any, options: {reload?: boolean, noSave?: boolean} = {reload: false}) { // if no settings exist for this site, create an empty object if (!this.settings.active.sites[this.site]) { this.settings.active.sites[this.site] = _cp(this.settings.active.sites['@empty']); @@ -226,22 +286,38 @@ export class SiteSettings { } else { let iterator = this.settings.active.sites[this.site]; let i; + let iterated = ''; + for (i = 0; i < pathParts.length - 1; i++) { + iterated = `${iterated}.${pathParts[i]}` + if (!iterator[pathParts[i]]) { // some optional paths may still be undefined, even after cloning empty object iterator[pathParts[i]] = {}; } iterator = iterator[pathParts[i]]; } + iterated = `${iterated}.${pathParts[i]}` + iterator[pathParts[i]] = optionValue; } - if (reload) { - this.settings.save(); - } else { - this.settings.saveWithoutReload(); + if (! options.noSave) { + if (options.reload) { + await this.settings.save(); + } else { + await this.settings.saveWithoutReload(); + } } } + async setUpdateFlags(flags: SettingsReloadFlags) { + this.settings.active._updateFlags = { + requireReload: flags, + forSite: this.site + }; + } + + /** * Sets temporary option value (for Persistence.UntilReload) * @param optionPath path to value in object notation (dot separated) diff --git a/src/ext/lib/video-data/PlayerData.ts b/src/ext/lib/video-data/PlayerData.ts index 725a001..cb7ccd0 100644 --- a/src/ext/lib/video-data/PlayerData.ts +++ b/src/ext/lib/video-data/PlayerData.ts @@ -146,12 +146,33 @@ class PlayerData { this.startChangeDetection(); document.addEventListener('fullscreenchange', this.trackDimensionChanges); + + // we want to reload on storage changes + this.siteSettings.subscribeToStorageChange('PlayerData', () => this.reloadPlayerDataConfig()); } catch (e) { console.error('[Ultrawidify::PlayerData::ctor] There was an error setting up player data. You should be never seeing this message. Error:', e); this.invalid = true; } } + private reloadPlayerDataConfig() { + console.log('reloading config ...') + this.element = this.getPlayer(); + + console.log('got player:', this.element); + + this.periodicallyRefreshPlayerElement = false; + try { + this.periodicallyRefreshPlayerElement = this.siteSettings.data.currentDOMConfig.periodicallyRefreshPlayerElement; + } catch (e) { + // no biggie — that means we don't have any special settings for this site. + } + + // because this is often caused by the UI + console.log('tree request:'); + this.handlePlayerTreeRequest(); + } + private initEventBus() { for (const action in this.eventBusCommands) { for (const command of this.eventBusCommands[action]) { @@ -474,10 +495,32 @@ class PlayerData { const playerQs = this.siteSettings.getCustomDOMQuerySelector('player'); const playerIndex = this.siteSettings.getPlayerIndex(); - if (playerQs) { - playerCandidate = this.getPlayerQs(playerQs, elementStack, videoWidth, videoHeight); - } else if (playerIndex) { // btw 0 is not a valid index for player - playerCandidate = elementStack[playerIndex]; + // on verbose, get both qs and index player + if (options.verbose) { + if (playerIndex) { + playerCandidate = elementStack[playerIndex]; + playerCandidate.heuristics['manualElementByParentIndex'] = true; + } + if (playerQs) { + playerCandidate = this.getPlayerQs(playerQs, elementStack, videoWidth, videoHeight); + } + } + // if mode is given, we follow the preference + if (this.siteSettings.data.currentDOMConfig?.elements?.player?.mode) { + if (this.siteSettings.data.currentDOMConfig?.elements?.player?.mode === 'qs') { + playerCandidate = this.getPlayerQs(playerQs, elementStack, videoWidth, videoHeight); + } else { + playerCandidate = elementStack[playerIndex]; + playerCandidate.heuristics['manualElementByParentIndex'] = true; + } + } else { + // try to figure it out based on what we have, with playerQs taking priority + if (playerQs) { + playerCandidate = this.getPlayerQs(playerQs, elementStack, videoWidth, videoHeight); + } else if (playerIndex) { // btw 0 is not a valid index for player + playerCandidate = elementStack[playerIndex]; + playerCandidate.heuristics['manualElementByParentIndex'] = true; + } } if (playerCandidate) { @@ -644,8 +687,10 @@ class PlayerData { * Lists elements between video and DOM root for display in player selector (UI) */ private handlePlayerTreeRequest() { + console.log('aya') // this populates this.elementStack fully this.getPlayer({verbose: true}); + console.log('tree:', JSON.parse(JSON.stringify(this.elementStack))); this.eventBus.send('uw-config-broadcast', {type: 'player-tree', config: JSON.parse(JSON.stringify(this.elementStack))}); }