Save player element settigs + simplified import/export

This commit is contained in:
Tamius Han 2025-12-27 23:22:54 +01:00
parent 3f5ab5ecc6
commit a8847bdeff
6 changed files with 218 additions and 36 deletions

View File

@ -354,6 +354,9 @@ const ExtensionConfPatch = Object.freeze([
userOptions.sites[site].defaultType = SiteSupportLevel.Unknown;
}
if (siteData.activeDOMConfig?.startsWith('community') || siteData.activeDOMConfig === 'official' || siteData.activeDOMConfig === 'empty' || siteData.activeDOMConfig === 'auto') {
siteData.activeDOMConfig = `@${siteData.activeDOMConfig}`;
}
for (const domConf in siteData.DOMConfig) {
logger.log('updateFn', "Updating domconf", domConf);
@ -405,27 +408,41 @@ const ExtensionConfPatch = Object.freeze([
}
}
console.log('new conf:', newConf)
userOptions.sites[site].DOMConfig[domConf] = newConf;
// migrate names — official and community options get @ at the start
let domConfName = domConf;
if (domConfName.startsWith('community') || domConfName === 'official' || domConfName === 'empty' || domConfName === 'auto') {
domConfName = `@${domConfName}`;
}
if (domConfName !== domConf) {
logger.warn(`updateFn`, '\n\nnaming for default domConf has changed. Old:', domConf, 'new:', domConfName);
delete userOptions.sites[site].DOMConfig[domConf];
}
userOptions.sites[site].DOMConfig[domConfName] = newConf;
}
}
// set new defaults for global and empty:
userOptions.sites['@global'].DOMConfig = {
'auto': {
'@auto': {
type: SiteSupportLevel.Unknown,
elements: {
player: {
detectionMode: PlayerDetectionMode.Auto,
allowAutoFallback: true,
},
video: {
detectionMode: PlayerDetectionMode.Auto,
allowAutoFallback: true,
}
}
}
}
userOptions.sites['@global'].activeDOMConfig = 'auto';
userOptions.sites['@global'].activeDOMConfig = '@auto';
userOptions.sites['@empty'].DOMConfig = {
'empty': {
'@empty': {
type: SiteSupportLevel.UserDefined,
elements: {
player: {
@ -439,7 +456,7 @@ const ExtensionConfPatch = Object.freeze([
},
}
};
userOptions.sites['@empty'].activeDOMConfig = 'empty';
userOptions.sites['@empty'].activeDOMConfig = '@empty';
logger.log('updateFn', 'Migration complete. New site settings:', userOptions.sites);
}

View File

@ -823,9 +823,9 @@ const ExtensionConf: SettingsInterface = {
alignment: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Center},
},
activeDOMConfig: 'auto',
activeDOMConfig: '@auto',
DOMConfig: {
'auto': {
'@auto': {
type: SiteSupportLevel.Unknown,
elements: {
player: {
@ -856,9 +856,9 @@ const ExtensionConf: SettingsInterface = {
persistCSA: CropModePersistence.Default,
activeDOMConfig: 'empty',
activeDOMConfig: '@empty',
DOMConfig: {
'empty': {
'@empty': {
type: SiteSupportLevel.UserDefined,
elements: {
player: {
@ -890,9 +890,9 @@ const ExtensionConf: SettingsInterface = {
defaultType: SiteSupportLevel.OfficialSupport, // if user mucks around with settings, type changes to 'user-defined'.
// We still want to know what the original type was, hence defaultType
activeDOMConfig: 'official',
activeDOMConfig: '@official',
DOMConfig: {
'official': {
'@official': {
type: SiteSupportLevel.OfficialSupport,
elements: {
player: {
@ -918,9 +918,9 @@ const ExtensionConf: SettingsInterface = {
defaultType: SiteSupportLevel.OfficialSupport, // if user mucks around with settings, type changes to 'user-defined'.
// We still want to know what the original type was, hence defaultType
activeDOMConfig: 'official',
activeDOMConfig: '@official',
DOMConfig: {
'official': {
'@official': {
type: SiteSupportLevel.OfficialSupport,
elements: {
player: {
@ -943,7 +943,7 @@ const ExtensionConf: SettingsInterface = {
defaultType: SiteSupportLevel.CommunitySupport,
DOMConfig: {
'community': {
'@community': {
type: SiteSupportLevel.CommunitySupport,
elements: {
player: {
@ -966,9 +966,9 @@ const ExtensionConf: SettingsInterface = {
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
type: SiteSupportLevel.CommunitySupport,
defaultType: SiteSupportLevel.CommunitySupport,
activeDOMConfig: 'community-mstefan99',
activeDOMConfig: '@community-mstefan99',
DOMConfig: {
'community-mstefan99': {
'@community-mstefan99': {
type: SiteSupportLevel.OfficialSupport,
elements: {
player: {
@ -996,9 +996,9 @@ const ExtensionConf: SettingsInterface = {
type: SiteSupportLevel.OfficialSupport,
defaultType: SiteSupportLevel.OfficialSupport,
activeDOMConfig: 'official',
activeDOMConfig: '@official',
DOMConfig: {
'official': {
'@official': {
type: SiteSupportLevel.OfficialSupport,
elements: {
player: {
@ -1049,9 +1049,9 @@ const ExtensionConf: SettingsInterface = {
applyToEmbeddedContent: EmbeddedContentSettingsOverridePolicy.UseAsDefault,
type: SiteSupportLevel.CommunitySupport,
defaultType: SiteSupportLevel.CommunitySupport,
activeDOMConfig: 'community',
activeDOMConfig: '@community',
DOMConfig: {
'community': {
'@community': {
type: SiteSupportLevel.CommunitySupport,
elements: {
player: {

View File

@ -11,7 +11,7 @@
'normal': dialogType === 'normal'
}"
>
{{ dialogText || 'Confirm action' }}
{{ dialogTitle || 'Confirm action' }}
</div>
<div class="body">
{{ dialogText || 'Are you sure you want to do that?' }}
@ -41,6 +41,7 @@
},
btnClass
]"
:disabled="disabled"
@click="popupVisible = true"
>
<slot></slot>
@ -62,7 +63,8 @@ export default defineComponent({
'dialogText',
'confirmText',
'cancelText',
'dialogType'
'dialogType',
'disabled',
],
methods: {
confirmAction() {

View File

@ -2,14 +2,15 @@
<!-- ADVANCED OPTIONS -->
<div class="flex flex-col gap-2">
<div class="flex flex-row gap-2 justify-end">
<button @click="openSelectSnapshotDialog()">Load existing config</button>
<UploadJsonFileButton
<button @click="openSelectSnapshotDialog()">Switch preset</button>
<!-- <UploadJsonFileButton
@importedJson="handleImportedSettings"
@error="handleSettingsImportError"
>
Import settings
</UploadJsonFileButton>
<button>Export config</button>
<button @click="exportDialog.visible = true">Export config</button> -->
<button class="flex flex-rows items-center" @click="openCopyPasteDialog()"><mdicon class="font-normal mr-2" name="content-copy" :size="16" /> Copy/paste config</button>
</div>
<div v-if="loaded" class="w-[690px] max-w-full">
@ -145,16 +146,16 @@
</div>
<div class="w-full flex flex-row gap-2 justify-end">
<button>Save as</button>
<button :disabled="DOMConfigName?.startsWith('@')">Save</button>
<button>Close</button>
<button @click="openSaveAsDialog()">Save as</button>
<button :disabled="!siteSettings.raw.DOMConfig?.[DOMConfigName] || DOMConfigName?.startsWith('@')">Save</button>
<button @click="closeForm()">Close</button>
</div>
<!-- PRESET SELECT DIALOG -->
<Popup
v-if="selectSnapshotDialog.visible"
title="Select configuration"
@onClose="selectSnapshotDialog.visible = false"
@onCancel="selectSnapshotDialog.visible = false"
>
<div>Select configuration:</div>
<div class="flex flex-col gap-2 py-4">
@ -177,7 +178,39 @@
</div>
</Popup>
<!-- FINISH IMPORT DIALOG -->
<!-- COPY/PASTE CONFIG DIALOG -->
<Popup
v-if="copyPasteDialog.visible"
confirmButtonText="Apply"
cancelButtonText="Cancel"
title="Copy or paste configuration"
@onCancel="copyPasteDialog.onClose()"
@onConfirm="copyPasteDialog.onSave()"
>
<div class="field">
<div class="label !min-w-16">Raw config:</div>
<div class="input !w-[560px] min-h-[320px]">
<textarea class="h-[320px] w-full resize-none" v-model="copyPasteDialog.data.jsonTxt"></textarea>
</div>
</div>
<div v-if="settings.active.ui.devMode" class="field">
<div class="label">Change support level:</div>
<div class="select">
<select @change="_cpDialog_setSupportLevel">
<option :value="undefined">(select option to override)</option>
<option :value="SiteSupportLevel.OfficialSupport">Official support</option>
<option :value="SiteSupportLevel.CommunitySupport">Community-supported</option>
<option :value="SiteSupportLevel.BetaSupport">Texsting/experimental</option>
<option :value="SiteSupportLevel.UserDefined">User-defined</option>
<option :value="SiteSupportLevel.UserModified">User-modified</option>
<option :value="SiteSupportLevel.OfficialBlacklist">Officially blacklisted</option>
<option :value="SiteSupportLevel.Unknown">Unknown</option>
</select>
</div>
</div>
</Popup>
<!-- FINISH IMPORT DIALOG (half-finished, currently unused)-->
<Popup
v-if="finishImportDialog.visible"
:title="finishImportDialog.data.invalidJson ? 'Import failed' : 'Select configuration'"
@ -210,9 +243,58 @@
</template>
</Popup>
<!-- EXPORT CONFIG DIALOG -->
<!-- EXPORT CONFIG DIALOG (half finished, currently unused) -->
<Popup
v-if="exportDialog.visible"
title="Export configuration"
:clientSideButtons="true"
>
<div class="w-[24rem] max-w-full">
<div class="field radio">
<input type="radio" v-model="exportDialog.data.exportType" value="current">
<div class="label-r">Export current configuration</div>
</div>
<div>
</div>
<div class="field radio ">
<input type="radio" v-model="exportDialog.data.exportType" value="multiple">
<div class="label-r">Export the following configurations</div>
</div>
</div>
</Popup>
<!-- SAVE AS DIALOG -->
<Popup
v-if="saveAsDialog.visible"
title="Save preset as ..."
:clientSideButtons="true"
>
<div class="field">
<div class="label">Configuration name</div>
<div class="input">
<input v-model="saveAsDialog.data.name" />
</div>
</div>
<div class="hint">
Configuration name may include letters, numbers, underscores (_) and dashes (-).
</div>
<div class="flex flex-row justify-end gap-2">
<ConfirmButton v-if="siteSettings.raw.DOMConfig?.[saveAsDialog.data.name]"
:disabled="!/^[a-zA-Z0-9-_]*$/.test(saveAsDialog.data.name)"
dialogType="danger"
dialogTitle="Settings preset exists"
dialogText="Settings preset with this name already exists. Do you want to overwrite it?"
confirmText="Overwrite"
@onConfirmed="finishSaveAs"
>
Save
</ConfirmButton>
<button v-else @click="finishSaveAs" :disabled="!/^[a-zA-Z0-9-_]*$/.test(saveAsDialog.data.name)">Save</button>
<button @click="saveAsDialog.visible = false">Cancel</button>
</div>
</Popup>
</template>
<script lang="ts">
@ -220,18 +302,19 @@ import { SiteSettings } from '@src/ext/lib/settings/SiteSettings';
import { PlayerDetectionMode } from '@src/common/enums/PlayerDetectionMode.enum';
import UploadJsonFileButton from '@components/common/UploadJsonFileButton.vue';
import SupportLevelIndicator from '@components/common/SupportLevelIndicator.vue';
import ConfirmButton from '@components/common/ConfirmButton.vue';
import Popup from '@components/common/Popup.vue';
import { _cp } from '@src/common/js/utils';
import { SiteDOMSettingsInterface } from '@src/common/interfaces/SettingsInterface';
import { SiteSupportLevel } from '../../../../../common/enums/SiteSupportLevel.enum';
export default({
components: {
PlayerDetectionMode,
UploadJsonFileButton,
SupportLevelIndicator,
Popup,
ConfirmButton,
},
props: [
'settings',
@ -240,11 +323,15 @@ export default({
data() {
return {
PlayerDetectionMode,
SiteSupportLevel,
loaded: false,
DOMConfigData: undefined as any,
DOMConfigName: undefined as any,
selectSnapshotDialog: {visible: false},
finishImportDialog: {visible: false}
copyPasteDialog: {visible: false},
finishImportDialog: {visible: false},
exportDialog: {visible: false, data: {exportType: 'current'}},
saveAsDialog: {visible: false}
}
},
watch: {
@ -281,6 +368,8 @@ export default({
this.loaded = true;
},
openSelectSnapshotDialog() {
this.selectSnapshotDialog = {
visible: true
@ -293,13 +382,66 @@ export default({
this.selectSnapshotDialog = false;
},
openCopyPasteDialog() {
this.copyPasteDialog = {
visible: true,
data: {
jsonTxt: JSON.stringify(this.DOMConfigData, null, 2)
},
onClose: () => {
this.copyPasteDialog = {visible: false};
},
onSave: () => {
if (JSON.stringify(JSON.parse(this.copyPasteDialog.data.jsonTxt)) !== JSON.stringify(this.DOMConfigData) ) {
this.DOMConfigData = JSON.parse(this.copyPasteDialog.data.jsonTxt);
if (!this.DOMConfigData.type) {
this.DOMConfigData.type = SiteSupportLevel.UserDefined;
}
let pastedCount = 0;
for (const key in this.siteSettings.data.DOMConfig) {
if (key.startsWith('pasted-settings')) {
pastedCount++;
}
}
if (!this.DOMConfigName || this.DOMConfigName.startsWith('@')) {
this.DOMConfigName = `pasted-settings${pastedCount ? `-${pastedCount}` : ''}`;
}
this.copyPasteDialog = {visible: false}
}
}
}
},
/** Used when dev mode settings are visible in order to easily override config type */
_cpDialog_setSupportLevel(event) {
const value = event.target.value;
const obj = JSON.parse(this.copyPasteDialog.data.jsonTxt);
delete obj.type;
this.copyPasteDialog.data.jsonTxt = JSON.stringify({
type: value,
...obj,
}, null, 2);
},
handleImportedSettings(json: {activeDOMConfig?: string, DOMConfig: { [x: string]: SiteDOMSettingsInterface & {overwrite?: boolean}}} | SiteDOMSettingsInterface) {
let afterImportDialogData;
if ((json as any).DOMConfig) {
afterImportDialogData = json;
} else if ((json as SiteDOMSettingsInterface).type) {
const key = `imported-${new Date().toISOString()}`;
let importedCount = 0;
for (const key in this.siteSettings.data.DOMConfig) {
if (key.startsWith('imported-settings')) {
importedCount++;
}
}
const key = `imported-settings${importedCount ? `-${importedCount}` : ''}`;
afterImportDialogData = {
activeDOMConfig: key,
DOMConfig: {
@ -321,6 +463,22 @@ export default({
handleSettingsImportError(error) {
console.error(`[ultrawidify] Failed to upload snapshot. Error:`, error);
},
openSaveAsDialog() {
this.saveAsDialog = {
visible: true,
data: {
name: this.DOMConfigName.replaceAll('@', '').trim(),
}
}
},
async finishSaveAs() {
this.DOMConfigName = this.saveAsDialog.data.name;
this.siteSettings.raw.DOMConfig[this.DOMConfigName] = this.DOMConfigData;
this.siteSettings.raw.activeDOMConfig = this.DOMConfigName;
this.saveAsDialog = {visible: false};
await this.settings.save();
}
}
});
</script>

View File

@ -132,6 +132,10 @@
}
}
}
&.radio {
@apply flex flex-row gap-4 items-center;
}
}
.hint {
@apply text-primary-200/70;

View File

@ -5,6 +5,7 @@
"outDir": "./ts-out",
"allowJs": true,
"target": "esnext",
"lib": ["esnext", "DOM"],
"types": [
"chrome",
"node"