Merge branch 'dependency-upgrades'

This commit is contained in:
Tamius Han 2025-12-02 01:03:05 +01:00
commit 13feb1e822
64 changed files with 22744 additions and 14628 deletions

33503
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,17 +4,17 @@
"description": "Aspect ratio fixer for youtube and other sites, with automatic aspect ratio detection. Supports ultrawide and other ratios.",
"author": "Tamius Han <tamius.han@gmail.com>",
"scripts": {
"build": "npm run pre-build; cross-env NODE_ENV=production BROWSER=firefox CHANNEL=stable webpack --hide-modules",
"build": "npm run pre-build; cross-env NODE_ENV=production BROWSER=firefox CHANNEL=stable webpack",
"build-all": "bash ./scripts/build-all.sh",
"build-chrome": "cross-env NODE_ENV=production BROWSER=chrome CHANNEL=stable webpack --hide-modules",
"build-chrome:dev": "cross-env NODE_ENV=development BROWSER=chrome webpack --hide-modules",
"build-edge": "cross-env NODE_ENV=production BROWSER=edge CHANNEL=stable webpack --hide-modules",
"build-nightly": "cross-env NODE_ENV=development BROWSER=firefox CHANNEL=nightly webpack --hide-modules",
"build-nightly-chrome": "cross-env NODE_ENV=development BROWSER=chrome CHANNEL=nightly webpack --hide-modules",
"build-testing": "cross-env NODE_ENV=development BROWSER=firefox CHANNEL=testing webpack --hide-modules",
"build-testing-chrome": "cross-env NODE_ENV=development BROWSER=chrome CHANNEL=testing webpack --hide-modules",
"build-chrome": "cross-env NODE_ENV=production BROWSER=chrome CHANNEL=stable webpack",
"build-chrome:dev": "cross-env NODE_ENV=development BROWSER=chrome webpack",
"build-edge": "cross-env NODE_ENV=production BROWSER=edge CHANNEL=stable webpack",
"build-nightly": "cross-env NODE_ENV=development BROWSER=firefox CHANNEL=nightly webpack",
"build-nightly-chrome": "cross-env NODE_ENV=development BROWSER=chrome CHANNEL=nightly webpack",
"build-testing": "cross-env NODE_ENV=development BROWSER=firefox CHANNEL=testing webpack",
"build-testing-chrome": "cross-env NODE_ENV=development BROWSER=chrome CHANNEL=testing webpack",
"build-zip": "node scripts/build-zip.js",
"build:dev": "webpack --hide-modules",
"build:dev": "webpack",
"dev": "cross-env NODE_ENV=development CHANNEL=dev concurrently \"cross-env BROWSER=firefox npm run build:dev -- --watch\" \"cross-env BROWSER=chrome npm run build:dev -- --watch\" \"cross-env BROWSER=edge npm run build:dev -- --watch\"",
"dev:windows": "cross-env NODE_ENV=development CHANNEL=dev concurrently \"cross-env BROWSER=firefox npm run build:dev -- -w --watch-poll\" \"cross-env BROWSER=chrome npm run build:dev -- -w --watch-poll\" \"cross-env BROWSER=edge npm run build:dev -- -w --watch-poll\"",
"dev-ff": "rm -rf ./dist-ff && cross-env NODE_ENV=development CHANNEL=dev BROWSER=firefox npm run build:dev -- --watch",
@ -25,55 +25,62 @@
"start:windows": "npm run dev:windows"
},
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.16.0",
"@mdi/font": "^6.5.95",
"@mdi/js": "^6.4.95",
"@types/resize-observer-browser": "^0.1.6",
"concurrently": "^5.3.0",
"fs-extra": "^7.0.1",
"gl-matrix": "^3.4.3",
"json-cyclic": "0.0.3",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@mdi/font": "^7.4.47",
"@mdi/js": "^7.4.47",
"@types/resize-observer-browser": "^0.1.11",
"concurrently": "^9.2.1",
"fs-extra": "^11.3.2",
"gl-matrix": "^3.4.4",
"json-cyclic": "1.0.2",
"lodash": "^4.17.21",
"mdi-vue": "^3.0.11",
"typescript": "^4.4.4",
"vanilla-jsoneditor": "^3.3.0",
"vue": "^3.2.21",
"mdi-vue": "^3.0.13",
"typescript": "^5.9.3",
"vanilla-jsoneditor": "^3.10.0",
"vue": "^3.5.25",
"vue-style-loader": "^4.1.3",
"vuex": "^4.0.2",
"vuex": "^4.1.0",
"vuex-webextensions": "^1.3.3",
"webextension-polyfill": "^0.12.0"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@babel/preset-env": "^7.16.0",
"@types/chrome": "0.0.240",
"@types/core-js": "^2.5.5",
"@types/es6-promise": "^3.3.0",
"@types/firefox": "0.0.31",
"@types/lodash": "^4.14.176",
"@types/node": "^14.17.32",
"@vue/cli": "^4.5.15",
"@vue/cli-plugin-typescript": "^4.5.15",
"@vue/compiler-sfc": "^3.2.21",
"archiver": "^3.0.0",
"babel-loader": "^8.2.3",
"@babel/core": "^7.28.5",
"@babel/preset-env": "^7.28.5",
"@tailwindcss/postcss": "^4.1.17",
"@types/chrome": "0.1.31",
"@types/core-js": "^2.5.8",
"@types/es6-promise": "^3.3.2",
"@types/firefox": "0.0.34",
"@types/lodash": "^4.17.21",
"@types/node": "^24.10.1",
"@vue/cli": "^5.0.9",
"@vue/cli-plugin-typescript": "^5.0.9",
"@vue/compiler-sfc": "^3.5.25",
"archiver": "^7.0.1",
"babel-loader": "^10.0.0",
"babel-preset-es2020": "^1.0.2",
"copy-webpack-plugin": "^4.5.3",
"cross-env": "^5.2.0",
"css-loader": "^0.28.11",
"ejs": "^2.7.4",
"file-loader": "^1.1.11",
"mini-css-extract-plugin": "^0.4.4",
"node-sass": "^4.14.1",
"copy-webpack-plugin": "^13.0.1",
"cross-env": "^10.1.0",
"css-loader": "^7.1.2",
"ejs": "^3.1.10",
"file-loader": "^6.2.0",
"mini-css-extract-plugin": "^2.9.4",
"postcss": "^8.5.6",
"postcss-import": "^16.1.1",
"postcss-nested": "^7.0.2",
"postcss-preset-env": "^10.4.0",
"postcss-strip-inline-comments": "^0.1.5",
"resolve-url-loader": "^5.0.0",
"sass-loader": "^7.1.0",
"ts-loader": "^8.3.0",
"tailwindcss": "^4.1.17",
"ts-loader": "^9.5.4",
"tsconfig-paths": "^4.2.0",
"vue-cli-plugin-vue-next": "~0.1.4",
"vue-loader": "^16.8.2",
"web-ext-types": "^2.3.0",
"webpack": "^4.46.0",
"webpack-chrome-extension-reloader": "^0.8.3",
"webpack-cli": "^3.3.12",
"webpack-shell-plugin": "^0.5.0"
"vue-loader": "^17.4.2",
"vue-tsc": "^3.1.5",
"web-ext-types": "^3.2.1",
"webpack": "^5.103.0",
"webpack-cli": "^6.0.1",
"webpack-shell-plugin": "^0.5.0",
"webpack-shell-plugin-next": "^2.3.3"
}
}

7
postcss.config.js Normal file
View File

@ -0,0 +1,7 @@
module.exports = {
plugins: [
require('@tailwindcss/postcss'),
require('postcss-nested'),
require('autoprefixer'),
],
};

View File

