This commit is contained in:
Tamius Han 2025-12-17 18:35:13 +01:00
parent 74c0358d33
commit 3457c4fcdc
40 changed files with 340 additions and 4531 deletions

View File

@ -5,7 +5,7 @@ const fs = require('fs');
const BUNDLE_DIR = path.join(__dirname, `../dist-${process.env.BROWSER === 'firefox' ? 'ff' : process.env.BROWSER}`);
const bundles = [
'csui/csui-popup.js',
// 'csui/csui-popup.js',
];
const evalRegexForProduction = /;([a-z])=function\(\){return this}\(\);try{\1=\1\|\|Function\("return this"\)\(\)\|\|\(0,eval\)\("this"\)}catch\(t\){"object"==typeof window&&\(\1=window\)}/g;

View File

@ -1,586 +0,0 @@
<template>
<div class="popup-panel" style="height: 100vh">
<!--
NOTE the code that makes ultrawidify popup work in firefox regardless of whether the
extension is being displayed in a normal or a small/overflow popup breaks the popup
behaviour on Chrome (where the popup would never reach the full width of 800px)
Since I'm tired and the hour is getting late, we'll just add an extra CSS class for
non-firefox builds of this extension and be done with it. No need to complicate things
further than that.
-->
<div v-if="settingsInitialized"
style="height: 100vh"
class="popup flex flex-col no-overflow"
:class="{'popup-chrome': ! BrowserDetect?.firefox}"
>
<div class="flex flex-col w-full relative header"
>
<div class="flex flex-row w-full" style="height: 42px">
<h1 class="flex-grow">
<span class="smallcaps">Ultrawidify</span>: <small>Quick settings</small>
</h1>
<button
class="settings-header-button"
style="align-self: stretch"
@click="showInPlayerUi()"
>
Show settings window
</button>
</div>
<div class="flex flex-row w-full">
<div v-if="site && siteSettings" style="transform: scale(0.75) translateX(-12.5%); margin-bottom: -0.5rem; align-content: center" class="flex flex-row flex-grow items-center">
<div>site: {{site.host}}</div>
<SupportLevelIndicator
:siteSupportLevel="siteSupportLevel"
>
</SupportLevelIndicator>
</div>
<!-- Version info -->
<div v-if="BrowserDetect?.processEnvChannel !== 'stable'" class="absolute channel-info version-info">
<label>Version:</label> <br/>
{{ settings.getExtensionVersion() }} (non-stable)
</div>
<div v-else class="version-info">
<label>Version:</label> <br/>
{{ settings.getExtensionVersion() }}
</div>
</div>
</div>
<!-- CONTAINER ROOT -->
<div class="flex flex-row body no-overflow flex-grow">
<!-- TABS -->
<div class="flex flex-col tab-row" style="flex: 3 3; border-right: 1px solid #222;">
<div
v-for="tab of tabs"
:key="tab.id"
class="tab flex flex-row"
:class="{
'active': tab.id === selectedTab,
'highlight-tab': tab.highlight,
}"
@click="selectTab(tab.id)"
>
<div class="icon-container">
<mdicon
:name="tab.icon"
:size="32"
/>
</div>
<div class="label">
{{tab.label}}
</div>
</div>
</div>
<!-- CONTENT -->
<div class="scrollable window-content" style="flex: 7 7; padding: 1rem;">
<template v-if="settings && siteSettings">
<PopupVideoSettings
v-if="selectedTab === 'videoSettings'"
:settings="settings"
:eventBus="eventBus"
:siteSettings="siteSettings"
:hosts="activeHosts"
></PopupVideoSettings>
<BaseExtensionSettings
v-if="selectedTab === 'extensionSettings'"
:settings="settings"
:eventBus="eventBus"
:siteSettings="siteSettings"
:site="site.host"
:hosts="activeHosts"
>
</BaseExtensionSettings>
<ChangelogPanel
v-if="selectedTab === 'changelog'"
:settings="settings"
></ChangelogPanel>
<AboutPanel
v-if="selectedTab === 'about'"
>
</AboutPanel>
</template>
<template v-else>No settings or site settings found.</template>
</div>
</div>
</div>
</div>
</template>
<script>
import BaseExtensionSettings from './src/PlayerUiPanels/BaseExtensionSettings.vue'
import PlayerDetectionPanel from './src/PlayerUiPanels/PlayerDetectionPanel.vue'
import ChangelogPanel from './src/PlayerUiPanels/ChangelogPanel.vue'
import PopupVideoSettings from './src/popup/panels/PopupVideoSettings.vue'
import AboutPanel from '@csui/src/popup/panels/AboutPanel.vue'
import Debug from '../ext/conf/Debug';
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 SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue'
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator';
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
export default {
components: {
Debug,
BrowserDetect,
PopupVideoSettings,
PlayerDetectionPanel,
BaseExtensionSettings,
SupportLevelIndicator,
ChangelogPanel,
AboutPanel
},
data () {
return {
comms: undefined,
eventBus: new EventBus(),
settings: {},
settingsInitialized: false,
narrowPopup: null,
sideMenuVisible: null,
logAggregator: undefined,
logger: undefined,
site: undefined,
siteSettings: undefined,
selectedTab: 'videoSettings',
tabs: [
// see this for icons: https://pictogrammers.com/library/mdi/
// {id: 'playerUiCtl', label: 'In-player UI', icon: 'artboard'},
{id: 'videoSettings', label: 'Video settings', icon: 'crop'},
// {id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
{id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' },
{id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
{id: 'about', label: 'About', icon: 'information-outline'},
],
}
},
computed: {
siteSupportLevel() {
return (this.site && this.siteSettings) ? this.siteSettings.data.type || 'no-support' : 'waiting';
}
},
mounted() {
this.tabs.find(x => x.id === 'changelog').highlight = !this.settings.active?.whatsNewChecked;
this.requestSite();
},
async created() {
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)
}
},
async updated() {
const body = document.getElementsByTagName('body')[0];
// ensure that narrowPopup only gets set the first time the popup renders
// if popup was rendered before, we don't do anything because otherwise
// we'll be causing an unwanted re-render
//
// another thing worth noting the popup gets first initialized with
// offsetWidth set to 0. This means proper popup will be displayed as a
// mini popup if we don't check for that.
if (this.narrowPopup === null && body.offsetWidth > 0) {
this.narrowPopup = body.offsetWidth < 600;
}
},
methods: {
showInPlayerUi() {
this.eventBus.send('uw-set-ui-state', {globalUiVisible: true}, {comms: {forwardTo: 'active'}});
},
async sleep(t) {
return new Promise( (resolve,reject) => {
setTimeout(() => resolve(), t);
});
},
toObject(obj) {
return JSON.parse(JSON.stringify(obj));
},
requestSite() {
try {
this.logger.log('info','popup', '[popup::getSite] Requesting current site ...')
// CSM.port.postMessage({command: 'get-current-site'});
this.eventBus.send(
'get-current-site',
{},
{
comms: {forwardTo: 'active'}
}
);
} catch (e) {
this.logger.log('error','popup','[popup::getSite] sending get-current-site failed for some reason. Reason:', e);
}
},
getRandomColor() {
return `rgb(${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)})`;
},
selectTab(tab) {
this.selectedTab = tab;
},
isDefaultFrame(frameId) {
return frameId === '__playing' || frameId === '__all';
},
loadHostnames() {
this.activeHosts = this.site.hostnames;
},
loadFrames() {
this.activeFrames = [{
host: this.site.host,
isIFrame: false, // not used tho. Maybe one day
}];
for (const frame in this.site.frames) {
if (!this.activeFrames.find(x => x.host === this.site.frames[frame].host)) {
this.activeFrames.push({
id: `${this.site.id}-${frame}`,
label: this.site.frames[frame].host,
host: this.site.frames[frame].host,
...this.site.frames[frame],
...this.settings.active.sites[this.site.frames[frame].host]
})
};
}
},
getRandomColor() {
return `rgb(${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)})`;
},
updateConfig() {
this.settings.init();
this.$nextTick( () => this.$forceUpdate());
}
}
}
</script>
<style lang="scss">
/*
@import 'res/css/colors.scss';
@import 'res/css/font/overpass.css';
@import 'res/css/font/overpass-mono.css';
@import 'res/css/common.scss';
@import './src/res-common/_variables';
*/
.header {
background-color: rgb(90, 28, 13);
background-color: rgb(0,0,0);
color: #fff;
padding: 8px;
border-bottom: 1px dotted #fa6;
h1 {
font-size: 2rem;
}
.version-info {
text-align: right;
font-size: 0.8rem;
opacity: 0.8;
label {
opacity: 0.5;
}
}
}
.settings-header-button {
display: flex;
flex-direction: row;
align-items: center;
padding: 0.5rem 2rem;
text-transform: lowercase;
font-variant: small-caps;
background-color: #000;
border: 1px solid #fa68;
color: #eee;
}
.site-support-info {
display: flex;
flex-direction: row;
align-items: center;
.site-support-site {
font-size: 1.5em;
}
.site-support {
display: inline-flex;
flex-direction: row;
align-items: center;
margin-left: 1rem;
border-radius: 8px;
padding: 0rem 1.5rem 0rem 1rem;
position: relative;
.tooltip {
padding: 1rem;
display: none;
position: absolute;
bottom: 0;
transform: translateY(110%);
width: 42em;
background-color: rgba(0,0,0,0.90);
color: #ccc;
}
&:hover {
.tooltip {
display: block;
}
}
.mdi {
margin-right: 1rem;
}
&.official {
background-color: #fa6;
color: #000;
.mdi {
fill: #000 !important;
}
}
&.community {
background-color: rgb(85, 85, 179);
color: #fff;
.mdi {
fill: #fff !important;
}
}
&.no-support {
background-color: rgb(138, 65, 126);
color: #eee;
.mdi {
fill: #eee !important;
}
}
&.user-added {
border: 1px solid #ff0;
color: #ff0;
.mdi {
fill: #ff0 !important;
}
}
}
}
.content {
flex-grow: 1;
.warning-area {
flex-grow: 0;
flex-shrink: 0;
}
.panel-content {
flex-grow: 1;
flex-shrink: 1;
overflow-y: auto;
padding: 1rem;
}
}
.warning-box {
background: rgb(255, 174, 107);
color: #000;
margin: 1rem;
padding: 1rem;
display: flex;
flex-direction: row;
align-items: center;
.icon-container {
margin-right: 1rem;
flex-shrink: 0;
flex-grow: 0;
}
a {
color: rgba(0,0,0,0.7);
cursor: pointer;
}
}
.popup-panel {
background-color: rgba(0,0,0,0.50);
color: #fff;
overflow-y: auto;
.popup-window-header {
padding: 1rem;
background-color: rgba(5,5,5, 0.75);
}
.tab-row {
background-color: rgba(11,11,11, 0.75);
.tab {
display: flex;
flex-direction: row;
align-items: center;
padding: 1rem;
font-size: 1.25rem;
min-height: 3rem;
border-bottom: 1px solid rgba(128, 128, 128, 0.5);
border-top: 1px solid rgba(128, 128, 128, 0.5);
opacity: 0.5;
&:hover {
opacity: 1;
}
&.active {
opacity: 1.0;
background-color: $primaryBg;
color: rgb(255, 174, 107);
border-bottom: 1px solid rgba(116, 78, 47, 0.5);
border-top: 1px solid rgba(116, 78, 47, 0.5);
}
.icon-container {
width: 64px;
flex-grow: 0;
flex-shrink: 0;
}
.label {
flex-grow: 1;
flex-shrink: 1;
padding: 0 !important;
}
&.highlight-tab {
opacity: 0.9;
color: #eee;
}
}
}
.popup-title, .popup-title h1 {
font-size: 48px !important;
}
}
pre {
white-space: pre-wrap;
}
.button {
border: 1px solid #222 !important;
}
h1 {
margin: 0; padding: 0; font-weight: 400; font-size:24px;
}
.window-content {
height: 100%;
width: 100%;
overflow: auto;
}
</style>

View File

@ -11,6 +11,6 @@
</head>
<body class="uw-ultrawidify-container-root">
<div id="app"></div>
<script src="csui-global.js"></script>
<!-- <script src="csui-global.js"></script> -->
</body>
</html>

View File

@ -8,6 +8,6 @@
</head>
<body class="uw-ultrawidify-container-root" style="background-color: transparent;">
<div id="app"></div>
<script src="csui.js"></script>
<!-- <script src="csui.js"></script> -->
</body>
</html>

View File

@ -8,6 +8,6 @@
</head>
<body class="uw-ultrawidify-container-root" style="background-color: transparent;">
<div id="app"></div>
<script src="csui.js"></script>
<!-- <script src="csui.js"></script> -->
</body>
</html>

View File

@ -7,6 +7,6 @@
</head>
<body class="uw-ultrawidify-container-root" style="background-color: transparent;">
<div id="app"></div>
<script src="csui.js"></script>
<!-- <script src="csui.js"></script> -->
</body>
</html>

View File

@ -1,20 +0,0 @@
<!DOCTYPE html>
<html lang="en" style="position: relative; height: 600px">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- <link rel="stylesheet" href="popup.css"> -->
<% if (NODE_ENV === 'development') { %>
<!-- Load some resources only in development environment -->
<% } %>
</head>
<body
style="width: 100%; height: 100vh; overflow-x: hidden; min-width: 750px;"
>
<div id="app" style="max-height: 100vh;">
</div>
<script src="csui-popup.js"></script>
</body>
</html>

View File

@ -1,8 +0,0 @@
import { createApp } from 'vue'
import Popup from './Popup';
import mdiVue from 'mdi-vue/v3';
import * as mdijs from '@mdi/js';
createApp(Popup)
.use(mdiVue, {icons: mdijs})
.mount('#app');

View File

