Settings snapshot handling, moved update page to unified settings page

This commit is contained in:
Tamius Han 2025-12-16 22:57:19 +01:00
parent ca751a660f
commit 4180d5b5b1
17 changed files with 624 additions and 147 deletions

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "ultrawidify",
"version": "6.3.994",
"version": "6.3.995",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ultrawidify",
"version": "6.3.994",
"version": "6.3.995",
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@mdi/font": "^7.4.47",

View File

@ -1,6 +1,6 @@
{
"name": "ultrawidify",
"version": "6.3.994",
"version": "6.3.995",
"description": "Aspect ratio fixer for youtube and other sites, with automatic aspect ratio detection. Supports ultrawide and other ratios.",
"author": "Tamius Han <tamius.han@gmail.com>",
"scripts": {

View File

@ -308,6 +308,14 @@ const ExtensionConfPatch = Object.freeze([
logger.log('migrated site', key, userOptions.sites[key]);
}
}
},
{
forVersion: '6.3.995',
updateFn: (userOptions: SettingsInterface, defaultOptions: SettingsInterface, logger?) => {
if (!userOptions.ui) {
userOptions.ui = defaultOptions.ui
};
}
}
]);

View File

@ -245,8 +245,8 @@ class Settings {
// save current settings object
if (!options?.skipSnapshot) {
this.snapshotManager.createSnapshot(
JSON.parse(JSON.stringify(currentSettings)),
{
settings: JSON.parse(JSON.stringify(currentSettings)),
label: 'Pre-migration snapshot',
isAutomatic: true
}
@ -279,6 +279,8 @@ class Settings {
}
async init(options?: {dryRun?: boolean, snapshot?: SettingsSnapshot, skipSnapshot?: boolean}) {
await this.snapshotManager.init();
let settings;
if (options?.snapshot) {

View File

@ -10,16 +10,37 @@ export interface SettingsSnapshot {
createdAt: Date;
}
export interface SettingsSnapshotOptions {
isAutomatic?: boolean,
isProtected?: boolean,
isDefault?: boolean,
label?: string,
forVersion?: string
export interface SnapshotManagerSettings {
maxAutomaticSnapshots: number,
minVersions: number,
}
const SNAPSHOT_MANAGER_CONF = 'uwSettings-snapshot-manager-conf';
export class SettingsSnapshotManager {
private MAX_AUTOMATIC_SNAPSHOTS = 5;
config: SnapshotManagerSettings;
async init() {
const ret = await chrome.storage.local.get(SNAPSHOT_MANAGER_CONF) as string;
try {
this.config = JSON.parse(ret[SNAPSHOT_MANAGER_CONF]);
} catch (e) {
this.config = {
maxAutomaticSnapshots: 10,
minVersions: 5
};
}
}
async saveConf() {
await chrome.storage.local.set({
[SNAPSHOT_MANAGER_CONF]: JSON.stringify(this.config)
});
}
async getSnapshot(index?: number) {
const snapshots = await this.listSnapshots();
@ -34,27 +55,53 @@ export class SettingsSnapshotManager {
}
}
async createSnapshot(settings: SettingsInterface, options?: SettingsSnapshotOptions) {
const snapshot = {
...options,
label: options.label ?? 'Automatic snapshot',
forVersion: options.forVersion || settings.version,
settings: JSON.parse(JSON.stringify(settings)),
createdAt: new Date(),
};
async createSnapshot(snapshot: Partial<SettingsSnapshot> & {settings: SettingsInterface}) {
if (!snapshot.createdAt) {
snapshot.createdAt = new Date()
}
if (!snapshot.forVersion) {
snapshot.forVersion = snapshot.settings.version;
}
if (!snapshot.label) {
snapshot.label = "Unknown snapshot"
}
const snapshots = await this.listSnapshots();
const automaticSnapshots = snapshots.filter((s) => s.isAutomatic && !s.isProtected);
const automaticSnapshots = snapshots
.filter((s) => s.isAutomatic && !s.isProtected)
.sort((a: SettingsSnapshot, b: SettingsSnapshot) => a.settings.version === b.settings.version ? 0 : a.settings.version < b.settings.version ? -1 : 1);
if (options.isAutomatic && automaticSnapshots.length >= this.MAX_AUTOMATIC_SNAPSHOTS) {
let minVersionCount = 0;
let lastVersion;
for (const snap of automaticSnapshots) {
if (lastVersion !== snap.settings.version) {
minVersionCount++;
lastVersion = snap.settings.version;
}
}
if (snapshot.isAutomatic && automaticSnapshots.length >= this.config.maxAutomaticSnapshots && minVersionCount > this.config.minVersions) {
const firstAutomaticIndex = snapshots.findIndex((s) => s.isAutomatic && !s.isProtected);
snapshots.splice(firstAutomaticIndex, 1);
}
snapshots.push(snapshot);
snapshots.push(snapshot as SettingsSnapshot);
this.set(snapshots);
}
async updateSnapshot(snapshot: SettingsSnapshot) {
const snapshots = await this.listSnapshots();
const i = snapshots.findIndex((x: SettingsSnapshot) => x.createdAt === snapshot.createdAt);
try {
snapshots[i] = snapshot;
this.set(snapshots);
} catch (e) {
console.error('uw::SettingsSnapshotManager::updateSnapshot — failed to update snapshot.', {e, i, snapshot, snapshots});
}
}
async setDefaultSnapshot(index: number, isDefault: boolean) {
const snapshots = await this.listSnapshots();
if (index < 0 || index >= snapshots.length) {

View File

@ -287,6 +287,7 @@ class UI {
}
setUiVisibility(visible) {
return;
// console.log('uwui - setting ui visibility!', visible, this.isGlobal ? 'global' : 'page', this.uiIframe, this.rootDiv);
// if (!this.uiIframe || !this.rootDiv) {
// this.init();

View File

@ -38,7 +38,7 @@ html, body {
}
a {
@apply text-primary-400;
@apply text-primary-400 cursor-pointer;
&:hover {
@apply text-primary-300;

View File

@ -2,7 +2,7 @@
"manifest_version": 3,
"name": "Ultrawidify",
"description": "Removes black bars on ultrawide videos and offers advanced options to fix aspect ratio.",
"version": "6.3.994",
"version": "6.3.995",
"icons": {
"32":"res/icons/uw-32.png",
"64":"res/icons/uw-64.png"

View File

@ -139,6 +139,11 @@
:eventBus="eventBus"
></KeyboardShortcutSettings>
<AfterUpdate
v-if="selectedTab === 'updated'"
:settings="settings"
>
</AfterUpdate>
<WhatsNew
v-if="selectedTab === 'changelog'"
@ -194,6 +199,8 @@ import FrameSiteSettings from '@components/segments/ExtensionSettings/Panels/Fra
import Debugging from '@components/segments/Debugging/Debugging.vue';
import ImportExportSettings from '@components/segments/ImportExportSettings/ImportExportSettings.vue';
import AfterUpdate from '@components/segments/AfterUpdate/AfterUpdate.vue';
import WhatsNew from '@components/segments/ExtensionInfo/WhatsNew.vue';
import About from '@components/segments/ExtensionInfo/About.vue';
@ -222,10 +229,14 @@ const AVAILABLE_TABS = {
'uiSettings': {id: 'uiSettings', label: 'UI settings', icon: 'movie-cog-outline' },
'keyboardShortcuts': {id: 'keyboardShortcuts', label: 'Keyboard shortcuts', icon: 'keyboard-outline' },
'playerDetection': {id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
'installed': { id: 'updated', label: 'Update completed', icon: 'monitor-arrow-down-variant'},
'updated': { id: 'updated', label: 'Update completed', icon: 'update'},
'changelog': {id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
'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'}
};
const TAB_LOADOUT = {
@ -239,6 +250,11 @@ const TAB_LOADOUT = {
'import-export-settings',
'debugging',
],
'updated': [
'updated',
'changelog',
'about',
],
'popup': [
'video-settings',
'site-extension-settings',
@ -259,6 +275,8 @@ export default defineComponent({
ImportExportSettings,
Debugging,
AfterUpdate,
WhatsNew,
About,
@ -390,7 +408,7 @@ export default defineComponent({
setDebugTabVisibility() {
const debugTab = this.tabs.find( x => x.id === 'debugging');
if (debugTab) {
debugTab.hidden = !this.settings.active.ui.devMode;
// debugTab.hidden = !this.settings.active.ui.devMode;
}
},

View File

@ -5,14 +5,14 @@
: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 class="h-full flex flex-col w-full">
<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">
<div v-else class="h-full w-full -mr-4 pr-4 grow shrink overflow-y-auto overflow-x-hidden">
<slot></slot>
</div>
<div class="grow-0 shrink-0 flex row gap-2 justify-end w-full">

View File

@ -0,0 +1,123 @@
<template>
<div class="flex flex-col h-full justify-center items-center">
<template v-if="!settings || !settings.active">Please wait ...</template>
<template v-else>
<!-- {{settings.active}} -->
<div class="body flex-grow text-stone-300">
<h1 class="text-[1.75rem] text-primary-300">Ultrawidify has been updated</h1>
<br/>
<p>This update introduces some new experimental features:</p>
<div class="flex flex-col gap-4 mt-8">
<div>
<div class="field !flex-col !items-start gap-2">
<b class="text-white">
What do you want to if there are subtitles in the video?
</b>
<div class="select">
<select v-model="placeholderSubtitleCrop">
<option :value="AardSubtitleCropMode.ResetAR">Reset aspect ratio while subtitles are visible</option>
<option :value="AardSubtitleCropMode.ResetAndDisable">Reset aspect ratio and stop autodetection for the video</option>
<option :value="AardSubtitleCropMode.CropSubtitles">Crop subtitles</option>
</select>
</div>
</div>
</div>
<div>
<div class="field !flex-col !items-start gap-2">
<b class="text-white">Use experimental aspect ratio detection?</b>
<div class="select">
<select v-model="settings.active.aard.useLegacy">
<option :value="true">Use legacy detection</option>
<option :value="false">Use experimental detection</option>
</select>
</div>
<div class="text-stone-400 text-[0.9rem]">
<p>Experimental aspect ratio detection should be more accurate, but it hasn't been extensively tested yet.</p>
<p>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.</p>
<p>Experimental detection will become the default sometime in 2026 unless people report issues.</p>
</div>
</div>
</div>
<div class="flex flex-row w-full justify-center items-center">
<button v-if="!settingsSaved" class="button primary" @click="saveSettings">
Save preferences
</button>
<template v-else>Your settings have been saved.</template>
</div>
<div class="flex flex-row w-full justify-center items-center">
<a class="button primary" @click="redirectToFullSettings">
More settings ...
</a>
</div>
</div>
<br/>
<br/>
<p>You can always change your settings later.</p>
</div>
<div class="footer flex-nogrow flex-noshrink">
</div>
</template>
</div>
</template>
<script lang="ts">
import BrowserDetect from '@src/ext/conf/BrowserDetect';
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator';
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
import Settings from '@src/ext/lib/settings/Settings';
import { AardSubtitleCropMode } from '@src/ext/lib/aard/enums/aard-subtitle-crop-mode.enum';
export default {
props: [
'settings',
],
data () {
return {
AardSubtitleCropMode,
placeholderSubtitleCrop: AardSubtitleCropMode.ResetAR,
settingsSaved: false
}
},
async created() {
this.logAggregator = new LogAggregator('');
this.logger = new ComponentLogger(this.logAggregator, 'App.vue');
// this.placeholderSubtitleCrop = (this.settings.active.aard.useLegacy ? this.settings.active.aardLegacy.subtitles?.subtitleCropMode : this.settings.active.aard.subtitles?.subtitleCropMode) ?? AardSubtitleCropMode.ResetAR;
this.settingsInitialized = true;
},
components: {
},
methods: {
async redirectToFullSettings() {
await this.saveSettings();
window.location.hash = '#settings';
window.location.reload();
},
async updateConfig() {
await this.settings.init();
this.$nextTick( () => this.$forceUpdate());
},
async saveSettings() {
this.settings.active[this.settings.active.aard.useLegacy ? 'aardLegacy' : 'aard'].subtitles.subtitleCropMode = this.placeholderSubtitleCrop;
await this.settings.save();
this.settingsSaved = true;
}
}
}
</script>
<style lang="postcss" scoped>
</style>

View File

@ -1,7 +1,48 @@
<template>
<h2 class="text-[1.75em]">Debugging</h2>
<div v-if="!isTrusted" class="h-full w-full flex flex-col justify-center items-center">
<p class="mt-[25vh] text-[1.75rem] text-primary-300 font-thin text-center">No vodka, no passage.</p>
<p class="text-[1rem] font-thin uppercase text-stone-400 text-center">Give vodka, you passage</p>
<p class="mt-8 text-center">
Unless you were asked to visit this page on github or via e-mail, you probably shouldn't be here.
</p>
<p class="mt-0 text-center cursor-pointer">
<a @click="showDebugging()">Proceed anyway</a>
</p>
</div>
<div v-else class="w-full flex flex-col gap-4">
<div class="flex gap-2">
<div class="label text-stone-500">Show developer options</div>
<input
type="checkbox"
v-model="settings.active.ui.devMode"
@change="settings.saveWithoutReload"
>
</div>
<div class="w-full grid grid-cols-1 min-[1200px]:grid-cols-2 min-[1920px]:grid-cols-3 max-w-[2300px] gap-8">
<div class="grow shrink">
<h3 class="mb-4">Logger configuration</h3>
<JsonEditor
v-model="lastSettings"
></JsonEditor>
<div class="flex flex-row justify-end mt-4 w-full">
<button class="bg-black button" @click="getLoggerSettings()">
Revert
</button>
<button class="button button-primary" @click="saveLoggerSettings()">
Save
</button>
<button class="button button-primary" @click="startLogging()">
Save and start logging
</button>
</div>
</div>
<div class="grow shrink">
<h3 class="mb-4">Settings editor</h3>
@ -37,28 +78,40 @@
</div>
<div class="grow shrink">
<h3 class="mb-4">Snapshots</h3>
<h3 class="mb-4 w-full inline-flex row justify-between">Snapshots <button class="bg-transparent border-none text-stone-500 hover:text-primary-300"><mdicon name="cog" :size="24"></mdicon></button></h3>
<div class="flex row gap-4 items-center mb-4">
<UploadJsonFileButton
@importedJson="handleImportedSnapshot"
@error="handleImportedSnapshotError"
>
Import settings as snapshot
</UploadJsonFileButton>
<button>Create snapshot</button>
</div>
<div class="flex flex-col gap-2">
<div
v-for="(snapshot, index) of settingsSnapshots" :key="snapshot.createdAt"
class="border border-stone-800 p-2"
:class="{'bg-primary-600': snapshot.isDefault, '!border-indigo-900/75': !snapshot.isAutomatic}"
>
<small>Created at: {{new Date(snapshot.createdAt).toISOString()}}</small>
<div class="flex flex-row">
<div class="grow font-bold">
<div class="flex row items-center gap-2">
<mdicon v-if="!snapshot.isAutomatic" class="text-indigo-300" name="account-arrow-down" :size="20"></mdicon>
<div class="grow font-bold" :class="{'text-indigo-300': !snapshot.isAutomatic}">
{{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 class="mr-8"><span class="text-stone-500 text-[0.75rem] uppercase mr-4">saved with:</span>v. {{snapshot.forVersion ?? '<unknown ver.>'}}</div>
<div v-if="snapshot.isAutomatic && settings.isProtected">(protected)</div>
<button title="Initialize settings with this snapshot" v-if=" snapshot.isDefault" class="bg-transparent border-none text-white hover:text-primary-300" @click="() => markDefaultSnapshot(index)"><mdicon name="file-download" size="20"></mdicon></button>
<button title="Initialize settings with this snapshot" v-if="!snapshot.isDefault" class="bg-transparent border-none text-stone-500 hover:text-primary-300" @click="() => markDefaultSnapshot(index)"><mdicon name="file-download-outline" size="20"></mdicon></button>
<button title="Edit this snapshot ..." class="bg-transparent border-none text-stone-500 hover:text-primary-300" @click="() => editSnapshot(index)"><mdicon name="pencil" size="20"></mdicon></button>
</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>
@ -66,38 +119,88 @@
</div>
</div>
<div class="grow shrink">
<h3 class="mb-4">Logger configuration</h3>
</div>
</div>
<Popup v-if="snapshotManagerSettingsPopup.visible"
title="Snapshot manager settings"
confirmButtonText="Save"
cancelButtonText="Cancel"
@onConfirm="snapshotManagerSettingsPopup.confirm"
@onCancel="snapshotManagerSettingsPopup.reject"
>
<p><b>NOTE: probably reload page if you change anything.</b></p>
<div class="field">
<div class="label">Max. automatic snapshots</div>
<div class="input">
<input v-model="settings.snapshotManager.config.maxAutomaticSnapshots" />
</div>
</div>
<div class="field">
<div class="label">Max. automatic snapshots</div>
<div class="input">
<input v-model="settings.snapshotManager.config.minVersions" />
</div>
</div>
</Popup>
<Popup v-if="editSnapshotPopup.visible"
title="Edit snapshot"
confirmButtonText="Save"
cancelButtonText="Cancel"
@onConfirm="editSnapshotPopup.confirm"
@onCancel="editSnapshotPopup.reject"
>
<div class="h-full w-[690px] max-h-[90dvh]">
<div class="field">
<div class="label">Snapshot name</div>
<div class="input">
<input v-model="editSnapshotPopup.data.snapshot.label" />
</div>
</div>
<div class="field">
<div class="label">For version</div>
<div class="input">
<input v-model="editSnapshotPopup.data.snapshot.forVersion" />
</div>
</div>
<div class="field">
<div class="label">Was created automatically</div>
<div class="checkbox">
<input type="checkbox" v-model="editSnapshotPopup.data.snapshot.isAutomatic" />
</div>
</div>
<div class="field">
<div class="label">Is protected</div>
<div class="checkbox">
<input type="checkbox" v-model="editSnapshotPopup.data.snapshot.isProtected" />
</div>
</div>
<div class="font-bold text-white mb-4">Snapshot settings:</div>
<JsonEditor
v-model="lastSettings"
v-model="editSnapshotPopup.data.snapshot.settings"
></JsonEditor>
<div class="flex flex-row justify-end mt-4 w-full">
<button class="bg-black button" @click="getLoggerSettings()">
Revert
</button>
<button class="button button-primary" @click="saveLoggerSettings()">
Save
</button>
<button class="button button-primary" @click="startLogging()">
Save and start logging
</button>
</div>
</div>
</div>
</Popup>
</template>
<script lang="ts">
import { LogAggregator, BLANK_LOGGER_CONFIG } from '@src/ext/lib/logging/LogAggregator';
import Popup from '@components/common/Popup.vue';
import JsonEditor from '@components/common/JsonEditor.vue';
import UploadJsonFileButton from '@components/common/UploadJsonFileButton.vue';
import { LogAggregator, BLANK_LOGGER_CONFIG } from '@src/ext/lib/logging/LogAggregator';
import { defineComponent } from 'vue';
import { SettingsSnapshot } from '@src/ext/lib/settings/SettingsSnapshotManager';
import SettingsInterface from '../../../../common/interfaces/SettingsInterface';
export default defineComponent({
components: {
JsonEditor
UploadJsonFileButton,
JsonEditor,
Popup,
},
data() {
return {
@ -132,17 +235,25 @@ export default defineComponent({
lastSettings: undefined,
parsedSettings: undefined,
currentSettings: undefined,
debugVisible: false,
allowSettingsEditing: false,
editorSaveFinished: false,
settingsJson: {},
settingsSnapshots: []
settingsSnapshots: [],
snapshotManagerSettingsPopup: {visible: false},
editSnapshotPopup: {visible: false},
};
},
props: [
'settings',
'eventBus',
],
computed: {
isTrusted() {
return this.debugVisible || this.settings?.active.ui.devMode;
}
},
created() {
this.loggerPanel = {
...this.loggerPanelRotation[Math.floor(Math.random() * this.loggerPanelRotation.length)],
@ -156,6 +267,15 @@ export default defineComponent({
this.loadSettingsSnapshots();
},
methods: {
/**
* Hides first-time warning screen and shows the real contents of debug page
*/
showDebugging() {
this.debugVisible = true;
},
/**
*
*/
loadDefaultConfig() {
this.lastSettings = JSON.parse(JSON.stringify(BLANK_LOGGER_CONFIG));
},
@ -237,6 +357,56 @@ export default defineComponent({
async testMigration(snapshotIndex) {
this.settings.testMigration(this.settingsSnapshots[snapshotIndex]);
},
editSnapshot(snapshotOrIndex: SettingsSnapshot | number) {
console.log('editing snapshot', snapshotOrIndex)
let snapshot: SettingsSnapshot;
let index;
if (typeof snapshotOrIndex === 'number') {
index = snapshotOrIndex as number;
snapshot = this.settingsSnapshots[index];
} else {
snapshot = snapshotOrIndex as SettingsSnapshot;
}
this.editSnapshotPopup = {
visible: true,
data: {
snapshot,
index,
},
confirm: () => {
console.log('confirmed snapshot. Data:', this.editSnapshotPopup.data);
if (this.editSnapshotPopup.index) {
// this.settings.snapshotManager.
} else {
this.settings.snapshotManager.createSnapshot(snapshot);
}
this.editSnapshotPopup = {visible: false};
},
reject: () => {
this.editSnapshotPopup = {visible: false};
},
};
},
handleImportedSnapshot(settings: SettingsInterface) {
console.log('loaded data:', settings);
const snapshot: SettingsSnapshot = {
settings,
label: 'Imported settings',
forVersion: settings.version,
createdAt: new Date()
}
this.editSnapshot(snapshot);
},
handleImportedSnapshotError(error: any) {
console.error(`[ultrawidify] Failed to upload snapshot. Error:`, e);
}
}
//#endregion
});

View File

@ -29,6 +29,9 @@
<li>
Re-design of settings page.
</li>
<li>
Settings, popup and in-page UI have been combined into a single HTML file in order to cut down on the file size.
</li>
</ul>
<b class="text-white">Other updates and fixes:</b>

View File

@ -1,5 +1,5 @@
<template>
<div class="flex flex-col w-full max-w-[960px]">
<div class="flex flex-col w-full w-[960px]">
<h2 text="text-[1.75rem]">Import &amp; export settings</h2>
<!-- Reset options -->
@ -8,6 +8,9 @@
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>
<p>
If the site you're having issues with embeds videos from a different site, you need to include embedded sites as well.
</p>
<div class="flex flex-row w-full gap-2">
<UploadJsonFileButton
@importedJson="handleImportedSettings"
@ -28,15 +31,6 @@
>
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>
@ -59,13 +53,33 @@
@onConfirm="partialExportDialogConfig.confirm"
@onCancel="partialExportDialogConfig.reject"
>
<p>Export settings for the following websites:</p>
<p>Select websites to export.</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"
<div class="flex row gap-4">
<span class="mr-2">Select: </span>
<a class="cursor-pointer" @click="selectExportSite('all')">all</a> ·
<a class="cursor-pointer" @click="selectExportSite('none')">none</a> ·
<a class="cursor-pointer" @click="selectExportSite('hide-mine')">deselect sites modified by me</a>
</div>
<div class="flex flex-col my-4 max-w-[80dvh] w-[1920px]">
<div v-for="category in partialExportDialogConfig.categories" :key="category.priority"
class="x-4 pt-4"
>
<input type="checkbox" v-model="site.selectedForExport" :disabled="site.key === '@global'"> {{site.key}} <SupportLevelIndicator :siteSupportLevel="site.type ?? site.defaultType ?? 'problemo'"></SupportLevelIndicator>
<div class="flex row w-full gap-4 items-center mb-2 mt-4">
<div class="text-bold font-bold">Group</div> <SupportLevelIndicator :siteSupportLevel="category.supportLevel ?? 'problemo'"></SupportLevelIndicator>
</div>
<div class="flex flex-col pl-8">
<div v-for="site in category.sites" :key="site"
class="border border-stone-900 px-4 py-2 flex flex-row gap-4 hover:bg-stone-900"
>
<input type="checkbox"
class="cursor-pointer"
v-model="site.selectedForExport"
:disabled="site.key === '@global' || site.key === '@empty'"
> {{site.key}} <SupportLevelIndicator :siteSupportLevel="site.type ?? site.defaultType ?? 'problemo'"></SupportLevelIndicator>
</div>
</div>
</div>
</div>
@ -76,15 +90,32 @@
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 SettingsInterface, { 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;
visible: boolean;
};
interface ExportSiteDataCategory {
priority: number,
supportLevel: SiteSupportLevel,
sites: ExportSiteData[],
}
const exportDialogPriorityMap = [
SiteSupportLevel.UserModified,
SiteSupportLevel.UserDefined,
SiteSupportLevel.BetaSupport,
SiteSupportLevel.Unknown,
SiteSupportLevel.CommunitySupport,
SiteSupportLevel.OfficialSupport,
SiteSupportLevel.OfficialBlacklist
];
export default {
components: {
Popup,
@ -122,39 +153,109 @@ export default {
/**
* Exports extension settings into a json file.
*/
exportSettings() {
exportSettings(settings?: SettingsInterface, filename = 'ultrawidify-settings.json') {
const settingBlob = new Blob(
[ JSON.stringify(this.settings.active, null, 2) ],
[ JSON.stringify(settings ?? 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";
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
},
startPartialExport() {
console.log('starting partial export ...');
const sites: ExportSiteData[] = [];
orderSitesForExport() {
const categories: ExportSiteDataCategory[] = [];
for (const site in this.settings.active.sites) {
sites.push({
...JSON.parse(JSON.stringify(this.settings.active.sites[site])),
const siteData: SiteSettingsInterface = JSON.parse(JSON.stringify(this.settings.active.sites[site]));
let category = categories.find(x => x.supportLevel === siteData.type ?? siteData.defaultType);
if (!category) {
const ssl = siteData.type ?? siteData.defaultType;
category = {
priority: exportDialogPriorityMap.indexOf(ssl),
supportLevel: ssl,
sites: []
}
categories.push(category);
}
category.sites.push({
...siteData,
key: site,
selectedForExport: true
selectedForExport: true,
visible: true,
});
}
categories.sort( (a: ExportSiteDataCategory,b: ExportSiteDataCategory) => a.priority - b.priority);
return categories;
},
getSelectedSites(categories: ExportSiteDataCategory[]) {
const sites = [];
for (const cat of categories) {
sites.push(...cat.sites.filter(x => x.selectedForExport));
}
return sites;
},
selectExportSite(quickSelection: 'all' | 'none' | 'hide-mine') {
if (quickSelection === 'hide-mine') {
this.partialExportDialogConfig.categories
.filter((x: ExportSiteDataCategory) => x.supportLevel === SiteSupportLevel.UserDefined || x.supportLevel === SiteSupportLevel.UserModified)
.map((x: ExportSiteDataCategory) => x.sites)
.filter((x: ExportSiteData) => !x.key.startsWith('@'))
.map((x: ExportSiteData) => x.selectedForExport = false);
return;
}
const setVisibility = quickSelection === 'all';
for(const cat of this.partialExportDialogConfig.categories) {
for (const site of cat.sites) {
if (site.key.startsWith('@')) {
site.selectedForExport = true;
} else {
site.selectedForExport = setVisibility;
}
}
}
},
startPartialExport() {
const categories = this.orderSitesForExport();
this.partialExportDialogConfig = {
visible: true,
sites,
categories,
exportSites: [] as ExportSiteData[],
confirm: () => {
console.log('selected sites:', this.partialExportDialogConfig.sites.filter(x => x.selectedForExport));
const selectedSites = this.getSelectedSites(this.partialExportDialogConfig.categories);
const settingsClone = JSON.parse(JSON.stringify(this.settings.active));
settingsClone.sites = {};
// ensure correct mapping and REMOVE GARBAGE PROPERTIES
for (const site of selectedSites) {
settingsClone[site.key] = {...site};
delete settingsClone[site.key].key;
delete settingsClone[site.key].selectedForExport;
delete settingsClone[site.key].visible;
}
this.exportSettings(settingsClone, 'uw-settings_partial-export.json');
// close the dialog
this.partialExportDialogConfig = {visible: false};
},
reject: () => {
this.partialExportDialogConfig = {visible: false};

View File

@ -88,8 +88,11 @@ export default defineComponent({
case '#iframe':
await this.setupIframe();
break;
case '#settings':
case '#updated':
case '#installed':
default:
await this.setupSettingsPage();
await this.setupSettingsPage(segment);
}
if (segment !== '#popup') {
@ -112,8 +115,12 @@ export default defineComponent({
/**
* Initializes page when it's being loaded from the settings
*/
async setupSettingsPage() {
async setupSettingsPage(segment) {
if (!segment) {
this.role = 'settings';
} else {
this.role = segment.replace('#', '');
}
this.logAggregator = new LogAggregator('');
this.logger = new ComponentLogger(this.logAggregator, 'App.vue');

View File

@ -17,7 +17,7 @@ const server = new UWServer();
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === "update") {
chrome.tabs.create({
url: chrome.runtime.getURL("ui/pages/updated/index.html")
url: chrome.runtime.getURL("ui/pages/settings/index.html#updated")
});
}
});

View File

@ -128,9 +128,6 @@ const config = {
{ from: 'csui/csui-overlay-dark.html', to: 'csui/csui-dark.html', transform: transformHtml },
{ from: 'csui/csui-overlay-light.html', to: 'csui/csui-light.html', transform: transformHtml },
{ from: 'ui/pages/settings/index.html', to: 'ui/pages/settings/index.html', transform: transformHtml },
// { from: 'ui/pages/installed/index.html', to: 'ui/pages/installed/index.html', transform: transformHtml },
{ from: 'ui/pages/updated/index.html', to: 'ui/pages/updated/index.html', transform: transformHtml },
{ from: 'ui/pages/settings/index.html', to: 'ui/pages/settings/index.html', transform: transformHtml },
{
from: 'manifest.json',
to: 'manifest.json',