Start on work on popup, mostly finish work on settings page

This commit is contained in:
Tamius Han 2025-12-03 03:14:15 +01:00
parent 5cfa5b0add
commit f543dcff24
22 changed files with 1271 additions and 138 deletions

90
package-lock.json generated
View File

@ -53,6 +53,7 @@
"postcss-nested": "^7.0.2", "postcss-nested": "^7.0.2",
"postcss-preset-env": "^10.4.0", "postcss-preset-env": "^10.4.0",
"postcss-strip-inline-comments": "^0.1.5", "postcss-strip-inline-comments": "^0.1.5",
"raw-loader": "^4.0.2",
"resolve-url-loader": "^5.0.0", "resolve-url-loader": "^5.0.0",
"tailwindcss": "^4.1.17", "tailwindcss": "^4.1.17",
"ts-loader": "^9.5.4", "ts-loader": "^9.5.4",
@ -17838,6 +17839,95 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/raw-loader": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz",
"integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==",
"dev": true,
"license": "MIT",
"dependencies": {
"loader-utils": "^2.0.0",
"schema-utils": "^3.0.0"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^4.0.0 || ^5.0.0"
}
},
"node_modules/raw-loader/node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/raw-loader/node_modules/ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"ajv": "^6.9.1"
}
},
"node_modules/raw-loader/node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT"
},
"node_modules/raw-loader/node_modules/loader-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
"integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==",
"dev": true,
"license": "MIT",
"dependencies": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
},
"engines": {
"node": ">=8.9.0"
}
},
"node_modules/raw-loader/node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/read-cache": { "node_modules/read-cache": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",

View File

@ -70,6 +70,7 @@
"postcss-nested": "^7.0.2", "postcss-nested": "^7.0.2",
"postcss-preset-env": "^10.4.0", "postcss-preset-env": "^10.4.0",
"postcss-strip-inline-comments": "^0.1.5", "postcss-strip-inline-comments": "^0.1.5",
"raw-loader": "^4.0.2",
"resolve-url-loader": "^5.0.0", "resolve-url-loader": "^5.0.0",
"tailwindcss": "^4.1.17", "tailwindcss": "^4.1.17",
"ts-loader": "^9.5.4", "ts-loader": "^9.5.4",

View File

@ -125,7 +125,6 @@ import BrowserDetect from '../ext/conf/BrowserDetect';
import CommsClient, {CommsOrigin} from '../ext/lib/comms/CommsClient'; import CommsClient, {CommsOrigin} from '../ext/lib/comms/CommsClient';
import Settings from '../ext/lib/settings/Settings'; import Settings from '../ext/lib/settings/Settings';
import EventBus from '../ext/lib/EventBus'; import EventBus from '../ext/lib/EventBus';
import {ChromeShittinessMitigations as CSM} from '../common/js/ChromeShittinessMitigations';
import SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue' import SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue'
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator'; import { LogAggregator } from '@src/ext/lib/logging/LogAggregator';
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger'; import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';

View File

@ -61,7 +61,7 @@ class Settings {
constructor(options: SettingsOptions) { constructor(options: SettingsOptions) {
// Options: activeSettings, updateCallback, logger // Options: activeSettings, updateCallback, logger
this.logger = options.logAggregator && new ComponentLogger(options.logAggregator, 'Settings', {styles: SETTINGS_LOGGER_STYLES}) || undefined;; this.logger = options.logAggregator && new ComponentLogger(options.logAggregator, 'Settings', {styles: SETTINGS_LOGGER_STYLES}) || undefined;
this.onSettingsChanged = options.onSettingsChanged; this.onSettingsChanged = options.onSettingsChanged;
this.afterSettingsSaved = options.afterSettingsSaved; this.afterSettingsSaved = options.afterSettingsSaved;
this.active = options.activeSettings ?? undefined; this.active = options.activeSettings ?? undefined;
@ -73,6 +73,24 @@ class Settings {
chrome.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)}); chrome.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)});
} }
updateOptions(options: SettingsOptions) {
if (options.logAggregator) {
this.logger = options.logAggregator && new ComponentLogger(options.logAggregator, 'Settings', {styles: SETTINGS_LOGGER_STYLES})
}
if (options.onSettingsChanged) {
this.onSettingsChanged = options.onSettingsChanged;
}
if (options.afterSettingsSaved) {
this.afterSettingsSaved = options.afterSettingsSaved;
}
if (options.activeSettings) {
this.active = options.activeSettings;
}
}
private storageChangeListener(changes, area) { private storageChangeListener(changes, area) {
if (!changes.uwSettings) { if (!changes.uwSettings) {
return; return;

View File

@ -38,4 +38,40 @@ html, body {
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
@apply font-mono; @apply font-mono;
} }
h2 {
@apply text-[1.75em];
}
h3 {
@apply text-primary-300 text-[1.25em] mt-4 border-b-1 border-b-stone-700;
}
a {
@apply text-primary-400;
&:hover {
@apply text-primary-300;
}
}
p {
@apply mt-1 mb-2;
}
p, li {
@apply leading-tight;
}
ul {
@apply bg-stone-900/50 px-4 py-2 mt-1 mb-2;
li {
@apply ml-4 list-disc marker:text-primary-400 mt-1 mb-2;
}
}
small, strike {
@apply opacity-50;
}
} }

View File

@ -0,0 +1,103 @@
<template>
<div>
<div>
<h1>{{loggerPanel.title}}<small><br/>{{loggerPanel.subtitle}}</small></h1>
<div>Logger configuration:</div>
<JsonEditor
v-model="lastSettings"
></JsonEditor>
<div class="flex flex-row flex-end">
<div class="button" @click="getLoggerSettings()">
Revert
</div>
<div class="button button-primary" @click="saveLoggerSettings()">
Save
</div>
<div class="button button-primary" @click="startLogging()">
Save and start logging
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { LogAggregator, BLANK_LOGGER_CONFIG } from '@src/ext/lib/logging/LogAggregator';
import JsonEditor from '@csui/src/components/JsonEditor';
import { defineComponent } from 'vue';
export default defineComponent({
components: {
JsonEditor
},
data() {
return {
loggerPanelRotation: [{
title: 'DEFORESTATOR 5000',
subtitle: 'Iron Legion\'s finest logging tool'
},{
title: 'Astinus',
subtitle: 'Ultrawidify logging tool'
}, {
title: 'Tracer',
subtitle: "Maybe I'll be Tracer — I'm already printing stack traces."
}, {
title: 'Grûmsh',
subtitle: "He who watches (all the things printed to the console)."
}, {
title: 'Situation Room/The Council',
subtitle: 'We will always be watching.'
}, {
title: 'Isengard Land Management Services',
subtitle: "#log4saruman"
}, {
title: 'Saw Hero',
subtitle: 'Ultrawidify logging tool'
}],
loggerPanel: {
title: 'whoopsie daisy',
subtitle: 'you broke the header choosing script',
pasteConfMode: false,
},
lastSettings: undefined,
parsedSettings: undefined,
currentSettings: undefined
};
},
props: [
'settings',
'eventBus',
],
created() {
this.loggerPanel = {
...this.loggerPanelRotation[Math.floor(Math.random() * this.loggerPanelRotation.length)],
pasteConfMode: false,
};
this.getLoggerSettings();
},
methods: {
loadDefaultConfig() {
this.lastSettings = JSON.parse(JSON.stringify(BLANK_LOGGER_CONFIG));
},
async getLoggerSettings() {
this.lastSettings = await LogAggregator.getConfig() || BLANK_LOGGER_CONFIG;
},
async saveLoggerSettings() {
console.log('Saving logger settings', this.lastSettings);
await LogAggregator.saveConfig({...this.lastSettings});
console.log('[ok] logger settings saved');
},
async startLogging(){
this.logStringified = undefined;
await LogAggregator.saveConfig({...this.lastSettings});
window.location.reload();
},
}
});
</script>

View File

@ -0,0 +1,35 @@
<template>
<div class="flex flex-col w-full h-full gap-2">
<h2 class="text-[1.75em]">About Ultrawidify</h2>
<ExtensionInfoContent :settings="settings"></ExtensionInfoContent>
</div>
</template>
<script lang="ts">
import BrowserDetect from '@src/ext/conf/BrowserDetect';
import { defineComponent } from 'vue';
import ExtensionInfoContent from './segments/ExtensionInfoContent.vue';
export default defineComponent({
components: {
ExtensionInfoContent,
},
props: [
'settings'
],
data() {
return {
BrowserDetect: BrowserDetect,
// reminder webextension-polyfill doesn't seem to work in vue!
addonVersion: BrowserDetect.firefox ? chrome.runtime.getManifest().version : chrome.runtime.getManifest().version,
addonSource: BrowserDetect.processEnvVersion,
mailtoLink: '',
redditLink: '',
showEasterEgg: false,
}
},
mounted() {
this.settings.active.whatsNewChecked = true;
this.settings.saveWithoutReload();
}
});
</script>

View File

@ -0,0 +1,80 @@
<template>
<div class="flex flex-col w-full h-full gap-2">
<h2 class="text-[1.75em]">What's new</h2>
<div class="flex flex-row md:flex-col gap-4">
<div>
<h3>Changelog</h3>
<ChangelogContent :settings="settings"></ChangelogContent>
</div>
<div>
<h3>Report problems</h3>
<ExtensionInfoContent :settings="settings"></ExtensionInfoContent>
</div>
</div>
</div>
</template>
<script lang="ts">
import BrowserDetect from '@src/ext/conf/BrowserDetect';
import { defineComponent } from 'vue';
import ChangelogContent from './segments/ChangelogContent.vue';
import ExtensionInfoContent from './segments/ExtensionInfoContent.vue';
export default defineComponent({
components: {
ChangelogContent,
ExtensionInfoContent,
},
props: [
'settings'
],
data() {
return {
BrowserDetect: BrowserDetect,
// reminder webextension-polyfill doesn't seem to work in vue!
addonVersion: BrowserDetect.firefox ? chrome.runtime.getManifest().version : chrome.runtime.getManifest().version,
addonSource: BrowserDetect.processEnvVersion,
mailtoLink: '',
redditLink: '',
showEasterEgg: false,
}
},
mounted() {
this.settings.active.whatsNewChecked = true;
this.settings.saveWithoutReload();
}
});
</script>
<style lang="scss" scoped>
.flex {
display: flex;
}
.flex-row {
flex-direction: row;
}
.grow {
flex-grow: 1;
}
p, li {
font-size: 1rem;
}
small {
opacity: 0.5;
font-size: 0.8em;
}
a {
color: #fa6;
}
.text-center {
text-align: center;
}
.donate {
margin: 1rem;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
background-color: #fa6;
color: #000;
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<p class="mb-4">Full changelog for older versions <a href="https://github.com/tamius-han/ultrawidify/blob/master/CHANGELOG.md" target="_blank">is available here</a>.</p>
<p>Changes in version <b class="font-semibold text-primary-400">6.4.0</b>:</p>
<ul>
<li>In-player UI can now appear in full-screen for websites that put players into top layer (allegedly).</li>
<li>Embedded sites now inherit settings of the parent frame. <small>However, this hasn't been tested for all edge cases and may contain bugs.</small></li>
<li>Added validation to custom aspect ratio entry menu. Corrected parsing of aspect ratios given in the X:Y format, even though aspect ratios should be ideally given as a single number.</li>
<li>Autodetection can be set to stop after first aspect ratio detection, or after a period of no changes.</li>
<li>
There is a new, experiental mode for autodetection. At this point, it can be manually enabled in the autodetection settings. It will become the default option in 2026.<br/>
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.
</li>
<li>
Autodetection can now detect hardcoded subtitles. You may change how extension handles subtitles in the settings.
Use of new experimental autodetection is required for subtitle handling options to work.
</li>
<li>
Keyboard shortcut settings have been split from UI settings, and are now presented in list form on the settings page.
</li>
<li>
Re-design of settings page.
</li>
</ul>
</template>
<script>
import BrowserDetect from '@src/ext/conf/BrowserDetect';
export default({
props: [
'settings'
],
data() {
return {
BrowserDetect: BrowserDetect,
// reminder webextension-polyfill doesn't seem to work in vue!
addonVersion: BrowserDetect.firefox ? chrome.runtime.getManifest().version : chrome.runtime.getManifest().version,
addonSource: BrowserDetect.processEnvVersion,
mailtoLink: '',
redditLink: '',
showEasterEgg: false,
}
},
mounted() {
this.settings.active.whatsNewChecked = true;
this.settings.saveWithoutReload();
}
});
</script>
<style lang="scss" scoped>
.donate {
margin: 1rem;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
background-color: #fa6;
color: #000;
}
</style>

View File

@ -0,0 +1,93 @@
<template>
<div class="flex flex-col ml-12 p-4">
<div class="text-[1.75em] font-thin text-primary-400">Ultrawidify</div>
<div class="-mb-1 text-stone-500 text-[0.9em]">Version {{addonVersion}}/{{addonSource}} ({{addonChannel}})</div>
<div class="text-stone-500 text-[0.9em]">Source code can be viewed on <a target="_blank" src="https://github.com/tamius-han/ultrawidify">github</a>.</div>
</div>
<p>
Please report <strike>undocumented features</strike> bugs using one of the following options (in order of preference):
</p>
<ul>
<li> <a target="_blank" href="https://github.com/tamius-han/ultrawidify/issues"><b>Github (preferred)</b></a><br/></li>
<li>Email: <a target="_blank" :href="mailtoLink">tamius.han@gmail.com</a></li>
</ul>
<p>
When reporting bugs, please include the following info:
</p>
<div class="bg-stone-900 border-l border-l-primary-500 px-4 py-2 font-mono pre">
Ultrawidify version: <span class="text-primary-400">{{addonVersion}}</span><br/>
Install source: <span class="text-primary-400">{{addonSource ?? '[fill out]'}}</span><br/>
OS: [fill out]
</div>
<p>
In addition, please include a description of your issue, which should ideally include:
</p>
<ul>
<li>What doesn't work</li>
<li>Where it happens (which website)</li>
<li>Steps to reproduce the issue</li>
<li>What you expected to happen</li>
<li>What actually happened</li>
</ul>
<p>
Optionally, you can also include screenshots in your report (though if screenshots contain any personal info,
such as names or addresses, you should redact that).
</p>
<h3>Thank you monies</h3>
<p>
If you think I deserve money for the work I did up to this point, you can bankroll my caffeine addiction.
</p>
<p class="text-center">
<a class="donate" href="https://www.paypal.com/paypalme/tamius" target="_blank">Donate on Paypal</a>
</p>
<p>
If you're into that, you can also stoke my ego over at
<a href="https://instagram.com/shaman_urkog" target="_blank">instagram</a> or
<a href="https://bsky.app/profile/tamius-han.bsky.social" target="_blank">bluesky</a>
by liking the stuff I put up on there <small>mostly things related to mini-painting</small>.
</p>
</template>
<script lang="ts">
import BrowserDetect from '@src/ext/conf/BrowserDetect';
export default({
props: [
'settings'
],
data() {
return {
BrowserDetect: BrowserDetect,
// reminder webextension-polyfill doesn't seem to work in vue!
addonVersion: chrome.runtime.getManifest().version,
addonSource: BrowserDetect.processEnvBrowser,
addonChannel: BrowserDetect.processEnvChannel,
mailtoLink: '',
redditLink: '',
showEasterEgg: false,
}
},
mounted() {
this.addonSource = BrowserDetect.processEnvBrowser;
this.addonChannel = BrowserDetect.processEnvChannel;
this.settings.active.whatsNewChecked = true;
this.settings.saveWithoutReload();
}
});
</script>
<style lang="scss" scoped>
.donate {
margin: 1rem;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
background-color: #fa6;
color: #000;
}
</style>

View File

@ -5,7 +5,7 @@
<div class="flex flex-row gap-2 w-full justify-end mt-4"> <div class="flex flex-row gap-2 w-full justify-end mt-4">
<button @click="clearShortcuts()"><mdicon name="close" :size="16"></mdicon>Clear all shortcuts</button> <button @click="clearShortcuts()"><mdicon name="close" :size="16"></mdicon>Clear all shortcuts</button>
<button @click="resetToDefault()"><mdicon name="reload" :size="16"></mdicon>Reset to default</button> <button @click="resetToDefault()"><mdicon name="restore" :size="16"></mdicon>Reset to default</button>
</div> </div>
<div class="flex flex-col w-full"> <div class="flex flex-col w-full">

View File

@ -1,8 +1,8 @@
<template> <template>
<div <div
class="flex flex-col uw-clickable w-full h-full main-window relative" class="grow shrink w-full h-full flex flex-col uw-clickable main-window relative overflow-hidden"
> >
<div class="flex flex-row"> <div class="flex flex-row w-full h-full overflow-hidden">
<div class="settings-categories"> <div class="settings-categories">
<div <div
v-for="tab of tabs" v-for="tab of tabs"
@ -30,7 +30,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="content flex flex-col"> <div class="grow content flex flex-col overflow-auto pr-4 pb-12">
<!-- autodetection warning --> <!-- autodetection warning -->
<div class="warning-area"> <div class="warning-area">
@ -50,24 +50,18 @@
<div class="flex flex-row panel-content"> <div class="flex flex-row panel-content">
<!-- Panel section --> <!-- Panel section -->
<!-- <VideoSettings <VideoSettings
v-if="selectedTab === 'videoSettings'" v-if="selectedTab === 'video-settings'"
:settings="settings" :settings="settings"
:siteSettings="siteSettings" :siteSettings="siteSettings"
:eventBus="eventBus" :eventBus="eventBus"
:site="site" :site="site"
></VideoSettings> --> ></VideoSettings>
<OtherSiteSettings <OtherSiteSettings
v-if="selectedTab === 'extensionSettings'" v-if="selectedTab === 'extensionSettings'"
:settings="settings" :settings="settings"
:enableSettingsEditor="true" :enableSettingsEditor="true"
></OtherSiteSettings> ></OtherSiteSettings>
<KeyboardShortcutSettings
v-if="selectedTab === 'keyboardShortcuts'"
:settings="settings"
:eventBus="eventBus"
></KeyboardShortcutSettings>
<AutodetectionSettings <AutodetectionSettings
v-if="selectedTab === 'autodetectionSettings'" v-if="selectedTab === 'autodetectionSettings'"
:settings="settings" :settings="settings"
@ -76,6 +70,31 @@
:site="site" :site="site"
> >
</AutodetectionSettings> </AutodetectionSettings>
<UISettings
v-if="selectedTab === 'uiSettings'"
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
>
</UISettings>
<KeyboardShortcutSettings
v-if="selectedTab === 'keyboardShortcuts'"
:settings="settings"
:eventBus="eventBus"
></KeyboardShortcutSettings>
<WhatsNew
v-if="selectedTab === 'changelog'"
:settings="settings"
></WhatsNew>
<About
v-if="selectedTab === 'about'"
:settings="settings"
>
</About>
<PlayerDetectionPanel <PlayerDetectionPanel
@ -85,13 +104,6 @@
:site="site" :site="site"
> >
</PlayerDetectionPanel> </PlayerDetectionPanel>
<PlayerUiSettings
v-if="selectedTab === 'playerUiSettings'"
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
>
</PlayerUiSettings>
<DebugPanel <DebugPanel
v-if="selectedTab === 'debugging'" v-if="selectedTab === 'debugging'"
@ -99,14 +111,7 @@
:eventBus="eventBus" :eventBus="eventBus"
:site="site" :site="site"
></DebugPanel> ></DebugPanel>
<ChangelogPanel
v-if="selectedTab === 'changelog'"
:settings="settings"
></ChangelogPanel>
<AboutPanel
v-if="selectedTab === 'about'"
>
</AboutPanel>
<!-- <ResetBackupPanel <!-- <ResetBackupPanel
v-if="selectedTab === 'resetBackup'" v-if="selectedTab === 'resetBackup'"
:settings="settings" :settings="settings"
@ -119,30 +124,69 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent } from 'vue';
import VideoSettings from '@components/VideoSettings/VideoSettings.vue';
import OtherSiteSettings from '@components/ExtensionSettings/Panels/OtherSiteSettings.vue'; import OtherSiteSettings from '@components/ExtensionSettings/Panels/OtherSiteSettings.vue';
import KeyboardShortcutSettings from '@components/KeyboardShortcuts/KeyboardShortcutSettings.vue';
import AutodetectionSettings from '@components/AutodetectionSettings/AutodetectionSettings.vue'; import AutodetectionSettings from '@components/AutodetectionSettings/AutodetectionSettings.vue';
import UISettings from '@components/UISettings/UISettings.vue';
import KeyboardShortcutSettings from '@components/KeyboardShortcuts/KeyboardShortcutSettings.vue';
import WhatsNew from '@components/ExtensionInfo/WhatsNew.vue';
import About from '@components/ExtensionInfo/About.vue';
import DebugPanel from '../../csui/src/PlayerUiPanels/DebugPanel.vue' import DebugPanel from '../../csui/src/PlayerUiPanels/DebugPanel.vue'
import PlayerDetectionPanel from '../../csui/src/PlayerUiPanels/PlayerDetectionPanel.vue' import PlayerDetectionPanel from '../../csui/src/PlayerUiPanels/PlayerDetectionPanel.vue'
import VideoSettings from '../../csui/src/PlayerUiPanels/VideoSettings.vue'
import BrowserDetect from '../../ext/conf/BrowserDetect' import BrowserDetect from '../../ext/conf/BrowserDetect'
import ChangelogPanel from '../../csui/src/PlayerUiPanels/ChangelogPanel.vue' import ChangelogPanel from '../../csui/src/PlayerUiPanels/ChangelogPanel.vue'
import AboutPanel from '@csui/src/PlayerUiPanels/AboutPanel.vue' import AboutPanel from '@csui/src/PlayerUiPanels/AboutPanel.vue'
import PlayerUiSettings from '../../csui/src/PlayerUiPanels/PlayerUiSettings.vue'
import ResetBackupPanel from '../../csui/src/PlayerUiPanels/ResetBackupPanel.vue' import ResetBackupPanel from '../../csui/src/PlayerUiPanels/ResetBackupPanel.vue'
import SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue' import SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue'
const AVAILABLE_TABS = {
'video-settings': {id: 'video-settings', label: 'Video settings', icon: 'crop'},
'site-extension-settings': {id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' },
'extensionSettings': {id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' },
'siteSettings': {id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' },
'autodetectionSettings': {id: 'autodetectionSettings', label: 'Autodetection options', icon: 'auto-fix'},
'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'},
'changelog': {id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
'about': {id: 'about', label: 'About', icon: 'information-outline'},
'debugging': {id: 'debugging', label: 'Debugging', icon: 'bug-outline', hidden: true}
};
const TAB_LOADOUT = {
'settings': [
'extensionSettings',
'autodetectionSettings',
'uiSettings',
'keyboardShortcuts',
'changelog',
'about',
'debugging',
],
'popup': [
'video-settings',
'site-extension-settings',
'changelog',
'about'
],
}
export default defineComponent({ export default defineComponent({
components: { components: {
OtherSiteSettings,
KeyboardShortcutSettings,
AutodetectionSettings,
VideoSettings, VideoSettings,
OtherSiteSettings,
AutodetectionSettings,
KeyboardShortcutSettings,
UISettings,
WhatsNew,
About,
PlayerDetectionPanel, PlayerDetectionPanel,
DebugPanel, DebugPanel,
PlayerUiSettings,
ChangelogPanel, ChangelogPanel,
AboutPanel, AboutPanel,
SupportLevelIndicator, SupportLevelIndicator,
@ -156,16 +200,16 @@ export default defineComponent({
}, },
tabs: [ tabs: [
// {id: 'videoSettings', label: 'Video settings', icon: 'crop'}, // // {id: 'videoSettings', label: 'Video settings', icon: 'crop'},
{id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' }, // {id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' },
{id: 'autodetectionSettings', label: 'Autodetection options', icon: 'auto-fix'}, // {id: 'autodetectionSettings', label: 'Autodetection options', icon: 'auto-fix'},
{id: 'playerUiSettings', label: 'UI settings', icon: 'movie-cog-outline' }, // {id: 'uiSettings', label: 'UI settings', icon: 'movie-cog-outline' },
{id: 'keyboardShortcuts', label: 'Keyboard shortcuts', icon: 'keyboard-outline' }, // {id: 'keyboardShortcuts', label: 'Keyboard shortcuts', icon: 'keyboard-outline' },
{id: 'playerDetection', label: 'Player detection', icon: 'television-play'}, // {id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
// {id: 'advancedOptions', label: 'Advanced options', icon: 'cogs' }, // // {id: 'advancedOptions', label: 'Advanced options', icon: 'cogs' },
{id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' }, // {id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
{id: 'about', label: 'About', icon: 'information-outline'}, // {id: 'about', label: 'About', icon: 'information-outline'},
{id: 'debugging', label: 'Debugging', icon: 'bug-outline', hidden: true}, // {id: 'debugging', label: 'Debugging', icon: 'bug-outline', hidden: true},
], ],
selectedTab: 'extensionSettings', selectedTab: 'extensionSettings',
BrowserDetect: BrowserDetect, BrowserDetect: BrowserDetect,
@ -174,12 +218,13 @@ export default defineComponent({
} }
}, },
props: [ props: [
'role',
'initialPath',
'settings', 'settings',
'eventBus', 'eventBus',
'logger', 'logger',
'inPlayer', 'inPlayer',
'site', 'site',
'defaultTab'
], ],
computed: { computed: {
// LPT: NO ARROW FUNCTIONS IN COMPUTED, // LPT: NO ARROW FUNCTIONS IN COMPUTED,
@ -190,14 +235,18 @@ export default defineComponent({
} }
}, },
created() { created() {
this.generateTabs();
this.settings.listenAfterChange(this.setDebugTabVisibility); this.settings.listenAfterChange(this.setDebugTabVisibility);
if (this.defaultTab) { if (this.initialPath && this.initialPath.length) {
this.selectedTab = this.defaultTab; this.selectedTab = this.initialPath[0];
}
const changelogTab = this.tabs.find(x => x.id === 'changelog');
if (changelogTab) {
changelogTab.highlight = !this.settings.active?.whatsNewChecked;
} }
this.siteSettings = this.settings.getSiteSettings({site: this.site});
this.tabs.find(x => x.id === 'changelog').highlight = !this.settings.active?.whatsNewChecked;
this.siteSettings = this.settings.getSiteSettings({site: this.site});
this.eventBus?.subscribe( this.eventBus?.subscribe(
'uw-show-ui', 'uw-show-ui',
{ {
@ -222,9 +271,22 @@ export default defineComponent({
getUrl(url) { getUrl(url) {
return BrowserDetect.getURL(url); return BrowserDetect.getURL(url);
}, },
generateTabs() {
const tabs = [];
for (const tab of TAB_LOADOUT[this.role]) {
if (!AVAILABLE_TABS[tab]) {
console.warn('[uw:SettingsWindowContent] tab', tab, 'is not present in available tabs:', AVAILABLE_TABS, '— tabs for role', this.role, TAB_LOADOUT[this.role]);
continue;
} else {
tabs.push(AVAILABLE_TABS[tab]);
}
}
this.tabs = tabs;
},
selectTab(tab) { selectTab(tab) {
console.log("Selecting tab", tab); console.log("Selecting tab", tab);
this.selectedTab = tab; this.selectedTab = tab;
window.location.hash = `#${this.role}/${this.selectedTab}`;
}, },
setPreventClose(bool) { setPreventClose(bool) {
this.preventClose = bool; this.preventClose = bool;
@ -235,7 +297,9 @@ export default defineComponent({
if (debugTab) { if (debugTab) {
debugTab.hidden = !this.settings.active.ui.devMode; debugTab.hidden = !this.settings.active.ui.devMode;
} }
} },
} }
}); });
</script> </script>
@ -249,15 +313,17 @@ export default defineComponent({
@apply flex flex-row gap-4 px-4 py-2 justify-end items-center @apply flex flex-row gap-4 px-4 py-2 justify-end items-center
text-[1.125em] text-stone-300 text-right font-mono text-[1.125em] text-stone-300 text-right font-mono
cursor-pointer cursor-pointer
border-r-2 border-r-transparent
hover:bg-stone-800 hover:bg-stone-800
hover:text-primary-200; hover:text-primary-200
hover:border-r-stone-600;
.label { .label {
@apply grow-0; @apply grow-0;
} }
&.active { &.active {
@apply !bg-transparent !text-primary-300 bg-gradient-to-r from-transparent to-black border-none hover:!text-primary-200; @apply !bg-transparent !text-primary-300 bg-gradient-to-r from-transparent to-black hover:!text-primary-200 border-r-primary-400;
} }
} }
} }

View File

@ -1,8 +1,10 @@
<template> <template>
<div class="flex flex-col" style="position: relative; width: 100%;"> <div class="flex flex-col relative w-full">
<h2 class="text-[1.75em]">Player UI options</h2>
<!-- The rest of the tab is under 'edit ratios and shortcuts' row --> <!-- The rest of the tab is under 'edit ratios and shortcuts' row -->
<div class="flex flex-col" style="width: 100%"> <div class="flex flex-col" style="width: 100%">
<h2>Player UI options</h2>
<div class="flex flex-col compact-form"> <div class="flex flex-col compact-form">
<div v-if="!siteSettings.data.enableUI.fullscreen"> <div v-if="!siteSettings.data.enableUI.fullscreen">
@ -104,66 +106,19 @@
</div> </div>
</div> </div>
<h2 class="mt2r">Menu options and keyboard shortcuts</h2>
<div>
Click 'add new' to add a new option. Click a button to edit or remove the keyboard shortcut.
</div>
<div class="keyboard-settings">
<!-- CROP OPTIONS -->
<div>
<div class="flex flex-row">
<h3 class="mth3">CROP OPTIONS</h3>
</div>
<CropOptionsPanel
:settings="settings"
:eventBus="eventBus"
:isEditing="true"
>
</CropOptionsPanel>
</div>
<!-- STRETCH OPTIONS -->
<div>
<div class="flex flex-row">
<h3 class="mth3">STRETCH OPTIONS</h3>
</div>
<StretchOptionsPanel
:settings="settings"
:eventBus="eventBus"
:isEditing="true"
></StretchOptionsPanel>
</div>
<!-- ZOOM OPTIONS -->
<div>
<div class="flex flex-row">
<h3 class="mth3">ZOOM OPTIONS</h3>
</div>
<ZoomOptionsPanel
:settings="settings"
:eventBus="eventBus"
:isEditing="true"
></ZoomOptionsPanel>
</div>
</div>
</div> </div>
</div> </div>
</template> </template>
<script> <script lang="ts">
import Button from '@csui/src/components/Button.vue'
import BrowserDetect from '@src/ext/conf/BrowserDetect'; import BrowserDetect from '@src/ext/conf/BrowserDetect';
import CropOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/CropOptionsPanel.vue' import CropOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/CropOptionsPanel.vue'
import StretchOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/StretchOptionsPanel.vue' import StretchOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/StretchOptionsPanel.vue'
import ZoomOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/ZoomOptionsPanel.vue' import ZoomOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/ZoomOptionsPanel.vue'
import { defineComponent } from 'vue';
export default { export default defineComponent({
components: { components: {
Button,
CropOptionsPanel, CropOptionsPanel,
StretchOptionsPanel, StretchOptionsPanel,
ZoomOptionsPanel, ZoomOptionsPanel,
@ -231,27 +186,9 @@ export default {
}, },
} }
} });
</script> </script>
<style lang="postcss" scoped>
<style lang="scss" src="../../res/css/flex.scss" scoped module></style>
<style lang="scss" src="@csui/src/res-common/panels.scss" scoped module></style>
<style lang="scss" src="@csui/src/res-common/common.scss" scoped module></style>
<style lang="scss" scoped>
.justify-center {
justify-content: center;
}
.items-center {
align-items: center;
}
.mt-4{
margin-top: 1rem;
}
.input {
max-width: 24rem;
}
.trigger-zone-editor { .trigger-zone-editor {
background-color: rgba(0,0,0,0.25); background-color: rgba(0,0,0,0.25);

View File

@ -0,0 +1,240 @@
<template>
<div class="flex flex-col" style="position: relative; width: 100%;">
<!-- 'Change UI' options is a tiny bit in upper right corner. -->
<h3>Crop</h3>
<div class="w-full grid grid-cols-4 gap-2 pt-2">
<ShortcutButton
v-for="(command, index) of settings?.active.commands.crop"
:key="index"
:label="command.label"
:shortcut="getKeyboardShortcutLabel(command)"
></ShortcutButton>
</div>
<h3>Zoom</h3>
<div class="w-full grid grid-cols-4 gap-2 pt-2">
<ShortcutButton
v-for="(command, index) of settings?.active.commands.zoom"
:key="index"
:label="command.label"
:shortcut="getKeyboardShortcutLabel(command)"
></ShortcutButton>
</div>
<div class="text-white font-mono text-semibold mt-4 mb-1">Free-form zoom</div>
<div class="flex flex-row">
<!-- SLIDERS BEFORE X/Y LOCK -->
<div class="grow flex flex-col gap-[0.125em]">
<div class="flex flex-row w-full items-center">
<div class="w-[4em] opacity-75 text-right leading-none">Width:</div>
<div class="w-[4.5em] pr-4 text-right leading-none">{{getZoomForDisplay('x')}}</div>
<div class="grow leading-none pt-[0.25em]">
<input id="_input_zoom_slider"
class="w-full"
type="range"
step="any"
min="-1"
max="4"
:value="zoom.x"
@input="changeZoom($event.target.value, 'x')"
/>
</div>
</div>
<div class="flex flex-row w-full items-center">
<div class="w-[4em] opacity-75 text-right leading-none">Height:</div>
<div class="w-[4.5em] pr-4 text-right leading-none">{{getZoomForDisplay('y')}}</div>
<div class="grow leading-none pt-[0.5em]">
<input id="_input_zoom_slider_2"
class="w-full"
type="range"
step="any"
min="-1"
max="4"
:value="zoom.y"
@input="changeZoom($event.target.value, 'y')"
/>
</div>
</div>
</div>
<!-- X/Y LOCK -->
<div class="h-full flex flex-col justify-center ml-2">
<div
class="h-[2.75em] mt-[0.125em] border-2 border-stone-500 border-l-transparent w-[0.5em]"
:class="{'xy-lock-bar-break': !zoomOptions.lockAr}"
></div>
</div>
<div class="h-full flex flex-col justify-center p-2">
<div
class="cursor-pointer border-1 border-stone-800 p-2 hover:bg-stone-700 hover:text-white"
@click="zoomOptions.lockAr = !zoomOptions.lockAr"
>
<mdicon v-if="zoomOptions.lockAr" name="link-variant" :size="16"></mdicon>
<mdicon v-if="!zoomOptions.lockAr" name="link-variant-off" :size="16"></mdicon>
</div>
</div>
<div class="h-full flex flex-col justify-center p-2">
<div
class="cursor-pointer border-1 border-stone-800 p-2 hover:bg-stone-700 hover:text-white"
@click="resetZoom()"
>
<mdicon name="restore" :size="16"></mdicon>
</div>
</div>
</div>
<h3>Stretch</h3>
<div class="w-full grid grid-cols-4 gap-2 pt-2">
<ShortcutButton
v-for="(command, index) of settings?.active.commands.stretch"
:key="index"
:label="command.label"
:shortcut="getKeyboardShortcutLabel(command)"
></ShortcutButton>
</div>
<h3>Align video</h3>
<div
ref="alignmentSvgContainer"
class="w-full h-[12em] flex flex-row justify-center"
></div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import BrowserDetect from '@src/ext/conf/BrowserDetect';
import ShortcutButton from './components/ShortcutButton.vue';
import CommsMixin from '@components/common/mixins/CommsMixin.vue';
import KeyboardShortcutParserMixin from '@components/common/mixins/KeyboardShortcutParserMixin.vue';
import alignmentIndicatorSvg from '!!raw-loader!@ui/res/img/alignment-indicators.svg?raw';
import {setupVideoAlignmentIndicatorInteraction, setVideoAlignmentIndicatorState} from '@ui/utils/video-alignment-indicator-handling.ts';
import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum';
export default defineComponent({
components: {
ShortcutButton
},
props: [
'settings', // required for buttons and actions, which are global
'siteSettings',
'frame',
'eventBus',
'site'
],
mixins: [
CommsMixin,
KeyboardShortcutParserMixin,
],
data() {
return {
zoom: {x: 0, y: 0}, // zoom is logarithmic, so "100%" is represented as 0 instead of 1.
zoomOptions: {
lockAr: false,
},
alignmentSvgContainer: undefined as HTMLElement | undefined,
}
},
created() {
console.log('created ....');
this.eventBus.subscribe(
'uw-config-broadcast',
{
source: this,
function: (config) => this.handleConfigBroadcast(config)
}
);
},
mounted() {
this.alignmentSvgContainer = this.$refs.alignmentSvgContainer as HTMLElement;
if (this.alignmentSvgContainer) {
this.alignmentSvgContainer.innerHTML = alignmentIndicatorSvg;
const svgElement = this.alignmentSvgContainer.querySelector('svg') as SVGSVGElement;
console.log('svg element:', svgElement);
if (svgElement) {
setupVideoAlignmentIndicatorInteraction(svgElement, (x: VideoAlignmentType, y: VideoAlignmentType) => {
console.log('clicked!');
// Update selection visually
setVideoAlignmentIndicatorState(svgElement, x, y);
this.align(x,y);
});
}
}
this.eventBus.sendToTunnel('get-ar');
},
destroyed() {
this.eventBus.unsubscribeAll(this);
},
methods: {
async openOptionsPage() {
BrowserDetect.runtime.openOptionsPage();
},
getZoomForDisplay(axis: 'x' | 'y') {
// zoom is internally handled logarithmically, because we want to have x0.5, x1, x2, x4 ... magnifications
// spaced out at regular intervals. When displaying, we need to convert that to non-logarithmic values.
return `${(Math.pow(2, this.zoom[axis]) * 100).toFixed()}%`;
},
toggleZoomAr() {
this.zoomOptions.lockAr = !this.zoomOptions.lockAr;
},
resetZoom() {
// we store zoom logarithmically on this component
this.zoom = {x: 0, y: 0};
// we do not use logarithmic zoom elsewhere
// todo: replace eventBus with postMessage to parent
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'y'});
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'x'});
this.eventBus?.sendToTunnel('set-zoom', {zoom: 1});
},
changeZoom(logZoom: number, axis: 'x' | 'y') {
// we do not use logarithmic zoom elsewhere, therefore we need to convert
const linearZoom = Math.pow(2, logZoom);
if (this.zoomOptions.lockAr) {
this.zoom.x = logZoom;
this.zoom.y = logZoom;
this.eventBus?.sendToTunnel('set-zoom', {zoom: linearZoom});
} else {
this.zoom[axis] = logZoom;
this.eventBus?.sendToTunnel('set-zoom', {zoom: {[axis]: linearZoom}});
}
},
isActiveZoom(command) {
return false;
},
align(alignmentX: VideoAlignmentType, alignmentY: VideoAlignmentType) {
this.eventBus?.sendToTunnel('set-alignment', {x: alignmentX, y: alignmentY})
}
}
});
</script>
<style lang="postcss" scoped>
@import '../../../main.css';
.xy-lock-bar-break {
@apply relative;
&:before {
@apply absolute box-content h-[1em] w-[3px] border-3 border-stone-950;
content: '';
top: 50%;
left: 100%;
transform: translate(-25%, -50%) rotate(45deg);
}
}
</style>

View File

@ -0,0 +1,22 @@
<template>
<button class="flex flex-col justify-center items-center" :class="classList">
<div class="flex justify-self-center">
{{label}}
</div>
<div class="-mt-2 font-[0.8em] flex justify-self-center text-stone-500">
{{shortcut ? `(${shortcut})` : ''}}
</div>
</button>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
props: {
label: String,
shortcut: String,
classList: String
}
});
</script>

View File

@ -0,0 +1,22 @@
<script lang="ts">
export default {
methods: {
handleConfigBroadcast(message) {
if (message.type === 'ar') {
this.resizerConfig.crop = message.config;
}
this.$nextTick( () => this.$forceUpdate() );
},
/**
* Sends commands to main content script in parent iframe
* @param {*} command
*/
execAction(command) {
const cmd = JSON.parse(JSON.stringify(command));
this.eventBus?.sendToTunnel(cmd.action, cmd.arguments);
},
}
}
</script>

View File

@ -1,3 +1,4 @@
<script lang="ts">
import KeyboardShortcutParser from '@src/common/js/KeyboardShortcutParser'; import KeyboardShortcutParser from '@src/common/js/KeyboardShortcutParser';
export default { export default {
@ -12,4 +13,5 @@ export default {
return KeyboardShortcutParser.parseShortcut(command.shortcut); return KeyboardShortcutParser.parseShortcut(command.shortcut);
}, },
} }
} };
</script>

View File

View File

@ -0,0 +1,30 @@
import EventBus from '../../../ext/lib/EventBus';
import { ComponentLogger } from '../../../ext/lib/logging/ComponentLogger';
import { LogAggregator } from '../../../ext/lib/logging/LogAggregator';
import Settings from '../../../ext/lib/settings/Settings';
const loadedTabs = [
'videoSettings',
'extensionSettings',
'changelog',
'about',
];
async function load() {
const _uw_data = {
eventBus: new EventBus(),
settings: {},
settingsInitialized: false,
logAggregator: undefined,
logger: undefined,
site: undefined,
siteSettings: undefined,
comms: undefined,
};
_uw_data.logAggregator = new LogAggregator('🔵ext-popup🔵');
_uw_data.logger = new ComponentLogger(_uw_data.logAggregator, 'Popup');
_uw_data.settings = new Settings({logAggregator: _uw_data.logAggregator});
_uw_data.
}

View File

@ -1,16 +1,20 @@
<template> <template>
<div class="w-full flex flex-row w-full h-full justify-center items-center py-4 px-16"> <div class="w-full h-[100dvh] overflow-hidden flex flex-row justify-center items-center py-4 px-16">
<!-- page content --> <!-- page content -->
<div class="w-full max-w-[1920px]"> <div class="w-full max-w-[1920px] h-[100dvh] overflow-hidden flex flex-col">
<h1 class="text-[3em]">Ultrawidify settings</h1> <h1 class="text-[3em] grow-0 shrink-0">Ultrawidify settings</h1>
<div v-if="!settingsInitialized" class="flex flex-row w-full justify-center items-center"> <div v-if="!settingsInitialized" class="flex flex-row w-full justify-center items-center">
Loading settings... Loading settings...
</div> </div>
<SettingsWindowContent v-else <SettingsWindowContent v-else
class="grow shrink"
:role="role"
:initialPath="initialPath"
:settings="settings" :settings="settings"
:eventBus="eventBus"
:logger="logger" :logger="logger"
:inPlayer="false" :inPlayer="false"
:site="null" :site="null"
@ -28,6 +32,10 @@ import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
import Settings from '@src/ext/lib/settings/Settings'; import Settings from '@src/ext/lib/settings/Settings';
import SettingsWindowContent from '@components/SettingsWindowContent.vue'; import SettingsWindowContent from '@components/SettingsWindowContent.vue';
import EventBus from '@src/ext/lib/EventBus';
import CommsClient, { CommsOrigin } from '@src/ext/lib/comms/CommsClient';
import {ChromeShittinessMitigations as CSM} from '@src/common/js/ChromeShittinessMitigations';
export default defineComponent({ export default defineComponent({
components: { components: {
SettingsWindowContent SettingsWindowContent
@ -38,16 +46,25 @@ export default defineComponent({
logger: undefined as ComponentLogger | undefined, logger: undefined as ComponentLogger | undefined,
logAggregator: undefined as LogAggregator | undefined, logAggregator: undefined as LogAggregator | undefined,
settingsInitialized: false, settingsInitialized: false,
role: 'settings',
initialPath: undefined as string[] | undefined,
} }
}, },
async created() { async created() {
this.logAggregator = new LogAggregator(''); const [segment, ...path] = window.location.hash.split('/');
this.logger = new ComponentLogger(this.logAggregator, 'App.vue'); this.initialPath = path;
switch (segment) {
case '#popup':
await this.setupPopup();
break;
case '#iframe':
await this.setupIframe();
break;
default:
await this.setupSettingsPage();
}
this.settings = new Settings({
logAggregator: this.logAggregator,
onSettingsChanged: () => this.updateConfig()
});
await this.settings.init(); await this.settings.init();
this.settingsInitialized = true; this.settingsInitialized = true;
}, },
@ -55,6 +72,140 @@ export default defineComponent({
updateConfig() { updateConfig() {
this.settings.init(); this.settings.init();
this.$nextTick( () => this.$forceUpdate()); this.$nextTick( () => this.$forceUpdate());
},
/**
* Initializes page when it's being loaded from the settings
*/
async setupSettingsPage() {
this.role = 'settings';
this.logAggregator = new LogAggregator('');
this.logger = new ComponentLogger(this.logAggregator, 'App.vue');
this.settings = new Settings({
logAggregator: this.logAggregator,
onSettingsChanged: () => this.updateConfig()
});
},
/**
* Initializes page when it's being loaded as extension popup
*/
async setupPopup() {
this.role = 'popup';
try {
this.logAggregator = new LogAggregator('🔵ext-popup🔵');
this.logger = new ComponentLogger(this.logAggregator, 'Popup');
this.settings = new Settings({afterSettingsSaved: () => this.updateConfig(), logAggregator: this.logAggregator});
await this.settings.init();
this.settingsInitialized = true;
// const port = chrome.runtime.connect({name: 'popup-port'});
// port.onMessage.addListener( (m,p) => this.processReceivedMessage(m,p));
// CSM.setProperty('port', port);
this.eventBus = new EventBus();
this.eventBus.subscribe(
'set-current-site',
{
source: this,
function: (config, context) => {
if (this.site) {
if (!this.site.host) {
// dunno why this fix is needed, but sometimes it is
this.site.host = config.site.host;
}
}
this.site = config.site;
// this.selectedSite = this.selectedSite || config.site.host;
this.siteSettings = this.settings.getSiteSettings({site: this.site.host});
this.eventBus.setupPopupTunnelWorkaround({
origin: CommsOrigin.Popup,
comms: {
forwardTo: 'active'
}
});
this.loadHostnames();
this.loadFrames();
}
},
);
this.eventBus.subscribe(
'open-popup-settings',
{
source: this,
function: (config) => {
this.selectTab(config.tab)
}
}
)
this.comms = new CommsClient('popup-port', this.logger, this.eventBus);
this.eventBus.setComms(this.comms);
this.eventBus.setupPopupTunnelWorkaround({
origin: CommsOrigin.Popup,
comms: {forwardTo: 'active'}
});
// ensure we'll clean player markings on popup close
window.addEventListener("unload", () => {
CSM.port.postMessage({
cmd: 'unmark-player',
forwardToAll: true,
});
// if (BrowserDetect.anyChromium) {
// chrome.extension.getBackgroundPage().sendUnmarkPlayer({
// cmd: 'unmark-player',
// forwardToAll: true,
// });
// }
});
// get info about current site from background script
while (true) {
this.requestSite();
await this.sleep(5000);
}
} catch (e) {
console.error('[Popup.vue::created()] An error happened:', e)
}
},
/**
* Initializes page when it's being loaded as in-page settings window
*/
async setupIframe() {
this.role='ui-window';
this.logAggregator = new LogAggregator('settings-page');
this.logger = new ComponentLogger(this.logAggregator, 'SettingsPage');
this.settings = new Settings({
logAggregator: this.logAggregator,
onSettingsChanged: () => this.updateConfig()
});
if (this.defaultTab) {
this.selectedTab = this.defaultTab;
}
this.siteSettings = this.settings.getSiteSettings({site: this.site});
this.tabs.find(x => x.id === 'changelog').highlight = !this.settings.active?.whatsNewChecked;
this.eventBus?.subscribe(
'uw-show-ui',
{
source: this,
function: () => {
if (this.inPlayer) {
return; // show-ui is only intended for global overlay
}
},
}
)
} }
} }
}); });

View File

@ -0,0 +1,81 @@
<svg width="90" height="90" viewBox="0 0 90 90" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- Glow filter for selected symbol -->
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feDropShadow dx="0" dy="0" stdDeviation="4" flood-color="#ffac66"/>
</filter>
</defs>
<style>
path {
/*fill: oklch(86.9% 0.005 56.366)*/;
fill: oklch(55.3% 0.013 58.071);
transition: fill 0.2s, filter 0.2s;
}
rect.indicator-bg {
fill: transparent;
transition: fill 0.2s;
}
/* Hover */
g:hover path {
fill: #fff;
}
g:hover rect.indicator-bg {
fill: oklch(37.4% 0.01 67.558);
}
g:hover {
cursor: pointer;
}
/* Selected state: white + glow */
g.selected path {
fill: #fff;
filter: url(#glow);
}
</style>
<!-- === Top Row === -->
<g id="top-left">
<rect class="indicator-bg" x="0" y="0" width="30" height="30"/>
<path d="M7.5 7.5h15v3h-15z M7.5 7.5h3v15h-3z"/>
</g>
<g id="top-center">
<rect class="indicator-bg" x="30" y="0" width="30" height="30"/>
<path d="M37.5 7.5h15v3h-15z"/>
</g>
<g id="top-right">
<rect class="indicator-bg" x="60" y="0" width="30" height="30"/>
<path d="M67.5 7.5h15v3h-15z M79.5 7.5h3v15h-3z"/>
</g>
<!-- === Middle Row === -->
<g id="center-left">
<rect class="indicator-bg" x="0" y="30" width="30" height="30"/>
<path d="M7.5 37.5h3v15h-3z"/>
</g>
<g id="center-center">
<rect class="indicator-bg" x="30" y="30" width="30" height="30"/>
<path d="M41 41h8v8h-8z"/>
</g>
<g id="center-right">
<rect class="indicator-bg" x="60" y="30" width="30" height="30"/>
<path d="M79.5 37.5h3v15h-3z"/>
</g>
<!-- === Bottom Row === -->
<g id="bottom-left">
<rect class="indicator-bg" x="0" y="60" width="30" height="30"/>
<path d="M7.5 79.5h15v3h-15z M7.5 67.5h3v15h-3z"/>
</g>
<g id="bottom-center">
<rect class="indicator-bg" x="30" y="60" width="30" height="30"/>
<path d="M37.5 79.5h15v3h-15z"/>
</g>
<g id="bottom-right">
<rect class="indicator-bg" x="60" y="60" width="30" height="30"/>
<path d="M67.5 79.5h15v3h-15z M79.5 67.5h3v15h-3z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,68 @@
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum';
const positionMap: Record<VideoAlignmentType, string> = {
[VideoAlignmentType.Left]: 'left',
[VideoAlignmentType.Center]: 'center',
[VideoAlignmentType.Right]: 'right',
[VideoAlignmentType.Top]: 'top',
[VideoAlignmentType.Bottom]: 'bottom',
[VideoAlignmentType.Default]: ''
};
const location2enum: Record<string, VideoAlignmentType> = {
left: VideoAlignmentType.Left,
center: VideoAlignmentType.Center,
right: VideoAlignmentType.Right,
top: VideoAlignmentType.Top,
bottom: VideoAlignmentType.Bottom
};
/**
* Sets alignment indicator state of the video alignment SVG
* @param svg SVG element
* @param x horizontal alignment
* @param y vertical alignment
* @returns
*/
export function setVideoAlignmentIndicatorState(
svg: SVGSVGElement,
x: VideoAlignmentType,
y: VideoAlignmentType
) {
// reset all indicators
svg.querySelectorAll<SVGGElement>('g').forEach(g => g.classList.remove('selected'));
// select the appropriate square
if (x === VideoAlignmentType.Default || y === VideoAlignmentType.Default) {
return;
}
const gId = `${positionMap[y]}-${positionMap[x]}`;
const selected = svg.getElementById(gId);
if (selected) selected.classList.add('selected');
}
/**
* Sets up interaction for the video alignment SVG
* @param svg SVG element
* @param callback function to be called, with x and y alignment (VideoAlignmentType)
*/
export function setupVideoAlignmentIndicatorInteraction(
svg: SVGSVGElement,
callback: (x: VideoAlignmentType, y: VideoAlignmentType) => void
) {
svg.querySelectorAll<SVGGElement>('g').forEach(g => {
g.addEventListener('click', () => {
const [y, x] = g.id.split('-');
if (!x || !y)
return;
callback(
location2enum[x],
location2enum[y]
)
});
});
}