Merge branch 'dependency-upgrades'
This commit is contained in:
commit
13feb1e822
33503
package-lock.json
generated
33503
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
107
package.json
107
package.json
@ -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
7
postcss.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
require('@tailwindcss/postcss'),
|
||||
require('postcss-nested'),
|
||||
require('autoprefixer'),
|
||||
],
|
||||
};
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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> <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]"> (except on whitelisted sites)</span>"
|
||||
</span>
|
||||
<br/>
|
||||
Autodetection: <span :style="getSiteEnabledColor('@global', 'enableAard')"><small>{{ getSiteEnabledModes('@global', 'enableAard') }}</small></span>;
|
||||
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>;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -156,7 +156,7 @@ const ExtensionConf: SettingsInterface = {
|
||||
|
||||
aard: {
|
||||
aardType: 'auto',
|
||||
useLegacy: true,
|
||||
useLegacy: false,
|
||||
|
||||
polling: {
|
||||
runInBackgroundTabs: AardPollingOptions.Reduced,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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') {
|
||||
|
||||
@ -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[];
|
||||
|
||||
33
src/ext/lib/settings/migrate-major-version.ts
Normal file
33
src/ext/lib/settings/migrate-major-version.ts
Normal 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;
|
||||
}
|
||||
56
src/ext/lib/uwui/BasicNotifications.ts
Normal file
56
src/ext/lib/uwui/BasicNotifications.ts
Normal 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>
|
||||
`;
|
||||
}
|
||||
}
|
||||
@ -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
40
src/main.css
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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": [
|
||||
"*://*/*"
|
||||
|
||||
310
src/ui/components/ExtensionSettings/BaseExtensionSettings.vue
Normal file
310
src/ui/components/ExtensionSettings/BaseExtensionSettings.vue
Normal 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>
|
||||
109
src/ui/components/ExtensionSettings/Panels/FrameSiteSettings.vue
Normal file
109
src/ui/components/ExtensionSettings/Panels/FrameSiteSettings.vue
Normal 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>
|
||||
160
src/ui/components/ExtensionSettings/Panels/OtherSiteSettings.vue
Normal file
160
src/ui/components/ExtensionSettings/Panels/OtherSiteSettings.vue
Normal 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"> (except on whitelisted sites)</span>
|
||||
</span> <br/>
|
||||
Autodetection: <span :style="getSiteEnabledColor('@global', 'enableAard')"><small>{{ getSiteEnabledModes('@global', 'enableAard') }}</small></span>;
|
||||
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>
|
||||
@ -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>
|
||||
79
src/ui/components/ExtensionSettings/Panels/SiteListItem.vue
Normal file
79
src/ui/components/ExtensionSettings/Panels/SiteListItem.vue
Normal 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>;
|
||||
Autodetect: <span :style="getSiteEnabledColor(host, 'enableAard')"><small>{{ getSiteEnabledModes(host, 'enableAard') }}</small></span>;
|
||||
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>
|
||||
@ -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>
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
259
src/ui/components/KeyboardShortcuts/KeyboardShortcutSettings.vue
Normal file
259
src/ui/components/KeyboardShortcuts/KeyboardShortcutSettings.vue
Normal 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>
|
||||
268
src/ui/components/SettingsWindowContent.vue
Normal file
268
src/ui/components/SettingsWindowContent.vue
Normal 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>
|
||||
296
src/ui/components/UISettings/UISettings.vue
Normal file
296
src/ui/components/UISettings/UISettings.vue
Normal 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>
|
||||
@ -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);
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
62
src/ui/pages/settings/App.vue
Normal file
62
src/ui/pages/settings/App.vue
Normal 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>
|
||||
|
||||
16
src/ui/pages/settings/index.html
Normal file
16
src/ui/pages/settings/index.html
Normal 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>
|
||||
13
src/ui/pages/settings/settings.js
Normal file
13
src/ui/pages/settings/settings.js
Normal 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');
|
||||
@ -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
BIN
src/ui/res/fonts/Heebo.ttf
Normal file
Binary file not shown.
93
src/ui/res/fonts/OFL-heebo.txt
Normal file
93
src/ui/res/fonts/OFL-heebo.txt
Normal 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.
|
||||
93
src/ui/res/fonts/OFL-source-code-pro.txt
Normal file
93
src/ui/res/fonts/OFL-source-code-pro.txt
Normal 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.
|
||||
0
src/ui/res/fonts/OFL.txtZone.Identifier
Normal file
0
src/ui/res/fonts/OFL.txtZone.Identifier
Normal file
BIN
src/ui/res/fonts/SourceCodePro-Italic.ttf
Normal file
BIN
src/ui/res/fonts/SourceCodePro-Italic.ttf
Normal file
Binary file not shown.
BIN
src/ui/res/fonts/SourceCodePro.ttf
Normal file
BIN
src/ui/res/fonts/SourceCodePro.ttf
Normal file
Binary file not shown.
BIN
src/ui/res/fonts/materialdesignicons-webfont.woff2
Normal file
BIN
src/ui/res/fonts/materialdesignicons-webfont.woff2
Normal file
Binary file not shown.
18
src/ui/res/styles/buttons.css
Normal file
18
src/ui/res/styles/buttons.css
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/ui/res/styles/fonts.css
Normal file
25
src/ui/res/styles/fonts.css
Normal 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
135
src/ui/res/styles/forms.css
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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
28
tailwind.config.json
Normal 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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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/**/*" ],
|
||||
}
|
||||
|
||||
@ -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
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user