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", "fs-extra": "^11.3.2",
"gl-matrix": "^3.4.4", "gl-matrix": "^3.4.4",
"json-cyclic": "1.0.2", "json-cyclic": "1.0.2",
"json-difference": "^1.16.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mdi-vue": "^3.0.13", "mdi-vue": "^3.0.13",
"typescript": "^5.9.3", "typescript": "^5.9.3",
@ -13496,6 +13497,15 @@
"integrity": "sha512-K0Y9wQnrc13Ef+P8KY8SjFyVAz941CTrTVmu3oyQPExhaFFYXXLXnEuRi1tqYOS1B8WtFIxIwA7nHRJ64Uvf5Q==", "integrity": "sha512-K0Y9wQnrc13Ef+P8KY8SjFyVAz941CTrTVmu3oyQPExhaFFYXXLXnEuRi1tqYOS1B8WtFIxIwA7nHRJ64Uvf5Q==",
"license": "MIT" "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": { "node_modules/json-parse-better-errors": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "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", "fs-extra": "^11.3.2",
"gl-matrix": "^3.4.4", "gl-matrix": "^3.4.4",
"json-cyclic": "1.0.2", "json-cyclic": "1.0.2",
"json-difference": "^1.16.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mdi-vue": "^3.0.13", "mdi-vue": "^3.0.13",
"typescript": "^5.9.3", "typescript": "^5.9.3",

View File

@ -11,32 +11,26 @@
:value="simpleExtensionSettings.enable" :value="simpleExtensionSettings.enable"
@click="setExtensionMode('enable', $event)" @click="setExtensionMode('enable', $event)"
> >
<option
v-if="simpleExtensionSettings.enable === 'complex'"
value="complex"
>
(Site uses advanced settings)
</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.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>
@ -56,32 +50,26 @@
:value="simpleExtensionSettings.enableAard" :value="simpleExtensionSettings.enableAard"
@click="setExtensionMode('enableAard', $event)" @click="setExtensionMode('enableAard', $event)"
> >
<option
v-if="simpleExtensionSettings.enable === 'complex'"
value="complex"
>
(Site uses advanced settings)
</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>
@ -99,32 +87,26 @@
:value="simpleExtensionSettings.enableKeyboard" :value="simpleExtensionSettings.enableKeyboard"
@click="setExtensionMode('enableKeyboard', $event)" @click="setExtensionMode('enableKeyboard', $event)"
> >
<option
v-if="simpleExtensionSettings.enable === 'complex'"
value="complex"
>
(Site uses advanced settings)
</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.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>
@ -143,23 +125,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.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">
Always where possible Fullscreen and theater mode
</option>
<option :value="ExtensionMode.All">
Always
</option> </option>
</select> </select>
</div> </div>
@ -425,64 +410,12 @@ export default {
break; break;
} }
return settingsData[component];
try {
if (
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI')
&& settingsData?.[component]?.theater === ExtensionMode.Disabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Disabled
) {
return 'disabled';
}
if (
( settingsData?.[component]?.normal === ExtensionMode.Default || component === 'enableUI')
&& settingsData?.[component]?.theater === ExtensionMode.Default
&& settingsData?.[component]?.fullscreen === ExtensionMode.Default
) {
// console.log(
// component, 'is set to default because:\n',
// `\nsettingsData[${component}].normal: ${settingsData?.[component]?.normal} || component is enableUI?`, component,
// `\nsettingsData[${component}].theater: ${settingsData?.[component]?.normal}`,
// `\nsettingsData[${component}].fullscreen: ${settingsData?.[component]?.normal}`,
// `\n\n(expected values:`, ExtensionMode.Default
// )
return 'default';
}
if (
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI')
&& settingsData?.[component]?.theater === ExtensionMode.Disabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled
) {
return 'fs';
}
if (
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI')
&& settingsData?.[component]?.theater === ExtensionMode.Enabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled
) {
return 'theater';
}
if (
( settingsData?.[component]?.normal === ExtensionMode.Enabled || component === 'enableUI')
&& settingsData?.[component]?.theater === ExtensionMode.Enabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled
) {
return 'enabled';
}
return 'complex';
} catch (e) {
return 'loading';
}
}, },
getDefaultOptionLabel(component) { getDefaultOptionLabel(component) {
const componentValue = this.compileSimpleSettings(component, 'default'); const componentValue = this.compileSimpleSettings(component, 'default');
if (componentValue === 'loading') {
return componentValue;
}
if (component === 'enableUI') { if (component === 'enableUI') {
switch (componentValue) { switch (componentValue) {
case 'fs': case 'fs':

View File

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

View File

@ -6,9 +6,10 @@ import ExtensionConfPatch from '../../conf/ExtConfPatches';
import SettingsInterface from '../../../common/interfaces/SettingsInterface'; import SettingsInterface from '../../../common/interfaces/SettingsInterface';
import AspectRatioType from '../../../common/enums/AspectRatioType.enum'; import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
import { GetSiteSettingsOptions, SiteSettings } from './SiteSettings'; import { GetSiteSettingsOptions, SiteSettings } from './SiteSettings';
import { SettingsSnapshotManager } from './SettingsSnapshotManager'; import { SettingsSnapshot, SettingsSnapshotManager } from './SettingsSnapshotManager';
import { ComponentLogger } from '../logging/ComponentLogger'; import { ComponentLogger } from '../logging/ComponentLogger';
import { LogAggregator } from '../logging/LogAggregator'; import { LogAggregator } from '../logging/LogAggregator';
import { getDiff } from 'json-difference'
if(process.env.CHANNEL !== 'stable'){ if(process.env.CHANNEL !== 'stable'){
console.info("Loading Settings"); 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)); // this.logger?.log('info', 'settings',"[Settings::<storage/on change>] new settings object:", JSON.parse(changes.uwSettings.newValue));
// } // }
const parsedSettings = 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); this.logger?.info('storageOnChange', 'Does parsedSettings.preventReload exist?', parsedSettings.preventReload, "Does callback exist?", !!this.onSettingsChanged);
if (!parsedSettings.preventReload) { if (!parsedSettings.preventReload) {
this.active.preventReload = true;
try { try {
for (const fn of this.onChangedCallbacks) { for (const fn of this.onChangedCallbacks) {
try { try {
@ -116,7 +138,6 @@ class Settings {
if (this.onSettingsChanged) { if (this.onSettingsChanged) {
this.onSettingsChanged(); this.onSettingsChanged();
} }
this.logger?.info('storageOnChange', 'Update callback finished.') this.logger?.info('storageOnChange', 'Update callback finished.')
} catch (e) { } catch (e) {
this.logger?.error('storageOnChange', "CALLING UPDATE CALLBACK FAILED. Reason:", e) this.logger?.error('storageOnChange', "CALLING UPDATE CALLBACK FAILED. Reason:", e)
@ -212,7 +233,7 @@ class Settings {
private findFirstNecessaryPatch(version) { private findFirstNecessaryPatch(version) {
return ExtensionConfPatch.findIndex(x => this.compareExtensionVersions(x.forVersion, version) > 0); 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); let index = this.findFirstNecessaryPatch(oldVersion);
if (index === -1) { if (index === -1) {
@ -221,8 +242,7 @@ class Settings {
} }
// save current settings object // save current settings object
const currentSettings = this.active; if (!options?.skipSnapshot) {
this.snapshotManager.createSnapshot( this.snapshotManager.createSnapshot(
JSON.parse(JSON.stringify(currentSettings)), JSON.parse(JSON.stringify(currentSettings)),
{ {
@ -230,17 +250,22 @@ class Settings {
isAutomatic: true isAutomatic: true
} }
); );
}
// this.active.newFeatureTracker = {};
// apply all remaining patches // 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) { while (index < ExtensionConfPatch.length) {
const updateFn = ExtensionConfPatch[index].updateFn; 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) { if (updateFn) {
try { 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) { } catch (e) {
this.logger?.error('applySettingsPatches', 'Failed to execute update function. Keeping settings object as-is. Error:', e); this.logger?.error('applySettingsPatches', 'Failed to execute update function. Keeping settings object as-is. Error:', e);
} }
@ -248,30 +273,37 @@ class Settings {
index++; index++;
} }
return currentSettings;
} }
async init() { async init(options?: {dryRun?: boolean, snapshot?: SettingsSnapshot, skipSnapshot?: boolean}) {
let settings = await this.get(); 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); this.logger?.info('init', 'Dev mode is enabled, Loading settings from snapshot:', settings.dev.loadFromSnapshot);
const snapshot = await this.snapshotManager.getSnapshot(); const snapshot = await this.snapshotManager.getSnapshot();
if (snapshot) { if (snapshot) {
settings = snapshot.settings; 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 // |—> on first setup, settings is undefined & settings.version is haram
// | since new installs ship with updates by default, no patching is // | since new installs ship with updates by default, no patching is
// | needed. In this case, we assume we're on the current version // | 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) { if (settings) {
this.logger?.info('init', "Configuration fetched from storage:", settings, this.logger?.info('init', "Configuration fetched from storage:", settings,
"\nlast saved with:", settings.version, "\nlast saved with:", settings.version,
"\ncurrent version:", this.version "\ncurrent version:", currentVersion
); );
} }
@ -284,40 +316,57 @@ class Settings {
'\nsettings:', '\nsettings:',
settings 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 // if there's settings, set saved object as active settings
this.active = settings;
// if version number is undefined, we make it defined // if version number is undefined, we make it defined
// this should only happen on first extension initialization // this should only happen on first extension initialization
if (!this.active.version) { if (!settings.version) {
this.active.version = this.version; settings.version = currentVersion;
if (!options?.dryRun) {
this.active = settings;
this.version = currentVersion;
await this.save(); await this.save();
return this.active; }
return settings;
} }
// check if extension has been updated. If not, return settings as they were retrieved // 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."); 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 // 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 // set 'whatsNewChecked' flag to false when updating, always
this.active.whatsNewChecked = false; settings.whatsNewChecked = false;
// update settings version to current // 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(); await this.save();
return this.active; }
return settings;
} }
async get(): Promise<SettingsInterface | undefined> { async get(): Promise<SettingsInterface | undefined> {
@ -437,6 +486,11 @@ class Settings {
removeAfterChangeListener(fn: () => void): void { removeAfterChangeListener(fn: () => void): void {
this.afterSettingsChangedCallbacks = this.afterSettingsChangedCallbacks.filter(x => x !== fn); 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; export default Settings;

View File

@ -1,29 +1,93 @@
<template> <template>
<div> <h2 class="text-[1.75em]">Debugging</h2>
<div> <div class="w-full grid grid-cols-1 min-[1200px]:grid-cols-2 min-[1920px]:grid-cols-3 max-w-[2300px] gap-8">
<h1>{{loggerPanel.title}}<small><br/>{{loggerPanel.subtitle}}</small></h1>
<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 <JsonEditor
v-model="lastSettings" v-model="lastSettings"
></JsonEditor> ></JsonEditor>
<div class="flex flex-row flex-end"> <div class="flex flex-row justify-end mt-4 w-full">
<div class="button" @click="getLoggerSettings()"> <button class="bg-black button" @click="getLoggerSettings()">
Revert Revert
</div> </button>
<div class="button button-primary" @click="saveLoggerSettings()"> <button class="button button-primary" @click="saveLoggerSettings()">
Save Save
</div> </button>
<div class="button button-primary" @click="startLogging()"> <button class="button button-primary" @click="startLogging()">
Save and start logging Save and start logging
</div> </button>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
@ -67,7 +131,12 @@ export default defineComponent({
}, },
lastSettings: undefined, lastSettings: undefined,
parsedSettings: undefined, parsedSettings: undefined,
currentSettings: undefined currentSettings: undefined,
allowSettingsEditing: false,
editorSaveFinished: false,
settingsJson: {},
settingsSnapshots: []
}; };
}, },
props: [ props: [
@ -81,6 +150,11 @@ export default defineComponent({
}; };
this.getLoggerSettings(); this.getLoggerSettings();
}, },
mounted() {
this.loadSettingsSnapshots();
this.resetSettingsEditor();
this.loadSettingsSnapshots();
},
methods: { methods: {
loadDefaultConfig() { loadDefaultConfig() {
this.lastSettings = JSON.parse(JSON.stringify(BLANK_LOGGER_CONFIG)); this.lastSettings = JSON.parse(JSON.stringify(BLANK_LOGGER_CONFIG));
@ -98,6 +172,72 @@ export default defineComponent({
await LogAggregator.saveConfig({...this.lastSettings}); await LogAggregator.saveConfig({...this.lastSettings});
window.location.reload(); 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> </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 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> <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> <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>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, 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. 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>
Autodetection can now detect hardcoded subtitles. You may change how extension handles subtitles in the settings. Experimental 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.
</li> </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> <li>
Keyboard shortcut settings have been split from UI settings, and are now presented in list form on the settings page. Keyboard shortcut settings have been split from UI settings, and are now presented in list form on the settings page.
</li> </li>
@ -23,6 +31,12 @@
</li> </li>
</ul> </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> </template>
<script> <script>
import BrowserDetect from '@src/ext/conf/BrowserDetect'; import BrowserDetect from '@src/ext/conf/BrowserDetect';

View File

@ -12,8 +12,8 @@
@click="setExtensionMode('enable', $event)" @click="setExtensionMode('enable', $event)"
> >
<option <option
v-if="simpleExtensionSettings.enable === 'complex'" v-if="simpleExtensionSettings.enable === 'invalid'"
value="complex" :value="undefined"
> >
(Site uses advanced settings) (Site uses advanced settings)
</option> </option>
@ -412,7 +412,7 @@ export default defineComponent({
/** /**
* Compiles our extension settings into more user-friendly options * Compiles our extension settings into more user-friendly options
*/ */
compileSimpleSettings(component, getFor = 'site') { compileSimpleSettings(component, getFor = 'site'): ExtensionMode {
let settingsData; let settingsData;
switch (getFor) { switch (getFor) {
@ -427,86 +427,25 @@ export default defineComponent({
break; break;
} }
return settingsData?.[component];
try {
if (
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI')
&& settingsData?.[component]?.theater === ExtensionMode.Disabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Disabled
) {
return 'disabled';
}
if (
( settingsData?.[component]?.normal === ExtensionMode.Default || component === 'enableUI')
&& settingsData?.[component]?.theater === ExtensionMode.Default
&& settingsData?.[component]?.fullscreen === ExtensionMode.Default
) {
// console.log(
// component, 'is set to default because:\n',
// `\nsettingsData[${component}].normal: ${settingsData?.[component]?.normal} || component is enableUI?`, component,
// `\nsettingsData[${component}].theater: ${settingsData?.[component]?.normal}`,
// `\nsettingsData[${component}].fullscreen: ${settingsData?.[component]?.normal}`,
// `\n\n(expected values:`, ExtensionMode.Default
// )
return 'default';
}
if (
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI')
&& settingsData?.[component]?.theater === ExtensionMode.Disabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled
) {
return 'fs';
}
if (
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI')
&& settingsData?.[component]?.theater === ExtensionMode.Enabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled
) {
return 'theater';
}
if (
( settingsData?.[component]?.normal === ExtensionMode.Enabled || component === 'enableUI')
&& settingsData?.[component]?.theater === ExtensionMode.Enabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled
) {
return 'enabled';
}
return 'complex';
} catch (e) {
return 'loading';
}
}, },
getDefaultOptionLabel(component) { 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) { switch (componentValue) {
case 'fs': case ExtensionMode.Disabled:
return 'disabled';
case ExtensionMode.Default:
return 'default';
case ExtensionMode.FullScreen:
return 'fullscreen only'; return 'fullscreen only';
case 'enabled': case ExtensionMode.Theater:
case 'theater': return 'fullscreen & theater';
return 'where possible'; case ExtensionMode.All:
case 'disabled':
return 'disabled';
}
}
switch (componentValue) {
case 'fs':
return 'Fullscreen only';
case 'theater':
return 'theater & FS';
case 'enabled':
return 'always'; return 'always';
case 'disabled': default:
return 'disabled'; return '<Invalid value>';
case 'default':
return 'complex'
} }
}, },

View File

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

View File

@ -6,7 +6,10 @@
"> ">
<!-- page content --> <!-- 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 <PopupHead
v-if="role === 'popup' && settings && siteSettings && eventBus" v-if="role === 'popup' && settings && siteSettings && eventBus"
:settings="settings" :settings="settings"
@ -32,6 +35,7 @@
:logger="logger" :logger="logger"
:inPlayer="false" :inPlayer="false"
:site="site" :site="site"
@debugStatusChanged="setDebugStatus"
> >
</SettingsWindowContent> </SettingsWindowContent>
</div> </div>
@ -70,6 +74,7 @@ export default defineComponent({
settingsInitialized: false, settingsInitialized: false,
role: 'settings', role: 'settings',
initialPath: undefined as string[] | undefined, initialPath: undefined as string[] | undefined,
isDebugging: false,
} }
}, },
async created() { async created() {
@ -87,10 +92,18 @@ export default defineComponent({
await this.setupSettingsPage(); 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(); await this.settings.init();
this.settingsInitialized = true; this.settingsInitialized = true;
}, },
methods: { methods: {
setDebugStatus(isDebugging: boolean) {
this.isDebugging = isDebugging;
},
updateConfig() { updateConfig() {
this.settings.init(); this.settings.init();
this.$nextTick( () => this.$forceUpdate()); this.$nextTick( () => this.$forceUpdate());