@ -688,23 +688,9 @@ export default {
flex-direction: row;
align-content: center;
align-items: center;
// width: 100%;
// height: 100%;
color: #fff;
// .context-item {
// font-size: .95rem;
// padding: 1rem 1.6rem;
// background-color: rgba(0, 0, 0, 0.5);
// backdrop-filter: blur(16px) saturate(120%);
// white-space: nowrap;
// }
// .spawn-container {
// border: 1px solid white;
// }
}
.extension-status-messages {
@ -716,9 +702,6 @@ export default {
font-size: 0.9rem;
// width: 112.25%;
// transform: translate(-12.5%, 12.5%) scale(0.75);
> * {
margin-top: 0.5rem;
margin-bottom: 0.5rem;

View File

@ -339,12 +339,13 @@ export default {
</script>
<style lang="scss">
// @import 'res/css/uwui-base.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);
@ -352,10 +353,6 @@ export default {
color: #fff;
padding: 8px;
// display: flex;
// flex-direction: row;
// justify-content: space-between;
border-bottom: 1px dotted #fa6;
@ -529,7 +526,6 @@ export default {
padding: 1rem;
font-size: 1.25rem;
// height: rem;
min-height: 3rem;
border-bottom: 1px solid rgba(128, 128, 128, 0.5);
@ -562,9 +558,6 @@ export default {
opacity: 0.9;
color: #eee;
// .label {
// color: rgb(239, 192, 152);
// }
}
}
}

View File

@ -1,9 +1,8 @@
@import "colors.scss";
/* @import "colors.scss"; */
/*
@import "fonts.scss";
@import "flex.scss";
// @import "~/@mdi/font/css/materialdesignicons.css";
// @import "form.scss";
*/
* {
box-sizing: border-box;
@ -255,10 +254,6 @@ small {
button,
.button {
/*display: inline-block;*/
// padding-top: 8px;
// padding-bottom: 3px;
//padding-left: 5px;
//padding-right: 5px;
border: 1px solid rgb(39, 39, 39);
margin-top: 3px;
margin-bottom: 3px;
@ -402,10 +397,8 @@ button,
}
.experimental::after {
// content: "Experimental";
content: "";
color: #ffde12;
// background-color: #1f1f1f;
display: inline-block;
font-size: .75em;
font-variant: small-caps;
@ -416,9 +409,7 @@ button,
}
.experimental:hover::after {
content: "Ⓔ Experimental";
// content: "";
color: #ffde12;
// background-color: #1f1f1f;
display: inline-block;
font-size: .75em;
font-variant: small-caps;
@ -431,7 +422,6 @@ button,
.sticky-bottom {
width: 100%;
position: sticky;
// position: fixed;
bottom: 0px;
background-color: rgba($background-primary, 0.7);
}

View File

@ -1,12 +1,14 @@
@import 'flex.scss';
/* @import 'flex.scss';
@import 'colors.scss';
@import 'common.scss';
*/
* {
font-family: 'Overpass';
}
.uw-ultrawidify-container-root {
/*
// here's the defaults:
// all: initial;
// * {
@ -18,6 +20,7 @@
// all: declaration overrides everything.)
// we put our UI _over_ site's player:
*/
z-index: 999999;
position: absolute;
width: 100%;
@ -26,10 +29,10 @@
left: 0;
background-color: none transparent !important;
// Ensure we're display:block
/* Ensure we're display:block */
display: block;
// we are click-through by default:
/* we are click-through by default: */
pointer-events: none;
}

View File

@ -25,112 +25,17 @@
</div>
</div>
<div class="tab-main flex flex-row">
<div class="tab-row flex flex-col grow-0 shrink-0">
<div
v-for="tab of tabs"
:key="tab.id"
>
<div
v-if="!tab.hidden"
class="tab"
:class="{
'active': tab.id === selectedTab,
'highlight-tab': tab.highlight,
}"
@click="selectTab(tab.id)"
>
<div class="icon-container">
<mdicon
v-if="tab.icon"
:name="tab.icon"
:size="32"
/>
</div>
<div class="label">
{{tab.label}}
</div>
</div>
</div>
</div>
<div class="content flex flex-col">
<!-- autodetection warning -->
<div class="warning-area">
<div
v-if="statusFlags.hasDrm"
class="warning-box"
>
<div class="icon-container">
<mdicon name="alert" :size="32" />
</div>
<div>
This site is blocking automatic aspect ratio detection. You will have to adjust aspect ratio manually.<br/>
<a>Learn more ...</a>
</div>
</div>
</div>
<div class="flex flex-row panel-content">
<!-- Panel section -->
<!-- <VideoSettings
v-if="selectedTab === 'videoSettings'"
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
:site="site"
></VideoSettings> -->
<PlayerDetectionPanel
v-if="selectedTab === 'playerDetection'"
:siteSettings="siteSettings"
:eventBus="eventBus"
:site="site"
>
</PlayerDetectionPanel>
<PlayerUiSettings
v-if="selectedTab === 'playerUiSettings'"
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
>
</PlayerUiSettings>
<BaseExtensionSettings
v-if="selectedTab === 'extensionSettings'"
:settings="settings"
:siteSettings="siteSettings"
:site="site"
:enableSettingsEditor="true"
></BaseExtensionSettings>
<AutodetectionSettingsPanel
v-if="selectedTab === 'autodetectionSettings'"
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
:site="site"
>
</AutodetectionSettingsPanel>
<DebugPanel
v-if="selectedTab === 'debugging'"
:settings="settings"
:eventBus="eventBus"
:site="site"
></DebugPanel>
<ChangelogPanel
v-if="selectedTab === 'changelog'"
:settings="settings"
></ChangelogPanel>
<AboutPanel
v-if="selectedTab === 'about'"
>
</AboutPanel>
<!-- <ResetBackupPanel
v-if="selectedTab === 'resetBackup'"
:settings="settings"
>
</ResetBackupPanel> -->
</div>
</div>
</div>
<SettingsWindowContent
:tabs="tabs"
:selectedTab="selectedTab"
:settings="settings"
:eventBus="eventBus"
:logger="logger"
:in-player="inPlayer"
:site="site"
@selectTab="selectTab"
@preventClose="setPreventClose"
></SettingsWindowContent>
</div>
</template>
<script>
@ -165,19 +70,6 @@ export default {
statusFlags: {
hasDrm: undefined,
},
tabs: [
// {id: 'videoSettings', label: 'Video settings', icon: 'crop'},
{id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' },
{id: 'playerUiSettings', label: 'UI and keyboard', icon: 'movie-cog-outline' },
{id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
{id: 'autodetectionSettings', label: 'Autodetection options', icon: 'auto-fix'},
// {id: 'advancedOptions', label: 'Advanced options', icon: 'cogs' },
{id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
{id: 'about', label: 'About', icon: 'information-outline'},
{id: 'debugging', label: 'Debugging', icon: 'bug-outline', hidden: true},
],
selectedTab: 'extensionSettings',
BrowserDetect: BrowserDetect,
preventClose: false,
siteSettings: null,
@ -249,19 +141,12 @@ export default {
}
</script>
<style lang="scss" scoped>
@import '../res/css/uwui-base.scss';
/*@import '../res/css/uwui-base.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';
// .relative-wrapper {
// position: relative;
// width: 100%;
// height: 100%;
// }
@import '../src/res-common/_variables';*/
.tab-row {
@ -416,10 +301,6 @@ export default {
&.highlight-tab {
opacity: 0.9;
color: #eee;
// .label {
// color: rgb(239, 192, 152);
// }
}
}
}

View File

@ -81,9 +81,6 @@ export default {
<style lang="scss" scoped module>
.alignment-box {
aspect-ratio: 1;
// width: 100%;
// min-width: 40px;
// max-width: 80px;
display: grid;
grid-template-columns: repeat(3, 1fr);

View File

@ -338,11 +338,6 @@ export default {
<style lang="scss" src="@csui/src/res-common/common.scss" scoped module></style>
<style lang="scss" scoped module>
@import '../res-common/variables';
// .aard-settings-group {
// max-width: 69rem;
// }
.performance-graph-container {
position: relative;
@ -361,7 +356,7 @@ export default {
width: 100%;
height: 100%;
// graph is 100 ms wide
/* graph is 100 ms wide */
.time-budget {
height: 100%;
display: inline-block;
@ -373,15 +368,12 @@ export default {
&.hz144 {
width: 6.9%;
// background-color: rgba(143, 143, 235, 0.47);
}
&.hz120 {
width: 1.39%;
// background-color: rgba(108, 108, 211, 0.441);
}
&.hz60 {
width: 8.33%;
// background-color: rgba(78, 78, 182, 0.327);
border-right: 2px solid rgb(96, 96, 227);
&::after {
content: '60fps';
@ -400,7 +392,6 @@ export default {
}
&.hz30 {
width: 16.67%;
// background-color: rgba(56, 56, 151, 0.308);
border-right: 2px solid #fb772a;
&::after {
@ -420,7 +411,6 @@ export default {
}
&.hz24 {
width: 8.33%;
// background-color: rgba(37, 37, 118, 0.269);
}
}

View File

@ -14,7 +14,12 @@
</div>
<div class="flex flex-row">
<small>
<span style="text-transform: uppercase; font-size: 0.9rem">Enable extension: <span style="font-size: 0.9rem;" :style="getSiteEnabledColor('@global', 'enable')">{{ getSiteEnabledModes('@global', 'enable') }}</span></span>&nbsp;<br/>
<span style="text-transform: uppercase; font-size: 0.9rem">
Enable extension: <span style="font-size: 0.9rem;" :style="getSiteEnabledColor('@global', 'enable')">{{ getSiteEnabledModes('@global', 'enable') }}</span>
{{getSiteEnabledModes('@global', 'enable') === 'disabled'}}
<span v-if="getSiteEnabledModes('@global', 'enable') === 'disabled'" class="text-[0.9em]">&nbsp;(except on whitelisted sites)</span>"
</span>&nbsp;
<br/>
Autodetection: <span :style="getSiteEnabledColor('@global', 'enableAard')"><small>{{ getSiteEnabledModes('@global', 'enableAard') }}</small></span>;&nbsp;
Keyboard shortcuts: <span :style="getSiteEnabledColor('@global', 'enableKeyboard')"><small>{{ getSiteEnabledModes('@global', 'enableKeyboard') }}</small></span>;
In-player UI: <span :style="getSiteEnabledColor('@global', 'enableUI')"><small>{{ getSiteEnabledModes('@global', 'enableUI') }}</small></span>;

View File

@ -417,13 +417,7 @@ export default({
}
})
</script>
<style lang="scss" src="../../res/css/flex.scss" scoped module></style>
<style lang="scss" src="@csui/src/res-common/panels.scss" scoped module></style>
<style lang="scss" src="@csui/src/res-common/common.scss" scoped module></style>
<style lang="scss" scoped>
@import "@csui/res/css/colors.scss";
p {
font-size: 1rem;
}
@ -513,8 +507,6 @@ p {
.element-tree {
.element-row {
// display: flex;
// flex-direction: row;
margin: 0.5rem;
.status {
@ -569,13 +561,6 @@ p {
}
}
// .css-fixes {
// // display: flex;
// // flex-direction: row;
// // flex-wrap: wrap;
// // align-items: flex-start;
// // justify-content:flex-start;
// }
.css-line {
display: inline-block;
flex-grow: 0;

View File

@ -82,7 +82,6 @@ export default {
width: 100%;
height: 100%;
// background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: saturate(50%) brightness(50%) blur(1rem);
z-index: 99999;

View File

@ -174,7 +174,6 @@ export default {
<style lang="scss" src="@csui/src/res-common/common.scss" scoped></style>
<style lang="scss" scoped>
@import "../res-common/variables";
.center-text {
text-align: center;

View File

@ -79,7 +79,6 @@ export default {
width: 100%;
height: 100%;
// background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: saturate(50%) brightness(50%) blur(1rem);
z-index: 99999;

View File

@ -191,7 +191,6 @@ button, .button {
max-width: 100%;
color: #fff;
// border: 0px solid transparent;
border: 1px solid rgba(255, 171, 102, 0.42);
padding: 0.5rem 1rem 0.25rem;
@ -208,7 +207,6 @@ button, .button {
margin-top: 0.25rem;
margin-bottom: 0.75rem;
margin-left: 5rem;
// width: 100%;
box-sizing:border-box;
&.error {
@ -241,9 +239,6 @@ button, .button {
padding-right: 0.25rem;
}
// .input-slider {
// width: 480px;
// }
.warning-lite {
padding-right: 16px;
padding-bottom: 16px;

View File

@ -156,7 +156,7 @@ const ExtensionConf: SettingsInterface = {
aard: {
aardType: 'auto',
useLegacy: true,
useLegacy: false,
polling: {
runInBackgroundTabs: AardPollingOptions.Reduced,

View File

@ -107,9 +107,9 @@ class Logger {
}
static syncConfig(callback: (x) => void) {
chrome.storage.onChanged.addListener( (changes, area) => {
chrome.storage.onChanged.addListener( (changes: any, area: string) => {
if (changes.uwLogger) {
const newLoggerConf = JSON.parse(changes.uwLogger.newValue)
const newLoggerConf = JSON.parse(changes.uwLogger.newValue as any)
if (process.env.CHANNEL === 'dev') {
console.info('Logger settings reloaded. New conf:', newLoggerConf);
}
@ -172,7 +172,7 @@ class Logger {
this.temp_disable = false;
this.stopTime = this.conf.timeout ? performance.now() + (this.conf.timeout * 1000) : undefined;
chrome.storage.onChanged.addListener( (changes, area) => {
chrome.storage.onChanged.addListener( (changes: any, area: any) => {
if (process.env.CHANNEL === 'dev') {
if (!changes.uwLogger) {
// console.info('[Logger::<storage/on change> No new logger settings!');
@ -195,7 +195,7 @@ class Logger {
});
}
storageChangeListener(changes, area) {
storageChangeListener(changes: any, area: any) {
if (!changes.uwLogger) {
console.info('We dont have any logging settings, not processing frther');
return;

View File

@ -42,7 +42,7 @@ export class LogAggregator {
history: any[];
static async getConfig() {
let ret = await chrome.storage.local.get(STORAGE_LOG_SETTINGS_KEY);
let ret: any = await chrome.storage.local.get(STORAGE_LOG_SETTINGS_KEY);
if (process.env.CHANNEL === 'dev') {
try {
@ -73,7 +73,7 @@ export class LogAggregator {
}
static syncConfig(callback: (x) => void) {
chrome.storage.onChanged.addListener( (changes, area) => {
chrome.storage.onChanged.addListener( (changes: any, area) => {
if (changes.uwLogger) {
const newLoggerConf = JSON.parse(changes.uwLogger.newValue)
if (process.env.CHANNEL === 'dev') {

View File

@ -1,7 +1,5 @@
import { settings } from 'cluster'
import SettingsInterface from '@src/common/interfaces/SettingsInterface';
export interface SettingsSnapshot {
isAutomatic?: boolean;
isProtected?: boolean;
@ -92,7 +90,7 @@ export class SettingsSnapshotManager {
async listSnapshots(): Promise<SettingsSnapshot[]> {
const ret = await chrome.storage.local.get('uwSettings-snapshots');
try {
const json = JSON.parse(ret['uwSettings-snapshots']) as SettingsSnapshot[];
const json = JSON.parse(ret['uwSettings-snapshots'] as string) as SettingsSnapshot[];
return json;
} catch (e) {
return [] as SettingsSnapshot[];

View File

@ -0,0 +1,33 @@
import SettingsInterface from '../../../common/interfaces/SettingsInterface';
/**
* Upgrading from v5 to v6 introduced major changes in the settings structure. As such, upgrading from v5 to v6
* start with default settings for v6, and then manually migrate some of the more important settings from v5.
* @param currentSettings
* @param defaultSettings
* @returns
*/
export function migrate5to6(currentSettings: SettingsInterface & any, defaultSettings: SettingsInterface): SettingsInterface {
// if settings do not have a version, return default settings.
// This is okay, because currentSettings.version should be undefined only if settings are either very ancient
// or settings were never modified at all.
if (!currentSettings.version) {
return defaultSettings;
}
// do not perform migration if the version is already 6 or higher
const [majorVersion] = currentSettings.version.split('.');
if (+majorVersion >= 6) {
return currentSettings;
}
const newSettings = JSON.parse(JSON.stringify(defaultSettings));
// TODO: migrate the following:
// * when migrating from v5, default extension mode should be enabled for all sites
// unless it was changed by the user
// * keyboard shortcuts should be migrated
// * attempt to migrate custom site settings for users's custom sites
return newSettings;
}

View File

@ -0,0 +1,56 @@
export class BasicNotifications {
bodyElement: HTMLBodyElement;
basicNotificationContainer: HTMLDivElement;
isIframe = window.self !== window.top;
activeNotifications = [];
notificationCount = 0;
constructor() {
this.bodyElement = document.body as HTMLBodyElement;
this.createNotificationContainer();
}
private createNotificationContainer() {
this.basicNotificationContainer = document.createElement('div')
this.basicNotificationContainer.style.all = 'initial';
this.basicNotificationContainer.style.fontSize = '16px';
this.basicNotificationContainer.style.fontFamily = "'Overpass', sans-serif";
this.basicNotificationContainer.style.position = 'fixed';
this.basicNotificationContainer.style.left = '0';
this.basicNotificationContainer.style.top = '0';
this.basicNotificationContainer.style.width = '100vw';
this.basicNotificationContainer.style.height = '100vh';
this.basicNotificationContainer.style.zIndex = '999999';
this.basicNotificationContainer.style.pointerEvents = 'none';
this.basicNotificationContainer.style.display = 'flex';
this.basicNotificationContainer.style.flexDirection = 'column';
this.bodyElement.appendChild(this.basicNotificationContainer);
}
showNotification(notification: {title: string, body?: string, expire?: number | false}) {
if (this.isIframe) {
return;
}
const notificationDiv = document.createElement('div');
const notificationId = this.notificationCount++;
notificationDiv.innerHTML = `
<div style="
display: flex; flex-direction: row;
color: #fff;
background-color: #fff;
backdrop-filter: blur(1rem) brightness(0.33);
margin: 1rem;
">
<div style="display: flex; flex-direction: column;">
<div><b style="text: #fff">${notification.title}</b></div>
${notification.body ? `<div style="color: #ccc">${document.body}</div>` : ''}
</div>
<div>
<button id="ultrawidify-uw-notification-id-${notificationId}">Dismiss</button>
</div>
</div>
`;
}
}

View File

@ -1,5 +1,4 @@
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
import { EventBusConnector } from '../EventBus';
import ExtensionMode from '@src/common/enums/ExtensionMode.enum';
if (process.env.CHANNEL !== 'stable'){
console.info("Loading: UI");

40
src/main.css Normal file
View File

@ -0,0 +1,40 @@
@import "tailwindcss";
@import "tailwindcss/utilities";
@theme {
--color-primary: #ff872c;
--color-primary-50: #ffddbe;
--color-primary-100: #ffcda0;
--color-primary-200: #ffbd83;
--color-primary-300: #ffac66;
--color-primary-400: #ff9a4a;
--color-primary-500: #ff872c;
--color-primary-600: #de7622;
--color-primary-700: #be6518;
--color-primary-800: #9f540e;
--color-primary-900: #824406;
--color-primary-950: #663400;
}
@import './ui/res/styles/forms.css';
@import './ui/res/styles/fonts.css';
@import './ui/res/styles/buttons.css';
html, body {
font-size: 16px;
}
@media (max-width: 999px) {
html, body {
font-size: 12px;
}
}
@layer base {
body {
@apply font-sans text-stone-300;
}
h1, h2, h3, h4, h5, h6 {
@apply font-mono;
}
}

View File

@ -31,6 +31,11 @@
]
},
"options_ui": {
"page": "ui/pages/settings/index.html",
"open_in_tab": true
},
"web_accessible_resources": [
{
"resources": [
@ -42,7 +47,7 @@
"res/icons/*",
"res/img/*",
"csui/*",
"install/*"
"ui/*"
],
"matches": [
"*://*/*"

View File

@ -0,0 +1,310 @@
<template>
<div class="flex flex-row w-full h-full">
<div class="flex flex-col w-full">
<!-- TAB ROW -->
<div class="flex flex-row">
<div
class="tab"
:class="{'active': tab === 'siteSettings'}"
@click="setTab('siteSettings')"
>
Current site<br/>
<small>{{ site }}</small>
</div>
<div
v-if="hosts"
class="tab"
:class="{'active': tab === 'embeddedSites'}"
@click="setTab(tab = 'embeddedSites')"
>
Embedded content ({{hosts?.length}} {{hosts?.length === 1 ? 'site' : 'sites'}})
</div>
<div
class="tab"
:class="{'active': tab === 'otherSites'}"
@click="setTab(tab = 'otherSites')"
>
Defaults & other sites
</div>
</div>
<template v-if="tab === 'siteSettings' && siteSettings">
<SiteExtensionSettings
v-if="settings"
:settings="settings"
:siteSettings="siteSettings"
:isDefaultConfiguration="false"
></SiteExtensionSettings>
</template>
<template v-if="hosts && tab === 'embeddedSites' && globalSettings">
<FrameSiteSettings
v-if="settings"
:parentHost="site"
:hosts="hosts"
:settings="settings"
></FrameSiteSettings>
</template>
<template v-if="tab === 'otherSites'">
<OtherSiteSettings
v-if="settings"
:settings="settings"
>
</OtherSiteSettings>
</template>
<!-- Reset options -->
<div class="flex flex-col" style="margin-top: 2rem">
<h2>Reset and backup</h2>
<p><small>Settings import must be done from in-player UI.</small></p>
<div class="flex flex-row w-full">
<UploadJsonFileButton
class="flex-grow"
@importedJson="handleImportedSettings"
@error="handleSettingsImportError"
>
Import settings
</UploadJsonFileButton>
<Popup
v-if="importSettingDialogConfig.visible"
title="Overwrite existing settings?"
message="Importing settings from a file will overwrite existing settings. Continue?"
confirmButtonText="Import settings"
cancelButtonText="Cancel"
@onConfirm="importSettingDialogConfig.confirm"
@onCancel="importSettingDialogConfig.reject"
>
</Popup>
<button class="flex-grow" @click="exportSettings()">Export settings</button>
</div>
<div></div>
<ConfirmButton
dialogType="danger"
@onConfirmed="resetSettings"
>
Reset settings
</ConfirmButton>
<div v-if="enableSettingsEditor" class="field">
<div class="label">Show developer options</div>
<input
type="checkbox"
v-model="settings.active.ui.devMode"
@change="settings.saveWithoutReload"
>
</div>
</div>
</div>
<div v-if="enableSettingsEditor && settings.active.ui.devMode" class="h-full grow">
<h2>Settings editor</h2>
<div class="flex flex-row w-full">
<div class="flex flex-row items-center">
<div>Enable save button:</div>
<input v-model="allowSettingsEditing" type="checkbox">
</div>
<div class="grow">
</div>
<div>
<div v-if="editorSaveFinished">Settings saved ...</div>
<button v-else
class="danger"
:class="{'disabled': !allowSettingsEditing}"
:disabled="!allowSettingsEditing"
@click="() => saveSettingsChanges()"
>
Save
</button>
</div>
<button @click="resetSettingsEditor">
Cancel
</button>
</div>
<div>
<JsonEditor
v-model="settingsJson"
>
</JsonEditor>
</div>
<h2>Settings snapshots</h2>
<div class="flex flex-col">
<div v-for="(snapshot, index) of settingsSnapshots" :key="snapshot.createdAt">
<small>{{new Date(snapshot.createdAt).toISOString()}}</small>
<div class="flex flex-row">
<div class="grow">
{{snapshot.name}}
</div>
<div v-if="settings.isAutomatic">(auto)</div>
<div v-if="settings.isAutomatic && settings.isProtected">(protected)</div>
<div v-if="settings.default">(default)</div>
</div>
<div>
<button @click="() => markDefaultSnapshot(index)"><template v-if="settings.isDefault">Revoke default</template><template v-else>Make default</template></button>
<button v-if="settings.isAutomatic" @click="() => toggleSnapshotProtection(index)">Toggle protection</button>
<button @click="() => deleteSnapshot(index)">Delete snapshot</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import SiteExtensionSettings from '@components/ExtensionSettings/Panels/SiteExtensionSettings.vue';
import FrameSiteSettings from '@components/ExtensionSettings/Panels/FrameSiteSettings.vue';
import OtherSiteSettings from '@components/ExtensionSettings/Panels/OtherSiteSettings.vue';
import Popup from '@csui/src/components/Popup';
import ConfirmButton from '@csui/src/components/ConfirmButton';
import UploadJsonFileButton from '@csui/src/components/UploadJsonFileButton';
import JsonEditor from '@csui/src/components/JsonEditor';
export default {
components: {
SiteExtensionSettings,
OtherSiteSettings,
Popup,
ConfirmButton,
UploadJsonFileButton,
JsonEditor,
FrameSiteSettings,
},
mixins: [],
props: [
'settings',
'site',
'enableSettingsEditor',
'hosts',
],
data() {
return {
tab: 'siteSettings',
importSettingDialogConfig: {visible: false},
allowSettingsEditing: false,
editorSaveFinished: false,
settingsJson: {},
settingsSnapshots: []
}
},
computed: {
globalSettings() {
return this.settings?.getSiteSettings('@global') ?? null;
},
siteSettings() {
if (this.site) {
return this.settings?.getSiteSettings({site: this.site}) ?? null;
}
return null;
},
},
mounted() {
this.resetSettingsEditor();
this.loadSettingsSnapshots();
},
methods: {
setTab(tab) {
this.tab = tab;
},
//#region settings management
/**
* Exports extension settings into a json file.
*/
exportSettings() {
const settingBlob = new Blob(
[ JSON.stringify(this.settings.active, null, 2) ],
{ type: "application/json"}
);
const url = window.URL.createObjectURL(settingBlob);
const a = document.createElement("a");
a.href = url;
a.download = "ultrawidify-settings.json";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
},
handleImportedSettings(newSettings) {
this.importSettingDialogConfig = {
visible: true,
confirm: () => {
this.settings.active = newSettings;
this.settings.saveWithoutReload();
this.importSettingDialogConfig = {visible: false};
this.resetSettingsEditor();
},
reject: () => {
this.importSettingDialogConfig = {visible: false};
}
}
},
handleSettingsImportError(error) {
console.error('Error importing settings:', error);
},
/**
* Resets settings to default
*/
resetSettings() {
this.settings.active = JSON.parse(JSON.stringify(this.settings.default));
this.settings.saveWithoutReload();
this.resetSettingsEditor();
},
async saveSettingsChanges() {
if (this.allowSettingsEditing) {
const currentVersion = this.settings.active?.version;
this.settings.active = this.settingsJson;
if (currentVersion !== this.settingsJson.version) {
await this.settings.save({forcePreserveVersion: true});
return;
} else {
await this.settings.saveWithoutReload();
}
this.resetSettingsEditor();
this.editorSaveFinished = true;
setTimeout(() => {
this.editorSaveFinished = false;
}, 3000);
}
},
resetSettingsEditor() {
this.settingsJson = JSON.parse(JSON.stringify(this.settings?.active ?? {}));
},
//#endregion
//#region settings snapshot management
async loadSettingsSnapshots() {
this.settingsSnapshots = await this.settings.snapshotManager.listSnapshots();
},
async markDefaultSnapshot(index) {
await this.settings.snapshotManager.setDefaultSnapshot(index, !this.settingsSnapshots[index].isDefault);
await this.loadSettingsSnapshots();
this.settings.active.dev.loadFromSnapshot = this.settingsSnapshots[index].isDefault;
this.saveSettingsChanges();
},
async toggleSnapshotProtection(index) {
await this.settings.snapshotManager.setSnapshotAsProtected(index, !this.settingsSnapshots[index].isProtected);
await this.loadSettingsSnapshots();
},
async deleteSnapshot(index) {
await this.settings.snapshotManager.deleteSnapshot(index);
await this.loadSettingsSnapshots();
},
}
//#endregion
}
</script>

View File

@ -0,0 +1,109 @@
<template>
<div class="">
<template v-if="!selectedSite">
<div style="margin: 1rem 0rem" class="w-full">
<div class="flex flex-row items-baseline">
<div style="margin-right: 1rem">Search for site:</div>
<div class="input flex-grow">
<input v-model="siteFilter" />
</div>
</div>
</div>
<div v-for="host of hosts" :key="host" @click="selectedSite = host" class="flex flex-col container pointer hoverable" style="margin-top: 4px; padding: 0.5rem 1rem;">
<SiteListItem
:parentHost="parentHost"
:host="host"
:settings="settings"
></SiteListItem>
</div>
</template>
<template v-if="selectedSite">
<div class="flex flex-row container" style="align-items: center; color: #dedede; margin-top: 1rem;">
<div @click="selectedSite = null" class="pointer button-hover" style=" font-size: 2em; padding: 0.5rem; margin-right: 1em;">
</div>
<div>
Editing {{ selectedSite === '@global' ? 'default settings' : selectedSite }}
</div>
</div>
<div>
<SiteExtensionSettings
v-if="selectedSiteSettings"
:settings="settings"
:siteSettings="selectedSiteSettings"
:isDefaultConfiguration="selectedSite === '@global'"
></SiteExtensionSettings>
</div>
</template>
</div>
</template>
<script>
import SiteExtensionSettings from './SiteExtensionSettings.vue';
import SiteListItem from './SiteListItem.vue';
export default {
components: {
SiteExtensionSettings,
SiteListItem,
},
props: [
'settings',
'parentHost',
'hosts',
],
data() {
return {
selectedSite: null,
siteFilter: '',
filteredSites: []
}
},
computed: {
sites() {
if (!this.settings?.active?.sites) {
return [];
} else {
const sites = [];
for (const siteKey in this.settings.active.sites) {
if (!siteKey.startsWith('@') && (!this.siteFilter.trim() || siteKey.includes(this.siteFilter))) {
sites.push({
key: siteKey,
...this.settings.active.sites[siteKey]
})
}
};
sites.sort((a, b) => {
const cmpa = a.key.replace('www.', '');
const cmpb = b.key.replace('www.', '');
return cmpa < cmpb ? -1 : 1;
});
return sites;
}
},
selectedSiteSettings() {
return this.settings?.getSiteSettings({site: this.selectedSite}) ?? null;
}
},
methods: {
}
}
</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>
.hoverable {
border: 1px solid #333;
&:hover {
border: 1px solid #fa6;
color: rgb(255, 231, 212);
background-color: rgba(#fa6, 0.125);
}
}
</style>

View File

@ -0,0 +1,160 @@
<template>
<div class="flex flex-row gap-8">
<div v-if="!selectedSite || !isCompact">
<div
class="flex flex-col cursor-pointer mt-1 px-4 py-2 bg-black border-1 border-stone-500"
:class="{'opacity-50 hover:opacity-100': selectedSite && selectedSite !== '@global' }"
@click="selectedSite = '@global'"
>
<div class="flex flex-row">
<div class="flex-grow pointer">
<b style="color: #fa6">Default settings</b>
</div>
<div>Edit</div>
</div>
<div class="flex flex-row">
<small>
<span style="text-transform: uppercase; font-size: 0.9rem">
Enable extension: <span style="font-size: 0.9rem;" :style="getSiteEnabledColor('@global', 'enable')">{{ getSiteEnabledModes('@global', 'enable') }}</span>
<span v-if="getSiteEnabledModes('@global', 'enable') === 'disabled'" class="text-[0.75em] lowercase text-stone-400">&nbsp;(except on whitelisted sites)</span>
</span>&nbsp;<br/>
Autodetection: <span :style="getSiteEnabledColor('@global', 'enableAard')"><small>{{ getSiteEnabledModes('@global', 'enableAard') }}</small></span>;&nbsp;
Keyboard shortcuts: <span :style="getSiteEnabledColor('@global', 'enableKeyboard')"><small>{{ getSiteEnabledModes('@global', 'enableKeyboard') }}</small></span>;
In-player UI: <span :style="getSiteEnabledColor('@global', 'enableUI')"><small>{{ getSiteEnabledModes('@global', 'enableUI') }}</small></span>;
</small>
</div>
</div>
<div style="margin-top: 1rem; margin-bottom: 1rem;">
<div class="border border-blue-500 text-blue-500 bg-slate-950 px-4 py-2" style="float: none">
<b>NOTE:</b> Sites not on this list use default extension settings.
</div>
</div>
<div class="w-full text-center" style="margin-bottom: -1.25rem">
<b>Other sites</b>
</div>
<div style="margin: 1rem 0rem" class="w-full">
<div class="flex flex-row items-baseline">
<div style="margin-right: 1rem">Search for site:</div>
<div class="input flex-grow">
<input v-model="siteFilter" />
</div>
</div>
</div>
<div class="flex flex-col gap-2">
<div v-for="site of sites" :key="site.key" :class="{'opacity-50 hover:opacity-100': selectedSite && selectedSite !== site.key }">
<SiteListItem
:host="site.key"
:settings="settings"
:isActive="selectedSite === site.key"
@edit="selectedSite = site.key"
></SiteListItem>
</div>
</div>
</div>
<div v-if="selectedSite">
<div class="flex flex-row text-[1.5em] items-center gap-2">
<div @click="selectedSite = null" class="cursor-pointer bg-black rounded-full px-2">
</div>
<div>
Editing {{ selectedSite === '@global' ? 'default settings' : selectedSite }}
</div>
</div>
<div class="border-t-1 border-t-stone-800 mt-4 pt-2">
<SiteExtensionSettings
v-if="selectedSiteSettings"
:settings="settings"
:siteSettings="selectedSiteSettings"
:isDefaultConfiguration="selectedSite === '@global'"
></SiteExtensionSettings>
</div>
</div>
</div>
</template>
<script>
import ExtensionMode from '@src/common/enums/ExtensionMode.enum';
import SiteExtensionSettings from './SiteExtensionSettings.vue';
import SiteListItem from './SiteListItem.vue';
export default {
data() {
return {
selectedSite: null,
siteFilter: '',
filteredSites: []
}
},
props: [
'settings',
'isCompact',
],
components: {
SiteExtensionSettings,
SiteListItem,
},
computed: {
sites() {
if (!this.settings?.active?.sites) {
return [];
} else {
const sites = [];
for (const siteKey in this.settings.active.sites) {
if (!siteKey.startsWith('@') && (!this.siteFilter.trim() || siteKey.includes(this.siteFilter))) {
sites.push({
key: siteKey,
...this.settings.active.sites[siteKey]
})
}
};
sites.sort((a, b) => {
const cmpa = a.key.replace('www.', '');
const cmpb = b.key.replace('www.', '');
return cmpa < cmpb ? -1 : 1;
});
return sites;
}
},
selectedSiteSettings() {
return this.settings?.getSiteSettings({site: this.selectedSite}) ?? null;
}
},
methods: {
getSiteTypeColor(siteType) {
switch (siteType) {
case 'official': return 'color: #fa6';
case 'community': return 'color: rgb(114, 114, 218)';
case 'officially-disabled': return 'color: #f00';
case 'testing': return 'color: #d81';
default: return 'color: rgb(138, 65, 126)'
};
},
getSiteEnabledColor(site, component) {
const status = this.getSiteEnabledModes(site, component);
return status === 'disabled' ? 'color: #f00' : 'color: #1f8';
},
getSiteEnabledModes(site, component) {
if (this.settings?.getSiteSettings({site: site}).data[component]?.normal === ExtensionMode.Enabled) {
return 'always';
}
if (this.settings?.getSiteSettings({site: site}).data[component]?.theater === ExtensionMode.Enabled) {
return 'T + FS';
}
if (this.settings?.getSiteSettings({site: site}).data[component]?.fullscreen === ExtensionMode.Enabled) {
return 'fullscreen';
}
return 'disabled';
}
}
}
</script>
<style lang="postcss" scoped>
.container {
@apply: bg-[#000] px-4 py-2 border border-primary-500;
}
</style>

View File

@ -0,0 +1,656 @@
<template>
<div>
<!-- Enable extension -->
<div class="field">
<div class="label">
Enable <span class="color-emphasis">extension</span>
<span class="sub-label"><br/>under the following conditions:</span>
</div>
<div class="select">
<select
:value="simpleExtensionSettings.enable"
@click="setExtensionMode('enable', $event)"
>
<option
v-if="simpleExtensionSettings.enable === 'complex'"
value="complex"
>
(Site uses advanced settings)
</option>
<template v-if="isDefaultConfiguration">
<option value="disabled">
Disabled by default
</option>
</template>
<template v-else>
<option value="default">
Use default ({{simpleDefaultSettings.enable}})
</option>
<option value="disabled">
Never
</option>
</template>
<option value="fs">
Fullscreen only
</option>
<option value="theater">
Fullscreen and theater mode
</option>
<option value="enabled">
Always
</option>
</select>
</div>
</div>
<!-- The rest of the menu is disabled when extension is disabled -->
<div :class="{disabled: simpleEffectiveSettings.enable === 'disabled' && !isDefaultConfiguration}">
<!-- Enable AARD -->
<div class="field">
<div class="label">
Enable <span class="color-emphasis">automatic aspect ratio detection</span>
<span class="sub-label"><br/>under the following conditions:</span>
</div>
<div class="select">
<select
:value="simpleExtensionSettings.enableAard"
@click="setExtensionMode('enableAard', $event)"
>
<option
v-if="simpleExtensionSettings.enable === 'complex'"
value="complex"
>
(Site uses advanced settings)
</option>
<template v-if="isDefaultConfiguration">
<option value="disabled">
Disabled by default
</option>
</template>
<template v-else>
<option value="default">
Use default ({{simpleDefaultSettings.enableAard}})
</option>
<option value="disabled">
Never
</option>
</template>
<option value="fs">
Fullscreen only
</option>
<option value="theater">
Fullscreen and theater mode
</option>
<option value="enabled">
Always
</option>
</select>
</div>
</div>
<!-- Enable keyboard -->
<div class="field">
<div class="label">
Enable <span class="color-emphasis">keyboard shortcuts</span>
<span class="sub-label"><br/>under the following conditions:</span>
</div>
<div class="select">
<select
:value="simpleExtensionSettings.enableKeyboard"
@click="setExtensionMode('enableKeyboard', $event)"
>
<option
v-if="simpleExtensionSettings.enable === 'complex'"
value="complex"
>
(Site uses advanced settings)
</option>
<template v-if="isDefaultConfiguration">
<option value="disabled">
Disabled by default
</option>
</template>
<template v-else>
<option value="default">
Use default ({{simpleDefaultSettings.enableKeyboard}})
</option>
<option value="disabled">
Never
</option>
</template>
<option value="fs">
Fullscreen only
</option>
<option value="theater">
Fullscreen and theater mode
</option>
<option value="enabled">
Always
</option>
</select>
</div>
</div>
<!-- Enable UI -->
<div class="field">
<div class="label">
Enable <span class="color-emphasis">in-player UI</span>
<span class="sub-label"><br/>under the following conditions:</span>
</div>
<div class="select">
<select
:value="simpleExtensionSettings.enableUI"
@click="setExtensionMode('enableUI', $event)"
>
<template v-if="isDefaultConfiguration">
<option value="disabled">
Disabled by default
</option>
</template>
<template v-else>
<option value="default">
Use default ({{simpleDefaultSettings.enableUI}})
</option>
<option value="disabled">
Never
</option>
</template>
<option value="fs">
Fullscreen only
</option>
<option value="theater">
Always where possible
</option>
</select>
</div>
</div>
<div class="field">
<div class="label">
Use these settings for <span class="color-emphasis">embedded content</span>?
<!-- <span class="sub-label"><br/>under the following conditions:</span> -->
</div>
<div class="select">
<select
:value="siteDefaultForEmbedded"
@click="setSiteOption('applyToEmbeddedContent', $event)"
>
<option :value="EmbeddedContentSettingsOverridePolicy.Always">
Always(-ish)
</option>
<option :value="EmbeddedContentSettingsOverridePolicy.UseAsDefault">
Use as default
</option>
<option :value="EmbeddedContentSettingsOverridePolicy.Never">
Never
</option>
</select>
</div>
</div>
<div class="field">
<div class="label">
Force these settings <span class="color-emphasis">when embedded</span>?
<!-- <span class="sub-label"><br/>under the following conditions:</span> -->
</div>
<div class="select">
<select
:value="siteDefaultOverrideEmbedded"
@click="setSiteOption('overrideWhenEmbedded', $event)"
>
<option :value="true">
Yes
</option>
<option :value="false">
No
</option>
</select>
</div>
</div>
<!-- Default crop -->
<div class="field">
<div class="label">Default crop:</div>
<div class="select">
<select
:value="siteDefaultCrop"
@change="setOption('defaults.crop', $event)"
>
<option
v-if="!isDefaultConfiguration"
:value="JSON.stringify({useDefault: true})"
>
Use default ({{getCommandValue(settings?.active.commands.crop, siteSettings.data.defaults.crop)}})
</option>
<option
v-for="(command, index) of settings?.active.commands.crop"
:key="index"
:value="JSON.stringify(command.arguments)"
>
{{command.label}}
</option>
</select>
</div>
</div>
<div class="hint">This is how extension will crop video if/when autodetection is disabled. Pick 'Reset' option to keep aspect ratio as-is by default.</div>
<!-- Default stretch -->
<div class="field">
<div class="label">Default stretch:</div>
<div class="select">
<select
v-model="siteDefaultStretch"
@change="setOption('defaults.stretch', $event)"
>
<option
v-if="!isDefaultConfiguration"
:value="JSON.stringify({useDefault: true})"
>
Use default ({{getCommandValue(settings?.active.commands.stretch, siteSettings.data.defaults.stretch)}})
</option>
<option
v-for="(command, index) of settings?.active.commands.stretch"
:key="index"
:value="JSON.stringify(command.arguments)"
>
{{command.label}}
</option>
</select>
</div>
</div>
<!-- Default alignment -->
<div class="field">
<div class="label">Default alignment:</div>
<div class="select">
<select
v-model="siteDefaultAlignment"
@change="setOption('defaults.alignment', $event)"
>
<option
v-if="!isDefaultConfiguration"
:value="JSON.stringify({useDefault: true})"
>
Use default ({{getAlignmentLabel(siteSettings.data.defaults.alignment)}})
</option>
<option
v-for="(command, index) of alignmentOptions"
:key="index"
:value="JSON.stringify(command.arguments)"
>
{{command.label}}
</option>
</select>
</div>
</div>
<!-- Crop, et. al. Persistence -->
<div class="field">
<div class="label">Persist crop, stretch, and alignment between videos</div>
<div class="select">
<select
v-model="siteDefaultCropPersistence"
@click="setOption('persistCSA', $event)"
>
<option
v-if="!isDefaultConfiguration"
:value="CropModePersistence.Default"
>
Use default ({{defaultPersistanceLabel}})
</option>
<option :value="CropModePersistence.Disabled">Disabled</option>
<option :value="CropModePersistence.UntilPageReload">Until page reload</option>
<option :value="CropModePersistence.CurrentSession">Current session</option>
<option :value="CropModePersistence.Forever">Always persist</option>
</select>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import ExtensionMode from '@src/common/enums/ExtensionMode.enum';
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum';
import CropModePersistence from '@src/common/enums/CropModePersistence.enum';
import EmbeddedContentSettingsOverridePolicy from '@src/common/enums/EmbeddedContentSettingsOverridePolicy.enum';
export default defineComponent({
data() {
return {
CropModePersistence: CropModePersistence,
ExtensionMode,
EmbeddedContentSettingsOverridePolicy,
alignmentOptions: [
{label: 'Top left', arguments: {x: VideoAlignmentType.Left, y: VideoAlignmentType.Top}},
{label: 'Top center', arguments: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Top}},
{label: 'Top right', arguments: {x: VideoAlignmentType.Right, y: VideoAlignmentType.Top}},
{label: 'Left', arguments: {x: VideoAlignmentType.Left, y: VideoAlignmentType.Center}},
{label: 'Center', arguments: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Center}},
{label: 'Right', arguments: {x: VideoAlignmentType.Right, y: VideoAlignmentType.Center}},
{label: 'Bottom left', arguments: {x: VideoAlignmentType.Left, y: VideoAlignmentType.Bottom}},
{label: 'Bottom center', arguments: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Bottom}},
{label: 'Bottom right', arguments: {x: VideoAlignmentType.Right, y: VideoAlignmentType.Bottom}}
]
}
},
mixins: [
],
props: [
'settings',
'siteSettings',
'isDefaultConfiguration'
],
components: {
},
computed: {
simpleExtensionSettings() {
return {
enable: this.compileSimpleSettings('enable'),
enableAard: this.compileSimpleSettings('enableAard'),
enableKeyboard: this.compileSimpleSettings('enableKeyboard'),
enableUI: this.compileSimpleSettings('enableUI')
}
},
simpleEffectiveSettings() {
return {
enable: this.compileSimpleSettings('enable', 'site-effective'),
enableAard: this.compileSimpleSettings('enableAard', 'site-effective'),
enableKeyboard: this.compileSimpleSettings('enableKeyboard', 'site-effective'),
enableUI: this.compileSimpleSettings('enableUI', 'site-effective')
}
},
simpleDefaultSettings() {
return {
enable: this.getDefaultOptionLabel('enable'),
enableAard: this.getDefaultOptionLabel('enableAard'),
enableKeyboard: this.getDefaultOptionLabel('enableKeyboard'),
enableUI: this.getDefaultOptionLabel('enableUI')
};
},
siteDefaultForEmbedded() {
return this.siteSettings.raw?.applyToEmbeddedContent;
},
siteDefaultOverrideEmbedded() {
return this.siteSettings.raw?.overrideWhenEmbedded ?? false;
},
siteDefaultCrop() {
return this.siteSettings.raw?.defaults?.crop ? JSON.stringify(this.siteSettings.raw?.defaults?.crop) : JSON.stringify({useDefault: true});
},
siteDefaultStretch() {
return this.siteSettings.raw?.defaults?.stretch ? JSON.stringify(this.siteSettings.raw?.defaults?.stretch) : JSON.stringify({useDefault: true});
},
siteDefaultAlignment() {
return this.siteSettings.raw?.defaults?.alignment ? JSON.stringify(this.siteSettings.raw?.defaults?.alignment) : JSON.stringify({useDefault: true});
},
siteDefaultCropPersistence() {
return this.siteSettings.raw?.persistCSA ?? undefined;
},
defaultPersistanceLabel() {
switch (this.siteSettings.defaultSettings.persistCSA) {
case CropModePersistence.CurrentSession:
return 'current session';
case CropModePersistence.Disabled:
return 'disabled';
case CropModePersistence.UntilPageReload:
return 'until page reload';
case CropModePersistence.Forever:
return 'Always persist';
}
return '??';
}
},
mounted() {
this.forceRefreshPage();
},
methods: {
/**
* Compiles our extension settings into more user-friendly options
*/
compileSimpleSettings(component, getFor = 'site') {
let settingsData;
switch (getFor) {
case 'site':
settingsData = this.siteSettings?.raw;
break;
case 'site-effective':
settingsData = this.siteSettings?.data;
break;
case 'default':
settingsData = this.settings.active.sites['@global'];
break;
}
try {
if (
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI')
&& settingsData?.[component]?.theater === ExtensionMode.Disabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Disabled
) {
return 'disabled';
}
if (
( settingsData?.[component]?.normal === ExtensionMode.Default || component === 'enableUI')
&& settingsData?.[component]?.theater === ExtensionMode.Default
&& settingsData?.[component]?.fullscreen === ExtensionMode.Default
) {
// console.log(
// component, 'is set to default because:\n',
// `\nsettingsData[${component}].normal: ${settingsData?.[component]?.normal} || component is enableUI?`, component,
// `\nsettingsData[${component}].theater: ${settingsData?.[component]?.normal}`,
// `\nsettingsData[${component}].fullscreen: ${settingsData?.[component]?.normal}`,
// `\n\n(expected values:`, ExtensionMode.Default
// )
return 'default';
}
if (
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI')
&& settingsData?.[component]?.theater === ExtensionMode.Disabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled
) {
return 'fs';
}
if (
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI')
&& settingsData?.[component]?.theater === ExtensionMode.Enabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled
) {
return 'theater';
}
if (
( settingsData?.[component]?.normal === ExtensionMode.Enabled || component === 'enableUI')
&& settingsData?.[component]?.theater === ExtensionMode.Enabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled
) {
return 'enabled';
}
return 'complex';
} catch (e) {
return 'loading';
}
},
getDefaultOptionLabel(component) {
const componentValue = this.compileSimpleSettings(component, 'default');
if (componentValue === 'loading') {
return componentValue;
}
if (component === 'enableUI') {
switch (componentValue) {
case 'fs':
return 'fullscreen only';
case 'enabled':
case 'theater':
return 'where possible';
case 'disabled':
return 'disabled';
}
}
switch (componentValue) {
case 'fs':
return 'Fullscreen only';
case 'theater':
return 'theater & FS';
case 'enabled':
return 'always';
case 'disabled':
return 'disabled';
case 'default':
return 'complex'
}
},
getCommandValue(availableCommands, command) {
for (const cmd of availableCommands) {
if (JSON.stringify(cmd.arguments) === JSON.stringify(command)) {
return cmd.label;
}
}
return 'Unknown command';
},
getAlignmentLabel(alignment) {
// in case default settings don't have this set
if (!alignment) {
return 'Center'
}
let x, y;
if (alignment.x === VideoAlignmentType.Center) {
x = 'center';
} else if (alignment.x === VideoAlignmentType.Left) {
x = 'left';
} else if (alignment.x === VideoAlignmentType.Right) {
x = 'right';
} else {
x = '??'
}
if (alignment.y === VideoAlignmentType.Center) {
y = 'center';
} else if (alignment.y === VideoAlignmentType.Bottom) {
y = 'bottom';
} else if (alignment.y === VideoAlignmentType.Top) {
y = 'top';
} else {
y = '???'
};
if (x === y) {
return x;
}
return `${y} ${x}`;
},
getOption(option) {
},
async setOption(option, $event) {
const value = $event.target.value;
let commandArguments;
// if argument is json, parse json. Otherwise, pass the value as-is
try {
commandArguments = value !== undefined ? JSON.parse(value) : undefined;
} catch(e) {
commandArguments = value;
}
if (commandArguments.useDefault) {
commandArguments = undefined;
}
await this.siteSettings.set(option, commandArguments, {reload: false});
// we also need to force re-compute all watchers, otherwise UI will lag behind
// actual state of settings until reload
this.forceRefreshPage();
},
forceRefreshPage() {
this._computedWatchers?.simpleExtensionSettings?.run();
this._computedWatchers?.simpleDefaultSettings?.run();
this._computedWatchers?.siteDefaultCrop?.run();
this._computedWatchers?.siteDefaultStretch?.run();
this._computedWatchers?.siteDefaultAlignment?.run();
this._computedWatchers?.siteDefaultCropPersistence?.run();
this._computedWatchers?.defaultPersistanceLabel?.run();
this.$nextTick( () => this.$forceUpdate());
},
setSiteOption(optionPath, event) {
const value = event.target.value;
this.siteSettings.set(optionPath, value);
},
setExtensionMode(component, event) {
const option = event.target.value;
if (option === 'complex') {
return;
}
if (component === 'enable' && !this.isDefaultConfiguration) {
this.setExtensionMode('enableAard', event);
this.setExtensionMode('enableKeyboard', event);
// in enableUI, 'enabled' is unused and 'theater' uses its place
if (option === 'enabled') {
this.setExtensionMode('enableUI', {target: {value: 'theater'}});
} else {
this.setExtensionMode('enableUI', event);
}
}
if (option === 'default') {
return this.siteSettings.set(component, {
normal: ExtensionMode.Default,
theater: ExtensionMode.Default,
fullscreen: ExtensionMode.Default
});
}
if (option === 'disabled') {
return this.siteSettings.set(component, {
normal: ExtensionMode.Disabled,
theater: ExtensionMode.Disabled,
fullscreen: ExtensionMode.Disabled
});
}
if (option === 'enabled') {
return this.siteSettings.set(component, {
normal: ExtensionMode.Enabled,
theater: ExtensionMode.Enabled,
fullscreen: ExtensionMode.Enabled
});
}
if (option === 'theater') {
return this.siteSettings.set(component, {
normal: ExtensionMode.Disabled,
theater: ExtensionMode.Enabled,
fullscreen: ExtensionMode.Enabled
});
}
if (option === 'fs') {
return this.siteSettings.set(component, {
normal: ExtensionMode.Disabled,
theater: ExtensionMode.Disabled,
fullscreen: ExtensionMode.Enabled
});
}
}
}
});
</script>
<style scoped>
</style>

View File

@ -0,0 +1,79 @@
<template>
<div
class="p-2 border border-stone-600 hover:border-primary-400 bg-black hover:bg-stone-900"
:class="{'!border-primary-400 text-primary-300': isActive}"
>
<div class="flex flex-row cursor-default w-full">
<div class="grow">
<b>{{ host }}</b>
<span :style="getSiteTypeColor(siteSettings?.data?.type)">
(config: {{siteSettings?.data?.type ?? 'unknown'}})
</span>
</div>
<div class="cursor-pointer" @click="triggerEdit()">Edit</div>
</div>
<div v-if="this.siteSettings?.usesSettingsFor">
<div v-if="this.siteSettings.usesSettingsFor === '@global'">Uses default settings</div>
<div v-else>Uses settings for: <span class="info-color">{{this.siteSettings.usesSettingsFor}}</span></div>
</div>
<div class="flex flex-row">
<small>
Enabled: <span :style="getSiteEnabledColor(host, 'enable')"><small>{{ getSiteEnabledModes(host, 'enable') }}</small></span>;&nbsp;
Autodetect: <span :style="getSiteEnabledColor(host, 'enableAard')"><small>{{ getSiteEnabledModes(host, 'enableAard') }}</small></span>;&nbsp;
kbd: <span :style="getSiteEnabledColor(host, 'enableKeyboard')"><small>{{ getSiteEnabledModes(host, 'enableKeyboard') }}</small></span>
UI: <span :style="getSiteEnabledColor(host, 'enableUI')"><small>{{ getSiteEnabledModes(host, 'enableUI') }}</small></span>
</small>
</div>
</div>
</template>
<script>
import ExtensionMode from '@src/common/enums/ExtensionMode.enum';
export default {
data() {
return {
siteSettings: undefined,
supportType: undefined
}
},
props: [
'settings',
'parentHost',
'host',
'isActive',
],
created() {
this.siteSettings = this.settings.getSiteSettings({site: this.host, isIframe: this.parentHost && this.host !== this.parentHost, parentHostname: this.parentHost});
},
methods: {
getSiteTypeColor(siteType) {
switch (siteType) {
case 'official': return 'color: #fa6';
case 'community': return 'color: rgb(114, 114, 218)';
case 'officially-disabled': return 'color: #f00';
case 'testing': return 'color: #d81';
default: return 'color: rgb(138, 65, 126)'
};
},
getSiteEnabledColor(site, component) {
const status = this.getSiteEnabledModes(site, component);
return status === 'disabled' ? 'color: #f00' : 'color: #1f8';
},
getSiteEnabledModes(site, component) {
if (this.siteSettings?.normal === ExtensionMode.Enabled) {
return 'always';
}
if (this.siteSettings?.data[component]?.theater === ExtensionMode.Enabled) {
return 'T + FS';
}
if (this.siteSettings?.data[component]?.fullscreen === ExtensionMode.Enabled) {
return 'fullscreen';
}
return '❌';
},
triggerEdit() {
this.$emit('edit');
}
}
}
</script>

View File

@ -0,0 +1,192 @@
<template>
<div class="flex flex-row items-center">
<div
class="shortcut-button"
@click="editShortcut()"
>
<template v-if="!editing">
<span v-if="!command?.shortcut" class="italic text-[0.9em] text-stone-500">
(not assigned)
</span>
<span v-else>
{{shortcutDisplay}}
</span>
</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>
</template>
<script lang="ts">
import KeyboardShortcutParser from '@src/common/js/KeyboardShortcutParser';
import { defineComponent } from 'vue';
export default defineComponent({
props: {
command: Object,
settings: Object,
},
data() {
return {
currentKeypress: undefined,
currentKey: undefined,
editing: false,
}
},
computed: {
shortcutDisplay() {
if (!this.command?.shortcut) {
return '(no shortcut)'
}
return KeyboardShortcutParser.parseShortcut(this.command.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.command.shortcut = this.currentKey;
this.settings?.saveWithoutReload();
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" scoped>
@import './KeyboardShortcutEntry.css';
.center-text {
text-align: center;
}
.dark {
opacity: 50%;
}
.hidden-input {
position: absolute;
z-index: -9999;
opacity: 0;
}
</style>

View File

@ -0,0 +1,30 @@
@import '../../../../main.css'; /** postcss processor doesn't support aliases */
.keyboard-shortcut-row {
@apply py-2 px-4
even:bg-black/25
flex flex-row items-center gap-4
border-y-1 border-y-transparent hover:border-y-stone-700 hover:bg-stone-900;
&:hover {
.hover-hide {
@apply hidden;
}
}
&:not(:hover) {
.hover-show {
@apply hidden;
}
}
}
.keyboard-shortcut-row, .hover-shortcut {
.shortcut-button {
@apply px-4 text-[0.9em] border-1 border-transparent
cursor-pointer select-none;
&:hover {
@apply bg-black border-1 border-stone-500 rounded-sm
}
}
}

View File

@ -0,0 +1,97 @@
<template>
<div class="keyboard-shortcut-row">
<div class="w-[12em] text-stone-300">
<span v-if="indent" :style="{width: indent + 'rem'}" class="inline-block"></span>{{command.label}}
</div>
<div class="w-[18em] flex flex-row items-center">
<EditShortcutButton
:settings="settings"
:command="command"
>
</EditShortcutButton>
</div>
<div v-if="state !== 'removing'" class="justify-self-end cursor-pointer hover-show flex flex-row items-center text-[0.8em]" @click="clearShortcut(command)">
<mdicon class="text-red-500" name="close" :size="12"></mdicon> <span class="pl-1">Clear</span>
</div>
<div v-if="removable && state !== 'removing'" class="justify-self-end text-red-500 cursor-pointer hover-show flex flex-row items-center text-[0.8em]" @click="removeAction(command)">
<mdicon name="delete" :size="12"></mdicon> <span class="pl-1">Remove action</span>
</div>
<div v-if="state === 'removing'" class="justify-self-end flex flex-row items-center gap-2 text-[0.8em]">
<div>Remove option?</div>
<div @click="cancelRemoval()" class="cursor-pointer flex flex-row items-center gap-1">
<mdicon name="close" :size="12"></mdicon> <span>No, do not remove</span>
</div>
<div @click="confirmRemoval(command)" class="cursor-pointer text-red-500 flex flex-row items-center gap-1">
<mdicon name="remove" :size="12"></mdicon> <span>Yes</span>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import KeyboardShortcutParserMixin from '../../../../csui/src/utils/KeyboardShortcutParserMixin';
import EditShortcutButton from './EditShortcutButton.vue';
export default defineComponent({
components: {
EditShortcutButton,
},
mixins: [
KeyboardShortcutParserMixin
],
props: [
'settings', // not used only passed on
'command',
'type',
'indent',
'removable',
],
data() {
return {
state: null,
}
},
methods: {
clearShortcut(command) {
command.shortcut = undefined;
this.settings.saveWithoutReload();
},
removeAction(command) {
if (!this.removable) {
return;
}
this.state = 'removing';
},
confirmRemoval(command) {
console.info('confirmong removal:', command)
const commandsArray = this.settings.active.commands[this.type];
let index = commandsArray.indexOf(command);
let reload = false;
// let's try fallback
if (index === -1) {
index = commandsArray.findIndex(x => JSON.stringify(x) === JSON.stringify(command))
reload = true;
}
console.log('command index:', index, this.settings.active.commands[this.type]);
if (index !== -1) {
commandsArray.splice(index, 1);
if (reload) {
this.settings.save();
} else {
this.settings.saveWithoutReload();
}
}
this.state = null;
},
cancelRemoval() {
this.state = null;
}
}
});
</script>
<style lang="postcss" scoped>
@import './KeyboardShortcutEntry.css';
</style>

View File

@ -0,0 +1,109 @@
<template>
<div class="compact-form">
<div class="field">
<div class="label"></div>
<div class="text-[0.9em] font-semibold">
Add new ratio:
</div>
</div>
<div class="field">
<div class="label">Aspect ratio:</div>
<div class="input">
<input
v-model="command.aspectRatio"
placeholder="Enter aspect ratio ..."
@blur="updateLabel()"
>
</div>
</div>
<div class="field">
<div class="label">Label:</div>
<div class="input">
<input
v-model="command.label"
placeholder="Button label"
>
</div>
</div>
<div class="field">
<div class="label">Shortcut:</div>
<div class="hover-shortcut">
<EditShortcutButton
:command="command"
></EditShortcutButton>
</div>
</div>
<div class="flex flex-row gap-1 justify-end -mt-2 text-[0.9em] text-stone-400 font-bold hover:font-bold">
<div
class="flex flex-row items-center px-2 py-1 hover:text-white cursor-pointer"
:class="{'opacity-50 pointer-events-none': !command.aspectRatio || !command.label}"
@click="add()"
>
<mdicon name="content-save" :size="16"></mdicon>
Add
</div>
<div
class="flex flex-row items-center px-2 py-1 hover:text-red-500 cursor-pointer"
@click="cancel()"
>
<mdicon name="close" :size="16"></mdicon>
Cancel
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import EditShortcutButton from './EditShortcutButton.vue';
export default defineComponent({
components: {
EditShortcutButton,
},
data() {
return {
command: {
aspectRatio: '',
label: '',
shortcut: undefined as any,
}
}
},
methods: {
updateLabel() {
this.command.label = this.command.aspectRatio;
},
add() {
console.log('emitting add', this.command);
this.$emit('add', this.command);
},
cancel() {
this.$emit('cancel');
}
}
});
</script>
<style lang="postcss" scoped>
@import './KeyboardShortcutEntry.css';
.compact-form {
@apply flex flex-col gap-1;
.field {
@apply flex flex-row items-center gap-2 mx-0 my-0 px-0 py-0;
.label {
@apply w-24 text-right;
}
.input {
@apply flex-1;
input {
@apply w-full placeholder:text-[0.8rem] placeholder:text-stone-500;
}
}
}
}
</style>

View File

@ -0,0 +1,259 @@
<template>
<div class="flex flex-col relative w-full">
<h2 class="text-[1.75em]">Keyboard shortcuts</h2>
<p class="text-stone-500 text-[0.8em]">You can edit keyboard shortcuts here. If the aspect ratio you're looking for doesn't appear on the list, you can add it.</p>
<div class="flex flex-row gap-2 w-full justify-end mt-4">
<button @click="clearShortcuts()"><mdicon name="close" :size="16"></mdicon>Clear all shortcuts</button>
<button @click="resetToDefault()"><mdicon name="reload" :size="16"></mdicon>Reset to default</button>
</div>
<div class="flex flex-col w-full">
<div class="keyboard-shortcut-row bg-stone-900 mt-4 pt-2 pb-1 border-b-1 border-b-stone-700">
<div class="w-[12em] text-center border-r-1 border-r-stone-700">Command</div>
<div class="w-[12em] text-center">Keystroke</div>
</div>
<!-- CROP -->
<div
class="keyboard-shortcut-row"
>
<div
class="w-[32em] flex flex-row gap-2 items-center"
@click="toggleGroupState('crop')"
>
<mdicon
v-if="!groupVisible.crop"
name="chevron-right"
:size="16"
/>
<mdicon
v-if="groupVisible.crop"
name="chevron-down"
:size="16"
/>
<div>Crop<br/>
<p class="text-[0.8em] text-stone-500">Safe crop options. Prefers to leave empty space instead of cutting off video edges.</p>
</div>
</div>
</div>
<div>
<!-- Here just to break even/odd -->
</div>
<template v-if="groupVisible.crop">
<KeyboardShortcutEntry
v-for="command in settings.active.commands.crop"
:key="command"
type="crop"
:indent="2"
:command="command"
:settings="settings"
:removable="command?.arguments?.type === AspectRatioType.Fixed"
></KeyboardShortcutEntry>
<div
v-if="state !== 'add-crop'"
class="ml-12 w-[16em] flex flex-row text-[0.8rem] gap-2 my-2 font-bold items-center hover:text-white cursor-pointer"
@click="startAdding('crop')"
>
<mdicon name="plus" :size="16"></mdicon><div>Add new ratio</div>
</div>
<template v-else>
<NewAspectRatioForm
type="crop"
:settings="settings"
@cancel="state = null"
@add="addAction('crop', $event)"
></NewAspectRatioForm>
</template>
</template>
<!-- ZOOM -->
<div
class="keyboard-shortcut-row"
>
<div
class="w-[32em] flex flex-row gap-2 items-center"
@click="toggleGroupState('zoom')"
>
<mdicon
v-if="!groupVisible.zoom"
name="chevron-right"
:size="16"
/>
<mdicon
v-if="groupVisible.zoom"
name="chevron-down"
:size="16"
/>
<div>Zoom<br/>
<p class="text-[0.8em] text-stone-500">Controls zoom. Will cut off parts of video in order to achieve desired zoom.</p>
</div>
</div>
</div>
<template v-if="groupVisible.zoom">
<KeyboardShortcutEntry
v-for="command in settings.active.commands.zoom"
:key="command"
type="zoom"
:indent="2"
:command="command"
:settings="settings"
:removable="command?.arguments?.type === AspectRatioType.Fixed"
></KeyboardShortcutEntry>
<div class="ml-12 w-[16em] flex flex-row text-[0.8rem] gap-2 my-2 font-bold items-center hover:text-white cursor-pointer">
<mdicon name="plus" :size="16"></mdicon><div>Add new</div>
</div>
</template>
<!-- STRETCH -->
<div
class="keyboard-shortcut-row"
>
<div
class="w-[32em] flex flex-row gap-2 items-center"
@click="toggleGroupState('stretch')"
>
<mdicon
v-if="!groupVisible.stretch"
name="chevron-right"
:size="16"
/>
<mdicon
v-if="groupVisible.stretch"
name="chevron-down"
:size="16"
/>
<div>Stretch<br/>
<p class="text-[0.8em] text-stone-500">Control stretching.</p>
</div>
</div>
</div>
<template v-if="groupVisible.stretch">
<KeyboardShortcutEntry
v-for="command in settings.active.commands.stretch"
:key="command"
type="stretch"
:indent="2"
:command="command"
:settings="settings"
:removable="command?.arguments?.type === StretchType.FixedSource"
></KeyboardShortcutEntry>
<div class="ml-12 w-[16em] flex flex-row text-[0.8rem] gap-2 my-2 font-bold items-center hover:text-white cursor-pointer">
<mdicon name="plus" :size="16"></mdicon><div>Add new</div>
</div>
</template>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import KeyboardShortcutEntry from './Components/KeyboardShortcutEntry.vue';
import AspectRatioType from '@src/common/enums/AspectRatioType.enum';
import StretchType from '@src/common/enums/StretchType.enum';
import NewAspectRatioForm from './Components/NewAspectRatioForm.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 defineComponent({
components: {
KeyboardShortcutEntry,
NewAspectRatioForm,
CropOptionsPanel,
StretchOptionsPanel,
ZoomOptionsPanel,
},
data() {
return {
AspectRatioType: AspectRatioType,
StretchType: StretchType,
groupVisible: {
crop: true,
stretch: true,
zoom: true,
},
state: null,
}
},
mixins: [
],
props: [
'settings', // required for buttons and actions, which are global
],
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: {
toggleGroupState(groupName: string) {
this.groupVisible[groupName] = !this.groupVisible[groupName];
},
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;
},
addAction(segment: 'stretch' | 'crop' | 'zoom', newCommand) {
const actionMap = {
stretch: 'set-stretch',
crop: 'set-ar',
zoom: 'set-ar-zoom',
};
const typeMap = {
stretch: StretchType.FixedSource,
crop: AspectRatioType.Fixed,
zoom: AspectRatioType.Fixed,
};
const command = {
action: actionMap[segment],
label: newCommand.label,
arguments: {
type: typeMap[segment],
ratio: newCommand.ratio
},
shortcut: newCommand.shortcut,
};
this.settings.active.commands[segment].push(command);
this.settings.saveWithoutReload();
this.state = null;
},
clearShortcuts() {
for (const commandList of ['stretch', 'crop', 'zoom']) {
for (const command of this.settings.active.commands[commandList]) {
command.shortcut = undefined;
}
}
this.settings.saveWithoutReload();
},
resetToDefault() {
this.settings.active.commands = JSON.parse(JSON.stringify(this.settings.default.commands));
this.settings.save();
},
startAdding(type: string) {
this.state = `add-${type}`;
},
}
});
</script>
<style lang="postcss" scoped>
@import './Components/KeyboardShortcutEntry.css';
</style>

View File

@ -0,0 +1,268 @@
<template>
<div
class="flex flex-col uw-clickable w-full h-full main-window relative"
>
<div class="flex flex-row">
<div class="settings-categories">
<div
v-for="tab of tabs"
:key="tab.id"
>
<div
v-if="!tab.hidden"
class="tab"
:class="{
'active': tab.id === selectedTab,
'highlight-tab': tab.highlight,
}"
@click="selectTab(tab.id)"
>
<div class="label">
{{tab.label}}
</div>
<div class="icon-container">
<mdicon
v-if="tab.icon"
:name="tab.icon"
:size="32"
/>
</div>
</div>
</div>
</div>
<div class="content flex flex-col">
<!-- autodetection warning -->
<div class="warning-area">
<div
v-if="statusFlags.hasDrm"
class="warning-box"
>
<div class="icon-container">
<mdicon name="alert" :size="32" />
</div>
<div>
This site is blocking automatic aspect ratio detection. You will have to adjust aspect ratio manually.<br/>
<a>Learn more ...</a>
</div>
</div>
</div>
<div class="flex flex-row panel-content">
<!-- Panel section -->
<!-- <VideoSettings
v-if="selectedTab === 'videoSettings'"
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
:site="site"
></VideoSettings> -->
<OtherSiteSettings
v-if="selectedTab === 'extensionSettings'"
:settings="settings"
:enableSettingsEditor="true"
></OtherSiteSettings>
<KeyboardShortcutSettings
v-if="selectedTab === 'keyboardShortcuts'"
:settings="settings"
:eventBus="eventBus"
></KeyboardShortcutSettings>
<PlayerDetectionPanel
v-if="selectedTab === 'playerDetection'"
:siteSettings="siteSettings"
:eventBus="eventBus"
:site="site"
>
</PlayerDetectionPanel>
<PlayerUiSettings
v-if="selectedTab === 'playerUiSettings'"
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
>
</PlayerUiSettings>
<AutodetectionSettingsPanel
v-if="selectedTab === 'autodetectionSettings'"
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
:site="site"
>
</AutodetectionSettingsPanel>
<DebugPanel
v-if="selectedTab === 'debugging'"
:settings="settings"
:eventBus="eventBus"
:site="site"
></DebugPanel>
<ChangelogPanel
v-if="selectedTab === 'changelog'"
:settings="settings"
></ChangelogPanel>
<AboutPanel
v-if="selectedTab === 'about'"
>
</AboutPanel>
<!-- <ResetBackupPanel
v-if="selectedTab === 'resetBackup'"
:settings="settings"
>
</ResetBackupPanel> -->
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import OtherSiteSettings from '@components/ExtensionSettings/Panels/OtherSiteSettings.vue';
import KeyboardShortcutSettings from '@components/KeyboardShortcuts/KeyboardShortcutSettings.vue';
import DebugPanel from '../../csui/src/PlayerUiPanels/DebugPanel.vue'
import AutodetectionSettingsPanel from '../../csui/src/PlayerUiPanels/AutodetectionSettingsPanel.vue'
import PlayerDetectionPanel from '../../csui/src/PlayerUiPanels/PlayerDetectionPanel.vue'
import VideoSettings from '../../csui/src/PlayerUiPanels/VideoSettings.vue'
import BrowserDetect from '../../ext/conf/BrowserDetect'
import ChangelogPanel from '../../csui/src/PlayerUiPanels/ChangelogPanel.vue'
import AboutPanel from '@csui/src/PlayerUiPanels/AboutPanel.vue'
import PlayerUiSettings from '../../csui/src/PlayerUiPanels/PlayerUiSettings.vue'
import ResetBackupPanel from '../../csui/src/PlayerUiPanels/ResetBackupPanel.vue'
import SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue'
export default defineComponent({
components: {
OtherSiteSettings,
KeyboardShortcutSettings,
VideoSettings,
PlayerDetectionPanel,
AutodetectionSettingsPanel,
DebugPanel,
PlayerUiSettings,
ChangelogPanel,
AboutPanel,
SupportLevelIndicator,
ResetBackupPanel,
},
mixins: [],
data() {
return {
statusFlags: {
hasDrm: undefined,
},
tabs: [
// {id: 'videoSettings', label: 'Video settings', icon: 'crop'},
{id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' },
{id: 'autodetectionSettings', label: 'Autodetection options', icon: 'auto-fix'},
{id: 'playerUiSettings', label: 'UI settings', icon: 'movie-cog-outline' },
{id: 'keyboardShortcuts', label: 'Keyboard shortcuts', icon: 'keyboard-outline' },
{id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
// {id: 'advancedOptions', label: 'Advanced options', icon: 'cogs' },
{id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
{id: 'about', label: 'About', icon: 'information-outline'},
{id: 'debugging', label: 'Debugging', icon: 'bug-outline', hidden: true},
],
selectedTab: 'extensionSettings',
BrowserDetect: BrowserDetect,
preventClose: false,
siteSettings: null,
}
},
props: [
'settings',
'eventBus',
'logger',
'inPlayer',
'site',
'defaultTab'
],
computed: {
// LPT: NO ARROW FUNCTIONS IN COMPUTED,
// IS SUPER HARAM
// THINGS WILL NOT WORK IF YOU USE ARROWS
siteSupportLevel() {
return (this.site && this.siteSettings) ? this.siteSettings.data.type || 'no-support' : 'waiting';
}
},
created() {
this.settings.listenAfterChange(this.setDebugTabVisibility);
if (this.defaultTab) {
this.selectedTab = this.defaultTab;
}
this.siteSettings = this.settings.getSiteSettings({site: this.site});
this.tabs.find(x => x.id === 'changelog').highlight = !this.settings.active?.whatsNewChecked;
this.eventBus?.subscribe(
'uw-show-ui',
{
source: this,
function: () => {
if (this.inPlayer) {
return; // show-ui is only intended for global overlay
}
},
}
)
this.setDebugTabVisibility();
},
destroyed() {
this.settings.removeListenerAfterChange(this.setDebugTabVisibility);
this.eventBus?.unsubscribeAll(this);
},
methods: {
/**
* Gets URL of the browser settings page (i think?)
*/
getUrl(url) {
return BrowserDetect.getURL(url);
},
selectTab(tab) {
console.log("Selecting tab", tab);
this.selectedTab = tab;
},
setPreventClose(bool) {
this.preventClose = bool;
this.$emit('preventClose', bool);
},
setDebugTabVisibility() {
const debugTab = this.tabs.find( x => x.id === 'debugging');
if (debugTab) {
debugTab.hidden = !this.settings.active.ui.devMode;
}
}
}
});
</script>
<style lang="postcss" scoped>
@import '../../main.css'; /** postcss processor doesn't support aliases */
.settings-categories {
@apply w-[20em] max-w-[20em] flex flex-col grow-0 shrink-0 mr-4 border-r border-r-stone-800 text-right;
.tab {
@apply flex flex-row gap-4 px-4 py-2 justify-end items-center
text-[1.125em] text-stone-300 text-right font-mono
cursor-pointer
hover:bg-stone-800
hover:text-primary-200;
.label {
@apply grow-0;
}
&.active {
@apply !bg-transparent !text-primary-300 bg-gradient-to-r from-transparent to-black border-none hover:!text-primary-200;
}
}
}
pre {
white-space: pre-wrap;
}
</style>

View File

@ -0,0 +1,296 @@
<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

@ -0,0 +1,15 @@
import KeyboardShortcutParser from '@src/common/js/KeyboardShortcutParser';
export default {
methods: {
/**
* Parses command's keyboard shortcut into human-readable label
*/
getKeyboardShortcutLabel(command): string {
if (! command.shortcut) {
return '';
}
return KeyboardShortcutParser.parseShortcut(command.shortcut);
},
}
}

View File

@ -61,7 +61,7 @@
</template>
<script>
import BrowserDetect from '../../ext/conf/BrowserDetect';
import BrowserDetect from '../../../ext/conf/BrowserDetect';
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator';
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
@ -187,8 +187,6 @@ html, body {
.tabitem {
font-variant: normal;
// font-size: 0.69em;
// margin-left: 16px;
border-left: transparent 3px solid;
padding-left: 12px;
margin-left: -10px;
@ -217,8 +215,6 @@ html, body {
}
.popup {
// max-width: 780px;
// width: 800px;
height: 600px;
}
</style>

View File

@ -0,0 +1,62 @@
<template>
<div class="w-full flex flex-row w-full h-full justify-center items-center py-4 px-16">
<!-- page content -->
<div class="w-full max-w-[1920px]">
<h1 class="text-[3em]">Ultrawidify settings</h1>
<div v-if="!settingsInitialized" class="flex flex-row w-full justify-center items-center">
Loading settings...
</div>
<SettingsWindowContent v-else
:settings="settings"
:logger="logger"
:inPlayer="false"
:site="null"
>
</SettingsWindowContent>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import BrowserDetect from '../../../ext/conf/BrowserDetect';
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator';
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
import Settings from '@src/ext/lib/settings/Settings';
import SettingsWindowContent from '@components/SettingsWindowContent.vue';
export default defineComponent({
components: {
SettingsWindowContent
},
data () {
return {
settings: undefined as Settings | undefined,
logger: undefined as ComponentLogger | undefined,
logAggregator: undefined as LogAggregator | undefined,
settingsInitialized: false,
}
},
async created() {
this.logAggregator = new LogAggregator('');
this.logger = new ComponentLogger(this.logAggregator, 'App.vue');
this.settings = new Settings({
logAggregator: this.logAggregator,
onSettingsChanged: () => this.updateConfig()
});
await this.settings.init();
this.settingsInitialized = true;
},
methods: {
updateConfig() {
this.settings.init();
this.$nextTick( () => this.$forceUpdate());
}
}
});
</script>

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en"; style="position: relative">
<head>
<meta charset="UTF-8">
<meta name="color-scheme" content="light dark">
<title>Ultrawidify - settings</title>
<!-- <link rel="stylesheet" href="csui.css"> -->
<% if (NODE_ENV === 'development') { %>
<!-- Load some resources only in development environment -->
<% } %>
</head>
<body class="uw-ultrawidify-container-root">
<div id="app"></div>
<script src="settings.js"></script>
</body>
</html>

View File

@ -0,0 +1,13 @@
import { createApp } from 'vue';
import App from './App';
import mdiVue from 'mdi-vue/v3';
import * as mdijs from '@mdi/js';
import '@src/main.css';
// NOTE — this is in-player interface for ultrawidify
// it is injected into the page in UI.init()
createApp(App)
.use(mdiVue, {icons: mdijs})
.mount('#app');

View File

@ -180,8 +180,6 @@ body {
.tabitem {
font-variant: normal;
// font-size: 0.69em;
// margin-left: 16px;
border-left: transparent 3px solid;
padding-left: 12px;
margin-left: -10px;
@ -210,8 +208,6 @@ body {
}
.popup {
// max-width: 780px;
// width: 800px;
height: 600px;
}
</style>

BIN
src/ui/res/fonts/Heebo.ttf Normal file

Binary file not shown.

View File

@ -0,0 +1,93 @@
Copyright 2014 The Heebo Project Authors (https://github.com/OdedEzer/heebo)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -0,0 +1,93 @@
Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,18 @@
@import "tailwindcss";
@import "tailwindcss/utilities";
@layer base {
button {
@apply
flex flex-row items-center gap-1
px-4 py-1
bg-black
text-white font-medium
border-1 border-stone-700
cursor-pointer;
&:hover {
@apply border-primary-500 text-primary-300;
}
}
}

View File

@ -0,0 +1,25 @@
@font-face {
font-family: "Heebo";
src: url("../fonts/Heebo.ttf") format("truetype-variations");
font-weight: 100 900;
font-stretch: 75% 125%;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Source Code Pro";
src: url("../fonts/SourceCodePro.ttf") format("truetype-variations");
font-weight: 200 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Source Code Pro";
src: url("../fonts/SourceCodePro-Italic.ttf") format("truetype-variations");
font-weight: 200 900;
font-style: italic;
font-display: swap;
}

135
src/ui/res/styles/forms.css Normal file
View File

@ -0,0 +1,135 @@
@import "tailwindcss";
@import "tailwindcss/utilities";
.input, .range-input {
@apply py-1 px-2 bg-black/25 border-b border-white/50 w-full relative;
&:active, &:focus, &:focus-within {
@apply border-b-1 border-b-primary-400;
}
&.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 {
@apply flex flex-row;
* {
@apply mx-2;
}
input {
max-width: 5rem;
}
input[type=range] {
max-width: none;
}
}
.field {
@apply flex flex-row w-full my-2 items-center;
&.l2 {
margin-left: 4rem;
}
.label {
flex: 0 0 25%;
min-width: 16rem;
text-align: right;
padding-right: 1rem;
.color-emphasis {
@apply text-primary-400;
}
.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 {
@apply bg-stone-950 border border-primary-800 text-white px-4 py-1;
min-width: 12px;
max-width: 100%;
outline: none;
font: inherit;
font-size: inherit;
option {
@apply bg-stone-950 text-white;
&:checked {
@apply text-primary-400;
}
&:before {
content: ">";
display: none;
background-color: #f41 !important;
font-size: 5rem;;
}
&:hover {
@apply text-black bg-primary-400;
}
}
}
}
}
.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;
}
}

View File

@ -17,7 +17,7 @@ const server = new UWServer();
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === "update") {
chrome.tabs.create({
url: chrome.runtime.getURL("install/updated/updated.html")
url: chrome.runtime.getURL("ui/pages/updated/index.html")
});
}
});

28
tailwind.config.json Normal file
View File

@ -0,0 +1,28 @@
{
"content": [
"./src/**/*.{vue,js,ts,jsx,tsx}"
],
"theme": {
"extend": {
"colors": {
"primary": {
"50": "#ffddbe",
"100": "#ffcda0",
"200": "#ffbd83",
"300": "#ffac66",
"400": "#ff9a4a",
"500": "#ff872c",
"600": "#de7622",
"700": "#be6518",
"800": "#9f540e",
"900": "#824406",
"950": "#663400"
}
},
"fontFamily": {
"sans": ["Heebo", "ui-sans-serif", "system-ui"],
"mono": ["Source Code Pro", "ui-monospace", "monospace"]
}
}
}
}

View File

@ -1,9 +1,10 @@
{
"compilerOptions": {
"module": "esnext",
"moduleResolution": "node",
"outDir": "./ts-out",
"allowJs": true,
"target": "es2018",
"target": "esnext",
"types": [
"chrome",
"node"
@ -12,6 +13,15 @@
"node_modules/@types",
"node_modules/web-ext-types"
],
"baseUrl": "./src",
"allowSyntheticDefaultImports": true,
"skipDefaultLibCheck": true,
"esModuleInterop": true,
"paths": {
"@src/*": [ "*" ],
"@csui/*": [ "csui/*" ],
"@/*": [ "*" ]
}
},
"include": [ "./src/**/*" ],
}

View File

@ -1,9 +1,8 @@
const webpack = require('webpack');
const ejs = require('ejs');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const WebpackShellPlugin = require('webpack-shell-plugin');
const WebpackShellPluginNext = require('webpack-shell-plugin-next');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ChromeExtensionReloader = require('webpack-chrome-extension-reloader');
const { VueLoaderPlugin } = require('vue-loader');
const path = require('path');
@ -18,9 +17,12 @@ const config = {
'ext/uw': './ext/uw.js',
'uw-bg': './uw-bg.js',
'csui/csui-popup': './csui/csui-popup.js',
'csui/csui': './csui/csui.js',
// 'install/first-time/first-time':'./install/first-time/first-time.js',
'install/updated/updated': './install/updated/updated.js',
// 'csui/settings': './csui/settings.js',
// 'csui/csui': './csui/csui.js',
// 'ui/pages/updated/updated': './ui/pages/updated/updated.js',
'ui/pages/updated/updated': './ui/pages/updated/updated.js',
'ui/pages/settings/settings': './ui/pages/settings/settings.js',
},
output: {
path: __dirname + `/dist-${process.env.BROWSER == 'firefox' ? 'ff' : process.env.BROWSER}`,
@ -33,6 +35,9 @@ const config = {
alias: {
'@src': path.resolve(__dirname, 'src'),
'@csui': path.resolve(__dirname, 'src/csui'),
'@ui': path.resolve(__dirname, 'src/ui'),
'@components': path.resolve(__dirname, 'src/ui/components'),
'@': path.resolve(__dirname, 'src'),
},
// maybe we'll move vue stuff to TS some day, but today is not the day
extensions: [
@ -44,166 +49,161 @@ const config = {
rules: [
{
test: /\.ts$/,
loader: 'ts-loader',
exclude: /node_modules/
},
{
test: /\.vue$/,
loaders: 'vue-loader',
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.(sc|c)ss$/,
use: [
// MiniCssExtractPlugin.loader,
'vue-style-loader',
{
loader: 'css-loader',
// modules: {
// localIdentName: "[name]-[hash]"
// }
// options: {
// modules: true,
// // localIdentName: "🔶uw_[local]"
// localIdentName: "[name]-[hash]"
// // localIdentName: "uw_[local]"
// }
},
{
loader: 'sass-loader',
loader: 'ts-loader',
options: {
sourceMap: true
appendTsSuffixTo: [/\.vue$/],
transpileOnly: true,
}
}
],
},
// {
// test: /\.scss$/,
// use: [
// // MiniCssExtractPlugin.loader,
// 'css-loader',
// // {
// // loader: 'css-loader',
// // // options: {
// // // modules: true,
// // // localIdentName: "🔶uw_[local]"
// // // }
// // },
// 'sass-loader'
// ],
// },
{
test: /\.(png|jpg|gif|svg|ico)$/,
loader: 'file-loader',
options: {
name: '[path][name].[ext]',
},
},
{
test: /\.(woff(2)?)$/,
loader: 'file-loader',
options: {
name: '[path][name].[ext]',
},
test: /\.vue$/,
use: [
{
loader: 'vue-loader'
}
]
},
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader"
use: [
{
loader: 'babel-loader'
}
]
},
],
{
test: /\.(sc|c|postc)ss$/,
use: [
'vue-style-loader',
{
loader: 'css-loader',
options: {
// Uncomment if you want CSS modules
// modules: {
// localIdentName: "[name]-[hash]"
// }
}
},
'postcss-loader',
]
},
{
test: /\.(png|jpg|webp|gif|svg|ico)$/,
type: 'asset/resource',
generator: {
filename: '[path][name][ext]' // Webpack 5 uses generator.filename
}
},
{
test: /\.(woff(2)?)$/,
type: 'asset/resource',
generator: {
filename: '[path][name][ext]'
}
}
]
},
plugins: [
new VueLoaderPlugin(),
new MiniCssExtractPlugin({
filename: '[name].css',
}),
new CopyWebpackPlugin([
{ from: 'res', to: 'res', ignore: ['css', 'css/**']},
{ from: 'ext', to: 'ext', ignore: ['conf/*', 'lib/**']},
{ from: 'csui', to: 'csui', ignore: ['src']},
{ from: 'install', to: 'install' },
new CopyWebpackPlugin({
patterns: [
{ from: 'res', to: 'res', globOptions: { ignore: ['css', 'css/**'] } },
{ from: 'ext', to: 'ext', globOptions: { ignore: ['conf/*', 'lib/**'] } },
{ from: 'csui', to: 'csui', globOptions: { ignore: ['src'] }},
{ from: 'ui/res', to: 'ui/res' },
// we need to get webextension-polyfill and put it in common/lib
{ from: '../node_modules/webextension-polyfill/dist/browser-polyfill.js', to: 'common/lib/browser-polyfill.js'},
// we need to get webextension-polyfill and put it in common/lib
{ from: '../node_modules/webextension-polyfill/dist/browser-polyfill.js', to: 'common/lib/browser-polyfill.js'},
// This is extension icon, as used on extension lists and/or extension's action button
// 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', 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: 'install/first-time/first-time.html', to: 'install/first-time/first-time.html', transform: transformHtml},
{ from: 'install/updated/updated.html', to: 'install/updated/updated.html', transform: transformHtml },
{
from: 'manifest.json',
to: 'manifest.json',
transform: (content) => {
const jsonContent = JSON.parse(content);
// jsonContent.version = version;
// This is extension icon, as used on extension lists and/or extension's action button
// 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: 'ui/pages/installed/index.html', to: 'ui/pages/installed/index.html', transform: transformHtml },
{ from: 'ui/pages/updated/index.html', to: 'ui/pages/updated/index.html', transform: transformHtml },
{ from: 'ui/pages/settings/index.html', to: 'ui/pages/settings/index.html', transform: transformHtml },
{
from: 'manifest.json',
to: 'manifest.json',
transform: (content) => {
const jsonContent = JSON.parse(content);
// jsonContent.version = version;
// if (config.mode === 'development') {
// jsonContent['content_security_policy'] = "script-src 'self' 'unsafe-eval'; object-src 'self'";
// }
// if (config.mode === 'development') {
// jsonContent['content_security_policy'] = "script-src 'self' 'unsafe-eval'; object-src 'self'";
// }
if (process.env.CHANNEL === 'nightly') {
jsonContent.name = "Ultrawidify - nightly";
jsonContent.description = "FOR TESTING ONLY -- THIS BUILD USES ONLY THE FRESHEST COMMITS FROM GITHUB AND MAY THEREFORE BE COMPLETELY BROKEN";
if (process.env.CHANNEL === 'nightly') {
jsonContent.name = "Ultrawidify - nightly";
jsonContent.description = "FOR TESTING ONLY -- THIS BUILD USES ONLY THE FRESHEST COMMITS FROM GITHUB AND MAY THEREFORE BE COMPLETELY BROKEN";
// version numbers for nightly builds: YYMM.DD.BUILD_NUMBER
jsonContent.version = `${new Date()
.toISOString() // YYYY-MM-DDTHH:MM:SS...
.split('T')[0] // gives YYYY-MM-DD
.substr(2) // YYYY -> YY
.replace('-', '') // YY-MM-DD -> YYMM-DD
.replace('-', '.') // YYMM-DD -> YYMM.DD
}.${process.env.BUILD_NUMBER === undefined ? 0 : process.env.BUILD_NUMBER}`;
jsonContent.browser_action.default_title = "Ultrawidify Nightly";
// version numbers for nightly builds: YYMM.DD.BUILD_NUMBER
jsonContent.version = `${new Date()
.toISOString() // YYYY-MM-DDTHH:MM:SS...
.split('T')[0] // gives YYYY-MM-DD
.substr(2) // YYYY -> YY
.replace('-', '') // YY-MM-DD -> YYMM-DD
.replace('-', '.') // YYMM-DD -> YYMM.DD
}.${process.env.BUILD_NUMBER === undefined ? 0 : process.env.BUILD_NUMBER}`;
jsonContent.browser_action.default_title = "Ultrawidify Nightly";
// because we don't want web-ext to submit this as proper release
delete jsonContent.applications;
} else if (process.env.CHANNEL === 'testing') {
jsonContent.name = "Ultrawidify - testing";
jsonContent.description = "FOR TESTING ONLY -- this build is intended for testing a fix of certain bugs. It's not fit for normal use.";
// because we don't want web-ext to submit this as proper release
delete jsonContent.applications;
} else if (process.env.CHANNEL === 'testing') {
jsonContent.name = "Ultrawidify - testing";
jsonContent.description = "FOR TESTING ONLY -- this build is intended for testing a fix of certain bugs. It's not fit for normal use.";
// version numbers for nightly builds: YYMM.DD.BUILD_NUMBER
jsonContent.version = `${new Date()
.toISOString() // YYYY-MM-DDTHH:MM:SS...
.split('T')[0] // gives YYYY-MM-DD
.substr(2) // YYYY -> YY
.replace('-', '') // YY-MM-DD -> YYMM-DD
.replace('-', '.') // YYMM-DD -> YYMM.DD
}.${process.env.BUILD_NUMBER === undefined ? 0 : process.env.BUILD_NUMBER}`;
jsonContent.browser_action.default_title = "Ultrawidify Testing";
// version numbers for nightly builds: YYMM.DD.BUILD_NUMBER
jsonContent.version = `${new Date()
.toISOString() // YYYY-MM-DDTHH:MM:SS...
.split('T')[0] // gives YYYY-MM-DD
.substr(2) // YYYY -> YY
.replace('-', '') // YY-MM-DD -> YYMM-DD
.replace('-', '.') // YYMM-DD -> YYMM.DD
}.${process.env.BUILD_NUMBER === undefined ? 0 : process.env.BUILD_NUMBER}`;
jsonContent.browser_action.default_title = "Ultrawidify Testing";
// because we don't want web-ext to submit this as proper release
delete jsonContent.applications;
}
// because we don't want web-ext to submit this as proper release
delete jsonContent.applications;
}
if (process.env.BROWSER !== 'firefox') {
jsonContent.version = jsonContent.version.replace(/[a-zA-Z-]/g, '');
try {
delete jsonContent.options_ui.browser_style;
} catch (e) { }
try {
delete jsonContent.background.scripts;
} catch (e) {}
} else {
delete jsonContent.background.service_worker;
}
if (process.env.BROWSER !== 'firefox') {
jsonContent.version = jsonContent.version.replace(/[a-zA-Z-]/g, '');
try {
delete jsonContent.options_ui.browser_style;
} catch (e) { }
try {
delete jsonContent.background.scripts;
} catch (e) {}
} else {
delete jsonContent.background.service_worker;
}
return JSON.stringify(jsonContent, null, 2);
return JSON.stringify(jsonContent, null, 2);
},
},
]
}),
new WebpackShellPluginNext({
onBuildEnd: {
scripts: ['node scripts/remove-evals.js'],
blocking: true,
parallel: false,
},
]),
new WebpackShellPlugin({
onBuildEnd: ['node scripts/remove-evals.js'],
}),
new webpack.DefinePlugin({
'process.env.BROWSER': JSON.stringify(process.env.BROWSER),
@ -239,7 +239,7 @@ if (config.mode === 'production') {
if (process.env.HMR === 'true') {
config.plugins = (config.plugins || []).concat([
new ChromeExtensionReloader(),
// new ChromeExtensionReloader(), // no longer a thing for webpack 5
]);
}