@ -1,103 +0,0 @@
<template>
<div class="flex flex-col w-full h-full gap-2">
<div class="flex flex-row gap-8 bg-black flex-wrap w-full">
<div class="min-w-[400px] max-w-[520px] grow shrink">
<h1>What's new</h1>
<!-- <p>Full changelog for older versions <a href="https://github.com/tamius-han/ultrawidify/blob/master/CHANGELOG.md" target="_blank">is available here</a>.</p> -->
<h2>6.4.0</h2>
<ul>
<li>In-player UI can now appear in full-screen for websites that put players into top layer</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>
</ul>
</div>
<div style="width: 1rem; height: 0px;"></div>
<div class="min-w-[400px] max-w-[520px] grow shrink">
<h2>Report a problem</h2>
<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 extension version, whether you installed the extension from, and description of your problem.
</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2>Thank you monies</h2>
<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>
</div>
</div>
</div>
</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>
.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

@ -1,103 +0,0 @@
<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>
import { LogAggregator, BLANK_LOGGER_CONFIG } from '@src/ext/lib/logging/LogAggregator';
import JsonEditor from '@csui/src/components/JsonEditor';
export default {
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',
'site'
],
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

@ -1,253 +0,0 @@
<template>
<div class="sub-panel-content flex flex-row flex-wrap">
<ShortcutButton
v-for="(command, index) of settings?.active.commands.crop"
class="flex button"
:class="{
active: editMode ? index === editModeOptions?.crop?.selectedIndex : isActiveCrop(command),
'b3-compact': compact,
b3: !compact
}"
:key="index"
:label="command.label"
:shortcut="getKeyboardShortcutLabel(command)"
@click="editMode ? editAction(command, index, 'crop') : execAction(command)"
>
</ShortcutButton>
<!-- "Add new" button -->
<ShortcutButton
v-if="editMode"
class="button b3"
:class="{active: editMode ? editModeOptions?.crop?.selectedIndex === null : isActiveCrop(command)}"
label="Add new"
@click="editAction(
{action: 'set-ar', label: 'New aspect ratio', arguments: {type: AspectRatioType.Fixed}},
null,
'crop'
)"
></ShortcutButton>
</div>
<!-- EDIT MODE PANEL -->
<div
v-if="editMode && !editModeOptions?.crop?.selected"
class="sub-panel-content"
>
<div class="edit-action-area">
Click a button to edit
</div>
</div>
<div v-if="editMode && editModeOptions?.crop?.selected" class="sub-panel-content">
<div class="edit-action-area-header">
<span class="text-primary">Editing options for:</span> <b>{{editModeOptions?.crop?.selected?.label}}</b>&nbsp;
<template v-if="editModeOptions?.crop?.selectedIndex === null && editModeOptions?.crop?.selected?.label !== 'New aspect ratio'">(New ratio)</template>
</div>
<div class="edit-action-area">
<!-- Some options are only shown for type 4 (fixed) crops -->
<template v-if="editModeOptions?.crop?.selected?.arguments?.type === AspectRatioType.Fixed">
<div class="field">
<div class="label">
Ratio:
</div>
<div class="input">
<!-- We do an ugly in order to avoid spamming functions down at the bottom -->
<input
v-model="editModeOptions.crop.selected.arguments.ratio"
@blur="updateLabel('crop')"
>
</div>
</div>
<div v-if="editModeOptions.crop?.error" class="hint error">
{{editModeOptions.crop.error}}
</div>
<div class="hint">
You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor
(in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter
your numbers without quote marks. Number will be converted to factor form on save.
</div>
<div class="field">
<div class="label">
Label:
</div>
<div class="input">
<input v-model="editModeOptions.crop.selected.label">
</div>
</div>
<div class="hint">
Label for the button. You can make it say something other than ratio.
</div>
</template>
<!-- editing keyboard shortcuts is always allowed -->
<div class="field">
<div class="label">Shortcut:</div>
<div class="">
<EditShortcutButton
:shortcut="editModeOptions?.crop?.selected?.shortcut"
@shortcutChanged="updateSelectedShortcut($event, 'crop')"
>
</EditShortcutButton>
</div>
</div>
<div class="hint">
<b>Note:</b> Your browser and OS already use certain key combinations that involve Ctrl and Meta (Windows) keys and, to a lesser extent, Alt.
The extension doesn't (and cannot) check whether the keyboard shortcut you enter is actually free for you to use. The extension also won't override
any keyboard shortcuts defined by the site itself.
</div>
<div class="flex flex-row flex-end">
<div
v-if="editModeOptions?.crop?.selected?.arguments?.type === AspectRatioType.Fixed && editModeOptions?.crop?.selectedIndex !== null"
class="button"
@click="deleteAction('crop')"
>
<mdicon name="delete"></mdicon> Delete
</div>
<div class="flex-grow"></div>
<div class="button" @click="cancelEdit('crop')">Cancel</div>
<div class="button" @click="saveShortcut('crop')">
<mdicon name="floppy"></mdicon>
&nbsp;
<template v-if="editModeOptions?.crop?.selectedIndex === null">Add</template>
<template v-else>Save</template>
</div>
</div>
</div>
</div>
<div v-if="siteSettings && allowSettingSiteDefault" class="edit-action-area">
<div class="field">
<div class="label">Default for this site</div>
<div class="select">
<select
:value="siteDefaultCrop"
@change="setDefaultCrop($event, 'site')"
>
<option
v-for="(command, index) of settings?.active.commands.crop"
:key="index"
:value="JSON.stringify(command.arguments)"
>
{{command.label}}
</option>
</select>
</div>
</div>
</div>
</template>
<script>
import ShortcutButton from '@csui/src/components/ShortcutButton.vue';
import EditShortcutButton from '@csui/src/components/EditShortcutButton';
import EditModeMixin from '@csui/src/utils/EditModeMixin';
import KeyboardShortcutParserMixin from '@csui/src/utils/KeyboardShortcutParserMixin';
import CommsMixin from '@csui/src/utils/CommsMixin';
import AspectRatioType from '@src/common/enums/AspectRatioType.enum';
export default {
components: {
ShortcutButton,
EditShortcutButton,
},
mixins: [
// ComputeActionsMixin,
EditModeMixin,
KeyboardShortcutParserMixin,
CommsMixin
],
data() {
return {
AspectRatioType: AspectRatioType,
// TODO: this should be mixin?
resizerConfig: {
crop: null,
stretch: null,
zoom: null,
pan: null
}
}
},
props: [
'settings', // required for buttons and actions, which are global
'siteSettings',
'eventBus',
'isEditing',
'allowSettingSiteDefault',
'compact',
],
computed: {
siteDefaultCrop() {
if (!this.siteSettings) {
return null;
}
return JSON.stringify(
this.siteSettings.data.defaults.crop
);
},
},
created() {
if (this.isEditing) {
this.enableEditMode();
}
},
watch: {
isEditing(newValue, oldValue) {
if (newValue) {
this.enableEditMode();
} else {
this.disableEditMode();
}
}
},
methods: {
/**
* Sets default crop, for either site or global
*/
setDefaultCrop($event, scope) {
if (!this.siteSettings) {
return;
}
const commandArguments = JSON.parse($event.target.value);
this.siteSettings.set('defaults.crop', commandArguments);
this.settings.saveWithoutReload();
},
/**
* Determines whether a given crop command is the currently active one
*/
isActiveCrop(cropCommand) {
if (! this.resizerConfig.crop || !this.siteSettings) {
return false;
}
const defaultCrop = this.siteSettings.data.defaults.crop;
if (cropCommand.arguments.type === AspectRatioType.Automatic) {
return this.resizerConfig.crop.type === AspectRatioType.Automatic
|| this.resizerConfig.crop.type === AspectRatioType.AutomaticUpdate
|| this.resizerConfig.crop.type === AspectRatioType.Initial && defaultCrop === AspectRatioType.Automatic;
}
if (cropCommand.arguments.type === AspectRatioType.Reset) {
return this.resizerConfig.crop.type === AspectRatioType.Reset
|| this.resizerConfig.crop.type === AspectRatioType.Initial && defaultCrop !== AspectRatioType.Automatic;
}
if (cropCommand.arguments.type === AspectRatioType.Fixed) {
return this.resizerConfig.crop.type === AspectRatioType.Fixed
&& this.resizerConfig.crop.ratio === cropCommand.arguments.ratio;
}
// only legacy options (fitw, fith) left to handle:
return cropCommand.arguments.type === this.resizerConfig.crop.type;
},
}
}
</script>
<style lang="scss" src="../../../../res/css/flex.scss" scoped></style>
<style lang="scss" src="@csui/src/res-common/panels.scss" scoped></style>
<style lang="scss" src="@csui/src/res-common/common.scss" scoped></style>

View File

@ -1,257 +0,0 @@
<template>
<div class="sub-panel-content flex flex-row flex-wrap">
<ShortcutButton
v-for="(command, index) of settings?.active.commands.stretch"
class="b3 button"
:class="{active: editMode ? index === editModeOptions?.stretch?.selectedIndex : isActiveStretch(command),
'b3-compact': compact,
b3: !compact
}"
:key="index"
:label="command.label"
:shortcut="getKeyboardShortcutLabel(command)"
@click="editMode ? editAction(command, index, 'stretch') : execAction(command)"
>
</ShortcutButton>
<!-- "Add new" button -->
<ShortcutButton
v-if="editMode"
class="button b3"
label="Add new"
@click="editAction(
{action: 'set-stretch', label: 'Stretch to ...', arguments: {type: StretchType.FixedSource}},
null,
'stretch'
)"
></ShortcutButton>
</div>
<!-- EDIT MODE PANEL -->
<div
v-if="editMode && !editModeOptions?.stretch?.selected"
class="sub-panel-content"
>
<div class="edit-action-area">
Click a button to edit
</div>
</div>
<div v-if="editMode && editModeOptions?.stretch?.selected" class="sub-panel-content">
<div class="edit-action-area-header">
<span class="text-primary">Editing options for:</span> <b>{{editModeOptions?.stretch?.selected?.label}}</b>&nbsp;
<template v-if="editModeOptions?.stretch?.selectedIndex === null && editModeOptions?.stretch?.selected?.label !== 'Stretch to ...'">(New option)</template>
</div>
<div class="edit-action-area">
<!-- There are some special options for 'thin borders' -->
<template v-if="editModeOptions?.stretch?.selected?.arguments?.type === StretchType.Conditional">
<div class="field">
<div class="label">
Limit:
</div>
<div class="input">
<input
v-model="editModeOptions.stretch.selected.arguments.limit"
>
</div>
</div>
<div class="hint">
If vertical borders would take up less than this much of screen width, the image will be stretched. If the borders are too thick, image will not be stretched.
Value of 1 means 100%. Value of 0.1 means vertical black bars can take up 10% of the width at most. There's no validation on this, use common sense.
</div>
</template>
<!-- Some options are only shown for type 5 (fixed) stretch -->
<template v-if="editModeOptions?.stretch?.selected?.arguments?.type === StretchType.FixedSource">
<div class="field">
<div class="label">
Ratio:
</div>
<div class="input">
<!-- We do an ugly in order to avoid spamming functions down at the bottom -->
<input
v-model="editModeOptions.stretch.selected.arguments.ratio"
@blur="updateLabel('stretch')"
>
</div>
</div>
<div v-if="editModeOptions.stretch?.error" class="hint error">
{{editModeOptions.stretch.error}}
</div>
<div class="hint">
You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor
(in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter
your numbers without quote marks. Number will be converted to factor form on save.
</div>
<div class="field">
<div class="label">
Label:
</div>
<div class="input">
<input v-model="editModeOptions.stretch.selected.label">
</div>
</div>
<div class="hint">
Label for the button. You can make it say something other than ratio.
</div>
</template>
<!-- editing keyboard shortcuts is always allowed -->
<div class="field">
<div class="label">Shortcut:</div>
<div class="">
<EditShortcutButton
:shortcut="editModeOptions?.stretch?.selected?.shortcut"
@shortcutChanged="updateSelectedShortcut($event, 'stretch')"
>
</EditShortcutButton>
</div>
</div>
<div class="hint">
<b>Note:</b> Your browser and OS already use certain key combinations that involve Ctrl and Meta (Windows) keys and, to a lesser extent, Alt.
The extension doesn't (and cannot) check whether the keyboard shortcut you enter is actually free for you to use. The extension also won't override
any keyboard shortcuts defined by the site itself.
</div>
<div class="flex flex-row flex-end">
<div
v-if="editModeOptions?.stretch?.selected?.arguments?.type === StretchType.FixedSource && editModeOptions?.stretch?.selectedIndex !== null"
class="button"
@click="deleteAction('stretch')"
>
<mdicon name="delete"></mdicon> Delete
</div>
<div class="flex-grow"></div>
<div class="button" @click="cancelEdit('stretch')">Cancel</div>
<div class="button" @click="saveShortcut('stretch')">
<mdicon name="floppy"></mdicon>
&nbsp;
<template v-if="editModeOptions?.crop?.selectedIndex === null">Add</template>
<template v-else>Save</template>
</div>
</div>
</div>
</div>
<div v-if="siteSettings && allowSettingSiteDefault" class="edit-action-area">
<div class="field">
<div class="label">Default for this site:</div>
<div class="select">
<div class="select">
<select
v-model="siteDefaultStretchMode"
@change="setDefaultStretchingMode($event, 'site')"
>
<option
v-for="(command, index) of settings?.active.commands.stretch"
:key="index"
:value="JSON.stringify(command.arguments)"
>
{{command.label}}
</option>
</select>
</div>
</div>
</div>
</div>
</template>
<script>
import ShortcutButton from '../../../components/ShortcutButton.vue';
import EditShortcutButton from '../../../components/EditShortcutButton';
import EditModeMixin from '../../../utils/EditModeMixin';
import KeyboardShortcutParserMixin from '../../../utils/KeyboardShortcutParserMixin';
import CommsMixin from '../../../utils/CommsMixin';
import StretchType from '../../../../../common/enums/StretchType.enum';
export default {
data() {
return {
exec: null,
StretchType: StretchType,
// TODO: this should be mixin?
resizerConfig: {
crop: null,
stretch: null,
zoom: null,
pan: null
}
}
},
mixins: [
// ComputeActionsMixin,
EditModeMixin,
KeyboardShortcutParserMixin,
CommsMixin
],
props: [
'settings', // required for buttons and actions, which are global
'siteSettings',
'eventBus',
'isEditing',
'allowSettingSiteDefault',
'compact',
],
components: {
ShortcutButton,
EditShortcutButton,
},
computed: {
siteDefaultStretch() {
if (!this.siteSettings) {
return null;
}
return JSON.stringify(
this.siteSettings.data.defaults.stretch
);
},
},
created() {
if (this.isEditing) {
this.enableEditMode();
}
},
watch: {
isEditing(newValue, oldValue) {
if (newValue) {
this.enableEditMode();
} else {
this.disableEditMode();
}
}
},
methods: {
/**
* Sets default stretching mode, for either site or global
*/
setDefaultStretchingMode($event, globalOrSite) {
if (!this.siteSettings) {
return;
}
const commandArguments = JSON.parse($event.target.value);
this.siteSettings.set('defaults.stretch', commandArguments);
},
/**
* Determines whether a given stretch command is the currently active one
*/
isActiveStretch(stretchCommand) {
if (! this.resizerConfig.stretch || !this.siteSettings) {
return false;
}
// const defaultCrop = this.settings.getDefaultStretch(this.site);
if ([StretchType.NoStretch, StretchType.Basic, StretchType.Hybrid, StretchType.Conditional, StretchType.Default].includes(stretchCommand.arguments.type)) {
return this.resizerConfig.stretch.type === stretchCommand.arguments.type;
}
return this.resizerConfig.crop.type === stretchCommand.arguments.type && this.resizerConfig.crop.ratio === stretchCommand.arguments.ratio;
},
}
}
</script>
<style lang="scss" src="@csui/res/css/flex.scss" scoped></style>
<style lang="scss" src="@csui/src/res-common/panels.scss" scoped></style>
<style lang="scss" src="@csui/src/res-common/common.scss" scoped></style>

View File

@ -1,317 +0,0 @@
<template>
<div class="flex flex-col">
<div class="sub-panel-content flex flex-row flex-wrap">
<ShortcutButton
v-for="(command, index) of settings?.active.commands.zoom"
class="flex button"
:class="{active: editMode ? index === editModeOptions?.zoom?.selectedIndex : isActiveZoom(command),
'b3-compact': compact,
b3: !compact
}"
:key="index"
:label="command.label"
:shortcut="getKeyboardShortcutLabel(command)"
@click="editMode ? editAction(command, index, 'zoom') : execAction(command)"
>
</ShortcutButton>
<!-- "Add new" button -->
<ShortcutButton
v-if="editMode"
class="button b3"
:class="{active: editMode ? editModeOptions?.crop?.selectedIndex === null : isActiveCrop(command)}"
label="Add new"
@click="editAction(
{action: 'set-ar-zoom', label: 'New aspect ratio', arguments: {type: AspectRatioType.Fixed}},
null,
'zoom'
)"
></ShortcutButton>
</div>
<template v-if="isEditing">
<div v-if="editMode && editModeOptions?.zoom?.selected" class="sub-panel-content">
<div class="edit-action-area-header">
<span class="text-primary">Editing options for:</span> <b>{{editModeOptions?.zoom?.selected?.label}}</b>&nbsp;
<template v-if="editModeOptions?.zoom?.selectedIndex === null && editModeOptions?.zoom?.selected?.label !== 'New aspect ratio'">(New ratio)</template>
</div>
<div class="edit-action-area">
<!-- Some options are only shown for type 4 (fixed) zooms -->
<template v-if="editModeOptions?.zoom?.selected?.arguments?.type === AspectRatioType.Fixed">
<div class="field">
<div class="label">
Ratio:
</div>
<div class="input">
<!-- We do an ugly in order to avoid spamming functions down at the bottom -->
<input
v-model="editModeOptions.zoom.selected.arguments.ratio"
@blur="updateLabel('zoom')"
>
</div>
</div>
<div v-if="editModeOptions.zoom?.error" class="hint error">
{{editModeOptions.zoom.error}}
</div>
<div class="hint">
You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor
(in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter
your numbers without quote marks. Number will be converted to factor form on save.
</div>
<div class="field">
<div class="label">
Label:
</div>
<div class="input">
<input v-model="editModeOptions.zoom.selected.label">
</div>
</div>
<div class="hint">
Label for the button. You can make it say something other than ratio.
</div>
</template>
<!-- editing keyboard shortcuts is always allowed -->
<div class="field">
<div class="label">Shortcut:</div>
<div class="">
<EditShortcutButton
:shortcut="editModeOptions?.zoom?.selected?.shortcut"
@shortcutChanged="updateSelectedShortcut($event, 'zoom')"
>
</EditShortcutButton>
</div>
</div>
<div class="hint">
<b>Note:</b> Your browser and OS already use certain key combinations that involve Ctrl and Meta (Windows) keys and, to a lesser extent, Alt.
The extension doesn't (and cannot) check whether the keyboard shortcut you enter is actually free for you to use. The extension also won't override
any keyboard shortcuts defined by the site itself.
</div>
<div class="flex flex-row flex-end">
<div
v-if="editModeOptions?.zoom?.selected?.arguments?.type === AspectRatioType.Fixed && editModeOptions?.zoom?.selectedIndex !== null"
class="button"
@click="deleteAction('zoom')"
>
<mdicon name="delete"></mdicon> Delete
</div>
<div class="flex-grow"></div>
<div class="button" @click="cancelEdit('zoom')">Cancel</div>
<div class="button" @click="saveShortcut('zoom')">
<mdicon name="floppy"></mdicon>
&nbsp;
<template v-if="editModeOptions?.zoom?.selectedIndex === null">Add</template>
<template v-else>Save</template>
</div>
</div>
</div>
</div>
</template>
<template v-else>
<!--
min, max and value need to be implemented in js as this slider
should use logarithmic scale
-->
<div class="flex flex-row w-full" style="margin-top: 0.66rem">
<div style="position:relative;" class="grow">
<template v-if="zoomAspectRatioLocked">
<div class="slider-label">
Zoom: {{getZoomForDisplay('x')}}
</div>
<input id="_input_zoom_slider"
class="input-slider"
type="range"
step="any"
min="-1"
max="3"
:value="zoom.x"
@input="changeZoom($event.target.value)"
/>
</template>
<template v-else>
<div class="slider-label">Horizontal zoom: {{getZoomForDisplay('x')}}</div>
<input id="_input_zoom_slider"
class="input-slider"
type="range"
step="any"
min="-1"
max="4"
:value="zoom.x"
@input="changeZoom($event.target.value, 'x')"
/>
<div class="slider-label">Vertical zoom: {{getZoomForDisplay('y')}}</div>
<input id="_input_zoom_slider_2"
class="input-slider"
type="range"
step="any"
min="-1"
max="3"
:value="zoom.y"
@input="changeZoom($event.target.value, 'y')"
/>
</template>
</div>
<div class="flex flex-row items-center justify-center" style="padding-left: 1rem">
<Button
v-if="zoomAspectRatioLocked"
icon="lock"
:iconSize="16"
:fixedWidth="true"
:noPad="true"
@click="toggleZoomAr()"
>
</Button>
<Button
v-else
icon="lock-open-variant"
:iconSize="16"
:fixedWidth="true"
:noPad="true"
@click="toggleZoomAr()"
>
</Button>
<Button
icon="restore"
:iconSize="16"
:noPad="true"
@click="resetZoom()"
></Button>
</div>
</div>
</template>
</div>
</template>
<script>
import Button from '@csui/src/components/Button.vue';
import ShortcutButton from '@csui/src/components/ShortcutButton.vue';
import EditShortcutButton from '@csui/src/components/EditShortcutButton';
import EditModeMixin from '@csui/src/utils/EditModeMixin';
import KeyboardShortcutParserMixin from '@csui/src/utils/KeyboardShortcutParserMixin';
import CommsMixin from '@csui/src/utils/CommsMixin';
import AspectRatioType from '@src/common/enums/AspectRatioType.enum';
export default {
components: {
Button,
ShortcutButton,
EditShortcutButton,
},
mixins: [
// ComputeActionsMixin,
EditModeMixin,
KeyboardShortcutParserMixin,
CommsMixin
],
props: [
'settings', // required for buttons and actions, which are global
'siteSettings',
'eventBus',
'isEditing',
'compact',
],
data() {
return {
AspectRatioType,
zoomAspectRatioLocked: true,
zoom: {
x: 0,
y: 0
},
// TODO: this should be mixin?
resizerConfig: {
crop: null,
stretch: null,
zoom: null,
pan: null
}
}
},
created() {
if (this.isEditing) {
this.enableEditMode();
}
},
watch: {
isEditing(newValue, oldValue) {
if (newValue) {
this.enableEditMode();
} else {
this.disableEditMode();
}
}
},
methods: {
getZoomForDisplay(axis) {
// 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.zoomAspectRatioLocked = !this.zoomAspectRatioLocked;
},
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(newZoom, axis) {
// we store zoom logarithmically on this component
if (!axis) {
this.zoom.x = newZoom;
} else {
this.zoom[axis] = newZoom;
}
// we do not use logarithmic zoom elsewhere, therefore we need to convert
newZoom = Math.pow(2, newZoom);
if (this.zoomAspectRatioLocked) {
this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom});
} else {
this.eventBus?.sendToTunnel('set-zoom', {zoom: {[axis ?? 'x']: newZoom}});
}
},
isActiveZoom(command) {
return false;
}
}
}
</script>
<style lang="scss" src="../../../../res/css/flex.scss" scoped></style>
<style lang="scss" src="@csui/src/res-common/panels.scss" scoped></style>
<style lang="scss" src="@csui/src/res-common/common.scss" scoped></style>
<style lang="scss" scoped>
.input-slider {
width: 100%;
box-sizing:border-box;
margin-right: 1rem;
margin-left: 0rem;
}
.slider-label {
margin-bottom: -0.5rem;
color: #aaa;
font-size: 0.75rem;
text-transform: uppercase;
}
</style>

