Settings snapshot handling, moved update page to unified settings page
This commit is contained in:
parent
ca751a660f
commit
4180d5b5b1
4
package-lock.json
generated
4
package-lock.json
generated
@ -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",
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
]);
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -38,7 +38,7 @@ html, body {
|
||||
}
|
||||
|
||||
a {
|
||||
@apply text-primary-400;
|
||||
@apply text-primary-400 cursor-pointer;
|
||||
|
||||
&:hover {
|
||||
@apply text-primary-300;
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
123
src/ui/components/segments/AfterUpdate/AfterUpdate.vue
Normal file
123
src/ui/components/segments/AfterUpdate/AfterUpdate.vue
Normal 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>
|
||||
@ -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 & 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
|
||||
});
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 & 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};
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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")
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@ -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',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user