UI/global UI mostly works

This commit is contained in:
Tamius Han 2024-06-05 01:08:50 +02:00
parent cb334aab0d
commit e0bb25466d
13 changed files with 278 additions and 70 deletions

View File

@ -1,4 +1,5 @@
<template> <template>
<div <div
v-if="settingsInitialized && uwTriggerZoneVisible && !isGlobal" v-if="settingsInitialized && uwTriggerZoneVisible && !isGlobal"
class="uw-hover uv-hover-trigger-region uw-clickable" class="uw-hover uv-hover-trigger-region uw-clickable"
@ -9,13 +10,12 @@
<div>Hover to activate</div> <div>Hover to activate</div>
</div> </div>
<!-- sss -->
<div <div
v-if="settingsInitialized && uwWindowVisible" v-if="settingsInitialized && uwWindowVisible"
class="uw-window flex flex-column uw-clickable" class="uw-window flex flex-column uw-clickable"
:class="{'fade-out': uwWindowFadeOut}" :class="{'fade-out': uwWindowFadeOut}"
@mouseenter="cancelUwWindowHide" @mouseenter="cancelUwWindowHide"
@mouseleave="hideUwWindow" @mouseleave="hideUwWindow()"
> >
<PlayerUIWindow <PlayerUIWindow
:settings="settings" :settings="settings"
@ -76,7 +76,8 @@ export default {
origin: '*', // will be set appropriately once the first uwui-probe event is received origin: '*', // will be set appropriately once the first uwui-probe event is received
lastProbeTs: null, lastProbeTs: null,
isGlobal: false, isGlobal: true,
disabled: false,
uiVisible: true, uiVisible: true,
debugData: { debugData: {
@ -85,10 +86,17 @@ export default {
}, },
debugDataPrettified: '', debugDataPrettified: '',
// in global overlay, this property is used to determine
// if closing the window should emit uw-set-ui-state
// event on eventBus
showPlayerUIAfterClose: false,
statusFlags: { statusFlags: {
hasDrm: undefined, hasDrm: undefined,
}, },
saveState: {},
tabs: [ tabs: [
{id: 'videoSettings', label: 'Video settings', icon: 'crop'}, {id: 'videoSettings', label: 'Video settings', icon: 'crop'},
{id: 'playerDetection', label: 'Player detection', icon: 'television-play'}, {id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
@ -145,25 +153,58 @@ export default {
this.handleMessage(event); this.handleMessage(event);
}); });
console.log('UW UI INITALIZED!');
this.eventBus.subscribe('uw-config-broadcast', {function: (data) => { this.eventBus.subscribe('uw-config-broadcast', {function: (data) => {
if (data.type === 'drm-status') { if (data.type === 'drm-status') {
this.statusFlags.hasDrm = data.hasDrm; this.statusFlags.hasDrm = data.hasDrm;
} }
}}); }});
this.eventBus.subscribe('set-as-global', {function: (data) => {
this.isGlobal = true;
}});
this.eventBus.subscribe('uw-set-ui-state', { function: (data) => { this.eventBus.subscribe('uw-set-ui-state', { function: (data) => {
console.log('received set ui statW!', data, 'are we global?', this.isGlobal); if (data.globalUiVisible !== undefined) {
if (!this.isGlobal) { if (this.isGlobal) {
return; // only intended for global overlay if (data.globalUiVisible) {
this.showUwWindow();
} else {
this.hideUwWindow(true);
}
// this.showPlayerUIAfterClose = data.showPlayerUIAfterClose;
} else {
// non global UIs are hidden while global overlay
// is visible and vice versa
// this.disabled = data.globalUiVisible;
this.saveState = {
uwWindowVisible: this.uwWindowVisible,
uwWindowFadeOutDisabled: this.uwWindowFadeOutDisabled,
uwWindowFadeOut: this.uwWindowFadeOut
};
this.uwWindowFadeOutDisabled = false;
this.hideUwWindow(true);
}
} }
this.uiVisible = data.uiVisible;
}}); }});
this.eventBus.subscribe(
'uw-restore-ui-state',
{
function: (data) => {
if (this.saveState) {
if (this.saveState.uwWindowVisible) {
this.showUwWindow();
}
this.uwWindowFadeOutDisabled = this.saveState.uwWindowFadeOutDisabled;
this.uwWindowFadeOut = this.saveState.uwWindowFadeOut;
}
this.saveState = {};
}
}
);
this.sendToParentLowLevel('uwui-get-role', null);
this.sendToParentLowLevel('uwui-get-theme', null);
//
}, },
methods: { methods: {
@ -180,17 +221,34 @@ export default {
* to the correct function down the line. * to the correct function down the line.
*/ */
handleMessage(event) { handleMessage(event) {
if (event.data.action === 'uwui-probe') { switch (event.data.action) {
if (!this.site) { case 'uwui-probe':
this.origin = event.origin; if (!this.site) {
this.site = event.origin.split('//')[1]; this.origin = event.origin;
} this.site = event.origin.split('//')[1];
this.handleProbe(event.data, event.origin); // handleProbe is defined in UIProbeMixin }
} else if (event.data.action === 'uw-bus-tunnel') { return this.handleProbe(event.data, event.origin); // handleProbe is defined in UIProbeMixin
this.handleBusTunnelIn(event.data.payload); case 'uw-bus-tunnel':
return this.handleBusTunnelIn(event.data.payload);
case 'uwui-set-role':
this.isGlobal = event.data.payload.role === 'global';
this.sendToParentLowLevel('uwui-interface-ready', true);
break;
} }
}, },
/**
* Sends message to parent _without_ using event bus.
*/
sendToParentLowLevel(action, payload, lowLevelExtras = {}) {
window.parent.postMessage(
{
action, payload, ...lowLevelExtras
},
'*'
);
},
showUwWindow() { showUwWindow() {
this.uwWindowFadeOut = false; this.uwWindowFadeOut = false;
this.uwWindowVisible = true; this.uwWindowVisible = true;
@ -198,13 +256,30 @@ export default {
// refresh DRM status // refresh DRM status
this.eventBus.send('get-drm-status'); this.eventBus.send('get-drm-status');
// if (this.isGlobal) {
// this.sendToParentLowLevel('uwui-clickable', undefined, {clickable: true});
// }
}, },
hideUwWindow() { hideUwWindow(skipTimeout = false) {
if (this.uwWindowFadeOutDisabled) { if (this.uwWindowFadeOutDisabled) {
return; return;
} }
this.uwWindowCloseTimeout = setTimeout(() => this.uwWindowVisible = false, 1100);
const timeout = skipTimeout ? 0 : 1100;
this.uwWindowCloseTimeout = setTimeout(
() => {
this.uwWindowVisible = false;
// Global UI has some extra housekeeping to do when window gets hidden
if (this.isGlobal) {
this.sendToParentLowLevel('uwui-global-window-hidden', {});
}
},
timeout
);
this.uwWindowFadeOut = true; this.uwWindowFadeOut = true;
}, },
@ -214,7 +289,7 @@ export default {
}, },
handleBusTunnelIn(payload) { handleBusTunnelIn(payload) {
this.eventBus.send(payload.action, payload.config); this.eventBus.send(payload.action, payload.config, payload.routingData);
}, },
selectTab(tab) { selectTab(tab) {

View File

@ -149,7 +149,6 @@ export default {
'set-current-site', 'set-current-site',
{ {
function: (config, context) => { function: (config, context) => {
console.log('Received set-current-site', config);
if (this.site) { if (this.site) {
if (!this.site.host) { if (!this.site.host) {
// dunno why this fix is needed, but sometimes it is // dunno why this fix is needed, but sometimes it is
@ -251,7 +250,6 @@ export default {
}, },
processReceivedMessage(message, port) { processReceivedMessage(message, port) {
this.logger.log('info', 'popup', '[popup::processReceivedMessage] received message:', message) this.logger.log('info', 'popup', '[popup::processReceivedMessage] received message:', message)
console.info('[popup::processReceivedMessage] got message:', message);
if (message.command === 'set-current-site'){ if (message.command === 'set-current-site'){
if (this.site) { if (this.site) {

View File

@ -2,6 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="color-scheme" content="light dark">
<title>Ultrawidify - Content Script User Interface (global overlay)</title> <title>Ultrawidify - Content Script User Interface (global overlay)</title>
<!-- <link rel="stylesheet" href="csui.css"> --> <!-- <link rel="stylesheet" href="csui.css"> -->
<% if (NODE_ENV === 'development') { %> <% if (NODE_ENV === 'development') { %>

View File

@ -2,13 +2,11 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="color-scheme" content="dark">
<title>Ultrawidify - Content Script User Interface (in-player overlay)</title> <title>Ultrawidify - Content Script User Interface (in-player overlay)</title>
<!-- <link rel="stylesheet" href="csui.css"> --> <!-- <link rel="stylesheet" href="csui.css"> -->
<% if (NODE_ENV === 'development') { %>
<!-- Load some resources only in development environment -->
<% } %>
</head> </head>
<body class="uw-ultrawidify-container-root"> <body class="uw-ultrawidify-container-root" style="background-color: transparent">
<div id="app"></div> <div id="app"></div>
<script src="csui.js"></script> <script src="csui.js"></script>
</body> </body>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="color-scheme" content="light">
<title>Ultrawidify - Content Script User Interface (in-player overlay)</title>
<!-- <link rel="stylesheet" href="csui.css"> -->
</head>
<body class="uw-ultrawidify-container-root" style="background-color: transparent">
<div id="app"></div>
<script src="csui.js"></script>
</body>
</html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Ultrawidify - Content Script User Interface (in-player overlay)</title>
<!-- <link rel="stylesheet" href="csui.css"> -->
</head>
<body class="uw-ultrawidify-container-root" style="background-color: transparent">
<div id="app"></div>
<script src="csui.js"></script>
</body>
</html>

View File

@ -3,14 +3,15 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Title</title> <title>Title</title>
<link rel="stylesheet" href="popup.css"> <!-- <link rel="stylesheet" href="popup.css"> -->
<% if (NODE_ENV === 'development') { %> <% if (NODE_ENV === 'development') { %>
<!-- Load some resources only in development environment --> <!-- Load some resources only in development environment -->
<% } %> <% } %>
</head> </head>
<body <body
style="width: 100%; height: 100%; overflow-x: hidden; min-width: 750px"> style="width: 100%; height: 100%; overflow-x: hidden; min-width: 750px"
>
<div id="app"> <div id="app">
</div> </div>

View File

@ -24,6 +24,7 @@
height: 100%; height: 100%;
top: 0; top: 0;
left: 0; left: 0;
background-color: none transparent !important;
// Ensure we're display:block // Ensure we're display:block
display: block; display: block;

View File

@ -11,6 +11,7 @@
<p></p> <p></p>
<p></p> <p></p>
<p>In-player UI should show and hide automatically as you start or stop moving your mouse inside the player window.</p> <p>In-player UI should show and hide automatically as you start or stop moving your mouse inside the player window.</p>
<p>Note that by default, in-player UI doesn't show if player window is not big enough.</p>
</div> </div>
</template> </template>
@ -26,7 +27,7 @@ export default {
], ],
methods: { methods: {
showInPlayerUi() { showInPlayerUi() {
this.eventBus.send('uw-set-ui-state', {uiVisible: true}, {comms: {forwardTo: 'active'}}); this.eventBus.send('uw-set-ui-state', {globalUiVisible: true}, {comms: {forwardTo: 'active'}});
} }
} }
} }

View File

@ -110,7 +110,9 @@ export default class UWContent {
this.logger.log('info', 'debug', "[uw.js::setup] KeyboardHandler initiated."); this.logger.log('info', 'debug', "[uw.js::setup] KeyboardHandler initiated.");
this.globalUi = new UI('ultrawidify-global-ui', {eventBus: this.eventBus, isGlobal: true}) this.globalUi = new UI('ultrawidify-global-ui', {eventBus: this.eventBus, isGlobal: true});
this.globalUi.enable();
this.globalUi.setUiVisibility(false);
} catch (e) { } catch (e) {
console.error('Ultrawidify: failed to start extension. Error:', e) console.error('Ultrawidify: failed to start extension. Error:', e)

View File

@ -98,7 +98,6 @@ export default class EventBus {
} }
send(command: string, commandData: any, context?: EventBusContext) { send(command: string, commandData: any, context?: EventBusContext) {
console.log('sending command ....', command, commandData, context);
// execute commands we have subscriptions for // execute commands we have subscriptions for
if (this.commands?.[command]) { if (this.commands?.[command]) {
for (const eventBusCommand of this.commands[command]) { for (const eventBusCommand of this.commands[command]) {

View File

@ -1,7 +1,15 @@
import { EventBusConnector } from '../EventBus';
if (process.env.CHANNEL !== 'stable'){ if (process.env.CHANNEL !== 'stable'){
console.info("Loading: UI"); 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
};
class UI { class UI {
constructor( constructor(
interfaceId, interfaceId,
@ -13,13 +21,24 @@ class UI {
this.isGlobal = uiConfig.isGlobal ?? false; this.isGlobal = uiConfig.isGlobal ?? false;
// 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.uiURI = chrome.runtime.getURL('/csui/csui.html'); const preferredScheme = window.getComputedStyle( document.body ,null).getPropertyValue('color-scheme');
const csuiVersion = csuiVersions[preferredScheme] ?? csuiVersions.normal;
this.uiURI = chrome.runtime.getURL(`/csui/${csuiVersion}.html`);
this.extensionBase = chrome.runtime.getURL('').replace(/\/$/, ""); this.extensionBase = chrome.runtime.getURL('').replace(/\/$/, "");
this.eventBus = uiConfig.eventBus; this.eventBus = uiConfig.eventBus;
this.disablePointerEvents = false;
this.saveState = undefined;
} }
async init() { async init() {
this.initIframes();
this.initMessaging();
}
initIframes() {
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}`
@ -30,10 +49,10 @@ class UI {
} }
rootDiv.setAttribute('id', uwid); rootDiv.setAttribute('id', uwid);
rootDiv.classList.add('uw-ultrawidify-container-root'); rootDiv.classList.add('uw-ultrawidify-container-root');
rootDiv.style.width = "100%"; // rootDiv.style.width = "100%";
rootDiv.style.height = "100%"; // rootDiv.style.height = "100%";
rootDiv.style.position = "absolute"; rootDiv.style.position = this.isGlobal ? "fixed" : "absolute";
rootDiv.style.zIndex = "1000"; rootDiv.style.zIndex = this.isGlobal ? '90009' : '90000';
rootDiv.style.border = 0; rootDiv.style.border = 0;
rootDiv.style.top = 0; rootDiv.style.top = 0;
rootDiv.style.pointerEvents = 'none'; rootDiv.style.pointerEvents = 'none';
@ -52,12 +71,14 @@ class UI {
const iframe = document.createElement('iframe'); const iframe = document.createElement('iframe');
iframe.setAttribute('src', uiURI); iframe.setAttribute('src', uiURI);
iframe.style.width = "100%"; iframe.setAttribute("allowTransparency", 'true');
iframe.style.height = "100%"; // iframe.style.width = "100%";
// iframe.style.height = "100%";
iframe.style.position = "absolute"; iframe.style.position = "absolute";
iframe.style.zIndex = "1000"; iframe.style.zIndex = this.isGlobal ? '90009' : '90000';
iframe.style.border = 0; iframe.style.border = 0;
iframe.style.pointerEvents = 'none'; iframe.style.pointerEvents = 'none';
iframe.style.backgroundColor = 'transparent !important';
/* so we have a problem: we want iframe to be clickthrough everywhere except /* so we have a problem: we want iframe to be clickthrough everywhere except
* on our actual overlay. There's no nice way of doing that, so we need some * on our actual overlay. There's no nice way of doing that, so we need some
@ -72,6 +93,9 @@ class UI {
// set uiIframe for handleMessage // set uiIframe for handleMessage
this.uiIframe = iframe; this.uiIframe = iframe;
// set not visible by default
this.setUiVisibility(false);
const fn = (event) => { const fn = (event) => {
// remove self on fucky wuckies // remove self on fucky wuckies
if (!iframe?.contentWindow ) { if (!iframe?.contentWindow ) {
@ -95,21 +119,21 @@ class UI {
); );
} }
// 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() { iframe.onload = function() {
document.addEventListener('mousemove', fn, true); document.addEventListener('mousemove', fn, true);
if (this.isGlobal) {
this.uiIframe.contentWindow.postMessage({
action: 'set-as-global'
});
}
} }
rootDiv.appendChild(iframe); rootDiv.appendChild(iframe);
}
initMessaging() {
// subscribe to events coming back to us. Unsubscribe if iframe vanishes. // subscribe to events coming back to us. Unsubscribe if iframe vanishes.
const messageHandlerFn = (message) => { const messageHandlerFn = (message) => {
if (!iframe?.contentWindow) { if (!this.uiIframe?.contentWindow) {
window.removeEventListener('message', messageHandlerFn); window.removeEventListener('message', messageHandlerFn);
return; return;
} }
@ -126,20 +150,50 @@ class UI {
this.eventBus.subscribe( this.eventBus.subscribe(
'uw-config-broadcast', 'uw-config-broadcast',
{ {
function: (config) => { function: (config, routingData) => {
this.sendToIframe('uw-config-broadcast', config, uiURI); this.sendToIframe('uw-config-broadcast', config, routingData);
} }
} }
); );
this.eventBus.subscribe( this.eventBus.subscribe(
'uw-set-ui-state', 'uw-set-ui-state',
{ {
function: (config) => { function: (config, routingData) => {
console.log('hello'); if (config.globalUiVisible !== undefined) {
this.sendToIframe('uw-set-ui-state', config, uiURI); if (this.isGlobal) {
this.setUiVisibility(config.globalUiVisible);
} else {
this.setUiVisibility(!config.globalUiVisible);
}
}
this.sendToIframe('uw-set-ui-state', {...config, isGlobal: this.isGlobal}, routingData);
} }
} }
) )
this.eventBus.subscribe(
'uw-restore-ui-state', {
function: (config, routingData) => {
if (!this.isGlobal) {
this.setUiVisibility(true);
this.sendToIframe('uw-restore-ui-state', config, routingData);
}
}
}
)
}
setUiVisibility(visible) {
if (visible) {
this.element.style.width = '100%';
this.element.style.height = '100%';
this.uiIframe.style.width = '100%';
this.uiIframe.style.height = '100%';
} else {
this.element.style.width = '0px';
this.element.style.height = '0px';
this.uiIframe.style.width = '0px';
this.uiIframe.style.height = '0px';
}
} }
async enable() { async enable() {
@ -161,37 +215,88 @@ class UI {
*/ */
handleMessage(event) { handleMessage(event) {
if (event.origin === this.extensionBase) { if (event.origin === this.extensionBase) {
if (event.data.action === 'uwui-clickable') { switch(event.data.action) {
if (event.data.ts < this.lastProbeResponseTs) { case 'uwui-clickable':
return; if (event.data.ts < this.lastProbeResponseTs) {
} return;
this.lastProbeResponseTs = event.data.ts; }
this.uiIframe.style.pointerEvents = event.data.clickable ? 'auto' : 'none'; if (this.disablePointerEvents) {
} else if (event.data.action === 'uw-bus-tunnel') { return;
const busCommand = event.data.payload; }
this.eventBus.send(busCommand.action, busCommand.config); this.lastProbeResponseTs = event.data.ts;
this.uiIframe.style.pointerEvents = event.data.clickable ? 'auto' : 'none';
break;
case 'uw-bus-tunnel':
const busCommand = event.data.payload;
this.eventBus.send(busCommand.action, busCommand.config, busCommand.routingData);
break;
case 'uwui-get-role':
this.sendToIframeLowLevel('uwui-set-role', {role: this.isGlobal ? 'global' : 'player'});
break;
case 'uwui-interface-ready':
this.setUiVisibility(!this.isGlobal);
break;
case 'uwui-global-window-hidden':
if (!this.isGlobal) {
return; // This shouldn't even happen in non-global windows
}
this.setUiVisibility(false);
this.eventBus.send('uw-restore-ui-state', {});
} }
} }
} }
/** /**
* Sends message to iframe * Sends messages to iframe. Messages sent with this function _generally_
* bypass eventBus on the receiving end.
* @param {*} action
* @param {*} payload
* @param {*} uiURI
*/ */
sendToIframe(action, actionConfig, 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.element && this.uiIframe) { if (this.element && this.uiIframe) {
this.uiIframe.contentWindow.postMessage( this.uiIframe.contentWindow?.postMessage(
{ {
action: 'uw-bus-tunnel', action,
payload: {action, config: actionConfig} payload
}, },
uiURI uiURI
) )
}; };
} }
/**
* Sends message to iframe. Messages sent with this function will be routed to eventbus.
*/
sendToIframe(action, actionConfig, routingData, uiURI = this.uiURI) {
// if (routingData) {
// if (routingData.crossedConnections?.includes(EventBusConnector.IframeBoundaryIn)) {
// console.warn('Denied message propagation. It has already crossed INTO an iframe once.');
// return;
// }
// }
// if (!routingData) {
// routingData = { };
// }
// if (!routingData.crossedConnections) {
// routingData.crossedConnections = [];
// }
// routingData.crossedConnections.push(EventBusConnector.IframeBoundaryIn);
this.sendToIframeLowLevel(
'uw-bus-tunnel',
{
action,
config: actionConfig,
routingData
},
uiURI
);
}
/** /**
* Replaces ui config and re-inits the UI * Replaces ui config and re-inits the UI
* @param {*} newUiConfig * @param {*} newUiConfig

View File

@ -130,7 +130,9 @@ const config = {
// (TODO: check if this copy is even necessary — /icons has same content as /res/icons) // (TODO: check if this copy is even necessary — /icons has same content as /res/icons)
{ from: 'icons', to: 'icons', ignore: ['icon.xcf'] }, { from: 'icons', to: 'icons', ignore: ['icon.xcf'] },
{ from: 'csui/csui-popup.html', to: 'csui/csui-popup.html', transform: transformHtml }, { from: 'csui/csui-popup.html', to: 'csui/csui-popup.html', transform: transformHtml },
{ from: 'csui/csui.html', to: 'csui/csui.html', transform: transformHtml }, { from: 'csui/csui-overlay-normal.html', to: 'csui/csui.html', transform: transformHtml },
{ from: 'csui/csui-overlay-dark.html', to: 'csui/csui-dark.html', transform: transformHtml },
{ from: 'csui/csui-overlay-light.html', to: 'csui/csui-light.html', transform: transformHtml },
// { from: 'install/first-time/first-time.html', to: 'install/first-time/first-time.html', transform: transformHtml}, // { from: 'install/first-time/first-time.html', to: 'install/first-time/first-time.html', transform: transformHtml},
{ {
from: 'manifest.json', from: 'manifest.json',