Compare commits

..

No commits in common. "a0cddb256ae3e86ded813a0af1b9f5fda9f539dc" and "af08c5094d0438b8ff60e6a83e7249026dbf7b2c" have entirely different histories.

5 changed files with 459 additions and 634 deletions

View File

@ -5,20 +5,19 @@ export interface MenuItemConfig {
customHTML?: HTMLElement; customHTML?: HTMLElement;
} }
export enum MenuPosition { export type MenuAnchor =
TopLeft = 'top-left', | "LeftCenter"
Left = 'left', | "RightCenter"
BottomLeft = 'bottom-left', | "TopCenter"
Top = 'top', | "BottomCenter"
Bottom = 'bottom', | "TopLeft"
TopRight = 'top-right', | "TopRight"
Right = 'right', | "BottomLeft"
BottomRight = 'bottom-right', | "BottomRight";
}
export interface MenuConfig { export interface MenuConfig {
isGlobal?: boolean; isGlobal?: boolean;
menuPosition: MenuPosition; anchor: MenuAnchor;
activationRadius?: number; activationRadius: number;
items: MenuItemConfig[]; items: MenuItemConfig[];
} }

View File

@ -1,4 +1,4 @@
import { MenuPosition, MenuConfig, MenuItemConfig } from '@src/common/interfaces/ClientUiMenu.interface'; import { MenuConfig, MenuItemConfig } from '@src/common/interfaces/ClientUiMenu.interface';
import extensionCss from '@src/main.css?inline'; import extensionCss from '@src/main.css?inline';
export class ClientMenu { export class ClientMenu {
@ -8,22 +8,9 @@ export class ClientMenu {
private root!: HTMLDivElement; private root!: HTMLDivElement;
private visible = false; private visible = false;
private menuPositionClasses: string[] = [];
private isHovered = false;
private isWithinActivation = false;
private lastMouseMove = performance.now();
private idleTimer?: number;
private onDocumentMouseMove?: (e: MouseEvent) => void;
private onDocumentMouseLeave?: () => void;
private idleIntervalId?: number;
constructor(private config: MenuConfig) {} constructor(private config: MenuConfig) {}
mount(anchorElement: HTMLElement) { mount(anchorElement: HTMLElement) {
this.buildMenuPositionClassList();
this.createHost(); this.createHost();
this.createShadow(); this.createShadow();
this.createMenu(); this.createMenu();
@ -34,55 +21,9 @@ export class ClientMenu {
anchorElement.appendChild(this.host); anchorElement.appendChild(this.host);
} }
public destroy() {
// 1. Stop timers
if (this.idleIntervalId != null) {
clearInterval(this.idleIntervalId);
this.idleIntervalId = undefined;
}
// 2. Remove global listeners
if (this.onDocumentMouseMove) {
document.removeEventListener('mousemove', this.onDocumentMouseMove);
this.onDocumentMouseMove = undefined;
}
if (this.onDocumentMouseLeave) {
document.removeEventListener('mouseleave', this.onDocumentMouseLeave);
this.onDocumentMouseLeave = undefined;
}
// 3. Remove DOM
if (this.host && this.host.parentNode) {
this.host.parentNode.removeChild(this.host);
}
// 4. Clear references
this.shadow = undefined as any;
this.root = undefined as any;
this.host = undefined as any;
// 5. Reset state
this.visible = false;
this.isHovered = false;
this.isWithinActivation = false;
}
private getActivationRadius(anchorEl: HTMLElement): number | null {
if (this.config.activationRadius == null) return null;
if (typeof this.config.activationRadius === 'number') {
return this.config.activationRadius;
}
// percentage string
const rect = anchorEl.getBoundingClientRect();
const pct = parseFloat(this.config.activationRadius);
return Math.max(rect.width, rect.height) * (pct / 100);
}
private injectStyles() { private injectStyles() {
const style = document.createElement('style'); const style = document.createElement("style");
console.warn('imported styles: imported CSS (raw):', typeof extensionCss, extensionCss);
let css = extensionCss let css = extensionCss
.trim() .trim()
.replace(/'html, body \{/g, ':host {') .replace(/'html, body \{/g, ':host {')
@ -96,8 +37,8 @@ export class ClientMenu {
css = ` css = `
${cssArr[0]} ${cssArr[0]}
@font-face { @font-face {
font-family: 'Heebo'; font-family: "Heebo";
src: url(__FONT_HEEBO__) format('truetype-variations'); src: url(__FONT_HEEBO__) format("truetype-variations");
font-weight: 100 900; font-weight: 100 900;
font-stretch: 75% 125%; font-stretch: 75% 125%;
font-style: normal; font-style: normal;
@ -105,16 +46,16 @@ export class ClientMenu {
} }
@font-face { @font-face {
font-family: 'Source Code Pro'; font-family: "Source Code Pro";
src: url(__FONT_SCP__) format('truetype-variations'); src: url(__FONT_SCP__) format("truetype-variations");
font-weight: 200 900; font-weight: 200 900;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: 'Source Code Pro'; font-family: "Source Code Pro";
src: url(__FONT_SCPI__) format('truetype-variations'); src: url(__FONT_SCPI__) format("truetype-variations");
font-weight: 200 900; font-weight: 200 900;
font-style: italic; font-style: italic;
font-display: swap; font-display: swap;
@ -122,6 +63,8 @@ export class ClientMenu {
${cssRemainder}; ${cssRemainder};
`; `;
console.log('+————————————————————————————————————', cssRemainder)
css = css css = css
.replace('__FONT_HEEBO__', chrome.runtime.getURL('/ui/res/fonts/Heebo.ttf')) .replace('__FONT_HEEBO__', chrome.runtime.getURL('/ui/res/fonts/Heebo.ttf'))
.replace('__FONT_SCP__', chrome.runtime.getURL('/ui/res/fonts/SourceCodePro.ttf')) .replace('__FONT_SCP__', chrome.runtime.getURL('/ui/res/fonts/SourceCodePro.ttf'))
@ -133,109 +76,55 @@ export class ClientMenu {
this.shadow.appendChild(style); this.shadow.appendChild(style);
} }
private createHost() { private createHost() {
this.host = document.createElement('div'); this.host = document.createElement("div");
this.host.classList.add('uw-ultrawidify-container-root'); this.host.classList.add('uw-ultrawidify-container-root');
Object.assign(this.host.style, { Object.assign(this.host.style, {
position: this.config.isGlobal ? 'fixed' : 'absolute', position: this.config.isGlobal ? "fixed" : "absolute",
left: 0, left: 0,
top: 0, top: 0,
border: 0, border: 0,
width: '100%', width: "100%",
height: '100%', height: "100%",
zIndex: this.config.isGlobal ? '2147483647' : '2147483640', zIndex: this.config.isGlobal ? "2147483647" : "2147483640",
pointerEvents: 'none', // pointerEvents: "none",
backgroundColor: "#00000099",
}); });
console.log('UI host created:', this.host); console.log('UI host created:', this.host);
} }
private createShadow() { private createShadow() {
this.shadow = this.host.attachShadow({ mode: 'open' }); this.shadow = this.host.attachShadow({ mode: "open" });
this.injectStyles(); this.injectStyles();
} }
/**
*
* @returns
*/
private buildMenuPositionClassList() {
let classList;
switch (this.config.menuPosition) {
case MenuPosition.TopLeft:
classList = ['uw-menu-left','uw-menu-top'];
break;
case MenuPosition.Left:
classList = ['uw-menu-left','uw-menu-ycenter'];
break;
case MenuPosition.BottomLeft:
classList = ['uw-menu-left','uw-menu-bottom'];
break;
case MenuPosition.Top:
classList = ['uw-menu-center','uw-menu-top'];
break;
case MenuPosition.Bottom:
classList = ['uw-menu-center', 'uw-menu-bottom'];
break;
case MenuPosition.TopRight:
classList = ['uw-menu-right', 'uw-menu-top'];
break;
case MenuPosition.Right:
classList = ['uw-menu-right', 'uw-menu-ycenter'];
break;
case MenuPosition.BottomRight:
classList = ['uw-menu-right', 'uw-menu-bottom'];
break;
default: // left-center is our default position
classList = ['uw-menu-left', 'uw-menu-ycenter'];
break;
}
this.menuPositionClasses = classList;
}
private createMenu() { private createMenu() {
this.root = document.createElement('div'); this.root = document.createElement("div");
this.root.className = 'uw-menu-root uw-hidden'; this.root.className = "uw-menu-root font-mono";
const trigger = document.createElement('div'); const trigger = document.createElement("div");
trigger.classList = 'uw-menu-trigger uw-trigger'; trigger.className = "uw-menu-trigger";
trigger.textContent = 'Ultrawidify'; trigger.textContent = "☰";
const submenu = this.buildSubmenu(this.config.items); const submenu = this.buildSubmenu(this.config.items);
trigger.addEventListener('mouseenter', () => this.show()); trigger.addEventListener("mouseenter", () => this.show());
this.root.addEventListener('mouseleave', () => { this.root.addEventListener("mouseleave", () => this.hide());
this.isHovered = false;
this.hide();
});
this.root.classList.add(...this.menuPositionClasses); this.root.append(trigger, submenu);
trigger.appendChild(submenu);
this.root.append(trigger);
this.shadow.appendChild(this.root); this.shadow.appendChild(this.root);
this.root.addEventListener('mouseenter', () => {
this.isHovered = true;
this.show();
});
this.root.addEventListener('mouseleave', () => {
this.isHovered = false;
this.updateVisibility();
});
} }
private buildSubmenu(items: MenuItemConfig[]): HTMLDivElement { private buildSubmenu(items: MenuItemConfig[]): HTMLDivElement {
const menu = document.createElement('div'); const menu = document.createElement("div");
menu.classList = 'uw-submenu'; menu.className = "uw-submenu";
menu.classList.add(...this.menuPositionClasses);
for (const item of items) { for (const item of items) {
const el = document.createElement('div'); const el = document.createElement("div");
el.className = `uw-menu-item uw-trigger`; el.className = "uw-menu-item";
if (item.customHTML) { if (item.customHTML) {
el.appendChild(item.customHTML); el.appendChild(item.customHTML);
@ -244,10 +133,10 @@ export class ClientMenu {
} }
if (item.action) { if (item.action) {
el.addEventListener('click', e => { el.addEventListener("click", e => {
e.stopPropagation(); e.stopPropagation();
item.action?.(); item.action?.();
// this.hide(); // maybe dont this.hide();
}); });
} }
@ -262,8 +151,23 @@ export class ClientMenu {
} }
private position(anchorEl: HTMLElement) { private position(anchorEl: HTMLElement) {
let appendClassList: string[] = []; const rect = anchorEl.getBoundingClientRect();
anchorEl.classList.add(...appendClassList); const r = this.root.style;
// switch (this.config.anchor) {
// case "LeftCenter":
// r.left = "0";
// r.top = "50%";
// r.transform = "translateY(-50%)";
// break;
// case "TopLeft":
// r.left = "0";
// r.top = "0";
// break;
// // others are trivial extensions
// }
} }
private bindGlobalMouse(anchorEl: HTMLElement) { private bindGlobalMouse(anchorEl: HTMLElement) {
@ -271,80 +175,27 @@ export class ClientMenu {
const cx = rect.left + rect.width / 2; const cx = rect.left + rect.width / 2;
const cy = rect.top + rect.height / 2; const cy = rect.top + rect.height / 2;
const activationRadius = this.getActivationRadius(anchorEl); document.addEventListener("mousemove", e => {
const d = Math.hypot(e.clientX - cx, e.clientY - cy);
this.onDocumentMouseMove = (e: MouseEvent) => { if (d < this.config.activationRadius) {
this.lastMouseMove = performance.now(); this.show();
} else if (!this.root.matches(":hover")) {
if (activationRadius != null) {
const d = Math.hypot(e.clientX - cx, e.clientY - cy);
this.isWithinActivation = d <= activationRadius;
} else {
this.isWithinActivation =
e.clientX >= rect.left &&
e.clientX <= rect.right &&
e.clientY >= rect.top &&
e.clientY <= rect.bottom;
}
this.updateVisibility();
};
this.onDocumentMouseLeave = () => {
this.isHovered = false;
this.isWithinActivation = false;
this.hide();
};
document.addEventListener('mousemove', this.onDocumentMouseMove);
document.addEventListener('mouseleave', this.onDocumentMouseLeave);
this.startIdleWatcher();
}
private startIdleWatcher() {
this.idleIntervalId = window.setInterval(() => {
const idle = performance.now() - this.lastMouseMove > 1000;
if (idle) {
this.hide(); this.hide();
} }
}, 200); });
}
private updateVisibility() {
const idle = performance.now() - this.lastMouseMove > 1000;
if (this.isHovered) {
this.show();
return;
}
if (this.isWithinActivation && !idle) {
this.show();
return;
}
if (this.visible) {
this.hide();
}
} }
private show() { private show() {
this.lastMouseMove = performance.now();
if (!this.visible) { if (!this.visible) {
this.visible = true; this.visible = true;
this.root.classList.add('uw-visible'); this.root.classList.add("visible");
this.root.classList.remove('uw-hidden');
} }
} }
private hide() { private hide() {
if (this.visible) { if (this.visible) {
this.visible = false; this.visible = false;
this.root.classList.remove('uw-visible'); this.root.classList.remove("visible");
this.root.classList.add('uw-hidden');
} }
} }
} }

View File

@ -3,10 +3,6 @@ import { ClientMenu } from './ClientMenu';
import EventBus from '../EventBus'; import EventBus from '../EventBus';
import PlayerData from '../video-data/PlayerData'; import PlayerData from '../video-data/PlayerData';
import { SiteSettings } from '../settings/SiteSettings'; 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'){ if (process.env.CHANNEL !== 'stable'){
console.info("Loading: UI"); console.info("Loading: UI");
@ -25,19 +21,22 @@ class UI {
isGlobal: boolean; isGlobal: boolean;
isIframe: boolean; isIframe: boolean;
private eventBus: EventBus; private eventBus: EventBus;
private playerData: PlayerData; private playerData: PlayerData;
private uiSettings: any; private uiSettings: any;
private settings: Settings;
private siteSettings: SiteSettings; private siteSettings: SiteSettings;
private disablePointerEvents: boolean = false;
private iframeErrorCount: number = 0;
private iframeConfirmed: boolean = false;
private iframeRejected: boolean = false;
private delayedDestroyTimer: any = undefined;
private csuiScheme; private csuiScheme;
private extensionBase: string;
// These can prolly be removed: // These can prolly be removed:
lastProbeResponseTs: number; lastProbeResponseTs: number
private extensionMenu: ClientMenu;
constructor( constructor(
public interfaceId, public interfaceId,
@ -53,13 +52,18 @@ class UI {
this.playerData = uiConfig.playerData; this.playerData = uiConfig.playerData;
this.uiSettings = uiConfig.uiSettings; this.uiSettings = uiConfig.uiSettings;
this.siteSettings = uiConfig.siteSettings; this.siteSettings = uiConfig.siteSettings;
this.settings = uiConfig.settings;
this.iframeErrorCount = 0;
this.iframeConfirmed = false;
this.iframeRejected = false;
this.delayedDestroyTimer = null;
// TODO: at some point, UI should be different for global popup and in-player UI // TODO: at some point, UI should be different for global popup and in-player UI
this.csuiScheme = this.getCsuiScheme(); this.csuiScheme = this.getCsuiScheme();
const csuiVersion = this.getCsuiVersion(this.csuiScheme.contentScheme); const csuiVersion = this.getCsuiVersion(this.csuiScheme.contentScheme);
// this.uiURI = chrome.runtime.getURL(`/csui/${csuiVersion}.html`); // this.uiURI = chrome.runtime.getURL(`/csui/${csuiVersion}.html`);
// this.extensionBase = chrome.runtime.getURL('').replace(/\/$/, ""); this.extensionBase = chrome.runtime.getURL('').replace(/\/$/, "");
// UI will be initialized when setUiVisibility is called // UI will be initialized when setUiVisibility is called
if (!this.isGlobal) { if (!this.isGlobal) {
@ -67,85 +71,23 @@ class UI {
} }
} }
canRun() {
if (this.isGlobal) {
return true;
}
return this.siteSettings?.data.enableUI !== ExtensionMode.Disabled;
}
async init() { async init() {
this.destroy() if (!this.canRun()) {
this.createExtensionMenu(); return;
// 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);
} }
this.initUIContainer();
this.loadIframe();
this.initMessaging();
} }
/** /**
@ -175,91 +117,242 @@ class UI {
const random = Math.round(Math.random() * 69420); const random = Math.round(Math.random() * 69420);
const uwid = `uw-ultrawidify-${this.interfaceId}-root-${random}` const uwid = `uw-ultrawidify-${this.interfaceId}-root-${random}`
if (this.uiConfig.parentElement) {
const uwMenu = new ClientMenu({isGlobal: this.isGlobal, anchor: "LeftCenter", items: [{label: 'test'}]});
uwMenu.mount(this.uiConfig.parentElement);
}
return; return;
const rootDiv = document.createElement('div');
if (this.uiConfig.additionalStyle) {
rootDiv.setAttribute('style', this.uiConfig.additionalStyle);
}
rootDiv.setAttribute('id', uwid);
rootDiv.classList.add('uw-ultrawidify-container-root');
rootDiv.style.width = "100%";
rootDiv.style.height = "100%";
rootDiv.style.position = this.isGlobal ? "fixed" : "absolute";
rootDiv.style.zIndex = this.isGlobal ? '90009' : '90000';
rootDiv.style.border = 0;
rootDiv.style.top = 0;
rootDiv.style.pointerEvents = 'none';
if (this.uiConfig?.parentElement) {
this.uiConfig.parentElement.appendChild(rootDiv);
} else {
document.body.appendChild(rootDiv);
}
this.rootDiv = rootDiv;
} }
// initMessaging() { loadIframe() {
// // subscribe to events coming back to us. Unsubscribe if iframe vanishes. return;
// window.addEventListener('message', this.messageHandlerFn); // in onMouseMove, we currently can't access this because we didn't
// do things the most properly
const uiURI = this.uiURI;
// /* set up event bus tunnel from content script to UI — necessary if we want to receive const iframe = document.createElement('iframe');
// * like current zoom levels & current aspect ratio & stuff. Some of these things are iframe.setAttribute('src', uiURI);
// * necessary for UI display in the popup. iframe.setAttribute("allowTransparency", 'true');
// */ // iframe.style.width = "100%";
// iframe.style.height = "100%";
iframe.style.position = "absolute";
iframe.style.zIndex = this.isGlobal ? '90009' : '90000';
iframe.style.border = 0;
iframe.style.pointerEvents = 'none';
iframe.style.opacity = 0;
iframe.style.backgroundColor = 'transparent !important';
// this.eventBus.subscribeMulti( // If colorScheme is defined via CSS on the HTML or BODY elements, then we need to also
// { // put a matching style to the iframe itself. Using the correct UI template is not enough.
// 'uw-config-broadcast': { if (this.csuiScheme.iframeScheme) {
// function: (config, routingData) => { iframe.style.colorScheme = this.csuiScheme.iframeScheme;
// 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) => { /* so we have a problem: we want iframe to be clickthrough everywhere except
// if (!this.uiIframe?.contentWindow) { * on our actual overlay. There's no nice way of doing that, so we need some
// window.removeEventListener('message', this.messageHandlerFn); * extra javascript to deal with this.
// return; *
// } * There's a second problem: while iframe is in clickable mode, onMouseMove
// this.handleMessage(message); * will not work (as all events will be hijacked by the iframe). This means
// } * that iframe also needs to run its own instance of onMouseMove.
*/
// set uiIframe for handleMessage
this.uiIframe = iframe;
// set not visible by default
// this.setUiVisibility(false);
const fn = (event) => {
// remove self on fucky wuckies
if (!iframe?.contentWindow ) {
document.removeEventListener('mousemove', fn, true);
return;
}
const rect = this.uiIframe.getBoundingClientRect();
const offsets = {
top: window.scrollY + rect.top,
left: window.scrollX + rect.left
};
const coords = {
x: event.pageX - offsets.left,
y: event.pageY - offsets.top,
frameOffset: offsets,
};
const playerData = this.canShowUI(coords);
// ask the iframe to check whether there's a clickable element
this.uiIframe.contentWindow.postMessage(
{
action: 'uwui-probe',
coords,
playerDimensions: playerData.playerDimensions,
canShowUI: playerData.canShowUI,
isIframe: this.isIframe,
ts: +new Date() // this should be accurate enough for our purposes,
},
uiURI
);
}
// NOTE: you cannot communicate with UI iframe inside onload function.
// onload triggers after iframe is initialized, but BEFORE vue finishes
// setting up all the components.
// If we need to have any data inside the vue component, we need to
// request that data from vue components.
iframe.onload = function() {
document.addEventListener('mousemove', fn, true);
}
this.eventBus.forwardToIframe(
this.uiIframe,
(action, payload) => {
this.sendToIframe(action, payload, {})
}
);
this.rootDiv.appendChild(iframe);
}
unloadIframe() {
this.eventBus.cancelIframeForwarding(this.uiIframe);
window.removeEventListener('message', this.messageHandlerFn);
this.uiIframe?.remove();
delete this.uiIframe;
}
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) { setUiVisibility(visible) {
return; return;
// console.log('uwui - setting ui visibility!', visible, this.isGlobal ? 'global' : 'page', this.uiIframe, this.rootDiv);
// if (!this.uiIframe || !this.rootDiv) {
// this.init();
// }
if (visible) {
this.rootDiv.style.width = '100%';
this.rootDiv.style.height = '100%';
this.uiIframe.style.width = '100%';
this.uiIframe.style.height = '100%';
// if (this.delayedDestroyTimer) {
// clearTimeout(this.delayedDestroyTimer);
// }
} else {
this.rootDiv.style.width = '0px';
this.rootDiv.style.height = '0px';
this.uiIframe.style.width = '0px';
this.uiIframe.style.height = '0px';
// destroy after 30 seconds of UI being hidden
// this.delayedDestroyTimer = setTimeout( () => this.unloadIframe(), 30000);
}
} }
async enable() { async enable() {
// if root element is not present, we need to init the UI. // if root element is not present, we need to init the UI.
// if (!this.rootDiv) { if (!this.rootDiv) {
// await this.init(); await this.init();
// } }
// otherwise, we don't have to do anything // otherwise, we don't have to do anything
} }
disable() { disable() {
// if (this.rootDiv) { if (this.rootDiv) {
// this.destroy(); this.destroy();
// } }
} }
/** /**
@ -300,144 +393,144 @@ class UI {
return result; return result;
} }
// /** /**
// * Handles events received from the iframe. * Handles events received from the iframe.
// * @param {*} event * @param {*} event
// */ */
// handleMessage(event) { handleMessage(event) {
// if (event.origin === this.extensionBase) { if (event.origin === this.extensionBase) {
// switch(event.data.action) { switch(event.data.action) {
// case 'uwui-clickable': case 'uwui-clickable':
// if (event.data.ts < this.lastProbeResponseTs) { if (event.data.ts < this.lastProbeResponseTs) {
// return; return;
// } }
// if (this.disablePointerEvents) { if (this.disablePointerEvents) {
// return; return;
// } }
// this.lastProbeResponseTs = event.data.ts; this.lastProbeResponseTs = event.data.ts;
// // If iframe returns 'yes, we are clickable' and iframe is currently set to pointerEvents=auto, // If iframe returns 'yes, we are clickable' and iframe is currently set to pointerEvents=auto,
// // but hasMouse is false, then UI is attached to the wrong element. This probably means our // but hasMouse is false, then UI is attached to the wrong element. This probably means our
// // detected player element is wrong. We need to perform this check if we aren't in global UI // detected player element is wrong. We need to perform this check if we aren't in global UI
// /** /**
// * action: 'uwui-clickable', * action: 'uwui-clickable',
// * clickable: isClickable, * clickable: isClickable,
// * hoverStats: { * hoverStats: {
// * isOverTriggerZone, * isOverTriggerZone,
// * isOverMenuTrigger, * isOverMenuTrigger,
// * isOverUIArea, * isOverUIArea,
// * hasMouse: !!document.querySelector(':hover'), * hasMouse: !!document.querySelector(':hover'),
// * }, * },
// * ts: +new Date() * ts: +new Date()
// */ */
// if (!this.global) { if (!this.global) {
// if ( if (
// this.uiIframe.style.pointerEvents === 'auto' this.uiIframe.style.pointerEvents === 'auto'
// ) { ) {
// if ( if (
// event.data.hoverStats.isOverMenuTrigger event.data.hoverStats.isOverMenuTrigger
// && !event.data.hoverStats.hasMouse && !event.data.hoverStats.hasMouse
// ) { ) {
// if (!this.iframeConfirmed) { if (!this.iframeConfirmed) {
// if (this.iframeErrorCount++ > MAX_IFRAME_ERROR_COUNT && !this.iframeRejected) { if (this.iframeErrorCount++ > MAX_IFRAME_ERROR_COUNT && !this.iframeRejected) {
// this.iframeRejected = true; this.iframeRejected = true;
// this.eventBus.send('change-player-element'); this.eventBus.send('change-player-element');
// return; return;
// } }
// } }
// } else if (event.data.hoverStats.isOverMenuTrigger && event.data.hoverStats.hasMouse) { } else if (event.data.hoverStats.isOverMenuTrigger && event.data.hoverStats.hasMouse) {
// this.iframeConfirmed = true; this.iframeConfirmed = true;
// } }
// } }
// } }
// this.uiIframe.style.pointerEvents = event.data.clickable ? 'auto' : 'none'; this.uiIframe.style.pointerEvents = event.data.clickable ? 'auto' : 'none';
// this.uiIframe.style.opacity = event.data.opacity || this.isGlobal ? '100' : '0'; this.uiIframe.style.opacity = event.data.opacity || this.isGlobal ? '100' : '0';
// break; break;
// case 'uw-bus-tunnel': case 'uw-bus-tunnel':
// const busCommand = event.data.payload; const busCommand = event.data.payload;
// this.eventBus.send( this.eventBus.send(
// busCommand.action, busCommand.action,
// busCommand.config, busCommand.config,
// { {
// ...busCommand?.context, ...busCommand?.context,
// borderCrossings: { borderCrossings: {
// ...busCommand?.context?.borderCrossings, ...busCommand?.context?.borderCrossings,
// iframe: true, iframe: true,
// } }
// } }
// ); );
// break; break;
// case 'uwui-get-role': case 'uwui-get-role':
// this.sendToIframeLowLevel('uwui-set-role', {role: this.isGlobal ? 'global' : 'player'}); this.sendToIframeLowLevel('uwui-set-role', {role: this.isGlobal ? 'global' : 'player'});
// break; break;
// case 'uwui-interface-ready': case 'uwui-interface-ready':
// this.setUiVisibility(!this.isGlobal); this.setUiVisibility(!this.isGlobal);
// break; break;
// case 'uwui-hidden': case 'uwui-hidden':
// this.uiIframe.style.opacity = event.data.opacity || this.isGlobal ? '100' : '0'; this.uiIframe.style.opacity = event.data.opacity || this.isGlobal ? '100' : '0';
// break; break;
// case 'uwui-global-window-hidden': case 'uwui-global-window-hidden':
// if (!this.isGlobal) { if (!this.isGlobal) {
// return; // This shouldn't even happen in non-global windows return; // This shouldn't even happen in non-global windows
// } }
// this.setUiVisibility(false); this.setUiVisibility(false);
// this.eventBus.send('uw-restore-ui-state', {}); this.eventBus.send('uw-restore-ui-state', {});
// } }
// } }
// } }
// /** /**
// * Sends messages to iframe. Messages sent with this function _generally_ * Sends messages to iframe. Messages sent with this function _generally_
// * bypass eventBus on the receiving end. * bypass eventBus on the receiving end.
// * @param {*} action * @param {*} action
// * @param {*} payload * @param {*} payload
// * @param {*} uiURI * @param {*} uiURI
// */ */
// sendToIframeLowLevel(action, payload, uiURI = this.uiURI) { sendToIframeLowLevel(action, payload, uiURI = this.uiURI) {
// // because existence of UI is not guaranteed — UI is not shown when extension is inactive. // because existence of UI is not guaranteed — UI is not shown when extension is inactive.
// // If extension is inactive due to "player element isn't big enough to justify it", however, // If extension is inactive due to "player element isn't big enough to justify it", however,
// // we can still receive eventBus messages. // we can still receive eventBus messages.
// if (this.rootDiv && this.uiIframe) { if (this.rootDiv && this.uiIframe) {
// this.uiIframe.contentWindow?.postMessage( this.uiIframe.contentWindow?.postMessage(
// { {
// action, action,
// payload payload
// }, },
// uiURI uiURI
// ) )
// }; };
// } }
// /** /**
// * Sends message to iframe. Messages sent with this function will be routed to eventBus. * Sends message to iframe. Messages sent with this function will be routed to eventBus.
// */ */
// sendToIframe(action, actionConfig, routingData, uiURI = this.uiURI) { sendToIframe(action, actionConfig, routingData, uiURI = this.uiURI) {
// // if (routingData) { // if (routingData) {
// // if (routingData.crossedConnections?.includes(EventBusConnector.IframeBoundaryIn)) { // if (routingData.crossedConnections?.includes(EventBusConnector.IframeBoundaryIn)) {
// // console.warn('Denied message propagation. It has already crossed INTO an iframe once.'); // console.warn('Denied message propagation. It has already crossed INTO an iframe once.');
// // return; // return;
// // } // }
// // } // }
// // if (!routingData) { // if (!routingData) {
// // routingData = { }; // routingData = { };
// // } // }
// // if (!routingData.crossedConnections) { // if (!routingData.crossedConnections) {
// // routingData.crossedConnections = []; // routingData.crossedConnections = [];
// // } // }
// // routingData.crossedConnections.push(EventBusConnector.IframeBoundaryIn); // routingData.crossedConnections.push(EventBusConnector.IframeBoundaryIn);
// this.sendToIframeLowLevel( this.sendToIframeLowLevel(
// 'uw-bus-tunnel', 'uw-bus-tunnel',
// { {
// action, action,
// config: actionConfig, config: actionConfig,
// routingData routingData
// }, },
// uiURI uiURI
// ); );
// } }
/** /**
* Replaces ui config and re-inits the UI * Replaces ui config and re-inits the UI
@ -453,17 +546,14 @@ class UI {
} }
destroy() { destroy() {
if (this.extensionMenu) { this.unloadIframe();
this.extensionMenu.destroy();
}
// this.unloadIframe();
// this.eventBus.unsubscribeAll(this); this.eventBus.unsubscribeAll(this);
// // this.comms?.destroy(); // this.comms?.destroy();
// this.rootDiv?.remove(); this.rootDiv?.remove();
// delete this.uiIframe; delete this.uiIframe;
// delete this.rootDiv; delete this.rootDiv;
} }
} }

View File

@ -290,7 +290,6 @@ class PlayerData {
playerData: this, playerData: this,
uiSettings: this.videoData.settings.active.ui, uiSettings: this.videoData.settings.active.ui,
siteSettings: this.siteSettings, siteSettings: this.siteSettings,
settings: this.videoData.settings,
} }
); );
@ -591,7 +590,6 @@ class PlayerData {
playerData: this, playerData: this,
uiSettings: this.videoData.settings.active.ui, uiSettings: this.videoData.settings.active.ui,
siteSettings: this.siteSettings, siteSettings: this.siteSettings,
settings: this.videoData.settings,
} }
); );
} }

View File

@ -2,122 +2,9 @@
@import "tailwindcss/utilities"; @import "tailwindcss/utilities";
.uw-menu-root { .uw-menu-root {
@apply h-full w-full font-mono text-stone-200 text-[16px] flex flex-row; @apply h-full w-full font-mono text-stone-200 text-[16px];
pointer-events: auto;
transition: opacity 120ms ease;
&:not(.uw-visible) {
pointer-events: none;
opacity: 0;
}
&.uw-hidden {
pointer-events: none;
opacity: 0;
}
&.uw-visible {
pointer-events: auto;
opacity: 1;
}
&.uw-menu-left {
@apply justify-start;
}
&.uw-menu-right {
@apply justify-end;
}
&.uw-menu-left, &.uw-menu-right {
&.uw-menu-top {
@apply items-start;
}
&.uw-menu-ycenter {
@apply items-center;
}
&.uw-menu-bottom {
@apply items-end;
}
}
&.uw-menu-center {
@apply flex-col items-center;
&.uw-menu-top {
@apply justify-start;
}
&.uw-menu-bottom {
@apply justify-end;
}
}
* { * {
@apply font-mono text-stone-200 text-[1em]; @apply font-mono text-stone-200 text-[1em];
} }
} }
.uw-submenu {
@apply absolute flex flex-col gap-0;
opacity: 0;
pointer-events: none;
/* we'll also animate a bit */
transform: translateX(-4px);
transition: opacity 120ms ease, transform 120ms ease;
&.uw-menu-left {
@apply left-full;
}
&.uw-menu-right {
@apply right-full;
}
&.uw-menu-left, &.uw-menu-right {
&.uw-menu-top {
@apply top-1/2 -translate-y-1/2;
}
&.uw-menu-ycenter {
@apply top-0;
}
&.uw-menu-bottom {
@apply bottom-0;
}
}
&.uw-menu-center {
@apply left-1/2 -translate-x-1/2 flex-row;
&.uw-menu-top {
@apply bottom-0;
}
&.uw-menu-bottom {
@apply top-0;
}
}
* {
@apply font-mono text-stone-200 text-[1em];
}
}
.uw-menu-item, .uw-menu-trigger {
&:hover > .uw-submenu,
&:focus-within > .uw-submenu {
opacity: 1 !important;
pointer-events: auto;
transform: translateX(0);
}
}
.uw-trigger {
@apply p-4 relative block
bg-stone-950/75 backdrop-blur-[1em] backdrop-brightness-75 backdrop-saturate-50
text-nowrap
;
&:hover {
@apply text-primary-200 bg-stone-800/75;
}
}