Compare commits
40 Commits
7.0-alpha1
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 78395e3ea0 | |||
| e269ff3d79 | |||
| be495b1c56 | |||
| 3f032619b4 | |||
| ef5ba706b7 | |||
| 3b91c2224c | |||
| 822d069b82 | |||
| 8af9f2cedf | |||
| 3d8d8eb199 | |||
| a0d599ce2f | |||
| fd7b5ec04e | |||
| c9818c92b3 | |||
| f57163f2bb | |||
| dbdcb4e367 | |||
| e1dd0f9c04 | |||
| 2cb6a88c5e | |||
| eb89ba03db | |||
| cb02c8619d | |||
| 7d296e6a3d | |||
| 35bb5b477b | |||
| 925a9fdea3 | |||
| f0baa6ad67 | |||
| f25749470b | |||
| 8a4985e6a3 | |||
| 2cce60fb9f | |||
| 826acf9a3f | |||
| 186de15781 | |||
| 17dc69f87a | |||
| 8268c7a7af | |||
| a71b99ff10 | |||
| 54c4f49edb | |||
| 92cd1cf58b | |||
| 64570c71ce | |||
| cbc4b7877f | |||
| b62f6feaea | |||
| b6dec385dc | |||
| a12581ed0e | |||
| e651e51c50 | |||
| 56321e52fc | |||
| 73eec1f7ba |
6
.vscode/settings.json
vendored
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"aard",
|
"aard",
|
||||||
|
"allowtransparency",
|
||||||
"ardetector",
|
"ardetector",
|
||||||
"autodetect",
|
"autodetect",
|
||||||
"autodetection",
|
"autodetection",
|
||||||
@ -8,6 +9,7 @@
|
|||||||
"blackbar",
|
"blackbar",
|
||||||
"blackbars",
|
"blackbars",
|
||||||
"blackframe",
|
"blackframe",
|
||||||
|
"bruteforce",
|
||||||
"canvas",
|
"canvas",
|
||||||
"clickthrough",
|
"clickthrough",
|
||||||
"cmpa",
|
"cmpa",
|
||||||
@ -15,6 +17,7 @@
|
|||||||
"com",
|
"com",
|
||||||
"comms",
|
"comms",
|
||||||
"csui",
|
"csui",
|
||||||
|
"ctab",
|
||||||
"cycleable",
|
"cycleable",
|
||||||
"decycle",
|
"decycle",
|
||||||
"dinked",
|
"dinked",
|
||||||
@ -90,7 +93,8 @@
|
|||||||
"wuckies",
|
"wuckies",
|
||||||
"wucky",
|
"wucky",
|
||||||
"ycenter",
|
"ycenter",
|
||||||
"youtube"
|
"youtube",
|
||||||
|
"YYMM"
|
||||||
],
|
],
|
||||||
"cSpell.ignoreWords": [
|
"cSpell.ignoreWords": [
|
||||||
"abcdefghijklmnopqrstuvwxyz",
|
"abcdefghijklmnopqrstuvwxyz",
|
||||||
|
|||||||
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ultrawidify",
|
"name": "ultrawidify",
|
||||||
"version": "6.3.996",
|
"version": "6.3.998",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ultrawidify",
|
"name": "ultrawidify",
|
||||||
"version": "6.3.996",
|
"version": "6.3.998",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||||
"@mdi/font": "^7.4.47",
|
"@mdi/font": "^7.4.47",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ultrawidify",
|
"name": "ultrawidify",
|
||||||
"version": "6.3.996",
|
"version": "6.3.998",
|
||||||
"description": "Aspect ratio fixer for youtube and other sites, with automatic aspect ratio detection. Supports ultrawide and other ratios.",
|
"description": "Aspect ratio fixer for youtube and other sites, with automatic aspect ratio detection. Supports ultrawide and other ratios.",
|
||||||
"author": "Tamius Han <tamius.han@gmail.com>",
|
"author": "Tamius Han <tamius.han@gmail.com>",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -2,7 +2,6 @@ const path = require('path')
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
'@src': path.resolve(__dirname, 'src'),
|
'@src': path.resolve(__dirname, 'src'),
|
||||||
'@csui': path.resolve(__dirname, 'src/csui'),
|
|
||||||
'@ui': path.resolve(__dirname, 'src/ui'),
|
'@ui': path.resolve(__dirname, 'src/ui'),
|
||||||
'@components': path.resolve(__dirname, 'src/ui/components'),
|
'@components': path.resolve(__dirname, 'src/ui/components'),
|
||||||
'@res': path.resolve(__dirname, 'src/ui/res'),
|
'@res': path.resolve(__dirname, 'src/ui/res'),
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
5
src/common/enums/InputHandlingMode.enum.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export enum InputHandlingMode {
|
||||||
|
Disabled = -1,
|
||||||
|
Default = 0,
|
||||||
|
Enabled = 1, // enable, but don't override site shortcuts
|
||||||
|
}
|
||||||
@ -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,10 +1,17 @@
|
|||||||
export interface MenuItemConfig {
|
import { CommandInterface, InPlayerUIOptions } from '@src/common/interfaces/SettingsInterface';
|
||||||
label: string;
|
|
||||||
|
export interface MenuItemBaseConfig {
|
||||||
|
label?: string;
|
||||||
subitems?: MenuItemConfig[];
|
subitems?: MenuItemConfig[];
|
||||||
|
command?: CommandInterface,
|
||||||
action?: () => void;
|
action?: () => void;
|
||||||
customHTML?: HTMLElement;
|
customHTML?: HTMLElement | string;
|
||||||
|
customId?: 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',
|
||||||
@ -18,7 +25,7 @@ export enum MenuPosition {
|
|||||||
|
|
||||||
export interface MenuConfig {
|
export interface MenuConfig {
|
||||||
isGlobal?: boolean;
|
isGlobal?: boolean;
|
||||||
menuPosition: MenuPosition;
|
ui: InPlayerUIOptions;
|
||||||
activationRadius?: number;
|
options?: {forceShow?: boolean};
|
||||||
items: MenuItemConfig[];
|
items: MenuItemConfig[];
|
||||||
}
|
}
|
||||||
|
|||||||
43
src/common/interfaces/EventBusMessage.interface.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { CommsOrigin } from '@/ext/module/comms/CommsClient';
|
||||||
|
import type { Runtime } from 'chrome';
|
||||||
|
|
||||||
|
export interface EventBusCommand {
|
||||||
|
isGlobal?: boolean,
|
||||||
|
source?: any,
|
||||||
|
function: (commandData: any, context?: EventBusContext) => void | Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventBusMessage<T = unknown> {
|
||||||
|
command: string;
|
||||||
|
config?: T;
|
||||||
|
context?: EventBusContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EventBusContext {
|
||||||
|
stopPropagation?: boolean;
|
||||||
|
|
||||||
|
origin?: CommsOrigin;
|
||||||
|
frameUrl?: string;
|
||||||
|
|
||||||
|
// tab?: number;
|
||||||
|
// frame?: number;
|
||||||
|
// port?: string;
|
||||||
|
|
||||||
|
visitedBusses?: string[];
|
||||||
|
commandId?: string;
|
||||||
|
|
||||||
|
comms?: {
|
||||||
|
forwardTo?: 'all' | 'active' | 'popup' | 'contentScript' | 'all-frames';
|
||||||
|
sender?: Runtime.MessageSender;
|
||||||
|
port?: Runtime.Port;
|
||||||
|
// frame?: any;
|
||||||
|
sourceFrame?: {
|
||||||
|
tabId: number;
|
||||||
|
frameId: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
borderCrossings?: {
|
||||||
|
commsServer?: boolean,
|
||||||
|
iframe?: boolean,
|
||||||
|
};
|
||||||
|
}
|
||||||
8
src/common/interfaces/HostData.interface.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { ExtensionEnvironment } from '@src/common/interfaces/SettingsInterface';
|
||||||
|
|
||||||
|
export interface HostInfo {
|
||||||
|
host: string,
|
||||||
|
hasVideo: boolean,
|
||||||
|
minEnvironment: ExtensionEnvironment,
|
||||||
|
maxEnvironment: ExtensionEnvironment
|
||||||
|
};
|
||||||
6
src/common/interfaces/IframeTunnelPayload.interface.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { EventBusMessage } from '@src/common/interfaces/EventBusMessage.interface';
|
||||||
|
|
||||||
|
export interface IframeTunnelPayload {
|
||||||
|
action: string,
|
||||||
|
payload: EventBusMessage,
|
||||||
|
}
|
||||||
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,16 +1,15 @@
|
|||||||
import { SiteSupportLevel } from './../enums/SiteSupportLevel.enum';
|
import { AardPollingOptions } from '@src/ext/module/aard/enums/aard-polling-options.enum'
|
||||||
import { Action } from '../../../node_modules/vuex/types/index'
|
import { AardSubtitleCropMode } from '@src/ext/module/aard/enums/aard-subtitle-crop-mode.enum'
|
||||||
import { AardPollingOptions } from '../../ext/lib/aard/enums/aard-polling-options.enum'
|
import { SiteSupportLevel } from '@src/common/enums/SiteSupportLevel.enum';
|
||||||
import { AardSubtitleCropMode } from '../../ext/lib/aard/enums/aard-subtitle-crop-mode.enum'
|
import AspectRatioType from '@src/common/enums/AspectRatioType.enum'
|
||||||
import AntiGradientMode from '../enums/AntiGradientMode.enum'
|
import CropModePersistence from '@src/common/enums/CropModePersistence.enum'
|
||||||
import AspectRatioType from '../enums/AspectRatioType.enum'
|
import EmbeddedContentSettingsOverridePolicy from '@src/common/enums/EmbeddedContentSettingsOverridePolicy.enum'
|
||||||
import CropModePersistence from '../enums/CropModePersistence.enum'
|
import ExtensionMode from '@src/common/enums/ExtensionMode.enum'
|
||||||
import EmbeddedContentSettingsOverridePolicy from '../enums/EmbeddedContentSettingsOverridePolicy.enum'
|
import StretchType from '@src/common/enums/StretchType.enum'
|
||||||
import ExtensionMode from '../enums/ExtensionMode.enum'
|
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum'
|
||||||
import { SiteSupportLevel } from '../enums/SiteSupportLevel.enum'
|
import { PlayerDetectionMode } from '@src/common/enums/PlayerDetectionMode.enum';
|
||||||
import StretchType from '../enums/StretchType.enum'
|
import { InputHandlingMode } from '@src/common/enums/InputHandlingMode.enum';
|
||||||
import VideoAlignmentType from '../enums/VideoAlignmentType.enum'
|
import { MenuPosition } from '@src/common/interfaces/ClientUiMenu.interface';
|
||||||
import { PlayerDetectionMode } from '../enums/PlayerDetectionMode.enum';
|
|
||||||
|
|
||||||
export enum ExtensionEnvironment {
|
export enum ExtensionEnvironment {
|
||||||
Normal = ExtensionMode.All,
|
Normal = ExtensionMode.All,
|
||||||
@ -310,6 +309,24 @@ interface DevSettings {
|
|||||||
loadFromSnapshot: boolean,
|
loadFromSnapshot: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface InPlayerUIOptions {
|
||||||
|
activatorAlignment: MenuPosition,
|
||||||
|
minEnabledWidth: number, // don't show UI if player is narrower than % of screen width
|
||||||
|
minEnabledHeight: number, // don't show UI if player is narrower than % of screen height
|
||||||
|
activation: 'player' | 'trigger-zone' | 'distance' | 'none', // what needs to be hovered in order for UI to be visible
|
||||||
|
activateWithCtrl: boolean,
|
||||||
|
activationDistance: number,
|
||||||
|
activationDistanceUnits: '%' | 'px',
|
||||||
|
activatorPadding: {x: number, y: number}
|
||||||
|
activatorPaddingUnit: {x: '%' | 'px', y: '%' | 'px'},
|
||||||
|
triggerZoneDimensions: { // how large the trigger zone is (relative to player size)
|
||||||
|
width: number
|
||||||
|
height: number,
|
||||||
|
offsetX: number, // fed to translateX(offsetX + '%'). Valid range [-100, 0]
|
||||||
|
offsetY: number // fed to translateY(offsetY + '%'). Valid range [-100, 100]
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
interface SettingsInterface {
|
interface SettingsInterface {
|
||||||
_updateFlags?: {
|
_updateFlags?: {
|
||||||
requireReload?: SettingsReloadFlags,
|
requireReload?: SettingsReloadFlags,
|
||||||
@ -321,18 +338,7 @@ interface SettingsInterface {
|
|||||||
aard: AardSettings,
|
aard: AardSettings,
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
inPlayer: {
|
inPlayer: InPlayerUIOptions,
|
||||||
popupAlignment: 'left' | 'right',
|
|
||||||
minEnabledWidth: number, // don't show UI if player is narrower than % of screen width
|
|
||||||
minEnabledHeight: number, // don't show UI if player is narrower than % of screen height
|
|
||||||
activation: 'player' | 'trigger-zone' | 'distance' | 'none', // what needs to be hovered in order for UI to be visible
|
|
||||||
triggerZoneDimensions: { // how large the trigger zone is (relative to player size)
|
|
||||||
width: number
|
|
||||||
height: number,
|
|
||||||
offsetX: number, // fed to translateX(offsetX + '%'). Valid range [-100, 0]
|
|
||||||
offsetY: number // fed to translateY(offsetY + '%'). Valid range [-100, 100]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
devMode?: boolean,
|
devMode?: boolean,
|
||||||
dev: DevUiConfig,
|
dev: DevUiConfig,
|
||||||
}
|
}
|
||||||
@ -347,7 +353,7 @@ interface SettingsInterface {
|
|||||||
conditionalDifferencePercent: number // black bars less than this wide will trigger stretch
|
conditionalDifferencePercent: number // black bars less than this wide will trigger stretch
|
||||||
// if mode is set to '1'. 1.0=100%
|
// if mode is set to '1'. 1.0=100%
|
||||||
},
|
},
|
||||||
kbm: {
|
kbm: { // TODO: we dont use this anymore
|
||||||
enabled: boolean, // if keyboard/mouse handler service will run
|
enabled: boolean, // if keyboard/mouse handler service will run
|
||||||
keyboardEnabled: boolean, // if keyboard shortcuts are processed
|
keyboardEnabled: boolean, // if keyboard shortcuts are processed
|
||||||
mouseEnabled: boolean, // if mouse movement is processed
|
mouseEnabled: boolean, // if mouse movement is processed
|
||||||
@ -440,9 +446,11 @@ interface SettingsInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SiteSettingsInterface {
|
export interface SiteSettingsInterface {
|
||||||
|
notes?: string; // any special things related to this site.
|
||||||
|
|
||||||
enable: ExtensionMode;
|
enable: ExtensionMode;
|
||||||
enableAard: ExtensionMode;
|
enableAard: ExtensionMode;
|
||||||
enableKeyboard: ExtensionMode;
|
enableKeyboard: InputHandlingMode;
|
||||||
enableUI: ExtensionMode;
|
enableUI: ExtensionMode;
|
||||||
|
|
||||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy; // presumed to be 'Use as default' if not defined
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy; // presumed to be 'Use as default' if not defined
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<p><i><a href="https://www.youtube.com/watch?v=Mn3YEJTSYs8&feature=youtu.be&t=770" target='_blank'>...</a> but as is normal, you can't use a free extensions without the developers begging for money. It's the bi-daily beggathon here on ... wherever here is.</i>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Jokes and references few will get aside, developing this extension does take a decent amount of time, motivation, carefully calibrated quantities
|
|
||||||
of alcohol and enough coffee to bankrupt a small nation.
|
|
||||||
</p>
|
|
||||||
<p>If you want to buy me a beer or bankroll my caffeine addiction, you can do so by <a href="https://paypal.me/tamius">clicking here</a>. All donations are appreciated.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
</script>
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import Debug from '../conf/Debug';
|
import Debug from '../../ext/conf/Debug';
|
||||||
|
|
||||||
class ObjectCopy {
|
class ObjectCopy {
|
||||||
static addNew(current, newValues){
|
static addNew(current, newValues){
|
||||||
@ -1,7 +1,3 @@
|
|||||||
export async function sleep(timeout) {
|
|
||||||
return new Promise<void>( (resolve, reject) => setTimeout(() => resolve(), timeout));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates deep copy of an object
|
* Creates deep copy of an object
|
||||||
* @param obj
|
* @param obj
|
||||||
@ -1,62 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="uw-clickthrough relative w-full h-full">
|
|
||||||
<template v-for="rectangle of drawnRectangles" :key="rectangle.id ?? rectangle">
|
|
||||||
|
|
||||||
<!-- Player element overlays -->
|
|
||||||
<div class="absolute z-index-overlay"
|
|
||||||
:style="rectangle.style"
|
|
||||||
>
|
|
||||||
<!-- used for drawing player element overlay rectangles - keep this section empty -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Notification overlay -->
|
|
||||||
<div class="absolute z-index-notification notification-area">
|
|
||||||
<template v-for="notification of displayedNotifications" :key="notification.id">
|
|
||||||
<div class="notification d-flex flex-row">
|
|
||||||
<div class="notification-icon">
|
|
||||||
<mdicon :name="notification.icon ?? 'alert'" :size="128"></mdicon>
|
|
||||||
</div>
|
|
||||||
<div class="notification-text">
|
|
||||||
<h3 class="notification-title">{{ notification.title }}</h3>
|
|
||||||
<p class="notification-verbose">{{ notification.text }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import UIProbeMixin from './src/utils/UIProbeMixin';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
},
|
|
||||||
mixins: [
|
|
||||||
UIProbeMixin
|
|
||||||
],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
drawnRectangles: [],
|
|
||||||
displayedNotifications: [],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async created() {
|
|
||||||
/**
|
|
||||||
* Setup the "companion" onMouseMove handler to the one in the content script.
|
|
||||||
* We can handle events with the same function we use to handle events from
|
|
||||||
* the content script.
|
|
||||||
*/
|
|
||||||
document.addEventListener('mousemove', (event) => {
|
|
||||||
this.handleProbe({
|
|
||||||
coords: {
|
|
||||||
x: event.clientX,
|
|
||||||
y: event.clientY
|
|
||||||
}
|
|
||||||
}, this.origin);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,740 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="context-spawn uw-ui-area"
|
|
||||||
style="z-index: 1000;"
|
|
||||||
v-if="!triggerZoneEditorVisible"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="spawn-container uw-ui-trigger"
|
|
||||||
:style="triggerZoneStyles"
|
|
||||||
>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="contextMenuActive || settingsInitialized && uwTriggerZoneVisible && !isGlobal"
|
|
||||||
class="context-spawn uw-ui-area"
|
|
||||||
style="z-index: 1001"
|
|
||||||
|
|
||||||
>
|
|
||||||
<GhettoContextMenu
|
|
||||||
alignment="right" class="uw-menu"
|
|
||||||
@mouseenter="() => {preventContextMenuHide(); newFeatureViewUpdate('uw6.ui-popup')}"
|
|
||||||
@mouseleave="allowContextMenuHide()"
|
|
||||||
>
|
|
||||||
<template v-slot:activator>
|
|
||||||
Ultrawidify
|
|
||||||
</template>
|
|
||||||
<slot>
|
|
||||||
<!--
|
|
||||||
Didn't manage to ensure that extension status pops up above other menu items in less than 3 minutes with z-index,
|
|
||||||
so wrapping 'status' and 'real menu items' in two different divs, ordering them in the opposite way, and then
|
|
||||||
ensuring correct ordering with flex-direction: column-reverse ended up being easier and faster.
|
|
||||||
-->
|
|
||||||
<div class="uw-clickable menu-width flex-reverse-order">
|
|
||||||
<div style="z-index: 1000">
|
|
||||||
<GhettoContextMenu alignment="right">
|
|
||||||
<template v-slot:activator>
|
|
||||||
Crop
|
|
||||||
</template>
|
|
||||||
<slot>
|
|
||||||
<GhettoContextMenuOption
|
|
||||||
v-for="(command, index) of settings?.active.commands.crop"
|
|
||||||
:key="index"
|
|
||||||
:label="command.label"
|
|
||||||
:shortcut="getKeyboardShortcutLabel(command)"
|
|
||||||
@click="execAction(command)"
|
|
||||||
>
|
|
||||||
</GhettoContextMenuOption>
|
|
||||||
</slot>
|
|
||||||
</GhettoContextMenu>
|
|
||||||
<GhettoContextMenu alignment="right">
|
|
||||||
<template v-slot:activator>
|
|
||||||
Stretch
|
|
||||||
</template>
|
|
||||||
<slot>
|
|
||||||
<GhettoContextMenuOption
|
|
||||||
v-for="(command, index) of settings?.active.commands.stretch"
|
|
||||||
:key="index"
|
|
||||||
:label="command.label"
|
|
||||||
:shortcut="getKeyboardShortcutLabel(command)"
|
|
||||||
@click="execAction(command)"
|
|
||||||
>
|
|
||||||
</GhettoContextMenuOption>
|
|
||||||
</slot>
|
|
||||||
</GhettoContextMenu>
|
|
||||||
<GhettoContextMenu alignment="right">
|
|
||||||
<template v-slot:activator>
|
|
||||||
Zoom
|
|
||||||
</template>
|
|
||||||
<slot>
|
|
||||||
<GhettoContextMenuOption
|
|
||||||
v-for="(command, index) of settings?.active.commands.zoom"
|
|
||||||
:key="index"
|
|
||||||
:label="command.label"
|
|
||||||
:shortcut="getKeyboardShortcutLabel(command)"
|
|
||||||
@click="execAction(command)"
|
|
||||||
>
|
|
||||||
</GhettoContextMenuOption>
|
|
||||||
<GhettoContextMenuItem
|
|
||||||
:disableHover="true"
|
|
||||||
>
|
|
||||||
<ZoomControl
|
|
||||||
:settings="settings"
|
|
||||||
:eventBus="eventBus"
|
|
||||||
/>
|
|
||||||
</GhettoContextMenuItem>
|
|
||||||
</slot>
|
|
||||||
</GhettoContextMenu>
|
|
||||||
<GhettoContextMenu alignment="right">
|
|
||||||
<template v-slot:activator>
|
|
||||||
<div class="context-item">
|
|
||||||
Align
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<slot>
|
|
||||||
<GhettoContextMenuItem :disableHover="true" :css="{'reduced-padding': true}">
|
|
||||||
<AlignmentOptionsControlComponent
|
|
||||||
:eventBus="eventBus"
|
|
||||||
>
|
|
||||||
</AlignmentOptionsControlComponent>
|
|
||||||
</GhettoContextMenuItem>
|
|
||||||
</slot>
|
|
||||||
</GhettoContextMenu>
|
|
||||||
|
|
||||||
<!-- shortcut for configuring UI -->
|
|
||||||
<GhettoContextMenuOption
|
|
||||||
v-if="settings.active.newFeatureTracker?.['uw6.ui-popup']?.show > 0"
|
|
||||||
@click="showUwWindow('playerUiSettings')"
|
|
||||||
>
|
|
||||||
<span style="color: #fa6;">Change when and if<br/>this popup appears</span>
|
|
||||||
<span style="font-size: 0.8rem">
|
|
||||||
<span style="font-size: 0.8rem; opacity: 0.5">This menu option will show {{settings.active.newFeatureTracker?.['uw6.ui-popup']?.show}} more<br/> times; or until clicked or dismissed.<br/>
|
|
||||||
Also accessible via:<br/> <span style="font-size: 0.85em">EXTENSION SETTINGS > UI AND KEYBOARD</span>.
|
|
||||||
</span>
|
|
||||||
<br/>
|
|
||||||
<a style="color: #fa6; cursor: pointer;" @click="() => acknowledgeNewFeature('uw6.ui-popup')">Dismiss this option</a>
|
|
||||||
</span>
|
|
||||||
</GhettoContextMenuOption>
|
|
||||||
|
|
||||||
<!-- -->
|
|
||||||
<GhettoContextMenuOption
|
|
||||||
@click="showUwWindow()"
|
|
||||||
label="Extension settings"
|
|
||||||
>
|
|
||||||
</GhettoContextMenuOption>
|
|
||||||
<GhettoContextMenuOption
|
|
||||||
@click="showUwWindow('playerDetection')"
|
|
||||||
label="Incorrect cropping?"
|
|
||||||
>
|
|
||||||
</GhettoContextMenuOption>
|
|
||||||
<GhettoContextMenuOption
|
|
||||||
@click="showUwWindow('about')"
|
|
||||||
label="Not working?"
|
|
||||||
>
|
|
||||||
</GhettoContextMenuOption>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="z-index: 10000">
|
|
||||||
<GhettoContextMenuItem
|
|
||||||
class="extension-status-messages"
|
|
||||||
:disableHover="true"
|
|
||||||
>
|
|
||||||
Site compatibility:
|
|
||||||
<SupportLevelIndicator
|
|
||||||
:siteSupportLevel="siteSupportLevel"
|
|
||||||
supportLevelStyle="font-size: 0.69rem !important;"
|
|
||||||
tooltipStyle="font-size: 0.8rem;"
|
|
||||||
>
|
|
||||||
</SupportLevelIndicator>
|
|
||||||
<div v-if="statusFlags.hasDrm" class="aard-blocked">
|
|
||||||
Autodetection blocked<br/>
|
|
||||||
by <a style="color: #fff" href="https://en.wikipedia.org/wiki/Digital_rights_management" target="_blank">DRM</a>.
|
|
||||||
</div>
|
|
||||||
<div v-else-if="statusFlags.aardErrors?.cors" class="aard-blocked">
|
|
||||||
Autodetection blocked<br/>
|
|
||||||
by site/browser (CORS).
|
|
||||||
</div>
|
|
||||||
<div v-else-if="statusFlags.aardErrors?.webglError" class="aard-blocked">
|
|
||||||
Autodetection unavailable<br/>
|
|
||||||
(webgl error)
|
|
||||||
</div>
|
|
||||||
</GhettoContextMenuItem>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</slot>
|
|
||||||
</GhettoContextMenu>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="settingsInitialized && uwWindowVisible"
|
|
||||||
class="uw-window flex flex-col uw-clickable uw-ui-area"
|
|
||||||
:class="{'fade-out': uwWindowFadeOut}"
|
|
||||||
>
|
|
||||||
<PlayerUIWindow
|
|
||||||
:settings="settings"
|
|
||||||
:eventBus="eventBus"
|
|
||||||
:logger="logger"
|
|
||||||
:in-player="!isGlobal"
|
|
||||||
:site="site"
|
|
||||||
:defaultTab="defaultWindowTab"
|
|
||||||
@close="uwWindowVisible = false"
|
|
||||||
@preventClose="(event) => uwWindowFadeOutDisabled = event"
|
|
||||||
></PlayerUIWindow>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
v-if="triggerZoneEditorVisible"
|
|
||||||
class="context-spawn uw-ui-area"
|
|
||||||
style="z-index: 1000;"
|
|
||||||
>
|
|
||||||
<TriggerZoneEditor
|
|
||||||
:settings="settings"
|
|
||||||
:eventBus="eventBus"
|
|
||||||
:playerDimensions="playerDimensions"
|
|
||||||
>
|
|
||||||
</TriggerZoneEditor>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import PlayerUIWindow from '@csui/src/PlayerUIWindow.vue';
|
|
||||||
import GhettoContextMenu from '@csui/src/components/GhettoContextMenu.vue';
|
|
||||||
import GhettoContextMenuItem from '@csui/src/components/GhettoContextMenuItem.vue';
|
|
||||||
import GhettoContextMenuOption from '@csui/src/components/GhettoContextMenuOption.vue';
|
|
||||||
import AlignmentOptionsControlComponent from '@csui/src/PlayerUiPanels/AlignmentOptionsControlComponent.vue';
|
|
||||||
import BrowserDetect from '@src/ext/conf/BrowserDetect';
|
|
||||||
import Settings from '@src/ext/lib/settings/Settings';
|
|
||||||
import EventBus from '@src/ext/lib/EventBus';
|
|
||||||
import UIProbeMixin from '@csui/src/utils/UIProbeMixin';
|
|
||||||
import KeyboardShortcutParserMixin from '@csui/src/utils/KeyboardShortcutParserMixin';
|
|
||||||
import CommsMixin from '@csui/src/utils/CommsMixin';
|
|
||||||
import SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue';
|
|
||||||
import TriggerZoneEditor from '@csui/src/components/TriggerZoneEditor.vue';
|
|
||||||
import ZoomControl from '@csui/src/popup/player-menu/ZoomControl.vue';
|
|
||||||
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator';
|
|
||||||
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
PlayerUIWindow,
|
|
||||||
GhettoContextMenu,
|
|
||||||
GhettoContextMenuItem,
|
|
||||||
GhettoContextMenuOption,
|
|
||||||
AlignmentOptionsControlComponent,
|
|
||||||
SupportLevelIndicator,
|
|
||||||
TriggerZoneEditor,
|
|
||||||
ZoomControl,
|
|
||||||
},
|
|
||||||
mixins: [
|
|
||||||
UIProbeMixin,
|
|
||||||
KeyboardShortcutParserMixin,
|
|
||||||
CommsMixin
|
|
||||||
],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
uwTriggerZoneVisible: false,
|
|
||||||
uwTriggerZoneTimeout: undefined,
|
|
||||||
uwTriggerRegionConf: {
|
|
||||||
left: "10%",
|
|
||||||
top: "10%",
|
|
||||||
height: "30%",
|
|
||||||
width: "30%",
|
|
||||||
maxWidth: "24rem",
|
|
||||||
maxHeight: "13.37rem",
|
|
||||||
},
|
|
||||||
|
|
||||||
uwWindowFadeOutDisabled: false,
|
|
||||||
uwWindowFadeOut: false,
|
|
||||||
uwWindowCloseTimeout: undefined,
|
|
||||||
uwWindowVisible: false,
|
|
||||||
triggerZoneEditorVisible: false,
|
|
||||||
|
|
||||||
// component properties
|
|
||||||
settings: {},
|
|
||||||
BrowserDetect: BrowserDetect,
|
|
||||||
settingsInitialized: false,
|
|
||||||
eventBus: new EventBus(),
|
|
||||||
logAggregator: null,
|
|
||||||
logger: null,
|
|
||||||
|
|
||||||
// NOTE: chromium doesn't allow us to access window.parent.location
|
|
||||||
// meaning we will have to correct this value from our uwui-probe
|
|
||||||
// messages ... which is a bummer.
|
|
||||||
site: null,
|
|
||||||
origin: '*', // will be set appropriately once the first uwui-probe event is received
|
|
||||||
lastProbeTs: null,
|
|
||||||
|
|
||||||
isGlobal: true,
|
|
||||||
disabled: false,
|
|
||||||
|
|
||||||
contextMenuActive: false,
|
|
||||||
triggerZoneActive: false,
|
|
||||||
|
|
||||||
uiVisible: true,
|
|
||||||
debugData: {
|
|
||||||
resizer: {},
|
|
||||||
player: {},
|
|
||||||
},
|
|
||||||
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,
|
|
||||||
aardErrors: undefined,
|
|
||||||
},
|
|
||||||
defaultWindowTab: 'videoSettings',
|
|
||||||
|
|
||||||
saveState: {},
|
|
||||||
siteSettings: undefined,
|
|
||||||
previewZoneVisible: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
// LPT: NO ARROW FUNCTIONS IN COMPUTED,
|
|
||||||
// IS SUPER HARAM
|
|
||||||
// THINGS WILL NOT WORK IF YOU USE ARROWS
|
|
||||||
windowWidth() {
|
|
||||||
return window.innerWidth;
|
|
||||||
},
|
|
||||||
windowHeight() {
|
|
||||||
return window.innerHeight;
|
|
||||||
},
|
|
||||||
// LPT: NO ARROW FUNCTIONS IN COMPUTED,
|
|
||||||
// IS SUPER HARAM
|
|
||||||
// THINGS WILL NOT WORK IF YOU USE ARROWS
|
|
||||||
siteSupportLevel() {
|
|
||||||
return (this.site && this.siteSettings) ? this.siteSettings.data.type || 'no-support' : 'waiting';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
showUi(visible) {
|
|
||||||
if (visible !== undefined) {
|
|
||||||
this.uiVisible = visible;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resizerDebugData(newData) {
|
|
||||||
this.debugData.resizer = newData;
|
|
||||||
this.debugDataPrettified = JSON.stringify(this.debugData, null, 2);
|
|
||||||
},
|
|
||||||
playerDebugData(newData) {
|
|
||||||
this.debugData.player = newData;
|
|
||||||
this.debugDataPrettified = JSON.stringify(this.debugData, null, 2);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async created() {
|
|
||||||
this.logAggregator = new LogAggregator('player-overlay');
|
|
||||||
this.logger = new ComponentLogger(this.logAggregator, 'PlayerOverlay.vue');
|
|
||||||
|
|
||||||
this.settings = new Settings({afterSettingsSaved: this.updateConfig, logger: this.logger});
|
|
||||||
this.settings.listenAfterChange(() => this.updateTriggerZones());
|
|
||||||
|
|
||||||
await this.settings.init();
|
|
||||||
this.settingsInitialized = true;
|
|
||||||
|
|
||||||
// set up communication with client script.
|
|
||||||
// NOTE: companion onmousemove is set up in UIProbeMixin
|
|
||||||
window.addEventListener('message', event => {
|
|
||||||
this.handleMessage(event);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.eventBus.subscribeMulti(
|
|
||||||
{
|
|
||||||
'uw-config-broadcast': {
|
|
||||||
function:
|
|
||||||
(data) => {
|
|
||||||
switch (data.type) {
|
|
||||||
case 'drm-status':
|
|
||||||
this.statusFlags.hasDrm = data.hasDrm;
|
|
||||||
break;
|
|
||||||
case 'aard-error':
|
|
||||||
this.statusFlags.aardErrors = data.aardErrors;
|
|
||||||
break;
|
|
||||||
case 'player-dimensions':
|
|
||||||
this.playerDimensionsUpdate(data.data);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'uw-set-ui-state': {
|
|
||||||
function: (data) => {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'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 = {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'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 = {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'ui-trigger-zone-update': {
|
|
||||||
function: (data) => {
|
|
||||||
this.showTriggerZonePreview = data.previewZoneVisible;
|
|
||||||
// this.;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'start-trigger-zone-edit': {
|
|
||||||
function: () => {
|
|
||||||
this.triggerZoneEditorVisible = true;
|
|
||||||
this.uwWindowVisible = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'finish-trigger-zone-edit': {
|
|
||||||
function: () => {
|
|
||||||
this.triggerZoneEditorVisible = false;
|
|
||||||
this.showUwWindow('playerUiSettings');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
this
|
|
||||||
);
|
|
||||||
|
|
||||||
this.sendToParentLowLevel('uwui-get-role', null);
|
|
||||||
this.sendToParentLowLevel('uwui-get-theme', null);
|
|
||||||
|
|
||||||
// console.log('player overlay created — get player dims:')
|
|
||||||
this.sendToParentLowLevel('uw-bus-tunnel', {
|
|
||||||
action: 'get-player-dimensions'
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
destroyed() {
|
|
||||||
this.eventBus.unsubscribeAll(this)
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/**
|
|
||||||
* Gets URL of the browser settings page (i think?)
|
|
||||||
*/
|
|
||||||
getUrl(url) {
|
|
||||||
return BrowserDetect.getURL(url);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mostly intended to process messages received via window.addEventListener('message').
|
|
||||||
* This method should include minimal logic — instead, it should only route messages
|
|
||||||
* to the correct function down the line.
|
|
||||||
*/
|
|
||||||
handleMessage(event) {
|
|
||||||
switch (event.data.action) {
|
|
||||||
case 'uwui-probe':
|
|
||||||
if (!this.site) {
|
|
||||||
this.origin = event.origin;
|
|
||||||
this.site = event.origin.split('//')[1];
|
|
||||||
this.siteSettings = this.settings.getSiteSettings({site: this.site, isIframe: event.data.isIframe});
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles trigger zone
|
|
||||||
*/
|
|
||||||
handleTriggerZone(mouseInside) {
|
|
||||||
// this.triggerZoneActive = mouseInside;
|
|
||||||
},
|
|
||||||
|
|
||||||
acknowledgeNewFeature(featureKey) {
|
|
||||||
this.settings.active.newFeatureTracker[featureKey].show = 0;
|
|
||||||
this.settings.active.newFeatureTracker[featureKey].acknowledged = true;
|
|
||||||
this.settings.saveWithoutReload();
|
|
||||||
},
|
|
||||||
newFeatureViewUpdate(featureKey) {
|
|
||||||
if (!this.settings.active.newFeatureTracker[featureKey]) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
this.settings.active.newFeatureTracker[featureKey].show--;
|
|
||||||
this.settings.saveWithoutReload();
|
|
||||||
|
|
||||||
if (this.settings.active.newFeatureTracker[featureKey]?.show < 0) {
|
|
||||||
this.acknowledgeNewFeature(featureKey);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends message to parent _without_ using event bus.
|
|
||||||
*/
|
|
||||||
sendToParentLowLevel(action, payload, lowLevelExtras = {}) {
|
|
||||||
window.parent.postMessage(
|
|
||||||
{
|
|
||||||
action, payload, ...lowLevelExtras
|
|
||||||
},
|
|
||||||
'*'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
preventContextMenuHide() {
|
|
||||||
this.contextMenuActive = true;
|
|
||||||
|
|
||||||
// refresh DRM status
|
|
||||||
this.eventBus.sendToTunnel('get-drm-status');
|
|
||||||
},
|
|
||||||
allowContextMenuHide() {
|
|
||||||
this.contextMenuActive = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
setTriggerZoneActive(active, event) {
|
|
||||||
this.triggerZoneActive = active;
|
|
||||||
},
|
|
||||||
|
|
||||||
showUwWindow(tab) {
|
|
||||||
this.defaultWindowTab = tab; // can be undefined
|
|
||||||
|
|
||||||
this.uwWindowFadeOut = false;
|
|
||||||
this.uwWindowVisible = true;
|
|
||||||
this.uwTriggerZoneVisible = false;
|
|
||||||
this.allowContextMenuHide();
|
|
||||||
|
|
||||||
// refresh DRM status
|
|
||||||
this.eventBus.sendToTunnel('get-drm-status');
|
|
||||||
|
|
||||||
// if (this.isGlobal) {
|
|
||||||
// this.sendToParentLowLevel('uwui-clickable', undefined, {clickable: true});
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
|
|
||||||
hideUwWindow(skipTimeout = false) {
|
|
||||||
if (this.uwWindowFadeOutDisabled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelUwWindowHide() {
|
|
||||||
this.uwWindowFadeOut = false;
|
|
||||||
clearTimeout(this.uwWindowCloseTimeout);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleBusTunnelIn(payload) {
|
|
||||||
this.eventBus.send(
|
|
||||||
payload.action,
|
|
||||||
payload.config,
|
|
||||||
{
|
|
||||||
...payload.context,
|
|
||||||
borderCrossings: {
|
|
||||||
...payload.context?.borderCrossings,
|
|
||||||
iframe: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateConfig() {
|
|
||||||
this.settings.init();
|
|
||||||
this.$nextTick( () => this.$forceUpdate());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
.ard-blocked {
|
|
||||||
color: rgb(219, 125, 48) !important;
|
|
||||||
background-color: rgba(0,0,0,0.85) !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss" src="@csui/src/res-common/panels.scss" scoped module></style>
|
|
||||||
<style lang="scss" src="@csui/src/res-common/common.scss" scoped module></style>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
|
|
||||||
.uw-hover {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 999999999999999999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reduced-padding {
|
|
||||||
padding: 1rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uv-hover-trigger-region {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
border: 0.5rem dashed #fff;
|
|
||||||
color: #fff;
|
|
||||||
backdrop-filter: blur(0.5rem) brightness(0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.uw-window {
|
|
||||||
position: fixed;
|
|
||||||
|
|
||||||
top: 10%;
|
|
||||||
left: 10%;
|
|
||||||
|
|
||||||
z-index: 999999999999999999;
|
|
||||||
|
|
||||||
width: 2500px;
|
|
||||||
height: 1200px;
|
|
||||||
max-width: 80%;
|
|
||||||
max-height: 80%;
|
|
||||||
|
|
||||||
pointer-events: all !important;
|
|
||||||
|
|
||||||
opacity: 1;
|
|
||||||
backdrop-filter: blur(16px) saturate(120%);
|
|
||||||
|
|
||||||
&.fade-out {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.5s;
|
|
||||||
transition-delay: 0.5s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.gib-bg {
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
backdrop-filter: blur(16px) saturate(120%);
|
|
||||||
|
|
||||||
width: fit-content;
|
|
||||||
block-size: fit-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui-warning {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
transform: translateY(-100%);
|
|
||||||
|
|
||||||
max-width: 16rem;
|
|
||||||
width: 16rem;
|
|
||||||
|
|
||||||
overflow: hidden;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
|
|
||||||
white-space: normal;
|
|
||||||
word-break: break-word;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.context-spawn {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
|
|
||||||
width: 100dvw;
|
|
||||||
height: 100dvh;
|
|
||||||
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.extension-status-messages {
|
|
||||||
z-index: 1000;
|
|
||||||
text-transform: uppercase;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
font-size: 0.9rem;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-reverse-order {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column-reverse;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.aard-blocked {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: #fa6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger-zone-preview {
|
|
||||||
border: 4px solid #fa4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.debug-1 {
|
|
||||||
border: 1px solid yellow;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rbba(255,255,0,0.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.debug-2 {
|
|
||||||
border: 1px solid blue;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rbba(0,0,255,.5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en"; style="position: relative">
|
|
||||||
<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') { %>
|
|
||||||
<!-- Load some resources only in development environment -->
|
|
||||||
<% } %>
|
|
||||||
</head>
|
|
||||||
<body class="uw-ultrawidify-container-root">
|
|
||||||
<div id="app"></div>
|
|
||||||
<!-- <script src="csui-global.js"></script> -->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
import { createApp } from 'vue';
|
|
||||||
import GlobalFrame from './GlobalFrame';
|
|
||||||
import mdiVue from 'mdi-vue/v3';
|
|
||||||
import * as mdijs from '@mdi/js';
|
|
||||||
|
|
||||||
import './src/res-common/common.scss';
|
|
||||||
|
|
||||||
// NOTE — this is in-player interface for ultrawidify
|
|
||||||
// it is injected into the page in UI.init()
|
|
||||||
|
|
||||||
createApp(GlobalFrame)
|
|
||||||
.use(mdiVue, {icons: mdijs})
|
|
||||||
.mount('#app');
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" style="position: relative">
|
|
||||||
<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"> -->
|
|
||||||
</head>
|
|
||||||
<body class="uw-ultrawidify-container-root" style="background-color: transparent;">
|
|
||||||
<div id="app"></div>
|
|
||||||
<!-- <script src="csui.js"></script> -->
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" style="position: relative">
|
|
||||||
<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>
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" style="position: relative">
|
|
||||||
<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>
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
import { createApp } from 'vue';
|
|
||||||
import PlayerOverlay from './PlayerOverlay';
|
|
||||||
import mdiVue from 'mdi-vue/v3';
|
|
||||||
import * as mdijs from '@mdi/js';
|
|
||||||
|
|
||||||
import './src/res-common/common.scss';
|
|
||||||
|
|
||||||
// NOTE — this is in-player interface for ultrawidify
|
|
||||||
// it is injected into the page in UI.init()
|
|
||||||
|
|
||||||
createApp(PlayerOverlay)
|
|
||||||
.use(mdiVue, {icons: mdijs})
|
|
||||||
.mount('#app');
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div>Ultrawidify</div>
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<div v-if="notification.icon">
|
|
||||||
<mdicon :name="notification.icon"></mdicon>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-grow flex-col">
|
|
||||||
<div v-if="notification.title">{{notification.title}}</div>
|
|
||||||
<div v-if="notification.description">{{notification.description}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="notification.actions" class="flex flex-row flex-end">
|
|
||||||
<button
|
|
||||||
v-for="(action, index) of notification.actions"
|
|
||||||
:key="index"
|
|
||||||
>
|
|
||||||
{{ action.label }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div>Notification countdown</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
},
|
|
||||||
mixins: [
|
|
||||||
],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
notification: {}
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
},
|
|
||||||
async created() {
|
|
||||||
this.logger = new Logger();
|
|
||||||
|
|
||||||
window.addEventListener('message', this.handleMessage);
|
|
||||||
this.sendToParentLowLevel('init-complete', {});
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
window.removeEventListener('message', this.handleMessage);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/**
|
|
||||||
* Mostly intended to process messages received via window.addEventListener('message').
|
|
||||||
* This method should include minimal logic — instead, it should only route messages
|
|
||||||
* to the correct function down the line.
|
|
||||||
*/
|
|
||||||
handleMessage(event) {
|
|
||||||
switch (event.data.action) {
|
|
||||||
case 'notification-data':
|
|
||||||
this.notification = event.data.payload;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends message to parent _without_ using event bus.
|
|
||||||
*/
|
|
||||||
sendToParentLowLevel(action, payload, lowLevelExtras = {}) {
|
|
||||||
window.parent.postMessage(
|
|
||||||
{
|
|
||||||
action, payload, ...lowLevelExtras
|
|
||||||
},
|
|
||||||
'*'
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss">
|
|
||||||
.ard-blocked {
|
|
||||||
color: rgb(219, 125, 48) !important;
|
|
||||||
background-color: rgba(0,0,0,0.85) !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss" src="../../src/res-common/panels.scss" scoped module></style>
|
|
||||||
<style lang="scss" src="../../src/res-common/common.scss" scoped module></style>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" style="position: relative">
|
|
||||||
<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-notification.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import { createApp } from 'vue';
|
|
||||||
import Notification from './Notification';
|
|
||||||
import mdiVue from 'mdi-vue/v3';
|
|
||||||
import * as mdijs from '@mdi/js';
|
|
||||||
|
|
||||||
// NOTE — this is in-player interface for ultrawidify
|
|
||||||
// it is injected into the page in UI.init()
|
|
||||||
|
|
||||||
createApp(Notification)
|
|
||||||
.use(mdiVue, {icons: mdijs})
|
|
||||||
.mount('#app');
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
$text-normal: #ddd;
|
|
||||||
$text-dim: #999;
|
|
||||||
$text-dark: #666;
|
|
||||||
$primary-color: #fb772a;
|
|
||||||
$primary: $primary-color;
|
|
||||||
$secondary-color: #e70c0c;
|
|
||||||
|
|
||||||
$input-background: #141414;
|
|
||||||
$input-border: #4e3527;
|
|
||||||
$page-background: #101010;
|
|
||||||
$popup-header-background: #7f1416;
|
|
||||||
|
|
||||||
$background-primary: #101010;
|
|
||||||
$selected-color: #f5cbaf;
|
|
||||||
$background-selected: #412d20;
|
|
||||||
|
|
||||||
|
|
||||||
$info-color: #bda9f3;
|
|
||||||
@ -1,443 +0,0 @@
|
|||||||
/* @import "colors.scss"; */
|
|
||||||
/*
|
|
||||||
@import "fonts.scss";
|
|
||||||
@import "flex.scss";
|
|
||||||
*/
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: $background-primary;
|
|
||||||
color: $text-normal;
|
|
||||||
font-family: 'Overpass', sans-serif;
|
|
||||||
font-size: 1.2em;
|
|
||||||
width: 100%;
|
|
||||||
min-height: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
border: 0px;
|
|
||||||
margin: 0px;
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main-window::before {
|
|
||||||
content: ' ';
|
|
||||||
color: #000;
|
|
||||||
position: absolute;
|
|
||||||
left: -0.5rem;
|
|
||||||
top: -0.5rem;
|
|
||||||
z-index: -1;
|
|
||||||
width: calc(100% + 1rem);
|
|
||||||
height: calc(100% + 1rem);
|
|
||||||
background-image: url('/csui/res/img/uw-window-bg-texture-3.webp');
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* STANDARD WIDTHS AND HEIGHTS */
|
|
||||||
.w100, .w-full {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h100, .h-full {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w24 {
|
|
||||||
width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.m-t-0-33em {
|
|
||||||
margin-top: 0.33em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.x-pad-1em {
|
|
||||||
padding-left: 1em;
|
|
||||||
padding-right: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* overflow stuff */
|
|
||||||
.overflow-y-auto {
|
|
||||||
overflow: auto;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
scrollbar-color: rgba($primary-color, 0.7) $background-primary;
|
|
||||||
}
|
|
||||||
.scrollbar-darker {
|
|
||||||
scrollbar-color: rgba($primary-color, 0.5) $background-primary;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 5px;
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background-color: rgba(#fa6, 0.5);
|
|
||||||
border-radius: 20px;
|
|
||||||
border: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* scrollbars for chrome/webkit */
|
|
||||||
.overflow-y-auto::-webkit-scrollbar {
|
|
||||||
width: 3px;
|
|
||||||
}
|
|
||||||
.overflow-y-auto::-webkit-scrollbar-track {
|
|
||||||
background: $background-primary;
|
|
||||||
}
|
|
||||||
.overflow-y-auto::-webkit-scrollbar-thumb {
|
|
||||||
background: rgba($primary-color, 0.7);
|
|
||||||
}
|
|
||||||
.overflow-y-auto::-webkit-scrollbar-button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.scrollbar-darker::-webkit-scrollbar-thumb {
|
|
||||||
background: rgba($primary-color, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-overflow {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-overflow-x {
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-overflow-y {
|
|
||||||
overflow-y: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .SELECTED CLASSES */
|
|
||||||
|
|
||||||
.selected-tab {
|
|
||||||
background-color: initial;
|
|
||||||
border-left: $primary-color 5px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-tab-secondary {
|
|
||||||
background-color: initial;
|
|
||||||
border-left: $secondary-color 3px solid !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* BASIC STYLING */
|
|
||||||
|
|
||||||
.description {
|
|
||||||
color: $text-dim;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-variant: small-caps;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label-secondary {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-box {
|
|
||||||
padding-top: 0.69rem;
|
|
||||||
padding-bottom: 0.69rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.indent {
|
|
||||||
padding-left: 4.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.row-padding {
|
|
||||||
padding-top: 0.69rem;
|
|
||||||
padding-bottom: 0.69rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
a, a:visited {
|
|
||||||
color: $primary-color;
|
|
||||||
}
|
|
||||||
a:hover {
|
|
||||||
color: lighten($primary-color, 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* INPUT FORMATTING */
|
|
||||||
input[type="number"], input[type="text"], input {
|
|
||||||
outline: none;
|
|
||||||
background-color: $input-background;
|
|
||||||
color: $text-normal;
|
|
||||||
padding: 0.1rem;
|
|
||||||
padding-top: 0.2rem;
|
|
||||||
padding-bottom: 0.1rem;
|
|
||||||
margin-left: 1rem;
|
|
||||||
border: 1px solid $input-border;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:disabled {
|
|
||||||
background: #444444;
|
|
||||||
color: darken($text-normal, 50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ELEMENT POSITIONING */
|
|
||||||
|
|
||||||
.row {
|
|
||||||
display: block;
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-row {
|
|
||||||
display: block;
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.float-left {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.float-right {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-block {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hide {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* TEXT FORMATTING (no colors) */
|
|
||||||
|
|
||||||
small {
|
|
||||||
font-size: 0.75em;
|
|
||||||
font-weight: 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
.small {
|
|
||||||
font-size: 0.75em;
|
|
||||||
font-weight: 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
.medium-small {
|
|
||||||
font-size: 0.85em;
|
|
||||||
font-weight: 200;
|
|
||||||
}
|
|
||||||
|
|
||||||
.smallcaps{
|
|
||||||
font-variant: small-caps;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center{
|
|
||||||
text-align: center;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.text-center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.invalid-input {
|
|
||||||
border: 1px solid #720 !important;
|
|
||||||
background-color: #410 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
.button {
|
|
||||||
/*display: inline-block;*/
|
|
||||||
border: 1px solid rgb(39, 39, 39);
|
|
||||||
margin-top: 3px;
|
|
||||||
margin-bottom: 3px;
|
|
||||||
color: $text-dim;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;;
|
|
||||||
|
|
||||||
background-color: rgba(11,11,11,0.75);
|
|
||||||
|
|
||||||
|
|
||||||
padding: 0.5rem 2rem;
|
|
||||||
background-color: rgba(11, 11, 11, 0.5);
|
|
||||||
border: 1px solid transparent;
|
|
||||||
user-select: none !important;
|
|
||||||
|
|
||||||
&.primary {
|
|
||||||
background-color: #fa6;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
&.danger {
|
|
||||||
background-color: #ff2211 !important;
|
|
||||||
color:#000;
|
|
||||||
}
|
|
||||||
&.disabled {
|
|
||||||
filter: saturate(0%);
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.selected, .setting-selected {
|
|
||||||
color: $selected-color !important;
|
|
||||||
background-color: $background-selected !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-tab {
|
|
||||||
color: $selected-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-selected {
|
|
||||||
border: 1px solid shade($selected-color, 25%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.button:hover {
|
|
||||||
color: lighten($selected-color, 10%);
|
|
||||||
background-color: lighten($background-selected, 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.disabled {
|
|
||||||
pointer-events: none;
|
|
||||||
/* color: #666; */
|
|
||||||
filter: contrast(50%) brightness(40%) grayscale(100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.disabled-button {
|
|
||||||
color: #666 !important;
|
|
||||||
cursor: not-allowed !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.disabled-button:hover {
|
|
||||||
color: #777 !important;
|
|
||||||
background-color: #222 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* BROWSER-SPECIFIC DISABLE */
|
|
||||||
.disabled-edge {
|
|
||||||
pointer-events: none !important;
|
|
||||||
filter: contrast(50%) brightness(40%) grayscale(100%) !important;
|
|
||||||
content: "NOT SUPPORTED IN THIS BROWSER";
|
|
||||||
}
|
|
||||||
.disabled-edge::after {
|
|
||||||
background-color: #333272;
|
|
||||||
color: #d8d9e6;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: .75em;
|
|
||||||
font-variant: small-caps;
|
|
||||||
padding-left: 5px;
|
|
||||||
padding-right: 5px;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** misc **/
|
|
||||||
|
|
||||||
.warning-color {
|
|
||||||
color: #d6ba4a;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning, .warning-lite {
|
|
||||||
color: #d6ba4a;
|
|
||||||
padding-left: 35px;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning::before, .warning-lite::before {
|
|
||||||
content: "⚠ ";
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
.warning::before {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 2.5em;
|
|
||||||
margin-left: -35px;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-button {
|
|
||||||
color: $info-color;
|
|
||||||
border: 1px solid $info-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
color: $info-color;
|
|
||||||
padding-left: 35px;
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
.info-color {
|
|
||||||
color: $info-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info::before {
|
|
||||||
content: "ⓘ";
|
|
||||||
display: inline-block;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-left: -35px;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.new::after {
|
|
||||||
content: "ⓘ";
|
|
||||||
color: $info-color !important;
|
|
||||||
display: inline-block;
|
|
||||||
font-weight: bold;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.experimental::after {
|
|
||||||
content: "Ⓔ";
|
|
||||||
color: #ffde12;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: .75em;
|
|
||||||
font-variant: small-caps;
|
|
||||||
padding-left: 5px;
|
|
||||||
padding-right: 5px;
|
|
||||||
margin-left: 10px;
|
|
||||||
margin-top: -4px;
|
|
||||||
}
|
|
||||||
.experimental:hover::after {
|
|
||||||
content: "Ⓔ Experimental";
|
|
||||||
color: #ffde12;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: .75em;
|
|
||||||
font-variant: small-caps;
|
|
||||||
padding-left: 5px;
|
|
||||||
padding-right: 5px;
|
|
||||||
margin-left: 10px;
|
|
||||||
margin-top: -4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sticky-bottom {
|
|
||||||
width: 100%;
|
|
||||||
position: sticky;
|
|
||||||
bottom: 0px;
|
|
||||||
background-color: rgba($background-primary, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rtl {
|
|
||||||
direction: rtl;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ltr {
|
|
||||||
direction: ltr;
|
|
||||||
}
|
|
||||||
.monospace {
|
|
||||||
font-family: 'Overpass Mono';
|
|
||||||
}
|
|
||||||
|
|
||||||
.pointer {
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
.flex {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-row {
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-col {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-auto {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-grow, .grow {
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
.flex-nogrow, .grow-0 {
|
|
||||||
flex-grow: 0;
|
|
||||||
}
|
|
||||||
.flex-shrink, .shrink {
|
|
||||||
flex-shrink: 1;
|
|
||||||
}
|
|
||||||
.flex-noshrink, .shrink-0 {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.items-center {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.items-baseline {
|
|
||||||
align-items: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-wrap {
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-center {
|
|
||||||
align-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-end {
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-cross-center {
|
|
||||||
justify-content: center;
|
|
||||||
justify-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-self-center {
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
@font-face {
|
|
||||||
font-family: 'overpass';
|
|
||||||
src: url('../../fonts/overpass-webfont/overpass-thin.woff2') format('woff2'); /* Super Modern Browsers */
|
|
||||||
font-weight: 200;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'overpass';
|
|
||||||
src: url('../../fonts/overpass-webfont/overpass-thin-italic.woff2') format('woff2');
|
|
||||||
font-weight: 200;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'overpass';
|
|
||||||
src: url('../../fonts/overpass-webfont/overpass-extralight.woff2') format('woff2');
|
|
||||||
font-weight: 300;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'overpass';
|
|
||||||
src: url('../../fonts/overpass-webfont/overpass-extralight-italic.woff2') format('woff2');
|
|
||||||
font-weight: 300;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'overpass';
|
|
||||||
src: url('../../fonts/overpass-webfont/overpass-light.woff2') format('woff2');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'overpass';
|
|
||||||
src: url('../../fonts/overpass-webfont/overpass-light-italic.woff2') format('woff2');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'overpass';
|
|
||||||
src: url('../../fonts/overpass-webfont/overpass-regular.woff2') format('woff2');
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'overpass';
|
|
||||||
src: url('../../fonts/overpass-webfont/overpass-italic.woff2') format('woff2');
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'overpass';
|
|
||||||
src: url('../../fonts/overpass-webfont/overpass-semibold.woff2') format('woff2');
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'overpass';
|
|
||||||
src: url('../../fonts/overpass-webfont/overpass-semibold-italic.woff2') format('woff2');
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'overpass';
|
|
||||||
src: url('../../fonts/overpass-webfont/overpass-bold.woff2') format('woff2');
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'overpass';
|
|
||||||
src: url('../../fonts/overpass-webfont/overpass-bold-italic.woff2') format('woff2');
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: url('/csui/res/fonts/overpass-webfont/overpass-thin.woff2') format('woff2'); /* Super Modern Browsers */
|
|
||||||
font-weight: 200;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: url('/csui/res/fonts/overpass-webfont/overpass-thin-italic.woff2') format('woff2');
|
|
||||||
font-weight: 200;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: url('/csui/res/fonts/overpass-webfont/overpass-extralight.woff2') format('woff2');
|
|
||||||
font-weight: 300;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: url('/csui/res/fonts/overpass-webfont/overpass-extralight-italic.woff2') format('woff2');
|
|
||||||
font-weight: 300;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: url('/csui/res/fonts/overpass-webfont/overpass-light.woff2') format('woff2');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: url('/csui/res/fonts/overpass-webfont/overpass-light-italic.woff2') format('woff2');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: url('/csui/res/fonts/overpass-webfont/overpass-regular.woff2') format('woff2');
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: url('/csui/res/fonts/overpass-webfont/overpass-italic.woff2') format('woff2');
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: url('/csui/res/fonts/overpass-webfont/overpass-semibold.woff2') format('woff2');
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: url('/csui/res/fonts/overpass-webfont/overpass-semibold-italic.woff2') format('woff2');
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: url('/csui/res/fonts/overpass-webfont/overpass-bold.woff2') format('woff2');
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: url('/csui/res/fonts/overpass-webfont/overpass-bold-italic.woff2') format('woff2');
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass mono';
|
|
||||||
src: url('/csui/res/fonts/overpass-mono-webfont/overpass-mono-light.woff2') format('woff2');
|
|
||||||
font-weight: 300;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass mono';
|
|
||||||
src: url('/csui/res/fonts/overpass-mono-webfont/overpass-mono-regular.woff2') format('woff2');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass mono';
|
|
||||||
src: url('/csui/res/fonts/overpass-mono-webfont/overpass-mono-semibold.woff2') format('woff2');
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass mono';
|
|
||||||
src: url('/csui/res/fonts/overpass-mono-webfont/overpass-mono-bold.woff2') format('woff2');
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
@ -1,38 +0,0 @@
|
|||||||
/* @import 'flex.scss';
|
|
||||||
@import 'colors.scss';
|
|
||||||
@import 'common.scss';
|
|
||||||
*/
|
|
||||||
|
|
||||||
* {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
}
|
|
||||||
|
|
||||||
.uw-ultrawidify-container-root {
|
|
||||||
/*
|
|
||||||
// here's the defaults:
|
|
||||||
// all: initial;
|
|
||||||
// * {
|
|
||||||
// all: unset;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// here's things that we don't want as defaults
|
|
||||||
// (must come after the all: declaration, otherwise
|
|
||||||
// all: declaration overrides everything.)
|
|
||||||
|
|
||||||
// we put our UI _over_ site's player:
|
|
||||||
*/
|
|
||||||
z-index: 999999;
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
background-color: none transparent !important;
|
|
||||||
|
|
||||||
/* Ensure we're display:block */
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
/* we are click-through by default: */
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
## License
|
|
||||||
|
|
||||||
Copyright 2015 Red Hat, Inc.,
|
|
||||||
This Font Software is dual licensed and available under the SIL Open Font License, Version 1.1. and also the LGPL 2.1
|
|
||||||
|
|
||||||
The Open Font license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
|
|
||||||
|
|
||||||
The GNU LGPL can be found at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### SIL OPEN FONT LICENSE
|
|
||||||
Version 1.1 - 26 February 2007
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### PREAMBLE
|
|
||||||
The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others.
|
|
||||||
|
|
||||||
The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives.
|
|
||||||
|
|
||||||
#### DEFINITIONS
|
|
||||||
“Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation.
|
|
||||||
|
|
||||||
“Reserved Font Name” refers to any names specified as such after the copyright statement(s).
|
|
||||||
|
|
||||||
“Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s).
|
|
||||||
|
|
||||||
“Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment.
|
|
||||||
|
|
||||||
“Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software.
|
|
||||||
|
|
||||||
#### PERMISSION & CONDITIONS
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions:
|
|
||||||
|
|
||||||
1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself.
|
|
||||||
|
|
||||||
2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user.
|
|
||||||
|
|
||||||
3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users.
|
|
||||||
|
|
||||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission.
|
|
||||||
|
|
||||||
5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software.
|
|
||||||
|
|
||||||
#### TERMINATION
|
|
||||||
This license becomes null and void if any of the above conditions are not met.
|
|
||||||
|
|
||||||
#### DISCLAIMER
|
|
||||||
THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.
|
|
||||||
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 273 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 10 KiB |
@ -1,318 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="popup-panel flex flex-col uw-clickable h-full main-window relative"
|
|
||||||
>
|
|
||||||
<div class="popup-window-header">
|
|
||||||
<div class="header-title">
|
|
||||||
<div class="popup-title">Ultrawidify <small>{{settings?.active?.version}} - {{BrowserDetect.processEnvChannel}}</small></div>
|
|
||||||
<div class="site-support-info flex flex-row">
|
|
||||||
<div class="site-support-site">{{site}}</div>
|
|
||||||
<SupportLevelIndicator
|
|
||||||
v-if="inPlayer"
|
|
||||||
:siteSupportLevel="siteSupportLevel"
|
|
||||||
>
|
|
||||||
</SupportLevelIndicator>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="header-buttons">
|
|
||||||
<div
|
|
||||||
class="header-button close-button"
|
|
||||||
@click="$emit('close')"
|
|
||||||
>
|
|
||||||
<mdicon name="close" :size="36"></mdicon>
|
|
||||||
</div>
|
|
||||||
<!-- <a >{{preventClose ? 'allow auto-close' : 'prevent auto-close'}}</a> -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SettingsWindowContent
|
|
||||||
:tabs="tabs"
|
|
||||||
:selectedTab="selectedTab"
|
|
||||||
:settings="settings"
|
|
||||||
:eventBus="eventBus"
|
|
||||||
:logger="logger"
|
|
||||||
:in-player="inPlayer"
|
|
||||||
:site="site"
|
|
||||||
@selectTab="selectTab"
|
|
||||||
@preventClose="setPreventClose"
|
|
||||||
></SettingsWindowContent>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import DebugPanel from './PlayerUiPanels/DebugPanel.vue'
|
|
||||||
import AutodetectionSettingsPanel from './PlayerUiPanels/AutodetectionSettingsPanel.vue'
|
|
||||||
import BaseExtensionSettings from './PlayerUiPanels/BaseExtensionSettings.vue'
|
|
||||||
import PlayerDetectionPanel from './PlayerUiPanels/PlayerDetectionPanel.vue'
|
|
||||||
import VideoSettings from './PlayerUiPanels/VideoSettings.vue'
|
|
||||||
import BrowserDetect from '../../ext/conf/BrowserDetect'
|
|
||||||
import ChangelogPanel from './PlayerUiPanels/ChangelogPanel.vue'
|
|
||||||
import AboutPanel from '@csui/src/PlayerUiPanels/AboutPanel.vue'
|
|
||||||
import PlayerUiSettings from './PlayerUiPanels/PlayerUiSettings.vue'
|
|
||||||
import ResetBackupPanel from './PlayerUiPanels/ResetBackupPanel.vue'
|
|
||||||
import SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
VideoSettings,
|
|
||||||
PlayerDetectionPanel,
|
|
||||||
BaseExtensionSettings,
|
|
||||||
AutodetectionSettingsPanel,
|
|
||||||
DebugPanel,
|
|
||||||
PlayerUiSettings,
|
|
||||||
ChangelogPanel,
|
|
||||||
AboutPanel,
|
|
||||||
SupportLevelIndicator,
|
|
||||||
ResetBackupPanel,
|
|
||||||
},
|
|
||||||
mixins: [],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
statusFlags: {
|
|
||||||
hasDrm: undefined,
|
|
||||||
},
|
|
||||||
BrowserDetect: BrowserDetect,
|
|
||||||
preventClose: false,
|
|
||||||
siteSettings: null,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: [
|
|
||||||
'settings',
|
|
||||||
'eventBus',
|
|
||||||
'logger',
|
|
||||||
'in-player',
|
|
||||||
'site',
|
|
||||||
'defaultTab'
|
|
||||||
],
|
|
||||||
computed: {
|
|
||||||
// LPT: NO ARROW FUNCTIONS IN COMPUTED,
|
|
||||||
// IS SUPER HARAM
|
|
||||||
// THINGS WILL NOT WORK IF YOU USE ARROWS
|
|
||||||
siteSupportLevel() {
|
|
||||||
return (this.site && this.siteSettings) ? this.siteSettings.data.type || 'no-support' : 'waiting';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.settings.listenAfterChange(this.setDebugTabVisibility);
|
|
||||||
|
|
||||||
if (this.defaultTab) {
|
|
||||||
this.selectedTab = this.defaultTab;
|
|
||||||
}
|
|
||||||
this.siteSettings = this.settings.getSiteSettings({site: this.site});
|
|
||||||
this.tabs.find(x => x.id === 'changelog').highlight = !this.settings.active?.whatsNewChecked;
|
|
||||||
|
|
||||||
this.eventBus.subscribe(
|
|
||||||
'uw-show-ui',
|
|
||||||
{
|
|
||||||
source: this,
|
|
||||||
function: () => {
|
|
||||||
if (this.inPlayer) {
|
|
||||||
return; // show-ui is only intended for global overlay
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
this.setDebugTabVisibility();
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
this.settings.removeListenerAfterChange(this.setDebugTabVisibility);
|
|
||||||
this.eventBus.unsubscribeAll(this);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
/**
|
|
||||||
* Gets URL of the browser settings page (i think?)
|
|
||||||
*/
|
|
||||||
getUrl(url) {
|
|
||||||
return BrowserDetect.getURL(url);
|
|
||||||
},
|
|
||||||
selectTab(tab) {
|
|
||||||
this.selectedTab = tab;
|
|
||||||
},
|
|
||||||
setPreventClose(bool) {
|
|
||||||
this.preventClose = bool;
|
|
||||||
this.$emit('preventClose', bool);
|
|
||||||
},
|
|
||||||
setDebugTabVisibility() {
|
|
||||||
const debugTab = this.tabs.find( x => x.id === 'debugging');
|
|
||||||
if (debugTab) {
|
|
||||||
debugTab.hidden = !this.settings.active.ui.devMode;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
/*@import '../res/css/uwui-base.scss';
|
|
||||||
@import '../res/css/colors.scss';
|
|
||||||
@import '../res/css/font/overpass.css';
|
|
||||||
@import '../res/css/font/overpass-mono.css';
|
|
||||||
@import '../res/css/common.scss';
|
|
||||||
@import '../src/res-common/_variables';*/
|
|
||||||
|
|
||||||
|
|
||||||
.tab-row {
|
|
||||||
width: 22rem;
|
|
||||||
flex-grow: 0;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-main {
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
.warning-area {
|
|
||||||
flex-grow: 0;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel-content {
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 1;
|
|
||||||
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning-box {
|
|
||||||
background: rgb(255, 174, 107);
|
|
||||||
color: #000;
|
|
||||||
margin: 1rem;
|
|
||||||
padding: 1rem;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.icon-container {
|
|
||||||
margin-right: 1rem;
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-grow: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: rgba(0,0,0,0.7);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.site-support-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: bottom;
|
|
||||||
|
|
||||||
.site-support-site {
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup-panel {
|
|
||||||
background-color: rgba(0,0,0,0.50);
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
.popup-window-header {
|
|
||||||
padding: 1rem;
|
|
||||||
background-color: rgba(5,5,5, 0.75);
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
flex: 1 1;
|
|
||||||
}
|
|
||||||
.header-buttons {
|
|
||||||
flex: 0 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
.header-button {
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
&.button-active {
|
|
||||||
background-color: #fa6;
|
|
||||||
color: #000;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.close-button {
|
|
||||||
color: #f00;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #fa6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tab-row {
|
|
||||||
background-color: rgba(11,11,11, 0.75);
|
|
||||||
|
|
||||||
.tab {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
padding: 2rem;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
height: 6rem;
|
|
||||||
|
|
||||||
border-bottom: 1px solid rgba(128, 128, 128, 0.5);
|
|
||||||
border-top: 1px solid rgba(128, 128, 128, 0.5);
|
|
||||||
opacity: 0.5;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
&.active {
|
|
||||||
opacity: 1.0;
|
|
||||||
background-color: $primaryBg;
|
|
||||||
color: rgb(255, 174, 107);
|
|
||||||
border-bottom: 1px solid rgba(116, 78, 47, 0.5);
|
|
||||||
border-top: 1px solid rgba(116, 78, 47, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-container {
|
|
||||||
width: 64px;
|
|
||||||
flex-grow: 0;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.label {
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-shrink: 1;
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.highlight-tab {
|
|
||||||
opacity: 0.9;
|
|
||||||
color: #eee;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.popup-title, .popup-title h1 {
|
|
||||||
font-size: 48px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
white-space: pre-wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -1,122 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col w-full h-full gap-2">
|
|
||||||
<h1>About Ultrawidify</h1>
|
|
||||||
<p>
|
|
||||||
Ultrawidify version: <b>{{addonVersion}}</b><br>
|
|
||||||
Install source: <b>{{addonSource}}</b>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="flex flex-row gap-2 bg-black">
|
|
||||||
<div class="w-[1/2]" style="width: 50%">
|
|
||||||
<h2>Report a problem</h2>
|
|
||||||
<p>
|
|
||||||
Please report <strike>undocumented features</strike> bugs using one of the following options (in order of preference):
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li> <a target="_blank" href="https://github.com/tamius-han/ultrawidify/issues"><b>Github (preferred)</b></a><br/></li>
|
|
||||||
<li>Email: <a target="_blank" :href="mailtoLink">tamius.han@gmail.com</a></li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
When reporting bugs, please include the following information:
|
|
||||||
</p>
|
|
||||||
<ul>
|
|
||||||
<li>Extension version</li>
|
|
||||||
<li>Which browser you're using¹</li>
|
|
||||||
<li>Which extension store you installed extension from (install source)²</li>
|
|
||||||
<li>Which site you're having the issue on (preferably link. For issues with autodetection, please also include a timestamp)</li>
|
|
||||||
<li>What do I need to do in order to make the issue happen</li>
|
|
||||||
<li>Please include a screenshot of the problem. It usually helps.</li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
<small>
|
|
||||||
[1] If using anything other than Firefox, Chrome, or Edge, please check if issue also happens in Chrome or Edge. Bugs affecting Opera will only get fixed if they also affect Chrome, because <a target="_blank" href="https://stuff.tamius.net/sacred-texts/2024/06/08/why-im-boycotting-opera/">I'm salty</a>.<br/>
|
|
||||||
[2] It is recommended that users of Microsoft Edge install Ultrawidify from the Microsoft Edge Addons source
|
|
||||||
</small>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="w-[1/2]" style="width: 50%">
|
|
||||||
<h2>Thank you monies</h2>
|
|
||||||
<p>
|
|
||||||
If you think I deserve money for the work I did up to this point, you can bankroll my caffeine addiction.
|
|
||||||
</p>
|
|
||||||
<p class="text-center">
|
|
||||||
<a class="donate" href="https://www.paypal.com/paypalme/tamius" target="_blank">Donate on Paypal</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
I also have <a href="https://instagram.com/shaman_urkog" target="_blank">instagram with nerdy shit</a> <small>(mini painting + various fantasy events)</small>.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import BrowserDetect from '../../../ext/conf/BrowserDetect';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
BrowserDetect,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
// reminder — webextension-polyfill doesn't seem to work in vue!
|
|
||||||
addonVersion: BrowserDetect.firefox ? chrome.runtime.getManifest().version : chrome.runtime.getManifest().version,
|
|
||||||
addonSource: BrowserDetect.processEnvVersion,
|
|
||||||
mailtoLink: '',
|
|
||||||
redditLink: '',
|
|
||||||
showEasterEgg: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async created() {
|
|
||||||
const messageTemplate = encodeURIComponent(
|
|
||||||
`Describe your issue in more detail. In case of misaligned videos, please provide screenshots. When reporting\
|
|
||||||
issues with autodetection not detecting aspect ratio correctly, please provide a link with timestamp to the\
|
|
||||||
problematic video at the time where the problem happens. You may delete this paragraph, as it's only a template.
|
|
||||||
|
|
||||||
|
|
||||||
Extension info (AUTOGENERATED — DO NOT CHANGE OR REMOVE):
|
|
||||||
* Build: ${BrowserDetect.processEnvBrowser}-${this.addonVersion}-stable
|
|
||||||
|
|
||||||
Browser-related stuff (please ensure this section is correct):
|
|
||||||
* User Agent string: ${window.navigator.userAgent}
|
|
||||||
* vendor: ${window.navigator.vendor}
|
|
||||||
* Operating system: ${window.navigator.platform}
|
|
||||||
`
|
|
||||||
);
|
|
||||||
this.mailtoLink = `mailto:tamius.han@gmail.com?subject=%5BUltrawidify%5D%20ENTER%20SUMMARY%20OF%20YOUR%20ISSUE%20HERE&body=${messageTemplate}`;
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.flex {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.flex-row {
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
p, li {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
small {
|
|
||||||
opacity: 0.5;
|
|
||||||
font-size: 0.8em;
|
|
||||||
}
|
|
||||||
a {
|
|
||||||
color: #fa6;
|
|
||||||
}
|
|
||||||
.text-center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.donate {
|
|
||||||
margin: 1rem;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
background-color: #fa6;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,176 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="alignment-box" :class="{large: large}">
|
|
||||||
<div
|
|
||||||
class="col top left"
|
|
||||||
@click="align(VideoAlignment.Left, VideoAlignment.Top)"
|
|
||||||
>
|
|
||||||
<div class="alignment-ui"></div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="col top center"
|
|
||||||
@click="align(VideoAlignment.Center, VideoAlignment.Top)"
|
|
||||||
>
|
|
||||||
<div class="alignment-ui"></div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="col top right"
|
|
||||||
@click="align(VideoAlignment.Right, VideoAlignment.Top)"
|
|
||||||
>
|
|
||||||
<div class="alignment-ui"></div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="col ycenter left"
|
|
||||||
@click="align(VideoAlignment.Left, VideoAlignment.Center)"
|
|
||||||
>
|
|
||||||
<div class="alignment-ui"></div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="col ycenter center"
|
|
||||||
@click="align(VideoAlignment.Center, VideoAlignment.Center)"
|
|
||||||
>
|
|
||||||
<div class="alignment-ui"></div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="col ycenter right"
|
|
||||||
@click="align(VideoAlignment.Right, VideoAlignment.Center)"
|
|
||||||
>
|
|
||||||
<div class="alignment-ui"></div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="col bottom left"
|
|
||||||
@click="align(VideoAlignment.Left, VideoAlignment.Bottom)"
|
|
||||||
>
|
|
||||||
<div class="alignment-ui"></div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="col bottom center"
|
|
||||||
@click="align(VideoAlignment.Center, VideoAlignment.Bottom)"
|
|
||||||
>
|
|
||||||
<div class="alignment-ui"></div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="col bottom right"
|
|
||||||
@click="align(VideoAlignment.Right, VideoAlignment.Bottom)"
|
|
||||||
>
|
|
||||||
<div class="alignment-ui"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: [
|
|
||||||
'eventBus',
|
|
||||||
'large',
|
|
||||||
],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
VideoAlignment: VideoAlignmentType
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
align(alignmentX, alignmentY) {
|
|
||||||
this.eventBus?.sendToTunnel('set-alignment', {x: alignmentX, y: alignmentY})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped module>
|
|
||||||
.alignment-box {
|
|
||||||
aspect-ratio: 1;
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
gap: 0.5rem;
|
|
||||||
|
|
||||||
&.large {
|
|
||||||
max-width: 15rem;
|
|
||||||
|
|
||||||
.col {
|
|
||||||
width: 4rem;
|
|
||||||
height: 4rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.col {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
background-color: rgba(0,0,0,0.25);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
padding: 0.5rem;
|
|
||||||
width: 1.5rem;
|
|
||||||
height: 1.5rem;
|
|
||||||
|
|
||||||
border-bottom: 1px solid transparent;
|
|
||||||
|
|
||||||
.alignment-ui {
|
|
||||||
|
|
||||||
width: 50%;
|
|
||||||
height: 50%;
|
|
||||||
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 0px;
|
|
||||||
border-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(0,0,0,0.5);
|
|
||||||
border-bottom: 1px solid #fa6;
|
|
||||||
|
|
||||||
.alignment-ui {
|
|
||||||
border-color: #fa6 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.center.ycenter {
|
|
||||||
.alignment-ui {
|
|
||||||
background-color: #fa6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.top {
|
|
||||||
align-items: flex-start;
|
|
||||||
|
|
||||||
.alignment-ui {
|
|
||||||
border-top-width: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.bottom {
|
|
||||||
align-items: flex-end;
|
|
||||||
|
|
||||||
.alignment-ui {
|
|
||||||
border-bottom-width: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.left {
|
|
||||||
justify-content: flex-start;
|
|
||||||
|
|
||||||
.alignment-ui {
|
|
||||||
border-left-width: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.right {
|
|
||||||
justify-content: flex-end;
|
|
||||||
|
|
||||||
.alignment-ui {
|
|
||||||
border-right-width: 3px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.center.ycenter {
|
|
||||||
|
|
||||||
.alignment-ui {
|
|
||||||
width: 25%;
|
|
||||||
height: 25%;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,486 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col tab-root">
|
|
||||||
<div class="flex flex-row flex-wrap">
|
|
||||||
|
|
||||||
<!-- AARD performance metrics -->
|
|
||||||
<div>
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<h1>Automatic Aspect Ratio Detection</h1>
|
|
||||||
</div>
|
|
||||||
<div class="aard-settings-group">
|
|
||||||
|
|
||||||
<div class="settings-segment">
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<div class="label">Autodetection mode (requires page reload)</div>
|
|
||||||
<div class="select">
|
|
||||||
<select v-model="settings.active.aard.useLegacy" @change="aardLegacyModeChanged">
|
|
||||||
<option :value="true">Legacy</option>
|
|
||||||
<option :value="false">Experimental</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<div class="label">Autodetection frequency (time between samples)</div>
|
|
||||||
<div class="range-input">
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
:value="Math.log(aardSettings.timers.playing)"
|
|
||||||
@change="setArCheckFrequency($event.target.value)"
|
|
||||||
min="2.3"
|
|
||||||
max="9.3"
|
|
||||||
step="0.01"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
v-model="aardSettings.timers.playing"
|
|
||||||
@change="setArCheckFrequency($event.target.value)"
|
|
||||||
class="input"
|
|
||||||
type="text"
|
|
||||||
>
|
|
||||||
<div class="unit">ms</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<div class="label">Reduced autodetection frequency</div>
|
|
||||||
<div class="range-input">
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
:value="Math.log(aardSettings.timers.playingReduced)"
|
|
||||||
@change="setArCheckFrequency($event.target.value, 'playingReduced')"
|
|
||||||
min="2.3"
|
|
||||||
max="9.3"
|
|
||||||
step="0.01"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
v-model="aardSettings.timers.playingReduced"
|
|
||||||
@change="setArCheckFrequency($event.target.value, 'playingReduced')"
|
|
||||||
class="input"
|
|
||||||
type="text"
|
|
||||||
>
|
|
||||||
<div class="unit">ms</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<div class="label">Poll for aspect ratio changes in background tabs:</div>
|
|
||||||
<div class="select">
|
|
||||||
<select v-model="aardSettings.polling.runInBackgroundTabs" @change="settings.saveWithoutReload">
|
|
||||||
<option :value="AardPollingOptions.No">Never</option>
|
|
||||||
<option :value="AardPollingOptions.Reduced">Use reduced polling rate</option>
|
|
||||||
<option :value="AardPollingOptions.Full">Use normal polling rate</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="aardSettings.polling.runInBackgroundTabs === AardPollingOptions.Full" class="hint warn">
|
|
||||||
Using normal polling rate in background tabs is NOT recommended.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<div class="label">Poll for aspect ratio changes in small players:</div>
|
|
||||||
<div class="select">
|
|
||||||
<select v-model="aardSettings.polling.runOnSmallVideos" @change="settings.saveWithoutReload">
|
|
||||||
<option :value="AardPollingOptions.No">Never</option>
|
|
||||||
<option :value="AardPollingOptions.Reduced">Use reduced polling rate</option>
|
|
||||||
<option :value="AardPollingOptions.Full">Use normal polling rate</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="aardSettings.polling.runOnSmallVideos === AardPollingOptions.Full" class="hint warn">
|
|
||||||
Using normal polling rate on small videos is NOT recommended.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<div class="label">Stop autodetection after first detection:</div>
|
|
||||||
<div class="select">
|
|
||||||
<select v-model="aardSettings.autoDisable.onFirstChange" @change="settings.saveWithoutReload">
|
|
||||||
<option :value="true">Yes</option>
|
|
||||||
<option :value="false">No</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field" :class="{disabled: aardSettings.autoDisable.onFirstChange}">
|
|
||||||
<div class="label">Stop autodetection if aspect ratio doesn't change for some time:</div>
|
|
||||||
<div class="select">
|
|
||||||
<select v-model="aardSettings.autoDisable.ifNotChanged" @change="settings.saveWithoutReload">
|
|
||||||
<option :value="true">Yes</option>
|
|
||||||
<option :value="false">No</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="aardSettings.autoDisable.ifNotChanged" class="field">
|
|
||||||
<div class="label">Stop autodetection if aspect ratio doesn't change for:</div>
|
|
||||||
<div class="range-input">
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
:value="Math.log(aardSettings.autoDisable.ifNotChangedTimeout / 10)"
|
|
||||||
@change="setAutoDisableTimeout($event.target.value, 10)"
|
|
||||||
min="2.3"
|
|
||||||
max="9.3"
|
|
||||||
step="0.01"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
:value="aardSettings.autoDisable.ifNotChangedTimeout / 1000"
|
|
||||||
@change="setAutoDisableTimeout($event.target.value, 1000)"
|
|
||||||
class="input"
|
|
||||||
type="text"
|
|
||||||
>
|
|
||||||
<div class="unit">s</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<div class="label">Autodetection canvas type:</div>
|
|
||||||
<div class="select">
|
|
||||||
<select v-model="aardSettings.aardType" @change="settings.saveWithoutReload">
|
|
||||||
<option value="auto">Automatic</option>
|
|
||||||
<option value="webgl">WebGL only</option>
|
|
||||||
<option value="legacy">Legacy / fallback</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<div class="label">Maximum allowed vertical video misalignment:</div>
|
|
||||||
<div class="input">
|
|
||||||
<input v-model="aardSettings.allowedMisaligned" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="hint">
|
|
||||||
Ultrawidify detects letterbox only if video is vertically centered. Some people are bad at vertically
|
|
||||||
centering the content, though. This is how off-center the video can be before autodetection will
|
|
||||||
refuse to crop it (% of total height).
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="settings-segment">
|
|
||||||
<div class="warning-area">
|
|
||||||
Subtitle detection is experimental to high heaven.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<div class="label">Subtitle detection:</div>
|
|
||||||
<div class="select">
|
|
||||||
<select v-model="aardSettings.subtitles.subtitleCropMode" @change="settings.saveWithoutReload">
|
|
||||||
<!-- <option :value="AardSubtitleCropMode.DisableScan">Do not detect subtitles</option> -->
|
|
||||||
<option :value="AardSubtitleCropMode.ResetAR">Do not crop while subtitles are on screen</option>
|
|
||||||
<option :value="AardSubtitleCropMode.ResetAndDisable">Stop autodetection if subtitles are detected</option>
|
|
||||||
<option :value="AardSubtitleCropMode.CropSubtitles">Always crop subtitles</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="aardSettings.subtitles.subtitleCropMode === AardSubtitleCropMode.ResetAR" class="field">
|
|
||||||
<div class="label">Wait before resuming detection:</div>
|
|
||||||
<div class="range-input">
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
:value="Math.log(aardSettings.subtitles.resumeAfter / 10)"
|
|
||||||
@change="setSubtitleTimeout($event.target.value, 10)"
|
|
||||||
min="2.3"
|
|
||||||
max="9.3"
|
|
||||||
step="0.01"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
:value="aardSettings.subtitles.resumeAfter / 1000"
|
|
||||||
@change="setSubtitleTimeout($event.target.value, 1000)"
|
|
||||||
class="input"
|
|
||||||
type="text"
|
|
||||||
>
|
|
||||||
<div class="unit">s</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="settings.active.ui.devMode" class="settings-segment">
|
|
||||||
<p>
|
|
||||||
<b>Debug options</b>
|
|
||||||
</p>
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<div>
|
|
||||||
<div>
|
|
||||||
<button @click="eventBus.sendToTunnel('aard-enable-debug', true)">Show debug overlay</button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div class="label">Show debug overlay on startup</div>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
v-model="settings.active.ui.dev.aardDebugOverlay.showOnStartup"
|
|
||||||
@change="settings.saveWithoutReload"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<JsonEditor
|
|
||||||
v-model="settingsJson"
|
|
||||||
>
|
|
||||||
</JsonEditor>
|
|
||||||
<button @click="saveDebugUiSettings">Save debug UI settings</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Button from '../components/Button.vue'
|
|
||||||
import KeyboardShortcutParser from '../../../common/js/KeyboardShortcutParser';
|
|
||||||
import ShortcutButton from '../components/ShortcutButton';
|
|
||||||
import EditShortcutButton from '../components/EditShortcutButton';
|
|
||||||
import BrowserDetect from '../../../ext/conf/BrowserDetect';
|
|
||||||
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
|
||||||
import StretchType from '../../../common/enums/StretchType.enum';
|
|
||||||
import CropModePersistence from '../../../common/enums/CropModePersistence.enum';
|
|
||||||
import AlignmentOptionsControlComponent from './AlignmentOptionsControlComponent.vue';
|
|
||||||
import JsonEditor from '@csui/src/components/JsonEditor';
|
|
||||||
import {AardPollingOptions} from '@src/ext/lib/aard/enums/aard-polling-options.enum';
|
|
||||||
import {AardSubtitleCropMode} from '@src/ext/lib/aard/enums/aard-subtitle-crop-mode.enum';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
ShortcutButton,
|
|
||||||
EditShortcutButton,
|
|
||||||
Button,
|
|
||||||
AlignmentOptionsControlComponent,
|
|
||||||
JsonEditor
|
|
||||||
},
|
|
||||||
mixins: [
|
|
||||||
],
|
|
||||||
props: [
|
|
||||||
'settings',
|
|
||||||
'frame',
|
|
||||||
'eventBus',
|
|
||||||
'site'
|
|
||||||
],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
exec: null,
|
|
||||||
performanceData: {},
|
|
||||||
graphRefreshInterval: undefined,
|
|
||||||
settingsJson: {},
|
|
||||||
|
|
||||||
// enums n stuff
|
|
||||||
AardPollingOptions,
|
|
||||||
AardSubtitleCropMode
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
aardSettings() {
|
|
||||||
return this.settings.active.aard.useLegacy ? this.settings.active.aardLegacy : this.settings.active.aard;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.eventBus.subscribe(
|
|
||||||
'uw-config-broadcast',
|
|
||||||
{
|
|
||||||
source: this,
|
|
||||||
function: (config) => this.handleConfigBroadcast(config)
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.eventBus.sendToTunnel('get-aard-timing');
|
|
||||||
// this.graphRefreshInterval = setInterval(() => this.eventBus.sendToTunnel('get-aard-timing'), 500);
|
|
||||||
this.resetSettingsEditor();
|
|
||||||
},
|
|
||||||
destroyed() {
|
|
||||||
this.eventBus.unsubscribeAll(this);
|
|
||||||
clearInterval(this.graphRefreshInterval);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async openOptionsPage() {
|
|
||||||
BrowserDetect.runtime.openOptionsPage();
|
|
||||||
},
|
|
||||||
setArCheckFrequency(event, timer) {
|
|
||||||
this.aardSettings.timers[timer ?? 'playing'] = Math.floor(Math.pow(Math.E, event));
|
|
||||||
this.settings.saveWithoutReload();
|
|
||||||
},
|
|
||||||
setAutoDisableTimeout(event, multiplier) {
|
|
||||||
this.aardSettings.autoDisable.ifNotChangedTimeout = Math.floor(Math.pow(Math.E, event)) * multiplier;
|
|
||||||
this.settings.saveWithoutReload();
|
|
||||||
},
|
|
||||||
setSubtitleTimeout(event, multiplier) {
|
|
||||||
this.aardSettings.subtitles.resumeAfter = Math.floor(Math.pow(Math.E, event)) * multiplier;
|
|
||||||
this.settings.saveWithoutReload();
|
|
||||||
},
|
|
||||||
refreshGraph() {
|
|
||||||
this.eventBus.sendToTunnel('get-aard-timing');
|
|
||||||
},
|
|
||||||
handleConfigBroadcast(data) {
|
|
||||||
if (data.type === 'aard-performance-data') {
|
|
||||||
this.performanceData = data.performanceData;
|
|
||||||
this.$nextTick( () => this.$forceUpdate() );
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resetSettingsEditor() {
|
|
||||||
this.settingsJson = JSON.parse(JSON.stringify(this.settings?.active.ui.dev.aardDebugOverlay ?? {}));
|
|
||||||
},
|
|
||||||
saveDebugUiSettings() {
|
|
||||||
this.settings.active.ui.dev.aardDebugOverlay = JSON.parse(JSON.stringify(this.settingsJson));
|
|
||||||
this.settings.saveWithoutReload();
|
|
||||||
},
|
|
||||||
aardLegacyModeChanged() {
|
|
||||||
this.settings.save();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" src="../../res/css/flex.scss" scoped module></style>
|
|
||||||
<style lang="scss" src="@csui/src/res-common/panels.scss" scoped module></style>
|
|
||||||
<style lang="scss" src="@csui/src/res-common/common.scss" scoped module></style>
|
|
||||||
|
|
||||||
<style lang="scss" scoped module>
|
|
||||||
|
|
||||||
.performance-graph-container {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 8rem;
|
|
||||||
padding: 1rem;
|
|
||||||
|
|
||||||
.performance-graph {
|
|
||||||
border: 1px solid #fa6;
|
|
||||||
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
/* graph is 100 ms wide */
|
|
||||||
.time-budget {
|
|
||||||
height: 100%;
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
box-sizing: border-box;
|
|
||||||
|
|
||||||
z-index: 100;
|
|
||||||
|
|
||||||
&.hz144 {
|
|
||||||
width: 6.9%;
|
|
||||||
}
|
|
||||||
&.hz120 {
|
|
||||||
width: 1.39%;
|
|
||||||
}
|
|
||||||
&.hz60 {
|
|
||||||
width: 8.33%;
|
|
||||||
border-right: 2px solid rgb(96, 96, 227);
|
|
||||||
&::after {
|
|
||||||
content: '60fps';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: -2px;
|
|
||||||
|
|
||||||
border-right: 2px solid rgb(96, 96, 227);
|
|
||||||
|
|
||||||
transform: translateY(-100%);
|
|
||||||
|
|
||||||
font-size: 0.6rem;
|
|
||||||
padding-right: 0.25rem;
|
|
||||||
text-transform: small-caps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.hz30 {
|
|
||||||
width: 16.67%;
|
|
||||||
border-right: 2px solid #fb772a;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '30fps';
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: -2px;
|
|
||||||
|
|
||||||
border-right: 2px solid #fb772a;
|
|
||||||
|
|
||||||
transform: translateY(-100%);
|
|
||||||
|
|
||||||
font-size: 0.6rem;
|
|
||||||
padding-right: 0.25rem;
|
|
||||||
text-transform: small-caps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.hz24 {
|
|
||||||
width: 8.33%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.bar-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
height: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stats {
|
|
||||||
background-color: rgba(0, 0, 0, 0.544);
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-family: 'Overpass Mono';
|
|
||||||
z-index: 11010;
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
z-index: 11011;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
width: 0.75rem;
|
|
||||||
height: 0.75rem;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.draw::before {
|
|
||||||
background-color: #fb772a;
|
|
||||||
}
|
|
||||||
&.draw-blackframe::before {
|
|
||||||
background-color: #e70c0c;
|
|
||||||
}
|
|
||||||
&.processing::before {
|
|
||||||
background-color: rgb(176, 167, 239);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bar {
|
|
||||||
width: 100%;
|
|
||||||
height: 0.69rem;
|
|
||||||
background-color: #000;
|
|
||||||
|
|
||||||
overflow: hidden;
|
|
||||||
* {
|
|
||||||
display: inline-block;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.draw {
|
|
||||||
background-color: #fb772a;
|
|
||||||
}
|
|
||||||
.draw-blackframe {
|
|
||||||
background-color: #e70c0c;
|
|
||||||
}
|
|
||||||
.processing {
|
|
||||||
background-color: rgb(176, 167, 239);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,316 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-row w-full h-full">
|
|
||||||
<div class="flex flex-col w-full">
|
|
||||||
|
|
||||||
<!-- TAB ROW -->
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<div
|
|
||||||
class="tab"
|
|
||||||
:class="{'active': tab === 'siteSettings'}"
|
|
||||||
@click="setTab('siteSettings')"
|
|
||||||
>
|
|
||||||
Current site<br/>
|
|
||||||
<small>{{ site }}</small>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-if="hosts"
|
|
||||||
class="tab"
|
|
||||||
:class="{'active': tab === 'embeddedSites'}"
|
|
||||||
@click="setTab(tab = 'embeddedSites')"
|
|
||||||
>
|
|
||||||
Embedded content ({{hosts?.length}} {{hosts?.length === 1 ? 'site' : 'sites'}})
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="tab"
|
|
||||||
:class="{'active': tab === 'otherSites'}"
|
|
||||||
@click="setTab(tab = 'otherSites')"
|
|
||||||
>
|
|
||||||
Defaults & other sites
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template v-if="tab === 'siteSettings' && siteSettings">
|
|
||||||
<SiteExtensionSettings
|
|
||||||
v-if="settings"
|
|
||||||
:settings="settings"
|
|
||||||
:siteSettings="siteSettings"
|
|
||||||
:isDefaultConfiguration="false"
|
|
||||||
></SiteExtensionSettings>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="hosts && tab === 'embeddedSites' && globalSettings">
|
|
||||||
<FrameSiteSettings
|
|
||||||
v-if="settings"
|
|
||||||
:parentHost="site"
|
|
||||||
:hosts="hosts"
|
|
||||||
:settings="settings"
|
|
||||||
></FrameSiteSettings>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="tab === 'otherSites'">
|
|
||||||
<OtherSiteSettings
|
|
||||||
v-if="settings"
|
|
||||||
:settings="settings"
|
|
||||||
>
|
|
||||||
</OtherSiteSettings>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<!-- Reset options -->
|
|
||||||
<div class="flex flex-col" style="margin-top: 2rem">
|
|
||||||
<h2>Reset and backup</h2>
|
|
||||||
<p><small>Settings import must be done from in-player UI.</small></p>
|
|
||||||
<div class="flex flex-row w-full">
|
|
||||||
<UploadJsonFileButton
|
|
||||||
class="flex-grow"
|
|
||||||
@importedJson="handleImportedSettings"
|
|
||||||
@error="handleSettingsImportError"
|
|
||||||
>
|
|
||||||
Import settings
|
|
||||||
</UploadJsonFileButton>
|
|
||||||
<Popup
|
|
||||||
v-if="importSettingDialogConfig.visible"
|
|
||||||
title="Overwrite existing settings?"
|
|
||||||
message="Importing settings from a file will overwrite existing settings. Continue?"
|
|
||||||
confirmButtonText="Import settings"
|
|
||||||
cancelButtonText="Cancel"
|
|
||||||
@onConfirm="importSettingDialogConfig.confirm"
|
|
||||||
@onCancel="importSettingDialogConfig.reject"
|
|
||||||
>
|
|
||||||
</Popup>
|
|
||||||
<button class="flex-grow" @click="exportSettings()">Export settings</button>
|
|
||||||
</div>
|
|
||||||
<div></div>
|
|
||||||
<ConfirmButton
|
|
||||||
dialogType="danger"
|
|
||||||
@onConfirmed="resetSettings"
|
|
||||||
>
|
|
||||||
Reset settings
|
|
||||||
</ConfirmButton>
|
|
||||||
|
|
||||||
<div v-if="enableSettingsEditor" class="field">
|
|
||||||
<div class="label">Show developer options</div>
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
v-model="settings.active.ui.devMode"
|
|
||||||
@change="settings.saveWithoutReload"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="enableSettingsEditor && settings.active.ui.devMode" class="h-full grow">
|
|
||||||
<h2>Settings editor</h2>
|
|
||||||
<div class="flex flex-row w-full">
|
|
||||||
<div class="flex flex-row items-center">
|
|
||||||
<div>Enable save button:</div>
|
|
||||||
<input v-model="allowSettingsEditing" type="checkbox">
|
|
||||||
</div>
|
|
||||||
<div class="grow">
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<div v-if="editorSaveFinished">Settings saved ...</div>
|
|
||||||
<button v-else
|
|
||||||
class="danger"
|
|
||||||
:class="{'disabled': !allowSettingsEditing}"
|
|
||||||
:disabled="!allowSettingsEditing"
|
|
||||||
@click="() => saveSettingsChanges()"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<button @click="resetSettingsEditor">
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<JsonEditor
|
|
||||||
v-model="settingsJson"
|
|
||||||
>
|
|
||||||
</JsonEditor>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2>Settings snapshots</h2>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div v-for="(snapshot, index) of settingsSnapshots" :key="snapshot.createdAt">
|
|
||||||
<small>{{new Date(snapshot.createdAt).toISOString()}}</small>
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<div class="grow">
|
|
||||||
{{snapshot.name}}
|
|
||||||
</div>
|
|
||||||
<div v-if="settings.isAutomatic">(auto)</div>
|
|
||||||
<div v-if="settings.isAutomatic && settings.isProtected">(protected)</div>
|
|
||||||
<div v-if="settings.default">(default)</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<button @click="() => markDefaultSnapshot(index)"><template v-if="settings.isDefault">Revoke default</template><template v-else>Make default</template></button>
|
|
||||||
<button v-if="settings.isAutomatic" @click="() => toggleSnapshotProtection(index)">Toggle protection</button>
|
|
||||||
<button @click="() => deleteSnapshot(index)">Delete snapshot</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import SiteExtensionSettings from './PanelComponents/ExtensionSettings/SiteExtensionSettings.vue';
|
|
||||||
import FrameSiteSettings from './PanelComponents/ExtensionSettings/FrameSiteSettings.vue';
|
|
||||||
import OtherSiteSettings from './PanelComponents/ExtensionSettings/OtherSiteSettings.vue';
|
|
||||||
import Popup from '@csui/src/components/Popup';
|
|
||||||
import ConfirmButton from '@csui/src/components/ConfirmButton';
|
|
||||||
import UploadJsonFileButton from '@csui/src/components/UploadJsonFileButton';
|
|
||||||
import JsonEditor from '@csui/src/components/JsonEditor';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
|
|
||||||
components: {
|
|
||||||
SiteExtensionSettings,
|
|
||||||
OtherSiteSettings,
|
|
||||||
Popup,
|
|
||||||
ConfirmButton,
|
|
||||||
UploadJsonFileButton,
|
|
||||||
JsonEditor,
|
|
||||||
FrameSiteSettings,
|
|
||||||
},
|
|
||||||
mixins: [],
|
|
||||||
props: [
|
|
||||||
'settings',
|
|
||||||
'site',
|
|
||||||
'enableSettingsEditor',
|
|
||||||
'hosts',
|
|
||||||
],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
tab: 'siteSettings',
|
|
||||||
importSettingDialogConfig: {visible: false},
|
|
||||||
allowSettingsEditing: false,
|
|
||||||
editorSaveFinished: false,
|
|
||||||
settingsJson: {},
|
|
||||||
settingsSnapshots: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
globalSettings() {
|
|
||||||
return this.settings?.getSiteSettings('@global') ?? null;
|
|
||||||
},
|
|
||||||
siteSettings() {
|
|
||||||
if (this.site) {
|
|
||||||
return this.settings?.getSiteSettings({site: this.site}) ?? null;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
this.resetSettingsEditor();
|
|
||||||
this.loadSettingsSnapshots();
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
setTab(tab) {
|
|
||||||
this.tab = tab;
|
|
||||||
},
|
|
||||||
|
|
||||||
//#region settings management
|
|
||||||
/**
|
|
||||||
* Exports extension settings into a json file.
|
|
||||||
*/
|
|
||||||
exportSettings() {
|
|
||||||
const settingBlob = new Blob(
|
|
||||||
[ JSON.stringify(this.settings.active, null, 2) ],
|
|
||||||
{ type: "application/json"}
|
|
||||||
);
|
|
||||||
const url = window.URL.createObjectURL(settingBlob);
|
|
||||||
|
|
||||||
const a = document.createElement("a");
|
|
||||||
a.href = url;
|
|
||||||
a.download = "ultrawidify-settings.json";
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
},
|
|
||||||
|
|
||||||
handleImportedSettings(newSettings) {
|
|
||||||
this.importSettingDialogConfig = {
|
|
||||||
visible: true,
|
|
||||||
confirm: () => {
|
|
||||||
this.settings.active = newSettings;
|
|
||||||
this.settings.saveWithoutReload();
|
|
||||||
this.importSettingDialogConfig = {visible: false};
|
|
||||||
this.resetSettingsEditor();
|
|
||||||
},
|
|
||||||
reject: () => {
|
|
||||||
this.importSettingDialogConfig = {visible: false};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
handleSettingsImportError(error) {
|
|
||||||
console.error('Error importing settings:', error);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resets settings to default
|
|
||||||
*/
|
|
||||||
resetSettings() {
|
|
||||||
this.settings.active = JSON.parse(JSON.stringify(this.settings.default));
|
|
||||||
this.settings.saveWithoutReload();
|
|
||||||
this.resetSettingsEditor();
|
|
||||||
},
|
|
||||||
|
|
||||||
async saveSettingsChanges() {
|
|
||||||
if (this.allowSettingsEditing) {
|
|
||||||
const currentVersion = this.settings.active?.version;
|
|
||||||
this.settings.active = this.settingsJson;
|
|
||||||
|
|
||||||
if (currentVersion !== this.settingsJson.version) {
|
|
||||||
await this.settings.save({forcePreserveVersion: true});
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
await this.settings.saveWithoutReload();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resetSettingsEditor();
|
|
||||||
this.editorSaveFinished = true;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.editorSaveFinished = false;
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
resetSettingsEditor() {
|
|
||||||
this.settingsJson = JSON.parse(JSON.stringify(this.settings?.active ?? {}));
|
|
||||||
},
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
//#region settings snapshot management
|
|
||||||
async loadSettingsSnapshots() {
|
|
||||||
this.settingsSnapshots = await this.settings.snapshotManager.listSnapshots();
|
|
||||||
},
|
|
||||||
|
|
||||||
async markDefaultSnapshot(index) {
|
|
||||||
await this.settings.snapshotManager.setDefaultSnapshot(index, !this.settingsSnapshots[index].isDefault);
|
|
||||||
await this.loadSettingsSnapshots();
|
|
||||||
this.settings.active.dev.loadFromSnapshot = this.settingsSnapshots[index].isDefault;
|
|
||||||
this.saveSettingsChanges();
|
|
||||||
},
|
|
||||||
|
|
||||||
async toggleSnapshotProtection(index) {
|
|
||||||
await this.settings.snapshotManager.setSnapshotAsProtected(index, !this.settingsSnapshots[index].isProtected);
|
|
||||||
await this.loadSettingsSnapshots();
|
|
||||||
},
|
|
||||||
|
|
||||||
async deleteSnapshot(index) {
|
|
||||||
await this.settings.snapshotManager.deleteSnapshot(index);
|
|
||||||
await this.loadSettingsSnapshots();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
//#endregion
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" src="../../res/css/flex.scss" scoped module></style>
|
|
||||||
<style lang="scss" src="@csui/src/res-common/panels.scss" scoped module></style>
|
|
||||||
<style lang="scss" src="@csui/src/res-common/common.scss" scoped module></style>
|
|
||||||
@ -1,109 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="">
|
|
||||||
<template v-if="!selectedSite">
|
|
||||||
<div style="margin: 1rem 0rem" class="w-full">
|
|
||||||
<div class="flex flex-row items-baseline">
|
|
||||||
<div style="margin-right: 1rem">Search for site:</div>
|
|
||||||
<div class="input flex-grow">
|
|
||||||
<input v-model="siteFilter" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-for="host of hosts" :key="host" @click="selectedSite = host" class="flex flex-col container pointer hoverable" style="margin-top: 4px; padding: 0.5rem 1rem;">
|
|
||||||
<SiteListItem
|
|
||||||
:parentHost="parentHost"
|
|
||||||
:host="host"
|
|
||||||
:settings="settings"
|
|
||||||
></SiteListItem>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-if="selectedSite">
|
|
||||||
<div class="flex flex-row container" style="align-items: center; color: #dedede; margin-top: 1rem;">
|
|
||||||
<div @click="selectedSite = null" class="pointer button-hover" style=" font-size: 2em; padding: 0.5rem; margin-right: 1em;">
|
|
||||||
←
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Editing {{ selectedSite === '@global' ? 'default settings' : selectedSite }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<SiteExtensionSettings
|
|
||||||
v-if="selectedSiteSettings"
|
|
||||||
:settings="settings"
|
|
||||||
:siteSettings="selectedSiteSettings"
|
|
||||||
:isDefaultConfiguration="selectedSite === '@global'"
|
|
||||||
></SiteExtensionSettings>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import SiteExtensionSettings from './SiteExtensionSettings.vue';
|
|
||||||
import SiteListItem from './SiteListItem.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
SiteExtensionSettings,
|
|
||||||
SiteListItem,
|
|
||||||
},
|
|
||||||
props: [
|
|
||||||
'settings',
|
|
||||||
'parentHost',
|
|
||||||
'hosts',
|
|
||||||
],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
selectedSite: null,
|
|
||||||
siteFilter: '',
|
|
||||||
filteredSites: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
sites() {
|
|
||||||
if (!this.settings?.active?.sites) {
|
|
||||||
return [];
|
|
||||||
} else {
|
|
||||||
const sites = [];
|
|
||||||
for (const siteKey in this.settings.active.sites) {
|
|
||||||
if (!siteKey.startsWith('@') && (!this.siteFilter.trim() || siteKey.includes(this.siteFilter))) {
|
|
||||||
sites.push({
|
|
||||||
key: siteKey,
|
|
||||||
...this.settings.active.sites[siteKey]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sites.sort((a, b) => {
|
|
||||||
const cmpa = a.key.replace('www.', '');
|
|
||||||
const cmpb = b.key.replace('www.', '');
|
|
||||||
|
|
||||||
return cmpa < cmpb ? -1 : 1;
|
|
||||||
});
|
|
||||||
return sites;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectedSiteSettings() {
|
|
||||||
return this.settings?.getSiteSettings({site: this.selectedSite}) ?? null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" src="../../../../res/css/flex.scss" scoped></style>
|
|
||||||
<style lang="scss" src="@csui/src/res-common/panels.scss" scoped></style>
|
|
||||||
<style lang="scss" src="@csui/src/res-common/common.scss" scoped></style>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.hoverable {
|
|
||||||
border: 1px solid #333;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border: 1px solid #fa6;
|
|
||||||
color: rgb(255, 231, 212);
|
|
||||||
background-color: rgba(#fa6, 0.125);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -1,179 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="">
|
|
||||||
<template v-if="!selectedSite">
|
|
||||||
|
|
||||||
<div class="flex flex-col container pointer hoverable" style="margin-top: 1rem; padding: 0.5rem 1rem;" @click="selectedSite = '@global'" >
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<div class="flex-grow pointer">
|
|
||||||
<b style="color: #fa6">Default settings</b>
|
|
||||||
<!-- <span :style="getSiteTypeColor('@global')"> -->
|
|
||||||
<!-- (config: {{site.type ?? 'unknown'}}) -->
|
|
||||||
<!-- </span> -->
|
|
||||||
</div>
|
|
||||||
<div>Edit</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<small>
|
|
||||||
<span style="text-transform: uppercase; font-size: 0.9rem">
|
|
||||||
Enable extension: <span style="font-size: 0.9rem;" :style="getSiteEnabledColor('@global', 'enable')">{{ getSiteEnabledModes('@global', 'enable') }}</span>
|
|
||||||
{{getSiteEnabledModes('@global', 'enable') === 'disabled'}}
|
|
||||||
<span v-if="getSiteEnabledModes('@global', 'enable') === 'disabled'" class="text-[0.9em]"> (except on whitelisted sites)</span>"
|
|
||||||
</span>
|
|
||||||
<br/>
|
|
||||||
Autodetection: <span :style="getSiteEnabledColor('@global', 'enableAard')"><small>{{ getSiteEnabledModes('@global', 'enableAard') }}</small></span>;
|
|
||||||
Keyboard shortcuts: <span :style="getSiteEnabledColor('@global', 'enableKeyboard')"><small>{{ getSiteEnabledModes('@global', 'enableKeyboard') }}</small></span>;
|
|
||||||
In-player UI: <span :style="getSiteEnabledColor('@global', 'enableUI')"><small>{{ getSiteEnabledModes('@global', 'enableUI') }}</small></span>;
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-top: 1rem; margin-bottom: 1rem;">
|
|
||||||
<div class="info" style="float: none">
|
|
||||||
<b>NOTE:</b> Sites not on this list use default extension settings.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="w-full text-center" style="margin-bottom: -1.25rem">
|
|
||||||
<b>Other sites</b>
|
|
||||||
</div>
|
|
||||||
<div style="margin: 1rem 0rem" class="w-full">
|
|
||||||
<div class="flex flex-row items-baseline">
|
|
||||||
<div style="margin-right: 1rem">Search for site:</div>
|
|
||||||
<div class="input flex-grow">
|
|
||||||
<input v-model="siteFilter" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-for="site of sites" :key="site.key" @click="selectedSite = site.key" class="flex flex-col container pointer hoverable" style="margin-top: 4px; padding: 0.5rem 1rem;">
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<div class="flex-grow pointer">
|
|
||||||
<b>{{ site.key }}</b>
|
|
||||||
<span :style="getSiteTypeColor(site.type)">
|
|
||||||
(config: {{site.type ?? 'unknown'}})
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>Edit</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<small>
|
|
||||||
Enabled: <span :style="getSiteEnabledColor(site.key, 'enable')"><small>{{ getSiteEnabledModes(site.key, 'enable') }}</small></span>;
|
|
||||||
Aard <span :style="getSiteEnabledColor(site.key, 'enableAard')"><small>{{ getSiteEnabledModes(site.key, 'enableAard') }}</small></span>;
|
|
||||||
kbd: <span :style="getSiteEnabledColor(site.key, 'enableKeyboard')"><small>{{ getSiteEnabledModes(site.key, 'enableKeyboard') }}</small></span>
|
|
||||||
UI: <span :style="getSiteEnabledColor(site.key, 'enableUI')"><small>{{ getSiteEnabledModes(site.key, 'enableUI') }}</small></span>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-if="selectedSite">
|
|
||||||
<div class="flex flex-row container" style="align-items: center; color: #dedede; margin-top: 1rem;">
|
|
||||||
<div @click="selectedSite = null" class="pointer button-hover" style=" font-size: 2em; padding: 0.5rem; margin-right: 1em;">
|
|
||||||
←
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
Editing {{ selectedSite === '@global' ? 'default settings' : selectedSite }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<SiteExtensionSettings
|
|
||||||
v-if="selectedSiteSettings"
|
|
||||||
:settings="settings"
|
|
||||||
:siteSettings="selectedSiteSettings"
|
|
||||||
:isDefaultConfiguration="selectedSite === '@global'"
|
|
||||||
:visibleSegments="{advancedPlayerSettings: role === 'settings'}"
|
|
||||||
></SiteExtensionSettings>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import ExtensionMode from '../../../../../common/enums/ExtensionMode.enum';
|
|
||||||
import SiteExtensionSettings from './SiteExtensionSettings.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
selectedSite: null,
|
|
||||||
siteFilter: '',
|
|
||||||
filteredSites: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: [
|
|
||||||
'settings',
|
|
||||||
'role',
|
|
||||||
],
|
|
||||||
components: {
|
|
||||||
SiteExtensionSettings,
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
sites() {
|
|
||||||
if (!this.settings?.active?.sites) {
|
|
||||||
return [];
|
|
||||||
} else {
|
|
||||||
const sites = [];
|
|
||||||
for (const siteKey in this.settings.active.sites) {
|
|
||||||
if (!siteKey.startsWith('@') && (!this.siteFilter.trim() || siteKey.includes(this.siteFilter))) {
|
|
||||||
sites.push({
|
|
||||||
key: siteKey,
|
|
||||||
...this.settings.active.sites[siteKey]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
sites.sort((a, b) => {
|
|
||||||
const cmpa = a.key.replace('www.', '');
|
|
||||||
const cmpb = b.key.replace('www.', '');
|
|
||||||
|
|
||||||
return cmpa < cmpb ? -1 : 1;
|
|
||||||
});
|
|
||||||
return sites;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectedSiteSettings() {
|
|
||||||
return this.settings?.getSiteSettings({site: this.selectedSite}) ?? null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getSiteTypeColor(siteType) {
|
|
||||||
switch (siteType) {
|
|
||||||
case 'official': return 'color: #fa6';
|
|
||||||
case 'community': return 'color: rgb(114, 114, 218)';
|
|
||||||
case 'officially-disabled': return 'color: #f00';
|
|
||||||
case 'testing': return 'color: #d81';
|
|
||||||
default: return 'color: rgb(138, 65, 126)'
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getSiteEnabledColor(site, component) {
|
|
||||||
const status = this.getSiteEnabledModes(site, component);
|
|
||||||
|
|
||||||
return status === 'disabled' ? 'color: #f00' : 'color: #1f8';
|
|
||||||
},
|
|
||||||
getSiteEnabledModes(site, component) {
|
|
||||||
if (this.settings?.getSiteSettings({site: site}).data[component]?.normal === ExtensionMode.Enabled) {
|
|
||||||
return 'always';
|
|
||||||
}
|
|
||||||
if (this.settings?.getSiteSettings({site: site}).data[component]?.theater === ExtensionMode.Enabled) {
|
|
||||||
return 'T + FS';
|
|
||||||
}
|
|
||||||
if (this.settings?.getSiteSettings({site: site}).data[component]?.fullscreen === ExtensionMode.Enabled) {
|
|
||||||
return 'fullscreen';
|
|
||||||
}
|
|
||||||
return 'disabled';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" src="../../../../res/css/flex.scss" scoped></style>
|
|
||||||
<style lang="scss" src="@csui/src/res-common/panels.scss" scoped></style>
|
|
||||||
<style lang="scss" src="@csui/src/res-common/common.scss" scoped></style>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.hoverable {
|
|
||||||
border: 1px solid #333;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border: 1px solid #fa6;
|
|
||||||
color: rgb(255, 231, 212);
|
|
||||||
background-color: rgba(#fa6, 0.125);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<div class="flex-grow pointer">
|
|
||||||
<b>{{ host }}</b>
|
|
||||||
<span :style="getSiteTypeColor(siteSettings?.data?.type)">
|
|
||||||
(config: {{siteSettings?.data?.type ?? 'unknown'}})
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div>Edit</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="this.siteSettings?.usesSettingsFor">
|
|
||||||
<div v-if="this.siteSettings.usesSettingsFor === '@global'">Uses default settings</div>
|
|
||||||
<div v-else>Uses settings for: <span class="info-color">{{this.siteSettings.usesSettingsFor}}</span></div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<small>
|
|
||||||
Enabled: <span :style="getSiteEnabledColor(host, 'enable')"><small>{{ getSiteEnabledModes(host, 'enable') }}</small></span>;
|
|
||||||
Aard <span :style="getSiteEnabledColor(host, 'enableAard')"><small>{{ getSiteEnabledModes(host, 'enableAard') }}</small></span>;
|
|
||||||
kbd: <span :style="getSiteEnabledColor(host, 'enableKeyboard')"><small>{{ getSiteEnabledModes(host, 'enableKeyboard') }}</small></span>
|
|
||||||
UI: <span :style="getSiteEnabledColor(host, 'enableUI')"><small>{{ getSiteEnabledModes(host, 'enableUI') }}</small></span>
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import ExtensionMode from '../../../../../common/enums/ExtensionMode.enum';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
siteSettings: undefined,
|
|
||||||
supportType: undefined
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: [
|
|
||||||
'settings',
|
|
||||||
'parentHost',
|
|
||||||
'host',
|
|
||||||
],
|
|
||||||
created() {
|
|
||||||
this.siteSettings = this.settings.getSiteSettings({site: this.host, isIframe: this.parentHost && this.host !== this.parentHost, parentHostname: this.parentHost});
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getSiteTypeColor(siteType) {
|
|
||||||
switch (siteType) {
|
|
||||||
case 'official': return 'color: #fa6';
|
|
||||||
case 'community': return 'color: rgb(114, 114, 218)';
|
|
||||||
case 'officially-disabled': return 'color: #f00';
|
|
||||||
case 'testing': return 'color: #d81';
|
|
||||||
default: return 'color: rgb(138, 65, 126)'
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getSiteEnabledColor(site, component) {
|
|
||||||
const status = this.getSiteEnabledModes(site, component);
|
|
||||||
return status === 'disabled' ? 'color: #f00' : 'color: #1f8';
|
|
||||||
},
|
|
||||||
getSiteEnabledModes(site, component) {
|
|
||||||
if (this.siteSettings?.normal === ExtensionMode.Enabled) {
|
|
||||||
return 'always';
|
|
||||||
}
|
|
||||||
if (this.siteSettings?.data[component]?.theater === ExtensionMode.Enabled) {
|
|
||||||
return 'T + FS';
|
|
||||||
}
|
|
||||||
if (this.siteSettings?.data[component]?.fullscreen === ExtensionMode.Enabled) {
|
|
||||||
return 'fullscreen';
|
|
||||||
}
|
|
||||||
return 'disabled';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,157 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="panel-root">
|
|
||||||
<h1>Resizer debug data</h1>
|
|
||||||
<div class="flex flex-row flex-wrap">
|
|
||||||
<div class="panel">
|
|
||||||
<h3 class="panel-title">
|
|
||||||
Player info
|
|
||||||
</h3>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-title">Window size:
|
|
||||||
<small>(Inner size)</small>
|
|
||||||
</div>
|
|
||||||
<div class="data">{{windowWidth}} x {{windowHeight}}</div>
|
|
||||||
<div class="button" @click="refreshWindowSize()">
|
|
||||||
<!-- <Icon icon="arrow-clockwise"></Icon> -->
|
|
||||||
Refresh</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-title">Player dimensions:</div>
|
|
||||||
<div class="data">{{debugData?.resizer?.playerData?.dimensions?.width ?? 'not detected'}} x {{debugData?.resizer?.playerData?.dimensions?.height ?? 'not detected'}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-title">Is fullscreen?</div>
|
|
||||||
<div class="data">{{debugData?.resizer?.playerData?.dimensions?.fullscreen ?? 'unknown'}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-title">Player id | classlist</div>
|
|
||||||
<div class="data">{{debugData?.resizer?.playerData?.elementId || '<no ID>'}} | {{debugData?.resizer?.playerData?.classList || '<no classes>'}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Stream info -->
|
|
||||||
<div class="panel">
|
|
||||||
<h3 class="panel-title">
|
|
||||||
Stream info
|
|
||||||
</h3>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-title">
|
|
||||||
Stream dimensions: <small>(Native size of the video)</small>
|
|
||||||
</div>
|
|
||||||
<div class="data">
|
|
||||||
{{debugData?.resizer?.videoRawData?.streamDimensions.x}} x {{debugData?.resizer?.videoRawData?.streamDimensions?.y}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-title">
|
|
||||||
Stream displayed dimensions: <small>(Video file is being upscaled to this size)</small>
|
|
||||||
</div>
|
|
||||||
<div class="data">
|
|
||||||
{{debugData?.resizer?.videoRawData?.displayedSize?.x.toFixed()}} x {{debugData?.resizer?.videoRawData?.displayedSize?.y.toFixed()}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-title">Video element native size: <small>(Size of the html element. Should be same as above most of the time!)</small></div>
|
|
||||||
<div class="data">{{debugData?.resizer?.videoRawData?.displayedSize?.x.toFixed(1)}} x {{debugData?.resizer?.videoRawData?.displayedSize?.y.toFixed(1)}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-title">Size difference to player (raw): <small>(positive numbers: video element is bigger than player element)</small></div>
|
|
||||||
<div class="data">x: {{debugData?.resizer?.sizeDifferenceToPlayer?.beforeZoom?.wdiff.toFixed(1)}}; y: {{debugData?.resizer?.sizeDifferenceToPlayer?.beforeZoom?.hdiff.toFixed(1)}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-title">Video element size (post zoom): <small>(Size of the html element after transform:scale is applied. Or at least, that's what Resizer::computeOffsets() thinks the final size is.)</small></div>
|
|
||||||
<div class="data">{{debugData?.resizer?.transformedSize?.x.toFixed(2)}} x {{debugData?.resizer?.transformedSize?.y.toFixed(2)}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-title"><b>Size difference to player (post-zoom):</b> <small>(same as above, except after cropping, stretching, panning and zoom are applied)</small></div>
|
|
||||||
<div class="data">x: {{debugData?.resizer?.sizeDifferenceToPlayer?.afterZoom?.wdiff.toFixed(2)}}; y: {{debugData?.resizer?.sizeDifferenceToPlayer?.afterZoom?.hdiff.toFixed(2)}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Transform info -->
|
|
||||||
<div class="panel">
|
|
||||||
<h3 class="panel-title">
|
|
||||||
Transformations
|
|
||||||
</h3>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-title">Alignment: <small>(I agree that 'left' and 'right' are both evil, but that's not the kind of alignments we're thinking of)</small></div>
|
|
||||||
<div class="data">{{debugData?.resizer?.videoTransform?.alignment || '<unknown>'}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-title">Scale factor</div>
|
|
||||||
<div class="data">x: {{debugData?.resizer?.videoTransform?.scale.x.toFixed(2)}}; y: {{debugData?.resizer?.videoTransform?.scale.y.toFixed(2)}}</div>
|
|
||||||
</div>
|
|
||||||
<div class="data-item">
|
|
||||||
<div class="data-title">Translation</div>
|
|
||||||
<div class="data">x: {{debugData?.resizer?.videoTransform?.translate.x.toFixed(2)}}; y: {{debugData?.resizer?.videoTransform?.translate.y.toFixed(2)}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- <div class="uw-debug-info flex"> -->
|
|
||||||
<!-- <pre> -->
|
|
||||||
<!-- {{debugDataPrettified}} -->
|
|
||||||
<!-- </pre> -->
|
|
||||||
<!-- </div> -->
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// import Icon from '../../common/components/Icon';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
// Icon,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
debugData: Object
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
windowWidth: window.innerWidth,
|
|
||||||
windowHeight: window.innerHeight
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
refreshWindowSize() {
|
|
||||||
this.windowWidth = window.innerWidth;
|
|
||||||
this.windowHeight = window.innerHeight;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import '../../../res/css/uwui-base.scss';
|
|
||||||
@import '../../../res/css/colors.scss';
|
|
||||||
@import '../../../res/css/font/overpass.css';
|
|
||||||
@import '../../../res/css/font/overpass-mono.css';
|
|
||||||
@import '../../../res/css/common.scss';
|
|
||||||
|
|
||||||
.panel-root {
|
|
||||||
display: block;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.panel {
|
|
||||||
display: inline-block;
|
|
||||||
width: 420px;
|
|
||||||
max-width: 100%;
|
|
||||||
padding: 12px 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-item {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.69rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-title {
|
|
||||||
|
|
||||||
small {
|
|
||||||
display: block;
|
|
||||||
opacity: 0.69;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.data {
|
|
||||||
color: $primary-color;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
siteSettings: Object,
|
|
||||||
hasDrm: Boolean,
|
|
||||||
problems: Object,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
</style>
|
|
||||||
@ -1,119 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="context-container" @mouseleave="hideContextMenu()">
|
|
||||||
<GhettoContextMenuItem
|
|
||||||
class="activator uw-clickable"
|
|
||||||
:css="{
|
|
||||||
'expand-left': alignment === 'left',
|
|
||||||
'expand-right': alignment === 'right',
|
|
||||||
}"
|
|
||||||
@click="showContextMenu()"
|
|
||||||
@mouseenter="showContextMenu()"
|
|
||||||
>
|
|
||||||
<slot name="activator"></slot>
|
|
||||||
</GhettoContextMenuItem>
|
|
||||||
<div
|
|
||||||
v-if="contextMenuVisible"
|
|
||||||
class="context-menu uw-clickable"
|
|
||||||
:class="{
|
|
||||||
'menu-left': alignment === 'left',
|
|
||||||
'menu-right': alignment === 'right'
|
|
||||||
}"
|
|
||||||
@mouseleave="hideContextMenu()"
|
|
||||||
>
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import GhettoContextMenuItem from './GhettoContextMenuItem.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
GhettoContextMenuItem,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
alignment: String,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
contextMenuVisible: false,
|
|
||||||
contextMenuHideTimeout: undefined,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
showContextMenu() {
|
|
||||||
this.contextMenuVisible = true;
|
|
||||||
},
|
|
||||||
hideContextMenu() {
|
|
||||||
this.contextMenuHideTimeout = setTimeout( () => {
|
|
||||||
this.contextMenuVisible = false;
|
|
||||||
}, 50);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.context-container {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.context-menu-wrapper {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.context-menu {
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-width: 5rem;
|
|
||||||
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
.menu-left {
|
|
||||||
right: 100%;
|
|
||||||
}
|
|
||||||
.menu-right {
|
|
||||||
left: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
.activator {
|
|
||||||
position: relative;
|
|
||||||
padding: 1rem 1.6rem;
|
|
||||||
|
|
||||||
font-size: .95rem;
|
|
||||||
padding: 1rem 1.6rem;
|
|
||||||
background-color: rgba(0, 0, 0, 0.75);
|
|
||||||
backdrop-filter: blur(16px) saturate(120%);
|
|
||||||
|
|
||||||
white-space: nowrap;
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(255, 128, 64, 0.95);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.expand-left {
|
|
||||||
padding-left: 2.2rem;
|
|
||||||
}
|
|
||||||
&.expand-right {
|
|
||||||
padding-right: 2.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.expand-left::before,
|
|
||||||
&.expand-right::after {
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
font-size: 0.88rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.expand-left::before {
|
|
||||||
content: '⮜';
|
|
||||||
left: 0.5rem;
|
|
||||||
}
|
|
||||||
&.expand-right::after {
|
|
||||||
content: '⮞';
|
|
||||||
right: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="flex flex-col flex-center center-text item"
|
|
||||||
:class="{
|
|
||||||
'can-hover': !disableHover,
|
|
||||||
...(css ? css : {}),
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
label: String,
|
|
||||||
shortcut: String,
|
|
||||||
childAlignment: String,
|
|
||||||
css: Object,
|
|
||||||
disableHover: Boolean,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.item {
|
|
||||||
position: relative;
|
|
||||||
font-size: .95rem;
|
|
||||||
font-family: 'Overpass';
|
|
||||||
padding: 1rem 1.6rem;
|
|
||||||
background-color: rgba(0, 0, 0, 0.75);
|
|
||||||
backdrop-filter: blur(16px) saturate(120%);
|
|
||||||
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&.can-hover:hover {
|
|
||||||
background-color: rgba(0,0,0,0.98);
|
|
||||||
border-bottom: 1px solid #fa6;
|
|
||||||
}
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div>
|
|
||||||
<GhettoContextMenuItem>
|
|
||||||
<template v-if="label">
|
|
||||||
{{label}} {{shortcut ? `(${shortcut})` : ''}}
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<slot></slot>
|
|
||||||
</template>
|
|
||||||
</GhettoContextMenuItem>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import GhettoContextMenuItem from './GhettoContextMenuItem.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
GhettoContextMenuItem,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
label: String,
|
|
||||||
shortcut: String,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,53 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="qsb flex flex-row flex-cross-center flex-center flex-nogrow"
|
|
||||||
:class="{
|
|
||||||
'id': selector.startsWith('#'),
|
|
||||||
'class': selector.startsWith('.'),
|
|
||||||
}">
|
|
||||||
<div class="flex mt2px">{{selector}}</div> <span class="flex closeButton" @click="remove">×</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
selector: String,
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
remove() {
|
|
||||||
this.$emit('remove');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.mt2px {
|
|
||||||
margin-top: 7px;
|
|
||||||
}
|
|
||||||
.id {
|
|
||||||
border: 1px solid #d00;
|
|
||||||
background-color: rgba(216, 94, 24, 0.25)
|
|
||||||
}
|
|
||||||
.class {
|
|
||||||
border: 1px solid #33d;
|
|
||||||
background-color: rgba(69, 69, 255, 0.25)
|
|
||||||
}
|
|
||||||
.closeButton {
|
|
||||||
margin-top: 2px;
|
|
||||||
margin-left: 4px;
|
|
||||||
margin-right: 4px;
|
|
||||||
font-size: 1.5em;
|
|
||||||
color: #e00;
|
|
||||||
}
|
|
||||||
.closeButton:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
color: #fa6;
|
|
||||||
}
|
|
||||||
.qsb {
|
|
||||||
cursor:default;
|
|
||||||
margin-left: 3px;
|
|
||||||
margin-right: 3px;
|
|
||||||
margin-bottom: 2px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,87 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div v-if="!editing && !adding" class="flex flex-row">
|
|
||||||
<div class="">
|
|
||||||
<b>Query selector:</b> {{qs.string}}<br/>
|
|
||||||
<b>Additional CSS:</b> {{qs.css || 'no style rules'}}
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col flex-nogrow">
|
|
||||||
<a @click="editing = true">Edit</a>
|
|
||||||
<a @click="$emit('delete')">Delete</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="flex flex-row">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<div class="flex label-secondary form-label">
|
|
||||||
Query selector:
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-input">
|
|
||||||
<input type="text"
|
|
||||||
v-model="qs.string"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row">
|
|
||||||
<div class="flex label-secondary form-label">
|
|
||||||
Additional CSS:
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-input">
|
|
||||||
<input type="text"
|
|
||||||
v-model="qs.css"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a v-if="editing" @click="cancelEdit">Cancel</a>
|
|
||||||
<a v-if="adding" @click="clear">Clear</a>
|
|
||||||
<a @click="save">Save</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
qs: {string: '', css: ''},
|
|
||||||
editing: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
querySelector: Object,
|
|
||||||
adding: {
|
|
||||||
type: Boolean,
|
|
||||||
required: false,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
querySelector(val) {
|
|
||||||
this.qs = JSON.parse(JSON.stringify(val));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
save() {
|
|
||||||
if (this.adding) {
|
|
||||||
this.$emit('create', this.qs);
|
|
||||||
this.qs = {string: '', css: ''};
|
|
||||||
} else {
|
|
||||||
this.emit$('update', this.qs)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cancelEdit() {
|
|
||||||
this.qs = JSON.parse(JSON.stringify(this.querySelector));
|
|
||||||
},
|
|
||||||
clear() {
|
|
||||||
this.qs = {string: '', css: ''};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="flex flex-col flex-center">
|
|
||||||
<div class="flex flex-self-center">
|
|
||||||
{{label}}
|
|
||||||
</div>
|
|
||||||
<div class="flex dark flex-self-center">
|
|
||||||
<small>
|
|
||||||
{{shortcut ? `(${shortcut})` : ''}}
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
label: String,
|
|
||||||
shortcut: String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.center-text {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.dark {
|
|
||||||
opacity: 50%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,148 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div v-if="siteSupportLevel === 'official'" class="site-support official" :style="supportLevelStyle">
|
|
||||||
<mdicon name="check-decagram" />
|
|
||||||
<div v-if="!small">Verified</div>
|
|
||||||
<div class="tooltip" :style="tooltipStyle">
|
|
||||||
<template v-if="small">Verified — </template>
|
|
||||||
The extension is being tested and should work on this site.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="siteSupportLevel === 'community'" class="site-support community" :style="supportLevelStyle">
|
|
||||||
<mdicon name="account-group" />
|
|
||||||
<div v-if="!small">Community</div>
|
|
||||||
<div class="tooltip" :style="tooltipStyle">
|
|
||||||
<template v-if="small">Community — </template>
|
|
||||||
People say extension works on this site (or have provided help getting the extension to work if it didn't).<br/><br/>
|
|
||||||
Tamius (the dev) does not test the extension on this site, probably because it requires a subscription or
|
|
||||||
is geoblocked.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="siteSupportLevel === 'no-support' || siteSupportLevel === 'unknown'" class="site-support no-support" :style="supportLevelStyle">
|
|
||||||
<mdicon name="help-circle-outline" />
|
|
||||||
<div v-if="!small">Unknown</div>
|
|
||||||
<div class="tooltip" :style="tooltipStyle">
|
|
||||||
<template v-if="small">Unknown — </template>
|
|
||||||
Not officially supported. Extension will try to fix things, but no promises.<br/><br/>
|
|
||||||
Tamius (the dev) does not test the extension on this site for various reasons
|
|
||||||
(unaware, not using the site, language barrier, geoblocking, paid services Tam doesn't use).
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="siteSupportLevel === 'user-added' || siteSupportLevel === 'user-defined'" class="site-support user-added" :style="supportLevelStyle">
|
|
||||||
<mdicon name="account" />
|
|
||||||
<div v-if="!small">Modified by you</div>
|
|
||||||
<div class="tooltip" :style="tooltipStyle">
|
|
||||||
<template v-if="small">Modified by you — </template>
|
|
||||||
You have manually changed settings for this site. The extension is doing what you told it to do.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="siteSupportLevel === 'officially-disabled'" class="site-support officially-disabled" :style="supportLevelStyle">
|
|
||||||
<mdicon class="site-support no-support" name="checkbox-marked-circle" />
|
|
||||||
<div v-if="!small">Not supported</div>
|
|
||||||
<div class="tooltip" :style="tooltipStyle">
|
|
||||||
<template v-if="small">Not supported — </template>
|
|
||||||
Extension is known to not work with this site.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
siteSupportLevel: String,
|
|
||||||
small: Boolean,
|
|
||||||
supportLevelStyle: String,
|
|
||||||
tooltipStyle: String,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
|
|
||||||
|
|
||||||
.site-support {
|
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
margin-left: 1rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 0rem 1.5rem 0rem 1rem;
|
|
||||||
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.tooltip {
|
|
||||||
padding: 1rem;
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
transform: translateY(110%);
|
|
||||||
width: 42em;
|
|
||||||
|
|
||||||
background-color: rgba(0,0,0,0.90);
|
|
||||||
color: #ccc;
|
|
||||||
z-index: 99999 !important;
|
|
||||||
|
|
||||||
white-space: normal;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
word-wrap: break-word;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
.tooltip {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mdi {
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.official {
|
|
||||||
background-color: #fa6;
|
|
||||||
color: #000;
|
|
||||||
|
|
||||||
.mdi {
|
|
||||||
fill: #000 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.community {
|
|
||||||
background-color: rgb(85, 85, 179);
|
|
||||||
color: #fff;
|
|
||||||
|
|
||||||
.mdi {
|
|
||||||
fill: #fff !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.officially-disabled {
|
|
||||||
background-color: rgb(132, 24, 40);
|
|
||||||
color: #eee;
|
|
||||||
|
|
||||||
.mdi {
|
|
||||||
fill: #eee !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.no-support {
|
|
||||||
background-color: rgb(83, 76, 77);
|
|
||||||
color: #eee;
|
|
||||||
|
|
||||||
.mdi {
|
|
||||||
fill: #eee !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.user-added {
|
|
||||||
border: 1px solid #ff0;
|
|
||||||
|
|
||||||
color: #ff0;
|
|
||||||
|
|
||||||
.mdi {
|
|
||||||
fill: #ff0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@ -1,471 +0,0 @@
|
|||||||
<template>
|
|
||||||
|
|
||||||
<!-- Preview + editor -->
|
|
||||||
<div
|
|
||||||
v-if="settings?.active?.ui"
|
|
||||||
class="active-trigger-area uw-clickable"
|
|
||||||
:style="triggerZoneStyles"
|
|
||||||
>
|
|
||||||
<div class="trigger-zone-editor"
|
|
||||||
@mousedown="(event) => handleMouseDown('offset', event)"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="uw-clickable corner tl"
|
|
||||||
@mousedown.stop="(event) => handleMouseDown('tl', event)"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="uw-clickable corner tr"
|
|
||||||
@mousedown.stop="(event) => handleMouseDown('tr', event)"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="uw-clickable corner bl"
|
|
||||||
@mousedown.stop="(event) => handleMouseDown('bl', event)"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="uw-clickable corner br"
|
|
||||||
@mousedown.stop="(event) => handleMouseDown('br', event)"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Sliders -->
|
|
||||||
<div
|
|
||||||
class="trigger-zone-editor-sliders-container"
|
|
||||||
>
|
|
||||||
<div class="panel uw-clickable">
|
|
||||||
<div class="trigger-zone-editor-window">
|
|
||||||
<div class="heading">
|
|
||||||
<h2>Trigger zone editor</h2>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
Trigger zone is represented by this very obvious gold grid. Ultrawidify menu button will only show when mouse enters the area marked by the grid.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Note that interacting with the grid area is slightly broken. The distance dragged doesn't correspond with the amount of resizing. I don't plan on
|
|
||||||
fixing that because the amount of effort required to get it working perfectly doesn't correspond with the amount of utility this part of the UI will
|
|
||||||
see. Like, it works well enough and I want to enjoy some of my end-of-year PTO.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Sliders work as they should.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="field">
|
|
||||||
<div class="label">Trigger zone width:</div>
|
|
||||||
<div class="input range-input">
|
|
||||||
<input
|
|
||||||
:value="settings.active.ui.inPlayer.triggerZoneDimensions.width"
|
|
||||||
class="slider"
|
|
||||||
type="range"
|
|
||||||
min="0.1"
|
|
||||||
max="1"
|
|
||||||
step="0.01"
|
|
||||||
@input="(event) => setValue('width', event.target.value)"
|
|
||||||
@change="(event) => updateSettings(true)"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
:value="ghettoComputed.width"
|
|
||||||
@input="(event) => setValue('width', event.target.value, true)"
|
|
||||||
@change="(event) => updateSettings(true)"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="hint">
|
|
||||||
Width of the trigger zone (% of player area).
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<div class="label">Trigger zone height:</div>
|
|
||||||
<div class="input range-input">
|
|
||||||
<input
|
|
||||||
:value="settings.active.ui.inPlayer.triggerZoneDimensions.height"
|
|
||||||
type="range"
|
|
||||||
min="0.1"
|
|
||||||
max="1"
|
|
||||||
step="0.01"
|
|
||||||
@input="(event) => setValue('height', event.target.value)"
|
|
||||||
@change="(event) => updateSettings(true)"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
:value="ghettoComputed.height"
|
|
||||||
@input="(event) => setValue('height', event.target.value, true)"
|
|
||||||
@change="(event) => updateSettings(true)"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="hint">
|
|
||||||
Height of the trigger zone (% of player area).
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<div class="label">Trigger zone horizontal offset:</div>
|
|
||||||
<div class="input range-input">
|
|
||||||
<input
|
|
||||||
v-model="settings.active.ui.inPlayer.triggerZoneDimensions.offsetX"
|
|
||||||
type="range"
|
|
||||||
min="-100"
|
|
||||||
max="100"
|
|
||||||
@input="(event) => setValue('offsetX', event.target.value)"
|
|
||||||
@change="(event) => updateSettings(true)"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
:value="settings.active.ui.inPlayer.triggerZoneDimensions.offsetX"
|
|
||||||
@input="(event) => setValue('offsetX', event.target.value)"
|
|
||||||
@change="(event) => updateSettings(true)"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="hint">
|
|
||||||
By default, trigger zone is centered around the button. This option moves trigger zone left and right.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<div class="label">Trigger zone vertical offset:</div>
|
|
||||||
<div class="input range-input">
|
|
||||||
<input
|
|
||||||
v-model="settings.active.ui.inPlayer.triggerZoneDimensions.offsetY"
|
|
||||||
type="range"
|
|
||||||
min="-100"
|
|
||||||
max="100"
|
|
||||||
@input="(event) => setValue('offsetY', event.target.value)"
|
|
||||||
@change="(event) => updateSettings(true)"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
:value="settings.active.ui.inPlayer.triggerZoneDimensions.offsetY"
|
|
||||||
@input="(event) => setValue('offsetY', event.target.value)"
|
|
||||||
@change="(event) => updateSettings(true)"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="hint">
|
|
||||||
By default, trigger zone is centered around the button. This option moves trigger zone up and down.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="action-row">
|
|
||||||
<button @click="finishTriggerZoneEdit">Finish editing</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: [
|
|
||||||
'settings',
|
|
||||||
'eventBus',
|
|
||||||
'playerDimensions',
|
|
||||||
],
|
|
||||||
watch: {
|
|
||||||
playerDimensions(newVal, oldVal) {
|
|
||||||
this.updateTriggerZones();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
triggerZoneStyles: {},
|
|
||||||
activeCornerDrag: undefined,
|
|
||||||
dragStartPosition: undefined,
|
|
||||||
dragStartConfiguration: undefined,
|
|
||||||
ghettoComputed: { }
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
document.addEventListener("mouseup", this.handleMouseUp);
|
|
||||||
document.addEventListener("mousemove", this.handleMouseMove);
|
|
||||||
this.ghettoComputed = {
|
|
||||||
width: this.optionalToFixed(this.settings.active.ui.inPlayer.triggerZoneDimensions.width * 100, 0),
|
|
||||||
height: this.optionalToFixed(this.settings.active.ui.inPlayer.triggerZoneDimensions.height * 100, 0)
|
|
||||||
};
|
|
||||||
this.updateTriggerZones(false);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
optionalToFixed(v, n) {
|
|
||||||
try {
|
|
||||||
if ((`${v}`.split('.')[1]?.length ?? 0) > n) {
|
|
||||||
return v.toFixed(n);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
},
|
|
||||||
updateTriggerZones(forceRefresh = true) {
|
|
||||||
if (this.playerDimensions && this.settings?.active?.ui?.inPlayer?.triggerZoneDimensions) {
|
|
||||||
this.triggerZoneStyles = {
|
|
||||||
width: `${Math.round(this.playerDimensions.width * this.settings.active.ui.inPlayer.triggerZoneDimensions.width)}px`,
|
|
||||||
height: `${Math.round(this.playerDimensions.height * this.settings.active.ui.inPlayer.triggerZoneDimensions.height)}px`,
|
|
||||||
transform: `translate(${(this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetX)}%, ${this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetY}%)`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// if (forceRefresh) {
|
|
||||||
// this.$forceUpdate();
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
handleMouseDown(corner, event) {
|
|
||||||
this.activeCornerDrag = corner;
|
|
||||||
|
|
||||||
// we need to save this because we don't know the location of the player element,
|
|
||||||
// just its dimensions ... that means we need to
|
|
||||||
this.dragStartPosition = {
|
|
||||||
x: event.clientX,
|
|
||||||
y: event.clientY
|
|
||||||
};
|
|
||||||
this.dragStartConfiguration = JSON.parse(JSON.stringify(this.settings.active.ui.inPlayer.triggerZoneDimensions));
|
|
||||||
},
|
|
||||||
handleMouseUp(event) {
|
|
||||||
if (!this.activeCornerDrag) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.activeCornerDrag = undefined;
|
|
||||||
this.settings.saveWithoutReload();
|
|
||||||
},
|
|
||||||
handleMouseMove(event) {
|
|
||||||
if (!this.activeCornerDrag) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.activeCornerDrag === 'offset') {
|
|
||||||
this.handleMove(event);
|
|
||||||
} else {
|
|
||||||
this.handleResize(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateTriggerZones();
|
|
||||||
},
|
|
||||||
handleResize(event) {
|
|
||||||
// drag distance in px
|
|
||||||
const dx = event.clientX - this.dragStartPosition.x;
|
|
||||||
const dy = event.clientY - this.dragStartPosition.y;
|
|
||||||
|
|
||||||
// convert drag distance to % of current width:
|
|
||||||
const dxr = dx / this.playerDimensions.width * 2;
|
|
||||||
const dyr = dy / this.playerDimensions.height * 2;
|
|
||||||
|
|
||||||
// // update settings:
|
|
||||||
let nw, nh;
|
|
||||||
switch (this.activeCornerDrag) {
|
|
||||||
case 'tl':
|
|
||||||
nw = this.dragStartConfiguration.width - dxr;
|
|
||||||
nh = this.dragStartConfiguration.height - dyr;
|
|
||||||
break;
|
|
||||||
case 'tr':
|
|
||||||
nw = this.dragStartConfiguration.width + dxr;
|
|
||||||
nh = this.dragStartConfiguration.height - dyr;
|
|
||||||
break;
|
|
||||||
case 'bl':
|
|
||||||
nw = this.dragStartConfiguration.width - dxr;
|
|
||||||
nh = this.dragStartConfiguration.height + dyr;
|
|
||||||
break;
|
|
||||||
case 'br':
|
|
||||||
nw = this.dragStartConfiguration.width + dxr;
|
|
||||||
nh = this.dragStartConfiguration.height + dyr;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure everything is properly limited
|
|
||||||
const cw = Math.min(1, Math.max(0.125, nw));
|
|
||||||
const ch = Math.min(1, Math.max(0.125, nh));
|
|
||||||
|
|
||||||
// // update properties
|
|
||||||
this.settings.active.ui.inPlayer.triggerZoneDimensions.width = cw;
|
|
||||||
this.settings.active.ui.inPlayer.triggerZoneDimensions.height = ch;
|
|
||||||
},
|
|
||||||
handleMove(event) {
|
|
||||||
const dx = event.clientX - this.dragStartPosition.x;
|
|
||||||
const dy = event.clientY - this.dragStartPosition.y;
|
|
||||||
|
|
||||||
// convert drag distance to % of current width:
|
|
||||||
const dxr = dx / this.playerDimensions.width;
|
|
||||||
const dyr = dy / this.playerDimensions.height;
|
|
||||||
|
|
||||||
// const [min, max] = this.settings.active.ui.inPlayer.popupAlignment === 'right' ? [5, 90] : [-90, -5];
|
|
||||||
// const [minCrossAxis, maxCrossAxis] = [-90, 90];
|
|
||||||
const min = -90;
|
|
||||||
const max = -5;
|
|
||||||
const minCrossAxis = -90;
|
|
||||||
const maxCrossAxis = 90;
|
|
||||||
|
|
||||||
const cx = Math.min(max, Math.max(min, this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetX + dxr));
|
|
||||||
const cy = Math.min(maxCrossAxis, Math.max(minCrossAxis, this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetY + dyr));
|
|
||||||
|
|
||||||
this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetX = cx;
|
|
||||||
this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetY = cy;
|
|
||||||
},
|
|
||||||
|
|
||||||
//#region slider window
|
|
||||||
forceNumber(value) {
|
|
||||||
// Change EU format to US if needed
|
|
||||||
// | remove everything after second period if necessary
|
|
||||||
// | | | remove non-numeric characters
|
|
||||||
// | | | |
|
|
||||||
return value.replaceAll(',', '.').split('.', 2).join('.').replace(/[^0-9.\-]/g, '');
|
|
||||||
},
|
|
||||||
setValue(key, originalValue, isTextInput) {
|
|
||||||
let value = originalValue;
|
|
||||||
if (isTextInput) {
|
|
||||||
value = (+this.forceNumber(value) / 100);
|
|
||||||
} else {
|
|
||||||
value = +this.forceNumber(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNaN(+value)) {
|
|
||||||
value = 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.settings.active.ui.inPlayer.triggerZoneDimensions[key] = value;
|
|
||||||
if (isTextInput) {
|
|
||||||
this.ghettoComputed[key] = this.optionalToFixed(originalValue, 0);
|
|
||||||
} else {
|
|
||||||
this.ghettoComputed[key] = this.optionalToFixed(originalValue * 100, 0);
|
|
||||||
}
|
|
||||||
this.updateSettings();
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSettings(forceRefresh) {
|
|
||||||
this.settings.saveWithoutReload();
|
|
||||||
this.updateTriggerZones();
|
|
||||||
|
|
||||||
if (forceRefresh) {
|
|
||||||
this.$nextTick( () => this.$forceUpdate() );
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
finishTriggerZoneEdit() {
|
|
||||||
this.eventBus.send('finish-trigger-zone-edit');
|
|
||||||
},
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" src="@csui/src/res-common/panels.scss" scoped module></style>
|
|
||||||
<style lang="scss" src="@csui/src/res-common/common.scss" scoped module></style>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.active-trigger-area {
|
|
||||||
background-image: url('/res/img/grid_512.webp');
|
|
||||||
background-position: center;
|
|
||||||
background-attachment: fixed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger-zone-editor {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
> .corner {
|
|
||||||
position: absolute;
|
|
||||||
width: 32px;
|
|
||||||
height: 32px;
|
|
||||||
background-color: #000;
|
|
||||||
background-image: url('/res/img/corner-marker_64.webp');
|
|
||||||
background-size: cover;
|
|
||||||
|
|
||||||
}
|
|
||||||
.tr, .tl {
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
.br, .bl {
|
|
||||||
bottom: 0;
|
|
||||||
}
|
|
||||||
.tl, .bl {
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
.tr, .br {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
.br {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
.bl {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
.tl {
|
|
||||||
transform: rotate(-90deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger-zone-editor-window {
|
|
||||||
max-width: 69rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger-zone-editor-sliders-container {
|
|
||||||
position: fixed;
|
|
||||||
width: 100dvw;
|
|
||||||
height: 100dvh;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 2000;
|
|
||||||
|
|
||||||
pointer-events: none;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.panel {
|
|
||||||
backdrop-filter: blur(0.5rem) brightness(0.5);
|
|
||||||
color: #ccc;
|
|
||||||
pointer-events: all;
|
|
||||||
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.range-input {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
* {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
input {
|
|
||||||
max-width: 5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=range] {
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.trigger-zone-editor {
|
|
||||||
padding-bottom: 2rem;
|
|
||||||
|
|
||||||
.field {
|
|
||||||
margin-bottom: -1em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.range-input {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
|
|
||||||
* {
|
|
||||||
margin-left: 0.5rem;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
max-width: 5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=range] {
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-row {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,77 +0,0 @@
|
|||||||
<template>
|
|
||||||
<button
|
|
||||||
class="button drop-zone"
|
|
||||||
:class="[{'drag-over': isDragOver}, className]"
|
|
||||||
@click="onClick"
|
|
||||||
@dragover="isDragOver = true"
|
|
||||||
@dragleave="isDragOver = false"
|
|
||||||
@drop="onDrop"
|
|
||||||
>
|
|
||||||
<slot></slot>
|
|
||||||
<input type="file" ref="fileInput" @change="onFileChange" accept=".json" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: {
|
|
||||||
className: {
|
|
||||||
type: String,
|
|
||||||
default: ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isDragOver: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
onClick() {
|
|
||||||
this.$refs.fileInput.click();
|
|
||||||
},
|
|
||||||
onDrop(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.isDragOver = false;
|
|
||||||
|
|
||||||
if (event.dataTransfer.files.length > 0) {
|
|
||||||
this.handleFile(event.dataTransfer.files[0]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onFileChange(event) {
|
|
||||||
if (event.target.files.length > 0) {
|
|
||||||
this.handleFile(event.target.files[0]);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleFile(file) {
|
|
||||||
if (file.type !== "application/json") {
|
|
||||||
this.$emit('error', 'NOT_JSON_FILE');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = async (event) => {
|
|
||||||
try {
|
|
||||||
const importedData = JSON.parse(event.target.result);
|
|
||||||
this.$emit('importedJson', importedData);
|
|
||||||
} catch (error) {
|
|
||||||
this.$emit('error', 'INVALID_JSON');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
reader.readAsText(file);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
input {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.drop-zone {
|
|
||||||
&.drag-over {
|
|
||||||
background-color: #fa6;
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
export default {
|
|
||||||
// computed: {
|
|
||||||
// aspectRatioActions: this.settings?.active.commands.crop,
|
|
||||||
// stretchActions: this.settings?.active.commands.stretch
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
export default {
|
|
||||||
computed: {
|
|
||||||
scopeActions: function() {
|
|
||||||
return this.settings?.active.actions?.filter(x => {
|
|
||||||
if (! x.scopes) {
|
|
||||||
console.error('This action does not have a scope.', x);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return x.scopes[this.scope] && x.scopes[this.scope].show
|
|
||||||
}) || [];
|
|
||||||
},
|
|
||||||
extensionActions: function(){
|
|
||||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-ExtensionMode') || [];
|
|
||||||
},
|
|
||||||
aardActions: function(){
|
|
||||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-autoar-mode') || [];
|
|
||||||
},
|
|
||||||
aspectRatioActions: function(){
|
|
||||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-ar') || [];
|
|
||||||
},
|
|
||||||
cropModePersistenceActions: function() {
|
|
||||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-ar-persistence') || [];
|
|
||||||
},
|
|
||||||
stretchActions: function(){
|
|
||||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-stretch') || [];
|
|
||||||
},
|
|
||||||
keyboardActions: function() {
|
|
||||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-keyboard') || [];
|
|
||||||
},
|
|
||||||
alignmentActions: function() {
|
|
||||||
return this.scopeActions.filter(x => x.cmd.length === 1 && x.cmd[0].action === 'set-alignment') || [];
|
|
||||||
},
|
|
||||||
otherActions: function() {
|
|
||||||
return this.scopeActions.filter(x => x.cmd.length > 1) || [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
export default {
|
|
||||||
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
|
|
||||||
* @param {*} command
|
|
||||||
*/
|
|
||||||
execAction(command) {
|
|
||||||
const cmd = JSON.parse(JSON.stringify(command));
|
|
||||||
this.eventBus?.sendToTunnel(cmd.action, cmd.arguments);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
editMode: false,
|
|
||||||
editModeOptions: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
enableEditMode() {
|
|
||||||
this.editMode = true;
|
|
||||||
this.editModeOptions = {};
|
|
||||||
},
|
|
||||||
|
|
||||||
disableEditMode() {
|
|
||||||
this.editMode = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
editAction(command, index, actionType) {
|
|
||||||
try {
|
|
||||||
if (!this.editModeOptions[actionType]) {
|
|
||||||
this.editModeOptions[actionType] = {selected: command, selectedIndex: index}
|
|
||||||
} else {
|
|
||||||
this.editModeOptions[actionType].selected = command;
|
|
||||||
this.editModeOptions[actionType].selectedIndex = index;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`[Ultrawidify] there's a problem with VideoSettings.vue::editAction():`, e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSelectedShortcut(shortcut, actionType) {
|
|
||||||
try {
|
|
||||||
if (!this.editModeOptions[actionType]?.selected) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.editModeOptions[actionType].selected.shortcut = shortcut
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`[Ultrawidify] there's a problem with VideoSettings.vue::updateShortcut():`, e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelEdit(actionType) {
|
|
||||||
try {
|
|
||||||
if (!this.editModeOptions[actionType]) {
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this.editModeOptions[actionType] = undefined;;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`[Ultrawidify] there's a problem with VideoSettings.vue::cancelEdit():`, e);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
validateAspectRatio(actionType) {
|
|
||||||
// deal with potentially invalid aspect ratios
|
|
||||||
if (this.editModeOptions[actionType].selected.arguments.ratio.includes(':')) {
|
|
||||||
const ratioParts = this.editModeOptions[actionType].selected.arguments.ratio.split(':');
|
|
||||||
if (ratioParts.length !== 2) {
|
|
||||||
this.editModeOptions[actionType].error = 'Entered aspect ratio is not valid.';
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
const ratio = ratioParts[0] / ratioParts[1];
|
|
||||||
if (isNaN(ratio) || ratio <= 0) {
|
|
||||||
this.editModeOptions[actionType].error = 'Entered aspect ratio is not valid.';
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.editModeOptions[actionType].selected.arguments.ratio = ratio;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isNaN(this.editModeOptions[actionType].selected.arguments.ratio)) {
|
|
||||||
this.editModeOptions[actionType].error = "Entered aspect ratio is not valid.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete this.editModeOptions[actionType].error;
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates button label, ideally after aspect ratio is entered,
|
|
||||||
* after input field blurred, and only if aspect ratio is valid.
|
|
||||||
*/
|
|
||||||
updateLabel(actionType) {
|
|
||||||
if (this.validateAspectRatio(actionType) && this.editModeOptions[actionType].selected.label === 'New aspect ratio') {
|
|
||||||
this.editModeOptions[actionType].selected.label = this.editModeOptions[actionType].selected.arguments.ratio;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
saveShortcut(actionType) {
|
|
||||||
if (! this.validateAspectRatio(actionType)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.editModeOptions[actionType]?.selectedIndex) {
|
|
||||||
this.settings.active.commands[actionType].push(this.editModeOptions[actionType].selected);
|
|
||||||
}
|
|
||||||
this.settings.active.commands[actionType][this.editModeOptions[actionType].selectedIndex] = this.editModeOptions[actionType]?.selected;
|
|
||||||
this.settings.saveWithoutReload();
|
|
||||||
|
|
||||||
this.editModeOptions[actionType] = undefined;
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteAction(actionType) {
|
|
||||||
const selectedIndex = this.editModeOptions[actionType].selectedIndex;
|
|
||||||
|
|
||||||
// prevent deleting first item if 'delete' button shows on 'add new' dialog
|
|
||||||
if (selectedIndex === undefined || selectedIndex === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.settings.active.commands[actionType].splice(selectedIndex, 1);
|
|
||||||
this.settings.saveWithoutReload();
|
|
||||||
|
|
||||||
this.editModeOptions[actionType] = undefined;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses command's keyboard shortcut into human-readable label
|
|
||||||
*/
|
|
||||||
getKeyboardShortcutLabel(command) {
|
|
||||||
if (! command.shortcut) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return KeyboardShortcutParser.parseShortcut(command.shortcut);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
import KeyboardShortcutParser from '../../../common/js/KeyboardShortcutParser';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
methods: {
|
|
||||||
/**
|
|
||||||
* Parses command's keyboard shortcut into human-readable label
|
|
||||||
*/
|
|
||||||
getKeyboardShortcutLabel(command) {
|
|
||||||
if (! command.shortcut) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
return KeyboardShortcutParser.parseShortcut(command.shortcut);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,157 +0,0 @@
|
|||||||
export default {
|
|
||||||
created() {
|
|
||||||
/**
|
|
||||||
* Setup the "companion" onMouseMove handler to the one in the content script.
|
|
||||||
* We can handle events with the same function we use to handle events from
|
|
||||||
* the content script.
|
|
||||||
*/
|
|
||||||
document.addEventListener('mousemove', (event) => {
|
|
||||||
this.handleProbe(
|
|
||||||
{
|
|
||||||
coords: {
|
|
||||||
x: event.clientX,
|
|
||||||
y: event.clientY
|
|
||||||
},
|
|
||||||
isCompanion: true,
|
|
||||||
},
|
|
||||||
this.origin
|
|
||||||
);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
playerDimensions: undefined,
|
|
||||||
triggerZoneStyles: {
|
|
||||||
height: '50dvh',
|
|
||||||
width: '50dvw',
|
|
||||||
transform: 'translateX(-50%)'
|
|
||||||
},
|
|
||||||
hoverStats: {
|
|
||||||
isOverTriggerZone: false,
|
|
||||||
isOverMenuTrigger: false,
|
|
||||||
isOverUIArea: false,
|
|
||||||
hasMouse: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
playerDimensionsUpdate(dimensions) {
|
|
||||||
if (!dimensions?.width || !dimensions?.height) {
|
|
||||||
this.playerDimensions = undefined;
|
|
||||||
}
|
|
||||||
if (dimensions?.width !== this.playerDimensions?.width || dimensions?.height !== this.playerDimensions?.height) {
|
|
||||||
this.playerDimensions = dimensions;
|
|
||||||
this.updateTriggerZones();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateTriggerZones() {
|
|
||||||
if (this.playerDimensions && this.settings) {
|
|
||||||
this.triggerZoneStyles = {
|
|
||||||
width: `${Math.round(this.playerDimensions.width * this.settings.active.ui.inPlayer.triggerZoneDimensions.width)}px`,
|
|
||||||
height: `${Math.round(this.playerDimensions.height * this.settings.active.ui.inPlayer.triggerZoneDimensions.height)}px`,
|
|
||||||
transform: `translate(${(this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetX)}%, ${this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetY}%)`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles 'uwui-probe' events. It checks whether there's a clickable element under
|
|
||||||
* cursor, and sends a reply to the content scripts that indicates whether pointer-events
|
|
||||||
* property of the iframe should be set to capture or ignore the clicks.
|
|
||||||
*/
|
|
||||||
handleProbe(eventData, origin) {
|
|
||||||
if (eventData.ts < this.lastProbeTs) {
|
|
||||||
return; // i don't know if events can arrive out-of-order. Prolly not. We still check.
|
|
||||||
}
|
|
||||||
this.lastProbeTs = eventData.ts;
|
|
||||||
|
|
||||||
|
|
||||||
/* we check if our mouse is hovering over an element.
|
|
||||||
*
|
|
||||||
* gentleman's agreement: elements with uw-clickable inside the iframe will
|
|
||||||
* toggle pointerEvents on the iframe from 'none' to 'auto'
|
|
||||||
* Children of uw-clickable events should also do that.
|
|
||||||
*
|
|
||||||
* TODO: rename uw-clickable to something else, since we pretty much need that on
|
|
||||||
* our top-level element.
|
|
||||||
*/
|
|
||||||
let isClickable = false;
|
|
||||||
let isOverTriggerZone = false;
|
|
||||||
let isOverUIArea = false;
|
|
||||||
let isOverMenuTrigger = false;
|
|
||||||
const elements = document.elementsFromPoint(eventData.coords.x, eventData.coords.y);
|
|
||||||
|
|
||||||
if (!elements.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const element of elements) {
|
|
||||||
if (element.classList?.contains('uw-clickable')) {
|
|
||||||
isClickable = true;
|
|
||||||
}
|
|
||||||
if (element.classList?.contains('uw-ui-trigger')) {
|
|
||||||
isOverTriggerZone = true;
|
|
||||||
}
|
|
||||||
if (element.classList?.contains('uw-ui-area')) {
|
|
||||||
isOverUIArea = true;
|
|
||||||
}
|
|
||||||
if (element.classList?.contains('uw-menu-trigger')) {
|
|
||||||
isOverMenuTrigger = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
this.triggerZoneActive = isOverTriggerZone;
|
|
||||||
|
|
||||||
// show ultrawidify trigger zone and set it to vanish after 250ms
|
|
||||||
// but don't show the trigger zone behind an active popup
|
|
||||||
if (
|
|
||||||
eventData.canShowUI
|
|
||||||
&& (this.settings.active.ui.inPlayer.activation === 'player' ? isOverUIArea : isOverTriggerZone)
|
|
||||||
) {
|
|
||||||
if (! this.uwWindowVisible) {
|
|
||||||
this.uwTriggerZoneVisible = true;
|
|
||||||
clearTimeout(this.uwTriggerZoneTimeout);
|
|
||||||
|
|
||||||
const windowParent = window.parent;
|
|
||||||
this.uwTriggerZoneTimeout = setTimeout(
|
|
||||||
() => {
|
|
||||||
this.uwTriggerZoneVisible = false;
|
|
||||||
|
|
||||||
windowParent.postMessage(
|
|
||||||
{action: 'uwui-hidden', opacity: false},
|
|
||||||
origin
|
|
||||||
);
|
|
||||||
},
|
|
||||||
500
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// this.uwTriggerZoneVisible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hasMouse = !!document.querySelector(':hover');
|
|
||||||
|
|
||||||
this.hoverStats.isOverTriggerZone = isOverTriggerZone;
|
|
||||||
this.hoverStats.isOverMenuTrigger = isOverMenuTrigger;
|
|
||||||
this.hoverStats.isOverUIArea = isOverUIArea;
|
|
||||||
this.hoverStats.hasMouse = hasMouse
|
|
||||||
|
|
||||||
window.parent.postMessage(
|
|
||||||
{
|
|
||||||
action: 'uwui-clickable',
|
|
||||||
clickable: isClickable,
|
|
||||||
opacity: isClickable || this.contextMenuActive || this.uwTriggerZoneVisible,
|
|
||||||
hoverStats: {
|
|
||||||
isOverTriggerZone: isOverTriggerZone,
|
|
||||||
isOverMenuTrigger: isOverMenuTrigger,
|
|
||||||
isOverUIArea: isOverUIArea,
|
|
||||||
hasMouse: hasMouse
|
|
||||||
},
|
|
||||||
ts: +new Date()
|
|
||||||
},
|
|
||||||
origin
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,14 +1,14 @@
|
|||||||
import Debug from './conf/Debug';
|
import Debug from '@src/ext/conf/Debug';
|
||||||
import Settings from './lib/settings/Settings';
|
import CommsClient, { CommsOrigin } from '@src/ext/module/comms/CommsClient';
|
||||||
import CommsClient from './lib/comms/CommsClient';
|
import EventBus from '@src/ext/module/EventBus';
|
||||||
import PageInfo from './lib/video-data/PageInfo';
|
import KeyboardHandler from '@src/ext/module/kbm/KeyboardHandler';
|
||||||
import EventBus from './lib/EventBus';
|
import { ComponentLogger } from '@src/ext/module/logging/ComponentLogger';
|
||||||
import KeyboardHandler from './lib/kbm/KeyboardHandler';
|
import { BLANK_LOGGER_CONFIG, LogAggregator } from '@src/ext/module/logging/LogAggregator';
|
||||||
import { SiteSettings } from './lib/settings/SiteSettings';
|
import Settings from '@src/ext/module/settings/Settings';
|
||||||
import UI from './lib/uwui/UI';
|
import { SiteSettings } from '@src/ext/module/settings/SiteSettings';
|
||||||
import { BLANK_LOGGER_CONFIG, LogAggregator } from './lib/logging/LogAggregator';
|
import UI from '@src/ext/module/uwui/UI';
|
||||||
import { ComponentLogger } from './lib/logging/ComponentLogger';
|
import PageInfo from '@src/ext/module/video-data/PageInfo';
|
||||||
import { getIframeParentHost, setupHostnameReporting } from './util/getHost';
|
import { getIframeParentHost, setupHostnameReporting } from '@src/common/utils/getHost';
|
||||||
|
|
||||||
export default class UWContent {
|
export default class UWContent {
|
||||||
pageInfo: PageInfo;
|
pageInfo: PageInfo;
|
||||||
@ -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();
|
this.eventBus = new EventBus({name: 'content-script', commsOrigin: CommsOrigin.ContentScript});
|
||||||
this.eventBus.subscribe(
|
this.eventBus.subscribe(
|
||||||
'uw-restart',
|
'uw-restart',
|
||||||
{
|
{
|
||||||
@ -120,9 +120,12 @@ export default class UWContent {
|
|||||||
|
|
||||||
this.logger.debug('setup', "KeyboardHandler initiated.");
|
this.logger.debug('setup', "KeyboardHandler initiated.");
|
||||||
|
|
||||||
this.globalUi = new UI('ultrawidify-global-ui', {eventBus: this.eventBus, isGlobal: true});
|
if (this.globalUi) {
|
||||||
this.globalUi.enable();
|
this.globalUi.destroy();
|
||||||
this.globalUi.setUiVisibility(false);
|
}
|
||||||
|
// this.globalUi = new UI('ultrawidify-global-ui', {eventBus: this.eventBus, isGlobal: true});
|
||||||
|
// this.globalUi.enable();
|
||||||
|
// this.globalUi.setUiVisibility(false);
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Ultrawidify: failed to start extension. Error:', e)
|
console.error('Ultrawidify: failed to start extension. Error:', e)
|
||||||
|
|||||||
@ -1,11 +1,13 @@
|
|||||||
import Debug from './conf/Debug.js';
|
import { Runtime } from 'chrome';
|
||||||
import BrowserDetect from './conf/BrowserDetect';
|
import EventBus from '@src/ext/module/EventBus';
|
||||||
import CommsServer from './lib/comms/CommsServer';
|
import Settings from '@src/ext/module/settings/Settings';
|
||||||
import Settings from './lib/settings/Settings';
|
import { ComponentLogger } from '@src/ext/module/logging/ComponentLogger';
|
||||||
import { sleep } from '../common/js/utils';
|
import { BLANK_LOGGER_CONFIG, LogAggregator } from '@src/ext/module/logging/LogAggregator';
|
||||||
import EventBus, { EventBusCommand } from './lib/EventBus';
|
import CommsServer from '@src/ext/module/comms/CommsServer';
|
||||||
import { ComponentLogger } from './lib/logging/ComponentLogger';
|
import BrowserDetect from '@src/ext/conf/BrowserDetect';
|
||||||
import { BLANK_LOGGER_CONFIG, LogAggregator } from './lib/logging/LogAggregator';
|
import { HostInfo } from '@src/common/interfaces/HostData.interface';
|
||||||
|
import { ExtensionEnvironment } from '@src/common/interfaces/SettingsInterface';
|
||||||
|
import { CommsOrigin } from '@src/ext/module/comms/CommsClient';
|
||||||
|
|
||||||
|
|
||||||
const BASE_LOGGING_STYLES = {
|
const BASE_LOGGING_STYLES = {
|
||||||
@ -50,7 +52,7 @@ export default class UWServer {
|
|||||||
function: (message, context) => this.replaceCss(message.oldCssString, message.newCssString, context.comms.sender)
|
function: (message, context) => this.replaceCss(message.oldCssString, message.newCssString, context.comms.sender)
|
||||||
},
|
},
|
||||||
'get-current-site': {
|
'get-current-site': {
|
||||||
function: (message, context) => this.getCurrentSite()
|
function: (message, context) => this.getCurrentSite(context.comms.sender)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -80,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);
|
||||||
|
|
||||||
@ -101,9 +103,14 @@ export default class UWServer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//#region CSS managemeent
|
//#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;
|
||||||
@ -124,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: {
|
||||||
@ -140,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);
|
||||||
@ -196,12 +212,16 @@ 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);
|
||||||
const frameHostname = this.extractHostname(sender.url);
|
const frameHostname = this.extractHostname(sender.url);
|
||||||
|
|
||||||
// check for orphaned/outdated values and remove them if neccessary
|
// check for orphaned/outdated values and remove them if necessary
|
||||||
if (this.videoTabs[sender.tab.id]?.host != tabHostname) {
|
if (this.videoTabs[sender.tab.id]?.host != tabHostname) {
|
||||||
delete this.videoTabs[sender.tab.id]
|
delete this.videoTabs[sender.tab.id]
|
||||||
} else if(this.videoTabs[sender.tab.id]?.frames[sender.frameId]?.host != frameHostname) {
|
} else if(this.videoTabs[sender.tab.id]?.frames[sender.frameId]?.host != frameHostname) {
|
||||||
@ -234,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) {
|
||||||
@ -252,9 +277,11 @@ export default class UWServer {
|
|||||||
this.selectedSubitem[menu] = subitem;
|
this.selectedSubitem[menu] = subitem;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCurrentSite() {
|
async getCurrentSite(sender: Runtime.MessageSender) {
|
||||||
this.logger.info('getCurrentSite', 'received get-current-site ...');
|
// 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.
|
||||||
@ -265,6 +292,7 @@ export default class UWServer {
|
|||||||
|
|
||||||
const tabHostname = await this.getCurrentTabHostname();
|
const tabHostname = await this.getCurrentTabHostname();
|
||||||
this.logger.info('getCurrentSite', 'Returning data:', {site, tabHostname});
|
this.logger.info('getCurrentSite', 'Returning data:', {site, tabHostname});
|
||||||
|
console.info('get-current-site : returning data:', {site, tabHostname});
|
||||||
|
|
||||||
this.eventBus.send(
|
this.eventBus.send(
|
||||||
'set-current-site',
|
'set-current-site',
|
||||||
@ -285,6 +313,36 @@ export default class UWServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private populateFrameVideoStatus(tabId: number, hostnames: string[]) {
|
||||||
|
const out: HostInfo[] = [];
|
||||||
|
|
||||||
|
for (const host of hostnames) {
|
||||||
|
let video = {hasVideo: false, minEnvironment: ExtensionEnvironment.Normal, maxEnvironment: ExtensionEnvironment.Fullscreen};
|
||||||
|
|
||||||
|
for (const frameKey in this.videoTabs[tabId].frames) {
|
||||||
|
const frame = this.videoTabs[tabId].frames[frameKey];
|
||||||
|
|
||||||
|
if (frame.host === host) {
|
||||||
|
video.hasVideo = true;
|
||||||
|
if (frame.environment > video.maxEnvironment) {
|
||||||
|
video.maxEnvironment = frame.environment;
|
||||||
|
}
|
||||||
|
if (frame.environment < video.minEnvironment) {
|
||||||
|
video.minEnvironment = frame.environment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push({
|
||||||
|
host,
|
||||||
|
...video,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _lastVideoTabData: any | undefined;
|
||||||
async getVideoTab() {
|
async getVideoTab() {
|
||||||
// friendly reminder: if current tab doesn't have a video,
|
// friendly reminder: if current tab doesn't have a video,
|
||||||
// there won't be anything in this.videoTabs[this.currentTabId]
|
// there won't be anything in this.videoTabs[this.currentTabId]
|
||||||
@ -301,7 +359,15 @@ export default class UWServer {
|
|||||||
|
|
||||||
const hostnames = await this.comms.listUniqueFrameHosts();
|
const hostnames = await this.comms.listUniqueFrameHosts();
|
||||||
|
|
||||||
if (this.videoTabs[ctab.id]) {
|
// this probably means we're inside a problematic page
|
||||||
|
if (!this.videoTabs[ctab.id]) {
|
||||||
|
return this._lastVideoTabData ?? {
|
||||||
|
host: 'INVALID SITE',
|
||||||
|
frames: [],
|
||||||
|
hostnames: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if video is older than PageInfo's video rescan period (+ 4000ms of grace),
|
// if video is older than PageInfo's video rescan period (+ 4000ms of grace),
|
||||||
// we clean it up from videoTabs[tabId].frames array.
|
// we clean it up from videoTabs[tabId].frames array.
|
||||||
const ageLimit = Date.now() - this.settings.active.pageInfo.timeouts.rescan - 4000;
|
const ageLimit = Date.now() - this.settings.active.pageInfo.timeouts.rescan - 4000;
|
||||||
@ -313,29 +379,32 @@ export default class UWServer {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// something went wrong. There's prolly no frames.
|
// something went wrong. There's prolly no frames.
|
||||||
|
// this probably shouldn't ever run.
|
||||||
return {
|
return {
|
||||||
host: this.extractHostname(ctab.url),
|
host: this.extractHostname(ctab.url),
|
||||||
|
hostnames: [],
|
||||||
frames: [],
|
frames: [],
|
||||||
selected: this.selectedSubitem
|
selected: this.selectedSubitem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
// Ensure hostnames with videos come before hostnames without videos
|
||||||
...this.videoTabs[ctab.id],
|
// Also ensure hostnames are sorted alphabetically
|
||||||
|
const populatedHostnames = this.populateFrameVideoStatus(ctab.id, hostnames);
|
||||||
|
populatedHostnames.sort((a: HostInfo, b: HostInfo) => {
|
||||||
|
return a.hasVideo === b.hasVideo
|
||||||
|
? a.host === b.host ? 0 : a.host < b.host ? -1 : 1
|
||||||
|
: a.hasVideo < b.hasVideo ? 1 : -1;
|
||||||
|
});
|
||||||
|
|
||||||
|
this._lastVideoTabData = {
|
||||||
host: this.extractHostname(ctab.url),
|
host: this.extractHostname(ctab.url),
|
||||||
hostnames,
|
hostnames: populatedHostnames.map(x => x.host), // todo: try eliminating this
|
||||||
|
populatedHostnames: populatedHostnames,
|
||||||
selected: this.selectedSubitem
|
selected: this.selectedSubitem
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
// return something more or less empty if this tab doesn't have
|
return this._lastVideoTabData;
|
||||||
// a video registered for it
|
|
||||||
return {
|
|
||||||
host: this.extractHostname(ctab.url),
|
|
||||||
frames: [],
|
|
||||||
hostnames,
|
|
||||||
selected: this.selectedSubitem,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCurrentTabHostname() {
|
async getCurrentTabHostname() {
|
||||||
|
|||||||
@ -1,19 +1,16 @@
|
|||||||
// How to use:
|
// How to use:
|
||||||
// version: {ExtensionConf object, but only properties that get overwritten}
|
// version: {ExtensionConf object, but only properties that get overwritten}
|
||||||
import StretchType from '../../common/enums/StretchType.enum';
|
import AspectRatioType from '@src/common/enums/AspectRatioType.enum';
|
||||||
// import ExtensionMode from '../../common/enums/ExtensionMode.enum';
|
import CropModePersistence from '@src/common/enums/CropModePersistence.enum';
|
||||||
import VideoAlignmentType from '../../common/enums/VideoAlignmentType.enum';
|
import EmbeddedContentSettingsOverridePolicy from '@src/common/enums/EmbeddedContentSettingsOverridePolicy.enum';
|
||||||
import BrowserDetect from './BrowserDetect';
|
import ExtensionMode from '@src/common/enums/ExtensionMode.enum';
|
||||||
import SettingsInterface, { SiteSettingsInterface } from '../../common/interfaces/SettingsInterface';
|
import { InputHandlingMode } from '@src/common/enums/InputHandlingMode.enum';
|
||||||
import { _cp } from '../../common/js/utils';
|
import LegacyExtensionMode from '@src/common/enums/LegacyExtensionMode.enum';
|
||||||
import CropModePersistence from '../../common/enums/CropModePersistence.enum';
|
import { PlayerDetectionMode } from '@src/common/enums/PlayerDetectionMode.enum';
|
||||||
import AspectRatioType from '../../common/enums/AspectRatioType.enum';
|
import { SiteSupportLevel } from '@src/common/enums/SiteSupportLevel.enum';
|
||||||
|
import SettingsInterface from '@src/common/interfaces/SettingsInterface';
|
||||||
|
import { _cp } from '@src/common/utils/_cp';
|
||||||
import { update } from 'lodash';
|
import { update } from 'lodash';
|
||||||
import EmbeddedContentSettingsOverridePolicy from '../../common/enums/EmbeddedContentSettingsOverridePolicy.enum';
|
|
||||||
import LegacyExtensionMode from '../../common/enums/LegacyExtensionMode.enum';
|
|
||||||
import ExtensionMode from '../../common/enums/ExtensionMode.enum';
|
|
||||||
import { PlayerDetectionMode } from '../../common/enums/PlayerDetectionMode.enum';
|
|
||||||
import { SiteSupportLevel } from '../../common/enums/SiteSupportLevel.enum';
|
|
||||||
|
|
||||||
|
|
||||||
const ExtensionConfPatch = Object.freeze([
|
const ExtensionConfPatch = Object.freeze([
|
||||||
@ -312,7 +309,13 @@ const ExtensionConfPatch = Object.freeze([
|
|||||||
logger.log('updateFn', '\n\n ... migrating default enable-state for site', key);
|
logger.log('updateFn', '\n\n ... migrating default enable-state for site', key);
|
||||||
userOptions.sites[key].enable = convertLegacyExtensionMode(userOptions.sites[key].enable as any);
|
userOptions.sites[key].enable = convertLegacyExtensionMode(userOptions.sites[key].enable as any);
|
||||||
userOptions.sites[key].enableAard = convertLegacyExtensionMode(userOptions.sites[key].enableAard as any);
|
userOptions.sites[key].enableAard = convertLegacyExtensionMode(userOptions.sites[key].enableAard as any);
|
||||||
userOptions.sites[key].enableKeyboard = convertLegacyExtensionMode(userOptions.sites[key].enableKeyboard as any);
|
|
||||||
|
if (key === '@global') {
|
||||||
|
userOptions.sites['@global'].enableKeyboard = userOptions.kbm.enabled && userOptions.kbm.keyboardEnabled ? InputHandlingMode.Enabled : InputHandlingMode.Disabled;
|
||||||
|
} else {
|
||||||
|
userOptions.sites[key].enableKeyboard = InputHandlingMode.Default;
|
||||||
|
}
|
||||||
|
|
||||||
userOptions.sites[key].enableUI = convertLegacyExtensionMode(
|
userOptions.sites[key].enableUI = convertLegacyExtensionMode(
|
||||||
userOptions.sites[key].enableUI ?? (key === '@global' ? ExtensionMode.FullScreen : ExtensionMode.Default) as any
|
userOptions.sites[key].enableUI ?? (key === '@global' ? ExtensionMode.FullScreen : ExtensionMode.Default) as any
|
||||||
);
|
);
|
||||||
@ -460,6 +463,74 @@ const ExtensionConfPatch = Object.freeze([
|
|||||||
|
|
||||||
logger.log('updateFn', 'Migration complete. New site settings:', userOptions.sites);
|
logger.log('updateFn', 'Migration complete. New site settings:', userOptions.sites);
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
forVersion: '6.3.997',
|
||||||
|
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface, logger?) => {
|
||||||
|
|
||||||
|
// default zoom key combinations that involved the 'shift' key should have
|
||||||
|
// shortcut.key in uppercase, but they didn't.
|
||||||
|
for (const command of userOptions.commands?.zoom) {
|
||||||
|
if (command.shortcut) {
|
||||||
|
if (command.shortcut.shiftKey) {
|
||||||
|
if (command.shortcut.key.toUpperCase() === command.shortcut.code.charAt(3)) {
|
||||||
|
command.shortcut.key = command.shortcut.key.toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
forVersion: '6.3.998',
|
||||||
|
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface, logger?) => {
|
||||||
|
if (!userOptions.sites["www.amazon.com"]) {
|
||||||
|
userOptions.sites["www.amazon.com"] = _cp(defaultOptions.sites["www.amazon.com"] );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userOptions.commands?.zoom) {
|
||||||
|
const firstFixed = userOptions.commands?.zoom.findIndex(x => x.arguments.type === AspectRatioType.Fixed);
|
||||||
|
|
||||||
|
if (firstFixed === -1) {
|
||||||
|
userOptions.commands.zoom.push({
|
||||||
|
action: 'set-ar-zoom',
|
||||||
|
label: 'Cover',
|
||||||
|
comment: 'Covers the entire screen, cropping as much as needed',
|
||||||
|
arguments: {
|
||||||
|
type: AspectRatioType.Cover
|
||||||
|
},
|
||||||
|
shortcut: {
|
||||||
|
key: 'w',
|
||||||
|
code: 'KeyW',
|
||||||
|
ctrlKey: false,
|
||||||
|
metaKey: false,
|
||||||
|
altKey: false,
|
||||||
|
shiftKey: true,
|
||||||
|
onKeyUp: true,
|
||||||
|
onKeyDown: false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
userOptions.commands.zoom.splice(firstFixed, 0, {
|
||||||
|
action: 'set-ar-zoom',
|
||||||
|
label: 'Cover',
|
||||||
|
comment: 'Covers the entire screen, cropping as much as needed',
|
||||||
|
arguments: {
|
||||||
|
type: AspectRatioType.Cover
|
||||||
|
},
|
||||||
|
shortcut: {
|
||||||
|
key: 'w',
|
||||||
|
code: 'KeyW',
|
||||||
|
ctrlKey: false,
|
||||||
|
metaKey: false,
|
||||||
|
altKey: false,
|
||||||
|
shiftKey: true,
|
||||||
|
onKeyUp: true,
|
||||||
|
onKeyDown: false,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -1,19 +1,17 @@
|
|||||||
import Debug from './Debug';
|
import AspectRatioType from '@src/common/enums/AspectRatioType.enum';
|
||||||
import currentBrowser from './BrowserDetect';
|
import CropModePersistence from '@src/common/enums/CropModePersistence.enum';
|
||||||
import VideoAlignmentType from '../../common/enums/VideoAlignmentType.enum';
|
import EmbeddedContentSettingsOverridePolicy from '@src/common/enums/EmbeddedContentSettingsOverridePolicy.enum';
|
||||||
import StretchType from '../../common/enums/StretchType.enum';
|
import ExtensionMode from '@src/common/enums/ExtensionMode.enum';
|
||||||
import ExtensionMode from '../../common/enums/ExtensionMode.enum';
|
import { InputHandlingMode } from '@src/common/enums/InputHandlingMode.enum';
|
||||||
import AntiGradientMode from '../../common/enums/AntiGradientMode.enum';
|
import { PlayerDetectionMode } from '@src/common/enums/PlayerDetectionMode.enum';
|
||||||
import AspectRatioType from '../../common/enums/AspectRatioType.enum';
|
import { SiteSupportLevel } from '@src/common/enums/SiteSupportLevel.enum';
|
||||||
import CropModePersistence from '../../common/enums/CropModePersistence.enum';
|
import StretchType from '@src/common/enums/StretchType.enum';
|
||||||
import SettingsInterface from '../../common/interfaces/SettingsInterface';
|
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum';
|
||||||
import BrowserDetect from './BrowserDetect';
|
import { MenuPosition } from '@src/common/interfaces/ClientUiMenu.interface';
|
||||||
import { Extension } from 'typescript';
|
import SettingsInterface from '@src/common/interfaces/SettingsInterface';
|
||||||
import EmbeddedContentSettingsOverridePolicy from '../../common/enums/EmbeddedContentSettingsOverridePolicy.enum';
|
import Debug from '@src/ext/conf/Debug';
|
||||||
import { AardPollingOptions } from '../lib/aard/enums/aard-polling-options.enum';
|
import { AardPollingOptions } from '@src/ext/module/aard/enums/aard-polling-options.enum';
|
||||||
import { AardSubtitleCropMode } from '../lib/aard/enums/aard-subtitle-crop-mode.enum';
|
import { AardSubtitleCropMode } from '@src/ext/module/aard/enums/aard-subtitle-crop-mode.enum';
|
||||||
import { SiteSupportLevel } from '../../common/enums/SiteSupportLevel.enum';
|
|
||||||
import { PlayerDetectionMode } from '../../common/enums/PlayerDetectionMode.enum';
|
|
||||||
|
|
||||||
if(Debug.debug)
|
if(Debug.debug)
|
||||||
console.log("Loading: ExtensionConf.js");
|
console.log("Loading: ExtensionConf.js");
|
||||||
@ -267,7 +265,11 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
minEnabledWidth: 0.75,
|
minEnabledWidth: 0.75,
|
||||||
minEnabledHeight: 0.75,
|
minEnabledHeight: 0.75,
|
||||||
activation: 'player',
|
activation: 'player',
|
||||||
popupAlignment: 'left',
|
activationDistance: 100,
|
||||||
|
activationDistanceUnits: '%',
|
||||||
|
activatorAlignment: MenuPosition.Left,
|
||||||
|
activatorPadding: {x: 16, y: 16},
|
||||||
|
activatorPaddingUnit: {x: 'px', y: 'px'},
|
||||||
triggerZoneDimensions: {
|
triggerZoneDimensions: {
|
||||||
width: 0.5,
|
width: 0.5,
|
||||||
height: 0.5,
|
height: 0.5,
|
||||||
@ -293,11 +295,6 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
maxLogZoom: 3,
|
maxLogZoom: 3,
|
||||||
announceDebounce: 200 // we wait this long before announcing new zoom
|
announceDebounce: 200 // we wait this long before announcing new zoom
|
||||||
},
|
},
|
||||||
kbm: {
|
|
||||||
enabled: true,
|
|
||||||
keyboardEnabled: true,
|
|
||||||
mouseEnabled: true
|
|
||||||
},
|
|
||||||
miscSettings: {
|
miscSettings: {
|
||||||
mousePan: {
|
mousePan: {
|
||||||
enabled: false
|
enabled: false
|
||||||
@ -357,40 +354,6 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
onKeyUp: true,
|
onKeyUp: true,
|
||||||
onKeyDown: false,
|
onKeyDown: false,
|
||||||
}
|
}
|
||||||
}, {
|
|
||||||
action: 'set-ar',
|
|
||||||
label: 'Fit width',
|
|
||||||
comment: 'Make the video fit the entire width of the player, crop top and bottom if necessary',
|
|
||||||
arguments: {
|
|
||||||
type: AspectRatioType.FitWidth
|
|
||||||
},
|
|
||||||
shortcut: {
|
|
||||||
key: 'w',
|
|
||||||
code: 'KeyW',
|
|
||||||
ctrlKey: false,
|
|
||||||
metaKey: false,
|
|
||||||
altKey: false,
|
|
||||||
shiftKey: false,
|
|
||||||
onKeyUp: true,
|
|
||||||
onKeyDown: false,
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
action: 'set-ar',
|
|
||||||
label: 'Fit height',
|
|
||||||
comment: 'Make the video fit the entire height of the player, crop left and right if necessary',
|
|
||||||
arguments: {
|
|
||||||
type: AspectRatioType.FitHeight
|
|
||||||
},
|
|
||||||
shortcut: {
|
|
||||||
key: 'e',
|
|
||||||
code: 'KeyE',
|
|
||||||
ctrlKey: false,
|
|
||||||
metaKey: false,
|
|
||||||
altKey: false,
|
|
||||||
shiftKey: false,
|
|
||||||
onKeyUp: true,
|
|
||||||
onKeyDown: false,
|
|
||||||
}
|
|
||||||
}, {
|
}, {
|
||||||
action: 'set-ar',
|
action: 'set-ar',
|
||||||
label: 'Cycle',
|
label: 'Cycle',
|
||||||
@ -576,6 +539,23 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
onKeyUp: true,
|
onKeyUp: true,
|
||||||
onKeyDown: false,
|
onKeyDown: false,
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
action: 'set-ar-zoom',
|
||||||
|
label: 'Cover',
|
||||||
|
comment: 'Covers the entire screen, cropping as much as needed',
|
||||||
|
arguments: {
|
||||||
|
type: AspectRatioType.Cover
|
||||||
|
},
|
||||||
|
shortcut: {
|
||||||
|
key: 'w',
|
||||||
|
code: 'KeyW',
|
||||||
|
ctrlKey: false,
|
||||||
|
metaKey: false,
|
||||||
|
altKey: false,
|
||||||
|
shiftKey: false,
|
||||||
|
onKeyUp: true,
|
||||||
|
onKeyDown: false,
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
action: 'set-ar-zoom',
|
action: 'set-ar-zoom',
|
||||||
label: 'Cycle',
|
label: 'Cycle',
|
||||||
@ -805,7 +785,7 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
// #s — only available for specific site
|
// #s — only available for specific site
|
||||||
enable: ExtensionMode.Disabled,
|
enable: ExtensionMode.Disabled,
|
||||||
enableAard: ExtensionMode.Theater,
|
enableAard: ExtensionMode.Theater,
|
||||||
enableKeyboard: ExtensionMode.All,
|
enableKeyboard: InputHandlingMode.Enabled,
|
||||||
enableUI: ExtensionMode.FullScreen,
|
enableUI: ExtensionMode.FullScreen,
|
||||||
|
|
||||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
||||||
@ -843,7 +823,7 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
|
|
||||||
enable: ExtensionMode.Default,
|
enable: ExtensionMode.Default,
|
||||||
enableAard: ExtensionMode.Default,
|
enableAard: ExtensionMode.Default,
|
||||||
enableKeyboard: ExtensionMode.Default,
|
enableKeyboard: InputHandlingMode.Enabled,
|
||||||
enableUI: ExtensionMode.Default,
|
enableUI: ExtensionMode.Default,
|
||||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Default,
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Default,
|
||||||
overrideWhenEmbedded: EmbeddedContentSettingsOverridePolicy.Default,
|
overrideWhenEmbedded: EmbeddedContentSettingsOverridePolicy.Default,
|
||||||
@ -880,7 +860,7 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
"www.youtube.com": {
|
"www.youtube.com": {
|
||||||
enable: ExtensionMode.All,
|
enable: ExtensionMode.All,
|
||||||
enableAard: ExtensionMode.All,
|
enableAard: ExtensionMode.All,
|
||||||
enableKeyboard: ExtensionMode.All,
|
enableKeyboard: InputHandlingMode.Default,
|
||||||
enableUI: ExtensionMode.Default,
|
enableUI: ExtensionMode.Default,
|
||||||
|
|
||||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
||||||
@ -907,7 +887,7 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
"www.youtube-nocookie.com": {
|
"www.youtube-nocookie.com": {
|
||||||
enable: ExtensionMode.All,
|
enable: ExtensionMode.All,
|
||||||
enableAard: ExtensionMode.All,
|
enableAard: ExtensionMode.All,
|
||||||
enableKeyboard: ExtensionMode.All,
|
enableKeyboard: InputHandlingMode.Default,
|
||||||
enableUI: ExtensionMode.FullScreen,
|
enableUI: ExtensionMode.FullScreen,
|
||||||
|
|
||||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
||||||
@ -935,7 +915,7 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
"www.netflix.com" : {
|
"www.netflix.com" : {
|
||||||
enable: ExtensionMode.Theater,
|
enable: ExtensionMode.Theater,
|
||||||
enableAard: ExtensionMode.Disabled,
|
enableAard: ExtensionMode.Disabled,
|
||||||
enableKeyboard: ExtensionMode.All,
|
enableKeyboard: InputHandlingMode.Default,
|
||||||
enableUI: ExtensionMode.All,
|
enableUI: ExtensionMode.All,
|
||||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
||||||
override: false,
|
override: false,
|
||||||
@ -960,7 +940,7 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
"www.disneyplus.com" : {
|
"www.disneyplus.com" : {
|
||||||
enable: ExtensionMode.Theater,
|
enable: ExtensionMode.Theater,
|
||||||
enableAard: ExtensionMode.Theater,
|
enableAard: ExtensionMode.Theater,
|
||||||
enableKeyboard: ExtensionMode.All,
|
enableKeyboard: InputHandlingMode.Default,
|
||||||
enableUI: ExtensionMode.FullScreen,
|
enableUI: ExtensionMode.FullScreen,
|
||||||
|
|
||||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
||||||
@ -986,10 +966,33 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"www.amazon.com": {
|
||||||
|
enable: ExtensionMode.Default,
|
||||||
|
enableAard: ExtensionMode.Default,
|
||||||
|
enableKeyboard: InputHandlingMode.Enabled,
|
||||||
|
enableUI: ExtensionMode.Default,
|
||||||
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Default,
|
||||||
|
overrideWhenEmbedded: EmbeddedContentSettingsOverridePolicy.Default,
|
||||||
|
type: SiteSupportLevel.CommunitySupport,
|
||||||
|
defaultType: SiteSupportLevel.CommunitySupport,
|
||||||
|
persistCSA: CropModePersistence.Default,
|
||||||
|
activeDOMConfig: "@community",
|
||||||
|
DOMConfig: {
|
||||||
|
"@community": {
|
||||||
|
type: SiteSupportLevel.CommunitySupport,
|
||||||
|
elements: {
|
||||||
|
player: {
|
||||||
|
detectionMode: PlayerDetectionMode.Auto,
|
||||||
|
allowAutoFallback: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
"www.twitch.tv": {
|
"www.twitch.tv": {
|
||||||
enable: ExtensionMode.All,
|
enable: ExtensionMode.All,
|
||||||
enableAard: ExtensionMode.All,
|
enableAard: ExtensionMode.All,
|
||||||
enableKeyboard: ExtensionMode.All,
|
enableKeyboard: InputHandlingMode.Default,
|
||||||
enableUI: ExtensionMode.FullScreen,
|
enableUI: ExtensionMode.FullScreen,
|
||||||
|
|
||||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
||||||
@ -1013,7 +1016,7 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
"old.reddit.com" : {
|
"old.reddit.com" : {
|
||||||
enable: ExtensionMode.Disabled,
|
enable: ExtensionMode.Disabled,
|
||||||
enableAard: ExtensionMode.Disabled,
|
enableAard: ExtensionMode.Disabled,
|
||||||
enableKeyboard: ExtensionMode.Disabled,
|
enableKeyboard: InputHandlingMode.Disabled,
|
||||||
enableUI: ExtensionMode.Disabled,
|
enableUI: ExtensionMode.Disabled,
|
||||||
|
|
||||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Never,
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Never,
|
||||||
@ -1023,7 +1026,7 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
"www.reddit.com" : {
|
"www.reddit.com" : {
|
||||||
enable: ExtensionMode.Disabled,
|
enable: ExtensionMode.Disabled,
|
||||||
enableAard: ExtensionMode.Disabled,
|
enableAard: ExtensionMode.Disabled,
|
||||||
enableKeyboard: ExtensionMode.Disabled,
|
enableKeyboard: InputHandlingMode.Disabled,
|
||||||
enableUI: ExtensionMode.Disabled,
|
enableUI: ExtensionMode.Disabled,
|
||||||
|
|
||||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Never,
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Never,
|
||||||
@ -1033,7 +1036,7 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
"imgur.com": {
|
"imgur.com": {
|
||||||
enable: ExtensionMode.Disabled,
|
enable: ExtensionMode.Disabled,
|
||||||
enableAard: ExtensionMode.Disabled,
|
enableAard: ExtensionMode.Disabled,
|
||||||
enableKeyboard: ExtensionMode.Disabled,
|
enableKeyboard: InputHandlingMode.Disabled,
|
||||||
enableUI: ExtensionMode.Disabled,
|
enableUI: ExtensionMode.Disabled,
|
||||||
|
|
||||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Never,
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Never,
|
||||||
@ -1043,7 +1046,7 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
"www.wakanim.tv": {
|
"www.wakanim.tv": {
|
||||||
enable: ExtensionMode.All,
|
enable: ExtensionMode.All,
|
||||||
enableAard: ExtensionMode.All,
|
enableAard: ExtensionMode.All,
|
||||||
enableKeyboard: ExtensionMode.All,
|
enableKeyboard: InputHandlingMode.Default,
|
||||||
enableUI: ExtensionMode.FullScreen,
|
enableUI: ExtensionMode.FullScreen,
|
||||||
|
|
||||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
||||||
@ -1066,7 +1069,7 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
"app.plex.tv": {
|
"app.plex.tv": {
|
||||||
enable: ExtensionMode.Theater,
|
enable: ExtensionMode.Theater,
|
||||||
enableAard: ExtensionMode.Theater,
|
enableAard: ExtensionMode.Theater,
|
||||||
enableKeyboard: ExtensionMode.Theater,
|
enableKeyboard: InputHandlingMode.Default,
|
||||||
enableUI: ExtensionMode.FullScreen,
|
enableUI: ExtensionMode.FullScreen,
|
||||||
|
|
||||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
||||||
@ -1076,7 +1079,7 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
"metaivi.com": {
|
"metaivi.com": {
|
||||||
enable: ExtensionMode.Theater,
|
enable: ExtensionMode.Theater,
|
||||||
enableAard: ExtensionMode.Theater,
|
enableAard: ExtensionMode.Theater,
|
||||||
enableKeyboard: ExtensionMode.Theater,
|
enableKeyboard: InputHandlingMode.Default,
|
||||||
enableUI: ExtensionMode.FullScreen,
|
enableUI: ExtensionMode.FullScreen,
|
||||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
||||||
type: SiteSupportLevel.CommunitySupport,
|
type: SiteSupportLevel.CommunitySupport,
|
||||||
@ -1085,7 +1088,7 @@ const ExtensionConf: SettingsInterface = {
|
|||||||
"piped.kavin.rocks": {
|
"piped.kavin.rocks": {
|
||||||
enable: ExtensionMode.Theater,
|
enable: ExtensionMode.Theater,
|
||||||
enableAard: ExtensionMode.Theater,
|
enableAard: ExtensionMode.Theater,
|
||||||
enableKeyboard: ExtensionMode.Theater,
|
enableKeyboard: InputHandlingMode.Default,
|
||||||
enableUI: ExtensionMode.FullScreen,
|
enableUI: ExtensionMode.FullScreen,
|
||||||
|
|
||||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
|
||||||
|
|||||||
@ -1,200 +0,0 @@
|
|||||||
import { IframeData } from './video-data/IframeManager';
|
|
||||||
import CommsClient, { CommsOrigin } from './comms/CommsClient';
|
|
||||||
import CommsServer from './comms/CommsServer';
|
|
||||||
|
|
||||||
export interface EventBusCommand {
|
|
||||||
isGlobal?: boolean,
|
|
||||||
source?: any,
|
|
||||||
function: (commandData: any, context?: any) => void | Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface EventBusContext {
|
|
||||||
stopPropagation?: boolean,
|
|
||||||
|
|
||||||
// Context stuff added by Comms
|
|
||||||
origin?: CommsOrigin,
|
|
||||||
comms?: {
|
|
||||||
sender?: any,
|
|
||||||
port?: any,
|
|
||||||
frame?: any,
|
|
||||||
sourceFrame?: IframeData
|
|
||||||
forwardTo?: 'all' | 'active' | 'contentScript' | 'server' | 'sameOrigin' | 'popup' | 'all-frames',
|
|
||||||
};
|
|
||||||
borderCrossings?: {
|
|
||||||
commsServer?: boolean,
|
|
||||||
iframe?: boolean,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class EventBus {
|
|
||||||
|
|
||||||
private commands: { [x: string]: EventBusCommand[]} = {};
|
|
||||||
private comms?: CommsClient | CommsServer;
|
|
||||||
|
|
||||||
private disableTunnel: boolean = false;
|
|
||||||
private popupContext: any = {};
|
|
||||||
|
|
||||||
private iframeForwardingList: {iframe: any, fn: (action, payload, context?) => void}[] = [];
|
|
||||||
|
|
||||||
// private uiUri = window.location.href;
|
|
||||||
|
|
||||||
constructor(options?: {isUWServer?: boolean}) {
|
|
||||||
if (!options?.isUWServer) {
|
|
||||||
this.setupIframeTunnelling();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setupPopupTunnelWorkaround(context: EventBusContext): void {
|
|
||||||
this.disableTunnel = true;
|
|
||||||
this.popupContext = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
//#region lifecycle
|
|
||||||
destroy() {
|
|
||||||
this.commands = null;
|
|
||||||
this.destroyIframeTunnelling();
|
|
||||||
}
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
setComms(comms: CommsClient | CommsServer) {
|
|
||||||
this.comms = comms;
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe(commandString: string, command: EventBusCommand) {
|
|
||||||
if (!this.commands[commandString]) {
|
|
||||||
this.commands[commandString] = [command];
|
|
||||||
} else {
|
|
||||||
this.commands[commandString].push(command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribeMulti(commands: {[commandString: string]: EventBusCommand}, source?: any) {
|
|
||||||
for (const key in commands) {
|
|
||||||
this.subscribe(
|
|
||||||
key,
|
|
||||||
{
|
|
||||||
...commands[key],
|
|
||||||
source: source ?? commands[key].source
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all commands from a given source
|
|
||||||
* @param source
|
|
||||||
*/
|
|
||||||
unsubscribeAll(source: any) {
|
|
||||||
for (const commandString in this.commands) {
|
|
||||||
this.commands[commandString] = this.commands[commandString].filter(x => x.source !== source);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
forwardToIframe(iframe: any, fn: (action: string, payload: any, context?: EventBusContext) => void) {
|
|
||||||
this.cancelIframeForwarding(iframe);
|
|
||||||
this.iframeForwardingList.push({iframe, fn});
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelIframeForwarding(iframe: any) {
|
|
||||||
const existingForwarding = this.iframeForwardingList.findIndex((x: any) => x.iframe === iframe);
|
|
||||||
if (existingForwarding !== -1) {
|
|
||||||
this.iframeForwardingList.splice(existingForwarding, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
send(command: string, commandData: any, context?: EventBusContext) {
|
|
||||||
// execute commands we have subscriptions for
|
|
||||||
|
|
||||||
if (this.commands?.[command]) {
|
|
||||||
for (const eventBusCommand of this.commands[command]) {
|
|
||||||
eventBusCommand.function(commandData, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// preventing messages from flowing back to their original senders is
|
|
||||||
// CommsServer's job. EventBus does not have enough data for this decision.
|
|
||||||
// We do, however, have enough data to prevent backflow of messages that
|
|
||||||
// crossed CommsServer once already.
|
|
||||||
if (
|
|
||||||
this.comms
|
|
||||||
&& context?.origin !== CommsOrigin.Server
|
|
||||||
&& !context?.borderCrossings?.commsServer
|
|
||||||
) {
|
|
||||||
try {
|
|
||||||
this.comms.sendMessage({command, config: commandData, context}, context);
|
|
||||||
} catch (e) {
|
|
||||||
if (command !== 'reload-required') {
|
|
||||||
// We shouldn't let reload-required command to trigger new reload-required commands.
|
|
||||||
this.send('reload-required', {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// call forwarding functions if they exist
|
|
||||||
if (!context?.borderCrossings?.iframe) {
|
|
||||||
for (const forwarding of this.iframeForwardingList) {
|
|
||||||
forwarding.fn(
|
|
||||||
command,
|
|
||||||
commandData,
|
|
||||||
{
|
|
||||||
...context,
|
|
||||||
borderCrossings: {
|
|
||||||
...context?.borderCrossings,
|
|
||||||
iframe: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context?.stopPropagation) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send, but intended for sending commands from iframe to content scripts
|
|
||||||
* @param command
|
|
||||||
* @param config
|
|
||||||
*/
|
|
||||||
sendToTunnel(command: string, config: any) {
|
|
||||||
if (!this.disableTunnel) {
|
|
||||||
window.parent.postMessage(
|
|
||||||
{
|
|
||||||
action: 'uw-bus-tunnel',
|
|
||||||
payload: {action: command, config}
|
|
||||||
},
|
|
||||||
'*'
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// because iframe UI components get reused in the popup, we
|
|
||||||
// also need to set up a detour because the tunnel is closed
|
|
||||||
// in the popup
|
|
||||||
if (this.comms) {
|
|
||||||
try {
|
|
||||||
this.comms.sendMessage({command, config, context: this.popupContext}, this.popupContext);
|
|
||||||
} catch (e) {
|
|
||||||
if (command !== 'reload-required') {
|
|
||||||
this.send('reload-required', {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//#region iframe tunnelling
|
|
||||||
private setupIframeTunnelling() {
|
|
||||||
// forward messages coming from iframe tunnels
|
|
||||||
window.addEventListener('message', this.handleIframeMessage);
|
|
||||||
}
|
|
||||||
private destroyIframeTunnelling() {
|
|
||||||
window.removeEventListener('message', this.handleIframeMessage);
|
|
||||||
}
|
|
||||||
private handleIframeMessage(event: any) {
|
|
||||||
// console.log('GOT IFRAME MESSAGE!', event)
|
|
||||||
}
|
|
||||||
|
|
||||||
//#endregion
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
class Interface {
|
|
||||||
constructor(videoData) {
|
|
||||||
this.conf = videoData;
|
|
||||||
this.player = videoData.player;
|
|
||||||
}
|
|
||||||
|
|
||||||
injectUi() {
|
|
||||||
this.detectorDiv = document.createElement('div');
|
|
||||||
this.uiRoot = document.createElement('div');
|
|
||||||
this.detectorDiv.appendChild(this.uiRoot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,350 +0,0 @@
|
|||||||
import { MenuPosition, MenuConfig, MenuItemConfig } from '@src/common/interfaces/ClientUiMenu.interface';
|
|
||||||
import extensionCss from '@src/main.css?inline';
|
|
||||||
|
|
||||||
export class ClientMenu {
|
|
||||||
|
|
||||||
private host!: HTMLDivElement;
|
|
||||||
private shadow!: ShadowRoot;
|
|
||||||
private root!: HTMLDivElement;
|
|
||||||
private visible = false;
|
|
||||||
|
|
||||||
private menuPositionClasses: string[] = [];
|
|
||||||
|
|
||||||
|
|
||||||
private isHovered = false;
|
|
||||||
private isWithinActivation = false;
|
|
||||||
private lastMouseMove = performance.now();
|
|
||||||
private idleTimer?: number;
|
|
||||||
|
|
||||||
private onDocumentMouseMove?: (e: MouseEvent) => void;
|
|
||||||
private onDocumentMouseLeave?: () => void;
|
|
||||||
private idleIntervalId?: number;
|
|
||||||
|
|
||||||
constructor(private config: MenuConfig) {}
|
|
||||||
|
|
||||||
mount(anchorElement: HTMLElement) {
|
|
||||||
this.buildMenuPositionClassList();
|
|
||||||
this.createHost();
|
|
||||||
this.createShadow();
|
|
||||||
this.createMenu();
|
|
||||||
this.position(anchorElement);
|
|
||||||
this.bindGlobalMouse(anchorElement);
|
|
||||||
|
|
||||||
// document.documentElement.appendChild(this.host);
|
|
||||||
anchorElement.appendChild(this.host);
|
|
||||||
}
|
|
||||||
|
|
||||||
public destroy() {
|
|
||||||
// 1. Stop timers
|
|
||||||
if (this.idleIntervalId != null) {
|
|
||||||
clearInterval(this.idleIntervalId);
|
|
||||||
this.idleIntervalId = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Remove global listeners
|
|
||||||
if (this.onDocumentMouseMove) {
|
|
||||||
document.removeEventListener('mousemove', this.onDocumentMouseMove);
|
|
||||||
this.onDocumentMouseMove = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.onDocumentMouseLeave) {
|
|
||||||
document.removeEventListener('mouseleave', this.onDocumentMouseLeave);
|
|
||||||
this.onDocumentMouseLeave = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Remove DOM
|
|
||||||
if (this.host && this.host.parentNode) {
|
|
||||||
this.host.parentNode.removeChild(this.host);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Clear references
|
|
||||||
this.shadow = undefined as any;
|
|
||||||
this.root = undefined as any;
|
|
||||||
this.host = undefined as any;
|
|
||||||
|
|
||||||
// 5. Reset state
|
|
||||||
this.visible = false;
|
|
||||||
this.isHovered = false;
|
|
||||||
this.isWithinActivation = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getActivationRadius(anchorEl: HTMLElement): number | null {
|
|
||||||
if (this.config.activationRadius == null) return null;
|
|
||||||
|
|
||||||
if (typeof this.config.activationRadius === 'number') {
|
|
||||||
return this.config.activationRadius;
|
|
||||||
}
|
|
||||||
|
|
||||||
// percentage string
|
|
||||||
const rect = anchorEl.getBoundingClientRect();
|
|
||||||
const pct = parseFloat(this.config.activationRadius);
|
|
||||||
return Math.max(rect.width, rect.height) * (pct / 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
private injectStyles() {
|
|
||||||
const style = document.createElement('style');
|
|
||||||
let css = extensionCss
|
|
||||||
.trim()
|
|
||||||
.replace(/'html, body \{/g, ':host {')
|
|
||||||
.replace(/'body \{/g, ':host {')
|
|
||||||
;
|
|
||||||
|
|
||||||
// this is bad but I can't be bothered to do it the proper way
|
|
||||||
const cssArr: string[] = css.split('@font-face');
|
|
||||||
const cssRemainder = cssArr[cssArr.length - 1].split('}').slice(1).join('}');
|
|
||||||
|
|
||||||
css = `
|
|
||||||
${cssArr[0]}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Heebo';
|
|
||||||
src: url(__FONT_HEEBO__) format('truetype-variations');
|
|
||||||
font-weight: 100 900;
|
|
||||||
font-stretch: 75% 125%;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
src: url(__FONT_SCP__) format('truetype-variations');
|
|
||||||
font-weight: 200 900;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Source Code Pro';
|
|
||||||
src: url(__FONT_SCPI__) format('truetype-variations');
|
|
||||||
font-weight: 200 900;
|
|
||||||
font-style: italic;
|
|
||||||
font-display: swap;
|
|
||||||
}
|
|
||||||
${cssRemainder};
|
|
||||||
`;
|
|
||||||
|
|
||||||
css = css
|
|
||||||
.replace('__FONT_HEEBO__', chrome.runtime.getURL('/ui/res/fonts/Heebo.ttf'))
|
|
||||||
.replace('__FONT_SCP__', chrome.runtime.getURL('/ui/res/fonts/SourceCodePro.ttf'))
|
|
||||||
.replace('__FONT_SCPI__', chrome.runtime.getURL('/ui/res/fonts/SourceCodePro-Italic.ttf'))
|
|
||||||
.replaceAll('html,', '')
|
|
||||||
;
|
|
||||||
|
|
||||||
style.textContent = css;
|
|
||||||
this.shadow.appendChild(style);
|
|
||||||
}
|
|
||||||
|
|
||||||
private createHost() {
|
|
||||||
this.host = document.createElement('div');
|
|
||||||
this.host.classList.add('uw-ultrawidify-container-root');
|
|
||||||
|
|
||||||
Object.assign(this.host.style, {
|
|
||||||
position: this.config.isGlobal ? 'fixed' : 'absolute',
|
|
||||||
left: 0,
|
|
||||||
top: 0,
|
|
||||||
border: 0,
|
|
||||||
width: '100%',
|
|
||||||
height: '100%',
|
|
||||||
zIndex: this.config.isGlobal ? '2147483647' : '2147483640',
|
|
||||||
pointerEvents: 'none',
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('UI host created:', this.host);
|
|
||||||
}
|
|
||||||
|
|
||||||
private createShadow() {
|
|
||||||
this.shadow = this.host.attachShadow({ mode: 'open' });
|
|
||||||
this.injectStyles();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
private buildMenuPositionClassList() {
|
|
||||||
let classList;
|
|
||||||
switch (this.config.menuPosition) {
|
|
||||||
case MenuPosition.TopLeft:
|
|
||||||
classList = ['uw-menu-left','uw-menu-top'];
|
|
||||||
break;
|
|
||||||
case MenuPosition.Left:
|
|
||||||
classList = ['uw-menu-left','uw-menu-ycenter'];
|
|
||||||
break;
|
|
||||||
case MenuPosition.BottomLeft:
|
|
||||||
classList = ['uw-menu-left','uw-menu-bottom'];
|
|
||||||
break;
|
|
||||||
case MenuPosition.Top:
|
|
||||||
classList = ['uw-menu-center','uw-menu-top'];
|
|
||||||
break;
|
|
||||||
case MenuPosition.Bottom:
|
|
||||||
classList = ['uw-menu-center', 'uw-menu-bottom'];
|
|
||||||
break;
|
|
||||||
case MenuPosition.TopRight:
|
|
||||||
classList = ['uw-menu-right', 'uw-menu-top'];
|
|
||||||
break;
|
|
||||||
case MenuPosition.Right:
|
|
||||||
classList = ['uw-menu-right', 'uw-menu-ycenter'];
|
|
||||||
break;
|
|
||||||
case MenuPosition.BottomRight:
|
|
||||||
classList = ['uw-menu-right', 'uw-menu-bottom'];
|
|
||||||
break;
|
|
||||||
default: // left-center is our default position
|
|
||||||
classList = ['uw-menu-left', 'uw-menu-ycenter'];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.menuPositionClasses = classList;
|
|
||||||
}
|
|
||||||
|
|
||||||
private createMenu() {
|
|
||||||
this.root = document.createElement('div');
|
|
||||||
this.root.className = 'uw-menu-root uw-hidden';
|
|
||||||
|
|
||||||
const trigger = document.createElement('div');
|
|
||||||
trigger.classList = 'uw-menu-trigger uw-trigger';
|
|
||||||
trigger.textContent = 'Ultrawidify';
|
|
||||||
|
|
||||||
const submenu = this.buildSubmenu(this.config.items);
|
|
||||||
|
|
||||||
trigger.addEventListener('mouseenter', () => this.show());
|
|
||||||
this.root.addEventListener('mouseleave', () => {
|
|
||||||
this.isHovered = false;
|
|
||||||
this.hide();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.root.classList.add(...this.menuPositionClasses);
|
|
||||||
|
|
||||||
trigger.appendChild(submenu);
|
|
||||||
this.root.append(trigger);
|
|
||||||
this.shadow.appendChild(this.root);
|
|
||||||
|
|
||||||
this.root.addEventListener('mouseenter', () => {
|
|
||||||
this.isHovered = true;
|
|
||||||
this.show();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.root.addEventListener('mouseleave', () => {
|
|
||||||
this.isHovered = false;
|
|
||||||
this.updateVisibility();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private buildSubmenu(items: MenuItemConfig[]): HTMLDivElement {
|
|
||||||
const menu = document.createElement('div');
|
|
||||||
menu.classList = 'uw-submenu';
|
|
||||||
menu.classList.add(...this.menuPositionClasses);
|
|
||||||
|
|
||||||
for (const item of items) {
|
|
||||||
const el = document.createElement('div');
|
|
||||||
el.className = `uw-menu-item uw-trigger`;
|
|
||||||
|
|
||||||
if (item.customHTML) {
|
|
||||||
el.appendChild(item.customHTML);
|
|
||||||
} else {
|
|
||||||
el.textContent = item.label;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.action) {
|
|
||||||
el.addEventListener('click', e => {
|
|
||||||
e.stopPropagation();
|
|
||||||
item.action?.();
|
|
||||||
// this.hide(); // maybe dont
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.subitems) {
|
|
||||||
el.appendChild(this.buildSubmenu(item.subitems));
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.appendChild(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
private position(anchorEl: HTMLElement) {
|
|
||||||
let appendClassList: string[] = [];
|
|
||||||
anchorEl.classList.add(...appendClassList);
|
|
||||||
}
|
|
||||||
|
|
||||||
private bindGlobalMouse(anchorEl: HTMLElement) {
|
|
||||||
const rect = anchorEl.getBoundingClientRect();
|
|
||||||
const cx = rect.left + rect.width / 2;
|
|
||||||
const cy = rect.top + rect.height / 2;
|
|
||||||
|
|
||||||
const activationRadius = this.getActivationRadius(anchorEl);
|
|
||||||
|
|
||||||
this.onDocumentMouseMove = (e: MouseEvent) => {
|
|
||||||
this.lastMouseMove = performance.now();
|
|
||||||
|
|
||||||
if (activationRadius != null) {
|
|
||||||
const d = Math.hypot(e.clientX - cx, e.clientY - cy);
|
|
||||||
this.isWithinActivation = d <= activationRadius;
|
|
||||||
} else {
|
|
||||||
this.isWithinActivation =
|
|
||||||
e.clientX >= rect.left &&
|
|
||||||
e.clientX <= rect.right &&
|
|
||||||
e.clientY >= rect.top &&
|
|
||||||
e.clientY <= rect.bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateVisibility();
|
|
||||||
};
|
|
||||||
|
|
||||||
this.onDocumentMouseLeave = () => {
|
|
||||||
this.isHovered = false;
|
|
||||||
this.isWithinActivation = false;
|
|
||||||
this.hide();
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('mousemove', this.onDocumentMouseMove);
|
|
||||||
document.addEventListener('mouseleave', this.onDocumentMouseLeave);
|
|
||||||
|
|
||||||
this.startIdleWatcher();
|
|
||||||
}
|
|
||||||
|
|
||||||
private startIdleWatcher() {
|
|
||||||
this.idleIntervalId = window.setInterval(() => {
|
|
||||||
const idle = performance.now() - this.lastMouseMove > 1000;
|
|
||||||
if (idle) {
|
|
||||||
this.hide();
|
|
||||||
}
|
|
||||||
}, 200);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private updateVisibility() {
|
|
||||||
const idle = performance.now() - this.lastMouseMove > 1000;
|
|
||||||
|
|
||||||
if (this.isHovered) {
|
|
||||||
this.show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isWithinActivation && !idle) {
|
|
||||||
this.show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.visible) {
|
|
||||||
this.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private show() {
|
|
||||||
this.lastMouseMove = performance.now();
|
|
||||||
|
|
||||||
if (!this.visible) {
|
|
||||||
this.visible = true;
|
|
||||||
this.root.classList.add('uw-visible');
|
|
||||||
this.root.classList.remove('uw-hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private hide() {
|
|
||||||
if (this.visible) {
|
|
||||||
this.visible = false;
|
|
||||||
this.root.classList.remove('uw-visible');
|
|
||||||
this.root.classList.add('uw-hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,128 +0,0 @@
|
|||||||
import BrowserDetect from '../../conf/BrowserDetect';
|
|
||||||
|
|
||||||
function url(file) {
|
|
||||||
return BrowserDetect.getURL(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class FontLoader {
|
|
||||||
static loadFonts() {
|
|
||||||
const fontsStyleElement = document.createElement('style');
|
|
||||||
fontsStyleElement.type = 'text/css';
|
|
||||||
|
|
||||||
fontsStyleElement.textContent = `
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: ${url('res/fonts/overpass-webfont/overpass-thin.woff2')} format('woff2'); /* Super Modern Browsers */
|
|
||||||
font-weight: 200;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: ${url('res/fonts/overpass-webfont/overpass-thin-italic.woff2')} format('woff2');
|
|
||||||
font-weight: 200;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: ${url('res/fonts/overpass-webfont/overpass-extralight.woff2')} format('woff2');
|
|
||||||
font-weight: 300;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: ${url('res/fonts/overpass-webfont/overpass-extralight-italic.woff2')} format('woff2');
|
|
||||||
font-weight: 300;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: ${url('res/fonts/overpass-webfont/overpass-light.woff2')} format('woff2');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: ${url('res/fonts/overpass-webfont/overpass-light-italic.woff2')} format('woff2');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: ${url('res/fonts/overpass-webfont/overpass-regular.woff2')} format('woff2');
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: ${url('res/fonts/overpass-webfont/overpass-italic.woff2')} format('woff2');
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: ${url('res/fonts/overpass-webfont/overpass-semibold.woff2')} format('woff2');
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: ${url('res/fonts/overpass-webfont/overpass-semibold-italic.woff2')} format('woff2');
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: ${url('res/fonts/overpass-webfont/overpass-bold.woff2')} format('woff2');
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass';
|
|
||||||
src: ${url('res/fonts/overpass-webfont/overpass-bold-italic.woff2')} format('woff2');
|
|
||||||
font-weight: 700;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass mono';
|
|
||||||
src: ${url('res/fonts/overpass-mono-webfont/overpass-mono-light.woff2')} format('woff2');
|
|
||||||
font-weight: 300;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass mono';
|
|
||||||
src: ${url('res/fonts/overpass-mono-webfont/overpass-mono-regular.woff2')} format('woff2');
|
|
||||||
font-weight: 400;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass mono';
|
|
||||||
src: ${url('res/fonts/overpass-mono-webfont/overpass-mono-semibold.woff2')} format('woff2');
|
|
||||||
font-weight: 500;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Overpass mono';
|
|
||||||
src: ${url('res/fonts/overpass-mono-webfont/overpass-mono-bold.woff2')} format('woff2');
|
|
||||||
font-weight: 600;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
document.head.appendChild(fontsStyleElement);
|
|
||||||
}
|
|
||||||
}
|
|
||||||