Finish syncing UI with extension state

Add active mode indicators to popup. Add handling of locked zoom & alignment indicator to both popup and UI. Improve message passing through Comms.
This commit is contained in:
Tamius Han 2026-01-28 21:49:51 +01:00
parent 8af9f2cedf
commit 822d069b82
15 changed files with 255 additions and 57 deletions

View File

@ -4,7 +4,8 @@ enum VideoAlignmentType {
Right = 2, Right = 2,
Top = 3, Top = 3,
Bottom = 4, Bottom = 4,
Default = -1 Default = -1,
Custom = -2,
}; };
export default VideoAlignmentType; export default VideoAlignmentType;

View File

@ -1,3 +1,4 @@
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum';
import { Ar } from '@src/common/interfaces/ArInterface'; import { Ar } from '@src/common/interfaces/ArInterface';
import { Stretch } from '@src/common/interfaces/StretchInterface'; import { Stretch } from '@src/common/interfaces/StretchInterface';
@ -6,6 +7,12 @@ export interface ScalingParamsBroadcast {
x: number, x: number,
y: number y: number
}, },
videoAlignment: {
x: VideoAlignmentType,
y: VideoAlignmentType,
xPos?: number,
yPos?: number,
}
lastAr: Ar, lastAr: Ar,
manualZoom: boolean, manualZoom: boolean,
stretch: Stretch, stretch: Stretch,

View File