View File

@ -1,296 +0,0 @@
<template>
<div class="flex flex-col" style="position: relative; width: 100%;">
<!-- 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">
UI is disabled for this site.
</div>
<div
class="flex flex-col field-group compact-form"
:class="{disabled: !siteSettings.data.enableUI.fullscreen}"
>
<div class="field disabled">
<div class="label">
Popup activator position:
</div>
<div class="select">
<select
v-model="settings.active.ui.inPlayer.popupAlignment"
@change="saveSettings()"
>
<option value="left">Left</option>
<option value="right">Right</option>
</select>
</div>
</div>
<div class="field">
<div class="label">
Activate in-player UI:
</div>
<div class="select">
<select
v-model="settings.active.ui.inPlayer.activation"
@change="saveSettings()"
>
<option value="player">
When mouse hovers over player
</option>
<option value="trigger-zone">
When mouse hovers over trigger zone
</option>
</select>
</div>
</div>
<div class="field" :class="{'disabled': settings.active.ui.inPlayer.activation !== 'trigger-zone'}">
<div class="label">Edit trigger zone:</div>
<button @click="startTriggerZoneEdit()">Edit</button>
</div>
<div class="field">
<div class="label">
Do not show in-player UI when video player is narrower than
</div>
<div class="input range-input">
<input
:value="settings.active.ui.inPlayer.minEnabledWidth"
class="slider"
type="range"
min="0"
max="1"
step="0.01"
@input="(event) => setPlayerRestrictions('minEnabledWidth', event.target.value)"
@change="(event) => saveSettings()"
>
<input
style="margin-right: 0.6rem;"
:value="ghettoComputed.minEnabledWidth"
@input="(event) => setPlayerRestrictions('minEnabledWidth', event.target.value, true)"
@change="(event) => saveSettings(true)"
>
<div class="unit">% of screen</div>
</div>
</div>
<div class="field">
<div class="label">
Do not show in-player UI when video player is shorter than
</div>
<div class="input range-input">
<input
:value="settings.active.ui.inPlayer.minEnabledHeight"
class="slider"
type="range"
min="0"
max="1"
step="0.01"
@input="(event) => setPlayerRestrictions('minEnabledHeight', event.target.value)"
@change="(event) => saveSettings()"
>
<input
style="margin-right: 0.6rem;"
:value="ghettoComputed.minEnabledHeight"
@input="(event) => setPlayerRestrictions('minEnabledHeight', event.target.value, true)"
@change="(event) => saveSettings(true)"
>
<div class="unit">% of screen</div>
</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>
</template>
<script>
import Button from '@csui/src/components/Button.vue'
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'
export default {
components: {
Button,
CropOptionsPanel,
StretchOptionsPanel,
ZoomOptionsPanel,
},
data() {
return {
ghettoComputed: { }
}
},
mixins: [
],
props: [
'settings', // required for buttons and actions, which are global
'siteSettings',
'eventBus',
],
mounted() {
this.ghettoComputed = {
minEnabledWidth: this.optionalToFixed(this.settings.active.ui.inPlayer.minEnabledWidth * 100, 0),
minEnabledHeight: this.optionalToFixed(this.settings.active.ui.inPlayer.minEnabledHeight * 100, 0),
}
},
methods: {
forcePositiveNumber(value) {
// Change EU format to US if needed
// | remove everything after second period if necessary
// | | | remove non-numeric characters
// | | | |
return value.replaceAll(',', '.').split('.', 2).join('.').replace(/[^0-9.]/g, '');
},
optionalToFixed(v, n) {
if ((`${v}`.split('.')[1]?.length ?? 0) > n) {
return v.toFixed(n);
}
return v;
},
setPlayerRestrictions(key, value, isTextInput) {
if (isTextInput) {
value = (+this.forcePositiveNumber(value) / 100);
}
if (isNaN(+value)) {
value = 0.5;
}
this.settings.active.ui.inPlayer[key] = value;
if (isTextInput) {
this.ghettoComputed[key] = this.optionalToFixed(value, 0);
} else {
this.ghettoComputed[key] = this.optionalToFixed(value * 100, 0);
}
},
saveSettings(forceRefresh) {
this.settings.saveWithoutReload();
if (forceRefresh) {
this.$nextTick( () => this.$forceRefresh() );
}
},
startTriggerZoneEdit() {
this.eventBus.send('start-trigger-zone-edit');
},
async openOptionsPage() {
BrowserDetect.runtime.openOptionsPage();
},
}
}
</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;
}
.trigger-zone-editor {
background-color: rgba(0,0,0,0.25);
padding-bottom: 2rem;
.field {
margin-bottom: -1em;
}
}
.disabled {
pointer-events: none;
/* color: #666; */
filter: contrast(50%) brightness(40%) grayscale(100%);
}
.compact-form {
> .field, > .field-group {
margin-top: 0;
margin-bottom: 0;
}
}
.keyboard-settings {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 1rem;
> * {
width: calc(33% - 0.5rem);
}
}
.mt2r {
margin-top: 2rem;
margin-bottom: 0.5rem;
}
.mth3 {
margin-top: 1.5rem;
}
</style>

