More migrating

This commit is contained in:
Tamius Han 2025-12-11 02:43:39 +01:00
parent fe2f52d76a
commit fb7f5cf1ba
10 changed files with 348 additions and 241 deletions

10
package-lock.json generated
View File

@ -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",
@ -13496,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",

View File

@ -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",

View File

@ -11,32 +11,26 @@
: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">
<option :value="ExtensionMode.Disabled">
Disabled by default
</option>
</template>
<template v-else>
<option value="default">
Use default ({{simpleDefaultSettings.enable}})
<option :value="ExtensionMode.Default">
Use default ({{simpleDefaultSettings.enableAard}})
</option>
<option value="disabled">
<option :value="ExtensionMode.Disabled">
Never
</option>
</template>
<option value="fs">
<option :value="ExtensionMode.FullScreen">
Fullscreen only
</option>
<option value="theater">
<option :value="ExtensionMode.Theater">
Fullscreen and theater mode
</option>
<option value="enabled">
<option :value="ExtensionMode.All">
Always
</option>
</select>
@ -56,32 +50,26 @@
: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">
<option :value="ExtensionMode.Disabled">
Disabled by default
</option>
</template>
<template v-else>
<option value="default">
<option :value="ExtensionMode.Default">
Use default ({{simpleDefaultSettings.enableAard}})
</option>
<option value="disabled">
<option :value="ExtensionMode.Disabled">
Never
</option>
</template>
<option value="fs">
<option :value="ExtensionMode.FullScreen">
Fullscreen only
</option>
<option value="theater">
<option :value="ExtensionMode.Theater">
Fullscreen and theater mode
</option>
<option value="enabled">
<option :value="ExtensionMode.All">
Always
</option>
</select>
@ -99,32 +87,26 @@
: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">
<option :value="ExtensionMode.Disabled">
Disabled by default
</option>
</template>
<template v-else>
<option value="default">
Use default ({{simpleDefaultSettings.enableKeyboard}})
<option :value="ExtensionMode.Default">
Use default ({{simpleDefaultSettings.enableAard}})
</option>
<option value="disabled">
<option :value="ExtensionMode.Disabled">
Never
</option>
</template>
<option value="fs">
<option :value="ExtensionMode.FullScreen">
Fullscreen only
</option>
<option value="theater">
<option :value="ExtensionMode.Theater">
Fullscreen and theater mode
</option>
<option value="enabled">
<option :value="ExtensionMode.All">
Always
</option>
</select>
@ -143,23 +125,26 @@
@click="setExtensionMode('enableUI', $event)"
>
<template v-if="isDefaultConfiguration">
<option value="disabled">
<option :value="ExtensionMode.Disabled">
Disabled by default
</option>
</template>
<template v-else>
<option value="default">
Use default ({{simpleDefaultSettings.enableUI}})
<option :value="ExtensionMode.Default">
Use default ({{simpleDefaultSettings.enableAard}})
</option>
<option value="disabled">
<option :value="ExtensionMode.Disabled">
Never
</option>
</template>
<option value="fs">
<option :value="ExtensionMode.FullScreen">
Fullscreen only
</option>
<option value="theater">
Always where possible
<option :value="ExtensionMode.Theater">
Fullscreen and theater mode
</option>
<option :value="ExtensionMode.All">
Always
</option>
</select>
</div>
@ -425,64 +410,12 @@ export default {
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';
}
return settingsData[component];
},
getDefaultOptionLabel(component) {
const componentValue = this.compileSimpleSettings(component, 'default');
if (componentValue === 'loading') {
return componentValue;
}
if (component === 'enableUI') {
switch (componentValue) {
case 'fs':

View File

@ -17,7 +17,7 @@ import ExtensionMode from '../../common/enums/ExtensionMode.enum';
const ExtensionConfPatch = Object.freeze([
{
forVersion: '6.2.4',
updateFn: (userOptions: any, defaultOptions) => {
updateFn: (userOptions: any, defaultOptions, logger?) => {
for (const site in userOptions.sites) {
(userOptions as any).sites[site].enableUI = {
fullscreen: LegacyExtensionMode.Default,
@ -38,7 +38,7 @@ const ExtensionConfPatch = Object.freeze([
}
}, {
forVersion: '6.2.6',
updateFn: (userOptions: any, defaultOptions) => {
updateFn: (userOptions: any, defaultOptions, logger?) => {
console.log('[ultrawidify] Migrating settings — applying patches for version 6.2.6');
if (!userOptions.commands) {
@ -273,7 +273,7 @@ const ExtensionConfPatch = Object.freeze([
},
{
forVersion: '6.3.98',
upgradeFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface) => {
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface) => {
userOptions.aard = defaultOptions.aard;
userOptions.aardLegacy = defaultOptions.aardLegacy;
delete (userOptions as any).arDetect;
@ -281,8 +281,9 @@ const ExtensionConfPatch = Object.freeze([
},
{
forVersion: '6.3.994',
upgradeFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface) => {
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface, logger?) => {
const convertLegacyExtensionMode = (option: {normal: LegacyExtensionMode, theater: LegacyExtensionMode, fullscreen: LegacyExtensionMode}) => {
logger.log('updateFn', 'converting option', option);
if (option.normal === LegacyExtensionMode.Enabled) {
return ExtensionMode.All;
}

View File

@ -6,9 +6,10 @@ import ExtensionConfPatch from '../../conf/ExtConfPatches';
import SettingsInterface from '../../../common/interfaces/SettingsInterface';
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
import { GetSiteSettingsOptions, SiteSettings } from './SiteSettings';
import { SettingsSnapshotManager } from './SettingsSnapshotManager';
import { SettingsSnapshot, SettingsSnapshotManager } from './SettingsSnapshotManager';
import { ComponentLogger } from '../logging/ComponentLogger';
import { LogAggregator } from '../logging/LogAggregator';
import { getDiff } from 'json-difference'
if(process.env.CHANNEL !== 'stable'){
console.info("Loading Settings");
@ -100,11 +101,32 @@ class Settings {
// this.logger?.log('info', 'settings',"[Settings::<storage/on change>] new settings object:", JSON.parse(changes.uwSettings.newValue));
// }
const parsedSettings = JSON.parse(changes.uwSettings.newValue);
this.setActive(parsedSettings);
const diff = getDiff(this.active, parsedSettings, {isLodashLike: true});
const validChangeFn = (x: any) =>
x[0] !== 'lastModified';
const validAdditions = diff.added.filter(validChangeFn);
const validEdits = diff.edited.filter(validChangeFn);
const validRemovals = diff.removed.filter(validChangeFn).filter(x => x[0] !== 'version');
this.logger?.info('storageOnChange', "Diff between current and changed settings:", diff, 'valid changes:', {validAdditions, validEdits, validRemovals});
if (
validAdditions.length === 0
&& validRemovals.length === 0
&& validEdits.length === 0
) {
this.logger?.warn('storageOnChange', "storageChangeListener fired, but no changes were detected. Sus.\nchanges:", changes, "storage area:", area, 'current:', this.active, 'diff:', diff);
return;
}
this.setActive(parsedSettings);
this.logger?.info('storageOnChange', 'Does parsedSettings.preventReload exist?', parsedSettings.preventReload, "Does callback exist?", !!this.onSettingsChanged);
if (!parsedSettings.preventReload) {
this.active.preventReload = true;
try {
for (const fn of this.onChangedCallbacks) {
try {
@ -116,7 +138,6 @@ class Settings {
if (this.onSettingsChanged) {
this.onSettingsChanged();
}
this.logger?.info('storageOnChange', 'Update callback finished.')
} catch (e) {
this.logger?.error('storageOnChange', "CALLING UPDATE CALLBACK FAILED. Reason:", e)
@ -212,7 +233,7 @@ class Settings {
private findFirstNecessaryPatch(version) {
return ExtensionConfPatch.findIndex(x => this.compareExtensionVersions(x.forVersion, version) > 0);
}
private applySettingsPatches(oldVersion) {
private applySettingsPatches(currentSettings, oldVersion, options?: {skipSnapshot?: boolean}) {
let index = this.findFirstNecessaryPatch(oldVersion);
if (index === -1) {
@ -221,8 +242,7 @@ class Settings {
}
// save current settings object
const currentSettings = this.active;
if (!options?.skipSnapshot) {
this.snapshotManager.createSnapshot(
JSON.parse(JSON.stringify(currentSettings)),
{
@ -230,17 +250,22 @@ class Settings {
isAutomatic: true
}
);
// this.active.newFeatureTracker = {};
}
// apply all remaining patches
this.logger?.info('applySettingsPatches', `There are ${ExtensionConfPatch.length - index} settings patches to apply`);
const pendingPatchesCount = ExtensionConfPatch.length - index;
this.logger?.info('applySettingsPatches', `There are ${pendingPatchesCount} settings patches to apply`);
const defaultSettings = this.getDefaultSettings();
while (index < ExtensionConfPatch.length) {
const updateFn = ExtensionConfPatch[index].updateFn;
this.logger.info('applySettingsPatches', `Processing patch ${index} / ${ExtensionConfPatch.length}. Patch ${!!updateFn ? 'has' : 'does NOT have'} an update function.`);
if (updateFn) {
try {
updateFn(this.active, this.getDefaultSettings());
this.logger.log('applySettingsPatches', `Starting to update patch for version ${ExtensionConfPatch[index].forVersion} (${pendingPatchesCount - (ExtensionConfPatch.length - index)} / ${pendingPatchesCount}) | index: ${index}, patches length: ${ExtensionConfPatch.length}`);
updateFn(currentSettings, defaultSettings, this.logger);
this.logger.log('applySettingsPatches', `Patch for version ${ExtensionConfPatch[index].forVersion} applied. (${pendingPatchesCount - (ExtensionConfPatch.length - index)} / ${pendingPatchesCount})`);
} catch (e) {
this.logger?.error('applySettingsPatches', 'Failed to execute update function. Keeping settings object as-is. Error:', e);
}
@ -248,30 +273,37 @@ class Settings {
index++;
}
return currentSettings;
}
async init() {
let settings = await this.get();
async init(options?: {dryRun?: boolean, snapshot?: SettingsSnapshot, skipSnapshot?: boolean}) {
let settings;
if (settings?.dev?.loadFromSnapshot) {
if (options?.snapshot) {
this.logger?.info('init', 'Snapshot was provided — will load settings from snapshot.', options.snapshot);
settings = options.snapshot.settings;
} else if (settings?.dev?.loadFromSnapshot) {
this.logger?.info('init', 'Dev mode is enabled, Loading settings from snapshot:', settings.dev.loadFromSnapshot);
const snapshot = await this.snapshotManager.getSnapshot();
if (snapshot) {
settings = snapshot.settings;
}
} else {
settings = await this.get();
}
this.version = this.getExtensionVersion();
const currentVersion = this.getExtensionVersion();
// |—> on first setup, settings is undefined & settings.version is haram
// | since new installs ship with updates by default, no patching is
// | needed. In this case, we assume we're on the current version
const oldVersion = settings?.version ?? this.version;
const oldVersion = settings?.version ?? currentVersion;
if (settings) {
this.logger?.info('init', "Configuration fetched from storage:", settings,
"\nlast saved with:", settings.version,
"\ncurrent version:", this.version
"\ncurrent version:", currentVersion
);
}
@ -284,40 +316,57 @@ class Settings {
'\nsettings:',
settings
);
this.active = this.getDefaultSettings();
this.active.version = this.version;
await this.save();
return this.active;
settings = this.getDefaultSettings();
settings.version = currentVersion;
if (!options?.dryRun) {
this.active = settings;
this.version = currentVersion;
await this.save();
}
return settings;
}
// if there's settings, set saved object as active settings
this.active = settings;
// if version number is undefined, we make it defined
// this should only happen on first extension initialization
if (!this.active.version) {
this.active.version = this.version;
if (!settings.version) {
settings.version = currentVersion;
if (!options?.dryRun) {
this.active = settings;
this.version = currentVersion;
await this.save();
return this.active;
}
return settings;
}
// check if extension has been updated. If not, return settings as they were retrieved
if (this.active.version === this.version) {
if (settings.version === currentVersion) {
this.logger?.info('init', "extension was saved with current version of ultrawidify. Returning object as-is.");
return this.active;
if (!options?.dryRun) {
this.active = settings;
}
return settings;
}
// in case settings in previous version contained a fucky wucky, we overwrite existing settings with a patch
this.applySettingsPatches(oldVersion);
settings = this.applySettingsPatches(settings, oldVersion, {skipSnapshot: options?.dryRun || options?.skipSnapshot});
// set 'whatsNewChecked' flag to false when updating, always
this.active.whatsNewChecked = false;
settings.whatsNewChecked = false;
// update settings version to current
this.active.version = this.version;
settings.version = currentVersion;
if (!options?.dryRun) {
this.active = settings;
this.version = currentVersion;
await this.save();
return this.active;
}
return settings;
}
async get(): Promise<SettingsInterface | undefined> {
@ -437,6 +486,11 @@ class Settings {
removeAfterChangeListener(fn: () => void): void {
this.afterSettingsChangedCallbacks = this.afterSettingsChangedCallbacks.filter(x => x !== fn);
}
async testMigration(snapshot: SettingsSnapshot) {
const afterMigrationSettings = await this.init({dryRun: true, snapshot});
this.logger.log('testMigration', 'Settings migrated. Settings model after initialization:', afterMigrationSettings);
}
}
export default Settings;

View File

@ -1,29 +1,93 @@
<template>
<div>
<h2 class="text-[1.75em]">Debugging</h2>
<div>
<h1>{{loggerPanel.title}}<small><br/>{{loggerPanel.subtitle}}</small></h1>
<div class="w-full grid grid-cols-1 min-[1200px]:grid-cols-2 min-[1920px]:grid-cols-3 max-w-[2300px] gap-8">
<div>Logger configuration:</div>
<div class="grow shrink">
<h3 class="mb-4">Settings editor</h3>
<div class="flex flex-row w-full">
<div class="flex flex-row items-center gap-2">
<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="{'opacity-50 select-none hover:border-stone-500 hover:text-stone-300': !allowSettingsEditing}"
:disabled="!allowSettingsEditing"
@click="() => saveSettingsChanges()"
>
Save
</button>
</div>
<button @click="resetSettingsEditor">
Cancel
</button>
</div>
<div>
<JsonEditor
v-model="settingsJson"
>
</JsonEditor>
</div>
</div>
<div class="grow shrink">
<h3 class="mb-4">Snapshots</h3>
<div class="flex flex-col gap-2">
<div
v-for="(snapshot, index) of settingsSnapshots" :key="snapshot.createdAt"
class="border border-stone-800 p-2"
>
<small>Created at: {{new Date(snapshot.createdAt).toISOString()}}</small>
<div class="flex flex-row">
<div class="grow font-bold">
{{snapshot.label ?? '<no label>'}}
</div>
<div class="mr-8">v. {{snapshot.settings.version ?? '<unknown ver.>'}}</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 class="flex flex-row w-full justify-end mt-2">
<button class="flex flex-col" @click="() => testMigration(index)">Test migration</button>
<button class="flex flex-col" @click="() => loadFromSnapshot(index)">Load snapshot and migrate<br/><small>normal migration</small></button>
<button class="flex flex-col" @click="() => loadFromSnapshot(index, true)">Load &amp; migrate<br/><small>without creating snapshot</small></button>
<button class="flex flex-col" @click="() => markDefaultSnapshot(index)"><template v-if="settings.isDefault">Revoke default</template><template v-else>Make default</template></button>
<button class="flex flex-col" v-if="settings.isAutomatic" @click="() => toggleSnapshotProtection(index)">Toggle protection</button>
<button class="flex flex-col" @click="() => deleteSnapshot(index)">Delete snapshot</button>
</div>
</div>
</div>
</div>
<div class="grow shrink">
<h3 class="mb-4">Logger configuration</h3>
<JsonEditor
v-model="lastSettings"
></JsonEditor>
<div class="flex flex-row flex-end">
<div class="button" @click="getLoggerSettings()">
<div class="flex flex-row justify-end mt-4 w-full">
<button class="bg-black button" @click="getLoggerSettings()">
Revert
</div>
<div class="button button-primary" @click="saveLoggerSettings()">
</button>
<button class="button button-primary" @click="saveLoggerSettings()">
Save
</div>
<div class="button button-primary" @click="startLogging()">
</button>
<button class="button button-primary" @click="startLogging()">
Save and start logging
</div>
</button>
</div>
</div>
</div>
</template>
<script lang="ts">
@ -67,7 +131,12 @@ export default defineComponent({
},
lastSettings: undefined,
parsedSettings: undefined,
currentSettings: undefined
currentSettings: undefined,
allowSettingsEditing: false,
editorSaveFinished: false,
settingsJson: {},
settingsSnapshots: []
};
},
props: [
@ -81,6 +150,11 @@ export default defineComponent({
};
this.getLoggerSettings();
},
mounted() {
this.loadSettingsSnapshots();
this.resetSettingsEditor();
this.loadSettingsSnapshots();
},
methods: {
loadDefaultConfig() {
this.lastSettings = JSON.parse(JSON.stringify(BLANK_LOGGER_CONFIG));
@ -98,6 +172,72 @@ export default defineComponent({
await LogAggregator.saveConfig({...this.lastSettings});
window.location.reload();
},
/**
* 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();
},
async loadFromSnapshot(snapshotIndex, skipSnapshot?: boolean) {
this.settings.init({snapshot: this.settingsSnapshots[snapshotIndex], skipSnapshot});
},
async testMigration(snapshotIndex) {
this.settings.testMigration(this.settingsSnapshots[snapshotIndex]);
},
}
//#endregion
});
</script>

View File

@ -2,19 +2,27 @@
<p class="mb-4">Full changelog for older versions <a href="https://github.com/tamius-han/ultrawidify/blob/master/CHANGELOG.md" target="_blank">is available here</a>.</p>
<p>Changes in version <b class="font-semibold text-primary-400">6.4.0</b>:</p>
<b class="text-white">Potentially breaking:</b>
<ul>
<li>De-spaghettified the part of the settings that controls whether extension runs on a given site or not.</li>
</ul>
<b class="text-white">Autodetection:</b>
<ul>
<li>In-player UI can now appear in full-screen for websites that put players into top layer (allegedly).</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>
<li>
Autodetection can now detect hardcoded subtitles. You may change how extension handles subtitles in the settings.
Use of new experimental autodetection is required for subtitle handling options to work.
Experimental autodetection can now detect hardcoded subtitles. You may change how extension handles subtitles in the settings.
</li>
</ul>
<b class="text-white">UI</b>
<ul>
<li>In-player UI can now appear in full-screen for websites that put players into top layer (allegedly).</li>
<li>
Keyboard shortcut settings have been split from UI settings, and are now presented in list form on the settings page.
</li>
@ -23,6 +31,12 @@
</li>
</ul>
<b class="text-white">Other updates and fixes:</b>
<ul>
<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>
</ul>
</template>
<script>
import BrowserDetect from '@src/ext/conf/BrowserDetect';

View File

@ -12,8 +12,8 @@
@click="setExtensionMode('enable', $event)"
>
<option
v-if="simpleExtensionSettings.enable === 'complex'"
value="complex"
v-if="simpleExtensionSettings.enable === 'invalid'"
:value="undefined"
>
(Site uses advanced settings)
</option>
@ -412,7 +412,7 @@ export default defineComponent({
/**
* Compiles our extension settings into more user-friendly options
*/
compileSimpleSettings(component, getFor = 'site') {
compileSimpleSettings(component, getFor = 'site'): ExtensionMode {
let settingsData;
switch (getFor) {
@ -427,86 +427,25 @@ export default defineComponent({
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';
}
return settingsData?.[component];
},
getDefaultOptionLabel(component) {
const componentValue = this.compileSimpleSettings(component, 'default');
const componentValue: ExtensionMode = this.compileSimpleSettings(component, 'default');
if (componentValue === 'loading') {
return componentValue;
}
if (component === 'enableUI') {
switch (componentValue) {
case 'fs':
case ExtensionMode.Disabled:
return 'disabled';
case ExtensionMode.Default:
return 'default';
case ExtensionMode.FullScreen:
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':
case ExtensionMode.Theater:
return 'fullscreen & theater';
case ExtensionMode.All:
return 'always';
case 'disabled':
return 'disabled';
case 'default':
return 'complex'
default:
return '<Invalid value>';
}
},

View File

@ -168,12 +168,12 @@
>
</PlayerDetectionPanel>
<DebugPanel
<Debugging
v-if="selectedTab === 'debugging'"
:settings="settings"
:eventBus="eventBus"
:site="site"
></DebugPanel>
></Debugging>
<!-- <ResetBackupPanel
v-if="selectedTab === 'resetBackup'"
@ -194,11 +194,11 @@ import UISettings from '@components/UISettings/UISettings.vue';
import KeyboardShortcutSettings from '@components/KeyboardShortcuts/KeyboardShortcutSettings.vue';
import SiteExtensionSettings from '@components/ExtensionSettings/Panels/SiteExtensionSettings.vue';
import FrameSiteSettings from '@components/ExtensionSettings/Panels/FrameSiteSettings.vue';
import Debugging from '@components/Debugging/Debugging.vue';
import WhatsNew from '@components/ExtensionInfo/WhatsNew.vue';
import About from '@components/ExtensionInfo/About.vue';
import DebugPanel from '../../csui/src/PlayerUiPanels/DebugPanel.vue'
import PlayerDetectionPanel from '../../csui/src/PlayerUiPanels/PlayerDetectionPanel.vue'
import BrowserDetect from '../../ext/conf/BrowserDetect'
import ChangelogPanel from '../../csui/src/PlayerUiPanels/ChangelogPanel.vue'
@ -257,12 +257,12 @@ export default defineComponent({
UISettings,
SiteExtensionSettings,
FrameSiteSettings,
Debugging,
WhatsNew,
About,
PlayerDetectionPanel,
DebugPanel,
ChangelogPanel,
AboutPanel,
SupportLevelIndicator,
@ -366,11 +366,13 @@ export default defineComponent({
console.log('setting initial path:', this.initialPath)
if (path && path.length) {
this.selectedTab = path[0];
this.$emit('debugStatusChanged', this.selectedTab === 'debugging');
}
},
selectTab(tab) {
console.log("Selecting tab", tab);
this.selectedTab = tab;
this.$emit('debugStatusChanged', tab === 'debugging');
window.location.hash = `#${this.role}/${this.selectedTab}`;
},
setPreventClose(bool) {

View File

@ -6,7 +6,10 @@
">
<!-- page content -->
<div class="w-full max-w-[1920px] h-[100dvh] overflow-hidden flex flex-col">
<div
class="w-full h-[100dvh] overflow-hidden flex flex-col"
:class="{'max-w-[1920px]': !isDebugging}"
>
<PopupHead
v-if="role === 'popup' && settings && siteSettings && eventBus"
:settings="settings"
@ -32,6 +35,7 @@
:logger="logger"
:inPlayer="false"
:site="site"
@debugStatusChanged="setDebugStatus"
>
</SettingsWindowContent>
</div>
@ -70,6 +74,7 @@ export default defineComponent({
settingsInitialized: false,
role: 'settings',
initialPath: undefined as string[] | undefined,
isDebugging: false,
}
},
async created() {
@ -87,10 +92,18 @@ export default defineComponent({
await this.setupSettingsPage();
}
if (segment !== '#popup') {
// undo that workaround that makes popup work correctly if we aren't in a popup
document.getElementsByTagName('html')[0].setAttribute('style', '');
}
await this.settings.init();
this.settingsInitialized = true;
},
methods: {
setDebugStatus(isDebugging: boolean) {
this.isDebugging = isDebugging;
},
updateConfig() {
this.settings.init();
this.$nextTick( () => this.$forceUpdate());