Checkpoint: aard status indicator + trigger zone editor now kinda works
This commit is contained in:
parent
aabd5e75d8
commit
13cfb9ff14
@ -52,6 +52,8 @@ export type SettingsReloadComponent = 'PlayerData' | 'VideoData';
|
||||
export type SettingsReloadFlags = true | SettingsReloadComponent;
|
||||
|
||||
export interface AardSettings {
|
||||
aardType: 'webgl' | 'legacy' | 'auto';
|
||||
|
||||
disabledReason: string, // if automatic aspect ratio has been disabled, show reason
|
||||
allowedMisaligned: number, // top and bottom letterbox thickness can differ by this much.
|
||||
// Any more and we don't adjust ar.
|
||||
|
@ -2,12 +2,11 @@
|
||||
<div
|
||||
class="context-spawn uw-ui-trigger"
|
||||
style="z-index: 1000"
|
||||
@mouseenter="(ev) => setTriggerZoneActive(true, ev)"
|
||||
@mouseleave="(ev) => setTriggerZoneActive(false, ev)"
|
||||
>
|
||||
<div
|
||||
class="spawn-container uw-trigger"
|
||||
:style="triggerZoneStyles"
|
||||
@mouseenter="(ev) => setTriggerZoneActive(true, ev)"
|
||||
>
|
||||
|
||||
</div>
|
||||
@ -30,99 +29,126 @@
|
||||
</div>
|
||||
</template>
|
||||
<slot>
|
||||
<div class="menu-width">
|
||||
<GhettoContextMenuItem :disableHover="true" :css="{'ard-blocked': true}">
|
||||
<div v-if="statusFlags.hasDrm || true" class="smallcaps text-center">
|
||||
<b>NOTE:</b><br/>
|
||||
<b>Autodetection<br/>blocked by website</b>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<!--
|
||||
Didn't manage to ensure that extension status pops up above other menu items in less than 3 minutes with z-index,
|
||||
so wrapping 'status' and 'real menu items' in two different divs, ordering them in the opposite way, and then
|
||||
ensuring correct ordering with flex-direction: column-reverse ended up being easier and faster.
|
||||
-->
|
||||
<div class="menu-width flex-reverse-order">
|
||||
<div style="z-index: 1000">
|
||||
<GhettoContextMenu alignment="right">
|
||||
<template v-slot:activator>
|
||||
Crop
|
||||
</template>
|
||||
<slot>
|
||||
<GhettoContextMenuOption
|
||||
v-for="(command, index) of settings?.active.commands.crop"
|
||||
:key="index"
|
||||
:label="command.label"
|
||||
:shortcut="getKeyboardShortcutLabel(command)"
|
||||
@click="execAction(command)"
|
||||
>
|
||||
</GhettoContextMenuOption>
|
||||
</slot>
|
||||
</GhettoContextMenu>
|
||||
<GhettoContextMenu alignment="right">
|
||||
<template v-slot:activator>
|
||||
Stretch
|
||||
</template>
|
||||
<slot>
|
||||
<GhettoContextMenuOption
|
||||
v-for="(command, index) of settings?.active.commands.stretch"
|
||||
:key="index"
|
||||
:label="command.label"
|
||||
:shortcut="getKeyboardShortcutLabel(command)"
|
||||
@click="execAction(command)"
|
||||
>
|
||||
</GhettoContextMenuOption>
|
||||
</slot>
|
||||
</GhettoContextMenu>
|
||||
<GhettoContextMenu alignment="right">
|
||||
<template v-slot:activator>
|
||||
<div class="context-item">
|
||||
Align
|
||||
</div>
|
||||
</template>
|
||||
<slot>
|
||||
<GhettoContextMenuItem :disableHover="true" :css="{'reduced-padding': true}">
|
||||
<AlignmentOptionsControlComponent
|
||||
:eventBus="eventBus"
|
||||
>
|
||||
</AlignmentOptionsControlComponent>
|
||||
</GhettoContextMenuItem>
|
||||
</slot>
|
||||
</GhettoContextMenu>
|
||||
|
||||
<!-- shortcut for configuring UI -->
|
||||
<GhettoContextMenuOption
|
||||
v-if="settings.active.newFeatureTracker?.['uw6.ui-popup']?.show > 0"
|
||||
@click="showUwWindow('playerUiSettings')"
|
||||
>
|
||||
<span style="color: #fa6;">I hate this popup<br/></span>
|
||||
<span style="font-size: 0.8em">
|
||||
<span style="text-transform: uppercase; font-size: 0.8em">
|
||||
<a @click="showUwWindow('playerUiSettings')">
|
||||
Do something about it
|
||||
</a> × <a @click="acknowledgeNewFeature('uw6.ui-popup')">keep the popup</a>
|
||||
</span>
|
||||
<br/>
|
||||
<span style="opacity: 0.5">This menu option will show {{settings.active.newFeatureTracker?.['uw6.ui-popup']?.show}} more<br/> times; or until clicked or dismissed.<br/>
|
||||
Also accessible via <span style="font-variant: small-caps">extension settings</span>.
|
||||
</span>
|
||||
</span>
|
||||
</GhettoContextMenuOption>
|
||||
|
||||
<!-- -->
|
||||
<GhettoContextMenuOption
|
||||
@click="showUwWindow()"
|
||||
label="Extension settings"
|
||||
>
|
||||
</GhettoContextMenuOption>
|
||||
<GhettoContextMenuOption
|
||||
@click="showUwWindow('playerDetection')"
|
||||
label="Incorrect cropping?"
|
||||
>
|
||||
</GhettoContextMenuOption>
|
||||
<GhettoContextMenuOption
|
||||
@click="showUwWindow('about')"
|
||||
label="Not working?"
|
||||
>
|
||||
</GhettoContextMenuOption>
|
||||
</div>
|
||||
</GhettoContextMenuItem>
|
||||
<GhettoContextMenu alignment="right">
|
||||
<template v-slot:activator>
|
||||
Crop
|
||||
</template>
|
||||
<slot>
|
||||
<GhettoContextMenuOption
|
||||
v-for="(command, index) of settings?.active.commands.crop"
|
||||
:key="index"
|
||||
:label="command.label"
|
||||
:shortcut="getKeyboardShortcutLabel(command)"
|
||||
@click="execAction(command)"
|
||||
|
||||
<div style="z-index: 10000">
|
||||
<GhettoContextMenuItem
|
||||
class="extension-status-messages"
|
||||
:disableHover="true"
|
||||
>
|
||||
</GhettoContextMenuOption>
|
||||
</slot>
|
||||
</GhettoContextMenu>
|
||||
<GhettoContextMenu alignment="right">
|
||||
<template v-slot:activator>
|
||||
Stretch
|
||||
</template>
|
||||
<slot>
|
||||
<GhettoContextMenuOption
|
||||
v-for="(command, index) of settings?.active.commands.stretch"
|
||||
:key="index"
|
||||
:label="command.label"
|
||||
:shortcut="getKeyboardShortcutLabel(command)"
|
||||
@click="execAction(command)"
|
||||
>
|
||||
</GhettoContextMenuOption>
|
||||
</slot>
|
||||
</GhettoContextMenu>
|
||||
<GhettoContextMenu alignment="right">
|
||||
<template v-slot:activator>
|
||||
<div class="context-item">
|
||||
Align
|
||||
</div>
|
||||
</template>
|
||||
<slot>
|
||||
<GhettoContextMenuItem :disableHover="true" :css="{'reduced-padding': true}">
|
||||
<AlignmentOptionsControlComponent
|
||||
:eventBus="eventBus"
|
||||
Site compatibility:
|
||||
<SupportLevelIndicator
|
||||
:siteSupportLevel="siteSupportLevel"
|
||||
>
|
||||
</AlignmentOptionsControlComponent>
|
||||
</SupportLevelIndicator>
|
||||
<div v-if="statusFlags.hasDrm" class="aard-blocked">
|
||||
Autodetection potentially<br/>
|
||||
unavailable due to <a href="https://en.wikipedia.org/wiki/Digital_rights_management">DRM</a>.
|
||||
</div>
|
||||
<div v-else-if="statusFlags.aardErrors?.cors" class="aard-blocked">
|
||||
Autodetection blocked<br/>
|
||||
by site/browser (CORS).
|
||||
</div>
|
||||
<div v-else-if="statusFlags.aardErrors?.webglError" class="aard-blocked">
|
||||
Autodetection unavailable<br/>
|
||||
due to webgl error.
|
||||
</div>
|
||||
</GhettoContextMenuItem>
|
||||
</slot>
|
||||
</GhettoContextMenu>
|
||||
|
||||
<!-- shortcut for configuring UI -->
|
||||
<GhettoContextMenuOption
|
||||
v-if="settings.active.newFeatureTracker?.['uw6.ui-popup']?.show > 0"
|
||||
@click="showUwWindow('playerUiSettings')"
|
||||
>
|
||||
<span style="color: #fa6;">I hate this popup<br/></span>
|
||||
<span style="font-size: 0.8em">
|
||||
<span style="text-transform: uppercase; font-size: 0.8em">
|
||||
<a @click="showUwWindow('playerUiSettings')">
|
||||
Do something about it
|
||||
</a> × <a @click="acknowledgeNewFeature('uw6.ui-popup')">keep the popup</a>
|
||||
</span>
|
||||
<br/>
|
||||
<span style="opacity: 0.5">This menu option will show {{settings.active.newFeatureTracker?.['uw6.ui-popup']?.show}} more<br/> times; or until clicked or dismissed.<br/>
|
||||
Also accessible via <span style="font-variant: small-caps">extension settings</span>.
|
||||
</span>
|
||||
</span>
|
||||
</GhettoContextMenuOption>
|
||||
|
||||
<!-- -->
|
||||
|
||||
<GhettoContextMenuOption
|
||||
@click="showUwWindow()"
|
||||
label="Extension settings"
|
||||
>
|
||||
</GhettoContextMenuOption>
|
||||
<GhettoContextMenuOption
|
||||
@click="showUwWindow('about')"
|
||||
label="Not working?"
|
||||
>
|
||||
</GhettoContextMenuOption>
|
||||
</div>
|
||||
</div>
|
||||
</slot>
|
||||
</GhettoContextMenu>
|
||||
</div>
|
||||
|
||||
|
||||
<div
|
||||
v-if="settingsInitialized && uwWindowVisible"
|
||||
class="uw-window flex flex-col uw-clickable"
|
||||
@ -139,6 +165,19 @@
|
||||
@preventClose="(event) => uwWindowFadeOutDisabled = event"
|
||||
></PlayerUIWindow>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="context-spawn uw-ui-trigger"
|
||||
style="z-index: 1000;"
|
||||
>
|
||||
<TriggerZoneEditor
|
||||
class="uw-clickable"
|
||||
:settings="settings"
|
||||
:playerDimensions="playerDimensions"
|
||||
>
|
||||
</TriggerZoneEditor>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@ -154,6 +193,8 @@ import EventBus from '../ext/lib/EventBus';
|
||||
import UIProbeMixin from './src/utils/UIProbeMixin';
|
||||
import KeyboardShortcutParserMixin from './src/utils/KeyboardShortcutParserMixin';
|
||||
import CommsMixin from './src/utils/CommsMixin';
|
||||
import SupportLevelIndicator from './src/components/SupportLevelIndicator.vue';
|
||||
import TriggerZoneEditor from './src/components/TriggerZoneEditor.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -162,6 +203,8 @@ export default {
|
||||
GhettoContextMenuItem,
|
||||
GhettoContextMenuOption,
|
||||
AlignmentOptionsControlComponent,
|
||||
SupportLevelIndicator,
|
||||
TriggerZoneEditor,
|
||||
},
|
||||
mixins: [
|
||||
UIProbeMixin,
|
||||
@ -220,10 +263,13 @@ export default {
|
||||
|
||||
statusFlags: {
|
||||
hasDrm: undefined,
|
||||
aardErrors: undefined,
|
||||
},
|
||||
defaultWindowTab: 'videoSettings',
|
||||
|
||||
saveState: {},
|
||||
siteSettings: undefined,
|
||||
previewZoneVisible: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@ -236,6 +282,12 @@ export default {
|
||||
windowHeight() {
|
||||
return window.innerHeight;
|
||||
},
|
||||
// LPT: NO ARROW FUNCTIONS IN COMPUTED,
|
||||
// IS SUPER HARAM
|
||||
// THINGS WILL NOT WORK IF YOU USE ARROWS
|
||||
siteSupportLevel() {
|
||||
return (this.site && this.siteSettings) ? this.siteSettings.data.type || 'no-support' : 'waiting';
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
showUi(visible) {
|
||||
@ -262,6 +314,8 @@ export default {
|
||||
});
|
||||
|
||||
this.settings = new Settings({afterSettingsSaved: this.updateConfig, logger: this.logger});
|
||||
this.settings.listenAfterChange(() => this.updateTriggerZones());
|
||||
|
||||
await this.settings.init();
|
||||
this.settingsInitialized = true;
|
||||
|
||||
@ -272,12 +326,20 @@ export default {
|
||||
});
|
||||
|
||||
this.eventBus.subscribe('uw-config-broadcast', {function: (data) => {
|
||||
if (data.type === 'drm-status') {
|
||||
this.statusFlags.hasDrm = data.hasDrm;
|
||||
switch (data.type) {
|
||||
case 'drm-status':
|
||||
this.statusFlags.hasDrm = data.hasDrm;
|
||||
break;
|
||||
case 'aard-error':
|
||||
this.statusFlags.aardErrors = data.aardErrors;
|
||||
break;
|
||||
case 'player-dimensions':
|
||||
console.log('player dimensions response received.', data);
|
||||
this.playerDimensionsUpdate(data.data);
|
||||
break;
|
||||
}
|
||||
}});
|
||||
|
||||
|
||||
this.eventBus.subscribe('uw-set-ui-state', { function: (data) => {
|
||||
if (data.globalUiVisible !== undefined) {
|
||||
if (this.isGlobal) {
|
||||
@ -318,11 +380,19 @@ export default {
|
||||
}
|
||||
);
|
||||
|
||||
this.eventBus.subscribe('ui-trigger-zone-update', {
|
||||
function: (data) => {
|
||||
this.showTriggerZonePreview = data.previewZoneVisible;
|
||||
// this.;
|
||||
}
|
||||
});
|
||||
|
||||
this.sendToParentLowLevel('uwui-get-role', null);
|
||||
this.sendToParentLowLevel('uwui-get-theme', null);
|
||||
|
||||
//
|
||||
|
||||
this.sendToParentLowLevel('uw-bus-tunnel', {
|
||||
action: 'get-player-dimensions'
|
||||
});
|
||||
},
|
||||
|
||||
methods: {
|
||||
@ -344,6 +414,7 @@ export default {
|
||||
if (!this.site) {
|
||||
this.origin = event.origin;
|
||||
this.site = event.origin.split('//')[1];
|
||||
this.siteSettings = this.settings.getSiteSettings(this.site);
|
||||
}
|
||||
return this.handleProbe(event.data, event.origin); // handleProbe is defined in UIProbeMixin
|
||||
case 'uw-bus-tunnel':
|
||||
@ -552,4 +623,34 @@ export default {
|
||||
// }
|
||||
}
|
||||
|
||||
.extension-status-messages {
|
||||
z-index: 1000;
|
||||
text-transform: uppercase;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
|
||||
width: 112.25%;
|
||||
transform: translate(-12.5%, 12.5%) scale(0.75);
|
||||
|
||||
> * {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.flex-reverse-order {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
|
||||
}
|
||||
|
||||
.aard-blocked {
|
||||
color: #fa6;
|
||||
}
|
||||
|
||||
.trigger-zone-preview {
|
||||
border: 4px solid #fa4;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
BIN
src/csui/res/img/grid_512.webp
Normal file
BIN
src/csui/res/img/grid_512.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 273 KiB |
@ -5,41 +5,13 @@
|
||||
<div class="popup-window-header">
|
||||
<div class="header-title">
|
||||
<div class="popup-title">Ultrawidify <small>{{settings?.active?.version}} - {{BrowserDetect.processEnvChannel}}</small></div>
|
||||
<div class="site-support-info">
|
||||
<div class="site-support-info flex flex-row">
|
||||
<div class="site-support-site">{{site}}</div>
|
||||
<template v-if="inPlayer">
|
||||
<div v-if="siteSupportLevel === 'official'" class="site-support official">
|
||||
<mdicon name="check-decagram" />
|
||||
<div>Verified</div>
|
||||
<div class="tooltip">The extension is being tested and should work on this site.</div>
|
||||
</div>
|
||||
<div v-if="siteSupportLevel === 'community'" class="site-support community">
|
||||
<mdicon name="handshake" />
|
||||
<div>Community</div>
|
||||
<div class="tooltip">
|
||||
People say extension works on this site (or have provided help getting the extension to work if it didn't).<br/><br/>
|
||||
Tamius (the dev) does not test the extension on this site, probably because it requires a subscription or
|
||||
is geoblocked.
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="siteSupportLevel === 'no-support'" class="site-support no-support">
|
||||
<mdicon name="help-circle-outline" />
|
||||
<div>Unknown</div>
|
||||
<div class="tooltip">
|
||||
Not officially supported. Extension will try to fix things, but no promises.<br/><br/>
|
||||
Tamius (the dev) does not test the extension on this site for various reasons
|
||||
(unaware, not using the site, language barrier, geoblocking, paid services Tam doesn't use).
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="siteSupportLevel === 'user-added'" class="site-support user-added">
|
||||
<mdicon name="account" />
|
||||
<div>Custom</div>
|
||||
<div class="tooltip">
|
||||
You have manually changed settings for this site. The extension is doing what you told it to do.
|
||||
</div>
|
||||
</div>
|
||||
<mdicon v-if="siteSupportLevel === 'community'" class="site-support supported" name="checkbox-marked-circle" />
|
||||
</template>
|
||||
<SupportLevelIndicator
|
||||
v-if="inPlayer"
|
||||
:siteSupportLevel="siteSupportLevel"
|
||||
>
|
||||
</SupportLevelIndicator>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-buttons">
|
||||
@ -116,7 +88,6 @@
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
>
|
||||
|
||||
</PlayerUiSettings>
|
||||
<BaseExtensionSettings
|
||||
v-if="selectedTab === 'extensionSettings'"
|
||||
@ -146,6 +117,11 @@
|
||||
v-if="selectedTab === 'about'"
|
||||
>
|
||||
</AboutPanel>
|
||||
<ResetBackupPanel
|
||||
v-if="selectedTab === 'resetBackup'"
|
||||
:settings="settings"
|
||||
>
|
||||
</ResetBackupPanel>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -161,6 +137,9 @@ import BrowserDetect from '../../ext/conf/BrowserDetect'
|
||||
import ChangelogPanel from './PlayerUiPanels/ChangelogPanel.vue'
|
||||
import AboutPanel from './PlayerUiPanels/AboutPanel.vue'
|
||||
import PlayerUiSettings from './PlayerUiPanels/PlayerUiSettings.vue'
|
||||
import ResetBackupPanel from './PlayerUiPanels/ResetBackupPanel.vue'
|
||||
|
||||
import SupportLevelIndicator from './components/SupportLevelIndicator.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -171,7 +150,9 @@ export default {
|
||||
DebugPanel,
|
||||
PlayerUiSettings,
|
||||
ChangelogPanel,
|
||||
AboutPanel
|
||||
AboutPanel,
|
||||
SupportLevelIndicator,
|
||||
ResetBackupPanel,
|
||||
},
|
||||
mixins: [],
|
||||
data() {
|
||||
@ -185,11 +166,12 @@ export default {
|
||||
{id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' },
|
||||
{id: 'playerUiSettings', label: 'In-player interface', icon: 'movie-cog-outline' },
|
||||
{id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
|
||||
{id: 'autodetectionSettings', label: 'Autodetection options', icon: ''},
|
||||
{id: 'autodetectionSettings', label: 'Autodetection options', icon: 'auto-fix'},
|
||||
// {id: 'advancedOptions', label: 'Advanced options', icon: 'cogs' },
|
||||
// {id: 'debugging', label: 'Debugging', icon: 'bug-outline' }
|
||||
{id: 'changelog', label: 'What\'s new', icon: 'newspaper-plus' },
|
||||
{id: 'about', label: 'About', icon: 'star-four-points-circle'}
|
||||
{id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
|
||||
{id: 'about', label: 'About', icon: 'information-outline'},
|
||||
{id: 'resetBackup', label: 'Reset and backup', icon: 'file-restore-outline'},
|
||||
],
|
||||
selectedTab: 'extensionSettings',
|
||||
BrowserDetect: BrowserDetect,
|
||||
@ -272,86 +254,6 @@ export default {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.site-support-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.site-support-site {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.site-support {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
margin-left: 1rem;
|
||||
border-radius: 8px;
|
||||
padding: 0rem 1.5rem 0rem 1rem;
|
||||
|
||||
position: relative;
|
||||
|
||||
.tooltip {
|
||||
padding: 1rem;
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
transform: translateY(110%);
|
||||
width: 42em;
|
||||
|
||||
background-color: rgba(0,0,0,0.90);
|
||||
color: #ccc;
|
||||
}
|
||||
&:hover {
|
||||
.tooltip {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.mdi {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
&.official {
|
||||
background-color: #fa6;
|
||||
color: #000;
|
||||
|
||||
.mdi {
|
||||
fill: #000 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.community {
|
||||
background-color: rgb(85, 85, 179);
|
||||
color: #fff;
|
||||
|
||||
.mdi {
|
||||
fill: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-support {
|
||||
background-color: rgb(138, 65, 126);
|
||||
color: #eee;
|
||||
|
||||
.mdi {
|
||||
fill: #eee !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.user-added {
|
||||
border: 1px solid #ff0;
|
||||
|
||||
color: #ff0;
|
||||
|
||||
.mdi {
|
||||
fill: #ff0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex-grow: 1;
|
||||
|
||||
@ -392,6 +294,16 @@ export default {
|
||||
}
|
||||
|
||||
|
||||
.site-support-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: bottom;
|
||||
|
||||
.site-support-site {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-panel {
|
||||
background-color: rgba(0,0,0,0.50);
|
||||
color: #fff;
|
||||
|
@ -92,7 +92,7 @@ export default({
|
||||
],
|
||||
mounted() {
|
||||
this.settings.active.whatsNewChecked = true;
|
||||
this.settings.save();
|
||||
this.settings.saveWithoutReload();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<div class="label">Enable in-player UI</div>
|
||||
<input type="checkbox" v-model="settings.active.ui.inPlayer.enabled" />
|
||||
</div>
|
||||
<!--
|
||||
|
||||
<div
|
||||
class="flex flex-col"
|
||||
:class="{disabled: settings.active.ui.inPlayer.enabled}"
|
||||
@ -53,8 +53,90 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label">Edit trigger zone:</div>
|
||||
<button>Edit</button>
|
||||
</div>
|
||||
|
||||
<div v-if="settings.active.ui.inPlayer.activation === 'trigger-zone'">
|
||||
Trigger zone size:
|
||||
<div class="trigger-zone-editor">
|
||||
<div class="heading">
|
||||
<h3>Trigger zone editor</h3>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="label">Trigger zone width:</div>
|
||||
<div class="input range-input">
|
||||
<input
|
||||
v-model="settings.active.ui.inPlayer.triggerZoneDimensions.width"
|
||||
class="slider"
|
||||
type="range"
|
||||
min="0.1"
|
||||
max="1"
|
||||
step="0.01"
|
||||
>
|
||||
<input
|
||||
:value="(settings.active.ui.inPlayer.triggerZoneDimensions.width * 100).toFixed(2)"
|
||||
@input="(event) => setTriggerZoneSize('width', event.target.value)"
|
||||
>
|
||||
</div>
|
||||
<div class="hint">
|
||||
Width of the trigger zone (% of player area).
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="label">Trigger zone height:</div>
|
||||
<div class="input range-input">
|
||||
<input
|
||||
v-model="settings.active.ui.inPlayer.triggerZoneDimensions.height"
|
||||
type="range"
|
||||
min="0.1"
|
||||
max="1"
|
||||
step="0.01"
|
||||
>
|
||||
<input
|
||||
:value="(settings.active.ui.inPlayer.triggerZoneDimensions.height * 100).toFixed(2)"
|
||||
@input="(event) => setTriggerZoneSize('width', event.target.value)"
|
||||
>
|
||||
</div>
|
||||
<div class="hint">
|
||||
Height of the trigger zone (% of player area).
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="label">Trigger zone horizontal offset:</div>
|
||||
<div class="input range-input">
|
||||
<input
|
||||
v-model="settings.active.ui.inPlayer.triggerZoneDimensions.offsetX"
|
||||
type="range"
|
||||
min="-100"
|
||||
max="100"
|
||||
>
|
||||
<input
|
||||
v-model="settings.active.ui.inPlayer.triggerZoneDimensions.offsetX"
|
||||
>
|
||||
</div>
|
||||
<div class="hint">
|
||||
By default, trigger zone is centered around the button. This option moves trigger zone left and right.
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="label">Trigger zone vertical offset:</div>
|
||||
<div class="input range-input">
|
||||
<input
|
||||
v-model="settings.active.ui.inPlayer.triggerZoneDimensions.offsetY"
|
||||
type="range"
|
||||
min="-100"
|
||||
max="100"
|
||||
>
|
||||
<input
|
||||
v-model="settings.active.ui.inPlayer.triggerZoneDimensions.offsetY"
|
||||
>
|
||||
</div>
|
||||
<div class="hint">
|
||||
By default, trigger zone is centered around the button. This option moves trigger zone up and down.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
@ -63,7 +145,7 @@
|
||||
</div>
|
||||
<div>TODO: slider</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@ -96,6 +178,24 @@ export default {
|
||||
setUiPage(key, event) {
|
||||
|
||||
},
|
||||
forceNumber(value) {
|
||||
// Change EU format to US if needed
|
||||
// | remove everything after second period if necessary
|
||||
// | | | remove non-numeric characters
|
||||
// | | | |
|
||||
return value.replaceAll(',', '.').split('.', 2).join('.').replace(/[^0-9.]/g, '');
|
||||
},
|
||||
setTriggerZoneSize(key, value) {
|
||||
let size = (+this.forceNumber(value) / 100);
|
||||
|
||||
if (isNaN(+size)) {
|
||||
size = 0.5;
|
||||
}
|
||||
|
||||
this.settings.active.ui.inPlayer.triggerZoneDimensions[key] = size;
|
||||
this.settings.saveWithoutReload();
|
||||
},
|
||||
|
||||
|
||||
async openOptionsPage() {
|
||||
BrowserDetect.runtime.openOptionsPage();
|
||||
@ -118,4 +218,37 @@ export default {
|
||||
.mt-4{
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.input {
|
||||
max-width: 24rem;
|
||||
}
|
||||
|
||||
.range-input {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
* {
|
||||
margin-left: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
|
||||
input {
|
||||
max-width: 5rem;
|
||||
}
|
||||
|
||||
input[type=range] {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
||||
.trigger-zone-editor {
|
||||
background-color: rgba(0,0,0,0.25);
|
||||
|
||||
padding-bottom: 2rem;
|
||||
.field {
|
||||
margin-bottom: -1em;
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
|
30
src/csui/src/PlayerUiPanels/ResetBackupPanel.vue
Normal file
30
src/csui/src/PlayerUiPanels/ResetBackupPanel.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<h1>Reset and backup</h1>
|
||||
<p>
|
||||
Pressing the button will reset settings to default without asking.
|
||||
</p>
|
||||
<button
|
||||
class="danger"
|
||||
@click="resetSettings"
|
||||
>
|
||||
Reset settings
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
settings: Object
|
||||
},
|
||||
methods: {
|
||||
resetSettings() {
|
||||
this.settings.active = JSON.parse(JSON.stringify(this.settings.default));
|
||||
this.settings.saveWithoutReload();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" src="../../res/css/flex.scss" scoped module></style>
|
||||
<style lang="scss" src="../res-common/panels.scss" scoped module></style>
|
||||
<style lang="scss" src="../res-common/common.scss" scoped module></style>
|
17
src/csui/src/components/AardStatusIndicator.vue
Normal file
17
src/csui/src/components/AardStatusIndicator.vue
Normal file
@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
siteSettings: Object,
|
||||
hasDrm: Boolean,
|
||||
problems: Object,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
137
src/csui/src/components/SupportLevelIndicator.vue
Normal file
137
src/csui/src/components/SupportLevelIndicator.vue
Normal file
@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<div v-if="siteSupportLevel === 'official'" class="site-support official">
|
||||
<mdicon name="check-decagram" />
|
||||
<div v-if="!small">Verified</div>
|
||||
<div class="tooltip">
|
||||
<template v-if="small">Verified — </template>
|
||||
The extension is being tested and should work on this site.
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="siteSupportLevel === 'community'" class="site-support community">
|
||||
<mdicon name="handshake" />
|
||||
<div v-if="!small">Community</div>
|
||||
<div class="tooltip">
|
||||
<template v-if="small">Community — </template>
|
||||
People say extension works on this site (or have provided help getting the extension to work if it didn't).<br/><br/>
|
||||
Tamius (the dev) does not test the extension on this site, probably because it requires a subscription or
|
||||
is geoblocked.
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="siteSupportLevel === 'no-support'" class="site-support no-support">
|
||||
<mdicon name="help-circle-outline" />
|
||||
<div v-if="!small">Unknown</div>
|
||||
<div class="tooltip">
|
||||
<template v-if="small">Unknown — </template>
|
||||
Not officially supported. Extension will try to fix things, but no promises.<br/><br/>
|
||||
Tamius (the dev) does not test the extension on this site for various reasons
|
||||
(unaware, not using the site, language barrier, geoblocking, paid services Tam doesn't use).
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="siteSupportLevel === 'user-added'" class="site-support user-added">
|
||||
<mdicon name="account" />
|
||||
<div v-if="!small">Custom</div>
|
||||
<div class="tooltip">
|
||||
<template v-if="small">Custom — </template>
|
||||
You have manually changed settings for this site. The extension is doing what you told it to do.
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="siteSupportLevel === 'community'">
|
||||
<mdicon class="site-support no-support" name="checkbox-marked-circle" />
|
||||
<div v-if="!small">Not supported</div>
|
||||
<div class="tooltip">
|
||||
<template v-if="small">Not supported — </template>
|
||||
Extension is known to not work with this site.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
siteSupportLevel: String,
|
||||
small: Boolean,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
|
||||
.site-support {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
margin-left: 1rem;
|
||||
border-radius: 8px;
|
||||
padding: 0rem 1.5rem 0rem 1rem;
|
||||
|
||||
position: relative;
|
||||
|
||||
.tooltip {
|
||||
padding: 1rem;
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
transform: translateY(110%);
|
||||
width: 42em;
|
||||
|
||||
background-color: rgba(0,0,0,0.90);
|
||||
color: #ccc;
|
||||
z-index: 99999 !important;
|
||||
|
||||
white-space: normal;
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
word-break: break-word;
|
||||
}
|
||||
&:hover {
|
||||
.tooltip {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.mdi {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
&.official {
|
||||
background-color: #fa6;
|
||||
color: #000;
|
||||
|
||||
.mdi {
|
||||
fill: #000 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.community {
|
||||
background-color: rgb(85, 85, 179);
|
||||
color: #fff;
|
||||
|
||||
.mdi {
|
||||
fill: #fff !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-support {
|
||||
background-color: rgb(132, 24, 40);
|
||||
color: #eee;
|
||||
|
||||
.mdi {
|
||||
fill: #eee !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.user-added {
|
||||
border: 1px solid #ff0;
|
||||
|
||||
color: #ff0;
|
||||
|
||||
.mdi {
|
||||
fill: #ff0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</style>
|
196
src/csui/src/components/TriggerZoneEditor.vue
Normal file
196
src/csui/src/components/TriggerZoneEditor.vue
Normal file
@ -0,0 +1,196 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="settings?.active?.ui"
|
||||
class="active-trigger-area uw-clickable"
|
||||
:style="triggerZoneStyles"
|
||||
>
|
||||
<div class="trigger-zone-editor"
|
||||
@mousedown="(event) => handleMouseDown('offset', event)"
|
||||
>
|
||||
<div
|
||||
class="uw-clickable tl"
|
||||
@mousedown.stop="(event) => handleMouseDown('tl', event)"
|
||||
>
|
||||
XX
|
||||
</div>
|
||||
<div
|
||||
class="uw-clickable tr"
|
||||
@mousedown.stop="(event) => handleMouseDown('tr', event)"
|
||||
>
|
||||
XX
|
||||
</div>
|
||||
<div
|
||||
class="uw-clickable bl"
|
||||
@mousedown.stop="(event) => handleMouseDown('bl', event)"
|
||||
>
|
||||
XX
|
||||
</div>
|
||||
<div
|
||||
class="uw-clickable br"
|
||||
@mousedown.stop="(event) => handleMouseDown('br', event)"
|
||||
>
|
||||
XX
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: [
|
||||
'settings',
|
||||
'playerDimensions',
|
||||
],
|
||||
watch: {
|
||||
playerDimensions(newVal, oldVal) {
|
||||
console.log('triggerzone -- dimensions changed!', this.playerDimensions, newVal, oldVal);
|
||||
this.updateTriggerZones();
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
triggerZoneStyles: {},
|
||||
activeCornerDrag: undefined,
|
||||
dragStartPosition: undefined,
|
||||
dragStartConfiguration: undefined,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
document.addEventListener("mouseup", this.handleMouseUp);
|
||||
document.addEventListener("mousemove", this.handleMouseMove);
|
||||
},
|
||||
methods: {
|
||||
updateTriggerZones() {
|
||||
if (this.playerDimensions && this.settings?.active?.ui?.inPlayer?.triggerZoneDimensions) {
|
||||
this.triggerZoneStyles = {
|
||||
width: `${Math.round(this.playerDimensions.width * this.settings.active.ui.inPlayer.triggerZoneDimensions.width)}px`,
|
||||
height: `${Math.round(this.playerDimensions.height * this.settings.active.ui.inPlayer.triggerZoneDimensions.height)}px`,
|
||||
transform: `translate(${(this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetX)}%, ${this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetY}%)`,
|
||||
};
|
||||
}
|
||||
},
|
||||
handleMouseDown(corner, event) {
|
||||
this.activeCornerDrag = corner;
|
||||
|
||||
// we need to save this because we don't know the location of the player element,
|
||||
// just its dimensions ... that means we need to
|
||||
this.dragStartPosition = {
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
};
|
||||
this.dragStartConfiguration = JSON.parse(JSON.stringify(this.settings.active.ui.inPlayer.triggerZoneDimensions));
|
||||
|
||||
console.log(`Mousedown on ${corner}`);
|
||||
},
|
||||
handleMouseUp(event) {
|
||||
if (!this.activeCornerDrag) {
|
||||
return;
|
||||
}
|
||||
this.activeCornerDrag = undefined;
|
||||
this.settings.saveWithoutReload();
|
||||
},
|
||||
handleMouseMove(event) {
|
||||
if (!this.activeCornerDrag) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeCornerDrag === 'offset') {
|
||||
this.handleMove(event);
|
||||
} else {
|
||||
this.handleResize(event);
|
||||
}
|
||||
|
||||
|
||||
this.updateTriggerZones();
|
||||
},
|
||||
handleResize(event) {
|
||||
// drag distance in px
|
||||
const dx = event.clientX - this.dragStartPosition.x;
|
||||
const dy = event.clientY - this.dragStartPosition.y;
|
||||
|
||||
// convert drag distance to % of current width:
|
||||
const dxr = dx / this.playerDimensions.width * 2;
|
||||
const dyr = dy / this.playerDimensions.height * 2;
|
||||
|
||||
// // update settings:
|
||||
let nw, nh;
|
||||
switch (this.activeCornerDrag) {
|
||||
case 'tl':
|
||||
nw = this.dragStartConfiguration.width - dxr;
|
||||
nh = this.dragStartConfiguration.height - dyr;
|
||||
break;
|
||||
case 'tr':
|
||||
nw = this.dragStartConfiguration.width + dxr;
|
||||
nh = this.dragStartConfiguration.height - dyr;
|
||||
break;
|
||||
case 'bl':
|
||||
nw = this.dragStartConfiguration.width - dxr;
|
||||
nh = this.dragStartConfiguration.height + dyr;
|
||||
break;
|
||||
case 'br':
|
||||
nw = this.dragStartConfiguration.width + dxr;
|
||||
nh = this.dragStartConfiguration.height + dyr;
|
||||
break;
|
||||
}
|
||||
|
||||
// ensure everything is properly limited
|
||||
const cw = Math.min(0.95, Math.max(0.125, nw));
|
||||
const ch = Math.min(0.95, Math.max(0.125, nh));
|
||||
|
||||
// // update properties
|
||||
this.settings.active.ui.inPlayer.triggerZoneDimensions.width = cw;
|
||||
this.settings.active.ui.inPlayer.triggerZoneDimensions.height = ch;
|
||||
},
|
||||
handleMove(event) {
|
||||
const dx = event.clientX - this.dragStartPosition.x;
|
||||
const dy = event.clientY - this.dragStartPosition.y;
|
||||
|
||||
// convert drag distance to % of current width:
|
||||
const dxr = dx / this.playerDimensions.width;
|
||||
const dyr = dy / this.playerDimensions.height;
|
||||
|
||||
// const [min, max] = this.settings.active.ui.inPlayer.popupAlignment === 'right' ? [5, 90] : [-90, -5];
|
||||
// const [minCrossAxis, maxCrossAxis] = [-90, 90];
|
||||
const min = -90;
|
||||
const max = -5;
|
||||
const minCrossAxis = -90;
|
||||
const maxCrossAxis = 90;
|
||||
|
||||
const cx = Math.min(max, Math.max(min, this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetX + dxr));
|
||||
const cy = Math.min(maxCrossAxis, Math.max(minCrossAxis, this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetY + dyr));
|
||||
|
||||
this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetX = cx;
|
||||
this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetY = cy;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.active-trigger-area {
|
||||
background-image: url('/res/img/grid_512.webp');
|
||||
}
|
||||
|
||||
.trigger-zone-editor {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
|
||||
> * {
|
||||
position: absolute;
|
||||
}
|
||||
.tr, .tl {
|
||||
top: 0;
|
||||
}
|
||||
.br, .bl {
|
||||
bottom: 0;
|
||||
}
|
||||
.tl, .bl {
|
||||
left: 0;
|
||||
}
|
||||
.tr, .br {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
@ -30,7 +30,7 @@ h1, h2, h3 {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.button {
|
||||
button, .button {
|
||||
background-color: rgba($blackBg, $normalTransparentOpacity);
|
||||
|
||||
padding: 0.5rem 2rem;
|
||||
@ -51,6 +51,11 @@ h1, h2, h3 {
|
||||
background-color: $primaryBg;
|
||||
border-color: rgba($primary, .5);
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background-color: #ff2211 !important;
|
||||
color:#000;
|
||||
}
|
||||
}
|
||||
.b3 {
|
||||
margin: 0.25rem;
|
||||
|
@ -5,14 +5,14 @@ export default {
|
||||
* We can handle events with the same function we use to handle events from
|
||||
* the content script.
|
||||
*/
|
||||
document.addEventListener('mousemove', (event) => {
|
||||
this.handleProbe({
|
||||
coords: {
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
}
|
||||
}, this.origin);
|
||||
});
|
||||
document.addEventListener('mousemove', (event) => {
|
||||
this.handleProbe({
|
||||
coords: {
|
||||
x: event.clientX,
|
||||
y: event.clientY
|
||||
}
|
||||
}, this.origin);
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -26,14 +26,28 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
playerDimensionsUpdate(dimensions) {
|
||||
if (!dimensions.width || !dimensions.height) {
|
||||
this.playerDimensions = undefined;
|
||||
}
|
||||
console.log('player dimensions update received:', dimensions);
|
||||
if (dimensions?.width !== this.playerDimensions?.width || dimensions?.height !== this.playerDimensions?.height) {
|
||||
this.playerDimensions = dimensions;
|
||||
console.log('Player dimensions changed!', dimensions);
|
||||
|
||||
this.playerDimensions = dimensions;
|
||||
this.updateTriggerZones();
|
||||
}
|
||||
},
|
||||
updateTriggerZones() {
|
||||
console.log('triggered zone style recheck. player dims:', this.playerDimensions, 'in player settings:', this.settings.active.ui);
|
||||
if (this.playerDimensions && this.settings) {
|
||||
this.triggerZoneStyles = {
|
||||
height: `${this.playerDimensions.height * 0.5}px`,
|
||||
width: `${this.playerDimensions.width * 0.5}px`,
|
||||
width: `${Math.round(this.playerDimensions.width * this.settings.active.ui.inPlayer.triggerZoneDimensions.width)}px`,
|
||||
height: `${Math.round(this.playerDimensions.height * this.settings.active.ui.inPlayer.triggerZoneDimensions.height)}px`,
|
||||
transform: `translate(${(this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetX)}%, ${this.settings.active.ui.inPlayer.triggerZoneDimensions.offsetY}%)`,
|
||||
};
|
||||
console.log(
|
||||
'player trigger zone css:', this.triggerZoneStyles
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -166,6 +166,7 @@ const ExtensionConfPatch = [
|
||||
for (const domOption in userOptions.sites[site].DOMConfig)
|
||||
userOptions.sites[site].DOMConfig[domOption].customCss;
|
||||
}
|
||||
userOptions.arDetect.aardType = 'auto';
|
||||
userOptions.ui = {
|
||||
inPlayer: {
|
||||
enabled: true, // enable by default on new installs
|
||||
|
@ -15,6 +15,7 @@ if(Debug.debug)
|
||||
|
||||
const ExtensionConf: SettingsInterface = {
|
||||
arDetect: {
|
||||
aardType: 'auto',
|
||||
disabledReason: "", // if automatic aspect ratio has been disabled, show reason
|
||||
allowedMisaligned: 0.05, // top and bottom letterbox thickness can differ by this much.
|
||||
// Any more and we don't adjust ar.
|
||||
|
@ -36,6 +36,9 @@ class Settings {
|
||||
//#region callbacks
|
||||
onSettingsChanged: any;
|
||||
afterSettingsSaved: any;
|
||||
|
||||
onChangedCallbacks: any[] = [];
|
||||
afterSettingsChangedCallbacks: any[] = [];
|
||||
//#endregion
|
||||
|
||||
constructor(options) {
|
||||
@ -63,15 +66,31 @@ class Settings {
|
||||
|
||||
this.logger?.log('info', 'debug', 'Does parsedSettings.preventReload exist?', parsedSettings.preventReload, "Does callback exist?", !!this.onSettingsChanged);
|
||||
|
||||
if (!parsedSettings.preventReload && this.onSettingsChanged) {
|
||||
if (!parsedSettings.preventReload) {
|
||||
try {
|
||||
this.onSettingsChanged();
|
||||
for (const fn of this.onChangedCallbacks) {
|
||||
try {
|
||||
fn();
|
||||
} catch (e) {
|
||||
this.logger?.log('warn', 'settings', "[Settings] afterSettingsChanged fallback failed. It's possible that a vue component got destroyed, and this function is nothing more than vestigal remains. It would be nice if we implemented something that allows us to remove callback functions from array, and remove vue callbacks from the callback array when their respective UI component gets destroyed. Or this could be an error with the function itself. IDK, here's the error.", e)
|
||||
}
|
||||
}
|
||||
if (this.onSettingsChanged) {
|
||||
this.onSettingsChanged();
|
||||
}
|
||||
|
||||
this.logger?.log('info', 'settings', '[Settings] Update callback finished.')
|
||||
} catch (e) {
|
||||
this.logger?.log('error', 'settings', "[Settings] CALLING UPDATE CALLBACK FAILED. Reason:", e)
|
||||
}
|
||||
}
|
||||
for (const fn of this.afterSettingsChangedCallbacks) {
|
||||
try {
|
||||
fn();
|
||||
} catch (e) {
|
||||
this.logger?.log('warn', 'settings', "[Settings] afterSettingsChanged fallback failed. It's possible that a vue component got destroyed, and this function is nothing more than vestigal remains. It would be nice if we implemented something that allows us to remove callback functions from array, and remove vue callbacks from the callback array when their respective UI component gets destroyed. Or this could be an error with the function itself. IDK, here's the error.", e)
|
||||
}
|
||||
}
|
||||
if (this.afterSettingsSaved) {
|
||||
this.afterSettingsSaved();
|
||||
}
|
||||
@ -179,6 +198,7 @@ class Settings {
|
||||
updateFn(this.active, this.getDefaultSettings());
|
||||
} catch (e) {
|
||||
this.logger?.log('error', 'settings', '[Settings::applySettingsPatches] Failed to execute update function. Keeping settings object as-is. Error:', e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -358,6 +378,13 @@ class Settings {
|
||||
getSiteSettings(site: string = window.location.hostname): SiteSettings {
|
||||
return new SiteSettings(this, site);
|
||||
}
|
||||
|
||||
listenOnChange(fn: () => void): void {
|
||||
this.onChangedCallbacks.push(fn);
|
||||
}
|
||||
listenAfterChange(fn: () => void): void {
|
||||
this.afterSettingsChangedCallbacks.push(fn);
|
||||
}
|
||||
}
|
||||
|
||||
export default Settings;
|
||||
|
@ -5,6 +5,7 @@ import Settings from '../Settings';
|
||||
import VideoData from '../video-data/VideoData';
|
||||
import { Corner } from './enums/corner.enum';
|
||||
import { VideoPlaybackState } from './enums/video-playback-state.enum';
|
||||
import { FallbackCanvas } from './gl/FallbackCanvas';
|
||||
import { GlCanvas } from './gl/GlCanvas';
|
||||
import { AardCanvasStore } from './interfaces/aard-canvas-store.interface';
|
||||
import { AardDetectionSample, generateSampleArray, resetSamples } from './interfaces/aard-detection-sample.interface';
|
||||
@ -234,6 +235,8 @@ export class Aard {
|
||||
//#region internal state
|
||||
public status: AardStatus = initAardStatus();
|
||||
private timers: AardTimers = initAardTimers();
|
||||
private inFallback: boolean = false;
|
||||
private fallbackReason: any;
|
||||
private canvasStore: AardCanvasStore;
|
||||
private testResults: AardTestResults;
|
||||
private canvasSamples: AardDetectionSample;
|
||||
@ -245,6 +248,8 @@ export class Aard {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this.video.setAttribute('crossOrigin', 'anonymous');
|
||||
|
||||
const ratio = this.video.videoWidth / this.video.videoHeight;
|
||||
if (isNaN(ratio)) {
|
||||
return undefined;
|
||||
@ -284,8 +289,10 @@ export class Aard {
|
||||
* This method should only ever be called from constructor.
|
||||
*/
|
||||
private init() {
|
||||
|
||||
|
||||
this.canvasStore = {
|
||||
main: new GlCanvas(new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'})),
|
||||
main: this.createCanvas('main-gl')
|
||||
};
|
||||
|
||||
|
||||
@ -302,6 +309,42 @@ export class Aard {
|
||||
|
||||
this.start();
|
||||
}
|
||||
|
||||
private createCanvas(canvasId: string, canvasType?: 'webgl' | 'fallback') {
|
||||
if (canvasType) {
|
||||
if (canvasType === this.settings.active.arDetect.aardType || this.settings.active.arDetect.aardType === 'auto') {
|
||||
if (canvasType === 'webgl') {
|
||||
return new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'});
|
||||
} else if (canvasType === 'fallback') {
|
||||
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-fallback'});
|
||||
} else {
|
||||
// TODO: throw error
|
||||
}
|
||||
} else {
|
||||
// TODO: throw error
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (['auto', 'webgl'].includes(this.settings.active.arDetect.aardType)) {
|
||||
try {
|
||||
return new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'});
|
||||
} catch (e) {
|
||||
if (this.settings.active.arDetect.aardType !== 'webgl') {
|
||||
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-fallback'});
|
||||
}
|
||||
console.error('[ultrawidify|Aard::createCanvas] could not create webgl canvas:', e);
|
||||
this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: {webglError: true}});
|
||||
throw e;
|
||||
}
|
||||
} else if (this.settings.active.arDetect.aardType === 'legacy') {
|
||||
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-fallback'});
|
||||
} else {
|
||||
console.error('[ultrawidify|Aard::createCanvas] invalid value in settings.arDetect.aardType:', this.settings.active.arDetect.aardType);
|
||||
this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: {invalidSettings: true}});
|
||||
throw 'AARD_INVALID_SETTINGS';
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
@ -393,8 +436,35 @@ export class Aard {
|
||||
do {
|
||||
const imageData = await new Promise<Uint8Array>(
|
||||
resolve => {
|
||||
this.canvasStore.main.drawVideoFrame(this.video);
|
||||
resolve(this.canvasStore.main.getImageData());
|
||||
try {
|
||||
this.canvasStore.main.drawVideoFrame(this.video);
|
||||
resolve(this.canvasStore.main.getImageData());
|
||||
} catch (e) {
|
||||
if (e.name === 'SecurityError') {
|
||||
this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: {cors: true}});
|
||||
this.stop();
|
||||
}
|
||||
if (this.canvasStore.main instanceof FallbackCanvas) {
|
||||
if (this.inFallback) {
|
||||
this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: this.fallbackReason});
|
||||
this.stop();
|
||||
} else {
|
||||
this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: {fallbackCanvasError: true}});
|
||||
this.stop();
|
||||
}
|
||||
} else {
|
||||
if (this.settings.active.arDetect.aardType === 'auto') {
|
||||
this.canvasStore.main.destroy();
|
||||
this.canvasStore.main = this.createCanvas('main-gl', 'fallback');
|
||||
}
|
||||
this.inFallback = true;
|
||||
this.fallbackReason = {cors: true};
|
||||
|
||||
if (this.settings.active.arDetect.aardType !== 'auto') {
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@ -407,6 +477,7 @@ export class Aard {
|
||||
);
|
||||
if (this.testResults.notLetterbox) {
|
||||
// TODO: reset aspect ratio to "AR not applied"
|
||||
console.log('NOT LETTERBOX!');
|
||||
this.testResults.lastStage = 1;
|
||||
break;
|
||||
}
|
||||
@ -420,6 +491,7 @@ export class Aard {
|
||||
this.settings.active.arDetect.canvasDimensions.sampleCanvas.width,
|
||||
this.settings.active.arDetect.canvasDimensions.sampleCanvas.height
|
||||
);
|
||||
console.log('LETTERBOX SHRINK CHECK RESULT — IS GUARDLINE INVALIDATED?', this.testResults.guardLine.invalidated)
|
||||
if (! this.testResults.guardLine.invalidated) {
|
||||
this.checkLetterboxGrow(
|
||||
imageData,
|
||||
@ -452,12 +524,17 @@ export class Aard {
|
||||
|
||||
// if detection is uncertain, we don't do anything at all
|
||||
if (this.testResults.aspectRatioUncertain) {
|
||||
console.info('aspect ratio not cettain.');
|
||||
console.warn('check finished:', JSON.parse(JSON.stringify(this.testResults)), JSON.parse(JSON.stringify(this.canvasSamples)), '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: emit debug values if debugging is enabled
|
||||
this.testResults.isFinished = true;
|
||||
|
||||
console.warn('check finished:', JSON.parse(JSON.stringify(this.testResults)), JSON.parse(JSON.stringify(this.canvasSamples)), '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
|
||||
|
||||
// if edge width changed, emit update event.
|
||||
if (this.testResults.aspectRatioUpdated) {
|
||||
this.videoData.resizer.updateAr({
|
||||
@ -1008,7 +1085,12 @@ export class Aard {
|
||||
// fact that it makes the 'if' statement governing gradient detection
|
||||
// bit more nicely visible (instead of hidden among spagheti)
|
||||
this.edgeScan(imageData, width, height);
|
||||
|
||||
console.log('edge scan:', JSON.parse(JSON.stringify(this.canvasSamples)));
|
||||
|
||||
this.validateEdgeScan(imageData, width, height);
|
||||
console.log('edge scan post valid:', JSON.parse(JSON.stringify(this.canvasSamples)));
|
||||
|
||||
|
||||
// TODO: _if gradient detection is enabled, then:
|
||||
this.sampleForGradient(imageData, width, height);
|
||||
@ -1061,6 +1143,7 @@ export class Aard {
|
||||
x = 0;
|
||||
isImage = false;
|
||||
finishedRows = 0;
|
||||
|
||||
while (row < topEnd) {
|
||||
i = 0;
|
||||
rowOffset = row * 4 * width;
|
||||
@ -1126,6 +1209,7 @@ export class Aard {
|
||||
|| imageData[rowOffset + x + 2] > this.testResults.blackLevel;
|
||||
|
||||
if (!isImage) {
|
||||
// console.log('(row:', row, ')', 'val:', imageData[rowOffset + x], 'col', x >> 2, x, 'pxoffset:', rowOffset + x, 'len:', imageData.length)
|
||||
// TODO: maybe some day mark this pixel as checked by writing to alpha channel
|
||||
i++;
|
||||
continue;
|
||||
@ -1272,6 +1356,7 @@ export class Aard {
|
||||
// didn't change meaningfully from the first, in which chance we aren't. If the brightness increased
|
||||
// anywhere between 'not enough' and 'too much', we mark the measurement as invalid.
|
||||
if (lastSubpixel - firstSubpixel > this.settings.active.arDetect.edgeDetection.gradientTestMinDelta) {
|
||||
console.log('sample invalidated cus gradient:');
|
||||
this.canvasSamples.top[i] = -1;
|
||||
}
|
||||
}
|
||||
@ -1645,6 +1730,26 @@ export class Aard {
|
||||
|
||||
const compensatedWidth = fileAr === canvasAr ? this.canvasStore.main.width : this.canvasStore.main.width * fileAr;
|
||||
|
||||
|
||||
console.log(`
|
||||
———— ASPECT RATIO CALCULATION: —————
|
||||
|
||||
canvas size: ${this.canvasStore.main.width} x ${this.canvasStore.main.height} (1:${this.canvasStore.main.width / this.canvasStore.main.height})
|
||||
file size: ${this.video.videoWidth} x ${this.video.videoHeight} (1:${this.video.videoWidth / this.video.videoHeight})
|
||||
|
||||
compensated size: ${compensatedWidth} x ${this.canvasStore.main.height} (1:${compensatedWidth / this.canvasStore.main.height})
|
||||
|
||||
letterbox height: ${this.testResults.letterboxWidth}
|
||||
net video height: ${this.canvasStore.main.height - (this.testResults.letterboxWidth * 2)}
|
||||
|
||||
calculated aspect ratio -----
|
||||
|
||||
${compensatedWidth} ${compensatedWidth} ${compensatedWidth}
|
||||
——————————————— = —————————————— = —————— = ${compensatedWidth / (this.canvasStore.main.height - (this.testResults.letterboxWidth * 2))}
|
||||
${this.canvasStore.main.height} - 2 x ${this.testResults.letterboxWidth} ${this.canvasStore.main.height} - ${2 * this.testResults.letterboxWidth} ${this.canvasStore.main.height - (this.testResults.letterboxWidth * 2)}
|
||||
`);
|
||||
|
||||
|
||||
return compensatedWidth / (this.canvasStore.main.height - (this.testResults.letterboxWidth * 2));
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,6 @@ export class FallbackCanvas extends GlCanvas {
|
||||
|
||||
constructor(options: GlCanvasOptions) {
|
||||
super(options);
|
||||
this.context = this.canvas.getContext('2d');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -18,9 +17,14 @@ export class FallbackCanvas extends GlCanvas {
|
||||
|
||||
destroy() { }
|
||||
|
||||
protected initWebgl() { }
|
||||
protected initContext() {
|
||||
this.context = this.canvas.getContext('2d', {desynchronized: true});
|
||||
}
|
||||
|
||||
protected initWebgl() { }
|
||||
|
||||
drawVideoFrame(video: HTMLVideoElement) {
|
||||
console.log('context:', this.context, 'canvas:', this.canvas );
|
||||
this.context.drawImage(video, this.context.canvas.width, this.context.canvas.height);
|
||||
}
|
||||
|
||||
|
@ -95,16 +95,7 @@ export class GlCanvas {
|
||||
this.canvas.setAttribute('width', `${options.width}`);
|
||||
this.canvas.setAttribute('height', `${options.height}`);
|
||||
|
||||
this.gl = this.canvas.getContext('webgl');
|
||||
|
||||
if (!this.gl) {
|
||||
throw new Error('WebGL not supported');
|
||||
}
|
||||
if(options.id) {
|
||||
this.canvas.setAttribute('id', options.id);
|
||||
}
|
||||
|
||||
this.frameBufferSize = options.width * options.height * 4;
|
||||
this.initContext(options);
|
||||
this.initWebgl();
|
||||
}
|
||||
|
||||
@ -156,6 +147,24 @@ export class GlCanvas {
|
||||
this.gl.deleteTexture(this.texture);
|
||||
}
|
||||
|
||||
protected initContext(options: GlCanvasOptions) {
|
||||
this.gl = this.canvas.getContext(
|
||||
'webgl2',
|
||||
{
|
||||
preserveDrawingBuffer: true
|
||||
}
|
||||
);
|
||||
|
||||
if (!this.gl) {
|
||||
throw new Error('WebGL not supported');
|
||||
}
|
||||
if(options.id) {
|
||||
this.canvas.setAttribute('id', options.id);
|
||||
}
|
||||
|
||||
this.frameBufferSize = options.width * options.height * 4;
|
||||
}
|
||||
|
||||
protected initWebgl() {
|
||||
// Initialize the GL context
|
||||
this.gl.clearColor(0.0, 0.0, 0.0, 1.0);
|
||||
|
@ -97,6 +97,16 @@ class PlayerData {
|
||||
'get-player-tree': [{
|
||||
function: () => this.handlePlayerTreeRequest()
|
||||
}],
|
||||
'get-player-dimensions': [{
|
||||
function: () => {
|
||||
console.log('received get player dimensions! -- returning:', this.dimensions)
|
||||
|
||||
this.eventBus.send('uw-config-broadcast', {
|
||||
type: 'player-dimensions',
|
||||
data: this.dimensions
|
||||
});
|
||||
}
|
||||
}],
|
||||
'set-mark-element': [{ // NOTE: is this still used?
|
||||
function: (data) => this.markElement(data)
|
||||
}],
|
||||
@ -371,6 +381,10 @@ class PlayerData {
|
||||
this.eventBus.send('restore-ar', null);
|
||||
this.eventBus.send('delayed-restore-ar', {delay: 500});
|
||||
// this.videoData.resizer?.restore();
|
||||
this.eventBus.send('uw-config-broadcast', {
|
||||
type: 'player-dimensions',
|
||||
data: newDimensions
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -378,6 +392,7 @@ class PlayerData {
|
||||
this.trackDimensionChanges();
|
||||
}
|
||||
|
||||
|
||||
//#region player element change detection
|
||||
/**
|
||||
* Starts change detection.
|
||||
|
BIN
src/res/img/grid_512.webp
Normal file
BIN
src/res/img/grid_512.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 273 KiB |
Loading…
Reference in New Issue
Block a user