View File

@ -1,30 +0,0 @@
<template>
<div class="flex flex-col">
<h1>Reset and backup</h1>
<p>
Pressing the button will reset settings to default without asking.
</p>
<button
class="danger"
@click="resetSettings"
>
Reset settings
</button>
</div>
</template>
<script>
export default {
props: {
settings: Object
},
methods: {
resetSettings() {
this.settings.active = JSON.parse(JSON.stringify(this.settings.default));
this.settings.saveWithoutReload();
}
}
}
</script>
<style lang="scss" src="../../res/css/flex.scss" scoped module></style>
<style lang="scss" src="@csui/src/res-common/panels.scss" scoped module></style>
<style lang="scss" src="@csui/src/res-common/common.scss" scoped module></style>

View File

@ -1,159 +0,0 @@
<template>
<div class="flex flex-col" style="position: relative; width: 100%;">
<!-- 'Change UI' options is a tiny bit in upper right corner. -->
<div
class="options-bar flex flex-row"
:class="{isEditing: editMode}"
>
<template v-if="editMode">
<div style="height: 100%; display: flex; flex-direction: column; justify-content: center; flex: 0 0; padding-right: 8px;">
<mdicon name="alert" size="32" />
</div>
<div class="flex-grow">
You are currently editing options and shortcuts.<br/>
<b>NOTE: changes will take effect after page reload.</b>
</div>
<div
class="flex-nogrow flex-noshrink"
@click="editMode = !editMode"
>
Exit edit mode
</div>
</template>
<template v-else>
<div class="flex-grow"></div>
<div
class=""
@click="editMode = !editMode"
>
Edit ratios and shortcuts
</div>
</template>
</div>
<!-- The rest of the tab is under 'edit ratios and shortcuts' row -->
<div class="flex flex-row flex-wrap" style="width: 100%">
<div class="flex flex-col">
<!-- CROP OPTIONS -->
<div v-if="settings" class="sub-panel">
<div class="flex flex-row">
<mdicon name="crop" :size="16" />
<h1>Crop video:</h1>
</div>
<CropOptionsPanel
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
:isEditing="editMode"
>
</CropOptionsPanel>
</div>
<!-- STRETCH OPTIONS -->
<div v-if="settings" class="sub-panel">
<div class="flex flex-row">
<mdicon name="stretch-to-page-outline" :size="32" />
<h1>Stretch video:</h1>
</div>
<StretchOptionsPanel
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
:isEditing="editMode"
></StretchOptionsPanel>
</div>
</div>
</div>
</div>
</template>
<script>
import ZoomOptionsPanel from './PanelComponents/VideoSettings/ZoomOptionsPanel.vue'
import CropOptionsPanel from './PanelComponents/VideoSettings/CropOptionsPanel'
import StretchOptionsPanel from './PanelComponents/VideoSettings/StretchOptionsPanel'
import Button from '../components/Button.vue'
import ShortcutButton from '../components/ShortcutButton';
import EditShortcutButton from '../components/EditShortcutButton';
import ComputeActionsMixin from '../mixins/ComputeActionsMixin';
import BrowserDetect from '../../../ext/conf/BrowserDetect';
import AlignmentOptionsControlComponent from './AlignmentOptionsControlComponent.vue';
import CommsMixin from '../utils/CommsMixin';
export default {
data() {
return {
exec: null,
scope: 'page',
editMode: false,
resizerConfig: {
crop: null,
stretch: null,
zoom: null,
pan: null
}
}
},
mixins: [
ComputeActionsMixin,
CommsMixin,
],
props: [
'settings', // required for buttons and actions, which are global
'siteSettings',
'frame',
'eventBus',
'site'
],
created() {
this.eventBus.subscribe(
'uw-config-broadcast',
{
source: this,
function: (config) => this.handleConfigBroadcast(config)
}
);
},
mounted() {
this.eventBus.sendToTunnel('get-ar');
},
destroyed() {
this.eventBus.unsubscribeAll(this);
},
components: {
ShortcutButton,
EditShortcutButton,
Button,
AlignmentOptionsControlComponent,
StretchOptionsPanel,
CropOptionsPanel, ZoomOptionsPanel
},
methods: {
async openOptionsPage() {
BrowserDetect.runtime.openOptionsPage();
},
}
}
</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;
}
</style>

View File

@ -1,247 +0,0 @@
<template>
<div class="action">
<div class="flex-row action-name-cmd-container">
<div class="">
</div>
<div class="flex action-name">
<span v-if="action.cmd && action.cmd.length > 1 || action.cmd[0].action === 'set-ar' && action.userAdded || (action.cmd[0].arg === AspectRatioType.Fixed)" class="icon red" @click="removeAction()">🗙</span>
<span v-else class="icon transparent">🗙</span> &nbsp; &nbsp;
<span class="icon" @click="editAction()">🖉</span> &nbsp; &nbsp;
{{action.name}}
</div>
</div>
<div class="command-details">
<div class="flex flex-col cmd-container cd-pad">
<div class="flex bold">
Command:
</div>
<div class="flex cmdlist">
{{parseCommand(action.cmd)}}
</div>
</div>
<!-- SCOPES -->
<div class="flex flex-col scopes-container cd-pad">
<div class="flex bold">Popup tabs:</div>
<!-- global scope -->
<div v-if="action.scopes.global"
class="flex flex-row scope-row"
>
<div class="flex scope-scope">
Global:
</div>
<div class="flex scope-row-label scope-visible">
Visible?&nbsp;<span class="scope-row-highlight">{{action.scopes.global.show ? 'Yes' : 'No'}}</span>
</div>
<div v-if="action.scopes.global.show"
class="flex scope-row-label scope-button-label"
>
Button label?&nbsp;
<span class="scope-row-highlight">
{{action.scopes.global.label ? action.scopes.global.label : (action.label ? action.label : '')}}
</span>
</div>
<div class="flex scope-row-label">
Keyboard shortcut:&nbsp;
<span class="scope-row-highlight">
{{action.scopes.global.shortcut ? parseActionShortcut(action.scopes.global.shortcut[0]) : 'None'}}
</span>
</div>
</div>
<!-- site scope -->
<div v-if="action.scopes.site"
class="flex flex-row scope-row"
>
<div class="flex scope-scope">
Site:
</div>
<div class="flex scope-row-label scope-visible">
Visible?&nbsp;<span class="scope-row-highlight">{{action.scopes.site.show ? 'Yes' : 'No'}}</span>
</div>
<div v-if="action.scopes.site.show"
class="flex scope-row-label scope-button-label"
>
Button label?&nbsp;
<span class="scope-row-highlight">
{{action.scopes.site.label ? action.scopes.site.label : (action.label ? action.label : '')}}
</span>
</div>
<div class="flex scope-row-label">
Keyboard shortcut:&nbsp;
<span class="scope-row-highlight">
{{action.scopes.site.shortcut ? parseActionShortcut(action.scopes.site.shortcut[0]) : 'None'}}
</span>
</div>
</div>
<!-- page scope -->
<div v-if="action.scopes.page"
class="flex flex-row scope-row"
>
<div class="flex scope-scope">
Page:
</div>
<div class="flex scope-row-label scope-visible">
Visible?&nbsp;<span class="scope-row-highlight">{{action.scopes.page.show ? 'Yes' : 'No'}}</span>
</div>
<div v-if="action.scopes.page.show"
class="flex scope-row-label scope-button-label"
>
Button label?&nbsp;
<span class="scope-row-highlight">
{{action.scopes.page.label ? action.scopes.page.label : (action.label ? action.label : '')}}
</span>
</div>
<div class="flex scope-row-label">
Keyboard shortcut:&nbsp;
<span class="scope-row-highlight">
{{action.scopes.page.shortcut ? parseActionShortcut(action.scopes.page.shortcut[0]) : 'None'}}
</span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import StretchType from '../enums/StretchType.enum';
import AspectRatioType from '../enums/AspectRatioType.enum';
import KeyboardShortcutParser from '../js/KeyboardShortcutParser';
export default {
data () {
return {
StretchType: StretchType,
AspectRatioType: AspectRatioType,
}
},
created () {
},
props: {
action: Object,
index: Number,
},
methods: {
parseActionShortcut(shortcut) {
return KeyboardShortcutParser.parseShortcut(shortcut);
},
parseCommand(cmd) {
let cmdstring = '';
for (const c of cmd) {
cmdstring += `${c.action} ${c.arg}${c.persistent ? ' persistent' : ''}; `;
}
return cmdstring;
},
editAction() {
this.$emit('edit');
},
removeAction() {
this.$emit('remove');
}
}
}
</script>
<style lang="scss" scoped>
@import '../../res/css/colors.scss';
.action {
cursor: pointer;
position: relative;
box-sizing: border-box;
}
.action .command-details {
height: 0px;
max-height: 0px;
transition: max-height 0.5s ease;
overflow: hidden;
transition: height 0.5s ease;
position: absolute;
top: 0;
right: 0;
width: 50%;
}
.action:hover .command-details {
height: auto;
max-height: 200px;
transition: max-height 0.5s ease;
}
.command-details {
position: absolute;
top: 0;
right: 0;
width: 50%;
}
.action-name-cmd-container, .p1rem {
padding: 1rem;
}
.cd-pad {
padding: 0.5em;
}
.action-name {
font-size: 1.5rem;
font-weight: 300;
color: $text-normal;
width: 50%;
}
.action-name:hover, .action:hover .action-name {
color: lighten($primary-color, 20%);
}
.red {
color: $primary-color !important;
}
.cmd-container {
width: 13.37rem;
}
.cmdlist {
font-family: 'Overpass Mono';
font-size: 0.9rem;
color: $text-dim;
}
.bold {
font-weight: 600;
}
.scope-scope {
width: 5rem;
text-align: right !important;
color: $secondary-color;
}
.scope-visible {
width: 7rem;
}
.scope-button-label {
width: 16rem;
}
.scope-row-label {
color: $text-dark;
}
.scope-row-highlight {
color: $text-normal;
}
.transparent {
opacity: 0;
}
</style>

View File

@ -1,76 +0,0 @@
<template>
<tr>
<td class="cmd monospace">{{parseCommand(action.cmd)}}</td>
<td class="">{{action.label ? action.label : ""}}</td>
<td class="shortcut center-text">{{parseActionShortcut(action)}}</td>
<td class="center-text">
<div class="checkbox-container">
<span class="checkbox"
:class="{'checkbox-checked': (action.shortcut && action.shortcut.length &&
(action.shortcut[0].onMouseMove || action.shortcut[0].onClick ||
action.shortcut[0].onScroll))}"
></span>
</div>
</td>
<td class="center-text">
<div class="checkbox-container">
<span class="checkbox"
:class="{'checkbox-checked': action.popup_global}"
>
</span>
</div>
</td>
<td class="center-text">
<div class="checkbox-container">
<span class="checkbox"
:class="{'checkbox-checked': action.popup_site}"
></span>
</div>
</td>
<td class="center-text">
<div class="checkbox-container">
<span class="checkbox"
:class="{'checkbox-checked': action.popup}"
></span>
</div>
</td>
<td class="center-text">
<div class="checkbox-container">
<span class="checkbox"
:class="{'checkbox-checked': action.ui}"
></span>
</div>
</td>
<td>{{action.ui_path ? action.ui_path : ""}}</td>
</tr>
</template>
<script>
import StretchType from '../enums/StretchType.enum';
import KeyboardShortcutParser from '../js/KeyboardShortcutParser'
export default {
data () {
return {
StretchType: StretchType
}
},
created () {
},
props: {
action: Object
},
methods: {
parseActionShortcut(action) {
return KeyboardShortcutParser.parseShortcut(action.shortcut[0]);
},
parseCommand(cmd) {
let cmdstring = '';
for(const c of cmd) {
cmdstring += `${c.action} ${c.arg}${c.persistent ? ' persistent' : ''}; `;
}
return cmdstring;
}
}
}
</script>

View File

@ -1,52 +0,0 @@
<template>
<div class="button center-text"
:class="{
'setting-selected': selected,
'no-pad': noPad,
}"
>
<mdicon v-if="icon" :name="icon" :size="iconSize ?? 24"></mdicon>
<div class="label">
{{label}}
</div>
</div>
</template>
<script>
export default {
props: {
fixedWidth: Boolean,
selected: Boolean,
label: String,
icon: String,
iconSize: Number,
noPad: Boolean
}
}
</script>
<style lang="scss" scoped>
.button {
display: flex;
flex-direction: row;
flex-wrap: none;
align-items: center;
.icon {
margin-right: 8px;
flex-grow: 0;
flex-shrink: 0;
}
.label {
text-align: center;
flex-grow: 1;
flex-shrink: 1;
}
}
.no-pad {
padding: 0.5rem 1rem !important;
}
</style>

View File

@ -1,107 +0,0 @@
<template>
<div v-if="popupVisible" class="popup">
<div class="popup-window">
<div
class="header"
:class="{
'danger': dialogType === 'danger',
'warning': dialogType === 'warning',
'info': dialogType === 'info',
'normal': dialogType === 'normal'
}"
>
{{ dialogText || 'Confirm action' }}
</div>
<div class="body">
{{ dialogText || 'Are you sure you want to do that?' }}
</div>
<div class="footer">
<button
class="button confirm"
:class="{
'danger': dialogType === 'danger',
'warning': dialogType === 'warning',
'info': dialogType === 'info',
'normal': dialogType === 'normal'
}"
@click="confirmAction"
>
{{ confirmText || 'Confirm' }}
</button>
<button class="button" @click="popupVisible = false">{{ cancelText || 'Cancel' }}</button>
</div>
</div>
</div>
<button
:class="[
{
'danger': dialogType === 'danger',
'warning': dialogType === 'warning',
},
btnClass
]"
@click="popupVisible = true"
>
<slot></slot>
</button>
</template>
<script>
export default {
data() {
return {
popupVisible: false,
}
},
props: [
'btnClass',
'dialogTitle',
'dialogText',
'confirmText',
'cancelText',
'dialogType'
],
methods: {
confirmAction() {
this.popupVisible = false;
this.$emit('onConfirmed');
}
}
}
</script>
<style lang="scss" scoped>
.popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
backdrop-filter: saturate(50%) brightness(50%) blur(1rem);
z-index: 99999;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.header {
font-size: 1.33rem;
color: #fff;
&.danger {
color: rgb(251, 107, 63);
font-weight: bold;
}
}
.body {
min-height: 5rem;
}
}
</style>

