2025-12-17 18:35:13 +01:00
|
|
|
<template>
|
2026-01-23 22:46:37 +01:00
|
|
|
<div class="w-full flex flex-col" style="margin-top: 1rem;">
|
|
|
|
|
<h2 class="text-[1.75em]">Simple video player picker</h2>
|
|
|
|
|
<div class="text text-sm text-stone-500">
|
|
|
|
|
<p>If your video is not aligned correctly, video player was not detected correctly.</p>
|
|
|
|
|
<p>You need to help by selecting the video player. Hover over boxes below. This will highlight part of the screen.</p>
|
|
|
|
|
<p>Select the first box that highlights the video player on the page.</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-01-25 21:47:31 +01:00
|
|
|
<!-- <div class="">
|
2026-01-23 22:46:37 +01:00
|
|
|
<div class="flex flex-row gap-4">
|
|
|
|
|
<button>How to use</button>
|
|
|
|
|
</div>
|
2026-01-25 21:47:31 +01:00
|
|
|
</div> -->
|
2025-12-17 18:35:13 +01:00
|
|
|
|
|
|
|
|
<!-- PLAYER ELEMENT SELECTOR FOR DUMMIES -->
|
2026-01-23 22:46:37 +01:00
|
|
|
<div class="flex flex-col-reverse gap-2">
|
|
|
|
|
<div
|
|
|
|
|
v-for="(element, index) of elementStack"
|
|
|
|
|
:key="index"
|
|
|
|
|
class="py-2 px-4 border border-stone-500 flex flex-col gap-2 text-sm cursor-pointer"
|
|
|
|
|
:class="{
|
|
|
|
|
'!border-blue-500': element.heuristics?.autoMatch,
|
|
|
|
|
'!border-teal-800': element.heuristics?.manualElementByParentIndex,
|
|
|
|
|
'!border-emerald-500': element.heuristics?.qsMatch,
|
|
|
|
|
'!border-red-700 bg-red-950/50': element.heuristics?.invalidSize,
|
|
|
|
|
'!border-primary-300': element.heuristics?.activePlayer,
|
|
|
|
|
'!pointer-events-none opacity-50': element.tagName == 'video',
|
|
|
|
|
}"
|
2025-12-17 18:35:13 +01:00
|
|
|
|
2026-01-23 22:46:37 +01:00
|
|
|
@mouseover="markElement(elementStack.length - index - 1, true)"
|
|
|
|
|
@mouseleave="markElement(elementStack.length - index - 1, false)"
|
2026-01-25 21:47:31 +01:00
|
|
|
@click="setPlayer(element, elementStack.length - index - 1)"
|
2026-01-23 22:46:37 +01:00
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
v-if="element.heuristics?.autoMatch"
|
|
|
|
|
class="text-primary-300 flex flex-row gap-2"
|
|
|
|
|
>
|
|
|
|
|
<mdicon name="check-circle" /> This element is currently treated as player element.
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<div class="tag">
|
|
|
|
|
<b><{{element.tagName}}></b> <i class="text-red-800">{{element.id ? `#`:''}}{{element.id}}</i> @ <span class="text-blue-500">{{element.width}}</span>x<span class="text-blue-500">{{element.height}}</span>
|
2025-12-17 18:35:13 +01:00
|
|
|
</div>
|
2026-01-23 22:46:37 +01:00
|
|
|
<div v-if="element.classList" class="text-nowrap overflow-hidden text-ellipsis text-stone-500/50">
|
|
|
|
|
<span v-for="cls of element.classList" :key="cls">.{{cls}} </span>
|
2025-12-17 18:35:13 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
2026-01-23 22:46:37 +01:00
|
|
|
<div
|
|
|
|
|
v-if="element.heuristics?.qsMatch"
|
|
|
|
|
class="text-emerald-600 flex flex-row gap-2"
|
|
|
|
|
>
|
|
|
|
|
<mdicon name="crosshairs" /> This element matches query string (advanced settings)
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
v-if="element.heuristics?.manualElementByParentIndex"
|
|
|
|
|
class="text-teal-800 flex flex-row gap-2"
|
|
|
|
|
>
|
|
|
|
|
<mdicon name="bookmark" /> This element has been manually selected as player element.
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
v-if="element.heuristics?.autoMatch"
|
|
|
|
|
class="text-blue-500 flex flex-row gap-2"
|
|
|
|
|
>
|
|
|
|
|
<mdicon name="refresh-auto" /> Automatic detections thinks this is the player.
|
|
|
|
|
</div>
|
|
|
|
|
<div
|
|
|
|
|
v-if="element.heuristics?.invalidSize"
|
|
|
|
|
class="text-red-700 flex flex-row gap-2"
|
|
|
|
|
>
|
|
|
|
|
<mdicon name="alert-remove" /> This element has invalid dimensions
|
2025-12-17 18:35:13 +01:00
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-01-23 22:46:37 +01:00
|
|
|
|
2025-12-17 18:35:13 +01:00
|
|
|
</div>
|
2026-01-23 22:46:37 +01:00
|
|
|
|
2025-12-17 18:35:13 +01:00
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script lang="ts">
|
2026-01-25 21:47:31 +01:00
|
|
|
import { PlayerDetectionMode } from '@src/common/enums/PlayerDetectionMode.enum';
|
|
|
|
|
import { SiteSupportLevel } from '@src/common/enums/SiteSupportLevel.enum';
|
|
|
|
|
import { SiteDOMSettingsInterface } from '@src/common/interfaces/SettingsInterface';
|
|
|
|
|
import { _cp } from '@src/common/utils/_cp';
|
2025-12-17 18:35:13 +01:00
|
|
|
import { defineComponent } from 'vue';
|
|
|
|
|
|
|
|
|
|
export default defineComponent({
|
|
|
|
|
components: {
|
|
|
|
|
|
|
|
|
|
},
|
|
|
|
|
data() {
|
|
|
|
|
return {
|
|
|
|
|
elementStack: [],
|
|
|
|
|
cssStack: [],
|
|
|
|
|
showLegend: false,
|
|
|
|
|
showAdvancedOptions: false,
|
|
|
|
|
tutorialVisible: false,
|
|
|
|
|
tutorialStep: 0
|
|
|
|
|
};
|
|
|
|
|
},
|
|
|
|
|
computed: {
|
|
|
|
|
},
|
|
|
|
|
mixins: [],
|
|
|
|
|
props: [
|
2026-01-23 22:46:37 +01:00
|
|
|
'settings', // not used?
|
2025-12-17 18:35:13 +01:00
|
|
|
'siteSettings',
|
|
|
|
|
'eventBus',
|
|
|
|
|
],
|
|
|
|
|
created() {
|
|
|
|
|
this.eventBus.subscribe(
|
|
|
|
|
'uw-config-broadcast',
|
|
|
|
|
{
|
|
|
|
|
source: this,
|
|
|
|
|
function: (config) => this.handleElementStack(config)
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
mounted() {
|
|
|
|
|
this.getPlayerTree();
|
|
|
|
|
},
|
|
|
|
|
destroyed() {
|
|
|
|
|
this.eventBus.unsubscribeAll(this);
|
|
|
|
|
},
|
|
|
|
|
methods: {
|
|
|
|
|
showTutorial() {
|
|
|
|
|
this.tutorialVisible = true;
|
|
|
|
|
this.tutorialStep = 0;
|
|
|
|
|
},
|
|
|
|
|
getPlayerTree() {
|
2026-01-15 02:33:21 +01:00
|
|
|
this.eventBus.send('get-player-tree');
|
2025-12-17 18:35:13 +01:00
|
|
|
},
|
|
|
|
|
handleElementStack(configBroadcast) {
|
|
|
|
|
if (configBroadcast.type === 'player-tree') {
|
|
|
|
|
this.elementStack = configBroadcast.config.reverse();
|
|
|
|
|
this.$nextTick( () => this.$forceUpdate() );
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
markElement(parentIndex, enable) {
|
2026-01-15 02:33:21 +01:00
|
|
|
this.eventBus.send('set-mark-element', {parentIndex, enable});
|
2025-12-17 18:35:13 +01:00
|
|
|
},
|
2026-01-25 21:47:31 +01:00
|
|
|
/**
|
|
|
|
|
* Designates new element as player element. Currently, we only need
|
|
|
|
|
* 'index', however at some point we might also set mode according
|
|
|
|
|
* to element flags.
|
|
|
|
|
*/
|
|
|
|
|
async setPlayer(element, index) {
|
|
|
|
|
await this.siteSettings.ensureSettings();
|
|
|
|
|
|
|
|
|
|
let domConfig: SiteDOMSettingsInterface = this.siteSettings.data.currentDOMConfig;
|
|
|
|
|
let domConfigName = this.siteSettings.data.activeDOMConfig.startsWith('@') ? 'custom' : this.siteSettings.data.activeDOMConfig;
|
|
|
|
|
|
|
|
|
|
switch (domConfig?.type) {
|
|
|
|
|
case SiteSupportLevel.UserModified:
|
|
|
|
|
case SiteSupportLevel.UserDefined:
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
domConfig = _cp(domConfig ?? this.settings.sites['@empty'].DOMConfig['@empty']);
|
|
|
|
|
domConfig.type = SiteSupportLevel.UserModified;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
domConfig.elements.player.detectionMode = index !== undefined ? PlayerDetectionMode.AncestorIndex : PlayerDetectionMode.Auto;
|
|
|
|
|
domConfig.elements.player.ancestorIndex = index;
|
|
|
|
|
|
|
|
|
|
await this.siteSettings.setUpdateFlags(['PlayerData']);
|
|
|
|
|
await this.siteSettings.set(`DOMConfig.${domConfigName}`, domConfig, {noSave: true});
|
|
|
|
|
await this.siteSettings.set('activeDOMConfig', domConfigName);
|
|
|
|
|
|
|
|
|
|
this.getPlayerTree();
|
2025-12-17 18:35:13 +01:00
|
|
|
},
|
|
|
|
|
/**
|
|
|
|
|
* Toggles active CSS for element of certain parent index.
|
|
|
|
|
* cssValue is optional and can be included in cssRule argument
|
|
|
|
|
*/
|
|
|
|
|
toggleCssForElement(index, cssRule, cssValue) {
|
|
|
|
|
// we will handle elements that put cssValue as a separate argument elsewhere
|
|
|
|
|
if (cssValue) {
|
|
|
|
|
return this.toggleCssForElement_3arg(index,cssRule, cssValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// this rule applies to current element — remove it!
|
|
|
|
|
if (this.cssStack[index]?.includes(cssRule)) {
|
|
|
|
|
this.cssStack[index] = this.cssStack[index].filter(x => ! x.includes(cssRule));
|
|
|
|
|
} else {
|
|
|
|
|
if (!this.cssStack[index]) {
|
|
|
|
|
this.cssStack[index] = [];
|
|
|
|
|
}
|
|
|
|
|
this.cssStack[index].push(cssRule)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//TODO: update settings!
|
|
|
|
|
},
|
|
|
|
|
toggleCssForElement_3arg(index, cssRule, cssValue) {
|
|
|
|
|
const matching = this.cssStack[index]?.find(x => x.includes(cssRule))
|
|
|
|
|
if (matching) {
|
|
|
|
|
this.cssStack[index] = this.cssStack[index].filter(x => ! x.includes(cssRule));
|
|
|
|
|
if (!matching.includes(cssValue)) {
|
|
|
|
|
this.cssStack[index].push(`${cssRule}: ${cssValue};`);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (!this.cssStack[index]) {
|
|
|
|
|
this.cssStack[index] = [];
|
|
|
|
|
}
|
|
|
|
|
this.cssStack[index].push(`${cssRule}: ${cssValue};`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//TODO: update settings!
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
</script>
|