Compare commits
74 Commits
dependency
...
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 | |||
| a0cddb256a | |||
| 14b3571af3 | |||
| af08c5094d | |||
| ef12dc8cc5 | |||
| fcfc0361dc | |||
| a8847bdeff | |||
| 3f5ab5ecc6 | |||
| 077c7cae15 | |||
| f916cffd41 | |||
| ea4fb95a3c | |||
| 3dbec85269 | |||
| 2a42c3e715 | |||
| c2a6a3f082 | |||
| 61972a7b2d | |||
| 4a562443b1 | |||
| 9515c1b01a | |||
| 126d891e5e | |||
| dad1df0244 | |||
| aff429e5ea | |||
| 3457c4fcdc | |||
| 74c0358d33 | |||
| 8735d3e81a | |||
| 4180d5b5b1 | |||
| ca751a660f | |||
| fb7f5cf1ba | |||
| fe2f52d76a | |||
| ee32ae93c3 | |||
| 877fc96da1 | |||
| 67a5b4e05a | |||
| d0a5083841 | |||
| 2d8c078a6f | |||
| f543dcff24 | |||
| 5cfa5b0add | |||
| 13feb1e822 |
26
.vscode/settings.json
vendored
@ -1,6 +1,7 @@
|
||||
{
|
||||
"cSpell.words": [
|
||||
"aard",
|
||||
"allowtransparency",
|
||||
"ardetector",
|
||||
"autodetect",
|
||||
"autodetection",
|
||||
@ -8,11 +9,15 @@
|
||||
"blackbar",
|
||||
"blackbars",
|
||||
"blackframe",
|
||||
"bruteforce",
|
||||
"canvas",
|
||||
"clickthrough",
|
||||
"cmpa",
|
||||
"cmpb",
|
||||
"com",
|
||||
"comms",
|
||||
"csui",
|
||||
"ctab",
|
||||
"cycleable",
|
||||
"decycle",
|
||||
"dinked",
|
||||
@ -23,34 +28,47 @@
|
||||
"fith",
|
||||
"fitw",
|
||||
"fuckup",
|
||||
"fucky",
|
||||
"geoblocked",
|
||||
"geoblocking",
|
||||
"gfycat",
|
||||
"gmail",
|
||||
"guardline",
|
||||
"han",
|
||||
"haram",
|
||||
"Heebo",
|
||||
"hoverable",
|
||||
"iframe",
|
||||
"imgur",
|
||||
"insta",
|
||||
"jsoneditor",
|
||||
"jwplayer",
|
||||
"kavin",
|
||||
"letterboxed",
|
||||
"manjaro",
|
||||
"mdicon",
|
||||
"mdijs",
|
||||
"metaivi",
|
||||
"minification",
|
||||
"mitigations",
|
||||
"mstefan",
|
||||
"nogrow",
|
||||
"noshrink",
|
||||
"outro",
|
||||
"PILLARBOX",
|
||||
"PILLARBOXED",
|
||||
"polyfill",
|
||||
"problemo",
|
||||
"recursing",
|
||||
"reddit",
|
||||
"rescan",
|
||||
"resizer",
|
||||
"resizers",
|
||||
"SCPI",
|
||||
"scrollbar",
|
||||
"shitiness",
|
||||
"smallcaps",
|
||||
"spaghettified",
|
||||
"suboption",
|
||||
"subscan",
|
||||
"tabitem",
|
||||
@ -61,6 +79,7 @@
|
||||
"ultrawidify",
|
||||
"unmark",
|
||||
"unmarking",
|
||||
"unsets",
|
||||
"unshift",
|
||||
"uwid",
|
||||
"uwui",
|
||||
@ -68,9 +87,14 @@
|
||||
"vids",
|
||||
"vuejs",
|
||||
"vuex",
|
||||
"wakanim",
|
||||
"webextension",
|
||||
"webextensions",
|
||||
"youtube"
|
||||
"wuckies",
|
||||
"wucky",
|
||||
"ycenter",
|
||||
"youtube",
|
||||
"YYMM"
|
||||
],
|
||||
"cSpell.ignoreWords": [
|
||||
"abcdefghijklmnopqrstuvwxyz",
|
||||
|
||||
104
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ultrawidify",
|
||||
"version": "6.3.993",
|
||||
"version": "6.3.998",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ultrawidify",
|
||||
"version": "6.3.993",
|
||||
"version": "6.3.998",
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
||||
"@mdi/font": "^7.4.47",
|
||||
@ -16,6 +16,7 @@
|
||||
"fs-extra": "^11.3.2",
|
||||
"gl-matrix": "^3.4.4",
|
||||
"json-cyclic": "1.0.2",
|
||||
"json-difference": "^1.16.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mdi-vue": "^3.0.13",
|
||||
"typescript": "^5.9.3",
|
||||
@ -53,6 +54,7 @@
|
||||
"postcss-nested": "^7.0.2",
|
||||
"postcss-preset-env": "^10.4.0",
|
||||
"postcss-strip-inline-comments": "^0.1.5",
|
||||
"raw-loader": "^4.0.2",
|
||||
"resolve-url-loader": "^5.0.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"ts-loader": "^9.5.4",
|
||||
@ -13495,6 +13497,15 @@
|
||||
"integrity": "sha512-K0Y9wQnrc13Ef+P8KY8SjFyVAz941CTrTVmu3oyQPExhaFFYXXLXnEuRi1tqYOS1B8WtFIxIwA7nHRJ64Uvf5Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-difference": {
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/json-difference/-/json-difference-1.16.1.tgz",
|
||||
"integrity": "sha512-tNVUzBKn2I4BB4Cjn+j+Kq/BfLNwqtEvxhnlyuX5XJmKn9VlfV5MvaTbB/ure/toFgLaD6XglOUt+dd4iMQ3dA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17.x"
|
||||
}
|
||||
},
|
||||
"node_modules/json-parse-better-errors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
||||
@ -17838,6 +17849,95 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-loader": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz",
|
||||
"integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loader-utils": "^2.0.0",
|
||||
"schema-utils": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webpack": "^4.0.0 || ^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-loader/node_modules/ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-loader/node_modules/ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"ajv": "^6.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-loader/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/raw-loader/node_modules/loader-utils": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
|
||||
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/raw-loader/node_modules/schema-utils": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
|
||||
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.8",
|
||||
"ajv": "^6.12.5",
|
||||
"ajv-keywords": "^3.5.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ultrawidify",
|
||||
"version": "6.3.993",
|
||||
"version": "6.3.998",
|
||||
"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>",
|
||||
"scripts": {
|
||||
@ -33,6 +33,7 @@
|
||||
"fs-extra": "^11.3.2",
|
||||
"gl-matrix": "^3.4.4",
|
||||
"json-cyclic": "1.0.2",
|
||||
"json-difference": "^1.16.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mdi-vue": "^3.0.13",
|
||||
"typescript": "^5.9.3",
|
||||
@ -70,6 +71,7 @@
|
||||
"postcss-nested": "^7.0.2",
|
||||
"postcss-preset-env": "^10.4.0",
|
||||
"postcss-strip-inline-comments": "^0.1.5",
|
||||
"raw-loader": "^4.0.2",
|
||||
"resolve-url-loader": "^5.0.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"ts-loader": "^9.5.4",
|
||||
|
||||
10
paths.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
const path = require('path')
|
||||
|
||||
module.exports = {
|
||||
'@src': path.resolve(__dirname, 'src'),
|
||||
'@ui': path.resolve(__dirname, 'src/ui'),
|
||||
'@components': path.resolve(__dirname, 'src/ui/components'),
|
||||
'@res': path.resolve(__dirname, 'src/ui/res'),
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
};
|
||||
|
||||
@ -1,5 +1,22 @@
|
||||
const path = require('path');
|
||||
const aliases = require('./paths.config');
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('postcss-import')({
|
||||
resolve(id) {
|
||||
for (const alias in aliases) {
|
||||
if (id === alias || id.startsWith(alias + '/')) {
|
||||
return path.resolve(
|
||||
aliases[alias],
|
||||
id.slice(alias.length + 1)
|
||||
)
|
||||
}
|
||||
}
|
||||
return id
|
||||
},
|
||||
}),
|
||||
|
||||
require('@tailwindcss/postcss'),
|
||||
require('postcss-nested'),
|
||||
require('autoprefixer'),
|
||||
|
||||
@ -5,7 +5,7 @@ const fs = require('fs');
|
||||
|
||||
const BUNDLE_DIR = path.join(__dirname, `../dist-${process.env.BROWSER === 'firefox' ? 'ff' : process.env.BROWSER}`);
|
||||
const bundles = [
|
||||
'csui/csui-popup.js',
|
||||
// 'csui/csui-popup.js',
|
||||
];
|
||||
|
||||
const evalRegexForProduction = /;([a-z])=function\(\){return this}\(\);try{\1=\1\|\|Function\("return this"\)\(\)\|\|\(0,eval\)\("this"\)}catch\(t\){"object"==typeof window&&\(\1=window\)}/g;
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
enum AspectRatioType {
|
||||
Default = -3, // inherit from global settings
|
||||
Cycle = -2,
|
||||
Initial = -1, // page default
|
||||
Reset = 0, // reset to initial
|
||||
@ -8,6 +9,7 @@ enum AspectRatioType {
|
||||
Fixed = 4, // pre-determined aspect ratio
|
||||
Manual = 5, // ratio achieved by zooming in/zooming out
|
||||
AutomaticUpdate = 6, // set by Aard
|
||||
Cover = 7, // replaces FitWidth and FitHeight
|
||||
}
|
||||
|
||||
export default AspectRatioType;
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
enum EmbeddedContentSettingsOverridePolicy {
|
||||
Always = 2,
|
||||
UseAsDefault = 1,
|
||||
Never = 0,
|
||||
Default = 0,
|
||||
Never = -1,
|
||||
}
|
||||
|
||||
export default EmbeddedContentSettingsOverridePolicy;
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
enum ExtensionMode {
|
||||
AutoDisabled = -2,
|
||||
Disabled = -1,
|
||||
Default = 0,
|
||||
Whitelist = 1,
|
||||
Basic = 2,
|
||||
Enabled = 3,
|
||||
};
|
||||
FullScreen = 1,
|
||||
Theater = 2,
|
||||
All = 3
|
||||
}
|
||||
|
||||
export default ExtensionMode;
|
||||
|
||||
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
|
||||
}
|
||||
10
src/common/enums/LegacyExtensionMode.enum.ts
Normal file
@ -0,0 +1,10 @@
|
||||
enum LegacyExtensionMode {
|
||||
AutoDisabled = -2,
|
||||
Disabled = -1,
|
||||
Default = 0,
|
||||
Whitelist = 1,
|
||||
Basic = 2,
|
||||
Enabled = 3,
|
||||
};
|
||||
|
||||
export default LegacyExtensionMode;
|
||||
5
src/common/enums/PlayerDetectionMode.enum.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export enum PlayerDetectionMode {
|
||||
Auto = 0,
|
||||
AncestorIndex = 1,
|
||||
QuerySelectors = 2,
|
||||
};
|
||||
9
src/common/enums/SiteSupportLevel.enum.ts
Normal file
@ -0,0 +1,9 @@
|
||||
export enum SiteSupportLevel {
|
||||
OfficialSupport = 'official',
|
||||
CommunitySupport = 'community',
|
||||
UserDefined = 'user-defined',
|
||||
BetaSupport = 'testing',
|
||||
OfficialBlacklist = 'officially-disabled',
|
||||
Unknown = 'unknown',
|
||||
UserModified = 'modified',
|
||||
}
|
||||
@ -4,7 +4,8 @@ enum VideoAlignmentType {
|
||||
Right = 2,
|
||||
Top = 3,
|
||||
Bottom = 4,
|
||||
Default = -1
|
||||
Default = -1,
|
||||
Custom = -2,
|
||||
};
|
||||
|
||||
export default VideoAlignmentType;
|
||||
|
||||
31
src/common/interfaces/ClientUiMenu.interface.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { CommandInterface, InPlayerUIOptions } from '@src/common/interfaces/SettingsInterface';
|
||||
|
||||
export interface MenuItemBaseConfig {
|
||||
label?: string;
|
||||
subitems?: MenuItemConfig[];
|
||||
command?: CommandInterface,
|
||||
action?: () => void;
|
||||
customHTML?: HTMLElement | string;
|
||||
customId?: string;
|
||||
customClassList?: string;
|
||||
}
|
||||
|
||||
export type MenuItemConfig = MenuItemBaseConfig & ({label: string} | {customHTML: HTMLElement | string});
|
||||
|
||||
export enum MenuPosition {
|
||||
TopLeft = 'top-left',
|
||||
Left = 'left',
|
||||
BottomLeft = 'bottom-left',
|
||||
Top = 'top',
|
||||
Bottom = 'bottom',
|
||||
TopRight = 'top-right',
|
||||
Right = 'right',
|
||||
BottomRight = 'bottom-right',
|
||||
}
|
||||
|
||||
export interface MenuConfig {
|
||||
isGlobal?: boolean;
|
||||
ui: InPlayerUIOptions;
|
||||
options?: {forceShow?: boolean};
|
||||
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,18 +1,20 @@
|
||||
import { Action } from '../../../node_modules/vuex/types/index'
|
||||
import { AardPollingOptions } from '../../ext/lib/aard/enums/aard-polling-options.enum'
|
||||
import { AardSubtitleCropMode } from '../../ext/lib/aard/enums/aard-subtitle-crop-mode.enum'
|
||||
import AntiGradientMode from '../enums/AntiGradientMode.enum'
|
||||
import AspectRatioType from '../enums/AspectRatioType.enum'
|
||||
import CropModePersistence from '../enums/CropModePersistence.enum'
|
||||
import EmbeddedContentSettingsOverridePolicy from '../enums/EmbeddedContentSettingsOverridePolicy.enum'
|
||||
import ExtensionMode from '../enums/ExtensionMode.enum'
|
||||
import StretchType from '../enums/StretchType.enum'
|
||||
import VideoAlignmentType from '../enums/VideoAlignmentType.enum'
|
||||
import { AardPollingOptions } from '@src/ext/module/aard/enums/aard-polling-options.enum'
|
||||
import { AardSubtitleCropMode } from '@src/ext/module/aard/enums/aard-subtitle-crop-mode.enum'
|
||||
import { SiteSupportLevel } from '@src/common/enums/SiteSupportLevel.enum';
|
||||
import AspectRatioType from '@src/common/enums/AspectRatioType.enum'
|
||||
import CropModePersistence from '@src/common/enums/CropModePersistence.enum'
|
||||
import EmbeddedContentSettingsOverridePolicy from '@src/common/enums/EmbeddedContentSettingsOverridePolicy.enum'
|
||||
import ExtensionMode from '@src/common/enums/ExtensionMode.enum'
|
||||
import StretchType from '@src/common/enums/StretchType.enum'
|
||||
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum'
|
||||
import { PlayerDetectionMode } from '@src/common/enums/PlayerDetectionMode.enum';
|
||||
import { InputHandlingMode } from '@src/common/enums/InputHandlingMode.enum';
|
||||
import { MenuPosition } from '@src/common/interfaces/ClientUiMenu.interface';
|
||||
|
||||
export enum ExtensionEnvironment {
|
||||
Normal = 'normal',
|
||||
Theater = 'theater',
|
||||
Fullscreen = 'fullscreen',
|
||||
Normal = ExtensionMode.All,
|
||||
Theater = ExtensionMode.Theater,
|
||||
Fullscreen = ExtensionMode.FullScreen,
|
||||
}
|
||||
|
||||
export interface DevUiConfig {
|
||||
@ -48,12 +50,6 @@ interface RestrictionsSettings {
|
||||
onlyAllowAutodetectionInFullScreen?: boolean; // if enabled, autodetection will only start once in full screen
|
||||
}
|
||||
|
||||
interface ExtensionEnvironmentSettingsInterface {
|
||||
normal: ExtensionMode,
|
||||
theater: ExtensionMode,
|
||||
fullscreen: ExtensionMode,
|
||||
}
|
||||
|
||||
export interface CommandInterface {
|
||||
action: string,
|
||||
label: string,
|
||||
@ -313,6 +309,24 @@ interface DevSettings {
|
||||
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 {
|
||||
_updateFlags?: {
|
||||
requireReload?: SettingsReloadFlags,
|
||||
@ -324,20 +338,7 @@ interface SettingsInterface {
|
||||
aard: AardSettings,
|
||||
|
||||
ui: {
|
||||
inPlayer: {
|
||||
enabled: boolean, // Deprecated — moved to site settings
|
||||
enabledFullscreenOnly: boolean, // Deprecated — moved to site settings
|
||||
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: 'trigger-zone' | 'player', // 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]
|
||||
},
|
||||
},
|
||||
inPlayer: InPlayerUIOptions,
|
||||
devMode?: boolean,
|
||||
dev: DevUiConfig,
|
||||
}
|
||||
@ -352,7 +353,7 @@ interface SettingsInterface {
|
||||
conditionalDifferencePercent: number // black bars less than this wide will trigger stretch
|
||||
// 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
|
||||
keyboardEnabled: boolean, // if keyboard shortcuts are processed
|
||||
mouseEnabled: boolean, // if mouse movement is processed
|
||||
@ -445,18 +446,20 @@ interface SettingsInterface {
|
||||
}
|
||||
|
||||
export interface SiteSettingsInterface {
|
||||
enable: ExtensionEnvironmentSettingsInterface;
|
||||
enableAard: ExtensionEnvironmentSettingsInterface;
|
||||
enableKeyboard: ExtensionEnvironmentSettingsInterface;
|
||||
enableUI: ExtensionEnvironmentSettingsInterface; // Lies! enableUI doesn't use 'theater' property (but uses the other two)
|
||||
notes?: string; // any special things related to this site.
|
||||
|
||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy; // presumed to be 'Always' if not defined
|
||||
overrideWhenEmbedded?: boolean;
|
||||
enable: ExtensionMode;
|
||||
enableAard: ExtensionMode;
|
||||
enableKeyboard: InputHandlingMode;
|
||||
enableUI: ExtensionMode;
|
||||
|
||||
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy; // presumed to be 'Use as default' if not defined
|
||||
overrideWhenEmbedded?: EmbeddedContentSettingsOverridePolicy;
|
||||
|
||||
|
||||
autocreated?: boolean;
|
||||
type?: 'official' | 'community' | 'user-defined' | 'testing' | 'officially-disabled' | 'unknown' | 'modified';
|
||||
defaultType: 'official' | 'community' | 'user-defined' | 'testing' | 'officially-disabled' | 'unknown' | 'modified';
|
||||
type?: SiteSupportLevel;
|
||||
defaultType: SiteSupportLevel;
|
||||
|
||||
// must be defined in @global and @empty
|
||||
persistCSA?: CropModePersistence, // CSA - crop, stretch, alignment
|
||||
@ -491,11 +494,10 @@ export interface PlayerAutoConfigInterface {
|
||||
}
|
||||
|
||||
export interface SiteDOMSettingsInterface {
|
||||
type: 'official' | 'community' | 'user-defined' | 'modified' | undefined;
|
||||
type: SiteSupportLevel; // 'official' | 'community' | 'user-defined' | 'modified' | undefined;
|
||||
elements?: {
|
||||
player?: SiteDOMElementSettingsInterface,
|
||||
video?: SiteDOMElementSettingsInterface,
|
||||
other?: { [x: number]: SiteDOMElementSettingsInterface }
|
||||
player?: PlayerDOMSettingsInterface,
|
||||
video?: PlayerDOMSettingsInterface,
|
||||
};
|
||||
customCss?: string;
|
||||
periodicallyRefreshPlayerElement?: boolean;
|
||||
@ -506,12 +508,18 @@ export interface SiteDOMSettingsInterface {
|
||||
anchorElement?: HTMLElement;
|
||||
}
|
||||
|
||||
export interface PlayerDOMSettingsInterface {
|
||||
detectionMode: PlayerDetectionMode,
|
||||
allowAutoFallback: boolean,
|
||||
ancestorIndex?: number,
|
||||
querySelectors?: string,
|
||||
}
|
||||
|
||||
export interface SiteDOMElementSettingsInterface {
|
||||
manual?: boolean;
|
||||
querySelectors?: string;
|
||||
index?: number; // previously: useRelativeAncestor + videoAncestor
|
||||
mode?: 'index' | 'qs';
|
||||
nodeCss?: {[x: string]: string};
|
||||
}
|
||||
|
||||
export default SettingsInterface;
|
||||
|
||||
@ -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,8 +1,8 @@
|
||||
import Debug from '../conf/Debug';
|
||||
import Debug from '../../ext/conf/Debug';
|
||||
|
||||
class ObjectCopy {
|
||||
static addNew(current, newValues){
|
||||
|
||||
|
||||
// clone target
|
||||
let out = JSON.parse(JSON.stringify(newValues));
|
||||
|
||||
@ -20,7 +20,7 @@ class ObjectCopy {
|
||||
|
||||
// Types and constructors of objects must match. If they don't, we always use the new value.
|
||||
if(typeof out[k] === typeof current[k] && out[k].constructor === current[k].constructor) {
|
||||
|
||||
|
||||
// objects are special, we need to check them recursively.
|
||||
if(out[k] && typeof out[k] === 'object' && out[k].constructor === Object ) {
|
||||
if(Debug.debug && Debug.settings) {
|
||||
@ -29,7 +29,7 @@ class ObjectCopy {
|
||||
|
||||
out[k] = this.addNew(current[k], out[k]);
|
||||
} else {
|
||||
out[k] = current[k];
|
||||
out[k] = current[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -52,7 +52,7 @@ class ObjectCopy {
|
||||
if (current[k] !== undefined) {
|
||||
// Types and constructors of objects must match. If they don't, we always use the new value.
|
||||
if (typeof newValues[k] === typeof current[k] && newValues[k].constructor === current[k].constructor) {
|
||||
|
||||
|
||||
// objects are special, we need to check them recursively.
|
||||
if(current[k] && typeof current[k] === 'object' && current[k].constructor === Object ) {
|
||||
if(Debug.debug && Debug.settings) {
|
||||
@ -61,7 +61,7 @@ class ObjectCopy {
|
||||
|
||||
current[k] = this.overwrite(current[k], newValues[k]);
|
||||
} else {
|
||||
current[k] = newValues[k];
|
||||
current[k] = newValues[k];
|
||||
}
|
||||
} else {
|
||||
current[k] = newValues[k];
|
||||
@ -82,4 +82,4 @@ class ObjectCopy {
|
||||
}
|
||||
}
|
||||
|
||||
export default ObjectCopy;
|
||||
export default ObjectCopy;
|
||||
@ -1,7 +1,3 @@
|
||||
export async function sleep(timeout) {
|
||||
return new Promise<void>( (resolve, reject) => setTimeout(() => resolve(), timeout));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates deep copy of an object
|
||||
* @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,587 +0,0 @@
|
||||
<template>
|
||||
<div class="popup-panel" style="height: 100vh">
|
||||
<!--
|
||||
NOTE — the code that makes ultrawidify popup work in firefox regardless of whether the
|
||||
extension is being displayed in a normal or a small/overflow popup breaks the popup
|
||||
behaviour on Chrome (where the popup would never reach the full width of 800px)
|
||||
|
||||
Since I'm tired and the hour is getting late, we'll just add an extra CSS class for
|
||||
non-firefox builds of this extension and be done with it. No need to complicate things
|
||||
further than that.
|
||||
-->
|
||||
<div v-if="settingsInitialized"
|
||||
style="height: 100vh"
|
||||
class="popup flex flex-col no-overflow"
|
||||
:class="{'popup-chrome': ! BrowserDetect?.firefox}"
|
||||
>
|
||||
<div class="flex flex-col w-full relative header"
|
||||
>
|
||||
<div class="flex flex-row w-full" style="height: 42px">
|
||||
<h1 class="flex-grow">
|
||||
<span class="smallcaps">Ultrawidify</span>: <small>Quick settings</small>
|
||||
</h1>
|
||||
<button
|
||||
class="settings-header-button"
|
||||
style="align-self: stretch"
|
||||
@click="showInPlayerUi()"
|
||||
>
|
||||
Show settings window
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row w-full">
|
||||
<div v-if="site && siteSettings" style="transform: scale(0.75) translateX(-12.5%); margin-bottom: -0.5rem; align-content: center" class="flex flex-row flex-grow items-center">
|
||||
<div>site: {{site.host}}</div>
|
||||
<SupportLevelIndicator
|
||||
:siteSupportLevel="siteSupportLevel"
|
||||
>
|
||||
</SupportLevelIndicator>
|
||||
</div>
|
||||
|
||||
<!-- Version info -->
|
||||
<div v-if="BrowserDetect?.processEnvChannel !== 'stable'" class="absolute channel-info version-info">
|
||||
<label>Version:</label> <br/>
|
||||
{{ settings.getExtensionVersion() }} (non-stable)
|
||||
</div>
|
||||
<div v-else class="version-info">
|
||||
<label>Version:</label> <br/>
|
||||
{{ settings.getExtensionVersion() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- CONTAINER ROOT -->
|
||||
<div class="flex flex-row body no-overflow flex-grow">
|
||||
|
||||
<!-- TABS -->
|
||||
<div class="flex flex-col tab-row" style="flex: 3 3; border-right: 1px solid #222;">
|
||||
<div
|
||||
v-for="tab of tabs"
|
||||
:key="tab.id"
|
||||
class="tab flex flex-row"
|
||||
:class="{
|
||||
'active': tab.id === selectedTab,
|
||||
'highlight-tab': tab.highlight,
|
||||
}"
|
||||
@click="selectTab(tab.id)"
|
||||
>
|
||||
<div class="icon-container">
|
||||
<mdicon
|
||||
:name="tab.icon"
|
||||
:size="32"
|
||||
/>
|
||||
</div>
|
||||
<div class="label">
|
||||
{{tab.label}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CONTENT -->
|
||||
<div class="scrollable window-content" style="flex: 7 7; padding: 1rem;">
|
||||
<template v-if="settings && siteSettings">
|
||||
<PopupVideoSettings
|
||||
v-if="selectedTab === 'videoSettings'"
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
:siteSettings="siteSettings"
|
||||
:hosts="activeHosts"
|
||||
></PopupVideoSettings>
|
||||
<BaseExtensionSettings
|
||||
v-if="selectedTab === 'extensionSettings'"
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
:siteSettings="siteSettings"
|
||||
:site="site.host"
|
||||
:hosts="activeHosts"
|
||||
>
|
||||
</BaseExtensionSettings>
|
||||
<ChangelogPanel
|
||||
v-if="selectedTab === 'changelog'"
|
||||
:settings="settings"
|
||||
></ChangelogPanel>
|
||||
<AboutPanel
|
||||
v-if="selectedTab === 'about'"
|
||||
>
|
||||
</AboutPanel>
|
||||
</template>
|
||||
<template v-else>No settings or site settings found.</template>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseExtensionSettings from './src/PlayerUiPanels/BaseExtensionSettings.vue'
|
||||
import PlayerDetectionPanel from './src/PlayerUiPanels/PlayerDetectionPanel.vue'
|
||||
import ChangelogPanel from './src/PlayerUiPanels/ChangelogPanel.vue'
|
||||
import PopupVideoSettings from './src/popup/panels/PopupVideoSettings.vue'
|
||||
import AboutPanel from '@csui/src/popup/panels/AboutPanel.vue'
|
||||
import Debug from '../ext/conf/Debug';
|
||||
import BrowserDetect from '../ext/conf/BrowserDetect';
|
||||
import CommsClient, {CommsOrigin} from '../ext/lib/comms/CommsClient';
|
||||
import Settings from '../ext/lib/settings/Settings';
|
||||
import EventBus from '../ext/lib/EventBus';
|
||||
import {ChromeShittinessMitigations as CSM} from '../common/js/ChromeShittinessMitigations';
|
||||
import SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue'
|
||||
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator';
|
||||
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Debug,
|
||||
BrowserDetect,
|
||||
PopupVideoSettings,
|
||||
PlayerDetectionPanel,
|
||||
BaseExtensionSettings,
|
||||
SupportLevelIndicator,
|
||||
ChangelogPanel,
|
||||
AboutPanel
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
comms: undefined,
|
||||
eventBus: new EventBus(),
|
||||
settings: {},
|
||||
settingsInitialized: false,
|
||||
narrowPopup: null,
|
||||
sideMenuVisible: null,
|
||||
logAggregator: undefined,
|
||||
logger: undefined,
|
||||
site: undefined,
|
||||
siteSettings: undefined,
|
||||
selectedTab: 'videoSettings',
|
||||
tabs: [
|
||||
// see this for icons: https://pictogrammers.com/library/mdi/
|
||||
// {id: 'playerUiCtl', label: 'In-player UI', icon: 'artboard'},
|
||||
{id: 'videoSettings', label: 'Video settings', icon: 'crop'},
|
||||
// {id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
|
||||
{id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' },
|
||||
{id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
|
||||
{id: 'about', label: 'About', icon: 'information-outline'},
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
siteSupportLevel() {
|
||||
return (this.site && this.siteSettings) ? this.siteSettings.data.type || 'no-support' : 'waiting';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.tabs.find(x => x.id === 'changelog').highlight = !this.settings.active?.whatsNewChecked;
|
||||
this.requestSite();
|
||||
},
|
||||
async created() {
|
||||
try {
|
||||
this.logAggregator = new LogAggregator('🔵ext-popup🔵');
|
||||
this.logger = new ComponentLogger(this.logAggregator, 'Popup');
|
||||
|
||||
this.settings = new Settings({afterSettingsSaved: () => this.updateConfig(), logAggregator: this.logAggregator});
|
||||
await this.settings.init();
|
||||
this.settingsInitialized = true;
|
||||
|
||||
// const port = chrome.runtime.connect({name: 'popup-port'});
|
||||
// port.onMessage.addListener( (m,p) => this.processReceivedMessage(m,p));
|
||||
// CSM.setProperty('port', port);
|
||||
|
||||
this.eventBus = new EventBus();
|
||||
this.eventBus.subscribe(
|
||||
'set-current-site',
|
||||
{
|
||||
source: this,
|
||||
function: (config, context) => {
|
||||
if (this.site) {
|
||||
if (!this.site.host) {
|
||||
// dunno why this fix is needed, but sometimes it is
|
||||
this.site.host = config.site.host;
|
||||
}
|
||||
}
|
||||
this.site = config.site;
|
||||
// this.selectedSite = this.selectedSite || config.site.host;
|
||||
this.siteSettings = this.settings.getSiteSettings({site: this.site.host});
|
||||
this.eventBus.setupPopupTunnelWorkaround({
|
||||
origin: CommsOrigin.Popup,
|
||||
comms: {
|
||||
forwardTo: 'active'
|
||||
}
|
||||
});
|
||||
|
||||
this.loadHostnames();
|
||||
this.loadFrames();
|
||||
}
|
||||
},
|
||||
);
|
||||
this.eventBus.subscribe(
|
||||
'open-popup-settings',
|
||||
{
|
||||
source: this,
|
||||
function: (config) => {
|
||||
this.selectTab(config.tab)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
this.comms = new CommsClient('popup-port', this.logger, this.eventBus);
|
||||
this.eventBus.setComms(this.comms);
|
||||
this.eventBus.setupPopupTunnelWorkaround({
|
||||
origin: CommsOrigin.Popup,
|
||||
comms: {forwardTo: 'active'}
|
||||
});
|
||||
|
||||
|
||||
// ensure we'll clean player markings on popup close
|
||||
window.addEventListener("unload", () => {
|
||||
CSM.port.postMessage({
|
||||
cmd: 'unmark-player',
|
||||
forwardToAll: true,
|
||||
});
|
||||
// if (BrowserDetect.anyChromium) {
|
||||
// chrome.extension.getBackgroundPage().sendUnmarkPlayer({
|
||||
// cmd: 'unmark-player',
|
||||
// forwardToAll: true,
|
||||
// });
|
||||
// }
|
||||
});
|
||||
|
||||
// get info about current site from background script
|
||||
while (true) {
|
||||
this.requestSite();
|
||||
await this.sleep(5000);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[Popup.vue::created()] An error happened:', e)
|
||||
}
|
||||
},
|
||||
async updated() {
|
||||
const body = document.getElementsByTagName('body')[0];
|
||||
|
||||
// ensure that narrowPopup only gets set the first time the popup renders
|
||||
// if popup was rendered before, we don't do anything because otherwise
|
||||
// we'll be causing an unwanted re-render
|
||||
//
|
||||
// another thing worth noting — the popup gets first initialized with
|
||||
// offsetWidth set to 0. This means proper popup will be displayed as a
|
||||
// mini popup if we don't check for that.
|
||||
if (this.narrowPopup === null && body.offsetWidth > 0) {
|
||||
this.narrowPopup = body.offsetWidth < 600;
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
showInPlayerUi() {
|
||||
this.eventBus.send('uw-set-ui-state', {globalUiVisible: true}, {comms: {forwardTo: 'active'}});
|
||||
},
|
||||
async sleep(t) {
|
||||
return new Promise( (resolve,reject) => {
|
||||
setTimeout(() => resolve(), t);
|
||||
});
|
||||
},
|
||||
toObject(obj) {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
},
|
||||
requestSite() {
|
||||
try {
|
||||
this.logger.log('info','popup', '[popup::getSite] Requesting current site ...')
|
||||
// CSM.port.postMessage({command: 'get-current-site'});
|
||||
this.eventBus.send(
|
||||
'get-current-site',
|
||||
{},
|
||||
{
|
||||
comms: {forwardTo: 'active'}
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
this.logger.log('error','popup','[popup::getSite] sending get-current-site failed for some reason. Reason:', e);
|
||||
}
|
||||
},
|
||||
getRandomColor() {
|
||||
return `rgb(${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)})`;
|
||||
},
|
||||
selectTab(tab) {
|
||||
this.selectedTab = tab;
|
||||
},
|
||||
isDefaultFrame(frameId) {
|
||||
return frameId === '__playing' || frameId === '__all';
|
||||
},
|
||||
loadHostnames() {
|
||||
this.activeHosts = this.site.hostnames;
|
||||
},
|
||||
loadFrames() {
|
||||
this.activeFrames = [{
|
||||
host: this.site.host,
|
||||
isIFrame: false, // not used tho. Maybe one day
|
||||
}];
|
||||
|
||||
for (const frame in this.site.frames) {
|
||||
if (!this.activeFrames.find(x => x.host === this.site.frames[frame].host)) {
|
||||
this.activeFrames.push({
|
||||
id: `${this.site.id}-${frame}`,
|
||||
label: this.site.frames[frame].host,
|
||||
host: this.site.frames[frame].host,
|
||||
...this.site.frames[frame],
|
||||
...this.settings.active.sites[this.site.frames[frame].host]
|
||||
})
|
||||
};
|
||||
}
|
||||
},
|
||||
getRandomColor() {
|
||||
return `rgb(${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)})`;
|
||||
},
|
||||
updateConfig() {
|
||||
this.settings.init();
|
||||
this.$nextTick( () => this.$forceUpdate());
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="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';
|
||||
*/
|
||||
|
||||
.header {
|
||||
background-color: rgb(90, 28, 13);
|
||||
background-color: rgb(0,0,0);
|
||||
color: #fff;
|
||||
padding: 8px;
|
||||
|
||||
border-bottom: 1px dotted #fa6;
|
||||
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.version-info {
|
||||
text-align: right;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.8;
|
||||
label {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.settings-header-button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
padding: 0.5rem 2rem;
|
||||
text-transform: lowercase;
|
||||
font-variant: small-caps;
|
||||
|
||||
background-color: #000;
|
||||
border: 1px solid #fa68;
|
||||
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.site-support-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.site-support-site {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
&: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;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-support {
|
||||
background-color: rgb(138, 65, 126);
|
||||
color: #eee;
|
||||
|
||||
.mdi {
|
||||
fill: #eee !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.user-added {
|
||||
border: 1px solid #ff0;
|
||||
|
||||
color: #ff0;
|
||||
|
||||
.mdi {
|
||||
fill: #ff0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.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);
|
||||
}
|
||||
.tab-row {
|
||||
background-color: rgba(11,11,11, 0.75);
|
||||
|
||||
.tab {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
padding: 1rem;
|
||||
font-size: 1.25rem;
|
||||
min-height: 3rem;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.button {
|
||||
border: 1px solid #222 !important;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0; padding: 0; font-weight: 400; font-size:24px;
|
||||
}
|
||||
|
||||
.window-content {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
</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,20 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" style="position: relative; height: 600px">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
<!-- <link rel="stylesheet" href="popup.css"> -->
|
||||
|
||||
<% if (NODE_ENV === 'development') { %>
|
||||
<!-- Load some resources only in development environment -->
|
||||
<% } %>
|
||||
</head>
|
||||
<body
|
||||
style="width: 100%; height: 100vh; overflow-x: hidden; min-width: 750px;"
|
||||
>
|
||||
<div id="app" style="max-height: 100vh;">
|
||||
|
||||
</div>
|
||||
<script src="csui-popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,8 +0,0 @@
|
||||
import { createApp } from 'vue'
|
||||
import Popup from './Popup';
|
||||
import mdiVue from 'mdi-vue/v3';
|
||||
import * as mdijs from '@mdi/js';
|
||||
|
||||
createApp(Popup)
|
||||
.use(mdiVue, {icons: mdijs})
|
||||
.mount('#app');
|
||||
@ -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,103 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col w-full h-full gap-2">
|
||||
<div class="flex flex-row gap-8 bg-black flex-wrap w-full">
|
||||
<div class="min-w-[400px] max-w-[520px] grow shrink">
|
||||
<h1>What's new</h1>
|
||||
<!-- <p>Full changelog for older versions <a href="https://github.com/tamius-han/ultrawidify/blob/master/CHANGELOG.md" target="_blank">is available here</a>.</p> -->
|
||||
|
||||
<h2>6.4.0</h2>
|
||||
<ul>
|
||||
<li>In-player UI can now appear in full-screen for websites that put players into top layer</li>
|
||||
<li>Embedded sites now inherit settings of the parent frame. <small>However, this hasn't been tested for all edge cases and may contain bugs.</small></li>
|
||||
<li>Added validation to custom aspect ratio entry menu. Corrected parsing of aspect ratios given in the X:Y format, even though aspect ratios should be ideally given as a single number.</li>
|
||||
<li>Autodetection can be set to stop after first aspect ratio detection, or after a period of no changes.</li>
|
||||
<li>
|
||||
There is a new, experiental mode for autodetection. At this point, it can be manually enabled in the autodetection settings. It will become the default option in 2026.<br/>
|
||||
If you enable experimental mode, please consider reporting problems <a href="https://github.com/tamius-han/ultrawidify/issues/291" target="_blank">in this thread</a> on Github.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
<div style="width: 1rem; height: 0px;"></div>
|
||||
<div class="min-w-[400px] max-w-[520px] grow shrink">
|
||||
<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 extension version, whether you installed the extension from, and description of your problem.
|
||||
</p>
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import BrowserDetect from '@src/ext/conf/BrowserDetect';
|
||||
|
||||
export default({
|
||||
props: [
|
||||
'settings'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
BrowserDetect: BrowserDetect,
|
||||
// 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,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.settings.active.whatsNewChecked = true;
|
||||
this.settings.saveWithoutReload();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
.flex-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
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,103 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
|
||||
<div>
|
||||
<h1>{{loggerPanel.title}}<small><br/>{{loggerPanel.subtitle}}</small></h1>
|
||||
|
||||
<div>Logger configuration:</div>
|
||||
|
||||
<JsonEditor
|
||||
v-model="lastSettings"
|
||||
></JsonEditor>
|
||||
|
||||
<div class="flex flex-row flex-end">
|
||||
<div class="button" @click="getLoggerSettings()">
|
||||
Revert
|
||||
</div>
|
||||
<div class="button button-primary" @click="saveLoggerSettings()">
|
||||
Save
|
||||
</div>
|
||||
<div class="button button-primary" @click="startLogging()">
|
||||
Save and start logging
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { LogAggregator, BLANK_LOGGER_CONFIG } from '@src/ext/lib/logging/LogAggregator';
|
||||
import JsonEditor from '@csui/src/components/JsonEditor';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
JsonEditor
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loggerPanelRotation: [{
|
||||
title: 'DEFORESTATOR 5000',
|
||||
subtitle: 'Iron Legion\'s finest logging tool'
|
||||
},{
|
||||
title: 'Astinus',
|
||||
subtitle: 'Ultrawidify logging tool'
|
||||
}, {
|
||||
title: 'Tracer',
|
||||
subtitle: "Maybe I'll be Tracer — I'm already printing stack traces."
|
||||
}, {
|
||||
title: 'Grûmsh',
|
||||
subtitle: "He who watches (all the things printed to the console)."
|
||||
}, {
|
||||
title: 'Situation Room/The Council',
|
||||
subtitle: 'We will always be watching.'
|
||||
}, {
|
||||
title: 'Isengard Land Management Services',
|
||||
subtitle: "#log4saruman"
|
||||
}, {
|
||||
title: 'Saw Hero',
|
||||
subtitle: 'Ultrawidify logging tool'
|
||||
}],
|
||||
|
||||
loggerPanel: {
|
||||
title: 'whoopsie daisy',
|
||||
subtitle: 'you broke the header choosing script',
|
||||
pasteConfMode: false,
|
||||
},
|
||||
lastSettings: undefined,
|
||||
parsedSettings: undefined,
|
||||
currentSettings: undefined
|
||||
};
|
||||
},
|
||||
props: [
|
||||
'settings',
|
||||
'eventBus',
|
||||
'site'
|
||||
],
|
||||
created() {
|
||||
this.loggerPanel = {
|
||||
...this.loggerPanelRotation[Math.floor(Math.random() * this.loggerPanelRotation.length)],
|
||||
pasteConfMode: false,
|
||||
};
|
||||
this.getLoggerSettings();
|
||||
},
|
||||
methods: {
|
||||
loadDefaultConfig() {
|
||||
this.lastSettings = JSON.parse(JSON.stringify(BLANK_LOGGER_CONFIG));
|
||||
},
|
||||
async getLoggerSettings() {
|
||||
this.lastSettings = await LogAggregator.getConfig() || BLANK_LOGGER_CONFIG;
|
||||
},
|
||||
async saveLoggerSettings() {
|
||||
console.log('Saving logger settings', this.lastSettings);
|
||||
await LogAggregator.saveConfig({...this.lastSettings});
|
||||
console.log('[ok] logger settings saved');
|
||||
},
|
||||
async startLogging(){
|
||||
this.logStringified = undefined;
|
||||
await LogAggregator.saveConfig({...this.lastSettings});
|
||||
window.location.reload();
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1,177 +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'"
|
||||
></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',
|
||||
],
|
||||
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,660 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- Enable extension -->
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Enable <span class="color-emphasis">extension</span>
|
||||
<span class="sub-label"><br/>under the following conditions:</span>
|
||||
</div>
|
||||
<div class="select">
|
||||
<select
|
||||
:value="simpleExtensionSettings.enable"
|
||||
@click="setExtensionMode('enable', $event)"
|
||||
>
|
||||
<option
|
||||
v-if="simpleExtensionSettings.enable === 'complex'"
|
||||
value="complex"
|
||||
>
|
||||
(Site uses advanced settings)
|
||||
</option>
|
||||
<template v-if="isDefaultConfiguration">
|
||||
<option value="disabled">
|
||||
Disabled by default
|
||||
</option>
|
||||
</template>
|
||||
<template v-else>
|
||||
<option value="default">
|
||||
Use default ({{simpleDefaultSettings.enable}})
|
||||
</option>
|
||||
<option value="disabled">
|
||||
Never
|
||||
</option>
|
||||
</template>
|
||||
<option value="fs">
|
||||
Fullscreen only
|
||||
</option>
|
||||
<option value="theater">
|
||||
Fullscreen and theater mode
|
||||
</option>
|
||||
<option value="enabled">
|
||||
Always
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- The rest of the menu is disabled when extension is disabled -->
|
||||
<div :class="{disabled: simpleEffectiveSettings.enable === 'disabled' && !isDefaultConfiguration}">
|
||||
<!-- Enable AARD -->
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Enable <span class="color-emphasis">automatic aspect ratio detection</span>
|
||||
<span class="sub-label"><br/>under the following conditions:</span>
|
||||
</div>
|
||||
<div class="select">
|
||||
<select
|
||||
:value="simpleExtensionSettings.enableAard"
|
||||
@click="setExtensionMode('enableAard', $event)"
|
||||
>
|
||||
<option
|
||||
v-if="simpleExtensionSettings.enable === 'complex'"
|
||||
value="complex"
|
||||
>
|
||||
(Site uses advanced settings)
|
||||
</option>
|
||||
<template v-if="isDefaultConfiguration">
|
||||
<option value="disabled">
|
||||
Disabled by default
|
||||
</option>
|
||||
</template>
|
||||
<template v-else>
|
||||
<option value="default">
|
||||
Use default ({{simpleDefaultSettings.enableAard}})
|
||||
</option>
|
||||
<option value="disabled">
|
||||
Never
|
||||
</option>
|
||||
</template>
|
||||
<option value="fs">
|
||||
Fullscreen only
|
||||
</option>
|
||||
<option value="theater">
|
||||
Fullscreen and theater mode
|
||||
</option>
|
||||
<option value="enabled">
|
||||
Always
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enable keyboard -->
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Enable <span class="color-emphasis">keyboard shortcuts</span>
|
||||
<span class="sub-label"><br/>under the following conditions:</span>
|
||||
</div>
|
||||
<div class="select">
|
||||
<select
|
||||
:value="simpleExtensionSettings.enableKeyboard"
|
||||
@click="setExtensionMode('enableKeyboard', $event)"
|
||||
>
|
||||
<option
|
||||
v-if="simpleExtensionSettings.enable === 'complex'"
|
||||
value="complex"
|
||||
>
|
||||
(Site uses advanced settings)
|
||||
</option>
|
||||
<template v-if="isDefaultConfiguration">
|
||||
<option value="disabled">
|
||||
Disabled by default
|
||||
</option>
|
||||
</template>
|
||||
<template v-else>
|
||||
<option value="default">
|
||||
Use default ({{simpleDefaultSettings.enableKeyboard}})
|
||||
</option>
|
||||
<option value="disabled">
|
||||
Never
|
||||
</option>
|
||||
</template>
|
||||
<option value="fs">
|
||||
Fullscreen only
|
||||
</option>
|
||||
<option value="theater">
|
||||
Fullscreen and theater mode
|
||||
</option>
|
||||
<option value="enabled">
|
||||
Always
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enable UI -->
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Enable <span class="color-emphasis">in-player UI</span>
|
||||
<span class="sub-label"><br/>under the following conditions:</span>
|
||||
</div>
|
||||
<div class="select">
|
||||
<select
|
||||
:value="simpleExtensionSettings.enableUI"
|
||||
@click="setExtensionMode('enableUI', $event)"
|
||||
>
|
||||
<template v-if="isDefaultConfiguration">
|
||||
<option value="disabled">
|
||||
Disabled by default
|
||||
</option>
|
||||
</template>
|
||||
<template v-else>
|
||||
<option value="default">
|
||||
Use default ({{simpleDefaultSettings.enableUI}})
|
||||
</option>
|
||||
<option value="disabled">
|
||||
Never
|
||||
</option>
|
||||
</template>
|
||||
<option value="fs">
|
||||
Fullscreen only
|
||||
</option>
|
||||
<option value="theater">
|
||||
Always where possible
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Use these settings for <span class="color-emphasis">embedded content</span>?
|
||||
<!-- <span class="sub-label"><br/>under the following conditions:</span> -->
|
||||
</div>
|
||||
<div class="select">
|
||||
<select
|
||||
:value="siteDefaultForEmbedded"
|
||||
@click="setSiteOption('applyToEmbeddedContent', $event)"
|
||||
>
|
||||
<option :value="EmbeddedContentSettingsOverridePolicy.Always">
|
||||
Always(-ish)
|
||||
</option>
|
||||
<option :value="EmbeddedContentSettingsOverridePolicy.UseAsDefault">
|
||||
Use as default
|
||||
</option>
|
||||
<option :value="EmbeddedContentSettingsOverridePolicy.Never">
|
||||
Never
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Force these settings <span class="color-emphasis">when embedded</span>?
|
||||
<!-- <span class="sub-label"><br/>under the following conditions:</span> -->
|
||||
</div>
|
||||
<div class="select">
|
||||
<select
|
||||
:value="siteDefaultOverrideEmbedded"
|
||||
@click="setSiteOption('overrideWhenEmbedded', $event)"
|
||||
>
|
||||
<option :value="true">
|
||||
Yes
|
||||
</option>
|
||||
<option :value="false">
|
||||
No
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Default crop -->
|
||||
<div class="field">
|
||||
<div class="label">Default crop:</div>
|
||||
<div class="select">
|
||||
<select
|
||||
:value="siteDefaultCrop"
|
||||
@change="setOption('defaults.crop', $event)"
|
||||
>
|
||||
<option
|
||||
v-if="!isDefaultConfiguration"
|
||||
:value="JSON.stringify({useDefault: true})"
|
||||
>
|
||||
Use default ({{getCommandValue(settings?.active.commands.crop, siteSettings.data.defaults.crop)}})
|
||||
</option>
|
||||
<option
|
||||
v-for="(command, index) of settings?.active.commands.crop"
|
||||
:key="index"
|
||||
:value="JSON.stringify(command.arguments)"
|
||||
>
|
||||
{{command.label}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">This is how extension will crop video if/when autodetection is disabled. Pick 'Reset' option to keep aspect ratio as-is by default.</div>
|
||||
|
||||
<!-- Default stretch -->
|
||||
<div class="field">
|
||||
<div class="label">Default stretch:</div>
|
||||
<div class="select">
|
||||
<select
|
||||
v-model="siteDefaultStretch"
|
||||
@change="setOption('defaults.stretch', $event)"
|
||||
>
|
||||
<option
|
||||
v-if="!isDefaultConfiguration"
|
||||
:value="JSON.stringify({useDefault: true})"
|
||||
>
|
||||
Use default ({{getCommandValue(settings?.active.commands.stretch, siteSettings.data.defaults.stretch)}})
|
||||
</option>
|
||||
<option
|
||||
v-for="(command, index) of settings?.active.commands.stretch"
|
||||
:key="index"
|
||||
:value="JSON.stringify(command.arguments)"
|
||||
>
|
||||
{{command.label}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Default alignment -->
|
||||
<div class="field">
|
||||
<div class="label">Default alignment:</div>
|
||||
<div class="select">
|
||||
<select
|
||||
v-model="siteDefaultAlignment"
|
||||
@change="setOption('defaults.alignment', $event)"
|
||||
>
|
||||
<option
|
||||
v-if="!isDefaultConfiguration"
|
||||
:value="JSON.stringify({useDefault: true})"
|
||||
>
|
||||
Use default ({{getAlignmentLabel(siteSettings.data.defaults.alignment)}})
|
||||
</option>
|
||||
<option
|
||||
v-for="(command, index) of alignmentOptions"
|
||||
:key="index"
|
||||
:value="JSON.stringify(command.arguments)"
|
||||
>
|
||||
{{command.label}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Crop, et. al. Persistence -->
|
||||
<div class="field">
|
||||
<div class="label">Persist crop, stretch, and alignment between videos</div>
|
||||
<div class="select">
|
||||
<select
|
||||
v-model="siteDefaultCropPersistence"
|
||||
@click="setOption('persistCSA', $event)"
|
||||
>
|
||||
<option
|
||||
v-if="!isDefaultConfiguration"
|
||||
:value="CropModePersistence.Default"
|
||||
>
|
||||
Use default ({{defaultPersistanceLabel}})
|
||||
</option>
|
||||
<option :value="CropModePersistence.Disabled">Disabled</option>
|
||||
<option :value="CropModePersistence.UntilPageReload">Until page reload</option>
|
||||
<option :value="CropModePersistence.CurrentSession">Current session</option>
|
||||
<option :value="CropModePersistence.Forever">Always persist</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ExtensionMode from '@src/common/enums/ExtensionMode.enum';
|
||||
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum';
|
||||
import CropModePersistence from '@src/common/enums/CropModePersistence.enum';
|
||||
import EmbeddedContentSettingsOverridePolicy from '@src/common/enums/EmbeddedContentSettingsOverridePolicy.enum';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
CropModePersistence: CropModePersistence,
|
||||
ExtensionMode,
|
||||
EmbeddedContentSettingsOverridePolicy,
|
||||
alignmentOptions: [
|
||||
{label: 'Top left', arguments: {x: VideoAlignmentType.Left, y: VideoAlignmentType.Top}},
|
||||
{label: 'Top center', arguments: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Top}},
|
||||
{label: 'Top right', arguments: {x: VideoAlignmentType.Right, y: VideoAlignmentType.Top}},
|
||||
{label: 'Left', arguments: {x: VideoAlignmentType.Left, y: VideoAlignmentType.Center}},
|
||||
{label: 'Center', arguments: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Center}},
|
||||
{label: 'Right', arguments: {x: VideoAlignmentType.Right, y: VideoAlignmentType.Center}},
|
||||
{label: 'Bottom left', arguments: {x: VideoAlignmentType.Left, y: VideoAlignmentType.Bottom}},
|
||||
{label: 'Bottom center', arguments: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Bottom}},
|
||||
{label: 'Bottom right', arguments: {x: VideoAlignmentType.Right, y: VideoAlignmentType.Bottom}}
|
||||
]
|
||||
}
|
||||
},
|
||||
mixins: [
|
||||
|
||||
],
|
||||
props: [
|
||||
'settings',
|
||||
'siteSettings',
|
||||
'isDefaultConfiguration'
|
||||
],
|
||||
components: {
|
||||
|
||||
},
|
||||
computed: {
|
||||
simpleExtensionSettings() {
|
||||
return {
|
||||
enable: this.compileSimpleSettings('enable'),
|
||||
enableAard: this.compileSimpleSettings('enableAard'),
|
||||
enableKeyboard: this.compileSimpleSettings('enableKeyboard'),
|
||||
enableUI: this.compileSimpleSettings('enableUI')
|
||||
}
|
||||
},
|
||||
simpleEffectiveSettings() {
|
||||
return {
|
||||
enable: this.compileSimpleSettings('enable', 'site-effective'),
|
||||
enableAard: this.compileSimpleSettings('enableAard', 'site-effective'),
|
||||
enableKeyboard: this.compileSimpleSettings('enableKeyboard', 'site-effective'),
|
||||
enableUI: this.compileSimpleSettings('enableUI', 'site-effective')
|
||||
}
|
||||
},
|
||||
simpleDefaultSettings() {
|
||||
return {
|
||||
enable: this.getDefaultOptionLabel('enable'),
|
||||
enableAard: this.getDefaultOptionLabel('enableAard'),
|
||||
enableKeyboard: this.getDefaultOptionLabel('enableKeyboard'),
|
||||
enableUI: this.getDefaultOptionLabel('enableUI')
|
||||
};
|
||||
},
|
||||
siteDefaultForEmbedded() {
|
||||
return this.siteSettings.raw?.applyToEmbeddedContent;
|
||||
},
|
||||
siteDefaultOverrideEmbedded() {
|
||||
return this.siteSettings.raw?.overrideWhenEmbedded ?? false;
|
||||
},
|
||||
siteDefaultCrop() {
|
||||
return this.siteSettings.raw?.defaults?.crop ? JSON.stringify(this.siteSettings.raw?.defaults?.crop) : JSON.stringify({useDefault: true});
|
||||
},
|
||||
siteDefaultStretch() {
|
||||
return this.siteSettings.raw?.defaults?.stretch ? JSON.stringify(this.siteSettings.raw?.defaults?.stretch) : JSON.stringify({useDefault: true});
|
||||
},
|
||||
siteDefaultAlignment() {
|
||||
return this.siteSettings.raw?.defaults?.alignment ? JSON.stringify(this.siteSettings.raw?.defaults?.alignment) : JSON.stringify({useDefault: true});
|
||||
},
|
||||
siteDefaultCropPersistence() {
|
||||
return this.siteSettings.raw?.persistCSA ?? undefined;
|
||||
},
|
||||
defaultPersistanceLabel() {
|
||||
switch (this.siteSettings.defaultSettings.persistCSA) {
|
||||
case CropModePersistence.CurrentSession:
|
||||
return 'current session';
|
||||
case CropModePersistence.Disabled:
|
||||
return 'disabled';
|
||||
case CropModePersistence.UntilPageReload:
|
||||
return 'until page reload';
|
||||
case CropModePersistence.Forever:
|
||||
return 'Always persist';
|
||||
}
|
||||
|
||||
return '??';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.forceRefreshPage();
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Compiles our extension settings into more user-friendly options
|
||||
*/
|
||||
compileSimpleSettings(component, getFor = 'site') {
|
||||
|
||||
let settingsData;
|
||||
switch (getFor) {
|
||||
case 'site':
|
||||
settingsData = this.siteSettings?.raw;
|
||||
break;
|
||||
case 'site-effective':
|
||||
settingsData = this.siteSettings?.data;
|
||||
break;
|
||||
case 'default':
|
||||
settingsData = this.settings.active.sites['@global'];
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
if (
|
||||
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI')
|
||||
&& settingsData?.[component]?.theater === ExtensionMode.Disabled
|
||||
&& settingsData?.[component]?.fullscreen === ExtensionMode.Disabled
|
||||
) {
|
||||
return 'disabled';
|
||||
}
|
||||
if (
|
||||
( settingsData?.[component]?.normal === ExtensionMode.Default || component === 'enableUI')
|
||||
&& settingsData?.[component]?.theater === ExtensionMode.Default
|
||||
&& settingsData?.[component]?.fullscreen === ExtensionMode.Default
|
||||
) {
|
||||
// console.log(
|
||||
// component, 'is set to default because:\n',
|
||||
// `\nsettingsData[${component}].normal: ${settingsData?.[component]?.normal} || component is enableUI?`, component,
|
||||
// `\nsettingsData[${component}].theater: ${settingsData?.[component]?.normal}`,
|
||||
// `\nsettingsData[${component}].fullscreen: ${settingsData?.[component]?.normal}`,
|
||||
|
||||
// `\n\n(expected values:`, ExtensionMode.Default
|
||||
// )
|
||||
return 'default';
|
||||
}
|
||||
if (
|
||||
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI')
|
||||
&& settingsData?.[component]?.theater === ExtensionMode.Disabled
|
||||
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled
|
||||
) {
|
||||
return 'fs';
|
||||
}
|
||||
if (
|
||||
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI')
|
||||
&& settingsData?.[component]?.theater === ExtensionMode.Enabled
|
||||
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled
|
||||
) {
|
||||
return 'theater';
|
||||
}
|
||||
if (
|
||||
( settingsData?.[component]?.normal === ExtensionMode.Enabled || component === 'enableUI')
|
||||
&& settingsData?.[component]?.theater === ExtensionMode.Enabled
|
||||
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled
|
||||
) {
|
||||
return 'enabled';
|
||||
}
|
||||
|
||||
return 'complex';
|
||||
} catch (e) {
|
||||
return 'loading';
|
||||
}
|
||||
},
|
||||
|
||||
getDefaultOptionLabel(component) {
|
||||
const componentValue = this.compileSimpleSettings(component, 'default');
|
||||
|
||||
if (componentValue === 'loading') {
|
||||
return componentValue;
|
||||
}
|
||||
if (component === 'enableUI') {
|
||||
switch (componentValue) {
|
||||
case 'fs':
|
||||
return 'fullscreen only';
|
||||
case 'enabled':
|
||||
case 'theater':
|
||||
return 'where possible';
|
||||
case 'disabled':
|
||||
return 'disabled';
|
||||
}
|
||||
}
|
||||
switch (componentValue) {
|
||||
case 'fs':
|
||||
return 'Fullscreen only';
|
||||
case 'theater':
|
||||
return 'theater & FS';
|
||||
case 'enabled':
|
||||
return 'always';
|
||||
case 'disabled':
|
||||
return 'disabled';
|
||||
case 'default':
|
||||
return 'complex'
|
||||
}
|
||||
},
|
||||
|
||||
getCommandValue(availableCommands, command) {
|
||||
for (const cmd of availableCommands) {
|
||||
if (JSON.stringify(cmd.arguments) === JSON.stringify(command)) {
|
||||
return cmd.label;
|
||||
}
|
||||
}
|
||||
return 'Unknown command';
|
||||
},
|
||||
|
||||
getAlignmentLabel(alignment) {
|
||||
// in case default settings don't have this set
|
||||
if (!alignment) {
|
||||
return 'Center'
|
||||
}
|
||||
|
||||
let x, y;
|
||||
if (alignment.x === VideoAlignmentType.Center) {
|
||||
x = 'center';
|
||||
} else if (alignment.x === VideoAlignmentType.Left) {
|
||||
x = 'left';
|
||||
} else if (alignment.x === VideoAlignmentType.Right) {
|
||||
x = 'right';
|
||||
} else {
|
||||
x = '??'
|
||||
}
|
||||
|
||||
if (alignment.y === VideoAlignmentType.Center) {
|
||||
y = 'center';
|
||||
} else if (alignment.y === VideoAlignmentType.Bottom) {
|
||||
y = 'bottom';
|
||||
} else if (alignment.y === VideoAlignmentType.Top) {
|
||||
y = 'top';
|
||||
} else {
|
||||
y = '???'
|
||||
};
|
||||
|
||||
if (x === y) {
|
||||
return x;
|
||||
}
|
||||
return `${y} ${x}`;
|
||||
},
|
||||
getOption(option) {
|
||||
|
||||
},
|
||||
async setOption(option, $event) {
|
||||
const value = $event.target.value;
|
||||
let commandArguments;
|
||||
|
||||
// if argument is json, parse json. Otherwise, pass the value as-is
|
||||
try {
|
||||
commandArguments = value !== undefined ? JSON.parse(value) : undefined;
|
||||
} catch(e) {
|
||||
commandArguments = value;
|
||||
}
|
||||
|
||||
if (commandArguments.useDefault) {
|
||||
commandArguments = undefined;
|
||||
}
|
||||
|
||||
await this.siteSettings.set(option, commandArguments, {reload: false});
|
||||
|
||||
// we also need to force re-compute all watchers, otherwise UI will lag behind
|
||||
// actual state of settings until reload
|
||||
this.forceRefreshPage();
|
||||
},
|
||||
forceRefreshPage() {
|
||||
this._computedWatchers?.simpleExtensionSettings?.run();
|
||||
this._computedWatchers?.simpleDefaultSettings?.run();
|
||||
this._computedWatchers?.siteDefaultCrop?.run();
|
||||
this._computedWatchers?.siteDefaultStretch?.run();
|
||||
this._computedWatchers?.siteDefaultAlignment?.run();
|
||||
this._computedWatchers?.siteDefaultCropPersistence?.run();
|
||||
this._computedWatchers?.defaultPersistanceLabel?.run();
|
||||
|
||||
this.$nextTick( () => this.$forceUpdate());
|
||||
},
|
||||
|
||||
setSiteOption(optionPath, event) {
|
||||
const value = event.target.value;
|
||||
this.siteSettings.set(optionPath, value);
|
||||
},
|
||||
|
||||
setExtensionMode(component, event) {
|
||||
const option = event.target.value;
|
||||
|
||||
if (option === 'complex') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (component === 'enable' && !this.isDefaultConfiguration) {
|
||||
this.setExtensionMode('enableAard', event);
|
||||
this.setExtensionMode('enableKeyboard', event);
|
||||
|
||||
// in enableUI, 'enabled' is unused and 'theater' uses its place
|
||||
if (option === 'enabled') {
|
||||
this.setExtensionMode('enableUI', {target: {value: 'theater'}});
|
||||
} else {
|
||||
this.setExtensionMode('enableUI', event);
|
||||
}
|
||||
}
|
||||
|
||||
if (option === 'default') {
|
||||
return this.siteSettings.set(component, {
|
||||
normal: ExtensionMode.Default,
|
||||
theater: ExtensionMode.Default,
|
||||
fullscreen: ExtensionMode.Default
|
||||
});
|
||||
}
|
||||
if (option === 'disabled') {
|
||||
return this.siteSettings.set(component, {
|
||||
normal: ExtensionMode.Disabled,
|
||||
theater: ExtensionMode.Disabled,
|
||||
fullscreen: ExtensionMode.Disabled
|
||||
});
|
||||
}
|
||||
if (option === 'enabled') {
|
||||
return this.siteSettings.set(component, {
|
||||
normal: ExtensionMode.Enabled,
|
||||
theater: ExtensionMode.Enabled,
|
||||
fullscreen: ExtensionMode.Enabled
|
||||
});
|
||||
}
|
||||
if (option === 'theater') {
|
||||
return this.siteSettings.set(component, {
|
||||
normal: ExtensionMode.Disabled,
|
||||
theater: ExtensionMode.Enabled,
|
||||
fullscreen: ExtensionMode.Enabled
|
||||
});
|
||||
}
|
||||
if (option === 'fs') {
|
||||
return this.siteSettings.set(component, {
|
||||
normal: ExtensionMode.Disabled,
|
||||
theater: ExtensionMode.Disabled,
|
||||
fullscreen: ExtensionMode.Enabled
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</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 scoped>
|
||||
.button-hover:hover {
|
||||
color: #fa6;
|
||||
}
|
||||
</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,253 +0,0 @@
|
||||
<template>
|
||||
<div class="sub-panel-content flex flex-row flex-wrap">
|
||||
|
||||
<ShortcutButton
|
||||
v-for="(command, index) of settings?.active.commands.crop"
|
||||
class="flex button"
|
||||
:class="{
|
||||
active: editMode ? index === editModeOptions?.crop?.selectedIndex : isActiveCrop(command),
|
||||
'b3-compact': compact,
|
||||
b3: !compact
|
||||
}"
|
||||
:key="index"
|
||||
:label="command.label"
|
||||
:shortcut="getKeyboardShortcutLabel(command)"
|
||||
@click="editMode ? editAction(command, index, 'crop') : execAction(command)"
|
||||
>
|
||||
</ShortcutButton>
|
||||
|
||||
<!-- "Add new" button -->
|
||||
<ShortcutButton
|
||||
v-if="editMode"
|
||||
class="button b3"
|
||||
:class="{active: editMode ? editModeOptions?.crop?.selectedIndex === null : isActiveCrop(command)}"
|
||||
label="Add new"
|
||||
@click="editAction(
|
||||
{action: 'set-ar', label: 'New aspect ratio', arguments: {type: AspectRatioType.Fixed}},
|
||||
null,
|
||||
'crop'
|
||||
)"
|
||||
></ShortcutButton>
|
||||
</div>
|
||||
|
||||
<!-- EDIT MODE PANEL -->
|
||||
<div
|
||||
v-if="editMode && !editModeOptions?.crop?.selected"
|
||||
class="sub-panel-content"
|
||||
>
|
||||
<div class="edit-action-area">
|
||||
Click a button to edit
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="editMode && editModeOptions?.crop?.selected" class="sub-panel-content">
|
||||
<div class="edit-action-area-header">
|
||||
<span class="text-primary">Editing options for:</span> <b>{{editModeOptions?.crop?.selected?.label}}</b>
|
||||
<template v-if="editModeOptions?.crop?.selectedIndex === null && editModeOptions?.crop?.selected?.label !== 'New aspect ratio'">(New ratio)</template>
|
||||
</div>
|
||||
<div class="edit-action-area">
|
||||
<!-- Some options are only shown for type 4 (fixed) crops -->
|
||||
<template v-if="editModeOptions?.crop?.selected?.arguments?.type === AspectRatioType.Fixed">
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Ratio:
|
||||
</div>
|
||||
<div class="input">
|
||||
<!-- We do an ugly in order to avoid spamming functions down at the bottom -->
|
||||
<input
|
||||
v-model="editModeOptions.crop.selected.arguments.ratio"
|
||||
@blur="updateLabel('crop')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="editModeOptions.crop?.error" class="hint error">
|
||||
{{editModeOptions.crop.error}}
|
||||
</div>
|
||||
<div class="hint">
|
||||
You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor
|
||||
(in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter
|
||||
your numbers without quote marks. Number will be converted to factor form on save.
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Label:
|
||||
</div>
|
||||
<div class="input">
|
||||
<input v-model="editModeOptions.crop.selected.label">
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
Label for the button. You can make it say something other than ratio.
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- editing keyboard shortcuts is always allowed -->
|
||||
<div class="field">
|
||||
<div class="label">Shortcut:</div>
|
||||
<div class="">
|
||||
<EditShortcutButton
|
||||
:shortcut="editModeOptions?.crop?.selected?.shortcut"
|
||||
@shortcutChanged="updateSelectedShortcut($event, 'crop')"
|
||||
>
|
||||
</EditShortcutButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
<b>Note:</b> Your browser and OS already use certain key combinations that involve Ctrl and Meta (Windows) keys — and, to a lesser extent, Alt.
|
||||
The extension doesn't (and cannot) check whether the keyboard shortcut you enter is actually free for you to use. The extension also won't override
|
||||
any keyboard shortcuts defined by the site itself.
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row flex-end">
|
||||
<div
|
||||
v-if="editModeOptions?.crop?.selected?.arguments?.type === AspectRatioType.Fixed && editModeOptions?.crop?.selectedIndex !== null"
|
||||
class="button"
|
||||
@click="deleteAction('crop')"
|
||||
>
|
||||
<mdicon name="delete"></mdicon> Delete
|
||||
</div>
|
||||
<div class="flex-grow"></div>
|
||||
<div class="button" @click="cancelEdit('crop')">Cancel</div>
|
||||
<div class="button" @click="saveShortcut('crop')">
|
||||
<mdicon name="floppy"></mdicon>
|
||||
|
||||
<template v-if="editModeOptions?.crop?.selectedIndex === null">Add</template>
|
||||
<template v-else>Save</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="siteSettings && allowSettingSiteDefault" class="edit-action-area">
|
||||
<div class="field">
|
||||
<div class="label">Default for this site</div>
|
||||
<div class="select">
|
||||
<select
|
||||
:value="siteDefaultCrop"
|
||||
@change="setDefaultCrop($event, 'site')"
|
||||
>
|
||||
<option
|
||||
v-for="(command, index) of settings?.active.commands.crop"
|
||||
:key="index"
|
||||
:value="JSON.stringify(command.arguments)"
|
||||
>
|
||||
{{command.label}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ShortcutButton from '@csui/src/components/ShortcutButton.vue';
|
||||
import EditShortcutButton from '@csui/src/components/EditShortcutButton';
|
||||
import EditModeMixin from '@csui/src/utils/EditModeMixin';
|
||||
import KeyboardShortcutParserMixin from '@csui/src/utils/KeyboardShortcutParserMixin';
|
||||
import CommsMixin from '@csui/src/utils/CommsMixin';
|
||||
import AspectRatioType from '@src/common/enums/AspectRatioType.enum';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ShortcutButton,
|
||||
EditShortcutButton,
|
||||
},
|
||||
mixins: [
|
||||
// ComputeActionsMixin,
|
||||
EditModeMixin,
|
||||
KeyboardShortcutParserMixin,
|
||||
CommsMixin
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
AspectRatioType: AspectRatioType,
|
||||
|
||||
// TODO: this should be mixin?
|
||||
resizerConfig: {
|
||||
crop: null,
|
||||
stretch: null,
|
||||
zoom: null,
|
||||
pan: null
|
||||
}
|
||||
}
|
||||
},
|
||||
props: [
|
||||
'settings', // required for buttons and actions, which are global
|
||||
'siteSettings',
|
||||
'eventBus',
|
||||
'isEditing',
|
||||
'allowSettingSiteDefault',
|
||||
'compact',
|
||||
],
|
||||
computed: {
|
||||
siteDefaultCrop() {
|
||||
if (!this.siteSettings) {
|
||||
return null;
|
||||
}
|
||||
return JSON.stringify(
|
||||
this.siteSettings.data.defaults.crop
|
||||
);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (this.isEditing) {
|
||||
this.enableEditMode();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isEditing(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.enableEditMode();
|
||||
} else {
|
||||
this.disableEditMode();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Sets default crop, for either site or global
|
||||
*/
|
||||
setDefaultCrop($event, scope) {
|
||||
if (!this.siteSettings) {
|
||||
return;
|
||||
}
|
||||
const commandArguments = JSON.parse($event.target.value);
|
||||
|
||||
this.siteSettings.set('defaults.crop', commandArguments);
|
||||
this.settings.saveWithoutReload();
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines whether a given crop command is the currently active one
|
||||
*/
|
||||
isActiveCrop(cropCommand) {
|
||||
if (! this.resizerConfig.crop || !this.siteSettings) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const defaultCrop = this.siteSettings.data.defaults.crop;
|
||||
|
||||
if (cropCommand.arguments.type === AspectRatioType.Automatic) {
|
||||
return this.resizerConfig.crop.type === AspectRatioType.Automatic
|
||||
|| this.resizerConfig.crop.type === AspectRatioType.AutomaticUpdate
|
||||
|| this.resizerConfig.crop.type === AspectRatioType.Initial && defaultCrop === AspectRatioType.Automatic;
|
||||
}
|
||||
if (cropCommand.arguments.type === AspectRatioType.Reset) {
|
||||
return this.resizerConfig.crop.type === AspectRatioType.Reset
|
||||
|| this.resizerConfig.crop.type === AspectRatioType.Initial && defaultCrop !== AspectRatioType.Automatic;
|
||||
}
|
||||
if (cropCommand.arguments.type === AspectRatioType.Fixed) {
|
||||
return this.resizerConfig.crop.type === AspectRatioType.Fixed
|
||||
&& this.resizerConfig.crop.ratio === cropCommand.arguments.ratio;
|
||||
}
|
||||
// only legacy options (fitw, fith) left to handle:
|
||||
return cropCommand.arguments.type === this.resizerConfig.crop.type;
|
||||
},
|
||||
}
|
||||
}
|
||||
</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>
|
||||
@ -1,257 +0,0 @@
|
||||
<template>
|
||||
<div class="sub-panel-content flex flex-row flex-wrap">
|
||||
<ShortcutButton
|
||||
v-for="(command, index) of settings?.active.commands.stretch"
|
||||
class="b3 button"
|
||||
:class="{active: editMode ? index === editModeOptions?.stretch?.selectedIndex : isActiveStretch(command),
|
||||
'b3-compact': compact,
|
||||
b3: !compact
|
||||
}"
|
||||
:key="index"
|
||||
:label="command.label"
|
||||
:shortcut="getKeyboardShortcutLabel(command)"
|
||||
@click="editMode ? editAction(command, index, 'stretch') : execAction(command)"
|
||||
>
|
||||
</ShortcutButton>
|
||||
|
||||
<!-- "Add new" button -->
|
||||
<ShortcutButton
|
||||
v-if="editMode"
|
||||
class="button b3"
|
||||
label="Add new"
|
||||
@click="editAction(
|
||||
{action: 'set-stretch', label: 'Stretch to ...', arguments: {type: StretchType.FixedSource}},
|
||||
null,
|
||||
'stretch'
|
||||
)"
|
||||
></ShortcutButton>
|
||||
</div>
|
||||
|
||||
<!-- EDIT MODE PANEL -->
|
||||
<div
|
||||
v-if="editMode && !editModeOptions?.stretch?.selected"
|
||||
class="sub-panel-content"
|
||||
>
|
||||
<div class="edit-action-area">
|
||||
Click a button to edit
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="editMode && editModeOptions?.stretch?.selected" class="sub-panel-content">
|
||||
<div class="edit-action-area-header">
|
||||
<span class="text-primary">Editing options for:</span> <b>{{editModeOptions?.stretch?.selected?.label}}</b>
|
||||
<template v-if="editModeOptions?.stretch?.selectedIndex === null && editModeOptions?.stretch?.selected?.label !== 'Stretch to ...'">(New option)</template>
|
||||
</div>
|
||||
<div class="edit-action-area">
|
||||
<!-- There are some special options for 'thin borders' -->
|
||||
<template v-if="editModeOptions?.stretch?.selected?.arguments?.type === StretchType.Conditional">
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Limit:
|
||||
</div>
|
||||
<div class="input">
|
||||
<input
|
||||
v-model="editModeOptions.stretch.selected.arguments.limit"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
If vertical borders would take up less than this much of screen width, the image will be stretched. If the borders are too thick, image will not be stretched.
|
||||
Value of 1 means 100%. Value of 0.1 means vertical black bars can take up 10% of the width at most. There's no validation on this, use common sense.
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Some options are only shown for type 5 (fixed) stretch -->
|
||||
<template v-if="editModeOptions?.stretch?.selected?.arguments?.type === StretchType.FixedSource">
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Ratio:
|
||||
</div>
|
||||
<div class="input">
|
||||
<!-- We do an ugly in order to avoid spamming functions down at the bottom -->
|
||||
<input
|
||||
v-model="editModeOptions.stretch.selected.arguments.ratio"
|
||||
@blur="updateLabel('stretch')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="editModeOptions.stretch?.error" class="hint error">
|
||||
{{editModeOptions.stretch.error}}
|
||||
</div>
|
||||
<div class="hint">
|
||||
You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor
|
||||
(in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter
|
||||
your numbers without quote marks. Number will be converted to factor form on save.
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Label:
|
||||
</div>
|
||||
<div class="input">
|
||||
<input v-model="editModeOptions.stretch.selected.label">
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
Label for the button. You can make it say something other than ratio.
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- editing keyboard shortcuts is always allowed -->
|
||||
<div class="field">
|
||||
<div class="label">Shortcut:</div>
|
||||
<div class="">
|
||||
<EditShortcutButton
|
||||
:shortcut="editModeOptions?.stretch?.selected?.shortcut"
|
||||
@shortcutChanged="updateSelectedShortcut($event, 'stretch')"
|
||||
>
|
||||
</EditShortcutButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
<b>Note:</b> Your browser and OS already use certain key combinations that involve Ctrl and Meta (Windows) keys — and, to a lesser extent, Alt.
|
||||
The extension doesn't (and cannot) check whether the keyboard shortcut you enter is actually free for you to use. The extension also won't override
|
||||
any keyboard shortcuts defined by the site itself.
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row flex-end">
|
||||
<div
|
||||
v-if="editModeOptions?.stretch?.selected?.arguments?.type === StretchType.FixedSource && editModeOptions?.stretch?.selectedIndex !== null"
|
||||
class="button"
|
||||
@click="deleteAction('stretch')"
|
||||
>
|
||||
<mdicon name="delete"></mdicon> Delete
|
||||
</div>
|
||||
<div class="flex-grow"></div>
|
||||
<div class="button" @click="cancelEdit('stretch')">Cancel</div>
|
||||
<div class="button" @click="saveShortcut('stretch')">
|
||||
<mdicon name="floppy"></mdicon>
|
||||
|
||||
<template v-if="editModeOptions?.crop?.selectedIndex === null">Add</template>
|
||||
<template v-else>Save</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="siteSettings && allowSettingSiteDefault" class="edit-action-area">
|
||||
<div class="field">
|
||||
<div class="label">Default for this site:</div>
|
||||
<div class="select">
|
||||
<div class="select">
|
||||
<select
|
||||
v-model="siteDefaultStretchMode"
|
||||
@change="setDefaultStretchingMode($event, 'site')"
|
||||
>
|
||||
<option
|
||||
v-for="(command, index) of settings?.active.commands.stretch"
|
||||
:key="index"
|
||||
:value="JSON.stringify(command.arguments)"
|
||||
>
|
||||
{{command.label}}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ShortcutButton from '../../../components/ShortcutButton.vue';
|
||||
import EditShortcutButton from '../../../components/EditShortcutButton';
|
||||
import EditModeMixin from '../../../utils/EditModeMixin';
|
||||
import KeyboardShortcutParserMixin from '../../../utils/KeyboardShortcutParserMixin';
|
||||
import CommsMixin from '../../../utils/CommsMixin';
|
||||
import StretchType from '../../../../../common/enums/StretchType.enum';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
exec: null,
|
||||
StretchType: StretchType,
|
||||
|
||||
// TODO: this should be mixin?
|
||||
resizerConfig: {
|
||||
crop: null,
|
||||
stretch: null,
|
||||
zoom: null,
|
||||
pan: null
|
||||
}
|
||||
}
|
||||
},
|
||||
mixins: [
|
||||
// ComputeActionsMixin,
|
||||
EditModeMixin,
|
||||
KeyboardShortcutParserMixin,
|
||||
CommsMixin
|
||||
],
|
||||
props: [
|
||||
'settings', // required for buttons and actions, which are global
|
||||
'siteSettings',
|
||||
'eventBus',
|
||||
'isEditing',
|
||||
'allowSettingSiteDefault',
|
||||
'compact',
|
||||
],
|
||||
components: {
|
||||
ShortcutButton,
|
||||
EditShortcutButton,
|
||||
},
|
||||
computed: {
|
||||
siteDefaultStretch() {
|
||||
if (!this.siteSettings) {
|
||||
return null;
|
||||
}
|
||||
return JSON.stringify(
|
||||
this.siteSettings.data.defaults.stretch
|
||||
);
|
||||
},
|
||||
},
|
||||
created() {
|
||||
if (this.isEditing) {
|
||||
this.enableEditMode();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isEditing(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.enableEditMode();
|
||||
} else {
|
||||
this.disableEditMode();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Sets default stretching mode, for either site or global
|
||||
*/
|
||||
setDefaultStretchingMode($event, globalOrSite) {
|
||||
if (!this.siteSettings) {
|
||||
return;
|
||||
}
|
||||
const commandArguments = JSON.parse($event.target.value);
|
||||
this.siteSettings.set('defaults.stretch', commandArguments);
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines whether a given stretch command is the currently active one
|
||||
*/
|
||||
isActiveStretch(stretchCommand) {
|
||||
if (! this.resizerConfig.stretch || !this.siteSettings) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// const defaultCrop = this.settings.getDefaultStretch(this.site);
|
||||
|
||||
if ([StretchType.NoStretch, StretchType.Basic, StretchType.Hybrid, StretchType.Conditional, StretchType.Default].includes(stretchCommand.arguments.type)) {
|
||||
return this.resizerConfig.stretch.type === stretchCommand.arguments.type;
|
||||
}
|
||||
return this.resizerConfig.crop.type === stretchCommand.arguments.type && this.resizerConfig.crop.ratio === stretchCommand.arguments.ratio;
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" src="@csui/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>
|
||||
@ -1,317 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
|
||||
<div class="sub-panel-content flex flex-row flex-wrap">
|
||||
|
||||
<ShortcutButton
|
||||
v-for="(command, index) of settings?.active.commands.zoom"
|
||||
class="flex button"
|
||||
:class="{active: editMode ? index === editModeOptions?.zoom?.selectedIndex : isActiveZoom(command),
|
||||
'b3-compact': compact,
|
||||
b3: !compact
|
||||
}"
|
||||
:key="index"
|
||||
:label="command.label"
|
||||
:shortcut="getKeyboardShortcutLabel(command)"
|
||||
@click="editMode ? editAction(command, index, 'zoom') : execAction(command)"
|
||||
>
|
||||
</ShortcutButton>
|
||||
|
||||
<!-- "Add new" button -->
|
||||
<ShortcutButton
|
||||
v-if="editMode"
|
||||
class="button b3"
|
||||
:class="{active: editMode ? editModeOptions?.crop?.selectedIndex === null : isActiveCrop(command)}"
|
||||
label="Add new"
|
||||
@click="editAction(
|
||||
{action: 'set-ar-zoom', label: 'New aspect ratio', arguments: {type: AspectRatioType.Fixed}},
|
||||
null,
|
||||
'zoom'
|
||||
)"
|
||||
></ShortcutButton>
|
||||
</div>
|
||||
|
||||
<template v-if="isEditing">
|
||||
<div v-if="editMode && editModeOptions?.zoom?.selected" class="sub-panel-content">
|
||||
<div class="edit-action-area-header">
|
||||
<span class="text-primary">Editing options for:</span> <b>{{editModeOptions?.zoom?.selected?.label}}</b>
|
||||
<template v-if="editModeOptions?.zoom?.selectedIndex === null && editModeOptions?.zoom?.selected?.label !== 'New aspect ratio'">(New ratio)</template>
|
||||
</div>
|
||||
<div class="edit-action-area">
|
||||
<!-- Some options are only shown for type 4 (fixed) zooms -->
|
||||
<template v-if="editModeOptions?.zoom?.selected?.arguments?.type === AspectRatioType.Fixed">
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Ratio:
|
||||
</div>
|
||||
<div class="input">
|
||||
<!-- We do an ugly in order to avoid spamming functions down at the bottom -->
|
||||
<input
|
||||
v-model="editModeOptions.zoom.selected.arguments.ratio"
|
||||
@blur="updateLabel('zoom')"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="editModeOptions.zoom?.error" class="hint error">
|
||||
{{editModeOptions.zoom.error}}
|
||||
</div>
|
||||
<div class="hint">
|
||||
You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor
|
||||
(in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter
|
||||
your numbers without quote marks. Number will be converted to factor form on save.
|
||||
</div>
|
||||
|
||||
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Label:
|
||||
</div>
|
||||
<div class="input">
|
||||
<input v-model="editModeOptions.zoom.selected.label">
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
Label for the button. You can make it say something other than ratio.
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- editing keyboard shortcuts is always allowed -->
|
||||
<div class="field">
|
||||
<div class="label">Shortcut:</div>
|
||||
<div class="">
|
||||
<EditShortcutButton
|
||||
:shortcut="editModeOptions?.zoom?.selected?.shortcut"
|
||||
@shortcutChanged="updateSelectedShortcut($event, 'zoom')"
|
||||
>
|
||||
</EditShortcutButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
<b>Note:</b> Your browser and OS already use certain key combinations that involve Ctrl and Meta (Windows) keys — and, to a lesser extent, Alt.
|
||||
The extension doesn't (and cannot) check whether the keyboard shortcut you enter is actually free for you to use. The extension also won't override
|
||||
any keyboard shortcuts defined by the site itself.
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row flex-end">
|
||||
<div
|
||||
v-if="editModeOptions?.zoom?.selected?.arguments?.type === AspectRatioType.Fixed && editModeOptions?.zoom?.selectedIndex !== null"
|
||||
class="button"
|
||||
@click="deleteAction('zoom')"
|
||||
>
|
||||
<mdicon name="delete"></mdicon> Delete
|
||||
</div>
|
||||
<div class="flex-grow"></div>
|
||||
<div class="button" @click="cancelEdit('zoom')">Cancel</div>
|
||||
<div class="button" @click="saveShortcut('zoom')">
|
||||
<mdicon name="floppy"></mdicon>
|
||||
|
||||
<template v-if="editModeOptions?.zoom?.selectedIndex === null">Add</template>
|
||||
<template v-else>Save</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<!--
|
||||
min, max and value need to be implemented in js as this slider
|
||||
should use logarithmic scale
|
||||
-->
|
||||
<div class="flex flex-row w-full" style="margin-top: 0.66rem">
|
||||
<div style="position:relative;" class="grow">
|
||||
<template v-if="zoomAspectRatioLocked">
|
||||
<div class="slider-label">
|
||||
Zoom: {{getZoomForDisplay('x')}}
|
||||
</div>
|
||||
<input id="_input_zoom_slider"
|
||||
class="input-slider"
|
||||
type="range"
|
||||
step="any"
|
||||
min="-1"
|
||||
max="3"
|
||||
:value="zoom.x"
|
||||
@input="changeZoom($event.target.value)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="slider-label">Horizontal zoom: {{getZoomForDisplay('x')}}</div>
|
||||
<input id="_input_zoom_slider"
|
||||
class="input-slider"
|
||||
type="range"
|
||||
step="any"
|
||||
min="-1"
|
||||
max="4"
|
||||
:value="zoom.x"
|
||||
@input="changeZoom($event.target.value, 'x')"
|
||||
/>
|
||||
|
||||
<div class="slider-label">Vertical zoom: {{getZoomForDisplay('y')}}</div>
|
||||
<input id="_input_zoom_slider_2"
|
||||
class="input-slider"
|
||||
type="range"
|
||||
step="any"
|
||||
min="-1"
|
||||
max="3"
|
||||
:value="zoom.y"
|
||||
@input="changeZoom($event.target.value, 'y')"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center justify-center" style="padding-left: 1rem">
|
||||
<Button
|
||||
v-if="zoomAspectRatioLocked"
|
||||
icon="lock"
|
||||
:iconSize="16"
|
||||
:fixedWidth="true"
|
||||
:noPad="true"
|
||||
@click="toggleZoomAr()"
|
||||
>
|
||||
</Button>
|
||||
<Button
|
||||
v-else
|
||||
icon="lock-open-variant"
|
||||
:iconSize="16"
|
||||
:fixedWidth="true"
|
||||
:noPad="true"
|
||||
@click="toggleZoomAr()"
|
||||
>
|
||||
</Button>
|
||||
<Button
|
||||
icon="restore"
|
||||
:iconSize="16"
|
||||
:noPad="true"
|
||||
@click="resetZoom()"
|
||||
></Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Button from '@csui/src/components/Button.vue';
|
||||
import ShortcutButton from '@csui/src/components/ShortcutButton.vue';
|
||||
import EditShortcutButton from '@csui/src/components/EditShortcutButton';
|
||||
import EditModeMixin from '@csui/src/utils/EditModeMixin';
|
||||
import KeyboardShortcutParserMixin from '@csui/src/utils/KeyboardShortcutParserMixin';
|
||||
import CommsMixin from '@csui/src/utils/CommsMixin';
|
||||
import AspectRatioType from '@src/common/enums/AspectRatioType.enum';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Button,
|
||||
ShortcutButton,
|
||||
EditShortcutButton,
|
||||
},
|
||||
mixins: [
|
||||
// ComputeActionsMixin,
|
||||
EditModeMixin,
|
||||
KeyboardShortcutParserMixin,
|
||||
CommsMixin
|
||||
],
|
||||
props: [
|
||||
'settings', // required for buttons and actions, which are global
|
||||
'siteSettings',
|
||||
'eventBus',
|
||||
'isEditing',
|
||||
'compact',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
AspectRatioType,
|
||||
|
||||
zoomAspectRatioLocked: true,
|
||||
zoom: {
|
||||
x: 0,
|
||||
y: 0
|
||||
},
|
||||
|
||||
// TODO: this should be mixin?
|
||||
resizerConfig: {
|
||||
crop: null,
|
||||
stretch: null,
|
||||
zoom: null,
|
||||
pan: null
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.isEditing) {
|
||||
this.enableEditMode();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isEditing(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.enableEditMode();
|
||||
} else {
|
||||
this.disableEditMode();
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getZoomForDisplay(axis) {
|
||||
// zoom is internally handled logarithmically, because we want to have x0.5, x1, x2, x4 ... magnifications
|
||||
// spaced out at regular intervals. When displaying, we need to convert that to non-logarithmic values.
|
||||
return `${(Math.pow(2, this.zoom[axis]) * 100).toFixed()}%`
|
||||
},
|
||||
toggleZoomAr() {
|
||||
this.zoomAspectRatioLocked = !this.zoomAspectRatioLocked;
|
||||
},
|
||||
|
||||
resetZoom() {
|
||||
// we store zoom logarithmically on this component
|
||||
this.zoom = {x: 0, y: 0};
|
||||
|
||||
// we do not use logarithmic zoom elsewhere
|
||||
// todo: replace eventBus with postMessage to parent
|
||||
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'y'});
|
||||
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'x'});
|
||||
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: 1});
|
||||
},
|
||||
changeZoom(newZoom, axis) {
|
||||
// we store zoom logarithmically on this compnent
|
||||
if (!axis) {
|
||||
this.zoom.x = newZoom;
|
||||
} else {
|
||||
this.zoom[axis] = newZoom;
|
||||
}
|
||||
|
||||
// we do not use logarithmic zoom elsewhere, therefore we need to convert
|
||||
newZoom = Math.pow(2, newZoom);
|
||||
|
||||
if (this.zoomAspectRatioLocked) {
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom});
|
||||
} else {
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: {[axis ?? 'x']: newZoom}});
|
||||
}
|
||||
},
|
||||
isActiveZoom(command) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</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>
|
||||
.input-slider {
|
||||
width: 100%;
|
||||
box-sizing:border-box;
|
||||
margin-right: 1rem;
|
||||
margin-left: 0rem;
|
||||
}
|
||||
.slider-label {
|
||||
margin-bottom: -0.5rem;
|
||||
color: #aaa;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
||||
@ -1,630 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex flex-row">
|
||||
<h1>Video player options</h1>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<div v-if="tutorialVisible" class="w-full">
|
||||
<button
|
||||
class="info-button"
|
||||
@click="tutorialVisible = false"
|
||||
>
|
||||
<mdicon name="arrow-left"></mdicon>
|
||||
Back
|
||||
</button>
|
||||
|
||||
<div v-if="tutorialStep == 0" class="flex flex-col w-full justify-center items-center tutorial-step">
|
||||
<h3>1. Start hovering over elements on this list</h3>
|
||||
<div class="flex flex-row w-full flex-wrap tutorial-list">
|
||||
<div class="card">
|
||||
<div class="card-text">
|
||||
<p>This list contains all the elements on the webpage that could be a video player.</p>
|
||||
<p>Icons to the left of the list indicate which element is currently selected as the player element, which element the extension thinks to be the player element, and which element should be the player according to manual settings.</p>
|
||||
</div>
|
||||
<img src="/res/img/player-select-demo/uw_player___element-list-1.webp" />
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-text">
|
||||
<p>Move your mouse over the first element on the list, but do not click it.</p>
|
||||
<p>Hovering over elements on the list will highlight parts of the page.</p>
|
||||
</div>
|
||||
<img src="/res/img/player-select-demo/uw_player___element-list-hover.webp" />
|
||||
</div>
|
||||
</div>
|
||||
<button @click="tutorialStep = 1">Next</button>
|
||||
</div>
|
||||
|
||||
<div v-if="tutorialStep == 1" class="flex flex-col w-full justify-center items-center tutorial-step">
|
||||
<h3>2. Observe highlight</h3>
|
||||
|
||||
<div class="flex flex-row w-full flex-wrap tutorial-list">
|
||||
<div class="card">
|
||||
<div class="card-text">
|
||||
<p>Hovering over the elements will highlight part of the page. Highlighted area should cover the player area.</p>
|
||||
<p>If the highlighted area covers the player, click the item on the list to select it and reload the page.</p>
|
||||
<p>If more than one element covers the player area, select the first (topmost) one on the list.</p>
|
||||
</div>
|
||||
<img src="/res/img/player-select-demo/uw_player_select___just-right.webp" />
|
||||
|
||||
<div class="icon correct">
|
||||
<mdicon name="check-circle" size="96"></mdicon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-text">
|
||||
<p>If highlight covers more than just the player area, that usually means the correct element is further down the list.</p>
|
||||
<p>Move the mouse cursor down the list of elements, until you encounter the first element that covers the player area.</p>
|
||||
</div>
|
||||
<img src="/res/img/player-select-demo/uw_player_select___too_much.webp" />
|
||||
<div class="icon wrong">
|
||||
<mdicon name="close-circle" size="96"></mdicon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-text">
|
||||
<p>If highlight doesn't cover the whole player area, that usually means the correct element is further down the list.</p>
|
||||
<p>Move your cursor up the list of elements, until you encounter something that highlights the entire player area.</p>
|
||||
<p>If more than one element covers the player area, select the first (topmost) one on the list.</p>
|
||||
</div>
|
||||
<img src="/res/img/player-select-demo/uw_player_select___too_little.webp" />
|
||||
<div class="icon wrong">
|
||||
<mdicon name="close-circle" size="96"></mdicon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button @click="tutorialStep = 0">Previous</button>
|
||||
<button @click="tutorialVisible = false">Done</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div v-else class="w-full">
|
||||
<button
|
||||
class="info-button"
|
||||
@click="showTutorial()"
|
||||
>
|
||||
<mdicon name="help"></mdicon>
|
||||
How do I use this?
|
||||
</button>
|
||||
<div class="w-full flex flex-row" style="margin-top: 1rem;">
|
||||
|
||||
<!-- PLAYER ELEMENT SELECTOR FOR DUMMIES -->
|
||||
<div style="width: 48%">
|
||||
<div class="sub-panel-content">
|
||||
<h2>Simple player selector</h2>
|
||||
|
||||
|
||||
<div v-if="showAdvancedOptions" style="display: flex; flex-direction: row">
|
||||
<div style="display: flex; flex-direction: column">
|
||||
<div>
|
||||
<input :checked="playerManualQs"
|
||||
@change="togglePlayerManualQs"
|
||||
type="checkbox"
|
||||
/>
|
||||
<div>
|
||||
Use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors" target="_blank">CSS selector</a> for player<br/>
|
||||
<small>If defining multiple selectors, separate them with commas.</small>
|
||||
</div>
|
||||
</div>
|
||||
<div>Selector</div>
|
||||
<input type="text"
|
||||
v-model="playerQs"
|
||||
@change="updatePlayerQuerySelector"
|
||||
@blur="updatePlayerQuerySelector"
|
||||
:disabled="playerByNodeIndex || !playerManualQs"
|
||||
/>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: column">
|
||||
<b>Custom CSS for site</b>
|
||||
<textarea></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: row;">
|
||||
<div class="element-tree">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<div class="status-relative">
|
||||
Status <mdicon name="help-circle" @click="showLegend = !showLegend" />
|
||||
|
||||
<div v-if="showLegend" class="element-symbol-legend">
|
||||
<b>Symbols:</b><br />
|
||||
<mdicon name="alert-remove" class="invalid" /> Element of invalid dimensions<br />
|
||||
<mdicon name="refresh-auto" class="auto-match" /> Ultrawidify's player detection thinks this should be the player<br />
|
||||
<mdicon name="bookmark" class="parent-offset-match" /> Site settings say this should be the player (based on counting parents)<br />
|
||||
<mdicon name="crosshairs" class="qs-match" /> Site settings say this should be the player (based on query selectors)<br />
|
||||
<mdicon name="check-circle" class="activePlayer" /> Element that is actually the player
|
||||
</div>
|
||||
</div>
|
||||
</th>
|
||||
<th>Element</th>
|
||||
<!-- <th>Actions</th> -->
|
||||
<!-- <th>Quick fixes</th> -->
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(element, index) of elementStack"
|
||||
:key="index"
|
||||
class="element-row"
|
||||
>
|
||||
<td>
|
||||
<div class="status">
|
||||
<div
|
||||
v-if="element.heuristics?.invalidSize"
|
||||
class="invalid"
|
||||
>
|
||||
<mdicon name="alert-remove" />
|
||||
</div>
|
||||
<div
|
||||
v-if="element.heuristics?.autoMatch"
|
||||
class="auto-match"
|
||||
>
|
||||
<mdicon name="refresh-auto" />
|
||||
</div>
|
||||
<div
|
||||
v-if="element.heuristics?.qsMatch"
|
||||
class="qs-match"
|
||||
>
|
||||
<mdicon name="crosshairs" />
|
||||
</div>
|
||||
<div
|
||||
v-if="element.heuristics?.manualElementByParentIndex"
|
||||
class="parent-offset-match"
|
||||
>
|
||||
<mdicon name="bookmark" />
|
||||
</div>
|
||||
<div
|
||||
v-if="element.heuristics?.activePlayer"
|
||||
class="activePlayer"
|
||||
>
|
||||
<mdicon name="check-circle" />
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div
|
||||
class="element-data"
|
||||
|
||||
@mouseover="markElement(elementStack.length - index - 1, true)"
|
||||
@mouseleave="markElement(elementStack.length - index - 1, false)"
|
||||
|
||||
@click="setPlayer(elementStack.length - index - 1)"
|
||||
>
|
||||
<div class="tag">
|
||||
<b>{{element.tagName}}</b> <i class="id">{{element.id ? `#`:''}}{{element.id}}</i> @ <span class="dimensions">{{element.width}}</span>x<span class="dimensions">{{element.height}}</span>
|
||||
|
||||
</div>
|
||||
<div v-if="element.classList" class="class-list">
|
||||
<div v-for="cls of element.classList" :key="cls">
|
||||
{{cls}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex flex-row">
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="element-config">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- <div class="css-preview">
|
||||
{{cssStack}}
|
||||
</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ADVANCED OPTIONS -->
|
||||
<div style="width: 48%" v-if="false">
|
||||
<h2>Advanced options</h2>
|
||||
|
||||
<pre>{{siteSettings.raw}}</pre>
|
||||
|
||||
<div class="">
|
||||
<h3>Player element</h3>
|
||||
<div class="field">
|
||||
<div class="label">Manually specify player</div>
|
||||
<input type="checkbox" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="label">Select player by/as:</div>
|
||||
<div class="select">
|
||||
<select>
|
||||
<option>n-th parent of video</option>
|
||||
<option>Query selectors</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="label">Player is n-th parent of video</div>
|
||||
<div class="range-input">
|
||||
<input type="range" min="0" max="100" />
|
||||
<input />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="label">Query selector for player element</div>
|
||||
<div class="input">
|
||||
<input />
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="label">In full screen, calculate crop based on monitor resolution</div>
|
||||
<input type="checkbox" />
|
||||
</div>
|
||||
|
||||
<h3>Video element</h3>
|
||||
<div class="field">
|
||||
<div class="label">Select video element automatically</div>
|
||||
<input type="checkbox">
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="label">Query selectors</div>
|
||||
<div class="input">
|
||||
<input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="label">Additional styles for video element</div>
|
||||
<div class="input">
|
||||
<textarea></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Additional css for this page</h3>
|
||||
<div class="field">
|
||||
<div class="label">Additional CSS for this page</div>
|
||||
<textarea></textarea>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default({
|
||||
components: {
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
elementStack: [],
|
||||
cssStack: [],
|
||||
showLegend: false,
|
||||
showAdvancedOptions: false,
|
||||
tutorialVisible: false,
|
||||
tutorialStep: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
mixins: [],
|
||||
props: [
|
||||
'siteSettings',
|
||||
'frame',
|
||||
'eventBus',
|
||||
'site',
|
||||
'isPopup'
|
||||
],
|
||||
created() {
|
||||
this.eventBus.subscribe(
|
||||
'uw-config-broadcast',
|
||||
{
|
||||
source: this,
|
||||
function: (config) => this.handleElementStack(config)
|
||||
}
|
||||
);
|
||||
},
|
||||
mounted() {
|
||||
this.getPlayerTree();
|
||||
},
|
||||
destroyed() {
|
||||
this.eventBus.unsubscribeAll(this);
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
showTutorial() {
|
||||
this.tutorialVisible = true;
|
||||
this.tutorialStep = 0;
|
||||
},
|
||||
getPlayerTree() {
|
||||
if (this.isPopup) {
|
||||
this.eventBus.send('get-player-tree');
|
||||
} else {
|
||||
this.eventBus.sendToTunnel('get-player-tree');
|
||||
}
|
||||
},
|
||||
handleElementStack(configBroadcast) {
|
||||
if (configBroadcast.type === 'player-tree') {
|
||||
this.elementStack = configBroadcast.config.reverse();
|
||||
this.$nextTick( () => this.$forceUpdate() );
|
||||
}
|
||||
},
|
||||
markElement(parentIndex, enable) {
|
||||
this.eventBus.sendToTunnel('set-mark-element', {parentIndex, enable});
|
||||
},
|
||||
async setPlayer(index) {
|
||||
// yup.
|
||||
this.siteSettings.getDOMConfig('modified', 'original');
|
||||
await this.siteSettings.setUpdateFlags(['PlayerData']);
|
||||
await this.siteSettings.set('DOMConfig.modified.type', 'modified', {noSave: true});
|
||||
await this.siteSettings.set('activeDOMConfig', 'modified', {noSave: true});
|
||||
|
||||
// if user agrees with ultrawidify on what element player should be,
|
||||
// we just unset our settings for this site
|
||||
if (this.elementStack[index].heuristics?.autoMatch) {
|
||||
await this.siteSettings.set('DOMConfig.modified.elements.player.manual', false);
|
||||
this.getPlayerTree();
|
||||
} else {
|
||||
// ensure settings exist:
|
||||
await this.siteSettings.set('DOMConfig.modified.elements.player.manual', true, {noSave: true});
|
||||
await this.siteSettings.set('DOMConfig.modified.elements.player.mode', 'index', {noSave: true});
|
||||
await this.siteSettings.set('DOMConfig.modified.elements.player.index', index, true);
|
||||
this.getPlayerTree();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Toggles active CSS for element of certain parent index.
|
||||
* cssValue is optional and can be included in cssRule argument
|
||||
*/
|
||||
toggleCssForElement(index, cssRule, cssValue) {
|
||||
// we will handle elements that put cssValue as a separate argument elsewhere
|
||||
if (cssValue) {
|
||||
return this.toggleCssForElement_3arg(index,cssRule, cssValue);
|
||||
}
|
||||
|
||||
// this rule applies to current element — remove it!
|
||||
if (this.cssStack[index]?.includes(cssRule)) {
|
||||
this.cssStack[index] = this.cssStack[index].filter(x => ! x.includes(cssRule));
|
||||
} else {
|
||||
if (!this.cssStack[index]) {
|
||||
this.cssStack[index] = [];
|
||||
}
|
||||
this.cssStack[index].push(cssRule)
|
||||
}
|
||||
|
||||
//TODO: update settings!
|
||||
},
|
||||
toggleCssForElement_3arg(index, cssRule, cssValue) {
|
||||
const matching = this.cssStack[index]?.find(x => x.includes(cssRule))
|
||||
if (matching) {
|
||||
this.cssStack[index] = this.cssStack[index].filter(x => ! x.includes(cssRule));
|
||||
if (!matching.includes(cssValue)) {
|
||||
this.cssStack[index].push(`${cssRule}: ${cssValue};`);
|
||||
}
|
||||
} else {
|
||||
if (!this.cssStack[index]) {
|
||||
this.cssStack[index] = [];
|
||||
}
|
||||
this.cssStack[index].push(`${cssRule}: ${cssValue};`);
|
||||
}
|
||||
|
||||
//TODO: update settings!
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
p {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.info-button {
|
||||
color: $info-color;
|
||||
border: 1px solid $info-color;
|
||||
padding: 0.5rem 2rem;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba($info-color, 0.25);
|
||||
color: #eee;
|
||||
border: 1px solid $info-color !important;
|
||||
}
|
||||
}
|
||||
|
||||
.tutorial-step {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
h3 {
|
||||
color: #fa6;
|
||||
text-transform: uppercase;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.tutorial-list {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 3rem;
|
||||
|
||||
.card {
|
||||
position: relative;
|
||||
|
||||
margin: 0.5rem;
|
||||
padding: 1rem;
|
||||
|
||||
max-width: 32rem;
|
||||
|
||||
border: 1px solid #333;
|
||||
border-radius: 0.5rem;
|
||||
background-color: #121110dd;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.card-text {
|
||||
color: #aaa;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
padding: 0rem;
|
||||
transform: translate(-50%, 50%);
|
||||
|
||||
background-color: #000;
|
||||
border-radius: 50%;
|
||||
|
||||
&.wrong {
|
||||
color: #dc3c14;
|
||||
}
|
||||
&.correct {
|
||||
color: rgb(102, 241, 218);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
color: #000 !important;
|
||||
background-color: #fa6 !important;
|
||||
border-radius: 0.5rem !important;
|
||||
|
||||
&:hover {
|
||||
border: 1px solid #fa6 !important;
|
||||
color: #fff !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.element-tree {
|
||||
.element-row {
|
||||
margin: 0.5rem;
|
||||
|
||||
.status {
|
||||
width: 6.9rem;
|
||||
margin-right: 1rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
||||
.invalid {
|
||||
color: #f00;
|
||||
}
|
||||
}
|
||||
.element-data {
|
||||
border: 1px solid rgba(255,255,255,0.5);
|
||||
padding: 0.5rem 1rem;
|
||||
|
||||
max-width: 30rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.tag {
|
||||
text-transform: lowercase;
|
||||
font-weight: bold;
|
||||
}
|
||||
.id {
|
||||
font-style: italic;
|
||||
}
|
||||
.class-list {
|
||||
font-style: italic;
|
||||
opacity: 0.75;
|
||||
font-size: 0.75rem;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
> div {
|
||||
background-color: rgba(255,255,255,0.5);
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.125rem 0.5rem;
|
||||
margin: 0.125rem 0.25rem;
|
||||
}
|
||||
}
|
||||
.dimensions {
|
||||
color: #473c85;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.css-line {
|
||||
display: inline-block;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
border: 1px solid rgba(255,255,255,0.5);
|
||||
background: #000;
|
||||
margin: 0.125rem 0.25rem;
|
||||
padding: 0.125rem 0.25rem;
|
||||
|
||||
&.active, >span.active {
|
||||
background: rgb(255, 174, 107);
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
.status-relative {
|
||||
position: relative;
|
||||
|
||||
.element-symbol-legend {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
|
||||
z-index: 20000;
|
||||
|
||||
width: 32rem;
|
||||
|
||||
text-align: left;
|
||||
|
||||
background-color: #000;
|
||||
padding: 1rem;
|
||||
border: 1px solid rgba(255,255,255,0.5);
|
||||
}
|
||||
}
|
||||
|
||||
.tab-root {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.demo-images {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
padding-top: 2rem;
|
||||
|
||||
.fig1, .fig2 {
|
||||
margin-top: -2rem;
|
||||
max-height: 18rem;
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
.fig1 {
|
||||
align-self: start;
|
||||
}
|
||||
.fig2 {
|
||||
align-self: end;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 32rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,296 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col" style="position: relative; width: 100%;">
|
||||
<!-- The rest of the tab is under 'edit ratios and shortcuts' row -->
|
||||
<div class="flex flex-col" style="width: 100%">
|
||||
<h2>Player UI options</h2>
|
||||
|
||||
<div class="flex flex-col compact-form">
|
||||
<div v-if="!siteSettings.data.enableUI.fullscreen">
|
||||
UI is disabled for this site.
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col field-group compact-form"
|
||||
:class="{disabled: !siteSettings.data.enableUI.fullscreen}"
|
||||
>
|
||||
<div class="field disabled">
|
||||
<div class="label">
|
||||
Popup activator position:
|
||||
</div>
|
||||
<div class="select">
|
||||
<select
|
||||
v-model="settings.active.ui.inPlayer.popupAlignment"
|
||||
@change="saveSettings()"
|
||||
>
|
||||
<option value="left">Left</option>
|
||||
<option value="right">Right</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Activate in-player UI:
|
||||
</div>
|
||||
<div class="select">
|
||||
<select
|
||||
v-model="settings.active.ui.inPlayer.activation"
|
||||
@change="saveSettings()"
|
||||
>
|
||||
<option value="player">
|
||||
When mouse hovers over player
|
||||
</option>
|
||||
<option value="trigger-zone">
|
||||
When mouse hovers over trigger zone
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field" :class="{'disabled': settings.active.ui.inPlayer.activation !== 'trigger-zone'}">
|
||||
<div class="label">Edit trigger zone:</div>
|
||||
<button @click="startTriggerZoneEdit()">Edit</button>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Do not show in-player UI when video player is narrower than
|
||||
</div>
|
||||
<div class="input range-input">
|
||||
<input
|
||||
:value="settings.active.ui.inPlayer.minEnabledWidth"
|
||||
class="slider"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
@input="(event) => setPlayerRestrictions('minEnabledWidth', event.target.value)"
|
||||
@change="(event) => saveSettings()"
|
||||
>
|
||||
<input
|
||||
style="margin-right: 0.6rem;"
|
||||
:value="ghettoComputed.minEnabledWidth"
|
||||
@input="(event) => setPlayerRestrictions('minEnabledWidth', event.target.value, true)"
|
||||
@change="(event) => saveSettings(true)"
|
||||
>
|
||||
<div class="unit">% of screen</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Do not show in-player UI when video player is shorter than
|
||||
</div>
|
||||
<div class="input range-input">
|
||||
<input
|
||||
:value="settings.active.ui.inPlayer.minEnabledHeight"
|
||||
class="slider"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
@input="(event) => setPlayerRestrictions('minEnabledHeight', event.target.value)"
|
||||
@change="(event) => saveSettings()"
|
||||
>
|
||||
<input
|
||||
style="margin-right: 0.6rem;"
|
||||
:value="ghettoComputed.minEnabledHeight"
|
||||
@input="(event) => setPlayerRestrictions('minEnabledHeight', event.target.value, true)"
|
||||
@change="(event) => saveSettings(true)"
|
||||
>
|
||||
<div class="unit">% of screen</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="mt2r">Menu options and keyboard shortcuts</h2>
|
||||
<div>
|
||||
Click 'add new' to add a new option. Click a button to edit or remove the keyboard shortcut.
|
||||
</div>
|
||||
<div class="keyboard-settings">
|
||||
<!-- CROP OPTIONS -->
|
||||
<div>
|
||||
<div class="flex flex-row">
|
||||
<h3 class="mth3">CROP OPTIONS</h3>
|
||||
</div>
|
||||
|
||||
<CropOptionsPanel
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
:isEditing="true"
|
||||
>
|
||||
</CropOptionsPanel>
|
||||
</div>
|
||||
|
||||
<!-- STRETCH OPTIONS -->
|
||||
<div>
|
||||
<div class="flex flex-row">
|
||||
<h3 class="mth3">STRETCH OPTIONS</h3>
|
||||
</div>
|
||||
|
||||
<StretchOptionsPanel
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
:isEditing="true"
|
||||
></StretchOptionsPanel>
|
||||
</div>
|
||||
|
||||
<!-- ZOOM OPTIONS -->
|
||||
<div>
|
||||
<div class="flex flex-row">
|
||||
<h3 class="mth3">ZOOM OPTIONS</h3>
|
||||
</div>
|
||||
|
||||
<ZoomOptionsPanel
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
:isEditing="true"
|
||||
></ZoomOptionsPanel>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Button from '@csui/src/components/Button.vue'
|
||||
import BrowserDetect from '@src/ext/conf/BrowserDetect';
|
||||
import CropOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/CropOptionsPanel.vue'
|
||||
import StretchOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/StretchOptionsPanel.vue'
|
||||
import ZoomOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/ZoomOptionsPanel.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Button,
|
||||
CropOptionsPanel,
|
||||
StretchOptionsPanel,
|
||||
ZoomOptionsPanel,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ghettoComputed: { }
|
||||
}
|
||||
},
|
||||
mixins: [
|
||||
],
|
||||
props: [
|
||||
'settings', // required for buttons and actions, which are global
|
||||
'siteSettings',
|
||||
'eventBus',
|
||||
],
|
||||
mounted() {
|
||||
this.ghettoComputed = {
|
||||
minEnabledWidth: this.optionalToFixed(this.settings.active.ui.inPlayer.minEnabledWidth * 100, 0),
|
||||
minEnabledHeight: this.optionalToFixed(this.settings.active.ui.inPlayer.minEnabledHeight * 100, 0),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
forcePositiveNumber(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, '');
|
||||
},
|
||||
optionalToFixed(v, n) {
|
||||
if ((`${v}`.split('.')[1]?.length ?? 0) > n) {
|
||||
return v.toFixed(n);
|
||||
}
|
||||
return v;
|
||||
},
|
||||
setPlayerRestrictions(key, value, isTextInput) {
|
||||
if (isTextInput) {
|
||||
value = (+this.forcePositiveNumber(value) / 100);
|
||||
}
|
||||
if (isNaN(+value)) {
|
||||
value = 0.5;
|
||||
}
|
||||
|
||||
this.settings.active.ui.inPlayer[key] = value;
|
||||
|
||||
if (isTextInput) {
|
||||
this.ghettoComputed[key] = this.optionalToFixed(value, 0);
|
||||
} else {
|
||||
this.ghettoComputed[key] = this.optionalToFixed(value * 100, 0);
|
||||
}
|
||||
},
|
||||
saveSettings(forceRefresh) {
|
||||
this.settings.saveWithoutReload();
|
||||
|
||||
if (forceRefresh) {
|
||||
this.$nextTick( () => this.$forceRefresh() );
|
||||
}
|
||||
},
|
||||
startTriggerZoneEdit() {
|
||||
this.eventBus.send('start-trigger-zone-edit');
|
||||
},
|
||||
async openOptionsPage() {
|
||||
BrowserDetect.runtime.openOptionsPage();
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</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>
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
.mt-4{
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.input {
|
||||
max-width: 24rem;
|
||||
}
|
||||
|
||||
.trigger-zone-editor {
|
||||
background-color: rgba(0,0,0,0.25);
|
||||
|
||||
padding-bottom: 2rem;
|
||||
.field {
|
||||
margin-bottom: -1em;
|
||||
}
|
||||
}
|
||||
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
/* color: #666; */
|
||||
filter: contrast(50%) brightness(40%) grayscale(100%);
|
||||
}
|
||||
|
||||
.compact-form {
|
||||
> .field, > .field-group {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.keyboard-settings {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
|
||||
> * {
|
||||
width: calc(33% - 0.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
.mt2r {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.mth3 {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
</style>
|
||||
@ -1,30 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<h1>Reset and backup</h1>
|
||||
<p>
|
||||
Pressing the button will reset settings to default without asking.
|
||||
</p>
|
||||
<button
|
||||
class="danger"
|
||||
@click="resetSettings"
|
||||
>
|
||||
Reset settings
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
settings: Object
|
||||
},
|
||||
methods: {
|
||||
resetSettings() {
|
||||
this.settings.active = JSON.parse(JSON.stringify(this.settings.default));
|
||||
this.settings.saveWithoutReload();
|
||||
}
|
||||
}
|
||||
}
|
||||
</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,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,159 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-col" style="position: relative; width: 100%;">
|
||||
<!-- 'Change UI' options is a tiny bit in upper right corner. -->
|
||||
<div
|
||||
class="options-bar flex flex-row"
|
||||
:class="{isEditing: editMode}"
|
||||
>
|
||||
<template v-if="editMode">
|
||||
<div style="height: 100%; display: flex; flex-direction: column; justify-content: center; flex: 0 0; padding-right: 8px;">
|
||||
<mdicon name="alert" size="32" />
|
||||
</div>
|
||||
<div class="flex-grow">
|
||||
You are currently editing options and shortcuts.<br/>
|
||||
<b>NOTE: changes will take effect after page reload.</b>
|
||||
</div>
|
||||
<div
|
||||
class="flex-nogrow flex-noshrink"
|
||||
@click="editMode = !editMode"
|
||||
>
|
||||
Exit edit mode
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="flex-grow"></div>
|
||||
<div
|
||||
class=""
|
||||
@click="editMode = !editMode"
|
||||
>
|
||||
Edit ratios and shortcuts
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- The rest of the tab is under 'edit ratios and shortcuts' row -->
|
||||
<div class="flex flex-row flex-wrap" style="width: 100%">
|
||||
|
||||
<div class="flex flex-col">
|
||||
<!-- CROP OPTIONS -->
|
||||
<div v-if="settings" class="sub-panel">
|
||||
<div class="flex flex-row">
|
||||
<mdicon name="crop" :size="16" />
|
||||
<h1>Crop video:</h1>
|
||||
</div>
|
||||
|
||||
<CropOptionsPanel
|
||||
:settings="settings"
|
||||
:siteSettings="siteSettings"
|
||||
:eventBus="eventBus"
|
||||
:isEditing="editMode"
|
||||
>
|
||||
</CropOptionsPanel>
|
||||
</div>
|
||||
|
||||
<!-- STRETCH OPTIONS -->
|
||||
<div v-if="settings" class="sub-panel">
|
||||
<div class="flex flex-row">
|
||||
<mdicon name="stretch-to-page-outline" :size="32" />
|
||||
<h1>Stretch video:</h1>
|
||||
</div>
|
||||
|
||||
<StretchOptionsPanel
|
||||
:settings="settings"
|
||||
:siteSettings="siteSettings"
|
||||
:eventBus="eventBus"
|
||||
:isEditing="editMode"
|
||||
></StretchOptionsPanel>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ZoomOptionsPanel from './PanelComponents/VideoSettings/ZoomOptionsPanel.vue'
|
||||
import CropOptionsPanel from './PanelComponents/VideoSettings/CropOptionsPanel'
|
||||
import StretchOptionsPanel from './PanelComponents/VideoSettings/StretchOptionsPanel'
|
||||
import Button from '../components/Button.vue'
|
||||
import ShortcutButton from '../components/ShortcutButton';
|
||||
import EditShortcutButton from '../components/EditShortcutButton';
|
||||
import ComputeActionsMixin from '../mixins/ComputeActionsMixin';
|
||||
import BrowserDetect from '../../../ext/conf/BrowserDetect';
|
||||
import AlignmentOptionsControlComponent from './AlignmentOptionsControlComponent.vue';
|
||||
import CommsMixin from '../utils/CommsMixin';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
exec: null,
|
||||
scope: 'page',
|
||||
|
||||
editMode: false,
|
||||
|
||||
resizerConfig: {
|
||||
crop: null,
|
||||
stretch: null,
|
||||
zoom: null,
|
||||
pan: null
|
||||
}
|
||||
}
|
||||
},
|
||||
mixins: [
|
||||
ComputeActionsMixin,
|
||||
CommsMixin,
|
||||
],
|
||||
props: [
|
||||
'settings', // required for buttons and actions, which are global
|
||||
'siteSettings',
|
||||
'frame',
|
||||
'eventBus',
|
||||
'site'
|
||||
],
|
||||
created() {
|
||||
this.eventBus.subscribe(
|
||||
'uw-config-broadcast',
|
||||
{
|
||||
source: this,
|
||||
function: (config) => this.handleConfigBroadcast(config)
|
||||
}
|
||||
);
|
||||
},
|
||||
mounted() {
|
||||
this.eventBus.sendToTunnel('get-ar');
|
||||
},
|
||||
destroyed() {
|
||||
this.eventBus.unsubscribeAll(this);
|
||||
},
|
||||
components: {
|
||||
ShortcutButton,
|
||||
EditShortcutButton,
|
||||
Button,
|
||||
AlignmentOptionsControlComponent,
|
||||
StretchOptionsPanel,
|
||||
CropOptionsPanel, ZoomOptionsPanel
|
||||
},
|
||||
methods: {
|
||||
|
||||
async openOptionsPage() {
|
||||
BrowserDetect.runtime.openOptionsPage();
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</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>
|
||||
.justify-center {
|
||||
justify-content: center;
|
||||
}
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
.mt-4{
|
||||
margin-top: 1rem;
|
||||
}
|
||||
</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,247 +0,0 @@
|
||||
<template>
|
||||
<div class="action">
|
||||
<div class="flex-row action-name-cmd-container">
|
||||
<div class="">
|
||||
|
||||
</div>
|
||||
<div class="flex action-name">
|
||||
<span v-if="action.cmd && action.cmd.length > 1 || action.cmd[0].action === 'set-ar' && action.userAdded || (action.cmd[0].arg === AspectRatioType.Fixed)" class="icon red" @click="removeAction()">🗙</span>
|
||||
<span v-else class="icon transparent">🗙</span>
|
||||
<span class="icon" @click="editAction()">🖉</span>
|
||||
{{action.name}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="command-details">
|
||||
<div class="flex flex-col cmd-container cd-pad">
|
||||
<div class="flex bold">
|
||||
Command:
|
||||
</div>
|
||||
<div class="flex cmdlist">
|
||||
{{parseCommand(action.cmd)}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SCOPES -->
|
||||
<div class="flex flex-col scopes-container cd-pad">
|
||||
<div class="flex bold">Popup tabs:</div>
|
||||
|
||||
<!-- global scope -->
|
||||
<div v-if="action.scopes.global"
|
||||
class="flex flex-row scope-row"
|
||||
>
|
||||
<div class="flex scope-scope">
|
||||
Global:
|
||||
</div>
|
||||
<div class="flex scope-row-label scope-visible">
|
||||
Visible? <span class="scope-row-highlight">{{action.scopes.global.show ? 'Yes' : 'No'}}</span>
|
||||
</div>
|
||||
<div v-if="action.scopes.global.show"
|
||||
class="flex scope-row-label scope-button-label"
|
||||
>
|
||||
Button label?
|
||||
<span class="scope-row-highlight">
|
||||
{{action.scopes.global.label ? action.scopes.global.label : (action.label ? action.label : '')}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex scope-row-label">
|
||||
Keyboard shortcut:
|
||||
<span class="scope-row-highlight">
|
||||
{{action.scopes.global.shortcut ? parseActionShortcut(action.scopes.global.shortcut[0]) : 'None'}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- site scope -->
|
||||
<div v-if="action.scopes.site"
|
||||
class="flex flex-row scope-row"
|
||||
>
|
||||
<div class="flex scope-scope">
|
||||
Site:
|
||||
</div>
|
||||
<div class="flex scope-row-label scope-visible">
|
||||
Visible? <span class="scope-row-highlight">{{action.scopes.site.show ? 'Yes' : 'No'}}</span>
|
||||
</div>
|
||||
<div v-if="action.scopes.site.show"
|
||||
class="flex scope-row-label scope-button-label"
|
||||
>
|
||||
Button label?
|
||||
<span class="scope-row-highlight">
|
||||
{{action.scopes.site.label ? action.scopes.site.label : (action.label ? action.label : '')}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex scope-row-label">
|
||||
Keyboard shortcut:
|
||||
<span class="scope-row-highlight">
|
||||
{{action.scopes.site.shortcut ? parseActionShortcut(action.scopes.site.shortcut[0]) : 'None'}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- page scope -->
|
||||
<div v-if="action.scopes.page"
|
||||
class="flex flex-row scope-row"
|
||||
>
|
||||
<div class="flex scope-scope">
|
||||
Page:
|
||||
</div>
|
||||
<div class="flex scope-row-label scope-visible">
|
||||
Visible? <span class="scope-row-highlight">{{action.scopes.page.show ? 'Yes' : 'No'}}</span>
|
||||
</div>
|
||||
<div v-if="action.scopes.page.show"
|
||||
class="flex scope-row-label scope-button-label"
|
||||
>
|
||||
Button label?
|
||||
<span class="scope-row-highlight">
|
||||
{{action.scopes.page.label ? action.scopes.page.label : (action.label ? action.label : '')}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex scope-row-label">
|
||||
Keyboard shortcut:
|
||||
<span class="scope-row-highlight">
|
||||
{{action.scopes.page.shortcut ? parseActionShortcut(action.scopes.page.shortcut[0]) : 'None'}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StretchType from '../enums/StretchType.enum';
|
||||
import AspectRatioType from '../enums/AspectRatioType.enum';
|
||||
import KeyboardShortcutParser from '../js/KeyboardShortcutParser';
|
||||
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
StretchType: StretchType,
|
||||
AspectRatioType: AspectRatioType,
|
||||
}
|
||||
},
|
||||
created () {
|
||||
},
|
||||
props: {
|
||||
action: Object,
|
||||
index: Number,
|
||||
},
|
||||
methods: {
|
||||
parseActionShortcut(shortcut) {
|
||||
return KeyboardShortcutParser.parseShortcut(shortcut);
|
||||
},
|
||||
parseCommand(cmd) {
|
||||
let cmdstring = '';
|
||||
for (const c of cmd) {
|
||||
cmdstring += `${c.action} ${c.arg}${c.persistent ? ' persistent' : ''}; `;
|
||||
}
|
||||
return cmdstring;
|
||||
},
|
||||
editAction() {
|
||||
this.$emit('edit');
|
||||
},
|
||||
removeAction() {
|
||||
this.$emit('remove');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '../../res/css/colors.scss';
|
||||
|
||||
.action {
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.action .command-details {
|
||||
height: 0px;
|
||||
max-height: 0px;
|
||||
transition: max-height 0.5s ease;
|
||||
overflow: hidden;
|
||||
transition: height 0.5s ease;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.action:hover .command-details {
|
||||
height: auto;
|
||||
max-height: 200px;
|
||||
transition: max-height 0.5s ease;
|
||||
}
|
||||
|
||||
.command-details {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.action-name-cmd-container, .p1rem {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.cd-pad {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.action-name {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 300;
|
||||
color: $text-normal;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.action-name:hover, .action:hover .action-name {
|
||||
color: lighten($primary-color, 20%);
|
||||
}
|
||||
|
||||
.red {
|
||||
color: $primary-color !important;
|
||||
}
|
||||
|
||||
.cmd-container {
|
||||
width: 13.37rem;
|
||||
}
|
||||
|
||||
.cmdlist {
|
||||
font-family: 'Overpass Mono';
|
||||
font-size: 0.9rem;
|
||||
color: $text-dim;
|
||||
}
|
||||
|
||||
.bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.scope-scope {
|
||||
width: 5rem;
|
||||
text-align: right !important;
|
||||
color: $secondary-color;
|
||||
}
|
||||
|
||||
.scope-visible {
|
||||
width: 7rem;
|
||||
}
|
||||
|
||||
.scope-button-label {
|
||||
width: 16rem;
|
||||
}
|
||||
|
||||
.scope-row-label {
|
||||
color: $text-dark;
|
||||
}
|
||||
|
||||
.scope-row-highlight {
|
||||
color: $text-normal;
|
||||
}
|
||||
|
||||
.transparent {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
@ -1,76 +0,0 @@
|
||||
<template>
|
||||
<tr>
|
||||
<td class="cmd monospace">{{parseCommand(action.cmd)}}</td>
|
||||
<td class="">{{action.label ? action.label : ""}}</td>
|
||||
<td class="shortcut center-text">{{parseActionShortcut(action)}}</td>
|
||||
<td class="center-text">
|
||||
<div class="checkbox-container">
|
||||
<span class="checkbox"
|
||||
:class="{'checkbox-checked': (action.shortcut && action.shortcut.length &&
|
||||
(action.shortcut[0].onMouseMove || action.shortcut[0].onClick ||
|
||||
action.shortcut[0].onScroll))}"
|
||||
></span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="center-text">
|
||||
<div class="checkbox-container">
|
||||
<span class="checkbox"
|
||||
:class="{'checkbox-checked': action.popup_global}"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="center-text">
|
||||
<div class="checkbox-container">
|
||||
<span class="checkbox"
|
||||
:class="{'checkbox-checked': action.popup_site}"
|
||||
></span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="center-text">
|
||||
<div class="checkbox-container">
|
||||
<span class="checkbox"
|
||||
:class="{'checkbox-checked': action.popup}"
|
||||
></span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="center-text">
|
||||
<div class="checkbox-container">
|
||||
<span class="checkbox"
|
||||
:class="{'checkbox-checked': action.ui}"
|
||||
></span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{action.ui_path ? action.ui_path : ""}}</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StretchType from '../enums/StretchType.enum';
|
||||
import KeyboardShortcutParser from '../js/KeyboardShortcutParser'
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
StretchType: StretchType
|
||||
}
|
||||
},
|
||||
created () {
|
||||
},
|
||||
props: {
|
||||
action: Object
|
||||
},
|
||||
methods: {
|
||||
parseActionShortcut(action) {
|
||||
return KeyboardShortcutParser.parseShortcut(action.shortcut[0]);
|
||||
},
|
||||
parseCommand(cmd) {
|
||||
let cmdstring = '';
|
||||
for(const c of cmd) {
|
||||
cmdstring += `${c.action} ${c.arg}${c.persistent ? ' persistent' : ''}; `;
|
||||
}
|
||||
return cmdstring;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1,52 +0,0 @@
|
||||
<template>
|
||||
<div class="button center-text"
|
||||
:class="{
|
||||
'setting-selected': selected,
|
||||
'no-pad': noPad,
|
||||
}"
|
||||
>
|
||||
<mdicon v-if="icon" :name="icon" :size="iconSize ?? 24"></mdicon>
|
||||
<div class="label">
|
||||
{{label}}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
fixedWidth: Boolean,
|
||||
selected: Boolean,
|
||||
label: String,
|
||||
icon: String,
|
||||
iconSize: Number,
|
||||
noPad: Boolean
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: none;
|
||||
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
margin-right: 8px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.label {
|
||||
text-align: center;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
}
|
||||
.no-pad {
|
||||
padding: 0.5rem 1rem !important;
|
||||
}
|
||||
</style>
|
||||
@ -1,190 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-row">
|
||||
<div
|
||||
class="flex-grow button"
|
||||
@click="editShortcut()"
|
||||
|
||||
>
|
||||
<template v-if="!editing">
|
||||
{{shortcutDisplay}}
|
||||
</template>
|
||||
<template v-else>
|
||||
{{currentKeypress ?? 'Press a key'}}
|
||||
<input ref="input"
|
||||
class="hidden-input"
|
||||
@keyup.capture="keyup($event)"
|
||||
@keydown.capture="keydown($event)"
|
||||
@input.prevent=""
|
||||
@blur="editing = false;"
|
||||
>
|
||||
</template>
|
||||
</div>
|
||||
<div class="button" @click="$emit('shortcutChanged', null)">
|
||||
<mdicon name="delete"></mdicon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import KeyboardShortcutParser from '../../../common/js/KeyboardShortcutParser';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
shortcut: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentKeypress: undefined,
|
||||
currentKey: undefined,
|
||||
editing: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
shortcutDisplay() {
|
||||
if (!this.shortcut) {
|
||||
return '(no shortcut)'
|
||||
}
|
||||
return KeyboardShortcutParser.parseShortcut(this.shortcut);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
editShortcut() {
|
||||
this.editing = true;
|
||||
this.currentKeypress = undefined;
|
||||
this.currentKey = undefined;
|
||||
|
||||
// input doesn't exist now, but will exist on the next tick
|
||||
this.$nextTick(()=> this.$refs.input.focus());
|
||||
},
|
||||
/**
|
||||
* Updates currently pressed keypress for display
|
||||
*/
|
||||
keydown(event) {
|
||||
// event.repeat is set to 'true' when key is being held down, but not on
|
||||
// first keydown. We don't need to process subsequent repeats of a keypress
|
||||
// we already processed.
|
||||
if (event.repeat) {
|
||||
return;
|
||||
}
|
||||
|
||||
const shortcut = KeyboardShortcutParser.generateShortcutFromKeypress(event);
|
||||
const fixedShortcut = this.handleModifierKeypress(shortcut);
|
||||
|
||||
if (this.currentKey === undefined) {
|
||||
this.currentKey = fixedShortcut;
|
||||
} else {
|
||||
// here's a fun fact. Keydown doesn't do modifier keys the way we want —
|
||||
// notably, A-Z0-9 keys are returned without modifier state (all modifiers)
|
||||
// are set to false in keydown events. That means we need to keep track of
|
||||
// modifiers ourselves.
|
||||
if (fixedShortcut.notModifier) {
|
||||
this.currentKey.key = fixedShortcut.key;
|
||||
this.currentKey.code = fixedShortcut.code;
|
||||
} else {
|
||||
this.currentKey = {
|
||||
...fixedShortcut,
|
||||
key: this.currentKey.key,
|
||||
code: this.currentKey.code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update display
|
||||
this.currentKeypress = KeyboardShortcutParser.parseShortcut(this.currentKey);
|
||||
},
|
||||
/**
|
||||
* Emits shortcutChanged when shortcut is considered changed
|
||||
*/
|
||||
keyup(event) {
|
||||
const shortcut = KeyboardShortcutParser.generateShortcutFromKeypress(event);
|
||||
const fixedShortcut = this.handleModifierKeypress(shortcut);
|
||||
|
||||
if (fixedShortcut.notModifier) {
|
||||
this.editing = false;
|
||||
this.$emit('shortcutChanged', this.currentKey);
|
||||
} else {
|
||||
// if none of the modifiers are pressed and if no other key is being held down,
|
||||
// we need to reset label back to 'pls press key'
|
||||
if (!fixedShortcut.altKey && !fixedShortcut.ctrlKey && !fixedShortcut.metaKey && !fixedShortcut.shiftKey && !fixedShortcut.code) {
|
||||
this.currentKeypress = undefined;
|
||||
this.currentKey = undefined;
|
||||
} else {
|
||||
this.currentKey = shortcut;
|
||||
this.currentKeypress = KeyboardShortcutParser.parseShortcut(this.currentKey);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handles current keypress if event.keyCode is a modifier key.
|
||||
* Returns true if current event was a modifier key, false if
|
||||
* if was a regular A-Z 0-9 key.
|
||||
*/
|
||||
handleModifierKeypress(event) {
|
||||
const modifierPressed = event.type === 'keydown';
|
||||
|
||||
switch (event.code) {
|
||||
case 'ShiftLeft':
|
||||
case 'ShiftRight':
|
||||
return {
|
||||
...event,
|
||||
key: '…',
|
||||
code: null,
|
||||
shiftKey: modifierPressed
|
||||
}
|
||||
case 'ControlLeft':
|
||||
case 'ControlRight':
|
||||
return {
|
||||
...event,
|
||||
key: '…',
|
||||
code: null,
|
||||
controlKey: modifierPressed
|
||||
};
|
||||
case 'MetaLeft':
|
||||
case 'MetaRight':
|
||||
return {
|
||||
...event,
|
||||
key: '…',
|
||||
code: null,
|
||||
metaKey: modifierPressed
|
||||
};
|
||||
case 'AltLeft':
|
||||
case 'AltRight':
|
||||
return {
|
||||
...event,
|
||||
key: '…',
|
||||
code: null,
|
||||
altKey: modifierPressed
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...event,
|
||||
notModifier: true,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" src="@csui/src/res-common/common.scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.center-text {
|
||||
text-align: center;
|
||||
}
|
||||
.dark {
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
.hidden-input {
|
||||
position: absolute;
|
||||
z-index: -9999;
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||