View File

@ -1,190 +0,0 @@
<template>
<div class="flex flex-row">
<div
class="flex-grow button"
@click="editShortcut()"
>
<template v-if="!editing">
{{shortcutDisplay}}
</template>
<template v-else>
{{currentKeypress ?? 'Press a key'}}
<input ref="input"
class="hidden-input"
@keyup.capture="keyup($event)"
@keydown.capture="keydown($event)"
@input.prevent=""
@blur="editing = false;"
>
</template>
</div>
<div class="button" @click="$emit('shortcutChanged', null)">
<mdicon name="delete"></mdicon>
</div>
</div>
</template>
<script>
import KeyboardShortcutParser from '../../../common/js/KeyboardShortcutParser';
export default {
props: {
shortcut: Object,
},
data() {
return {
currentKeypress: undefined,
currentKey: undefined,
editing: false,
}
},
computed: {
shortcutDisplay() {
if (!this.shortcut) {
return '(no shortcut)'
}
return KeyboardShortcutParser.parseShortcut(this.shortcut);
}
},
methods: {
editShortcut() {
this.editing = true;
this.currentKeypress = undefined;
this.currentKey = undefined;
// input doesn't exist now, but will exist on the next tick
this.$nextTick(()=> this.$refs.input.focus());
},
/**
* Updates currently pressed keypress for display
*/
keydown(event) {
// event.repeat is set to 'true' when key is being held down, but not on
// first keydown. We don't need to process subsequent repeats of a keypress
// we already processed.
if (event.repeat) {
return;
}
const shortcut = KeyboardShortcutParser.generateShortcutFromKeypress(event);
const fixedShortcut = this.handleModifierKeypress(shortcut);
if (this.currentKey === undefined) {
this.currentKey = fixedShortcut;
} else {
// here's a fun fact. Keydown doesn't do modifier keys the way we want
// notably, A-Z0-9 keys are returned without modifier state (all modifiers)
// are set to false in keydown events. That means we need to keep track of
// modifiers ourselves.
if (fixedShortcut.notModifier) {
this.currentKey.key = fixedShortcut.key;
this.currentKey.code = fixedShortcut.code;
} else {
this.currentKey = {
...fixedShortcut,
key: this.currentKey.key,
code: this.currentKey.code
}
}
}
// update display
this.currentKeypress = KeyboardShortcutParser.parseShortcut(this.currentKey);
},
/**
* Emits shortcutChanged when shortcut is considered changed
*/
keyup(event) {
const shortcut = KeyboardShortcutParser.generateShortcutFromKeypress(event);
const fixedShortcut = this.handleModifierKeypress(shortcut);
if (fixedShortcut.notModifier) {
this.editing = false;
this.$emit('shortcutChanged', this.currentKey);
} else {
// if none of the modifiers are pressed and if no other key is being held down,
// we need to reset label back to 'pls press key'
if (!fixedShortcut.altKey && !fixedShortcut.ctrlKey && !fixedShortcut.metaKey && !fixedShortcut.shiftKey && !fixedShortcut.code) {
this.currentKeypress = undefined;
this.currentKey = undefined;
} else {
this.currentKey = shortcut;
this.currentKeypress = KeyboardShortcutParser.parseShortcut(this.currentKey);
}
}
},
/**
* Handles current keypress if event.keyCode is a modifier key.
* Returns true if current event was a modifier key, false if
* if was a regular A-Z 0-9 key.
*/
handleModifierKeypress(event) {
const modifierPressed = event.type === 'keydown';
switch (event.code) {
case 'ShiftLeft':
case 'ShiftRight':
return {
...event,
key: '…',
code: null,
shiftKey: modifierPressed
}
case 'ControlLeft':
case 'ControlRight':
return {
...event,
key: '…',
code: null,
controlKey: modifierPressed
};
case 'MetaLeft':
case 'MetaRight':
return {
...event,
key: '…',
code: null,
metaKey: modifierPressed
};
case 'AltLeft':
case 'AltRight':
return {
...event,
key: '…',
code: null,
altKey: modifierPressed
};
}
return {
...event,
notModifier: true,
}
}
}
}
</script>
<style lang="scss" src="@csui/src/res-common/common.scss" scoped></style>
<style lang="scss" scoped>
.center-text {
text-align: center;
}
.dark {
opacity: 50%;
}
.hidden-input {
position: absolute;
z-index: -9999;
opacity: 0;
}
</style>

View File

@ -1,120 +0,0 @@
<template>
<div class="flex flex-row w-full">
<button
v-if="editorMode === 'tree'"
@click="() => setEditorMode('text')"
>
Edit as text
</button>
<button
v-else
@click="() => setEditorMode('tree')"
>
Show as tree
</button>
</div>
<div ref="refContainer" class="w-full h-full"></div>
</template>
<script>
import { createJSONEditor, Mode } from "vanilla-jsoneditor";
export default {
props: {
modelValue: Object // v-model binding
},
emits: ["update:modelValue"],
data() {
return {
refContainer: null,
editor: null,
editorMode: Mode.tree,
};
},
watch: {
modelValue: {
deep: true,
handler(newValue) {
if (this.editor) {
this.editor.updateProps({ content: { json: newValue || {} } });
}
}
}
},
mounted() {
if (this.$refs.refContainer) {
this.editor = createJSONEditor({
target: this.$refs.refContainer,
props: {
mode: Mode.tree,
content: { json: this.modelValue || {} },
onChange: (updatedContent) => {
this.$emit("update:modelValue", updatedContent.json);
},
onRenderContextMenu: false,
navigationBar: false,
statusBar: false,
mainMenuBar: false
}
});
}
},
methods: {
setEditorMode(mode) {
this.editorMode = mode
this.editor.updateProps({ mode });
}
},
beforeUnmount() {
if (this.editor) {
this.editor.destroy();
this.editor = null;
}
}
};
</script>
<style lang="scss">
:root {
--jse-panel-background: #111;
--jse-background-color: #000;
--jse-text-color: #ccc;
--jse-key-color: #8c8bef;
--jse-selection-background-color: rgba(255, 171, 102, 0.177);
--jse-context-menu-pointer-background: rgba(255, 171, 102, 0.177);
--jse-delimiter-color: #fa6;
--jse-value-color-boolean: rgb(132, 132, 137);
}
.jse-boolean-toggle[role="checkbox"] {
&[aria-checked="true"] {
color: rgb(218, 244, 238);
}
&[aria-checked="false"] {
border: 0px transparent;
outline: 0px transparent;
color: rgb(241, 107, 25);
background-color: rgb(241, 107, 25);
position: relative;
&:after {
position: absolute;
content: '×';
top: 0;
left: 0;
width: 100%;
height: 100%;
color: black;
text-align: center;
font-weight: bold;
}
}
}
</style>

View File

@ -1,28 +0,0 @@
<template>
<div class="root">
<template v-for="key in json"
>
<div class="element" :key="key">
<span class="json-key">"{{key}}"</span>:
<template v-if="Array.isArray(json[key])">
[<br/>
<div v-for="index of json[key]"
class="json-table-row"
:key="index"
>
</div>
]
</template>
</div>
</template>
</div>
</template>
<script>
export default {
name: "json-explorer",
props: {
json: Object
}
}
</script>

View File

@ -1,104 +0,0 @@
<template>
<div
v-if="message"
class="popup-overlay"
:class="{'dim': dimOverlay}"
>
<div class="popup-content" >
<div v-if="title" class="header" :class="type">
{{title}}
</div>
<p>
{{ message }}
</p>
<div class="flex flex-row justify-end">
<button
class="primary"
v-if="confirmButtonText"
:class="type"
@click="$emit('onConfirm')"
>
{{ confirmButtonText }}
</button>
<button
:class="type"
@click="$emit('onCancel')"
>
{{ cancelButtonText }}
</button>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
// onConfirm: {
// type: Function,
// required: false
// },
// onCancel: {
// type: Function,
// default: () => this.$emit('close')
// },
title: {
type: String,
required: false
},
message: {
type: String,
required: false, // technically prolly should be true, but we don't have to guard against skill issue
},
dimOverlay: {
type: Boolean,
default: true
},
type: {
type: String,
default: 'info'
},
confirmButtonText: {
type: String,
required: false,
},
cancelButtonText: {
type: String,
default: 'Cancel'
}
}
}
</script>
<style lang="scss" scoped>
.popup-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
backdrop-filter: saturate(50%) brightness(50%) blur(1rem);
z-index: 99999;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.header {
font-size: 1.33rem;
color: #fff;
&.danger {
color: rgb(251, 107, 63);
font-weight: bold;
}
}
.body {
min-height: 5rem;
}
}
</style>

View File

@ -1,85 +0,0 @@
<template>
<div class="flex flex-col h100">
<div class="row">
<span class="label">Ultrawidify version:</span><br/> {{addonVersion}}
</div>
<div class="row">
<span class="label">Having an issue?</span><br/> Report <strike>undocumented features</strike> bugs using one of the following options (in order of preference):
<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">{{gmailLink}}</a></li>
</ul>
</div>
</div>
</template>
<script>
// import Comms from '@src/ext/lib/comms/Comms';
// import ShortcutButton from '@src/common/components/ShortcutButton';
import BrowserDetect from '@src/ext/conf/BrowserDetect';
export default {
components: {
},
data() {
return {
// reminder webextension-polyfill doesn't seem to work in vue!
addonVersion: BrowserDetect.firefox ? chrome.runtime.getManifest().version : chrome.runtime.getManifest().version,
loggingEnabled: false,
loggerSettings: '',
loggerSettingsError: false,
lastLoadedLoggerSettings: undefined,
mailtoLink: '',
showEasterEgg: false,
gmailLink: '',
}
},
async created() {
const b64="dGFtaXVzLmhhbkBnbWFpbC5jb20";
this.gmailLink = atob(b64);
const messageTemplate = encodeURIComponent(
`Describe your issue in more detail. In case of misaligned videos, please provide screenshots. When reporting\
issues with autodetection not detecting aspect ratio correctly, please provide a link with timestamp to the\
problematic video at the time where the problem happens. You may delete this paragraph, as it's only a template.
Extension info (AUTOGENERATED DO NOT CHANGE OR REMOVE):
* Build: ${BrowserDetect.processEnvBrowser}-${this.addonVersion}-stable
Browser-related stuff (please ensure this section is correct):
* User Agent string: ${window.navigator.userAgent}
* vendor: ${window.navigator.vendor}
* Operating system: ${window.navigator.platform}
`
);
this.mailtoLink = `mailto:${this.gmailLink}?subject=%5BUltrawidify%5D%20ENTER%20SUMMARY%20OF%20YOUR%20ISSUE%20HERE&body=${messageTemplate}`;
},
methods: {
async updateLoggerSettings(allowLogging) {
// this.loggingEnabled = allowLogging;
// if (allowLogging) {
// const parsedSettings = JSON.parse(this.loggerSettings);
// Logger.saveConfig({
// allowLogging: allowLogging,
// timeout: parsedSettings.timeout || undefined,
// fileOptions: parsedSettings.fileOptions || {},
// consoleOptions: parsedSettings.consoleOptions || {},
// });
// } else {
// // we need to save logger settings because logging is triggered by allow logging
// Logger.saveConfig({allowLogging: allowLogging, ...lastLoadedLoggerSettings});
// }
},
// showLogger() {
// Comms.sendMessage({cmd: 'show-logger', forwardToActive: true});
// },
// sendMark() {
// this.showEasterEgg = !this.showEasterEgg;
// },
// hideLogger() {
// Comms.sendMessage({cmd: 'hide-logger', forwardToActive: true});
// }
}
}
</script>

View File

