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>
<div
v-if="settingsInitialized && uwTriggerZoneVisible && !isGlobal"
class="uw-hover uv-hover-trigger-region uw-clickable"
@ -9,13 +10,12 @@
<div>Hover to activate</div>
</div>
<!-- sss -->
<div
v-if="settingsInitialized && uwWindowVisible"
class="uw-window flex flex-column uw-clickable"
:class="{'fade-out': uwWindowFadeOut}"
@mouseenter="cancelUwWindowHide"
@mouseleave="hideUwWindow"
@mouseleave="hideUwWindow()"
>
<PlayerUIWindow
:settings="settings"
@ -76,7 +76,8 @@ export default {
origin: '*', // will be set appropriately once the first uwui-probe event is received
lastProbeTs: null,
isGlobal: false,
isGlobal: true,
disabled: false,
uiVisible: true,
debugData: {
@ -85,10 +86,17 @@ export default {
},
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: {
hasDrm: undefined,
},
saveState: {},
tabs: [
{id: 'videoSettings', label: 'Video settings', icon: 'crop'},
{id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
@ -145,25 +153,58 @@ export default {
this.handleMessage(event);
});
console.log('UW UI INITALIZED!');
this.eventBus.subscribe('uw-config-broadcast', {function: (data) => {
if (data.type === 'drm-status') {
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) => {
console.log('received set ui statW!', data, 'are we global?', this.isGlobal);
if (!this.isGlobal) {
return; // only intended for global overlay
if (data.globalUiVisible !== undefined) {
if (this.isGlobal) {
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: {
@ -180,17 +221,34 @@ export default {
* to the correct function down the line.
*/
handleMessage(event) {
if (event.data.action === 'uwui-probe') {
if (!this.site) {
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') {
this.handleBusTunnelIn(event.data.payload);
switch (event.data.action) {
case 'uwui-probe':
if (!this.site) {
this.origin = event.origin;
this.site = event.origin.split('//')[1];
}
return this.handleProbe(event.data, event.origin); // handleProbe is defined in UIProbeMixin
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() {
this.uwWindowFadeOut = false;
this.uwWindowVisible = true;
@ -198,13 +256,30 @@ export default {
// refresh 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) {
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;
},
@ -214,7 +289,7 @@ export default {
},
handleBusTunnelIn(payload) {
this.eventBus.send(payload.action, payload.config);
this.eventBus.send(payload.action, payload.config, payload.routingData);
},
selectTab(tab) {

View File

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

View File

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

View File

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

View File

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

View File

@ -11,6 +11,7 @@
<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>Note that by default, in-player UI doesn't show if player window is not big enough.</p>
</div>
</template>
@ -26,7 +27,7 @@ export default {
],
methods: {
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.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) {
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) {
console.log('sending command ....', command, commandData, context);
// execute commands we have subscriptions for
if (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'){
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 {
constructor(
interfaceId,
@ -13,13 +21,24 @@ class UI {
this.isGlobal = uiConfig.isGlobal ?? false;
// 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.eventBus = uiConfig.eventBus;
this.disablePointerEvents = false;
this.saveState = undefined;
}
async init() {
this.initIframes();
this.initMessaging();
}
initIframes() {
const random = Math.round(Math.random() * 69420);
const uwid = `uw-ultrawidify-${this.interfaceId}-root-${random}`
@ -30,10 +49,10 @@ class UI {
}
rootDiv.setAttribute('id', uwid);
rootDiv.classList.add('uw-ultrawidify-container-root');
rootDiv.style.width = "100%";
rootDiv.style.height = "100%";
rootDiv.style.position = "absolute";
rootDiv.style.zIndex = "1000";
// 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';
@ -52,12 +71,14 @@ class UI {
const iframe = document.createElement('iframe');
iframe.setAttribute('src', uiURI);
iframe.style.width = "100%";
iframe.style.height = "100%";
iframe.setAttribute("allowTransparency", 'true');
// iframe.style.width = "100%";
// iframe.style.height = "100%";
iframe.style.position = "absolute";
iframe.style.zIndex = "1000";
iframe.style.zIndex = this.isGlobal ? '90009' : '90000';
iframe.style.border = 0;
iframe.style.pointerEvents = 'none';
iframe.style.backgroundColor = 'transparent !important';
/* 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
@ -72,6 +93,9 @@ class UI {
// 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 ) {
@ -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() {
document.addEventListener('mousemove', fn, true);
if (this.isGlobal) {
this.uiIframe.contentWindow.postMessage({
action: 'set-as-global'
});
}
}
rootDiv.appendChild(iframe);
}
initMessaging() {
// subscribe to events coming back to us. Unsubscribe if iframe vanishes.
const messageHandlerFn = (message) => {
if (!iframe?.contentWindow) {
if (!this.uiIframe?.contentWindow) {
window.removeEventListener('message', messageHandlerFn);
return;
}
@ -126,20 +150,50 @@ class UI {
this.eventBus.subscribe(
'uw-config-broadcast',
{
function: (config) => {
this.sendToIframe('uw-config-broadcast', config, uiURI);
function: (config, routingData) => {
this.sendToIframe('uw-config-broadcast', config, routingData);
}
}
);
this.eventBus.subscribe(
'uw-set-ui-state',
{
function: (config) => {
console.log('hello');
this.sendToIframe('uw-set-ui-state', config, uiURI);
function: (config, routingData) => {
if (config.globalUiVisible !== undefined) {
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() {
@ -161,37 +215,88 @@ class UI {
*/
handleMessage(event) {
if (event.origin === this.extensionBase) {
if (event.data.action === 'uwui-clickable') {
if (event.data.ts < this.lastProbeResponseTs) {
return;
}
this.lastProbeResponseTs = event.data.ts;
this.uiIframe.style.pointerEvents = event.data.clickable ? 'auto' : 'none';
} else if (event.data.action === 'uw-bus-tunnel') {
const busCommand = event.data.payload;
this.eventBus.send(busCommand.action, busCommand.config);
switch(event.data.action) {
case 'uwui-clickable':
if (event.data.ts < this.lastProbeResponseTs) {
return;
}
if (this.disablePointerEvents) {
return;
}
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.
// If extension is inactive due to "player element isn't big enough to justify it", however,
// we can still receive eventBus messages.
if (this.element && this.uiIframe) {
this.uiIframe.contentWindow.postMessage(
this.uiIframe.contentWindow?.postMessage(
{
action: 'uw-bus-tunnel',
payload: {action, config: actionConfig}
action,
payload
},
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
* @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)
{ from: 'icons', to: 'icons', ignore: ['icon.xcf'] },
{ 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: 'manifest.json',