@ -1,5 +1,5 @@
import Debug from '@src/ext/conf/Debug'; import Debug from '@src/ext/conf/Debug';
import CommsClient from '@src/ext/module/comms/CommsClient'; import CommsClient, { CommsOrigin } from '@src/ext/module/comms/CommsClient';
import EventBus from '@src/ext/module/EventBus'; import EventBus from '@src/ext/module/EventBus';
import KeyboardHandler from '@src/ext/module/kbm/KeyboardHandler'; import KeyboardHandler from '@src/ext/module/kbm/KeyboardHandler';
import { ComponentLogger } from '@src/ext/module/logging/ComponentLogger'; import { ComponentLogger } from '@src/ext/module/logging/ComponentLogger';
@ -82,7 +82,7 @@ export default class UWContent {
this.siteSettings = this.settings.getSiteSettings({site: window.location.hostname, isIframe: this.isIframe, parentHostname: this.parentHostname}); this.siteSettings = this.settings.getSiteSettings({site: window.location.hostname, isIframe: this.isIframe, parentHostname: this.parentHostname});
} }
this.eventBus = new EventBus({name: 'content-script'}); this.eventBus = new EventBus({name: 'content-script', commsOrigin: CommsOrigin.ContentScript});
this.eventBus.subscribe( this.eventBus.subscribe(
'uw-restart', 'uw-restart',
{ {

View File

@ -7,6 +7,7 @@ import CommsServer from '@src/ext/module/comms/CommsServer';
import BrowserDetect from '@src/ext/conf/BrowserDetect'; import BrowserDetect from '@src/ext/conf/BrowserDetect';
import { HostInfo } from '@src/common/interfaces/HostData.interface'; import { HostInfo } from '@src/common/interfaces/HostData.interface';
import { ExtensionEnvironment } from '@src/common/interfaces/SettingsInterface'; import { ExtensionEnvironment } from '@src/common/interfaces/SettingsInterface';
import { CommsOrigin } from '@src/ext/module/comms/CommsClient';
const BASE_LOGGING_STYLES = { const BASE_LOGGING_STYLES = {
@ -81,7 +82,7 @@ export default class UWServer {
this.settings = new Settings({logAggregator: this.logAggregator}); this.settings = new Settings({logAggregator: this.logAggregator});
await this.settings.init(); await this.settings.init();
this.eventBus = new EventBus({isUWServer: true}); this.eventBus = new EventBus({isUWServer: true, commsOrigin: CommsOrigin.Server});
this.eventBus.subscribeMulti(this.eventBusCommands, this); this.eventBus.subscribeMulti(this.eventBusCommands, this);
@ -105,6 +106,11 @@ export default class UWServer {
//#region CSS management //#region CSS management
async injectCss(css, sender) { async injectCss(css, sender) {
if (!sender?.tab?.id) {
// console.warn('invalid injectCss received!');
return;
}
this.logger.info('injectCss', 'Trying to inject CSS into tab', sender.tab.id, ', frameId:', sender.frameId, 'css:\n', css) this.logger.info('injectCss', 'Trying to inject CSS into tab', sender.tab.id, ', frameId:', sender.frameId, 'css:\n', css)
if (!css) { if (!css) {
return; return;
@ -125,6 +131,11 @@ export default class UWServer {
} }
} }
async removeCss(css, sender) { async removeCss(css, sender) {
if (!sender?.tab) {
// console.warn('invalid removeCss received!');
return;
}
try { try {
await chrome.scripting.removeCSS({ await chrome.scripting.removeCSS({
target: { target: {
@ -141,6 +152,10 @@ export default class UWServer {
} }
} }
async replaceCss(oldCss, newCss, sender) { async replaceCss(oldCss, newCss, sender) {
if (!sender?.tab) {
// console.warn('invalid replaceCss received!');
return;
}
if (oldCss !== newCss) { if (oldCss !== newCss) {
this.removeCss(oldCss, sender); this.removeCss(oldCss, sender);
this.injectCss(newCss, sender); this.injectCss(newCss, sender);
@ -197,6 +212,10 @@ export default class UWServer {
} }
registerVideo(sender) { registerVideo(sender) {
if (!sender?.tab?.url) {
// console.warn('invalid registerVideo received!');
return;
}
this.logger.info('registerVideo', 'Registering video.\nsender:', sender); this.logger.info('registerVideo', 'Registering video.\nsender:', sender);
const tabHostname = this.extractHostname(sender.tab.url); const tabHostname = this.extractHostname(sender.tab.url);
@ -235,6 +254,11 @@ export default class UWServer {
} }
unregisterVideo(sender) { unregisterVideo(sender) {
if (!sender?.tab) {
// console.warn('invalid unregisterVideo received!');
return;
}
this.logger.info('unregisterVideo', 'Unregistering video.\nsender:', sender); this.logger.info('unregisterVideo', 'Unregistering video.\nsender:', sender);
if (this.videoTabs[sender.tab.id]) { if (this.videoTabs[sender.tab.id]) {
if ( Object.keys(this.videoTabs[sender.tab.id].frames).length <= 1) { if ( Object.keys(this.videoTabs[sender.tab.id].frames).length <= 1) {
@ -254,6 +278,10 @@ export default class UWServer {
} }
async getCurrentSite(sender: Runtime.MessageSender) { async getCurrentSite(sender: Runtime.MessageSender) {
// if (!sender?.tab) {
// console.warn('invalid unregisterVideo received!');
// return;
// }
const site = await this.getVideoTab(); const site = await this.getVideoTab();
// Don't propagate 'INVALID SITE' to the popup. // Don't propagate 'INVALID SITE' to the popup.

View File

@ -24,7 +24,7 @@ export default class EventBus {
// private uiUri = window.location.href; // private uiUri = window.location.href;
constructor(options?: {isUWServer?: boolean, name?: string, commsOrigin?: CommsOrigin}) { constructor(options?: {isUWServer?: boolean, name?: string, commsOrigin: CommsOrigin}) {
if (!options?.isUWServer) { if (!options?.isUWServer) {
this.setupIframeTunnelling(); this.setupIframeTunnelling();
} }
@ -119,11 +119,16 @@ export default class EventBus {
const i = this.lastExecutedCommandIndex++ % this.lastExecutedCommandIds.length; const i = this.lastExecutedCommandIndex++ % this.lastExecutedCommandIds.length;
this.lastExecutedCommandIds[i] = context.commandId; this.lastExecutedCommandIds[i] = context.commandId;
} }
if (!context.origin) {
context.origin = this.commsOrigin;
}
if ( if (
this.comms this.comms
&& context?.origin !== CommsOrigin.Server && ( // ensure each message only enters commsServer once!
&& !context?.borderCrossings?.commsServer this.commsOrigin === context.origin // if these two differ, we already sent that message through Comms once,
|| this.commsOrigin === CommsOrigin.Server // CommsServer needs to forward everything, otherwise messages stop on
)
) { ) {
try { try {
this.comms.sendMessage({command, config: commandData, context}, context); this.comms.sendMessage({command, config: commandData, context}, context);

View File

@ -172,35 +172,47 @@ class CommsServer {
context = message.context; context = message.context;
} }
/**
* Here's how message forwarding works:
* * messages NOT originating from a content script get forwarded to content script
* * messages NOT originating from extension popup get forwarded to extension popup
*
* This way, messages originating from background script get forwarded both to
* content script as well as popup for absolutely free.
*/
forwardToContentScript:
{
if (context?.origin !== CommsOrigin.ContentScript) { if (context?.origin !== CommsOrigin.ContentScript) {
if (context?.comms.forwardTo === 'all') { if (context?.comms.forwardTo === 'all') {
return this.sendToAll(message); this.sendToAll(message);
break forwardToContentScript;
} }
if (context?.comms.forwardTo === 'active' || !context?.comms.forwardTo) { if (context?.comms.forwardTo === 'active') {
return this.sendToActive(message); this.sendToActive(message);
break forwardToContentScript;
} }
if (context?.comms.forwardTo === 'contentScript') { if (context?.comms.forwardTo === 'contentScript') {
return this.sendToFrame(message, context.tab, context.frame, context.port); this.sendToFrame(message, context.tab, context.frame, context.port);
break forwardToContentScript;
} }
}
if (context?.origin !== CommsOrigin.Popup) { this.sendToActive(message);
if (context?.comms.forwardTo === 'popup') { break forwardToContentScript;
return this.sendToPopup(message);
} }
} }
if (context?.origin !== CommsOrigin.Popup) {
this.sendToPopup(message);
}
// okay I lied! Messages originating from content script can be forwarded to // okay I lied! Messages originating from content script can be forwarded to
// content scripts running in _other_ frames of the tab // content scripts running in _other_ frames of the tab.
let forwarded = false;
if (context?.origin === CommsOrigin.ContentScript) { if (context?.origin === CommsOrigin.ContentScript) {
if (context?.comms.forwardTo === 'all-frames') { if (context?.comms.forwardTo === 'all-frames') {
forwarded = true;
this.sendToOtherFrames(message, context); this.sendToOtherFrames(message, context);
} }
} }
if (!forwarded) {
this.logger.warn('sendMessage', `message ${message.command ?? ''} was not forwarded to any destination!`, {message, context});
}
} }
/** /**

View File

@ -1,9 +1,11 @@
import AspectRatioType from '@src/common/enums/AspectRatioType.enum'; import AspectRatioType from '@src/common/enums/AspectRatioType.enum';
import StretchType from '@src/common/enums/StretchType.enum'; import StretchType from '@src/common/enums/StretchType.enum';
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum';
import { ArVariant } from '@src/common/interfaces/ArInterface'; import { ArVariant } from '@src/common/interfaces/ArInterface';
import { MenuPosition, MenuConfig, MenuItemConfig } from '@src/common/interfaces/ClientUiMenu.interface'; import { MenuPosition, MenuConfig, MenuItemConfig } from '@src/common/interfaces/ClientUiMenu.interface';
import { ScalingParamsBroadcast } from '@src/common/interfaces/ScalingParamsBroadcast.interface'; import { ScalingParamsBroadcast } from '@src/common/interfaces/ScalingParamsBroadcast.interface';
import extensionCss from '@src/main.css?inline'; import extensionCss from '@src/main.css?inline';
import { setVideoAlignmentIndicatorState } from '@src/ui/utils/video-alignment-indicator-handling';
export class ClientMenu { export class ClientMenu {
@ -425,6 +427,9 @@ export class ClientMenu {
} }
markActiveElements(scalingParams: ScalingParamsBroadcast) { markActiveElements(scalingParams: ScalingParamsBroadcast) {
if (!this._root) {
return;
}
const currentlyActiveElements = this._root.querySelectorAll('.uw-active-within, .uw-active'); const currentlyActiveElements = this._root.querySelectorAll('.uw-active-within, .uw-active');
for (const element of currentlyActiveElements) { for (const element of currentlyActiveElements) {
@ -517,5 +522,28 @@ export class ClientMenu {
zoomHSlider.value = Math.log2(scalingParams.effectiveZoom.y) as any; zoomHSlider.value = Math.log2(scalingParams.effectiveZoom.y) as any;
} }
} }
const lockXYButton = this._root.querySelector('#_button_toggle_aspect_lock');
const sliderLockBar = this._root.querySelector('#slider-lock');
if (scalingParams.effectiveZoom.x === scalingParams.effectiveZoom.y) {
lockXYButton?.classList.add('uw-linked');
lockXYButton?.classList.remove('uw-unlinked');
sliderLockBar?.classList.add('uw-linked');
sliderLockBar?.classList.remove('uw-unlinked');
} else {
lockXYButton?.classList.add('uw-unlinked');
lockXYButton?.classList.remove('uw-linked');
sliderLockBar?.classList.add('uw-unlinked');
sliderLockBar?.classList.remove('uw-linked');
}
// set alignment indicator
const videoAlignmentIndicator = this._root.querySelector('#_uw_ui_alignment_indicator') as SVGSVGElement;
if (videoAlignmentIndicator) {
setVideoAlignmentIndicatorState(videoAlignmentIndicator, scalingParams.videoAlignment.x, scalingParams.videoAlignment.y);
}
} }
} }

View File

@ -104,7 +104,7 @@ class UI {
'broadcast-scaling-params': { 'broadcast-scaling-params': {
function: (commandData: ScalingParamsBroadcast, context) => { function: (commandData: ScalingParamsBroadcast, context) => {
this.currentScalingParams = commandData; this.currentScalingParams = commandData;
this.extensionMenu?.markActiveElements(commandData); this.updateMenuStatus(commandData);
} }
} }
}); });
@ -427,7 +427,7 @@ class UI {
this.extensionMenu = new ClientMenu(menuConfig); this.extensionMenu = new ClientMenu(menuConfig);
this.extensionMenu.mount(this.uiConfig.parentElement); this.extensionMenu.mount(this.uiConfig.parentElement);
if (this.currentScalingParams) { if (this.currentScalingParams) {
this.extensionMenu.markActiveElements(this.currentScalingParams); this.updateMenuStatus(this.currentScalingParams);
} }
/** /**
@ -541,6 +541,13 @@ class UI {
} }
} }
updateMenuStatus(scalingParams: ScalingParamsBroadcast) {
this.extensionMenu?.markActiveElements(scalingParams);
// we also need to handle this here
this.uiState.lockXY = scalingParams.effectiveZoom.x === scalingParams.effectiveZoom.y;
}
createSettingsWindow(path?: string) { createSettingsWindow(path?: string) {
const iframe = document.createElement('iframe'); const iframe = document.createElement('iframe');

View File

@ -7,6 +7,7 @@ import { RunLevel } from '@src/ext/enum/run-level.enum';
import { Aard } from '@src/ext/module/aard/Aard'; import { Aard } from '@src/ext/module/aard/Aard';
import { AardLegacy } from '@src/ext/module/aard/AardLegacy'; import { AardLegacy } from '@src/ext/module/aard/AardLegacy';
import { hasDrm } from '@src/ext/module/ar-detect/DrmDetector'; import { hasDrm } from '@src/ext/module/ar-detect/DrmDetector';
import { CommsOrigin } from '@src/ext/module/comms/CommsClient';
import EventBus from '@src/ext/module/EventBus'; import EventBus from '@src/ext/module/EventBus';
import { ComponentLogger } from '@src/ext/module/logging/ComponentLogger'; import { ComponentLogger } from '@src/ext/module/logging/ComponentLogger';
import { LogAggregator } from '@src/ext/module/logging/LogAggregator'; import { LogAggregator } from '@src/ext/module/logging/LogAggregator';
@ -147,7 +148,7 @@ class VideoData {
}; };
if (!pageInfo.eventBus) { if (!pageInfo.eventBus) {
this.eventBus = new EventBus({name: 'video-data'}); this.eventBus = new EventBus({name: 'video-data', commsOrigin: CommsOrigin.ContentScript});
} else { } else {
this.eventBus = pageInfo.eventBus; this.eventBus = pageInfo.eventBus;
} }

View File

@ -64,6 +64,16 @@ class Resizer {
currentVideoSettings: any; currentVideoSettings: any;
private effectiveZoom: {x: number, y: number} = {x: 1, y: 1}; private effectiveZoom: {x: number, y: number} = {x: 1, y: 1};
private currentScalingParams: ScalingParamsBroadcast = {
effectiveZoom: {x: 1, y: 1},
lastAr: {type: AspectRatioType.Initial},
stretch: {type: StretchType.Default},
videoAlignment: {
x: VideoAlignmentType.Center,
y: VideoAlignmentType.Center
},
manualZoom: false
};
private pendingAr?: {ar: Ar, lastAr?: Ar}; private pendingAr?: {ar: Ar, lastAr?: Ar};
@ -101,6 +111,11 @@ class Resizer {
this.eventBus.send('announce-zoom', this.manualZoom ? {x: this.zoom.scale, y: this.zoom.scaleY} : this.zoom.effectiveZoom); this.eventBus.send('announce-zoom', this.manualZoom ? {x: this.zoom.scale, y: this.zoom.scaleY} : this.zoom.effectiveZoom);
} }
}], }],
'request-scaling-params': [{
function: () => {
this.eventBus.send('broadcast-scaling-params', this.currentScalingParams);
}
}],
'set-ar': [{ 'set-ar': [{
function: (config: any) => { function: (config: any) => {
this.manualZoom = false; // this only gets called from UI or keyboard shortcuts, making this action safe. this.manualZoom = false; // this only gets called from UI or keyboard shortcuts, making this action safe.
@ -506,12 +521,15 @@ class Resizer {
const translate = this.computeOffsets(stretchFactors, options?.ar); const translate = this.computeOffsets(stretchFactors, options?.ar);
this.applyCss(stretchFactors, translate); this.applyCss(stretchFactors, translate);
this.eventBus.send('broadcast-scaling-params', { this.currentScalingParams = {
effectiveZoom: {x: stretchFactors.xFactor, y: stretchFactors.yFactor}, effectiveZoom: {x: stretchFactors.xFactor, y: stretchFactors.yFactor},
videoAlignment: this.videoAlignment,
lastAr: this.lastAr, lastAr: this.lastAr,
manualZoom: this.manualZoom, manualZoom: this.manualZoom,
stretch: this.stretcher.stretch stretch: this.stretcher.stretch
} as ScalingParamsBroadcast); }
this.eventBus.send('broadcast-scaling-params', this.currentScalingParams);
} catch (e) { } catch (e) {
this.logger.warn('applyScaling', 'error while applying CSS:', e); this.logger.warn('applyScaling', 'error while applying CSS:', e);
// don't apply CSS if there's an error // don't apply CSS if there's an error

View File

@ -7,6 +7,9 @@
<ShortcutButton <ShortcutButton
v-for="(command, index) of settings?.active.commands.crop" v-for="(command, index) of settings?.active.commands.crop"
:key="index" :key="index"
:classList="{
'border !border-primary-700 text-primary-500 active-option': currentCropCommand === `${command.action}-${command.arguments.type}-${command.arguments.ratio ?? 'x'}`
}"
:label="command.label" :label="command.label"
:shortcut="getKeyboardShortcutLabel(command)" :shortcut="getKeyboardShortcutLabel(command)"
@click="execAction(command)" @click="execAction(command)"
@ -18,6 +21,9 @@
<ShortcutButton <ShortcutButton
v-for="(command, index) of settings?.active.commands.zoom" v-for="(command, index) of settings?.active.commands.zoom"
:key="index" :key="index"
:classList="{
'border !border-primary-700 text-primary-500 active-option': currentCropCommand === `${command.action}-${command.arguments.type}-${command.arguments.ratio ?? 'x'}`
}"
:label="command.label" :label="command.label"
:shortcut="getKeyboardShortcutLabel(command)" :shortcut="getKeyboardShortcutLabel(command)"
@click="execAction(command)" @click="execAction(command)"
@ -40,6 +46,10 @@
max="4" max="4"
:value="zoom.x" :value="zoom.x"
@input="changeZoom($event.target.value, 'x')" @input="changeZoom($event.target.value, 'x')"
@pointerdown="zoomUpdatesDisabled = true"
@pointerup="zoomUpdatesDisabled = false"
@pointercancel="zoomUpdatesDisabled = false"
@pointerleave="zoomUpdatesDisabled = false"
/> />
</div> </div>
</div> </div>
@ -55,6 +65,10 @@
max="4" max="4"
:value="zoom.y" :value="zoom.y"
@input="changeZoom($event.target.value, 'y')" @input="changeZoom($event.target.value, 'y')"
@pointerdown="zoomUpdatesDisabled = true"
@pointerup="zoomUpdatesDisabled = false"
@pointercancel="zoomUpdatesDisabled = false"
@pointerleave="zoomUpdatesDisabled = false"
/> />
</div> </div>
</div> </div>
@ -94,6 +108,9 @@
<ShortcutButton <ShortcutButton
v-for="(command, index) of settings?.active.commands.stretch" v-for="(command, index) of settings?.active.commands.stretch"
:key="index" :key="index"
:classList="{
'border border-primary-700 text-primary-500 active-option': currentStretchCommand === `${command.action}-${command.arguments.type}-${command.arguments.ratio ?? 'x'}`
}"
:label="command.label" :label="command.label"
:shortcut="getKeyboardShortcutLabel(command)" :shortcut="getKeyboardShortcutLabel(command)"
@click="execAction(command)" @click="execAction(command)"
@ -102,6 +119,7 @@
<h3>Align video</h3> <h3>Align video</h3>
<div <div
id="videoAlignmentController"
ref="alignmentSvgContainer" ref="alignmentSvgContainer"
class="w-full h-[12em] flex flex-row justify-center" class="w-full h-[12em] flex flex-row justify-center"
></div> ></div>
@ -119,6 +137,10 @@ import KeyboardShortcutParserMixin from '@ui/utils/mixins/KeyboardShortcutParser
import alignmentIndicatorSvg from '!!raw-loader!@ui/res/img/alignment-indicators.svg'; import alignmentIndicatorSvg from '!!raw-loader!@ui/res/img/alignment-indicators.svg';
import {setupVideoAlignmentIndicatorInteraction, setVideoAlignmentIndicatorState} from '@ui/utils/video-alignment-indicator-handling'; import {setupVideoAlignmentIndicatorInteraction, setVideoAlignmentIndicatorState} from '@ui/utils/video-alignment-indicator-handling';
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum'; import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum';
import { ScalingParamsBroadcast } from '@src/common/interfaces/ScalingParamsBroadcast.interface';
import { ArVariant } from '@src/common/interfaces/ArInterface';
import AspectRatioType from '@src/common/enums/AspectRatioType.enum';
import StretchType from '@src/common/enums/StretchType.enum';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -139,21 +161,26 @@ export default defineComponent({
return { return {
zoom: {x: 0, y: 0}, // zoom is logarithmic, so "100%" is represented as 0 instead of 1. zoom: {x: 0, y: 0}, // zoom is logarithmic, so "100%" is represented as 0 instead of 1.
zoomOptions: { zoomOptions: {
lockAr: false, lockAr: true,
}, },
alignmentSvgContainer: undefined as HTMLElement | undefined, alignmentSvgContainer: undefined as HTMLElement | undefined,
alignmentIndicatorSvg: undefined as SVGSVGElement | undefined,
currentCropCommand: '',
currentStretchCommand: '',
zoomUpdatesDisabled: false,
} }
}, },
created() { created() {
console.log('created ....'); this.eventBus.subscribeMulti({
this.eventBus.subscribe( 'broadcast-scaling-params': {
'uw-config-broadcast', function: (commandData: ScalingParamsBroadcast, context) => {
{ this.markActiveElements(commandData);
source: this,
function: (config) => this.handleConfigBroadcast(config)
} }
); }
});
}, },
mounted() { mounted() {
this.alignmentSvgContainer = this.$refs.alignmentSvgContainer as HTMLElement; this.alignmentSvgContainer = this.$refs.alignmentSvgContainer as HTMLElement;
@ -161,10 +188,10 @@ export default defineComponent({
if (this.alignmentSvgContainer) { if (this.alignmentSvgContainer) {
this.alignmentSvgContainer.innerHTML = alignmentIndicatorSvg; this.alignmentSvgContainer.innerHTML = alignmentIndicatorSvg;
const svgElement = this.alignmentSvgContainer.querySelector('svg') as SVGSVGElement; const svgElement = this.alignmentSvgContainer.querySelector('svg') as SVGSVGElement;
console.log('svg element:', svgElement); this.alignmentIndicatorSvg = svgElement;
if (svgElement) { if (svgElement) {
setupVideoAlignmentIndicatorInteraction(svgElement, (x: VideoAlignmentType, y: VideoAlignmentType) => { setupVideoAlignmentIndicatorInteraction(svgElement, (x: VideoAlignmentType, y: VideoAlignmentType) => {
console.log('clicked!');
// Update selection visually // Update selection visually
setVideoAlignmentIndicatorState(svgElement, x, y); setVideoAlignmentIndicatorState(svgElement, x, y);
@ -174,6 +201,7 @@ export default defineComponent({
} }
this.eventBus.send('get-ar'); this.eventBus.send('get-ar');
this.eventBus.send('request-scaling-params');
}, },
destroyed() { destroyed() {
this.eventBus.unsubscribeAll(this); this.eventBus.unsubscribeAll(this);
@ -221,8 +249,70 @@ export default defineComponent({
}, },
align(alignmentX: VideoAlignmentType, alignmentY: VideoAlignmentType) { align(alignmentX: VideoAlignmentType, alignmentY: VideoAlignmentType) {
this.eventBus?.send('set-alignment', {x: alignmentX, y: alignmentY}) this.eventBus?.send('set-alignment', {x: alignmentX, y: alignmentY})
},
markActiveElements(scalingParams: ScalingParamsBroadcast) {
if (this.zoomUpdatesDisabled) {
return;
}
// we only set active options when manual zoom is NOT set
if (!scalingParams.manualZoom) {
const cropCommand = scalingParams.lastAr.variant === ArVariant.Zoom ? 'set-ar-zoom' : 'set-ar';
let typeCrop;
switch (scalingParams.lastAr.type) {
case AspectRatioType.Automatic:
case AspectRatioType.AutomaticUpdate:
typeCrop = `${AspectRatioType.Automatic}-x`;
break;
case AspectRatioType.Cover:
case AspectRatioType.FitWidth:
case AspectRatioType.FitHeight:
typeCrop = `${scalingParams.lastAr.type}-x`;
break;
case AspectRatioType.Cycle:
case AspectRatioType.Initial:
typeCrop = 'non-selectable';
break;
default:
typeCrop = `${scalingParams.lastAr.type}-${scalingParams.lastAr.ratio ?? 'x'}`;
} }
this.currentCropCommand = `${cropCommand}-${typeCrop}`;
let typeStretch;
switch (scalingParams.stretch.type) {
case StretchType.FixedSource:
case StretchType.Fixed:
typeStretch = `${scalingParams.stretch.type}-${scalingParams.stretch.ratio ?? 'x'}`;
break;
case StretchType.Default:
case StretchType.NoStretch:
typeStretch = `non-selectable`;
break;
default:
typeStretch = `${scalingParams.stretch.type}-x`;
}
this.currentStretchCommand = `set-stretch-${typeStretch}`;
} else {
this.currentCropCommand = '';
this.currentStretchCommand = '';
}
if (!this.zoomUpdatesDisabled) {
this.zoom = {
x: Math.log2(scalingParams.effectiveZoom.x),
y: Math.log2(scalingParams.effectiveZoom.y)
};
}
this.zoomOptions.lockAr = scalingParams.effectiveZoom.x === scalingParams.effectiveZoom.y;
if (this.alignmentIndicatorSvg) {
setVideoAlignmentIndicatorState(this.alignmentIndicatorSvg, scalingParams.videoAlignment.x, scalingParams.videoAlignment.y);
}
}
} }
}); });
</script> </script>

View File

@ -16,7 +16,7 @@ export default defineComponent({
props: { props: {
label: String, label: String,
shortcut: String, shortcut: String,
classList: String classList: Object
} }
}); });
</script> </script>

View File

@ -448,8 +448,14 @@
stroke: transparent; stroke: transparent;
} }
.selected { .uw-active {
@apply fill-primary-400 stroke-primary-400; @apply fill-primary-400 stroke-primary-400 text-primary-400 bg-primary-400;
path {
@apply fill-primary-300 stroke-primary-100;
filter: drop-shadow(0 0 0.5rem theme('colors.primary.400'));
}
} }
} }
} }

View File

@ -1,13 +1,6 @@
<script lang="ts"> <script lang="ts">
export default { export default {
methods: { methods: {
handleConfigBroadcast(message) {
if (message.type === 'ar') {
this.resizerConfig.crop = message.config;
}
this.$nextTick( () => this.$forceUpdate() );
},
/** /**
* Sends commands to main content script in parent iframe * Sends commands to main content script in parent iframe

View File

@ -31,7 +31,7 @@ export function setVideoAlignmentIndicatorState(
y: VideoAlignmentType y: VideoAlignmentType
) { ) {
// reset all indicators // reset all indicators
svg.querySelectorAll<SVGGElement>('g').forEach(g => g.classList.remove('selected')); svg?.querySelectorAll<SVGGElement>('g').forEach(g => g.classList.remove('uw-active'));
// select the appropriate square // select the appropriate square
if (x === VideoAlignmentType.Default || y === VideoAlignmentType.Default) { if (x === VideoAlignmentType.Default || y === VideoAlignmentType.Default) {
@ -39,8 +39,10 @@ export function setVideoAlignmentIndicatorState(
} }
const gId = `${positionMap[y]}-${positionMap[x]}`; const gId = `${positionMap[y]}-${positionMap[x]}`;
const selected = svg.getElementById(gId); const selected = svg?.getElementById(gId);
if (selected) selected.classList.add('selected'); if (selected) {
selected.classList.add('uw-active');
}
} }
/** /**
@ -52,7 +54,7 @@ export function setupVideoAlignmentIndicatorInteraction(
svg: SVGSVGElement, svg: SVGSVGElement,
callback: (x: VideoAlignmentType, y: VideoAlignmentType) => void callback: (x: VideoAlignmentType, y: VideoAlignmentType) => void
) { ) {
svg.querySelectorAll<SVGGElement>('g').forEach(g => { svg?.querySelectorAll<SVGGElement>('g').forEach(g => {
g.addEventListener('click', () => { g.addEventListener('click', () => {
const [y, x] = g.id.split('-'); const [y, x] = g.id.split('-');