@ -1,210 +0,0 @@
<template>
<div class="flex flex-col relative h-full" style="padding-bottom: 20px">
<!--
Extension is disabled for a given site when it's disabled in full screen, since
current settings do not allow the extension to only be disabled while in full screen
-->
<template v-if="siteSettings.isEnabledForEnvironment(false, true) === ExtensionMode.Disabled && !enabledHosts?.length">
<div class="h-full flex flex-col items-center justify-center" style="margin-top: 8rem">
<div class="info">
Extension is not enabled for this site.
</div>
<div class="text-center">
<p>Please enable extension for this site.</p>
</div>
<div>
<button
class="flex flex-row items-center"
style="background-color: transparent; padding: 0.5rem 1rem; margin-top: 1rem; border: 1px solid #fa68;"
@click="openSettings()"
>
Open settings <mdicon style="margin-left: 0.5rem;" name="open-in-new" size="16"></mdicon>
</button>
</div>
<div class="text-center">
<p><small>Extension may work on content embedded from other sites.</small></p>
</div>
</div>
</template>
<template v-else>
<div
v-if="siteSettings.isEnabledForEnvironment(false, true) === ExtensionMode.Disabled"
class="warning-compact"
>
<div class="w-full flex flex-row">
<div class="grow">
<b>Extension is disabled for this site.</b>
</div>
<div>
<button
class="flex flex-row items-center"
style="border: 1px solid black; background-color: transparent; color: black; padding: 0.25rem 0.5rem; margin-top: -0.25rem; margin-right: -0.5rem;"
@click="openSettings()"
>
Open settings <mdicon style="margin-left: 0.5rem;" name="open-in-new" size="16"></mdicon>
</button>
</div>
</div>
<small>Controls will only work on content embedded from the following sites:</small><br/>
<div class="w-full flex flex-row justify-center">
<span v-for="host of enabledHosts" :key="host" class="website-name">{{host}}</span>
</div>
</div>
<div class="flex flex-row">
<mdicon name="crop" :size="16" />&nbsp;&nbsp;
<span>CROP</span>
</div>
<div
style="margin-top: -0.69rem; margin-bottom: 0.88rem;"
>
<CropOptionsPanel
:settings="settings"
:eventBus="eventBus"
:siteSettings="siteSettings"
:isEditing="false"
:compact="true"
>
</CropOptionsPanel>
</div>
<div class="flex flex-row">
<mdicon name="crop" :size="16" />&nbsp;&nbsp;
<span>STRETCH</span>
</div>
<div
style="margin-top: -0.69rem; margin-bottom: 0.88rem;"
>
<StretchOptionsPanel
:settings="settings"
:eventBus="eventBus"
:siteSettings="siteSettings"
:isEditing="false"
:compact="true"
></StretchOptionsPanel>
</div>
<div class="flex flex-row">
<mdicon name="crop" :size="16" />&nbsp;&nbsp;
<span>ZOOM</span>
</div>
<div
style="margin-top: -0.69rem; margin-bottom: 0.88rem;"
>
<ZoomOptionsPanel
:settings="settings"
:eventBus="eventBus"
:siteSettings="siteSettings"
:isEditing="false"
:compact="true"
>
</ZoomOptionsPanel>
</div>
<div class="flex flex-row">
<mdicon name="crop" :size="16" />&nbsp;&nbsp;
<span>ALIGN</span>
</div>
<div
style="margin-bottom: 0.88rem;"
>
<AlignmentOptionsControlComponent
:eventBus="eventBus"
:large="true"
> </AlignmentOptionsControlComponent>
</div>
</template>
</div>
</template>
<script>
import CropOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/CropOptionsPanel';
import StretchOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/StretchOptionsPanel.vue';
import ZoomOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/ZoomOptionsPanel.vue';
import ExtensionMode from '@src/common/enums/ExtensionMode.enum.ts';
import AlignmentOptionsControlComponent from '@csui/src/PlayerUiPanels/AlignmentOptionsControlComponent.vue';
import { SiteSettings } from '../../../../ext/lib/settings/SiteSettings';
export default {
components: {
CropOptionsPanel,
StretchOptionsPanel,
ZoomOptionsPanel,
AlignmentOptionsControlComponent
},
mixins: [
],
props: [
'site',
'settings',
'siteSettings',
'eventBus',
'hosts'
],
data() {
return {
exec: null,
ExtensionMode: ExtensionMode,
enabledHosts: [],
};
},
watch: {
hosts(val) {
this.filterActiveSites(val);
}
},
created() {
this.eventBus.subscribe(
'uw-config-broadcast',
{
source: this,
function: (config) => this.handleConfigBroadcast(config)
}
);
this.filterActiveSites(this.hosts);
},
mounted() {
this.eventBus.sendToTunnel('get-ar');
},
destroyed() {
this.eventBus.unsubscribeAll(this);
},
methods: {
filterActiveSites(val) {
this.enabledHosts = [];
for (const host of val) {
const siteSettings = new SiteSettings(this.settings, {site: host});
if (siteSettings.isEnabledForEnvironment(false, true) === ExtensionMode.Enabled) {
this.enabledHosts.push(host);
}
}
},
openSettings() {
this.eventBus.send('open-popup-settings', {tab: 'extensionSettings'})
}
}
}
</script>
<style lang="scss" scoped>
.warning-compact {
background-color: #d6ba4a;
color: #000;
padding: 0.5rem 1rem;
margin-top: -0.5rem;
margin-bottom: 0.5rem;
.website-name {
font-size: 0.85rem;
&:not(:last-of-type)::after {
content: ','
}
}
}
</style>

View File

