Move files around, clean up imports

This commit is contained in:
Tamius Han 2025-12-15 19:42:07 +01:00
parent fb7f5cf1ba
commit ca751a660f
53 changed files with 1151 additions and 214 deletions

19
.vscode/settings.json vendored
View File

@ -10,6 +10,8 @@
"blackframe", "blackframe",
"canvas", "canvas",
"clickthrough", "clickthrough",
"cmpa",
"cmpb",
"com", "com",
"comms", "comms",
"csui", "csui",
@ -23,26 +25,37 @@
"fith", "fith",
"fitw", "fitw",
"fuckup", "fuckup",
"fucky",
"geoblocked",
"geoblocking",
"gfycat", "gfycat",
"gmail", "gmail",
"guardline", "guardline",
"han", "han",
"haram", "haram",
"Heebo",
"hoverable",
"iframe", "iframe",
"imgur", "imgur",
"insta", "insta",
"jsoneditor",
"jwplayer",
"kavin",
"letterboxed", "letterboxed",
"manjaro", "manjaro",
"mdicon", "mdicon",
"mdijs", "mdijs",
"metaivi",
"minification", "minification",
"mitigations", "mitigations",
"mstefan",
"nogrow", "nogrow",
"noshrink", "noshrink",
"outro", "outro",
"PILLARBOX", "PILLARBOX",
"PILLARBOXED", "PILLARBOXED",
"polyfill", "polyfill",
"problemo",
"recursing", "recursing",
"reddit", "reddit",
"rescan", "rescan",
@ -51,6 +64,7 @@
"scrollbar", "scrollbar",
"shitiness", "shitiness",
"smallcaps", "smallcaps",
"spaghettified",
"suboption", "suboption",
"subscan", "subscan",
"tabitem", "tabitem",
@ -61,6 +75,7 @@
"ultrawidify", "ultrawidify",
"unmark", "unmark",
"unmarking", "unmarking",
"unsets",
"unshift", "unshift",
"uwid", "uwid",
"uwui", "uwui",
@ -68,8 +83,12 @@
"vids", "vids",
"vuejs", "vuejs",
"vuex", "vuex",
"wakanim",
"webextension", "webextension",
"webextensions", "webextensions",
"wuckies",
"wucky",
"ycenter",
"youtube" "youtube"
], ],
"cSpell.ignoreWords": [ "cSpell.ignoreWords": [

11
paths.config.js Normal file
View File

@ -0,0 +1,11 @@
const path = require('path')
module.exports = {
'@src': path.resolve(__dirname, 'src'),
'@csui': path.resolve(__dirname, 'src/csui'),
'@ui': path.resolve(__dirname, 'src/ui'),
'@components': path.resolve(__dirname, 'src/ui/components'),
'@res': path.resolve(__dirname, 'src/ui/res'),
'@': path.resolve(__dirname, 'src'),
};

View File

@ -1,5 +1,22 @@
const path = require('path');
const aliases = require('./paths.config');
module.exports = { module.exports = {
plugins: [ 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('@tailwindcss/postcss'),
require('postcss-nested'), require('postcss-nested'),
require('autoprefixer'), require('autoprefixer'),

View File

@ -0,0 +1,9 @@
export enum SiteSupportLevel {
OfficialSupport = 'official',
CommunitySupport = 'community',
UserDefined = 'user-defined',
BetaSupport = 'testing',
OfficialBlacklist = 'officially-disabled',
Unknown = 'unknown',
UserModified = 'modified',
}

View File

@ -6,6 +6,7 @@ import AspectRatioType from '../enums/AspectRatioType.enum'
import CropModePersistence from '../enums/CropModePersistence.enum' import CropModePersistence from '../enums/CropModePersistence.enum'
import EmbeddedContentSettingsOverridePolicy from '../enums/EmbeddedContentSettingsOverridePolicy.enum' import EmbeddedContentSettingsOverridePolicy from '../enums/EmbeddedContentSettingsOverridePolicy.enum'
import ExtensionMode from '../enums/ExtensionMode.enum' import ExtensionMode from '../enums/ExtensionMode.enum'
import { SiteSupportLevel } from '../enums/SiteSupportLevel.enum'
import StretchType from '../enums/StretchType.enum' import StretchType from '../enums/StretchType.enum'
import VideoAlignmentType from '../enums/VideoAlignmentType.enum' import VideoAlignmentType from '../enums/VideoAlignmentType.enum'
@ -319,8 +320,6 @@ interface SettingsInterface {
ui: { ui: {
inPlayer: { inPlayer: {
enabled: boolean, // Deprecated — moved to site settings
enabledFullscreenOnly: boolean, // Deprecated — moved to site settings
popupAlignment: 'left' | 'right', popupAlignment: 'left' | 'right',
minEnabledWidth: number, // don't show UI if player is narrower than % of screen width 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 minEnabledHeight: number, // don't show UI if player is narrower than % of screen height
@ -449,8 +448,8 @@ export interface SiteSettingsInterface {
autocreated?: boolean; autocreated?: boolean;
type?: 'official' | 'community' | 'user-defined' | 'testing' | 'officially-disabled' | 'unknown' | 'modified'; type?: SiteSupportLevel;
defaultType: 'official' | 'community' | 'user-defined' | 'testing' | 'officially-disabled' | 'unknown' | 'modified'; defaultType: SiteSupportLevel;
// must be defined in @global and @empty // must be defined in @global and @empty
persistCSA?: CropModePersistence, // CSA - crop, stretch, alignment persistCSA?: CropModePersistence, // CSA - crop, stretch, alignment

View File

@ -1,11 +1,15 @@
<template> <template>
<div> <div>
<!-- Enable extension --> <!-- Enable extension -->
<pre>{{simpleExtensionSettings}}</pre>
<div class="field"> <div class="field">
<div class="label"> <div class="label">
Enable <span class="color-emphasis">extension</span> Enable <span class="color-emphasis">extension</span>
<span class="sub-label"><br/>under the following conditions:</span> <span class="sub-label"><br/>under the following conditions:</span>
</div> </div>
<pre>{{simpleExtensionSettings}}</pre>
<div class="select"> <div class="select">
<select <select
:value="simpleExtensionSettings.enable" :value="simpleExtensionSettings.enable"
@ -281,7 +285,7 @@
v-if="!isDefaultConfiguration" v-if="!isDefaultConfiguration"
:value="CropModePersistence.Default" :value="CropModePersistence.Default"
> >
Use default ({{defaultPersistanceLabel}}) Use default ({{defaultPersistenceLabel}})
</option> </option>
<option :value="CropModePersistence.Disabled">Disabled</option> <option :value="CropModePersistence.Disabled">Disabled</option>
<option :value="CropModePersistence.UntilPageReload">Until page reload</option> <option :value="CropModePersistence.UntilPageReload">Until page reload</option>
@ -373,7 +377,7 @@ export default {
siteDefaultCropPersistence() { siteDefaultCropPersistence() {
return this.siteSettings.raw?.persistCSA ?? undefined; return this.siteSettings.raw?.persistCSA ?? undefined;
}, },
defaultPersistanceLabel() { defaultPersistenceLabel() {
switch (this.siteSettings.defaultSettings.persistCSA) { switch (this.siteSettings.defaultSettings.persistCSA) {
case CropModePersistence.CurrentSession: case CropModePersistence.CurrentSession:
return 'current session'; return 'current session';
@ -513,7 +517,7 @@ export default {
this._computedWatchers?.siteDefaultStretch?.run(); this._computedWatchers?.siteDefaultStretch?.run();
this._computedWatchers?.siteDefaultAlignment?.run(); this._computedWatchers?.siteDefaultAlignment?.run();
this._computedWatchers?.siteDefaultCropPersistence?.run(); this._computedWatchers?.siteDefaultCropPersistence?.run();
this._computedWatchers?.defaultPersistanceLabel?.run(); this._computedWatchers?.defaultPersistenceLabel?.run();
this.$nextTick( () => this.$forceUpdate()); this.$nextTick( () => this.$forceUpdate());
}, },

View File

@ -274,7 +274,7 @@ export default {
this.eventBus?.sendToTunnel('set-zoom', {zoom: 1}); this.eventBus?.sendToTunnel('set-zoom', {zoom: 1});
}, },
changeZoom(newZoom, axis) { changeZoom(newZoom, axis) {
// we store zoom logarithmically on this compnent // we store zoom logarithmically on this component
if (!axis) { if (!axis) {
this.zoom.x = newZoom; this.zoom.x = newZoom;
} else { } else {

View File

@ -110,7 +110,7 @@ export default class UWContent {
this.pageInfo = new PageInfo(this.eventBus, this.siteSettings, this.settings, this.logAggregator); this.pageInfo = new PageInfo(this.eventBus, this.siteSettings, this.settings, this.logAggregator);
this.logger.debug('setup', "pageInfo initialized."); this.logger.debug('setup', "pageInfo initialized.");
this.logger.debug('setup', "will try to initate KeyboardHandler."); this.logger.debug('setup', "will try to initiate KeyboardHandler.");
if (this.keyboardHandler) { if (this.keyboardHandler) {
this.keyboardHandler.destroy(); this.keyboardHandler.destroy();

View File

@ -304,6 +304,8 @@ const ExtensionConfPatch = Object.freeze([
userOptions.sites[key].enableAard = convertLegacyExtensionMode(userOptions.sites[key].enable as any); userOptions.sites[key].enableAard = convertLegacyExtensionMode(userOptions.sites[key].enable as any);
userOptions.sites[key].enableKeyboard = convertLegacyExtensionMode(userOptions.sites[key].enable as any); userOptions.sites[key].enableKeyboard = convertLegacyExtensionMode(userOptions.sites[key].enable as any);
userOptions.sites[key].enableUI = convertLegacyExtensionMode(userOptions.sites[key].enable as any); userOptions.sites[key].enableUI = convertLegacyExtensionMode(userOptions.sites[key].enable as any);
logger.log('migrated site', key, userOptions.sites[key]);
} }
} }
} }

View File

@ -12,6 +12,7 @@ import { Extension } from 'typescript';
import EmbeddedContentSettingsOverridePolicy from '../../common/enums/EmbeddedContentSettingsOverridePolicy.enum'; import EmbeddedContentSettingsOverridePolicy from '../../common/enums/EmbeddedContentSettingsOverridePolicy.enum';
import { AardPollingOptions } from '../lib/aard/enums/aard-polling-options.enum'; import { AardPollingOptions } from '../lib/aard/enums/aard-polling-options.enum';
import { AardSubtitleCropMode } from '../lib/aard/enums/aard-subtitle-crop-mode.enum'; import { AardSubtitleCropMode } from '../lib/aard/enums/aard-subtitle-crop-mode.enum';
import { SiteSupportLevel } from '../../common/enums/SiteSupportLevel.enum';
if(Debug.debug) if(Debug.debug)
console.log("Loading: ExtensionConf.js"); console.log("Loading: ExtensionConf.js");
@ -262,8 +263,6 @@ const ExtensionConf: SettingsInterface = {
ui: { ui: {
inPlayer: { inPlayer: {
enabled: true, // enable by default on new installs
enabledFullscreenOnly: false,
minEnabledWidth: 0.75, minEnabledWidth: 0.75,
minEnabledHeight: 0.75, minEnabledHeight: 0.75,
activation: 'player', activation: 'player',
@ -810,7 +809,7 @@ const ExtensionConf: SettingsInterface = {
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always, applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
defaultType: 'unknown', defaultType: SiteSupportLevel.Unknown,
persistCSA: CropModePersistence.Disabled, persistCSA: CropModePersistence.Disabled,
defaults: { defaults: {
@ -827,8 +826,8 @@ const ExtensionConf: SettingsInterface = {
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always, applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
type: 'user-defined', type: SiteSupportLevel.UserDefined,
defaultType: 'user-defined', defaultType: SiteSupportLevel.UserDefined,
persistCSA: CropModePersistence.Default, persistCSA: CropModePersistence.Default,
defaults: { defaults: {
crop: null, crop: null,
@ -845,14 +844,14 @@ const ExtensionConf: SettingsInterface = {
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always, applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
overrideWhenEmbedded: true, overrideWhenEmbedded: true,
override: false, // ignore value localStorage in favour of this override: false, // ignore value localStorage in favour of this
type: 'official', // is officially supported? (Alternatives are 'community' and 'user-defined') type: SiteSupportLevel.OfficialSupport, // is officially supported? (Alternatives are 'community' and 'user-defined')
defaultType: 'official', // if user mucks around with settings, type changes to 'user-defined'. defaultType: SiteSupportLevel.OfficialSupport, // if user mucks around with settings, type changes to 'user-defined'.
// We still want to know what the original type was, hence defaultType // We still want to know what the original type was, hence defaultType
activeDOMConfig: 'official', activeDOMConfig: 'official',
DOMConfig: { DOMConfig: {
'official': { 'official': {
type: 'official', type: SiteSupportLevel.OfficialSupport,
elements: { elements: {
player: { player: {
manual: true, manual: true,
@ -872,14 +871,14 @@ const ExtensionConf: SettingsInterface = {
overrideWhenEmbedded: true, overrideWhenEmbedded: true,
override: false, // ignore value localStorage in favour of this override: false, // ignore value localStorage in favour of this
type: 'official', // is officially supported? (Alternatives are 'community' and 'user-defined') type: SiteSupportLevel.OfficialSupport, // is officially supported? (Alternatives are 'community' and 'user-defined')
defaultType: 'official', // if user mucks around with settings, type changes to 'user-defined'. defaultType: SiteSupportLevel.OfficialSupport, // if user mucks around with settings, type changes to 'user-defined'.
// We still want to know what the original type was, hence defaultType // We still want to know what the original type was, hence defaultType
activeDOMConfig: 'official', activeDOMConfig: 'official',
DOMConfig: { DOMConfig: {
'official': { 'official': {
type: 'official', type: SiteSupportLevel.OfficialSupport,
elements: { elements: {
player: { player: {
manual: true, manual: true,
@ -896,8 +895,8 @@ const ExtensionConf: SettingsInterface = {
enableUI: ExtensionMode.All, enableUI: ExtensionMode.All,
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always, applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
override: false, override: false,
type: 'community', type: SiteSupportLevel.CommunitySupport,
defaultType: 'community', defaultType: SiteSupportLevel.CommunitySupport,
}, },
"www.disneyplus.com" : { "www.disneyplus.com" : {
enable: ExtensionMode.Theater, enable: ExtensionMode.Theater,
@ -906,12 +905,12 @@ const ExtensionConf: SettingsInterface = {
enableUI: ExtensionMode.FullScreen, enableUI: ExtensionMode.FullScreen,
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always, applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
type: 'community', type: SiteSupportLevel.CommunitySupport,
defaultType: 'community', defaultType: SiteSupportLevel.CommunitySupport,
activeDOMConfig: 'community-mstefan99', activeDOMConfig: 'community-mstefan99',
DOMConfig: { DOMConfig: {
'community-mstefan99': { 'community-mstefan99': {
type: 'official', type: SiteSupportLevel.OfficialSupport,
elements: { elements: {
player: { player: {
manual: true, manual: true,
@ -933,8 +932,8 @@ const ExtensionConf: SettingsInterface = {
enableUI: ExtensionMode.FullScreen, enableUI: ExtensionMode.FullScreen,
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always, applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
type: 'official', type: SiteSupportLevel.OfficialSupport,
defaultType: 'official', defaultType: SiteSupportLevel.OfficialSupport,
}, },
"old.reddit.com" : { "old.reddit.com" : {
enable: ExtensionMode.Disabled, enable: ExtensionMode.Disabled,
@ -943,12 +942,12 @@ const ExtensionConf: SettingsInterface = {
enableUI: ExtensionMode.Disabled, enableUI: ExtensionMode.Disabled,
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Never, applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Never,
type: 'officially-disabled', type: SiteSupportLevel.OfficialBlacklist,
defaultType: 'officially-disabled', defaultType: SiteSupportLevel.OfficialBlacklist,
activeDOMConfig: 'official', activeDOMConfig: 'official',
DOMConfig: { DOMConfig: {
'official': { 'official': {
type: 'official', type: SiteSupportLevel.OfficialSupport,
// customCss: 'video {\n width: 100% !important;\n height: 100% !important;\n}', // customCss: 'video {\n width: 100% !important;\n height: 100% !important;\n}',
elements: { elements: {
player: { player: {
@ -966,12 +965,12 @@ const ExtensionConf: SettingsInterface = {
enableUI: ExtensionMode.Disabled, enableUI: ExtensionMode.Disabled,
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Never, applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Never,
type: 'officially-disabled', type: SiteSupportLevel.OfficialBlacklist,
defaultType: 'officially-disabled', defaultType: SiteSupportLevel.OfficialBlacklist,
activeDOMConfig: 'official', activeDOMConfig: 'official',
DOMConfig: { DOMConfig: {
'official': { 'official': {
type: 'official', type: SiteSupportLevel.OfficialSupport,
// customCss: 'video {\n width: 100% !important;\n height: 100% !important;\n}', // customCss: 'video {\n width: 100% !important;\n height: 100% !important;\n}',
elements: { elements: {
player: { player: {
@ -989,8 +988,8 @@ const ExtensionConf: SettingsInterface = {
enableUI: ExtensionMode.Disabled, enableUI: ExtensionMode.Disabled,
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Never, applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Never,
type: 'officially-disabled', type: SiteSupportLevel.OfficialBlacklist,
defaultType: 'officially-disabled', defaultType: SiteSupportLevel.OfficialBlacklist,
}, },
"www.wakanim.tv": { "www.wakanim.tv": {
enable: ExtensionMode.All, enable: ExtensionMode.All,
@ -999,12 +998,12 @@ const ExtensionConf: SettingsInterface = {
enableUI: ExtensionMode.FullScreen, enableUI: ExtensionMode.FullScreen,
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always, applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
type: 'community', type: SiteSupportLevel.CommunitySupport,
defaultType: 'community', defaultType: SiteSupportLevel.CommunitySupport,
activeDOMConfig: 'community', activeDOMConfig: 'community',
DOMConfig: { DOMConfig: {
'community': { 'community': {
type: 'community', type: SiteSupportLevel.CommunitySupport,
elements: { elements: {
player: { player: {
manual: true, manual: true,
@ -1021,12 +1020,12 @@ const ExtensionConf: SettingsInterface = {
enableUI: ExtensionMode.FullScreen, enableUI: ExtensionMode.FullScreen,
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always, applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
type: 'community', type: SiteSupportLevel.CommunitySupport,
defaultType: 'community', defaultType: SiteSupportLevel.CommunitySupport,
activeDOMConfig: 'community', activeDOMConfig: 'community',
DOMConfig: { DOMConfig: {
'community': { 'community': {
type: 'community', type: SiteSupportLevel.CommunitySupport,
// customCss: "body {\n background-color: #000;\n}\n\n.application {\n background-color: #000;\n}" // customCss: "body {\n background-color: #000;\n}\n\n.application {\n background-color: #000;\n}"
} }
} }
@ -1037,12 +1036,12 @@ const ExtensionConf: SettingsInterface = {
enableKeyboard: ExtensionMode.Theater, enableKeyboard: ExtensionMode.Theater,
enableUI: ExtensionMode.FullScreen, enableUI: ExtensionMode.FullScreen,
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always, applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
type: "community", type: SiteSupportLevel.CommunitySupport,
defaultType: "community", defaultType: SiteSupportLevel.CommunitySupport,
activeDOMConfig: 'community', activeDOMConfig: 'community',
DOMConfig: { DOMConfig: {
'community': { 'community': {
type: 'community', type: SiteSupportLevel.CommunitySupport,
elements: { elements: {
video: { video: {
nodeCss: {'position': 'absolute !important'} nodeCss: {'position': 'absolute !important'}
@ -1058,12 +1057,12 @@ const ExtensionConf: SettingsInterface = {
enableUI: ExtensionMode.FullScreen, enableUI: ExtensionMode.FullScreen,
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always, applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.Always,
type: "community", type: SiteSupportLevel.CommunitySupport,
defaultType: "community", defaultType: SiteSupportLevel.CommunitySupport,
activeDOMConfig: 'community', activeDOMConfig: 'community',
DOMConfig: { DOMConfig: {
'community': { 'community': {
type: 'community', type: SiteSupportLevel.CommunitySupport,
// customCss: ".shaka-video-container {\n flex-direction: column !important;\n}" // customCss: ".shaka-video-container {\n flex-direction: column !important;\n}"
} }
} }

View File

@ -24,10 +24,6 @@ const BASE_LOGGING_STYLES = {
*/ */
export class KeyboardHandler extends KbmBase { export class KeyboardHandler extends KbmBase {
listenFor: string[] = ['keyup']; listenFor: string[] = ['keyup'];
settings: Settings;
siteSettings: SiteSettings;
eventBus: EventBus;
playerElements: HTMLElement[] = []; playerElements: HTMLElement[] = [];

View File

@ -113,7 +113,6 @@ export class LogAggregator {
private storageChangeListener(changes, area) { private storageChangeListener(changes, area) {
if (!changes[STORAGE_LOG_SETTINGS_KEY]) { if (!changes[STORAGE_LOG_SETTINGS_KEY]) {
// console.info('We dont have any logging settings, not processing frther', changes);
return; return;
} }

View File

@ -96,6 +96,7 @@ class Settings {
if (!changes.uwSettings) { if (!changes.uwSettings) {
return; return;
} }
console.log('new settings change.')
this.logger?.info('storageOnChange', "Settings have been changed outside of here. Updating active settings. Changes:", changes, "storage area:", area); this.logger?.info('storageOnChange', "Settings have been changed outside of here. Updating active settings. Changes:", changes, "storage area:", area);
// if (changes['uwSettings'] && changes['uwSettings'].newValue) { // if (changes['uwSettings'] && changes['uwSettings'].newValue) {
// this.logger?.log('info', 'settings',"[Settings::<storage/on change>] new settings object:", JSON.parse(changes.uwSettings.newValue)); // this.logger?.log('info', 'settings',"[Settings::<storage/on change>] new settings object:", JSON.parse(changes.uwSettings.newValue));
@ -132,7 +133,7 @@ class Settings {
try { try {
fn(); fn();
} catch (e) { } catch (e) {
this.logger?.warn('storageOnChange', "afterSettingsChanged fallback failed. It's possible that a vue component got destroyed, and this function is nothing more than vestigal remains. It would be nice if we implemented something that allows us to remove callback functions from array, and remove vue callbacks from the callback array when their respective UI component gets destroyed. Or this could be an error with the function itself. IDK, here's the error.", e) this.logger?.warn('storageOnChange', "afterSettingsChanged fallback failed. It's possible that a vue component got destroyed, and this function is nothing more than vestigial remains. It would be nice if we implemented something that allows us to remove callback functions from array, and remove vue callbacks from the callback array when their respective UI component gets destroyed. Or this could be an error with the function itself. IDK, here's the error.", e)
} }
} }
if (this.onSettingsChanged) { if (this.onSettingsChanged) {
@ -147,7 +148,7 @@ class Settings {
try { try {
fn(); fn();
} catch (e) { } catch (e) {
this.logger?.warn('storageOnChange', "afterSettingsChanged fallback failed. It's possible that a vue component got destroyed, and this function is nothing more than vestigal remains. It would be nice if we implemented something that allows us to remove callback functions from array, and remove vue callbacks from the callback array when their respective UI component gets destroyed. Or this could be an error with the function itself. IDK, here's the error.", e) this.logger?.warn('storageOnChange', "afterSettingsChanged fallback failed. It's possible that a vue component got destroyed, and this function is nothing more than vestigial remains. It would be nice if we implemented something that allows us to remove callback functions from array, and remove vue callbacks from the callback array when their respective UI component gets destroyed. Or this could be an error with the function itself. IDK, here's the error.", e)
} }
} }
if (this.afterSettingsSaved) { if (this.afterSettingsSaved) {

View File

@ -6,6 +6,7 @@ import Settings from './Settings';
import StretchType from '../../../common/enums/StretchType.enum'; import StretchType from '../../../common/enums/StretchType.enum';
import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum'; import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum';
import EmbeddedContentSettingsOverridePolicy from '../../../common/enums/EmbeddedContentSettingsOverridePolicy.enum'; import EmbeddedContentSettingsOverridePolicy from '../../../common/enums/EmbeddedContentSettingsOverridePolicy.enum';
import { SiteSupportLevel } from '../../../common/enums/SiteSupportLevel.enum';
export interface GetSiteSettingsOptions { export interface GetSiteSettingsOptions {
@ -182,6 +183,8 @@ export class SiteSettings {
*/ */
private compileSettingsObject() { private compileSettingsObject() {
const {siteSettings, usesSettingsFor} = this.getSettingsForSite(this.options); const {siteSettings, usesSettingsFor} = this.getSettingsForSite(this.options);
console.log('got settings for site:', siteSettings, usesSettingsFor)
this.data = _cp(siteSettings); this.data = _cp(siteSettings);
this.usesSettingsFor = usesSettingsFor; this.usesSettingsFor = usesSettingsFor;
@ -213,11 +216,10 @@ export class SiteSettings {
} }
for (const enableSegment of ['enable', 'enableAard', 'enableKeyboard', 'enableUI']) { for (const enableSegment of ['enable', 'enableAard', 'enableKeyboard', 'enableUI']) {
if (!this.data[enableSegment]) { if (this.data[enableSegment] === undefined) {
this.data[enableSegment] = {}; this.data[enableSegment] = {};
} } else if (this.data[enableSegment] === ExtensionMode.Default) {
if (!this.data[enableSegment] || this.data[enableSegment] === ExtensionMode.Default) { this.data[enableSegment] = this.defaultSettings[enableSegment];
this.data[enableSegment] = _cp(this.defaultSettings[enableSegment]);
} }
} }
@ -459,11 +461,11 @@ export class SiteSettings {
if (options.scripted && !this.settings.active.sites[targetSite]) { if (options.scripted && !this.settings.active.sites[targetSite]) {
this.settings.active.sites[targetSite] = _cp(this.settings.active.sites['@global']); this.settings.active.sites[targetSite] = _cp(this.settings.active.sites['@global']);
this.settings.active.sites[targetSite].autocreated = true; this.settings.active.sites[targetSite].autocreated = true;
this.settings.active.sites[targetSite].type = 'unknown'; this.settings.active.sites[targetSite].type = SiteSupportLevel.Unknown;
} else { } else {
if (!this.settings.active.sites[targetSite] || this.settings.active.sites[targetSite].autocreated) { if (!this.settings.active.sites[targetSite] || this.settings.active.sites[targetSite].autocreated) {
this.settings.active.sites[targetSite] = _cp(this.data); this.settings.active.sites[targetSite] = _cp(this.data);
this.settings.active.sites[targetSite].type = 'user-defined'; this.settings.active.sites[targetSite].type = SiteSupportLevel.UserDefined;
} }
} }

View File

@ -41,7 +41,7 @@ class UI {
// TODO: at some point, UI should be different for global popup and in-player UI // TODO: at some point, UI should be different for global popup and in-player UI
this.csuiScheme = this.getCsuiScheme(); this.csuiScheme = this.getCsuiScheme();
const csuiVersion = this.getCsuiVersion(this.csuiScheme.contentScheme); const csuiVersion = this.getCsuiVersion(this.csuiScheme.contentScheme);
this.uiURI = chrome.runtime.getURL(`/csui/${csuiVersion}.html`); // this.uiURI = chrome.runtime.getURL(`/csui/${csuiVersion}.html`);
this.extensionBase = chrome.runtime.getURL('').replace(/\/$/, ""); this.extensionBase = chrome.runtime.getURL('').replace(/\/$/, "");
// UI will be initialized when setUiVisibility is called // UI will be initialized when setUiVisibility is called
@ -58,10 +58,8 @@ class UI {
async init() { async init() {
if (!this.canRun()) { if (!this.canRun()) {
// console.log('ui config: canRun returned false', this.siteSettings?.data.enableUI.fullscreen === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.theater === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.normal === ExtensionMode.Enabled)
return; return;
} }
// console.log('ui config: canRun returned truie', this.siteSettings?.data.enableUI.fullscreen === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.theater === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.normal === ExtensionMode.Enabled)
this.initUIContainer(); this.initUIContainer();
@ -121,6 +119,7 @@ class UI {
} }
loadIframe() { loadIframe() {
return;
// in onMouseMove, we currently can't access this because we didn't // in onMouseMove, we currently can't access this because we didn't
// do things the most properly // do things the most properly
const uiURI = this.uiURI; const uiURI = this.uiURI;
@ -475,7 +474,7 @@ class UI {
} }
/** /**
* Sends message to iframe. Messages sent with this function will be routed to eventbus. * Sends message to iframe. Messages sent with this function will be routed to eventBus.
*/ */
sendToIframe(action, actionConfig, routingData, uiURI = this.uiURI) { sendToIframe(action, actionConfig, routingData, uiURI = this.uiURI) {
// if (routingData) { // if (routingData) {

View File

@ -269,14 +269,14 @@ class PlayerData {
* @returns * @returns
*/ */
deferredUiInitialization(playerDimensions = this.dimensions) { deferredUiInitialization(playerDimensions = this.dimensions) {
if (this.ui || this.siteSettings.data.enableUI.fullscreen === ExtensionMode.Disabled) { if (this.ui || this.siteSettings.data.enableUI === ExtensionMode.Disabled) {
return; return;
} }
if ( if (
this.isFullscreen this.isFullscreen
|| ( || (
this.siteSettings.data.enableUI.theater !== ExtensionMode.Disabled this.siteSettings.data.enableUI >= ExtensionMode.FullScreen
&& playerDimensions.width > 1208 && playerDimensions.width > 1208
&& playerDimensions.height > 720 && playerDimensions.height > 720
) )

View File

@ -1,27 +1,11 @@
@import "tailwindcss"; @import "tailwindcss";
@import "tailwindcss/utilities"; @import "tailwindcss/utilities";
@theme { @import '@ui/res/styles/theme.css';
--breakpoint-popup: 640px; @import '@ui/res/styles/forms.css';
--breakpoint-window: 960px; @import '@ui/res/styles/fonts.css';
@import '@ui/res/styles/buttons.css';
--color-primary: #ff872c;
--color-primary-50: #ffddbe;
--color-primary-100: #ffcda0;
--color-primary-200: #ffbd83;
--color-primary-300: #ffac66;
--color-primary-400: #ff9a4a;
--color-primary-500: #ff872c;
--color-primary-600: #de7622;
--color-primary-700: #be6518;
--color-primary-800: #9f540e;
--color-primary-900: #824406;
--color-primary-950: #663400;
}
@import './ui/res/styles/forms.css';
@import './ui/res/styles/fonts.css';
@import './ui/res/styles/buttons.css';
html, body { html, body {
@apply bg-stone-950 text-stone-300; @apply bg-stone-950 text-stone-300;

View File

@ -19,7 +19,8 @@
"common/lib/browser-polyfill.js", "common/lib/browser-polyfill.js",
"ext/uw.js" "ext/uw.js"
], ],
"all_frames": true "all_frames": true,
"type": "module"
}], }],
"minimum_chrome_version": "93", "minimum_chrome_version": "93",

View File

@ -65,7 +65,7 @@
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import BrowserDetect from '@src/ext/conf/BrowserDetect'; import BrowserDetect from '@src/ext/conf/BrowserDetect';
import SupportLevelIndicator from '@components/common/components/SupportLevelIndicator.vue'; import SupportLevelIndicator from '@components/common/SupportLevelIndicator.vue';
export default defineComponent({ export default defineComponent({
@ -95,7 +95,7 @@ export default defineComponent({
}) })
</script> </script>
<style lang="postcss" scoped> <style lang="postcss" scoped>
@import '../../../main.css'; /** postcss processor doesn't support aliases */ @import '@src/main.css'; /** postcss processor doesn't support aliases */
.open-settings-popup { .open-settings-popup {
width: 16rem; width: 16rem;

View File

@ -76,30 +76,23 @@
<div class="flex flex-col panel-content"> <div class="flex flex-col panel-content">
<!-- Panel section --> <!-- Panel section -->
<VideoSettings <VideoSettings v-if="selectedTab === 'video-settings'"
v-if="selectedTab === 'video-settings'"
:settings="settings" :settings="settings"
:siteSettings="siteSettings" :siteSettings="siteSettings"
:eventBus="eventBus" :eventBus="eventBus"
:site="site" :site="site"
></VideoSettings> ></VideoSettings>
<template <template v-if="settings && selectedTab === 'site-extension-settings'" >
v-if="settings && selectedTab === 'site-extension-settings'"
>
<h3>Settings for {{site?.host}}</h3> <h3>Settings for {{site?.host}}</h3>
<SiteExtensionSettings <SiteExtensionSettings
:settings="settings" :settings="settings"
:siteSettings="siteSettings" :siteSettings="siteSettings"
:isDefaultConfiguration="false" :isDefaultConfiguration="false"
></SiteExtensionSettings> ></SiteExtensionSettings>
<pre>{{JSON.stringify(siteSettings.data, null, 2)}}</pre>
</template> </template>
<template <template v-if="settings && selectedTab === 'embedded-extension-settings'" >
v-if="settings && selectedTab === 'embedded-extension-settings'"
>
<h3>Settings for embedded sites</h3> <h3>Settings for embedded sites</h3>
<FrameSiteSettings <FrameSiteSettings
:parentHost="site?.host" :parentHost="site?.host"
@ -109,9 +102,7 @@
</template> </template>
<template <template v-if="settings && selectedTab === 'default-extension-settings'" >
v-if="settings && selectedTab === 'default-extension-settings'"
>
<h3>Default settings</h3> <h3>Default settings</h3>
<SiteExtensionSettings <SiteExtensionSettings
:settings="settings" :settings="settings"
@ -125,6 +116,7 @@
:settings="settings" :settings="settings"
:enableSettingsEditor="true" :enableSettingsEditor="true"
></OtherSiteSettings> ></OtherSiteSettings>
<AutodetectionSettings <AutodetectionSettings
v-if="selectedTab === 'autodetectionSettings'" v-if="selectedTab === 'autodetectionSettings'"
:settings="settings" :settings="settings"
@ -168,6 +160,11 @@
> >
</PlayerDetectionPanel> </PlayerDetectionPanel>
<ImportExportSettings v-if="selectedTab === 'import-export-settings'"
:settings="settings"
>
</ImportExportSettings>
<Debugging <Debugging
v-if="selectedTab === 'debugging'" v-if="selectedTab === 'debugging'"
:settings="settings" :settings="settings"
@ -187,24 +184,24 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import VideoSettings from '@components/VideoSettings/VideoSettings.vue'; import VideoSettings from '@components/segments/VideoSettings/VideoSettings.vue';
import OtherSiteSettings from '@components/ExtensionSettings/Panels/OtherSiteSettings.vue'; import OtherSiteSettings from '@components/segments/ExtensionSettings/Panels/OtherSiteSettings.vue';
import AutodetectionSettings from '@components/AutodetectionSettings/AutodetectionSettings.vue'; import AutodetectionSettings from '@components/segments/AutodetectionSettings/AutodetectionSettings.vue';
import UISettings from '@components/UISettings/UISettings.vue'; import UISettings from '@components/segments/UISettings/UISettings.vue';
import KeyboardShortcutSettings from '@components/KeyboardShortcuts/KeyboardShortcutSettings.vue'; import KeyboardShortcutSettings from '@components/segments/KeyboardShortcuts/KeyboardShortcutSettings.vue';
import SiteExtensionSettings from '@components/ExtensionSettings/Panels/SiteExtensionSettings.vue'; import SiteExtensionSettings from '@components/segments/ExtensionSettings/Panels/SiteExtensionSettings.vue';
import FrameSiteSettings from '@components/ExtensionSettings/Panels/FrameSiteSettings.vue'; import FrameSiteSettings from '@components/segments/ExtensionSettings/Panels/FrameSiteSettings.vue';
import Debugging from '@components/Debugging/Debugging.vue'; import Debugging from '@components/segments/Debugging/Debugging.vue';
import ImportExportSettings from '@components/segments/ImportExportSettings/ImportExportSettings.vue';
import WhatsNew from '@components/ExtensionInfo/WhatsNew.vue'; import WhatsNew from '@components/segments/ExtensionInfo/WhatsNew.vue';
import About from '@components/ExtensionInfo/About.vue'; import About from '@components/segments/ExtensionInfo/About.vue';
// to replace:
import PlayerDetectionPanel from '../../csui/src/PlayerUiPanels/PlayerDetectionPanel.vue' import PlayerDetectionPanel from '../../csui/src/PlayerUiPanels/PlayerDetectionPanel.vue'
import BrowserDetect from '../../ext/conf/BrowserDetect'
import ChangelogPanel from '../../csui/src/PlayerUiPanels/ChangelogPanel.vue' // not component:
import AboutPanel from '@csui/src/PlayerUiPanels/AboutPanel.vue' import BrowserDetect from '@src/ext/conf/BrowserDetect'
import ResetBackupPanel from '../../csui/src/PlayerUiPanels/ResetBackupPanel.vue'
import SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue'
const AVAILABLE_TABS = { const AVAILABLE_TABS = {
@ -227,6 +224,7 @@ const AVAILABLE_TABS = {
'playerDetection': {id: 'playerDetection', label: 'Player detection', icon: 'television-play'}, 'playerDetection': {id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
'changelog': {id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' }, 'changelog': {id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
'about': {id: 'about', label: 'About', icon: 'information-outline'}, 'about': {id: 'about', label: 'About', icon: 'information-outline'},
'import-export-settings': { id: 'import-export-settings', label: 'Import & export settings', icon: 'file-export-outline'},
'debugging': {id: 'debugging', label: 'Debugging', icon: 'bug-outline', hidden: true} 'debugging': {id: 'debugging', label: 'Debugging', icon: 'bug-outline', hidden: true}
}; };
@ -238,6 +236,7 @@ const TAB_LOADOUT = {
'keyboardShortcuts', 'keyboardShortcuts',
'changelog', 'changelog',
'about', 'about',
'import-export-settings',
'debugging', 'debugging',
], ],
'popup': [ 'popup': [
@ -257,16 +256,13 @@ export default defineComponent({
UISettings, UISettings,
SiteExtensionSettings, SiteExtensionSettings,
FrameSiteSettings, FrameSiteSettings,
ImportExportSettings,
Debugging, Debugging,
WhatsNew, WhatsNew,
About, About,
PlayerDetectionPanel, PlayerDetectionPanel,
ChangelogPanel,
AboutPanel,
SupportLevelIndicator,
ResetBackupPanel,
}, },
mixins: [], mixins: [],
data() { data() {
@ -305,6 +301,9 @@ export default defineComponent({
watch: { watch: {
'initialPath': function (newVal) { 'initialPath': function (newVal) {
this.setInitialPath(newVal); this.setInitialPath(newVal);
},
'site': function (newVal) {
this.updateSite(newVal);
} }
}, },
computed: { computed: {
@ -325,7 +324,9 @@ export default defineComponent({
changelogTab.highlight = !this.settings.active?.whatsNewChecked; changelogTab.highlight = !this.settings.active?.whatsNewChecked;
} }
if (this.site) {
this.siteSettings = this.settings.getSiteSettings({site: this.site}); this.siteSettings = this.settings.getSiteSettings({site: this.site});
}
this.eventBus?.subscribe( this.eventBus?.subscribe(
'uw-show-ui', 'uw-show-ui',
{ {
@ -344,6 +345,13 @@ export default defineComponent({
this.eventBus?.unsubscribeAll(this); this.eventBus?.unsubscribeAll(this);
}, },
methods: { methods: {
/**
* Regenerates this.siteSettings
*/
updateSite(newSite: {host: string, hostnames: string[],}) {
this.siteSettings = this.settings.getSiteSettings({site: newSite.host});
this.$nextTick( () => this.$forceUpdate());
},
/** /**
* Gets URL of the browser settings page (i think?) * Gets URL of the browser settings page (i think?)
*/ */
@ -391,7 +399,7 @@ export default defineComponent({
}); });
</script> </script>
<style lang="postcss" scoped> <style lang="postcss" scoped>
@import '../../main.css'; /** postcss processor doesn't support aliases */ @import '@src/main.css'; /** postcss processor doesn't support aliases */
.settings-categories { .settings-categories {
@apply @apply

View File

@ -0,0 +1,109 @@
<template>
<div v-if="popupVisible" class="popup">
<div class="popup-window">
<div
class="header"
:class="{
'danger': dialogType === 'danger',
'warning': dialogType === 'warning',
'info': dialogType === 'info',
'normal': dialogType === 'normal'
}"
>
{{ dialogText || 'Confirm action' }}
</div>
<div class="body">
{{ dialogText || 'Are you sure you want to do that?' }}
</div>
<div class="footer w-full flex row gap-2 justify-end">
<button
class="button confirm"
:class="{
'danger': dialogType === 'danger',
'warning': dialogType === 'warning',
'info': dialogType === 'info',
'normal': dialogType === 'normal'
}"
@click="confirmAction"
>
{{ confirmText || 'Confirm' }}
</button>
<button class="button" @click="popupVisible = false">{{ cancelText || 'Cancel' }}</button>
</div>
</div>
</div>
<button
:class="[
{
'danger': dialogType === 'danger',
'warning': dialogType === 'warning',
},
btnClass
]"
@click="popupVisible = true"
>
<slot></slot>
</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
data() {
return {
popupVisible: false,
}
},
props: [
'btnClass',
'dialogTitle',
'dialogText',
'confirmText',
'cancelText',
'dialogType'
],
methods: {
confirmAction() {
this.popupVisible = false;
this.$emit('onConfirmed');
}
}
});
</script>
<style lang="postcss" scoped>
.popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
backdrop-filter: saturate(50%) brightness(50%) blur(1rem);
z-index: 99999;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.header {
font-size: 1.33rem;
color: #fff;
&.danger {
color: rgb(251, 107, 63);
font-weight: bold;
}
}
.body {
min-height: 5rem;
}
}
</style>

View File

@ -0,0 +1,120 @@
<template>
<div class="flex flex-row w-full">
<button
v-if="editorMode === 'tree'"
@click="() => setEditorMode('text')"
>
Edit as text
</button>
<button
v-else
@click="() => setEditorMode('tree')"
>
Show as tree
</button>
</div>
<div ref="refContainer" class="w-full h-full"></div>
</template>
<script lang="ts">
import { createJSONEditor, Mode } from "vanilla-jsoneditor";
export default {
props: {
modelValue: Object // v-model binding
},
emits: ["update:modelValue"],
data() {
return {
refContainer: null,
editor: null,
editorMode: Mode.tree,
};
},
watch: {
modelValue: {
deep: true,
handler(newValue) {
if (this.editor) {
this.editor.updateProps({ content: { json: newValue || {} } });
}
}
}
},
mounted() {
if (this.$refs.refContainer) {
this.editor = createJSONEditor({
target: this.$refs.refContainer,
props: {
mode: Mode.tree,
content: { json: this.modelValue || {} },
onChange: (updatedContent) => {
this.$emit("update:modelValue", updatedContent.json);
},
onRenderContextMenu: false,
navigationBar: false,
statusBar: false,
mainMenuBar: false
}
});
}
},
methods: {
setEditorMode(mode) {
this.editorMode = mode
this.editor.updateProps({ mode });
}
},
beforeUnmount() {
if (this.editor) {
this.editor.destroy();
this.editor = null;
}
}
};
</script>
<style lang="postcss">
:root {
--jse-panel-background: #111;
--jse-background-color: #000;
--jse-text-color: #ccc;
--jse-key-color: #8c8bef;
--jse-selection-background-color: rgba(255, 171, 102, 0.177);
--jse-context-menu-pointer-background: rgba(255, 171, 102, 0.177);
--jse-delimiter-color: #fa6;
--jse-value-color-boolean: rgb(132, 132, 137);
}
.jse-boolean-toggle[role="checkbox"] {
&[aria-checked="true"] {
color: rgb(218, 244, 238);
}
&[aria-checked="false"] {
border: 0px transparent;
outline: 0px transparent;
color: rgb(241, 107, 25);
background-color: rgb(241, 107, 25);
position: relative;
&:after {
position: absolute;
content: '×';
top: 0;
left: 0;
width: 100%;
height: 100%;
color: black;
text-align: center;
font-weight: bold;
}
}
}
</style>

View File

@ -0,0 +1,111 @@
<template>
<div
v-if="title"
class="popup-overlay"
:class="{'dim': dimOverlay}"
>
<div class="popup-content p-4 border border-stone-700 max-h-[90dvh] max-w-[90dvw]" >
<div class="h-full flex flex-col">
<div v-if="title" class="header grow-0 shrink-0" :class="type">
<h3 class="mb-4 mt-0">{{title}}</h3>
</div>
<p v-if="message">
{{ message }}
</p>
<div v-else class="grow shrink overflow-auto">
<slot></slot>
</div>
<div class="grow-0 shrink-0 flex row gap-2 justify-end w-full">
<button
class="primary"
v-if="confirmButtonText"
:class="type"
@click="$emit('onConfirm')"
>
{{ confirmButtonText }}
</button>
<button
:class="type"
@click="$emit('onCancel')"
>
{{ cancelButtonText }}
</button>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
export default {
props: {
// onConfirm: {
// type: Function,
// required: false
// },
// onCancel: {
// type: Function,
// default: () => this.$emit('close')
// },
title: {
type: String,
required: false
},
message: {
type: String,
required: false, // technically prolly should be true, but we don't have to guard against skill issue
},
dimOverlay: {
type: Boolean,
default: true
},
type: {
type: String,
default: 'info'
},
confirmButtonText: {
type: String,
required: false,
},
cancelButtonText: {
type: String,
default: 'Cancel'
}
}
}
</script>
<style lang="postcss" scoped>
.popup-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
backdrop-filter: saturate(50%) brightness(50%) blur(1rem);
z-index: 99999;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.header {
font-size: 1.33rem;
color: #fff;
&.danger {
color: rgb(251, 107, 63);
font-weight: bold;
}
}
.body {
min-height: 5rem;
}
}
</style>

View File

@ -62,7 +62,7 @@ export default defineComponent({
}, },
computed: { computed: {
computedSiteSupportLevel() { computedSiteSupportLevel() {
return this.siteSupportLevel ?? this.siteSettings ? this.siteSettings.data.type || 'no-support' : 'waiting'; return this.siteSupportLevel ?? (this.siteSettings ? this.siteSettings.data.type ?? 'no-support' : 'waiting');
} }
} }
@ -70,7 +70,7 @@ export default defineComponent({
</script> </script>
<style lang="postcss" scoped> <style lang="postcss" scoped>
@import '../../../../main.css'; /** postcss processor doesn't support aliases */ @import '@src/main.css'; /** postcss processor doesn't support aliases */
.site-support { .site-support {
@apply inline-flex flex-row items-center gap-1 rounded-[0.5rem] relative; @apply inline-flex flex-row items-center gap-1 rounded-[0.5rem] relative;

View File

@ -0,0 +1,79 @@
<template>
<button
class="button drop-zone"
:class="[{'drag-over': isDragOver}, className]"
@click="onClick"
@dragover="isDragOver = true"
@dragleave="isDragOver = false"
@drop="onDrop"
>
<slot></slot>
<input type="file" ref="fileInput" @change="onFileChange" accept=".json" />
</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
props: {
className: {
type: String,
default: ""
}
},
data() {
return {
isDragOver: false
};
},
methods: {
onClick() {
this.$refs.fileInput.click();
},
onDrop(event) {
event.preventDefault();
this.isDragOver = false;
if (event.dataTransfer.files.length > 0) {
this.handleFile(event.dataTransfer.files[0]);
}
},
onFileChange(event) {
if (event.target.files.length > 0) {
this.handleFile(event.target.files[0]);
}
},
handleFile(file) {
if (file.type !== "application/json") {
this.$emit('error', 'NOT_JSON_FILE');
return;
}
const reader = new FileReader();
reader.onload = async (event) => {
try {
const importedData = JSON.parse(event.target.result as string);
this.$emit('importedJson', importedData);
} catch (error) {
this.$emit('error', 'INVALID_JSON');
}
};
reader.readAsText(file);
},
}
});
</script>
<style lang="postcss" scoped>
input {
display: none;
}
.drop-zone {
&.drag-over {
background-color: #fa6;
color: black;
}
}
</style>

View File

@ -232,8 +232,8 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import BrowserDetect from '../../../ext/conf/BrowserDetect'; import BrowserDetect from '@src/ext/conf/BrowserDetect';
import JsonEditor from '@csui/src/components/JsonEditor'; import JsonEditor from '@components/common/JsonEditor.vue';
import {AardPollingOptions} from '@src/ext/lib/aard/enums/aard-polling-options.enum'; 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'; import {AardSubtitleCropMode} from '@src/ext/lib/aard/enums/aard-subtitle-crop-mode.enum';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';

View File

@ -92,7 +92,7 @@
<script lang="ts"> <script lang="ts">
import { LogAggregator, BLANK_LOGGER_CONFIG } from '@src/ext/lib/logging/LogAggregator'; import { LogAggregator, BLANK_LOGGER_CONFIG } from '@src/ext/lib/logging/LogAggregator';
import JsonEditor from '@csui/src/components/JsonEditor'; import JsonEditor from '@components/common/JsonEditor.vue';
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
export default defineComponent({ export default defineComponent({

View File

@ -12,7 +12,7 @@
<ul> <ul>
<li>Autodetection can be set to stop after first aspect ratio detection, or after a period of no changes.</li> <li>Autodetection can be set to stop after first aspect ratio detection, or after a period of no changes.</li>
<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/> There is a new, experimental 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. 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> </li>
<li> <li>

View File

@ -153,9 +153,9 @@
</template> </template>
<script> <script>
import SiteExtensionSettings from '@components/ExtensionSettings/Panels/SiteExtensionSettings.vue'; import SiteExtensionSettings from '@components/segments/ExtensionSettings/Panels/SiteExtensionSettings.vue';
import FrameSiteSettings from '@components/ExtensionSettings/Panels/FrameSiteSettings.vue'; import FrameSiteSettings from '@components/segments/ExtensionSettings/Panels/FrameSiteSettings.vue';
import OtherSiteSettings from '@components/ExtensionSettings/Panels/OtherSiteSettings.vue'; import OtherSiteSettings from '@components/segments/ExtensionSettings/Panels/OtherSiteSettings.vue';
import Popup from '@csui/src/components/Popup'; import Popup from '@csui/src/components/Popup';
import ConfirmButton from '@csui/src/components/ConfirmButton'; import ConfirmButton from '@csui/src/components/ConfirmButton';
import UploadJsonFileButton from '@csui/src/components/UploadJsonFileButton'; import UploadJsonFileButton from '@csui/src/components/UploadJsonFileButton';

View File

@ -18,25 +18,25 @@
(Site uses advanced settings) (Site uses advanced settings)
</option> </option>
<template v-if="isDefaultConfiguration"> <template v-if="isDefaultConfiguration">
<option value="disabled"> <option :value="ExtensionMode.Disabled">
Disabled by default Disabled by default
</option> </option>
</template> </template>
<template v-else> <template v-else>
<option value="default"> <option :value="ExtensionMode.Default">
Use default ({{simpleDefaultSettings.enable}}) Use default ({{simpleDefaultSettings.enable}})
</option> </option>
<option value="disabled"> <option :value="ExtensionMode.Disabled">
Never Never
</option> </option>
</template> </template>
<option value="fs"> <option :value="ExtensionMode.FullScreen">
Fullscreen only Fullscreen only
</option> </option>
<option value="theater"> <option :value="ExtensionMode.Theater">
Fullscreen and theater mode Fullscreen and theater mode
</option> </option>
<option value="enabled"> <option :value="ExtensionMode.All">
Always Always
</option> </option>
</select> </select>
@ -63,25 +63,25 @@
(Site uses advanced settings) (Site uses advanced settings)
</option> </option>
<template v-if="isDefaultConfiguration"> <template v-if="isDefaultConfiguration">
<option value="disabled"> <option :value="ExtensionMode.Disabled">
Disabled by default Disabled by default
</option> </option>
</template> </template>
<template v-else> <template v-else>
<option value="default"> <option :value="ExtensionMode.Default">
Use default ({{simpleDefaultSettings.enableAard}}) Use default ({{simpleDefaultSettings.enableAard}})
</option> </option>
<option value="disabled"> <option :value="ExtensionMode.Disabled">
Never Never
</option> </option>
</template> </template>
<option value="fs"> <option :value="ExtensionMode.FullScreen">
Fullscreen only Fullscreen only
</option> </option>
<option value="theater"> <option :value="ExtensionMode.Theater">
Fullscreen and theater mode Fullscreen and theater mode
</option> </option>
<option value="enabled"> <option :value="ExtensionMode.All">
Always Always
</option> </option>
</select> </select>
@ -106,25 +106,25 @@
(Site uses advanced settings) (Site uses advanced settings)
</option> </option>
<template v-if="isDefaultConfiguration"> <template v-if="isDefaultConfiguration">
<option value="disabled"> <option :value="ExtensionMode.Disabled">
Disabled by default Disabled by default
</option> </option>
</template> </template>
<template v-else> <template v-else>
<option value="default"> <option :value="ExtensionMode.Default">
Use default ({{simpleDefaultSettings.enableKeyboard}}) Use default ({{simpleDefaultSettings.enableKeyboard}})
</option> </option>
<option value="disabled"> <option :value="ExtensionMode.Disabled">
Never Never
</option> </option>
</template> </template>
<option value="fs"> <option :value="ExtensionMode.FullScreen">
Fullscreen only Fullscreen only
</option> </option>
<option value="theater"> <option :value="ExtensionMode.Theater">
Fullscreen and theater mode Fullscreen and theater mode
</option> </option>
<option value="enabled"> <option :value="ExtensionMode.All">
Always Always
</option> </option>
</select> </select>
@ -143,23 +143,26 @@
@click="setExtensionMode('enableUI', $event)" @click="setExtensionMode('enableUI', $event)"
> >
<template v-if="isDefaultConfiguration"> <template v-if="isDefaultConfiguration">
<option value="disabled"> <option :value="ExtensionMode.Disabled">
Disabled by default Disabled by default
</option> </option>
</template> </template>
<template v-else> <template v-else>
<option value="default"> <option :value="ExtensionMode.Default">
Use default ({{simpleDefaultSettings.enableUI}}) Use default ({{simpleDefaultSettings.enableUI}})
</option> </option>
<option value="disabled"> <option :value="ExtensionMode.Disabled">
Never Never
</option> </option>
</template> </template>
<option value="fs"> <option :value="ExtensionMode.FullScreen">
Fullscreen only Fullscreen only
</option> </option>
<option value="theater"> <option :value="ExtensionMode.Theater">
Always where possible Fullscreen and theater
</option>
<option :value="ExtensionMode.Theater">
Where possible
</option> </option>
</select> </select>
</div> </div>
@ -296,7 +299,7 @@
v-if="!isDefaultConfiguration" v-if="!isDefaultConfiguration"
:value="CropModePersistence.Default" :value="CropModePersistence.Default"
> >
Use default ({{defaultPersistanceLabel}}) Use default ({{defaultPersistenceLabel}})
</option> </option>
<option :value="CropModePersistence.Disabled">Disabled</option> <option :value="CropModePersistence.Disabled">Disabled</option>
<option :value="CropModePersistence.UntilPageReload">Until page reload</option> <option :value="CropModePersistence.UntilPageReload">Until page reload</option>
@ -306,6 +309,14 @@
</div> </div>
</div> </div>
</div> </div>
<pre>
SITE SETTINGS: raw
{{JSON.stringify(siteSettings.raw)}}
SITE SETTINGS: data
{{JSON.stringify(siteSettings.data)}}
</pre>
</div> </div>
</template> </template>
@ -390,7 +401,7 @@ export default defineComponent({
siteDefaultCropPersistence() { siteDefaultCropPersistence() {
return this.siteSettings.raw?.persistCSA ?? undefined; return this.siteSettings.raw?.persistCSA ?? undefined;
}, },
defaultPersistanceLabel() { defaultPersistenceLabel() {
switch (this.siteSettings.defaultSettings.persistCSA) { switch (this.siteSettings.defaultSettings.persistCSA) {
case CropModePersistence.CurrentSession: case CropModePersistence.CurrentSession:
return 'current session'; return 'current session';
@ -427,6 +438,8 @@ export default defineComponent({
break; break;
} }
console.log('compiling simple settings;', settingsData, 'original settings:', this.siteSettings)
return settingsData?.[component]; return settingsData?.[component];
}, },
@ -521,7 +534,7 @@ export default defineComponent({
this._computedWatchers?.siteDefaultStretch?.run(); this._computedWatchers?.siteDefaultStretch?.run();
this._computedWatchers?.siteDefaultAlignment?.run(); this._computedWatchers?.siteDefaultAlignment?.run();
this._computedWatchers?.siteDefaultCropPersistence?.run(); this._computedWatchers?.siteDefaultCropPersistence?.run();
this._computedWatchers?.defaultPersistanceLabel?.run(); this._computedWatchers?.defaultPersistenceLabel?.run();
this.$nextTick( () => this.$forceUpdate()); this.$nextTick( () => this.$forceUpdate());
}, },
@ -534,10 +547,15 @@ export default defineComponent({
setExtensionMode(component, event) { setExtensionMode(component, event) {
const option = event.target.value; const option = event.target.value;
console.log('set extension mode - we received option', option, 'for component', component);
return;
if (option === 'complex') { if (option === 'complex') {
return; return;
} }
if (component === 'enable' && !this.isDefaultConfiguration) { if (component === 'enable' && !this.isDefaultConfiguration) {
this.setExtensionMode('enableAard', event); this.setExtensionMode('enableAard', event);
this.setExtensionMode('enableKeyboard', event); this.setExtensionMode('enableKeyboard', event);
@ -550,41 +568,6 @@ export default defineComponent({
} }
} }
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
});
}
} }
} }

View File

@ -0,0 +1,190 @@
<template>
<div class="flex flex-col w-full max-w-[960px]">
<h2 text="text-[1.75rem]">Import &amp; export settings</h2>
<!-- Reset options -->
<h3>Import &amp; export</h3>
<p>
Note that settings may contain the sites you visited and used this extension on. If you are being asked to share settings for debugging a bug report
and are concerned with your privacy <small>(which you should)</small>, select partial export and only select the websites relevant to your issue.
</p>
<div class="flex flex-row w-full gap-2">
<UploadJsonFileButton
@importedJson="handleImportedSettings"
@error="handleSettingsImportError"
>
Import settings
</UploadJsonFileButton>
<button @click="exportSettings()">Export settings</button>
<button @click="startPartialExport()">Partial export </button>
</div>
<h3>Reset settings</h3>
<p>Reset settings to this version's default settings. Your custom settings will be lost.</p>
<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>
<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>
<Popup
v-if="partialExportDialogConfig.visible"
title="Custom settings export"
confirmButtonText="Export"
cancelButtonText="Cancel"
@onConfirm="partialExportDialogConfig.confirm"
@onCancel="partialExportDialogConfig.reject"
>
<p>Export settings for the following websites:</p>
<div class="flex flex-col my-4">
<div v-for="site in userSites" :key="site"
class="border border-stone-700 px-4 py-2 flex row gap-4"
>
<input type="checkbox" v-model="site.selectedForExport" :disabled="site.key === '@global'"> {{site.key}} <SupportLevelIndicator :siteSupportLevel="site.type ?? site.defaultType ?? 'problemo'"></SupportLevelIndicator>
</div>
</div>
</Popup>
</template>
<script lang="ts">
import Popup from '@components/common/Popup.vue';
import ConfirmButton from '@components/common/ConfirmButton.vue';
import UploadJsonFileButton from '@components/common/UploadJsonFileButton.vue';
import { SiteSettingsInterface } from '@src/common/interfaces/SettingsInterface';
import SupportLevelIndicator from '@components/common/SupportLevelIndicator.vue';
import { SiteSupportLevel } from '../../../../common/enums/SiteSupportLevel.enum';
interface ExportSiteData extends SiteSettingsInterface {
key: string,
selectedForExport: boolean;
};
export default {
components: {
Popup,
ConfirmButton,
UploadJsonFileButton,
SupportLevelIndicator,
},
mixins: [],
props: [
'settings',
],
data() {
return {
tab: 'siteSettings',
importSettingDialogConfig: {visible: false},
partialExportDialogConfig: {visible: false},
allowSettingsEditing: false,
editorSaveFinished: false,
settingsJson: {},
settingsSnapshots: []
}
},
computed: {
userSites() {
return this.partialExportDialogConfig.filter(
(x: ExportSiteData) => (x.type ?? x.defaultType) === SiteSupportLevel.UserDefined || (x.type ?? x.defaultType) === SiteSupportLevel.UserModified);
},
},
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);
},
startPartialExport() {
console.log('starting partial export ...');
const sites: ExportSiteData[] = [];
for (const site in this.settings.active.sites) {
sites.push({
...JSON.parse(JSON.stringify(this.settings.active.sites[site])),
key: site,
selectedForExport: true
});
}
this.partialExportDialogConfig = {
visible: true,
sites,
confirm: () => {
console.log('selected sites:', this.partialExportDialogConfig.sites.filter(x => x.selectedForExport));
},
reject: () => {
this.partialExportDialogConfig = {visible: false};
}
}
},
handleImportedSettings(newSettings) {
this.importSettingDialogConfig = {
visible: true,
confirm: () => {
this.settings.active = newSettings;
this.settings.saveWithoutReload();
this.importSettingDialogConfig = {visible: false};
},
reject: () => {
this.importSettingDialogConfig = {visible: false};
}
}
},
handleSettingsImportError(error) {
console.error('Error importing settings:', error);
},
resetSettings() {
this.settings.active = JSON.parse(JSON.stringify(this.settings.default));
this.settings.saveWithoutReload();
},
}
}
</script>

View File

@ -1,4 +1,4 @@
@import '../../../../main.css'; /** postcss processor doesn't support aliases */ @import '@src/main.css'; /** postcss processor doesn't support aliases */
.keyboard-shortcut-row { .keyboard-shortcut-row {
@apply py-2 px-4 @apply py-2 px-4

View File

@ -30,7 +30,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import KeyboardShortcutParserMixin from '../../../../csui/src/utils/KeyboardShortcutParserMixin'; import KeyboardShortcutParserMixin from '@ui/utils/mixins/KeyboardShortcutParserMixin.vue';
import EditShortcutButton from './EditShortcutButton.vue'; import EditShortcutButton from './EditShortcutButton.vue';
export default defineComponent({ export default defineComponent({

View File

@ -155,7 +155,6 @@ import AspectRatioType from '@src/common/enums/AspectRatioType.enum';
import StretchType from '@src/common/enums/StretchType.enum'; import StretchType from '@src/common/enums/StretchType.enum';
import NewAspectRatioForm from './Components/NewAspectRatioForm.vue'; import NewAspectRatioForm from './Components/NewAspectRatioForm.vue';
import BrowserDetect from '@src/ext/conf/BrowserDetect';
import CropOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/CropOptionsPanel.vue' import CropOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/CropOptionsPanel.vue'
import StretchOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/StretchOptionsPanel.vue' import StretchOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/StretchOptionsPanel.vue'
import ZoomOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/ZoomOptionsPanel.vue' import ZoomOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/ZoomOptionsPanel.vue'

View File

@ -113,12 +113,12 @@ import { defineComponent } from 'vue';
import BrowserDetect from '@src/ext/conf/BrowserDetect'; import BrowserDetect from '@src/ext/conf/BrowserDetect';
import ShortcutButton from './components/ShortcutButton.vue'; import ShortcutButton from './components/ShortcutButton.vue';
import CommsMixin from '@components/common/mixins/CommsMixin.vue'; import CommsMixin from '@ui/utils/mixins/CommsMixin.vue';
import KeyboardShortcutParserMixin from '@components/common/mixins/KeyboardShortcutParserMixin.vue'; import KeyboardShortcutParserMixin from '@ui/utils/mixins/KeyboardShortcutParserMixin.vue';
import alignmentIndicatorSvg from '!!raw-loader!@ui/res/img/alignment-indicators.svg?raw'; import alignmentIndicatorSvg from '!!raw-loader!@ui/res/img/alignment-indicators.svg?raw';
import {setupVideoAlignmentIndicatorInteraction, setVideoAlignmentIndicatorState} from '@ui/utils/video-alignment-indicator-handling.ts'; import {setupVideoAlignmentIndicatorInteraction, setVideoAlignmentIndicatorState} from '@ui/utils/video-alignment-indicator-handling';
import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum'; import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum';
export default defineComponent({ export default defineComponent({
components: { components: {
@ -227,7 +227,7 @@ export default defineComponent({
}); });
</script> </script>
<style lang="postcss" scoped> <style lang="postcss" scoped>
@import '../../../main.css'; @import '@src/main.css';
.button-container { .button-container {
@apply w-full pt-2 @apply w-full pt-2

View File

@ -61,7 +61,7 @@
</template> </template>
<script> <script>
import BrowserDetect from '../../../ext/conf/BrowserDetect'; import BrowserDetect from '@src/ext/conf/BrowserDetect';
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator'; import { LogAggregator } from '@src/ext/lib/logging/LogAggregator';
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger'; import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';

View File

@ -55,7 +55,7 @@ import EventBus from '@src/ext/lib/EventBus';
import CommsClient, { CommsOrigin } from '@src/ext/lib/comms/CommsClient'; import CommsClient, { CommsOrigin } from '@src/ext/lib/comms/CommsClient';
import {ChromeShittinessMitigations as CSM} from '@src/common/js/ChromeShittinessMitigations'; import {ChromeShittinessMitigations as CSM} from '@src/common/js/ChromeShittinessMitigations';
import PopupHead from '@components/PopupHead/PopupHead.vue'; import PopupHead from '@components/PopupHead.vue';
export default defineComponent({ export default defineComponent({
components: { components: {

View File

@ -1,6 +1,6 @@
@font-face { @font-face {
font-family: "Heebo"; font-family: "Heebo";
src: url("../fonts/Heebo.ttf") format("truetype-variations"); src: url("@res/fonts/Heebo.ttf") format("truetype-variations");
font-weight: 100 900; font-weight: 100 900;
font-stretch: 75% 125%; font-stretch: 75% 125%;
font-style: normal; font-style: normal;
@ -9,7 +9,7 @@
@font-face { @font-face {
font-family: "Source Code Pro"; font-family: "Source Code Pro";
src: url("../fonts/SourceCodePro.ttf") format("truetype-variations"); src: url("@res/fonts/SourceCodePro.ttf") format("truetype-variations");
font-weight: 200 900; font-weight: 200 900;
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
@ -17,7 +17,7 @@
@font-face { @font-face {
font-family: "Source Code Pro"; font-family: "Source Code Pro";
src: url("../fonts/SourceCodePro-Italic.ttf") format("truetype-variations"); src: url("@res/fonts/SourceCodePro-Italic.ttf") format("truetype-variations");
font-weight: 200 900; font-weight: 200 900;
font-style: italic; font-style: italic;
font-display: swap; font-display: swap;

View File

@ -0,0 +1,20 @@
@import "tailwindcss";
@import "tailwindcss/utilities";
@theme {
--breakpoint-popup: 640px;
--breakpoint-window: 960px;
--color-primary: #ff872c;
--color-primary-50: #ffddbe;
--color-primary-100: #ffcda0;
--color-primary-200: #ffbd83;
--color-primary-300: #ffac66;
--color-primary-400: #ff9a4a;
--color-primary-500: #ff872c;
--color-primary-600: #de7622;
--color-primary-700: #be6518;
--color-primary-800: #9f540e;
--color-primary-900: #824406;
--color-primary-950: #663400;
}

281
src/ui/utils/svg.menu.ts Normal file
View File

@ -0,0 +1,281 @@
import { CommandInterface } from '@src/common/interfaces/SettingsInterface';
import Settings from '@src/ext/lib/settings/Settings';
const SVG_NS = "http://www.w3.org/2000/svg";
class MenuItemConfig {
textSize: number;
width: number
height: number;
xPad: number;
yPad: number;
constructor(options: {textSize: number, width: number, xPad: number, yPad: number}) {
this.textSize = options.textSize;
this.xPad = options.xPad;
this.yPad = options.yPad;
this.height = options.textSize + 2 * options.yPad;
this.width = options.width;
}
}
class MenuItem {
label: string;
hasHover: boolean;
clickAction: () => void;
parent?: MenuItem;
children?: MenuItem[];
svg: any;
static config: MenuItemConfig;
static fromCommand(command: CommandInterface, executor: (command: string, args?: any) => void, parent?: MenuItem) {
return new MenuItem({
label: command.label,
clickAction: () => {executor(command.action, command.arguments)},
parent: parent
});
}
constructor(data: any) {
this.label = data.label;
this.clickAction = data.clickAction;
this.hasHover = false;
this.children = [] as MenuItem[];
};
private createSvg() {
const group = document.createElementNS(SVG_NS, "g");
const textElem = document.createElementNS(SVG_NS, "text");
textElem.textContent = this.label;
textElem.setAttribute('x', `${MenuItem.config.xPad}`);
textElem.setAttribute('y', `${MenuItem.config.yPad}`);
group.appendChild(textElem);
// Measure text width safely
// const width = textElem.getComputedTextLength() + 2 * hPadding;
// const height = fontSize + 2 * vPadding;
// Background rectangle
const rect = document.createElementNS(SVG_NS, "rect");
rect.setAttribute('x', '0');
rect.setAttribute('y', `0`);
rect.setAttribute('width', `${MenuItem.config.width}`);
rect.setAttribute('height', `${MenuItem.config.height}`);
group.insertBefore(rect, textElem);
this.svg = group;
}
addSubitem(subitem: MenuItem) {
this.children.push(subitem);
if (!subitem.parent) {
subitem.parent = this;
}
}
}
function buildMenu(settings: Settings, executor: (command: string, args?: any) => void) {
const menu = new MenuItem(
{label: 'Ultrawidify'}
);
const topLevelItems: { label: string; submenu?: CommandInterface[]; isZoom?: boolean }[] = [
{ label: "Ultrawidify" },
...(settings.active.commands.crop ? [{ label: "Crop", submenu: settings.active.commands.crop }] : []),
...(settings.active.commands.stretch ? [{ label: "Stretch", submenu: settings.active.commands.stretch }] : []),
...(settings.active.commands.zoom ? [{ label: "Zoom", submenu: settings.active.commands.zoom, isZoom: true }] : []),
{ label: "Alignment", submenu: [] },
{ label: "Extension settings" },
{ label: "Incorrect cropping?" },
{ label: "Report problem" }
];
}
function createMenuItem(label) {
}
export function createSvgMenu(playerEl: HTMLElement, settings: Settings) {
if (!settings) {
console.warn('trying to create menu, but settings are not defined:', settings, 'player el:', playerEl);
return;
}
const style = document.createElement('style');
style.textContent = `
@font-face {
font-family: 'Heebo';
src: url(${chrome.runtime.getURL('/ui/res/fonts/Heebo.tff')}) format('truetype');
}
@font-face {
font-family: 'SourceCodePro';
src: url(${chrome.runtime.getURL('/ui/res/fonts/SourceCodePro.tff')}) format('truetype');
}
#_uw_player_menu { width:100%; height:100%; position:absolute; top:0; left:0; pointer-events:none; }
#_uw_player_menu {
.menu { pointer-events:auto; }
.menu-item text { font-family: 'Heebo'; font-size:14px; fill:#fff; }
.submenu { display:none; }
.menu-item:hover > .submenu { display:block; }
.hover-zone { fill:transparent; pointer-events:auto; }
.menu-item rect { fill:#222; rx:4; }
.menu-item:hover > rect { fill:#444; }
}
`;
const svg = document.createElementNS(SVG_NS, "svg");
svg.setAttribute('id', '_uw_player_menu');
svg.append(style);
playerEl.appendChild(svg);
// shadow.appendChild(svg);
// menuGroup.setAttribute('class', 'menu');
svg.appendChild(menuGroup);
const fontSize = 14;
const vPadding = fontSize * 0.4;
const hPadding = fontSize * 0.6;
let currentY = 0;
// --- Top level items ---
const topLevelItems: { label: string; submenu?: CommandInterface[]; isZoom?: boolean }[] = [
{ label: "Ultrawidify" },
...(settings.active.commands.crop ? [{ label: "Crop", submenu: settings.active.commands.crop }] : []),
...(settings.active.commands.stretch ? [{ label: "Stretch", submenu: settings.active.commands.stretch }] : []),
...(settings.active.commands.zoom ? [{ label: "Zoom", submenu: settings.active.commands.zoom, isZoom: true }] : []),
{ label: "Alignment", submenu: [] },
{ label: "Extension settings" },
{ label: "Incorrect cropping?" },
{ label: "Report problem" }
];
topLevelItems.forEach(item => {
const itemGroup = document.createElementNS(SVG_NS, "g");
itemGroup.setAttribute('class','menu-item');
menuGroup.appendChild(itemGroup);
// Text element
const textElem = document.createElementNS(SVG_NS, "text");
textElem.textContent = item.label;
textElem.setAttribute('x', `${hPadding}`);
textElem.setAttribute('y', `${currentY + fontSize}`);
itemGroup.appendChild(textElem);
// Measure text width safely
const width = textElem.getComputedTextLength() + 2 * hPadding;
const height = fontSize + 2 * vPadding;
// Background rectangle
const rect = document.createElementNS(SVG_NS, "rect");
rect.setAttribute('x', '0');
rect.setAttribute('y', `${currentY}`);
rect.setAttribute('width', `${width}`);
rect.setAttribute('height', `${height}`);
itemGroup.insertBefore(rect, textElem);
// --- Submenu ---
if(item.submenu && item.submenu.length > 0) {
const submenuGroup = document.createElementNS(SVG_NS, "g");
submenuGroup.setAttribute('class','submenu');
itemGroup.appendChild(submenuGroup);
let subY = currentY;
item.submenu.forEach(cmd => {
const subGroup = document.createElementNS(SVG_NS, "g");
subGroup.setAttribute('class','menu-item');
submenuGroup.appendChild(subGroup);
// Submenu text
const subText = document.createElementNS(SVG_NS, "text");
subText.textContent = cmd.label + (cmd.shortcut ? ` (${cmd.shortcut.key})` : '');
subText.setAttribute('x', `${width + hPadding}`);
subText.setAttribute('y', `${subY + fontSize}`);
subGroup.appendChild(subText);
const subWidth = subText.getComputedTextLength() + 2*hPadding;
const subHeight = fontSize + 2*vPadding;
const subRect = document.createElementNS(SVG_NS, "rect");
subRect.setAttribute('x', `${width}`);
subRect.setAttribute('y', `${subY}`);
subRect.setAttribute('width', `${subWidth}`);
subRect.setAttribute('height', `${subHeight}`);
subGroup.insertBefore(subRect, subText);
subY += subHeight;
});
// Zoom slider
if(item.isZoom) {
const sliderGroup = document.createElementNS(SVG_NS, "foreignObject");
sliderGroup.setAttribute('x', `${width}`);
sliderGroup.setAttribute('y', `${subY}`);
sliderGroup.setAttribute('width', '150');
sliderGroup.setAttribute('height', '40');
sliderGroup.innerHTML = `<div style="width:100%;height:100%;background:#333;color:#fff;font-family:Heebo;padding:5px;">Zoom slider placeholder</div>`;
submenuGroup.appendChild(sliderGroup);
}
// Hover retention zone
const hoverZone = document.createElementNS(SVG_NS, "rect");
hoverZone.setAttribute('class','hover-zone');
hoverZone.setAttribute('x', `${width}`);
hoverZone.setAttribute('y', `${currentY}`);
hoverZone.setAttribute('width', '150');
hoverZone.setAttribute('height', `${subY - currentY}`);
itemGroup.appendChild(hoverZone);
}
currentY += height;
});
// --- Trigger logic ---
const activation = settings.active.ui.inPlayer.activation;
const triggerZone = settings.active.ui.inPlayer.triggerZoneDimensions;
const distanceOptions = settings.active.ui.inPlayer.distanceOptions;
function updateMenuVisibility(x: number, y: number) {
let show = false;
const rect = playerEl.getBoundingClientRect();
if(activation === 'player') {
if(x >= 0 && x <= rect.width && y >= 0 && y <= rect.height) show = true;
} else if(activation === 'trigger-zone' && triggerZone) {
const tx = rect.width * (triggerZone.offsetX / 100);
const ty = rect.height * (triggerZone.offsetY / 100);
if(x >= tx && x <= tx + triggerZone.width && y >= ty && y <= ty + triggerZone.height) show = true;
} else if(activation === 'distance' && distanceOptions) {
const dist = distanceOptions.distance / 100 * (distanceOptions.relativeTo === 'width' ? rect.width : rect.height);
const cx = rect.width/2;
const cy = rect.height/2;
if(Math.sqrt((x-cx)**2 + (y-cy)**2) <= dist) show = true;
}
menuGroup.style.display = show ? 'block' : 'none';
}
svg.addEventListener('mousemove', e => {
const rect = svg.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
updateMenuVisibility(x,y);
});
menuGroup.style.display = 'none';
return { svg, menuGroup };
}

View File

@ -5,6 +5,7 @@ const WebpackShellPluginNext = require('webpack-shell-plugin-next');
const CopyWebpackPlugin = require('copy-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin');
const { VueLoaderPlugin } = require('vue-loader'); const { VueLoaderPlugin } = require('vue-loader');
const path = require('path'); const path = require('path');
const aliases = require('./paths.config');
const config = { const config = {
watchOptions: { watchOptions: {
@ -32,13 +33,7 @@ const config = {
devtool: "source-map", devtool: "source-map",
resolve: { resolve: {
alias: { alias: aliases, // SEE: paths.conf.js
'@src': path.resolve(__dirname, 'src'),
'@csui': path.resolve(__dirname, 'src/csui'),
'@ui': path.resolve(__dirname, 'src/ui'),
'@components': path.resolve(__dirname, 'src/ui/components'),
'@': path.resolve(__dirname, 'src'),
},
// maybe we'll move vue stuff to TS some day, but today is not the day // maybe we'll move vue stuff to TS some day, but today is not the day
extensions: [ extensions: [
'.ts', '.tsx', '.ts', '.tsx',