Compare commits
2 Commits
3d8d8eb199
...
822d069b82
| Author | SHA1 | Date | |
|---|---|---|---|
| 822d069b82 | |||
| 8af9f2cedf |
@ -9,6 +9,7 @@ enum AspectRatioType {
|
|||||||
Fixed = 4, // pre-determined aspect ratio
|
Fixed = 4, // pre-determined aspect ratio
|
||||||
Manual = 5, // ratio achieved by zooming in/zooming out
|
Manual = 5, // ratio achieved by zooming in/zooming out
|
||||||
AutomaticUpdate = 6, // set by Aard
|
AutomaticUpdate = 6, // set by Aard
|
||||||
|
Cover = 7, // replaces FitWidth and FitHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AspectRatioType;
|
export default AspectRatioType;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -1,14 +1,17 @@
|
|||||||
import { InPlayerUIOptions } from '@src/common/interfaces/SettingsInterface';
|
import { CommandInterface, InPlayerUIOptions } from '@src/common/interfaces/SettingsInterface';
|
||||||
|
|
||||||
export interface MenuItemConfig {
|
export interface MenuItemBaseConfig {
|
||||||
label: string;
|
label?: string;
|
||||||
subitems?: MenuItemConfig[];
|
subitems?: MenuItemConfig[];
|
||||||
|
command?: CommandInterface,
|
||||||
action?: () => void;
|
action?: () => void;
|
||||||
customHTML?: HTMLElement | string;
|
customHTML?: HTMLElement | string;
|
||||||
customId?: string;
|
customId?: string;
|
||||||
customClassList?: string;
|
customClassList?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MenuItemConfig = MenuItemBaseConfig & ({label: string} | {customHTML: HTMLElement | string});
|
||||||
|
|
||||||
export enum MenuPosition {
|
export enum MenuPosition {
|
||||||
TopLeft = 'top-left',
|
TopLeft = 'top-left',
|
||||||
Left = 'left',
|
Left = 'left',
|
||||||
|
|||||||
19
src/common/interfaces/ScalingParamsBroadcast.interface.ts
Normal file
19
src/common/interfaces/ScalingParamsBroadcast.interface.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum';
|
||||||
|
import { Ar } from '@src/common/interfaces/ArInterface';
|
||||||
|
import { Stretch } from '@src/common/interfaces/StretchInterface';
|
||||||
|
|
||||||
|
export interface ScalingParamsBroadcast {
|
||||||
|
effectiveZoom: {
|
||||||
|
x: number,
|
||||||
|
y: number
|
||||||
|
},
|
||||||
|
videoAlignment: {
|
||||||
|
x: VideoAlignmentType,
|
||||||
|
y: VideoAlignmentType,
|
||||||
|
xPos?: number,
|
||||||
|
yPos?: number,
|
||||||
|
}
|
||||||
|
lastAr: Ar,
|
||||||
|
manualZoom: boolean,
|
||||||
|
stretch: Stretch,
|
||||||
|
}
|
||||||
@ -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',
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,5 +1,11 @@
|
|||||||
|
import AspectRatioType from '@src/common/enums/AspectRatioType.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 { 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 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 {
|
||||||
|
|
||||||
@ -15,8 +21,6 @@ export class ClientMenu {
|
|||||||
private trigger: HTMLDivElement;
|
private trigger: HTMLDivElement;
|
||||||
private visible = false;
|
private visible = false;
|
||||||
|
|
||||||
private hiddenClass = 'uw-hidden';
|
|
||||||
|
|
||||||
private menuPositionClasses: string[] = [];
|
private menuPositionClasses: string[] = [];
|
||||||
|
|
||||||
|
|
||||||
@ -284,6 +288,7 @@ export class ClientMenu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (item.subitems) {
|
if (item.subitems) {
|
||||||
|
el.classList.add('uw-has-submenu');
|
||||||
el.appendChild(this.buildSubmenu(item.subitems));
|
el.appendChild(this.buildSubmenu(item.subitems));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -420,4 +425,125 @@ export class ClientMenu {
|
|||||||
this.root.classList.add('uw-hidden');
|
this.root.classList.add('uw-hidden');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markActiveElements(scalingParams: ScalingParamsBroadcast) {
|
||||||
|
if (!this._root) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const currentlyActiveElements = this._root.querySelectorAll('.uw-active-within, .uw-active');
|
||||||
|
|
||||||
|
for (const element of currentlyActiveElements) {
|
||||||
|
element.classList.remove('uw-active-within');
|
||||||
|
element.classList.remove('uw-active');
|
||||||
|
}
|
||||||
|
|
||||||
|
// we only set active options when manual zoom is NOT set
|
||||||
|
if (!scalingParams.manualZoom) {
|
||||||
|
const cropMenuGroup = scalingParams.lastAr.variant === ArVariant.Zoom ? 'zoom' : 'crop';
|
||||||
|
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'}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeGroupId = `#uw-${cropMenuGroup}`.replaceAll('.', '_');
|
||||||
|
const activeCropCommandId = `#uw-${cropCommand}-${typeCrop}`.replaceAll('.', '_');
|
||||||
|
|
||||||
|
const groupEl = this._root.querySelector(activeGroupId);
|
||||||
|
const optionEl = groupEl?.querySelector(activeCropCommandId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When manual zoom is in effect, optionEl (probably) won't match any of the options.
|
||||||
|
* Therefore, optionEl will be undefined. We can use this fact to sus out whether
|
||||||
|
* we're _really_ using a zoom preset, or did the user went for the sliders n stuff.
|
||||||
|
*/
|
||||||
|
if (optionEl) {
|
||||||
|
groupEl.classList.add('uw-active-within');
|
||||||
|
optionEl.classList.add('uw-active');
|
||||||
|
}
|
||||||
|
|
||||||
|
let typeStretch;
|
||||||
|
switch (scalingParams.stretch.type) {
|
||||||
|
case StretchType.FixedSource:
|
||||||
|
case StretchType.Fixed:
|
||||||
|
typeStretch = `${scalingParams.stretch.type}-${scalingParams.stretch.ratio ?? 'x'}`.replaceAll('.', '_');
|
||||||
|
break;
|
||||||
|
case StretchType.Default:
|
||||||
|
case StretchType.NoStretch:
|
||||||
|
typeStretch = `non-selectable`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
typeStretch = `${scalingParams.stretch.type}-x`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const stretchGroupEl = this._root.querySelector('#uw-stretch');
|
||||||
|
const stretchOptionEl = stretchGroupEl?.querySelector(`#uw-set-stretch-${typeStretch}`);
|
||||||
|
|
||||||
|
if (stretchOptionEl) {
|
||||||
|
stretchGroupEl.classList.add('uw-active-within');
|
||||||
|
stretchOptionEl.classList.add('uw-active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const zoomWLabel = this._root.querySelector('#zoomWidth');
|
||||||
|
const zoomWSlider = this._root.querySelector('#_input_zoom_slider') as undefined | null | (HTMLInputElement & {isInteracting?: boolean});
|
||||||
|
const zoomHLabel = this._root.querySelector('#zoomHeight');
|
||||||
|
const zoomHSlider = this._root.querySelector('#_input_zoom_slider_2') as undefined | null | (HTMLInputElement & {isInteracting?: boolean});
|
||||||
|
|
||||||
|
if (zoomWSlider?.isInteracting || zoomHSlider?.isInteracting) {
|
||||||
|
// do nothing
|
||||||
|
} else {
|
||||||
|
if (zoomWLabel) {
|
||||||
|
zoomWLabel.textContent = `${(scalingParams.effectiveZoom.x * 100).toFixed()}%`;
|
||||||
|
}
|
||||||
|
if (zoomHLabel) {
|
||||||
|
zoomHLabel.textContent = `${(scalingParams.effectiveZoom.y * 100).toFixed()}%`;
|
||||||
|
}
|
||||||
|
if (zoomWSlider) {
|
||||||
|
zoomWSlider.value = Math.log2(scalingParams.effectiveZoom.x) as any;
|
||||||
|
}
|
||||||
|
if (zoomHSlider) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import { createApp } from 'vue';
|
|||||||
import SettingsWindowContent from '@components/SettingsWindowContent.vue';
|
import SettingsWindowContent from '@components/SettingsWindowContent.vue';
|
||||||
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';
|
||||||
|
import { ScalingParamsBroadcast } from '@src/common/interfaces/ScalingParamsBroadcast.interface';
|
||||||
// import jsonEditorCSS from 'vanilla-jsoneditor/themes/jse-theme-dark.css?inline'
|
// import jsonEditorCSS from 'vanilla-jsoneditor/themes/jse-theme-dark.css?inline'
|
||||||
|
|
||||||
if (process.env.CHANNEL !== 'stable'){
|
if (process.env.CHANNEL !== 'stable'){
|
||||||
@ -44,6 +45,8 @@ class UI {
|
|||||||
private forwardedCommandIds: string[] = new Array(64);
|
private forwardedCommandIds: string[] = new Array(64);
|
||||||
private lastForwardedCommandIndex = 0;
|
private lastForwardedCommandIndex = 0;
|
||||||
|
|
||||||
|
private currentScalingParams?: ScalingParamsBroadcast;
|
||||||
|
|
||||||
private uiState = {
|
private uiState = {
|
||||||
lockXY: true,
|
lockXY: true,
|
||||||
zoom: { // log2 scale — 100% is 0
|
zoom: { // log2 scale — 100% is 0
|
||||||
@ -97,9 +100,11 @@ class UI {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
'broadcast-scaling-params': {
|
'broadcast-scaling-params': {
|
||||||
function: (commandData: {effectiveZoom: {x: number, y: number}, lastAr: Ar, stretch: Stretch}, context) => {
|
function: (commandData: ScalingParamsBroadcast, context) => {
|
||||||
console.warn('got scaling params:', commandData)
|
this.currentScalingParams = commandData;
|
||||||
|
this.updateMenuStatus(commandData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -293,28 +298,37 @@ class UI {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Crop',
|
label: 'Crop',
|
||||||
|
customId: 'uw-crop',
|
||||||
subitems: this.settings.active.commands.crop.map((x: CommandInterface) => {
|
subitems: this.settings.active.commands.crop.map((x: CommandInterface) => {
|
||||||
return {
|
return {
|
||||||
label: x.label,
|
label: x.label,
|
||||||
|
command: x,
|
||||||
|
customId: `uw-${x.action}-${x.arguments.type}-${x.arguments.ratio ?? 'x'}`.replaceAll('.', '_'),
|
||||||
action: () => this.executeCommand(x)
|
action: () => this.executeCommand(x)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Stretch',
|
label: 'Stretch',
|
||||||
|
customId: 'uw-stretch',
|
||||||
subitems: this.settings.active.commands.stretch.map((x: CommandInterface) => {
|
subitems: this.settings.active.commands.stretch.map((x: CommandInterface) => {
|
||||||
return {
|
return {
|
||||||
label: x.label,
|
label: x.label,
|
||||||
|
command: x,
|
||||||
|
customId: `uw-${x.action}-${x.arguments.type}-${x.arguments.ratio ?? 'x'}`.replaceAll('.', '_'),
|
||||||
action: () => this.executeCommand(x)
|
action: () => this.executeCommand(x)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Zoom (presets)',
|
label: 'Zoom (presets)',
|
||||||
|
customId: 'uw-zoom',
|
||||||
subitems: [
|
subitems: [
|
||||||
... this.settings.active.commands.zoom.map((x: CommandInterface) => {
|
... this.settings.active.commands.zoom.map((x: CommandInterface) => {
|
||||||
return {
|
return {
|
||||||
label: x.label,
|
label: x.label,
|
||||||
|
command: x,
|
||||||
|
customId: `uw-${x.action}-${x.arguments.type}-${x.arguments.ratio ?? 'x'}`.replaceAll('.', '_'),
|
||||||
action: () => this.executeCommand(x)
|
action: () => this.executeCommand(x)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
@ -412,6 +426,9 @@ 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) {
|
||||||
|
this.updateMenuStatus(this.currentScalingParams);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SETUP MENU INTERACTIONS
|
* SETUP MENU INTERACTIONS
|
||||||
@ -433,6 +450,24 @@ class UI {
|
|||||||
const zoomWidthLabel: HTMLDivElement = menuElement.querySelector('#zoomWidth');
|
const zoomWidthLabel: HTMLDivElement = menuElement.querySelector('#zoomWidth');
|
||||||
const zoomHeightLabel: HTMLDivElement = menuElement.querySelector('#zoomHeight');
|
const zoomHeightLabel: HTMLDivElement = menuElement.querySelector('#zoomHeight');
|
||||||
|
|
||||||
|
for (const slider of [zoomWidthSlider, zoomHeightSlider]) {
|
||||||
|
slider.addEventListener('pointerdown', function() {
|
||||||
|
(this as any).isInteracting = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
slider.addEventListener('pointerup', function() {
|
||||||
|
(this as any).isInteracting = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
slider.addEventListener('pointercancel', function() {
|
||||||
|
(this as any).isInteracting = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
slider.addEventListener('pointerleave', function() {
|
||||||
|
(this as any).isInteracting = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const updateZoomDisplayValues = () => {
|
const updateZoomDisplayValues = () => {
|
||||||
zoomWidthLabel.textContent = `${Math.round((Math.exp(this.uiState.zoom.x) * 100))}%`;
|
zoomWidthLabel.textContent = `${Math.round((Math.exp(this.uiState.zoom.x) * 100))}%`;
|
||||||
zoomHeightLabel.textContent = `${Math.round((Math.exp(this.uiState.zoom.y) * 100))}%`;
|
zoomHeightLabel.textContent = `${Math.round((Math.exp(this.uiState.zoom.y) * 100))}%`;
|
||||||
@ -506,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');
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ 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 VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum';
|
||||||
import { Ar, ArVariant } from '@src/common/interfaces/ArInterface';
|
import { Ar, ArVariant } from '@src/common/interfaces/ArInterface';
|
||||||
|
import { ScalingParamsBroadcast } from '@src/common/interfaces/ScalingParamsBroadcast.interface';
|
||||||
import { Stretch } from '@src/common/interfaces/StretchInterface';
|
import { Stretch } from '@src/common/interfaces/StretchInterface';
|
||||||
import getElementStyles from '@src/common/utils/getElementStyles';
|
import getElementStyles from '@src/common/utils/getElementStyles';
|
||||||
import Debug from '@src/ext/conf/Debug';
|
import Debug from '@src/ext/conf/Debug';
|
||||||
@ -63,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};
|
||||||
|
|
||||||
@ -100,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.
|
||||||
@ -149,6 +165,11 @@ class Resizer {
|
|||||||
}],
|
}],
|
||||||
'set-zoom': [{
|
'set-zoom': [{
|
||||||
function: (config: any) => {
|
function: (config: any) => {
|
||||||
|
if (!config || config.zoom === 1 || (config.zoom.x === 1 && config.zoom.y === 1)) {
|
||||||
|
this.manualZoom = false;
|
||||||
|
} else {
|
||||||
|
this.manualZoom = true;
|
||||||
|
}
|
||||||
this.setZoom(config?.zoom ?? {zoom: 1});
|
this.setZoom(config?.zoom ?? {zoom: 1});
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
@ -334,7 +355,7 @@ class Resizer {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setAr(ar: Ar, lastAr?: Ar) {
|
async setAr(ar: Ar, lastAr?: Ar, flags?: {manualZoom?: boolean}) {
|
||||||
if (this.destroyed || ar == null) {
|
if (this.destroyed || ar == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -375,9 +396,13 @@ class Resizer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ar.type !== AspectRatioType.AutomaticUpdate) {
|
if (ar.type !== AspectRatioType.AutomaticUpdate && !flags?.manualZoom) {
|
||||||
|
if (flags?.manualZoom) {
|
||||||
|
this.manualZoom = true;
|
||||||
|
} else {
|
||||||
this.manualZoom = false;
|
this.manualZoom = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.video.videoWidth || !this.video.videoHeight) {
|
if (!this.video.videoWidth || !this.video.videoHeight) {
|
||||||
this.logger.warn('setAr', `<rid:${this.resizerId}> Video has no width or no height. This is not allowed. Aspect ratio will not be set, and videoData will be uninitialized.`);
|
this.logger.warn('setAr', `<rid:${this.resizerId}> Video has no width or no height. This is not allowed. Aspect ratio will not be set, and videoData will be uninitialized.`);
|
||||||
@ -424,7 +449,7 @@ class Resizer {
|
|||||||
this.logger.info('setAr', `<rid:${this.resizerId}> Something wrong with ar or the player. Doing nothing.`);
|
this.logger.info('setAr', `<rid:${this.resizerId}> Something wrong with ar or the player. Doing nothing.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.lastAr = {type: ar.type, ratio: ar.ratio};
|
this.lastAr = {type: ar.type, ratio: ar.ratio, variant: ar.variant};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! this.video) {
|
if (! this.video) {
|
||||||
@ -496,24 +521,35 @@ 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,
|
||||||
stretch: this.stretcher.stretch
|
stretch: this.stretcher.stretch
|
||||||
});
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toFixedAr() {
|
toFixedAr(flags?: {manualZoom?: boolean}) {
|
||||||
// converting to fixed AR means we also turn off autoAR
|
// converting to fixed AR means we also turn off autoAR
|
||||||
|
this.setAr(
|
||||||
this.setAr({
|
{
|
||||||
ratio: this.lastAr.ratio ?? this.getFileAr(),
|
ratio: this.lastAr.ratio ?? this.getFileAr(),
|
||||||
type: AspectRatioType.Fixed
|
type: AspectRatioType.Fixed
|
||||||
});
|
},
|
||||||
|
undefined,
|
||||||
|
flags
|
||||||
|
);
|
||||||
|
|
||||||
|
if (flags?.manualZoom) {
|
||||||
|
this.manualZoom = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resetLastAr() {
|
resetLastAr() {
|
||||||
|
|||||||
@ -71,7 +71,7 @@ class Zoom {
|
|||||||
this.scale = Math.pow(2, this.logScale);
|
this.scale = Math.pow(2, this.logScale);
|
||||||
this.scaleY = Math.pow(2, this.logScaleY);
|
this.scaleY = Math.pow(2, this.logScaleY);
|
||||||
|
|
||||||
this.processZoom();
|
this.processZoom({manualZoom: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,11 +92,11 @@ class Zoom {
|
|||||||
this.scale = Math.min(Math.max(scaleIn.x, MIN_SCALE), MAX_SCALE);
|
this.scale = Math.min(Math.max(scaleIn.x, MIN_SCALE), MAX_SCALE);
|
||||||
this.scaleY = Math.min(Math.max(scaleIn.y, MIN_SCALE), MAX_SCALE);
|
this.scaleY = Math.min(Math.max(scaleIn.y, MIN_SCALE), MAX_SCALE);
|
||||||
|
|
||||||
this.processZoom();
|
this.processZoom({manualZoom: true});
|
||||||
}
|
}
|
||||||
|
|
||||||
processZoom() {
|
processZoom(flags?: {manualZoom?: boolean}) {
|
||||||
this.conf.resizer.toFixedAr();
|
this.conf.resizer.toFixedAr(flags);
|
||||||
this.conf.resizer.applyScaling({xFactor: this.scale, yFactor: this.scaleY}, {noAnnounce: true});
|
this.conf.resizer.applyScaling({xFactor: this.scale, yFactor: this.scaleY}, {noAnnounce: true});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -101,8 +101,8 @@
|
|||||||
<optgroup label="corners">
|
<optgroup label="corners">
|
||||||
<option :value="MenuPosition.TopLeft">Top left</option>
|
<option :value="MenuPosition.TopLeft">Top left</option>
|
||||||
<option :value="MenuPosition.BottomLeft">Bottom left</option>
|
<option :value="MenuPosition.BottomLeft">Bottom left</option>
|
||||||
<option :value="MenuPosition.TopRight">Top right"</option>
|
<option :value="MenuPosition.TopRight">Top right</option>
|
||||||
<option :value="MenuPosition.BottomRight">Bottom right"</option>
|
<option :value="MenuPosition.BottomRight">Bottom right</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export default defineComponent({
|
|||||||
props: {
|
props: {
|
||||||
label: String,
|
label: String,
|
||||||
shortcut: String,
|
shortcut: String,
|
||||||
classList: String
|
classList: Object
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -86,6 +86,156 @@
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Menu + active markers for left/right menus */
|
||||||
|
.uw-menu-left {
|
||||||
|
.uw-menu-item {
|
||||||
|
&.uw-active {
|
||||||
|
padding-right: 2rem;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
top: 50%;
|
||||||
|
right: 0.5rem;
|
||||||
|
content: '◈';
|
||||||
|
transform: translateY(calc(-50% + 0.25rem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uw-has-submenu {
|
||||||
|
padding-right: 2rem;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&.uw-active-within {
|
||||||
|
padding-right: 5rem;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
top: 50%;
|
||||||
|
right: 1.5rem;
|
||||||
|
content: '◈';
|
||||||
|
/* transform: translateY(-50%); */
|
||||||
|
transform: translateY(calc(-50% + 0.125rem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
right: 0;
|
||||||
|
content: '▶';
|
||||||
|
transform: scale(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.uw-menu-right {
|
||||||
|
.uw-menu-item {
|
||||||
|
padding-left: 2rem;
|
||||||
|
|
||||||
|
&.uw-active {
|
||||||
|
padding-right: 2rem;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
top: 50%;
|
||||||
|
right: 0.5rem;
|
||||||
|
content: '◈';
|
||||||
|
transform: translateY(calc(-50% + 0.25rem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uw-has-submenu {
|
||||||
|
&.uw-active-within {
|
||||||
|
padding-right: 2rem;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
top: 50%;
|
||||||
|
right: 0.5rem;
|
||||||
|
content: '◈';
|
||||||
|
transform: translateY(calc(-50% + 0.125rem));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
left: 0;
|
||||||
|
content: '◀';
|
||||||
|
transform: scale(0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Menus on top/center and bottom/center */
|
||||||
|
.uw-menu-center {
|
||||||
|
.uw-menu-top {
|
||||||
|
|
||||||
|
.uw-has-submenu {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
content: '▼';
|
||||||
|
transform: scale(0.5) translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We don't add :after marker, as it's already used for other purposes */
|
||||||
|
/* &.uw-active-within {
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
bottom: 0;
|
||||||
|
left: 50%;
|
||||||
|
content: '◈';
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.uw-menu-bottom {
|
||||||
|
.uw-menu-item {
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uw-has-submenu {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
content: '▲';
|
||||||
|
transform: scale(0.5) translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We don't add :after marker, as it's already used for other purposes */
|
||||||
|
/* &.uw-active-within {
|
||||||
|
&::after {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
top: 0;
|
||||||
|
left: 50%;
|
||||||
|
content: '◈';
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.uw-submenu {
|
.uw-submenu {
|
||||||
@apply absolute flex-col gap-0;
|
@apply absolute flex-col gap-0;
|
||||||
|
|
||||||
@ -148,6 +298,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.uw-menu-item, .uw-menu-trigger {
|
.uw-menu-item, .uw-menu-trigger {
|
||||||
|
@apply cursor-pointer select-none;
|
||||||
|
|
||||||
&:hover > .uw-submenu {
|
&:hover > .uw-submenu {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -158,6 +309,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.uw-menu-item {
|
||||||
|
&.uw-active-within, &.uw-active {
|
||||||
|
@apply text-primary-500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.uw-trigger {
|
.uw-trigger {
|
||||||
@apply p-4 relative block
|
@apply p-4 relative block
|
||||||
@ -291,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'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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('-');
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
"outDir": "./ts-out",
|
"outDir": "./ts-out",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"lib": ["esnext", "DOM"],
|
"lib": ["esnext", "DOM", "DOM.Iterable"],
|
||||||
"types": [
|
"types": [
|
||||||
"chrome",
|
"chrome",
|
||||||
"node"
|
"node"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user