@ -1,423 +0,0 @@
<template>
<div class="flex flex-col" style="padding-bottom: 20px; position: relative">
<!-- <div class="">
<div class="label">Player picker</div>
<div class="desc">
If extension doesn't detect player correctly, you can override it.
<small>(NOTE: this currently doesn't work for embedded videos)</small>
</div>
<div>Meaning of outlines:</div>
<div class="flex flex-row flex-wrap">
<div class="pp_video flex flex-nogrow"><code>&lt;video&gt;</code>&nbsp;element</div>
<div class="pp_current flex flex-nogrow">Selected and not matched</div>
<div class="pp_matched flex flex-nogrow">Elements that match query selector</div>
<div class="pp_current_matched">Selected and matched, selector ok</div>
<div class="pp_match_children">Selected and matched, selector too vague</div>
</div>
<div class="flex flex-row">
<ShortcutButton label="Move up"
/>
<ShortcutButton label="Move down"
/>
</div>
<div class="flex flex-row flex-wrap">
<QsElement selector="#test_id" />
<QsElement selector=".test_class" />
<template v-for="qse of currentElementQs" >
<QsElement :selector="qse" :key="qse" />
</template>
</div>
</div> -->
<div class="label">
Player detection settings<br/><small>for {{site}}</small>
</div>
<div class="description">
If extension doesn't work, you can help a little by telling it where to look for the player.<br/>
</div>
<div class="">
<div class="">
<input :checked="playerManualQs"
@change="togglePlayerManualQs"
type="checkbox"
/> Manually specify player
<span v-if="playerManualQs" class="small-samecolor">
&nbsp;
<a
v-if="playerByNodeIndex"
@click="toggleByNodeIndex"
>(use advanced options)</a>
<a
v-if="!playerByNodeIndex"
@click="toggleByNodeIndex"
>(use basic options)</a>
</span>
</div>
<div v-if="playerManualQs">
<div v-if="!playerByNodeIndex" class="flex flex-col">
<div class="">Query selectors for player:</div>
<input type="text"
v-model="playerQs"
@change="updatePlayerQuerySelector"
@blur="updatePlayerQuerySelector"
:disabled="playerByNodeIndex || !playerManualQs"
/>
</div>
<div v-if="playerByNodeIndex" class="flex flex-row">
<div>
Player is n-th parent of video:
</div>
<input v-model="playerParentNodeIndex"
:disabled="!playerManualQs"
@change="updatePlayerParentNodeIndex"
@blur="updatePlayerParentNodeIndex"
type="number"
/>
</div>
<div class="description">
<small>
<b>Hint:</b> Player is a HTML element that represents the portion of the page you expect the video to play in.
The correct value for n-th player value should normally between 1-10 (depends on the site). If none of these
values works, then the site probably has a different issue.
Note that you need to save settings and reload the page every time you change the number.
</small>
</div>
</div>
<div class="flex flex-row">
<input :checked="usePlayerAr"
@change="toggleUsePlayerAr"
type="checkbox"
/>
<div>
Do not use monitor AR in fullscreen
</div>
</div>
<div class="description">
<small>
<b>Hint:</b> When in full screen, the extension will assume that player element is as big as your screen.
You generally want to keep this option off, unless you like to browse in fullscreen a lot.
</small>
</div>
</div>
<div class="label">
Additional css<br/><small>for {{site}}</small>
</div>
<div class="description">
This css will be inserted into webpage every time it loads.
</div>
<div class="flex flex-col">
<textarea
v-model="playerCss"
@change="updatePlayerCss"
@blur="updatePlayerCss"
>
</textarea>
</div>
<div class="label">
Video detection settings<br/><small>for {{site}}</small>
</div>
<div class="description">Video is just the moving picture bit without the player.</div>
<div class="">
<div class="">
<input :checked="!videoManualQs"
@change="toggleVideoManualQs"
type="checkbox"
/> Detect automatically
</div>
<div class="flex flex-col">
<div class="flex label-secondary form-label">Query selectors</div>
<input type="text"
v-model="videoQs"
:disabled="!videoManualQs"
@change="updateVideoQuerySelector"
@blur="updateVideoQuerySelector"
/>
</div>
<div class="flex flex-col">
<div class="flex label-secondary form-label">Additional style for video element</div>
<input type="text"
v-model="videoCss"
@change="updateVideoCss"
@blur="updateVideoCss"
/>
</div>
<div class="label">
Browser quirk mitigations
</div>
<div class="description">
Sometimes, the extension may misbehave as a result of issues and bugs present in your browser, operating system or your GPU driver.
Some of the issues can be fixed by limiting certain functionalities of this addon.
</div>
<div class="flex flex-col">
<div
v-if="BrowserDetect.anyChromium"
class="workaround flex flex-col"
>
<div class="flex label-secondary form-label">
<input :checked="settings?.active?.mitigations?.zoomLimit?.enabled"
@change="setMitigation(['zoomLimit', 'enabled'], $event.target.checked)"
type="checkbox"
/> Limit zoom.
</div>
<div class="flex flex-row">
<div class="label-secondary form-label">
<small>Limit zoom to % of width (1=100%):</small>
</div>
<input type="number"
:value="settings?.active?.mitigations?.zoomLimit?.limit || 0.997"
step="0.001"
min="0.5"
@change="setMitigation(['zoomLimit', 'limit'], +$event.target.value)"
@blur="updateVideoQuerySelector"
:disabled="!settings?.active?.mitigations?.zoomLimit?.enabled"
/>
</div>
<div class="flex label-secondary form-label">
<input :checked="settings?.active?.mitigations?.zoomLimit?.fullscreenOnly"
@change="setMitigation(['zoomLimit', 'fullscreenOnly'], $event.target.checked)"
:disabled="!settings?.active?.mitigations?.zoomLimit?.enabled"
type="checkbox"
/> Limit zoom only while in fullscreen
</div>
<div class="description">
<small>
<b>Fix for:</b> Chrome and Edge used to have a bug where videos would get incorrectly stretched when zoomed in too far.
The issue only appeared in fullscreen, on nVidia GPUs, and with hardware acceleration enabled. While this option only
needs to be applied in fullscreen, fullscreen detection in Chrome can be a bit unreliable (depending on your OS and/or
display scaling settings). <a href="https://stuff.tamius.net/sacred-texts/2021/02/01/ultrawidify-and-chrome-2021-edition/" target="_blank">More about the issue</a>.
</small>
</div>
</div>
</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
</div>
<div id="save-banner-observer-bait">
&nbsp;
</div>
<div
id="save-banner"
class="save-banner"
>
<div class="button">Save settings</div>
</div>
</div>
</template>
<script>
import ShortcutButton from '../../common/components/ShortcutButton.vue';
import QsElement from '../../common/components/QsElement.vue';
import QuerySelectorSetting from '../../common/components/QuerySelectorSetting.vue';
import ExtensionMode from '../../common/enums/ExtensionMode.enum';
import VideoAlignmentType from '../../common/enums/VideoAlignmentType.enum';
import StretchType from '../../common/enums/StretchType.enum';
import BrowserDetect from '../../ext/conf/BrowserDetect';
export default {
components: {
QuerySelectorSetting,
ShortcutButton,
QsElement,
},
data() {
return {
videoManualQs: false,
videoQs: '',
videoCss: '',
playerManualQs: false,
playerQs: '',
playerCss: '',
playerByNodeIndex: true,
playerParentNodeIndex: undefined,
usePlayerAr: false,
BrowserDetect
};
},
props: {
site: String,
siteSettings: Object,
},
created() {
try {
this.videoManualQs = this.siteSettings.data.currentDOMConfig?.elements?.video?.manual ?? this.videoManualQs;
this.videoQs = this.siteSettings.data.currentDOMConfig?.elements?.video?.querySelectors;
this.videoCss = this.siteSettings.data.currentDOMConfig?.elements?.video?.nodeCss;
} catch (e) {
// that's here just in case relevant settings for this site don't exist yet
}
try {
this.playerManualQs = this.siteSettings.data.currentDOMConfig?.elements?.player?.manual ?? this.playerManualQs;
this.playerQs = this.siteSettings.data.currentDOMConfig?.elements?.player?.querySelectors;
this.playerByNodeIndex = !this.siteSettings.data.currentDOMConfig?.elements?.player?.querySelectors || this.playerByNodeIndex;
this.playerParentNodeIndex = this.siteSettings.data.currentDOMConfig?.elements?.player?.index;
// this.usePlayerAr = this.settings.active.sites[this.site]?.usePlayerArInFullscreen;
} catch (e) {
// that's here just in case relevant settings for this site don't exist yet
}
try {
this.playerCss = this.settings.active.sites[this.site].css || '';
} catch (e) {
// that's here just in case relevant settings for this site don't exist yet
}
},
mounted() {
this.createObserver();
},
methods: {
createObserver() {
const saveButtonBait = document.getElementById('save-banner-observer-bait');
const saveButton = document.getElementById('save-banner');
const observer = new IntersectionObserver(
([e]) => {
saveButton.classList.toggle('floating', e.intersectionRatio < 0.95);
},
{threshold: [0, 0.5, 0.9, 0.95, 1]}
);
observer.observe(saveButtonBait);
},
updateVideoQuerySelector() {
this.siteSettings.set('currentDOMConfig')
this.ensureSettings('video');
this.settings.active.sites[this.site].DOM.video.querySelectors = this.videoQs;
this.settings.save();
},
updateVideoCss() {
this.ensureSettings('video');
this.settings.active.sites[this.site].DOM.video.additionalCss = this.videoCss;
this.settings.save();
},
updatePlayerQuerySelector() {
this.ensureSettings('player');
this.settings.active.sites[this.site].DOM.player.querySelectors = this.playerQs;
this.settings.save();
},
updatePlayerCss() {
this.ensureSettings('player');
this.settings.active.sites[this.site].css = this.playerCss;
this.settings.save();
},
updatePlayerParentNodeIndex() {
this.ensureSettings('player');
this.settings.active.sites[this.site].DOM.player.videoAncestor = this.playerParentNodeIndex;
this.settings.save();
},
toggleVideoManualQs() {
this.ensureSettings('video');
this.videoManualQs = !this.videoManualQs;
this.settings.active.sites[this.site].DOM.video.manual = this.videoManualQs;
this.settings.save();
},
togglePlayerManualQs() {
this.ensureSettings('player');
this.playerManualQs = !this.playerManualQs;
this.settings.active.sites[this.site].DOM.player.manual = this.playerManualQs;
this.settings.save();
},
toggleByNodeIndex() {
this.ensureSettings('player');
this.playerByNodeIndex = !this.playerByNodeIndex;
this.settings.active.sites[this.site].DOM.player.useRelativeAncestor = this.playerByNodeIndex;
this.settings.save();
},
toggleUsePlayerAr() {
this.ensureSettings('player');
this.usePlayerAr = !this.usePlayerAr;
this.settings.active.sites[this.site].usePlayerArInFullscreen = this.usePlayerAr;
this.settings.save();
},
setMitigation(mitigation, value) {
// ensure mitigations object exists.
// it may not exist in the settings on first load
if (! this.settings.active.mitigations) {
this.settings.active.mitigations = {};
}
if (Array.isArray(mitigation)) {
let currentMitigationsParent = this.settings.active.mitigations;
for (let i = 0; i < mitigation.length - 1; i++) {
if (!currentMitigationsParent[mitigation[i]]) {
currentMitigationsParent[mitigation[i]] = {};
}
currentMitigationsParent = currentMitigationsParent[mitigation[i]];
}
currentMitigationsParent[mitigation[mitigation.length - 1]] = value;
} else {
this.settings.active.mitigations[mitigation] = value;
}
this.settings.save();
}
}
}
</script>
<style lang="scss">
@import '../../res/css/colors.scss';
.pp_video {
margin: 2px;
padding: 5px;
border: 1px solid #00f;
}
.pp_current {
margin: 2px;
padding: 5px;
border: 1px solid #88f;
}
.pp_matched {
margin: 2px;
padding: 5px;
border: 1px dashed #fd2;
}
.pp_current_matched {
margin: 2px;
padding: 2px;
border: 2px solid #027a5c;
}
.pp_match_children {
margin: 2px;
padding: 2px;
border: 2px solid #f00;
}
.save-banner {
display: block;
position: sticky;
bottom: 0px;
left: 0px;
right: 0px;
background-color: #131313;
text-align: center;
&.floating {
box-shadow: 0 2rem 3rem 1rem $selected-color;
}
}
.small-samecolor {
font-size: 0.8em;
}
</style>

View File

@ -1,215 +0,0 @@
<template>
<div class="flex flex-row" style="width: 250px;">
<div class="flex-grow">
Custom zoom
</div>
<div class="flex flex-row">
<Button
v-if="zoomAspectRatioLocked"
icon="lock"
:iconSize="16"
:fixedWidth="true"
:noPad="true"
@click="toggleZoomAr()"
>
</Button>
<Button
v-else
icon="lock-open-variant"
:iconSize="16"
:fixedWidth="true"
:noPad="true"
@click="toggleZoomAr()"
>
</Button>
<Button
icon="restore"
:iconSize="16"
:noPad="true"
@click="resetZoom()"
></Button>
</div>
</div>
<template v-if="zoomAspectRatioLocked">
<div class="input range-input no-bg">
<input
type="range"
class="slider"
min="-1"
max="3"
step="0.01"
:value="zoom.x"
@input="changeZoom($event.target.value)"
/>
<input
class="disabled"
style="width: 2rem;"
:value="getZoomForDisplay('x')"
/>
</div>
</template>
<template v-else>
<div class="top-label">Horizontal zoom:</div>
<div class="input range-input no-bg">
<input
type="range"
class="slider"
min="-1"
max="3"
step="0.01"
:value="zoom.x"
@input="changeZoom($event.target.value, 'x')"
/>
<input
class="disabled"
style="width: 2rem;"
:value="getZoomForDisplay('x')"
/>
</div>
<div class="top-label">Vertical zoom:</div>
<div class="input range-input no-bg">
<input
type="range"
class="slider"
min="-1"
max="3"
step="0.01"
:value="zoom.y"
@input="changeZoom($event.target.value, 'y')"
/>
<input
class="disabled"
style="width: 2rem;"
:value="getZoomForDisplay('y')"
/>
</div>
</template>
</template>
<script>
import Button from '@csui/src/components/Button.vue';
import * as _ from 'lodash';
export default {
components: {
Button,
},
mixins: [
],
props: [
'settings', // required for buttons and actions, which are global
'eventBus'
],
data() {
return {
zoomAspectRatioLocked: true,
zoom: {
x: 0,
y: 0
},
// TODO: this should be mixin?
resizerConfig: {
crop: null,
stretch: null,
zoom: null,
pan: null
},
pollingInterval: undefined,
debouncedGetEffectiveZoom: undefined,
}
},
created() {
this.eventBus?.subscribeMulti(
{
'announce-zoom': {
function: (data) => {
this.zoom = {
x: Math.log2(data.x),
y: Math.log2(data.y)
};
}
}
},
this
);
this.debouncedGetEffectiveZoom = _.debounce(
() => {
this.getEffectiveZoom();
},
250
),
this.getEffectiveZoom();
this.pollingInterval = setInterval(this.debouncedGetEffectiveZoom, 2000);
},
destroyed() {
this.eventBus.unsubscribe(this);
clearInterval(this.pollingInterval);
},
methods: {
getEffectiveZoom() {
this.eventBus?.sendToTunnel('get-effective-zoom', {});
},
getZoomForDisplay(axis) {
// 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.zoomAspectRatioLocked = !this.zoomAspectRatioLocked;
},
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(newZoom, axis, isLinear) {
if (isNaN(+newZoom)) {
return;
}
let logZoom, linZoom;
if (isLinear) {
newZoom /= 100;
logZoom = Math.log2(newZoom);
linZoom = newZoom;
} else {
logZoom = newZoom;
linZoom = Math.pow(2, newZoom);
}
// we store zoom logarithmically on this component
if (!axis) {
this.zoom.x = logZoom;
} else {
this.zoom[axis] = logZoom;
}
// we do not use logarithmic zoom elsewhere, therefore we need to convert
if (this.zoomAspectRatioLocked) {
this.eventBus?.sendToTunnel('set-zoom', {zoom: linZoom});
} else {
this.eventBus?.sendToTunnel('set-zoom', {zoom: {[axis ?? 'x']: linZoom}});
}
},
}
}
</script>
<style lang="scss" src="@csui/res/css/flex.scss" scoped></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>

View File

@ -1,9 +0,0 @@
$warning-color: #d6ba4a;
$primary: rgb(255, 147, 85);
$primaryBrighter: rgb(255, 174, 127);
$primaryBg: rgba(54, 31, 21, 0.5);
$blackBg: rgb(11,11,11);
$normalTransparentOpacity: 0.5;
$hoverTransparentOpacity: 0.9;

View File

@ -1,263 +0,0 @@
@import './_variables.scss';
div, p, span {
font-size: 16px;
}
.disabled {
pointer-events: none;
/* color: #666; */
filter: contrast(50%) brightness(40%) grayscale(100%);
}
.warning-box {
display: flex;
flex-direction: row;
align-items: center;
border: 1px solid $warning-color;
color: $warning-color;
margin: 0px 16px;
padding: 16px;
font-size: 16;
border-radius: 2px;
*:not(:first-child) {
margin-left: 24px;
}
}
h1, h2, h3 {
font-family: 'Overpass' !important;
font-weight: 100 !important;
margin: 0;
padding: 0;
}
button, .button {
background-color: rgba($blackBg, $normalTransparentOpacity);
padding: 0.5rem 2rem;
margin: 3px;
color: #aaa;
border: 1px solid transparent;
user-select: none !important;
&:hover {
color: #fff;
background-color: rgba($blackBg, $hoverTransparentOpacity);
border-bottom: 1px solid rgba($primary, 0.5);
}
&.active {
color: $primary;
background-color: $primaryBg;
border-color: rgba($primary, .5);
}
&.primary {
background-color: #fa6;
color: #000;
}
&.danger {
background-color: #ff2211 !important;
color:#000;
}
&.disabled {
filter: saturate(0%);
}
}
.b3 {
margin: 0.25rem;
padding: 0.5rem 2rem;
}
.input, .range-input {
background-color: rgba($blackBg, $normalTransparentOpacity);
border: 1px solid transparent;
border-bottom: 1px solid rgba(255,255,255,0.5);
padding-top: 0.25rem;
padding-bottom: 0.25rem;
position: relative;
&:active, &:focus, &:focus-within {
border-bottom: 1px solid rgba($primary, 0.5);
}
&.no-bg {
background-color: transparent;
border-color: transparent;
}
input {
width: 100%;
outline: none;
border: 1px solid transparent;
background-color: transparent;
color: #fff;
}
.unit {
position: absolute;
right: 0px;
pointer-events: none;
opacity: 0.69;
font-size: 0.8rem;
top: 0;
transform: translateY(69%);
}
}
.range-input {
display: flex;
flex-direction: row;
* {
margin-left: 0.5rem;
margin-right: 0.5rem;
}
input {
max-width: 5rem;
}
input[type=range] {
max-width: none;
}
}
.field {
display: flex;
flex-direction: row;
box-sizing: border-box;
width: 100%;
max-width: 100%;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
padding-top: 0.5rem;
align-items: center;
&.l2 {
margin-left: 4rem;
}
.label {
flex: 0 0 25%;
min-width: 16rem;
text-align: right;
padding-right: 1rem;
.color-emphasis {
color: #fa6;
}
.sub-label {
font-size: 0.9em;
opacity: 0.69;
}
}
.input, .range-input {
flex-grow: 1;
flex-shrink: 1;
max-width: 24rem;
min-width: 16rem;
}
.has-hint {
display: flex;
flex-direction: column;
}
.select {
flex-grow: 1;
flex-shrink: 1;
min-width: 12px;
max-width: 24rem;
select {
background: rgba($blackBg, $hoverTransparentOpacity);
min-width: 12px;
max-width: 100%;
color: #fff;
border: 1px solid rgba(255, 171, 102, 0.42);
padding: 0.5rem 1rem 0.25rem;
outline: none;
font: inherit;
font-size: inherit;
}
}
}
.hint {
font-size: 0.8rem;
opacity: 0.7;
margin-top: 0.25rem;
margin-bottom: 0.75rem;
margin-left: 5rem;
box-sizing:border-box;
&.error {
color: #f41;
}
}
.options-bar {
position: absolute;
width: calc(100% - 4rem);
top: 0;
margin: 1rem;
padding: 1rem;
&.isEditing {
background-color: $primary;
position: relative;
color: #000;
}
}
.b3 {
width: 9rem;
padding-left: 0.33rem;
padding-right: 0.33rem;
}
.b3-compact {
width: 7rem;
padding-left: 0.25rem;
padding-right: 0.25rem;
}
.warning-lite {
padding-right: 16px;
padding-bottom: 16px;
padding-top: 8px;
}
.edit-action-area {
background-color: rgba($blackBg,0.5);
padding: 0.5rem;
margin-bottom: 2rem;
}
.edit-action-area-header {
background-color: $primary;
color: #000;
padding: 0.25rem 0.5rem;
padding-top: 0.5rem;
}
.pointer {
cursor: pointer;
user-select: none;
}

View File

@ -1,26 +0,0 @@
.sub-panel {
max-width: 52rem;
margin: 1rem;
h1:not(:first-child) {
margin-left: 16px;
}
}
.sub-panel-content {
margin-top: 0.5rem;
}
.tab {
border-bottom: 1px solid black;
background-color: rgba(0, 0, 0, 0.5);
padding: 1rem 2rem;
&:hover, &.active {
border-bottom: 1px solid #fa6;
background-color:rgba(0, 0, 0, 0.75)
}
}
.container {
background-color: rgba(0, 0, 0, 0.5);
}

View File

@ -156,15 +156,6 @@
</About>
<PlayerDetectionPanel
v-if="selectedTab === 'playerDetection'"
:siteSettings="siteSettings"
:eventBus="eventBus"
:site="site"
>
</PlayerDetectionPanel>
<ImportExportSettings v-if="selectedTab === 'import-export-settings'"
:settings="settings"
>
@ -204,9 +195,6 @@ import AfterUpdate from '@components/segments/AfterUpdate/AfterUpdate.vue';
import WhatsNew from '@components/segments/ExtensionInfo/WhatsNew.vue';
import About from '@components/segments/ExtensionInfo/About.vue';
// to replace:
import PlayerDetectionPanel from '../../csui/src/PlayerUiPanels/PlayerDetectionPanel.vue'
// not component:
import BrowserDetect from '@src/ext/conf/BrowserDetect'
@ -279,8 +267,6 @@ export default defineComponent({
WhatsNew,
About,
PlayerDetectionPanel,
},
mixins: [],
data() {

View File

@ -188,13 +188,15 @@
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import Popup from '@components/common/Popup.vue';
import JsonEditor from '@components/common/JsonEditor.vue';
import UploadJsonFileButton from '@components/common/UploadJsonFileButton.vue';
import { LogAggregator, BLANK_LOGGER_CONFIG } from '@src/ext/lib/logging/LogAggregator';
import { defineComponent } from 'vue';
import { SettingsSnapshot } from '@src/ext/lib/settings/SettingsSnapshotManager';
import SettingsInterface from '../../../../common/interfaces/SettingsInterface';
import SettingsInterface from '@src/common/interfaces/SettingsInterface';
export default defineComponent({
components: {

View File

@ -155,18 +155,11 @@ import AspectRatioType from '@src/common/enums/AspectRatioType.enum';
import StretchType from '@src/common/enums/StretchType.enum';
import NewAspectRatioForm from './Components/NewAspectRatioForm.vue';
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'
export default defineComponent({
components: {
KeyboardShortcutEntry,
NewAspectRatioForm,
CropOptionsPanel,
StretchOptionsPanel,
ZoomOptionsPanel,
},
data() {
return {

View File

@ -0,0 +1,69 @@
<template>
<!-- ADVANCED OPTIONS -->
<pre>{{siteSettings.raw}}</pre>
<div class="">
<h3>Player element</h3>
<div class="field">
<div class="label">Manually specify player</div>
<input type="checkbox" />
</div>
<div class="field">
<div class="label">Select player by/as:</div>
<div class="select">
<select>
<option>n-th parent of video</option>
<option>Query selectors</option>
</select>
</div>
</div>
<div class="field">
<div class="label">Player is n-th parent of video</div>
<div class="range-input">
<input type="range" min="0" max="100" />
<input />
</div>
</div>
<div class="field">
<div class="label">Query selector for player element</div>
<div class="input">
<input />
</div>
</div>
<div class="field">
<div class="label">In full screen, calculate crop based on monitor resolution</div>
<input type="checkbox" />
</div>
<h3>Video element</h3>
<div class="field">
<div class="label">Select video element automatically</div>
<input type="checkbox">
</div>
<div class="field">
<div class="label">Query selectors</div>
<div class="input">
<input>
</div>
</div>
<div class="field">
<div class="label">Additional styles for video element</div>
<div class="input">
<textarea></textarea>
</div>
</div>
<h3>Additional css for this page</h3>
<div class="field">
<div class="label">Additional CSS for this page</div>
<textarea></textarea>
</div>
</div>
</template>
<script lang="ts">
export default({
});
</script>

View File

@ -0,0 +1,261 @@
<template>
<div class="w-full flex flex-row" style="margin-top: 1rem;">
<!-- PLAYER ELEMENT SELECTOR FOR DUMMIES -->
<div style="width: 48%">
<div class="sub-panel-content">
<div v-if="showAdvancedOptions" style="display: flex; flex-direction: row">
<div style="display: flex; flex-direction: column">
<div>
<input :checked="playerManualQs"
@change="togglePlayerManualQs"
type="checkbox"
/>
<div>
Use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors" target="_blank">CSS selector</a> for player<br/>
<small>If defining multiple selectors, separate them with commas.</small>
</div>
</div>
<div>Selector</div>
<input type="text"
v-model="playerQs"
@change="updatePlayerQuerySelector"
@blur="updatePlayerQuerySelector"
:disabled="playerByNodeIndex || !playerManualQs"
/>
</div>
<div style="display: flex; flex-direction: column">
<b>Custom CSS for site</b>
<textarea></textarea>
</div>
</div>
<div style="display: flex; flex-direction: row;">
<div class="element-tree">
<table>
<thead>
<tr>
<th>
<div class="status-relative">
Status <mdicon name="help-circle" @click="showLegend = !showLegend" />
<div v-if="showLegend" class="element-symbol-legend">
<b>Symbols:</b><br />
<mdicon name="alert-remove" class="invalid" /> Element of invalid dimensions<br />
<mdicon name="refresh-auto" class="auto-match" /> Ultrawidify's player detection thinks this should be the player<br />
<mdicon name="bookmark" class="parent-offset-match" /> Site settings say this should be the player (based on counting parents)<br />
<mdicon name="crosshairs" class="qs-match" /> Site settings say this should be the player (based on query selectors)<br />
<mdicon name="check-circle" class="activePlayer" /> Element that is actually the player
</div>
</div>
</th>
<th>Element</th>
<!-- <th>Actions</th> -->
<!-- <th>Quick fixes</th> -->
</tr>
</thead>
<tbody>
<tr
v-for="(element, index) of elementStack"
:key="index"
class="element-row"
>
<td>
<div class="status">
<div
v-if="element.heuristics?.invalidSize"
class="invalid"
>
<mdicon name="alert-remove" />
</div>
<div
v-if="element.heuristics?.autoMatch"
class="auto-match"
>
<mdicon name="refresh-auto" />
</div>
<div
v-if="element.heuristics?.qsMatch"
class="qs-match"
>
<mdicon name="crosshairs" />
</div>
<div
v-if="element.heuristics?.manualElementByParentIndex"
class="parent-offset-match"
>
<mdicon name="bookmark" />
</div>
<div
v-if="element.heuristics?.activePlayer"
class="activePlayer"
>
<mdicon name="check-circle" />
</div>
</div>
</td>
<td>
<div
class="element-data"
@mouseover="markElement(elementStack.length - index - 1, true)"
@mouseleave="markElement(elementStack.length - index - 1, false)"
@click="setPlayer(elementStack.length - index - 1)"
>
<div class="tag">
<b>{{element.tagName}}</b> <i class="id">{{element.id ? `#`:''}}{{element.id}}</i> @ <span class="dimensions">{{element.width}}</span>x<span class="dimensions">{{element.height}}</span>
</div>
<div v-if="element.classList" class="class-list">
<div v-for="cls of element.classList" :key="cls">
{{cls}}
</div>
</div>
</div>
</td>
<td>
<div class="flex flex-row">
</div>
</td>
</tr>
</tbody>
</table>
<div class="element-config">
</div>
</div>
<!-- <div class="css-preview">
{{cssStack}}
</div> -->
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
components: {
},
data() {
return {
elementStack: [],
cssStack: [],
showLegend: false,
showAdvancedOptions: false,
tutorialVisible: false,
tutorialStep: 0
};
},
computed: {
},
mixins: [],
props: [
'siteSettings',
'frame',
'eventBus',
'site',
'isPopup'
],
created() {
this.eventBus.subscribe(
'uw-config-broadcast',
{
source: this,
function: (config) => this.handleElementStack(config)
}
);
},
mounted() {
this.getPlayerTree();
},
destroyed() {
this.eventBus.unsubscribeAll(this);
},
methods: {
showTutorial() {
this.tutorialVisible = true;
this.tutorialStep = 0;
},
getPlayerTree() {
if (this.isPopup) {
this.eventBus.send('get-player-tree');
} else {
this.eventBus.sendToTunnel('get-player-tree');
}
},
handleElementStack(configBroadcast) {
if (configBroadcast.type === 'player-tree') {
this.elementStack = configBroadcast.config.reverse();
this.$nextTick( () => this.$forceUpdate() );
}
},
markElement(parentIndex, enable) {
this.eventBus.sendToTunnel('set-mark-element', {parentIndex, enable});
},
async setPlayer(index) {
// yup.
this.siteSettings.getDOMConfig('modified', 'original');
await this.siteSettings.setUpdateFlags(['PlayerData']);
await this.siteSettings.set('DOMConfig.modified.type', 'modified', {noSave: true});
await this.siteSettings.set('activeDOMConfig', 'modified', {noSave: true});
// if user agrees with ultrawidify on what element player should be,
// we just unset our settings for this site
if (this.elementStack[index].heuristics?.autoMatch) {
await this.siteSettings.set('DOMConfig.modified.elements.player.manual', false);
this.getPlayerTree();
} else {
// ensure settings exist:
await this.siteSettings.set('DOMConfig.modified.elements.player.manual', true, {noSave: true});
await this.siteSettings.set('DOMConfig.modified.elements.player.mode', 'index', {noSave: true});
await this.siteSettings.set('DOMConfig.modified.elements.player.index', index, true);
this.getPlayerTree();
}
},
/**
* Toggles active CSS for element of certain parent index.
* cssValue is optional and can be included in cssRule argument
*/
toggleCssForElement(index, cssRule, cssValue) {
// we will handle elements that put cssValue as a separate argument elsewhere
if (cssValue) {
return this.toggleCssForElement_3arg(index,cssRule, cssValue);
}
// this rule applies to current element remove it!
if (this.cssStack[index]?.includes(cssRule)) {
this.cssStack[index] = this.cssStack[index].filter(x => ! x.includes(cssRule));
} else {
if (!this.cssStack[index]) {
this.cssStack[index] = [];
}
this.cssStack[index].push(cssRule)
}
//TODO: update settings!
},
toggleCssForElement_3arg(index, cssRule, cssValue) {
const matching = this.cssStack[index]?.find(x => x.includes(cssRule))
if (matching) {
this.cssStack[index] = this.cssStack[index].filter(x => ! x.includes(cssRule));
if (!matching.includes(cssValue)) {
this.cssStack[index].push(`${cssRule}: ${cssValue};`);
}
} else {
if (!this.cssStack[index]) {
this.cssStack[index] = [];
}
this.cssStack[index].push(`${cssRule}: ${cssValue};`);
}
//TODO: update settings!
}
}
});
</script>

View File

@ -88,213 +88,11 @@
<mdicon name="help"></mdicon>
How do I use this?
</button>
<div class="w-full flex flex-row" style="margin-top: 1rem;">
<!-- PLAYER ELEMENT SELECTOR FOR DUMMIES -->
<div style="width: 48%">
<div class="sub-panel-content">
<h2>Simple player selector</h2>
<div v-if="showAdvancedOptions" style="display: flex; flex-direction: row">
<div style="display: flex; flex-direction: column">
<div>
<input :checked="playerManualQs"
@change="togglePlayerManualQs"
type="checkbox"
/>
<div>
Use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors" target="_blank">CSS selector</a> for player<br/>
<small>If defining multiple selectors, separate them with commas.</small>
</div>
</div>
<div>Selector</div>
<input type="text"
v-model="playerQs"
@change="updatePlayerQuerySelector"
@blur="updatePlayerQuerySelector"
:disabled="playerByNodeIndex || !playerManualQs"
/>
</div>
<div style="display: flex; flex-direction: column">
<b>Custom CSS for site</b>
<textarea></textarea>
</div>
</div>
<div style="display: flex; flex-direction: row;">
<div class="element-tree">
<table>
<thead>
<tr>
<th>
<div class="status-relative">
Status <mdicon name="help-circle" @click="showLegend = !showLegend" />
<div v-if="showLegend" class="element-symbol-legend">
<b>Symbols:</b><br />
<mdicon name="alert-remove" class="invalid" /> Element of invalid dimensions<br />
<mdicon name="refresh-auto" class="auto-match" /> Ultrawidify's player detection thinks this should be the player<br />
<mdicon name="bookmark" class="parent-offset-match" /> Site settings say this should be the player (based on counting parents)<br />
<mdicon name="crosshairs" class="qs-match" /> Site settings say this should be the player (based on query selectors)<br />
<mdicon name="check-circle" class="activePlayer" /> Element that is actually the player
</div>
</div>
</th>
<th>Element</th>
<!-- <th>Actions</th> -->
<!-- <th>Quick fixes</th> -->
</tr>
</thead>
<tbody>
<tr
v-for="(element, index) of elementStack"
:key="index"
class="element-row"
>
<td>
<div class="status">
<div
v-if="element.heuristics?.invalidSize"
class="invalid"
>
<mdicon name="alert-remove" />
</div>
<div
v-if="element.heuristics?.autoMatch"
class="auto-match"
>
<mdicon name="refresh-auto" />
</div>
<div
v-if="element.heuristics?.qsMatch"
class="qs-match"
>
<mdicon name="crosshairs" />
</div>
<div
v-if="element.heuristics?.manualElementByParentIndex"
class="parent-offset-match"
>
<mdicon name="bookmark" />
</div>
<div
v-if="element.heuristics?.activePlayer"
class="activePlayer"
>
<mdicon name="check-circle" />
</div>
</div>
</td>
<td>
<div
class="element-data"
@mouseover="markElement(elementStack.length - index - 1, true)"
@mouseleave="markElement(elementStack.length - index - 1, false)"
@click="setPlayer(elementStack.length - index - 1)"
>
<div class="tag">
<b>{{element.tagName}}</b> <i class="id">{{element.id ? `#`:''}}{{element.id}}</i> @ <span class="dimensions">{{element.width}}</span>x<span class="dimensions">{{element.height}}</span>
</div>
<div v-if="element.classList" class="class-list">
<div v-for="cls of element.classList" :key="cls">
{{cls}}
</div>
</div>
</div>
</td>
<td>
<div class="flex flex-row">
</div>
</td>
</tr>
</tbody>
</table>
<div class="element-config">
</div>
</div>
<!-- <div class="css-preview">
{{cssStack}}
</div> -->
</div>
</div>
</div>
<!-- ADVANCED OPTIONS -->
<div style="width: 48%" v-if="false">
<h2>Advanced options</h2>
<pre>{{siteSettings.raw}}</pre>
<div class="">
<h3>Player element</h3>
<div class="field">
<div class="label">Manually specify player</div>
<input type="checkbox" />
</div>
<div class="field">
<div class="label">Select player by/as:</div>
<div class="select">
<select>
<option>n-th parent of video</option>
<option>Query selectors</option>
</select>
</div>
</div>
<div class="field">
<div class="label">Player is n-th parent of video</div>
<div class="range-input">
<input type="range" min="0" max="100" />
<input />
</div>
</div>
<div class="field">
<div class="label">Query selector for player element</div>
<div class="input">
<input />
</div>
</div>
<div class="field">
<div class="label">In full screen, calculate crop based on monitor resolution</div>
<input type="checkbox" />
</div>
<h3>Video element</h3>
<div class="field">
<div class="label">Select video element automatically</div>
<input type="checkbox">
</div>
<div class="field">
<div class="label">Query selectors</div>
<div class="input">
<input>
</div>
</div>
<div class="field">
<div class="label">Additional styles for video element</div>
<div class="input">
<textarea></textarea>
</div>
</div>
<h3>Additional css for this page</h3>
<div class="field">
<div class="label">Additional CSS for this page</div>
<textarea></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
<script lang="ts">
export default({
components: {
@ -334,7 +132,6 @@ export default({
destroyed() {
this.eventBus.unsubscribeAll(this);
},
computed: {},
methods: {
showTutorial() {
this.tutorialVisible = true;

View File

@ -112,16 +112,10 @@
<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 defineComponent({
components: {
CropOptionsPanel,
StretchOptionsPanel,
ZoomOptionsPanel,
},
data() {
return {

View File

@ -17,8 +17,6 @@ const config = {
entry: {
'ext/uw': './ext/uw.js',
'uw-bg': './uw-bg.js',
'csui/csui-popup': './csui/csui-popup.js',
'ui/pages/settings/settings': './ui/pages/settings/settings.js',
},
output: {
@ -119,10 +117,6 @@ const config = {
// This folder does not contain any GUI icons — these are in /res/icons.
// (TODO: check if this copy is even necessary — /icons has same content as /res/icons)
{ from: 'icons', to: 'icons', globOptions: { ignore: ['icon.xcf'] } },
{ from: 'csui/csui-popup.html', to: 'csui/csui-popup.html', transform: transformHtml },
{ from: 'csui/csui-overlay-normal.html', to: 'csui/csui.html', transform: transformHtml },
{ from: 'csui/csui-overlay-dark.html', to: 'csui/csui-dark.html', transform: transformHtml },
{ from: 'csui/csui-overlay-light.html', to: 'csui/csui-light.html', transform: transformHtml },
{ from: 'ui/pages/settings/index.html', to: 'ui/pages/settings/index.html', transform: transformHtml },
{
from: 'manifest.json',