Start on work on popup, mostly finish work on settings page
This commit is contained in:
parent
5cfa5b0add
commit
f543dcff24
90
package-lock.json
generated
90
package-lock.json
generated
@ -53,6 +53,7 @@
|
||||
"postcss-nested": "^7.0.2",
|
||||
"postcss-preset-env": "^10.4.0",
|
||||
"postcss-strip-inline-comments": "^0.1.5",
|
||||
"raw-loader": "^4.0.2",
|
||||
"resolve-url-loader": "^5.0.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"ts-loader": "^9.5.4",
|
||||
@ -17838,6 +17839,95 @@
|
||||
"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": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
|
||||
@ -70,6 +70,7 @@
|
||||
"postcss-nested": "^7.0.2",
|
||||
"postcss-preset-env": "^10.4.0",
|
||||
"postcss-strip-inline-comments": "^0.1.5",
|
||||
"raw-loader": "^4.0.2",
|
||||
"resolve-url-loader": "^5.0.0",
|
||||
"tailwindcss": "^4.1.17",
|
||||
"ts-loader": "^9.5.4",
|
||||
|
||||
@ -125,7 +125,6 @@ import BrowserDetect from '../ext/conf/BrowserDetect';
|
||||
import CommsClient, {CommsOrigin} from '../ext/lib/comms/CommsClient';
|
||||
import Settings from '../ext/lib/settings/Settings';
|
||||
import EventBus from '../ext/lib/EventBus';
|
||||
import {ChromeShittinessMitigations as CSM} from '../common/js/ChromeShittinessMitigations';
|
||||
import SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue'
|
||||
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator';
|
||||
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
|
||||
|
||||
@ -61,7 +61,7 @@ class Settings {
|
||||
|
||||
constructor(options: SettingsOptions) {
|
||||
// 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.afterSettingsSaved = options.afterSettingsSaved;
|
||||
this.active = options.activeSettings ?? undefined;
|
||||
@ -73,6 +73,24 @@ class Settings {
|
||||
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) {
|
||||
if (!changes.uwSettings) {
|
||||
return;
|
||||
|
||||
36
src/main.css
36
src/main.css
@ -38,4 +38,40 @@ html, body {
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
103
src/ui/components/Debugging/Debugging.vue
Normal file
103
src/ui/components/Debugging/Debugging.vue
Normal 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>
|
||||
35
src/ui/components/ExtensionInfo/About.vue
Normal file
35
src/ui/components/ExtensionInfo/About.vue
Normal 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>
|
||||
80
src/ui/components/ExtensionInfo/WhatsNew.vue
Normal file
80
src/ui/components/ExtensionInfo/WhatsNew.vue
Normal 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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
@ -5,7 +5,7 @@
|
||||
|
||||
<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="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 class="flex flex-col w-full">
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<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
|
||||
v-for="tab of tabs"
|
||||
@ -30,7 +30,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content flex flex-col">
|
||||
<div class="grow content flex flex-col overflow-auto pr-4 pb-12">
|
||||
<!-- autodetection warning -->
|
||||
|
||||
<div class="warning-area">
|
||||
@ -50,24 +50,18 @@
|
||||
|
||||
<div class="flex flex-row panel-content">
|
||||
<!-- Panel section -->
|
||||
<!-- <VideoSettings
|
||||
v-if="selectedTab === 'videoSettings'"
|
||||
<VideoSettings
|
||||
v-if="selectedTab === 'video-settings'"
|
||||
:settings="settings"
|
||||
:siteSettings="siteSettings"
|
||||
:eventBus="eventBus"
|
||||
:site="site"
|
||||
></VideoSettings> -->
|
||||
></VideoSettings>
|
||||
<OtherSiteSettings
|
||||
v-if="selectedTab === 'extensionSettings'"
|
||||
:settings="settings"
|
||||
:enableSettingsEditor="true"
|
||||
></OtherSiteSettings>
|
||||
<KeyboardShortcutSettings
|
||||
v-if="selectedTab === 'keyboardShortcuts'"
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
></KeyboardShortcutSettings>
|
||||
|
||||
<AutodetectionSettings
|
||||
v-if="selectedTab === 'autodetectionSettings'"
|
||||
:settings="settings"
|
||||
@ -76,6 +70,31 @@
|
||||
:site="site"
|
||||
>
|
||||
</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
|
||||
@ -85,13 +104,6 @@
|
||||
:site="site"
|
||||
>
|
||||
</PlayerDetectionPanel>
|
||||
<PlayerUiSettings
|
||||
v-if="selectedTab === 'playerUiSettings'"
|
||||
:settings="settings"
|
||||
:siteSettings="siteSettings"
|
||||
:eventBus="eventBus"
|
||||
>
|
||||
</PlayerUiSettings>
|
||||
|
||||
<DebugPanel
|
||||
v-if="selectedTab === 'debugging'"
|
||||
@ -99,14 +111,7 @@
|
||||
:eventBus="eventBus"
|
||||
:site="site"
|
||||
></DebugPanel>
|
||||
<ChangelogPanel
|
||||
v-if="selectedTab === 'changelog'"
|
||||
:settings="settings"
|
||||
></ChangelogPanel>
|
||||
<AboutPanel
|
||||
v-if="selectedTab === 'about'"
|
||||
>
|
||||
</AboutPanel>
|
||||
|
||||
<!-- <ResetBackupPanel
|
||||
v-if="selectedTab === 'resetBackup'"
|
||||
:settings="settings"
|
||||
@ -119,30 +124,69 @@
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import VideoSettings from '@components/VideoSettings/VideoSettings.vue';
|
||||
import OtherSiteSettings from '@components/ExtensionSettings/Panels/OtherSiteSettings.vue';
|
||||
import KeyboardShortcutSettings from '@components/KeyboardShortcuts/KeyboardShortcutSettings.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 PlayerDetectionPanel from '../../csui/src/PlayerUiPanels/PlayerDetectionPanel.vue'
|
||||
import VideoSettings from '../../csui/src/PlayerUiPanels/VideoSettings.vue'
|
||||
import BrowserDetect from '../../ext/conf/BrowserDetect'
|
||||
import ChangelogPanel from '../../csui/src/PlayerUiPanels/ChangelogPanel.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 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({
|
||||
components: {
|
||||
OtherSiteSettings,
|
||||
KeyboardShortcutSettings,
|
||||
AutodetectionSettings,
|
||||
|
||||
VideoSettings,
|
||||
OtherSiteSettings,
|
||||
AutodetectionSettings,
|
||||
KeyboardShortcutSettings,
|
||||
UISettings,
|
||||
|
||||
WhatsNew,
|
||||
About,
|
||||
|
||||
PlayerDetectionPanel,
|
||||
DebugPanel,
|
||||
PlayerUiSettings,
|
||||
ChangelogPanel,
|
||||
AboutPanel,
|
||||
SupportLevelIndicator,
|
||||
@ -156,16 +200,16 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
tabs: [
|
||||
// {id: 'videoSettings', label: 'Video settings', icon: 'crop'},
|
||||
{id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' },
|
||||
{id: 'autodetectionSettings', label: 'Autodetection options', icon: 'auto-fix'},
|
||||
{id: 'playerUiSettings', label: 'UI settings', icon: 'movie-cog-outline' },
|
||||
{id: 'keyboardShortcuts', label: 'Keyboard shortcuts', icon: 'keyboard-outline' },
|
||||
{id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
|
||||
// {id: 'advancedOptions', label: 'Advanced options', icon: 'cogs' },
|
||||
{id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
|
||||
{id: 'about', label: 'About', icon: 'information-outline'},
|
||||
{id: 'debugging', label: 'Debugging', icon: 'bug-outline', hidden: true},
|
||||
// // {id: 'videoSettings', label: 'Video settings', icon: 'crop'},
|
||||
// {id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' },
|
||||
// {id: 'autodetectionSettings', label: 'Autodetection options', icon: 'auto-fix'},
|
||||
// {id: 'uiSettings', label: 'UI settings', icon: 'movie-cog-outline' },
|
||||
// {id: 'keyboardShortcuts', label: 'Keyboard shortcuts', icon: 'keyboard-outline' },
|
||||
// {id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
|
||||
// // {id: 'advancedOptions', label: 'Advanced options', icon: 'cogs' },
|
||||
// {id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
|
||||
// {id: 'about', label: 'About', icon: 'information-outline'},
|
||||
// {id: 'debugging', label: 'Debugging', icon: 'bug-outline', hidden: true},
|
||||
],
|
||||
selectedTab: 'extensionSettings',
|
||||
BrowserDetect: BrowserDetect,
|
||||
@ -174,12 +218,13 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
props: [
|
||||
'role',
|
||||
'initialPath',
|
||||
'settings',
|
||||
'eventBus',
|
||||
'logger',
|
||||
'inPlayer',
|
||||
'site',
|
||||
'defaultTab'
|
||||
],
|
||||
computed: {
|
||||
// LPT: NO ARROW FUNCTIONS IN COMPUTED,
|
||||
@ -190,14 +235,18 @@ export default defineComponent({
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.generateTabs();
|
||||
this.settings.listenAfterChange(this.setDebugTabVisibility);
|
||||
|
||||
if (this.defaultTab) {
|
||||
this.selectedTab = this.defaultTab;
|
||||
if (this.initialPath && this.initialPath.length) {
|
||||
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(
|
||||
'uw-show-ui',
|
||||
{
|
||||
@ -222,9 +271,22 @@ export default defineComponent({
|
||||
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) {
|
||||
console.log("Selecting tab", tab);
|
||||
this.selectedTab = tab;
|
||||
window.location.hash = `#${this.role}/${this.selectedTab}`;
|
||||
},
|
||||
setPreventClose(bool) {
|
||||
this.preventClose = bool;
|
||||
@ -235,7 +297,9 @@ export default defineComponent({
|
||||
if (debugTab) {
|
||||
debugTab.hidden = !this.settings.active.ui.devMode;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@ -249,15 +313,17 @@ export default defineComponent({
|
||||
@apply flex flex-row gap-4 px-4 py-2 justify-end items-center
|
||||
text-[1.125em] text-stone-300 text-right font-mono
|
||||
cursor-pointer
|
||||
border-r-2 border-r-transparent
|
||||
hover:bg-stone-800
|
||||
hover:text-primary-200;
|
||||
hover:text-primary-200
|
||||
hover:border-r-stone-600;
|
||||
|
||||
.label {
|
||||
@apply grow-0;
|
||||
}
|
||||
|
||||
&.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
<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 -->
|
||||
<div class="flex flex-col" style="width: 100%">
|
||||
<h2>Player UI options</h2>
|
||||
|
||||
<div class="flex flex-col compact-form">
|
||||
<div v-if="!siteSettings.data.enableUI.fullscreen">
|
||||
@ -104,66 +106,19 @@
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Button from '@csui/src/components/Button.vue'
|
||||
<script lang="ts">
|
||||
import BrowserDetect from '@src/ext/conf/BrowserDetect';
|
||||
import CropOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/CropOptionsPanel.vue'
|
||||
import StretchOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/StretchOptionsPanel.vue'
|
||||
import ZoomOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/ZoomOptionsPanel.vue'
|
||||
import { defineComponent } from 'vue';
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Button,
|
||||
CropOptionsPanel,
|
||||
StretchOptionsPanel,
|
||||
ZoomOptionsPanel,
|
||||
@ -231,27 +186,9 @@ export default {
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<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;
|
||||
}
|
||||
<style lang="postcss" scoped>
|
||||
|
||||
.trigger-zone-editor {
|
||||
background-color: rgba(0,0,0,0.25);
|
||||
|
||||
240
src/ui/components/VideoSettings/VideoSettings.vue
Normal file
240
src/ui/components/VideoSettings/VideoSettings.vue
Normal 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>
|
||||
@ -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>
|
||||
22
src/ui/components/common/mixins/CommsMixin.vue
Normal file
22
src/ui/components/common/mixins/CommsMixin.vue
Normal 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>
|
||||
@ -1,3 +1,4 @@
|
||||
<script lang="ts">
|
||||
import KeyboardShortcutParser from '@src/common/js/KeyboardShortcutParser';
|
||||
|
||||
export default {
|
||||
@ -12,4 +13,5 @@ export default {
|
||||
return KeyboardShortcutParser.parseShortcut(command.shortcut);
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
0
src/ui/pages/popup/index.html
Normal file
0
src/ui/pages/popup/index.html
Normal file
30
src/ui/pages/popup/popup.js
Normal file
30
src/ui/pages/popup/popup.js
Normal 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.
|
||||
}
|
||||
@ -1,16 +1,20 @@
|
||||
<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 -->
|
||||
<div class="w-full max-w-[1920px]">
|
||||
<h1 class="text-[3em]">Ultrawidify settings</h1>
|
||||
<div class="w-full max-w-[1920px] h-[100dvh] overflow-hidden flex flex-col">
|
||||
<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">
|
||||
Loading settings...
|
||||
</div>
|
||||
|
||||
<SettingsWindowContent v-else
|
||||
class="grow shrink"
|
||||
:role="role"
|
||||
:initialPath="initialPath"
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
:logger="logger"
|
||||
:inPlayer="false"
|
||||
:site="null"
|
||||
@ -28,6 +32,10 @@ import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
|
||||
import Settings from '@src/ext/lib/settings/Settings';
|
||||
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({
|
||||
components: {
|
||||
SettingsWindowContent
|
||||
@ -38,16 +46,25 @@ export default defineComponent({
|
||||
logger: undefined as ComponentLogger | undefined,
|
||||
logAggregator: undefined as LogAggregator | undefined,
|
||||
settingsInitialized: false,
|
||||
role: 'settings',
|
||||
initialPath: undefined as string[] | undefined,
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.logAggregator = new LogAggregator('');
|
||||
this.logger = new ComponentLogger(this.logAggregator, 'App.vue');
|
||||
const [segment, ...path] = window.location.hash.split('/');
|
||||
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();
|
||||
this.settingsInitialized = true;
|
||||
},
|
||||
@ -55,6 +72,140 @@ export default defineComponent({
|
||||
updateConfig() {
|
||||
this.settings.init();
|
||||
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
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
81
src/ui/res/img/alignment-indicators.svg
Normal file
81
src/ui/res/img/alignment-indicators.svg
Normal 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 |
68
src/ui/utils/video-alignment-indicator-handling.ts
Normal file
68
src/ui/utils/video-alignment-indicator-handling.ts
Normal 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]
|
||||
)
|
||||
});
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user