Compare commits
No commits in common. "master" and "hotfix/iframe-fix" have entirely different histories.
master
...
hotfix/ifr
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -13,7 +13,6 @@
|
||||
"com",
|
||||
"comms",
|
||||
"csui",
|
||||
"cycleable",
|
||||
"decycle",
|
||||
"dinked",
|
||||
"dinks",
|
||||
@ -47,7 +46,6 @@
|
||||
"reddit",
|
||||
"rescan",
|
||||
"resizer",
|
||||
"resizers",
|
||||
"scrollbar",
|
||||
"shitiness",
|
||||
"smallcaps",
|
||||
|
61
CHANGELOG.md
61
CHANGELOG.md
@ -1,57 +1,18 @@
|
||||
# Changelog
|
||||
|
||||
|
||||
|
||||
### Plans for the future
|
||||
|
||||
* Native builds for Chromium Edge
|
||||
* Settings page looks ugly af right now. Maybe fix it some time later
|
||||
* other bug fixes
|
||||
|
||||
## v7.0 (planned major)
|
||||
* WebGL autodetection
|
||||
|
||||
## v6.0 (current major)
|
||||
|
||||
### v6.3.0
|
||||
* Added zoom segment to in-player UI and popup.
|
||||
* Fixed keyboard zoom
|
||||
* Added additional zoom options. If you wonder how zoom options differ from crop, mess around and find out.
|
||||
* Subdomains now inherit same settings as their parent domain by default
|
||||
* Extension detects embedded content (but not always)
|
||||
* Added `www.youtube-nocookie.com` to "officially supported" list
|
||||
|
||||
### v6.2.5
|
||||
|
||||
* Popup appearance changed — UI advertisement panel was moved to the popup header
|
||||
* Fixed the bug where popup wouldn't be showing the correct settings
|
||||
* Fixed the bug where site settings would default to 'disabled', even if Extension default setting was not disabled.
|
||||
* Added ability to export and import settings from file (ft. developer mode editor)
|
||||
* [dev goodies] Added debug overlay for aard
|
||||
* Fixed an issue where aspect ratio wouldn't get calculated correctly on youtube videos with native aspect ratios other than 16:9
|
||||
* Fixed an issue that would crash the extension if video element didn't have a player element associated with it
|
||||
* Fixed an issue where extension sometimes wouldn't work if video element was grafted/re-parented to a different element
|
||||
|
||||
### v6.2.4
|
||||
* [#264](https://github.com/tamius-han/ultrawidify/issues/264) — fixed issue with white screen that affected some youtube users. Special thanks to [SnowyOwlNugget](https://github.com/SnowyOwlNugget">SnowyOwlNugget), who instead of whining provided the necessary information.
|
||||
* Minor updates to the settings
|
||||
* Switching between full screen, theater, and normal player now correctly enables and disables the extension according to the settings.
|
||||
* Don't reset settings if updating conf patches fails.
|
||||
* Darken in-player UI backgrounds until Chrome fixes its backdrop-filter bug
|
||||
|
||||
### v6.2.3
|
||||
* Fixed default persistent mode
|
||||
|
||||
### v6.2.2
|
||||
* Fixed the issue where stretching was not applied if no cropping was used
|
||||
* Added way to manually enable or disable color scheme detection, as a compromise between #259 and #264
|
||||
|
||||
### v6.2.1
|
||||
|
||||
* Fixed offset issue on Netflix
|
||||
* Fixed issue with overlay iframe not being transparent on twitter
|
||||
|
||||
### v6.2.0
|
||||
|
||||
* Parts of automatic aspect ratio detection use WebGL now. This should result in improved performance.
|
||||
* In-player UI now indicates whether a site is having a problem with automatic aspect ratio detection.
|
||||
* UI improvements
|
||||
|
||||
Since automatic aspect ratio detection had to be rewritten completely and since the new version needs to be out 5 days ago, there are some regressions in the automatic aspect ratio detection.
|
||||
|
||||
### v6.1.0
|
||||
|
||||
Was skipped due to how major the changes were.
|
||||
|
||||
### v6.0.1
|
||||
|
||||
* Fixed external links
|
||||
|
@ -1,39 +0,0 @@
|
||||
# Contributing to Ultrawidify
|
||||
|
||||
Thank you for considering contributing to this project! We welcome contributions from the community and are grateful for your efforts.
|
||||
|
||||
## How to Contribute
|
||||
|
||||
0. Open an issue, where you introduce the feature you want to add or thing you want to fix
|
||||
1. Fork the repository.
|
||||
2. Create a new branch for your contribution.
|
||||
3. Write readable code
|
||||
4. Check that your additions do not break existing features
|
||||
5. Open a pull request
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Please follow the code of conduct:
|
||||
|
||||
* don't be too much of an asshole
|
||||
* language policing my code comments is not a worthwhile contribution, and is considered spam. If you're here to do that, fuck off. I'm looking at you, ScamOSS and co.
|
||||
|
||||
## Code Style
|
||||
|
||||
- Use formatting consistent with the rest of the project
|
||||
- Write meaningful commit messages.
|
||||
- Include comments where necessary to explain complex logic.
|
||||
|
||||
## Licensing & Contribution Agreement
|
||||
|
||||
By submitting a contribution (e.g., via pull request), you agree to the following:
|
||||
|
||||
- You grant the project maintainer(s) a non-exclusive, irrevocable, worldwide, royalty-free license to use, copy, modify, and distribute your contributions as part of the project.
|
||||
- You certify that your contributions are your original work and that you have the right to submit them.
|
||||
- You agree that your contributions are licensed under the same license as the rest of the project, unless explicitly stated otherwise.
|
||||
|
||||
**TL;DR:** once your code is in the project, there's no take-backsies.
|
||||
|
||||
## Questions?
|
||||
|
||||
The discussion board is right over there, three tabs to the right.
|
35
LICENSE.MD
35
LICENSE.MD
@ -1,35 +0,0 @@
|
||||
# License
|
||||
|
||||
Copyright (c) 2025 @tamius-han
|
||||
|
||||
The source code is provided on the "you can look, but you can't take" basis.
|
||||
|
||||
## Permissions
|
||||
|
||||
You are permitted to:
|
||||
|
||||
* You can view the code
|
||||
* You can create forks and modify the code for personal use
|
||||
* You can build and run modified versions of this project for personal use on your local machine
|
||||
* Push modifications to your personal github fork
|
||||
|
||||
## Restrictions
|
||||
|
||||
You may NOT:
|
||||
|
||||
* Distribute this project or any modified versions outside of your personal Github fork
|
||||
* Publish or distribute compiled binaries, builds, or packages of this project or any derivative work
|
||||
* Use the code in commercial products, services, or other public-facing deployments.
|
||||
* Sub-license, sell, or otherwise make the software or derivatives available to third parties.
|
||||
|
||||
## Contributions
|
||||
|
||||
By submitting a pull request or other contribution, you agree that:
|
||||
|
||||
- You grant the project maintainer(s) a non-exclusive, irrevocable, worldwide, royalty-free license to use, copy, modify, and distribute your contributions as part of the project.
|
||||
- You certify that your contributions are your original work and that you have the right to submit them.
|
||||
- You agree that your contributions are licensed under the same license as the rest of the project, unless explicitly stated otherwise.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This software is provided "as is", without warranty of any kind, express or implied. Use at your own risk.
|
411
package-lock.json
generated
411
package-lock.json
generated
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ultrawidify",
|
||||
"version": "6.3.0",
|
||||
"version": "6.2.3",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
@ -28,6 +28,7 @@
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
|
||||
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
@ -1375,109 +1376,6 @@
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@codemirror/autocomplete": {
|
||||
"version": "6.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
|
||||
"integrity": "sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==",
|
||||
"requires": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.17.0",
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@codemirror/commands": {
|
||||
"version": "6.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.0.tgz",
|
||||
"integrity": "sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==",
|
||||
"requires": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@codemirror/state": "^6.4.0",
|
||||
"@codemirror/view": "^6.27.0",
|
||||
"@lezer/common": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"@codemirror/lang-json": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
|
||||
"integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
|
||||
"requires": {
|
||||
"@codemirror/language": "^6.0.0",
|
||||
"@lezer/json": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@codemirror/language": {
|
||||
"version": "6.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.0.tgz",
|
||||
"integrity": "sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==",
|
||||
"requires": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.23.0",
|
||||
"@lezer/common": "^1.1.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0",
|
||||
"style-mod": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@codemirror/lint": {
|
||||
"version": "6.8.5",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz",
|
||||
"integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==",
|
||||
"requires": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.35.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"@codemirror/search": {
|
||||
"version": "6.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.10.tgz",
|
||||
"integrity": "sha512-RMdPdmsrUf53pb2VwflKGHEe1XVM07hI7vV2ntgw1dmqhimpatSJKva4VA9h4TLUDOD4EIF02201oZurpnEFsg==",
|
||||
"requires": {
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"crelt": "^1.0.5"
|
||||
}
|
||||
},
|
||||
"@codemirror/state": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
||||
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
||||
"requires": {
|
||||
"@marijn/find-cluster-break": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@codemirror/view": {
|
||||
"version": "6.36.5",
|
||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.5.tgz",
|
||||
"integrity": "sha512-cd+FZEUlu3GQCYnguYm3EkhJ8KJVisqqUsCOKedBoAt/d9c76JUUap6U0UrpElln5k6VyrEOYliMuDAKIeDQLg==",
|
||||
"requires": {
|
||||
"@codemirror/state": "^6.5.0",
|
||||
"style-mod": "^4.1.0",
|
||||
"w3c-keyname": "^2.2.4"
|
||||
}
|
||||
},
|
||||
"@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz",
|
||||
"integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg=="
|
||||
},
|
||||
"@fortawesome/free-regular-svg-icons": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.7.2.tgz",
|
||||
"integrity": "sha512-7Z/ur0gvCMW8G93dXIQOkQqHo2M5HLhYrRVC0//fakJXxcF1VmMPsxnG6Ee8qEylA8b8Q3peQXWMNZ62lYF28g==",
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-common-types": "6.7.2"
|
||||
}
|
||||
},
|
||||
"@fortawesome/free-solid-svg-icons": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz",
|
||||
"integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==",
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-common-types": "6.7.2"
|
||||
}
|
||||
},
|
||||
"@hapi/address": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
|
||||
@ -1557,57 +1455,6 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"@jsep-plugin/assignment": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz",
|
||||
"integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ=="
|
||||
},
|
||||
"@jsep-plugin/regex": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz",
|
||||
"integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg=="
|
||||
},
|
||||
"@jsonquerylang/jsonquery": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@jsonquerylang/jsonquery/-/jsonquery-4.1.1.tgz",
|
||||
"integrity": "sha512-Rfyvq70Zrb561BqSuXLsl0rG0/1tz913EQDL/4zpkp+laFGUxXIVPSaJWcdREJwADXLZDkQyaWplzEaPQvh+7A=="
|
||||
},
|
||||
"@lezer/common": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz",
|
||||
"integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA=="
|
||||
},
|
||||
"@lezer/highlight": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz",
|
||||
"integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==",
|
||||
"requires": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@lezer/json": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz",
|
||||
"integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==",
|
||||
"requires": {
|
||||
"@lezer/common": "^1.2.0",
|
||||
"@lezer/highlight": "^1.0.0",
|
||||
"@lezer/lr": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@lezer/lr": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz",
|
||||
"integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==",
|
||||
"requires": {
|
||||
"@lezer/common": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"@marijn/find-cluster-break": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="
|
||||
},
|
||||
"@mdi/font": {
|
||||
"version": "6.9.96",
|
||||
"resolved": "https://registry.npmjs.org/@mdi/font/-/font-6.9.96.tgz",
|
||||
@ -1735,27 +1582,12 @@
|
||||
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
|
||||
"dev": true
|
||||
},
|
||||
"@replit/codemirror-indentation-markers": {
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@replit/codemirror-indentation-markers/-/codemirror-indentation-markers-6.5.3.tgz",
|
||||
"integrity": "sha512-hL5Sfvw3C1vgg7GolLe/uxX5T3tmgOA3ZzqlMv47zjU1ON51pzNWiVbS22oh6crYhtVhv8b3gdXwoYp++2ilHw=="
|
||||
},
|
||||
"@sindresorhus/is": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz",
|
||||
"integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==",
|
||||
"dev": true
|
||||
},
|
||||
"@sphinxxxx/color-conversion": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@sphinxxxx/color-conversion/-/color-conversion-2.2.2.tgz",
|
||||
"integrity": "sha512-XExJS3cLqgrmNBIP3bBw6+1oQ1ksGjFh0+oClDKFYpCCqx/hlqwWO5KO/S63fzUo67SxI9dMrF0y5T/Ey7h8Zw=="
|
||||
},
|
||||
"@sveltejs/acorn-typescript": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz",
|
||||
"integrity": "sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ=="
|
||||
},
|
||||
"@types/accepts": {
|
||||
"version": "1.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz",
|
||||
@ -1839,11 +1671,6 @@
|
||||
"es6-promise": "*"
|
||||
}
|
||||
},
|
||||
"@types/estree": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="
|
||||
},
|
||||
"@types/express": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
|
||||
@ -3205,11 +3032,6 @@
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"aria-query": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
|
||||
"integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="
|
||||
},
|
||||
"arr-diff": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
|
||||
@ -3553,11 +3375,6 @@
|
||||
"integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==",
|
||||
"dev": true
|
||||
},
|
||||
"axobject-query": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||
"integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="
|
||||
},
|
||||
"babel-code-frame": {
|
||||
"version": "6.26.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
|
||||
@ -4943,11 +4760,6 @@
|
||||
"mimic-response": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
"integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="
|
||||
},
|
||||
"cmd-shim": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-3.0.3.tgz",
|
||||
@ -4973,11 +4785,6 @@
|
||||
"integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==",
|
||||
"dev": true
|
||||
},
|
||||
"codemirror-wrapped-line-indent": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/codemirror-wrapped-line-indent/-/codemirror-wrapped-line-indent-1.0.8.tgz",
|
||||
"integrity": "sha512-5UwuHCz4oAZuvot1DbfFxSxJacTESdNGa/KpJD7HfpVpDAJdgB1vV9OG4b4pkJqPWuOfIpFLTQEKS85kTpV+XA=="
|
||||
},
|
||||
"collection-visit": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
|
||||
@ -5559,11 +5366,6 @@
|
||||
"sha.js": "^2.4.8"
|
||||
}
|
||||
},
|
||||
"crelt": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="
|
||||
},
|
||||
"cross-env": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.1.tgz",
|
||||
@ -6213,11 +6015,6 @@
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"dev": true
|
||||
},
|
||||
"diff-sequences": {
|
||||
"version": "29.6.3",
|
||||
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
|
||||
"integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="
|
||||
},
|
||||
"diffie-hellman": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
|
||||
@ -6569,25 +6366,12 @@
|
||||
"estraverse": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"esm-env": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz",
|
||||
"integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"dev": true
|
||||
},
|
||||
"esrap": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.5.tgz",
|
||||
"integrity": "sha512-CjNMjkBWWZeHn+VX+gS8YvFwJ5+NDhg8aWZBSFJPR8qQduDNjbJodA2WcwCm7uQa5Rjqj+nZvVmceg1RbHFB9g==",
|
||||
"requires": {
|
||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||
}
|
||||
},
|
||||
"esrecurse": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
||||
@ -6947,7 +6731,8 @@
|
||||
"fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
|
||||
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
|
||||
"dev": true
|
||||
},
|
||||
"fast-glob": {
|
||||
"version": "2.2.7",
|
||||
@ -6969,11 +6754,6 @@
|
||||
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
|
||||
"dev": true
|
||||
},
|
||||
"fast-uri": {
|
||||
"version": "3.0.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
|
||||
"integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw=="
|
||||
},
|
||||
"fastparse": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
|
||||
@ -8335,11 +8115,6 @@
|
||||
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
|
||||
"dev": true
|
||||
},
|
||||
"immutable-json-patch": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/immutable-json-patch/-/immutable-json-patch-6.0.1.tgz",
|
||||
"integrity": "sha512-BHL/cXMjwFZlTOffiWNdY8ZTvNyYLrutCnWxrcKPHr5FqpAb6vsO6WWSPnVSys3+DruFN6lhHJJPHi8uELQL5g=="
|
||||
},
|
||||
"import-fresh": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
|
||||
@ -8843,14 +8618,6 @@
|
||||
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
|
||||
"dev": true
|
||||
},
|
||||
"is-reference": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz",
|
||||
"integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==",
|
||||
"requires": {
|
||||
"@types/estree": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"is-regex": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
|
||||
@ -9003,11 +8770,6 @@
|
||||
"integrity": "sha512-fnjC0up+0SjEJtgmmG+teeel68kutkvzfctO/KxE3qJlbunkJYAshgH3boU++gSBHP8z5/r0ts0qRIrHf0RTQQ==",
|
||||
"dev": true
|
||||
},
|
||||
"jmespath": {
|
||||
"version": "0.16.0",
|
||||
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
|
||||
"integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw=="
|
||||
},
|
||||
"js-base64": {
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz",
|
||||
@ -9103,11 +8865,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"jsep": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz",
|
||||
"integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw=="
|
||||
},
|
||||
"jsesc": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
|
||||
@ -9147,11 +8904,6 @@
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"json-source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/json-source-map/-/json-source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-1QoztHPsMQqhDq0hlXY5ZqcEdUzxQEIxgFkKl4WUp2pgShObl+9ovi4kRh2TfvAfxAoHOJ9vIMEqk3k4iex7tg=="
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
@ -9174,21 +8926,6 @@
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"jsonpath-plus": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz",
|
||||
"integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==",
|
||||
"requires": {
|
||||
"@jsep-plugin/assignment": "^1.3.0",
|
||||
"@jsep-plugin/regex": "^1.0.4",
|
||||
"jsep": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"jsonrepair": {
|
||||
"version": "3.12.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonrepair/-/jsonrepair-3.12.0.tgz",
|
||||
"integrity": "sha512-SWfjz8SuQ0wZjwsxtSJ3Zy8vvLg6aO/kxcp9TWNPGwJKgTZVfhNEQBMk/vPOpYCDFWRxD6QWuI6IHR1t615f0w=="
|
||||
},
|
||||
"jsprim": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
|
||||
@ -9293,11 +9030,6 @@
|
||||
"json5": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"locate-character": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
|
||||
"integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="
|
||||
},
|
||||
"locate-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
|
||||
@ -9312,11 +9044,6 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
|
||||
},
|
||||
"lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
@ -9536,11 +9263,6 @@
|
||||
"fs-monkey": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
"integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="
|
||||
},
|
||||
"memory-fs": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
|
||||
@ -9841,11 +9563,6 @@
|
||||
"to-regex": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"natural-compare-lite": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
|
||||
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g=="
|
||||
},
|
||||
"ndjson": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/ndjson/-/ndjson-1.5.0.tgz",
|
||||
@ -13615,11 +13332,6 @@
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="
|
||||
},
|
||||
"require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="
|
||||
},
|
||||
"require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
@ -14686,11 +14398,6 @@
|
||||
"escape-string-regexp": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"style-mod": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
||||
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw=="
|
||||
},
|
||||
"subscriptions-transport-ws": {
|
||||
"version": "0.9.19",
|
||||
"resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz",
|
||||
@ -14717,47 +14424,6 @@
|
||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
|
||||
},
|
||||
"svelte": {
|
||||
"version": "5.25.3",
|
||||
"resolved": "https://registry.npmjs.org/svelte/-/svelte-5.25.3.tgz",
|
||||
"integrity": "sha512-J9rcZ/xVJonAoESqVGHHZhrNdVbrCfkdB41BP6eiwHMoFShD9it3yZXApVYMHdGfCshBsZCKsajwJeBbS/M1zg==",
|
||||
"requires": {
|
||||
"@ampproject/remapping": "^2.3.0",
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0",
|
||||
"@sveltejs/acorn-typescript": "^1.0.5",
|
||||
"@types/estree": "^1.0.5",
|
||||
"acorn": "^8.12.1",
|
||||
"aria-query": "^5.3.1",
|
||||
"axobject-query": "^4.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"esm-env": "^1.2.1",
|
||||
"esrap": "^1.4.3",
|
||||
"is-reference": "^3.0.3",
|
||||
"locate-character": "^3.0.0",
|
||||
"magic-string": "^0.30.11",
|
||||
"zimmerframe": "^1.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
|
||||
},
|
||||
"acorn": {
|
||||
"version": "8.14.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
|
||||
"integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="
|
||||
},
|
||||
"magic-string": {
|
||||
"version": "0.30.17",
|
||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
||||
"integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
|
||||
"requires": {
|
||||
"@jridgewell/sourcemap-codec": "^1.5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"svgo": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz",
|
||||
@ -15809,65 +15475,6 @@
|
||||
"builtins": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"vanilla-jsoneditor": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/vanilla-jsoneditor/-/vanilla-jsoneditor-3.3.0.tgz",
|
||||
"integrity": "sha512-HvCh0qaeQwB4HUW+G7SAKpMPy7jugnt697W9b4zJ4SkbKkjvX3Z1HEBuImtllT47IshR3HcPg8rHHA0Uq/e8Eg==",
|
||||
"requires": {
|
||||
"@codemirror/autocomplete": "^6.18.1",
|
||||
"@codemirror/commands": "^6.7.1",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/language": "^6.10.3",
|
||||
"@codemirror/lint": "^6.8.2",
|
||||
"@codemirror/search": "^6.5.6",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
"@codemirror/view": "^6.34.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.6.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.6.0",
|
||||
"@jsonquerylang/jsonquery": "^3.1.1 || ^4.0.0",
|
||||
"@lezer/highlight": "^1.2.1",
|
||||
"@replit/codemirror-indentation-markers": "^6.5.3",
|
||||
"ajv": "^8.17.1",
|
||||
"codemirror-wrapped-line-indent": "^1.0.8",
|
||||
"diff-sequences": "^29.6.3",
|
||||
"immutable-json-patch": "^6.0.1",
|
||||
"jmespath": "^0.16.0",
|
||||
"json-source-map": "^0.6.1",
|
||||
"jsonpath-plus": "^10.3.0",
|
||||
"jsonrepair": "^3.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"memoize-one": "^6.0.0",
|
||||
"natural-compare-lite": "^1.4.0",
|
||||
"svelte": "^5.0.0",
|
||||
"vanilla-picker": "^2.12.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
|
||||
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"vanilla-picker": {
|
||||
"version": "2.12.3",
|
||||
"resolved": "https://registry.npmjs.org/vanilla-picker/-/vanilla-picker-2.12.3.tgz",
|
||||
"integrity": "sha512-qVkT1E7yMbUsB2mmJNFmaXMWE2hF8ffqzMMwe9zdAikd8u2VfnsVY2HQcOUi2F38bgbxzlJBEdS1UUhOXdF9GQ==",
|
||||
"requires": {
|
||||
"@sphinxxxx/color-conversion": "^2.2.2"
|
||||
}
|
||||
},
|
||||
"vary": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
|
||||
@ -16344,11 +15951,6 @@
|
||||
"resolved": "https://registry.npmjs.org/vuex-webextensions/-/vuex-webextensions-1.3.3.tgz",
|
||||
"integrity": "sha512-Qz+KmF4CYLfomAIuEjY4A9DYqzwXZo18TkvTyO84DkW/s274iI2IyuOSFZoLcVHMS5BjUhRbyFclqqAwz58zgw=="
|
||||
},
|
||||
"w3c-keyname": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="
|
||||
},
|
||||
"watch": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/watch/-/watch-1.0.2.tgz",
|
||||
@ -16986,11 +16588,6 @@
|
||||
"zen-observable": "^0.8.0"
|
||||
}
|
||||
},
|
||||
"zimmerframe": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.2.tgz",
|
||||
"integrity": "sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w=="
|
||||
},
|
||||
"zip-stream": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ultrawidify",
|
||||
"version": "6.3.0",
|
||||
"version": "6.2.3",
|
||||
"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": {
|
||||
@ -36,7 +36,6 @@
|
||||
"lodash": "^4.17.21",
|
||||
"mdi-vue": "^3.0.11",
|
||||
"typescript": "^4.4.4",
|
||||
"vanilla-jsoneditor": "^3.3.0",
|
||||
"vue": "^3.2.21",
|
||||
"vue-style-loader": "^4.1.3",
|
||||
"vuex": "^4.0.2",
|
||||
|
@ -1,13 +1,6 @@
|
||||
import AspectRatioType from '../enums/AspectRatioType.enum';
|
||||
|
||||
export enum ArVariant {
|
||||
Crop = undefined,
|
||||
Zoom = 1
|
||||
}
|
||||
|
||||
export interface Ar {
|
||||
type: AspectRatioType,
|
||||
ratio?: number,
|
||||
variant?: ArVariant,
|
||||
offset?: number,
|
||||
ratio?: number
|
||||
}
|
||||
|
@ -6,19 +6,6 @@ import ExtensionMode from '../enums/ExtensionMode.enum'
|
||||
import StretchType from '../enums/StretchType.enum'
|
||||
import VideoAlignmentType from '../enums/VideoAlignmentType.enum'
|
||||
|
||||
export enum ExtensionEnvironment {
|
||||
Normal = 'normal',
|
||||
Theater = 'theater',
|
||||
Fullscreen = 'fullscreen',
|
||||
}
|
||||
|
||||
export interface DevUiConfig {
|
||||
aardDebugOverlay: {
|
||||
showOnStartup: boolean,
|
||||
showDetectionDetails: boolean,
|
||||
}
|
||||
}
|
||||
|
||||
export interface KeyboardShortcutInterface {
|
||||
key?: string,
|
||||
code?: string,
|
||||
@ -168,23 +155,18 @@ export interface AardSettings {
|
||||
}
|
||||
}
|
||||
|
||||
interface DevSettings {
|
||||
loadFromSnapshot: boolean,
|
||||
}
|
||||
|
||||
interface SettingsInterface {
|
||||
_updateFlags?: {
|
||||
requireReload?: SettingsReloadFlags,
|
||||
forSite?: string
|
||||
}
|
||||
dev: DevSettings,
|
||||
|
||||
arDetect: AardSettings,
|
||||
|
||||
ui: {
|
||||
inPlayer: {
|
||||
enabled: boolean, // Deprecated — moved to site settings
|
||||
enabledFullscreenOnly: boolean, // Deprecated — moved to site settings
|
||||
enabled: boolean,
|
||||
enabledFullscreenOnly: boolean,
|
||||
popupAlignment: 'left' | 'right',
|
||||
minEnabledWidth: number, // don't show UI if player is narrower than % of screen width
|
||||
minEnabledHeight: number, // don't show UI if player is narrower than % of screen height
|
||||
@ -195,9 +177,7 @@ interface SettingsInterface {
|
||||
offsetX: number, // fed to translateX(offsetX + '%'). Valid range [-100, 0]
|
||||
offsetY: number // fed to translateY(offsetY + '%'). Valid range [-100, 100]
|
||||
},
|
||||
},
|
||||
devMode?: boolean,
|
||||
dev: DevUiConfig,
|
||||
}
|
||||
}
|
||||
|
||||
restrictions?: RestrictionsSettings;
|
||||
@ -245,7 +225,6 @@ interface SettingsInterface {
|
||||
pan?: any,
|
||||
version?: string,
|
||||
preventReload?: boolean,
|
||||
lastModified?: Date,
|
||||
|
||||
// -----------------------------------------
|
||||
// ::: MITIGATIONS :::
|
||||
@ -258,6 +237,33 @@ interface SettingsInterface {
|
||||
limit?: number,
|
||||
}
|
||||
}
|
||||
// -----------------------------------------
|
||||
// ::: ACTIONS :::
|
||||
// -----------------------------------------
|
||||
// Nastavitve za ukaze. Zamenja stare nastavitve za bližnične tipke.
|
||||
//
|
||||
// Polje 'shortcut' je tabela, če se slučajno lotimo kdaj delati choordov.
|
||||
actions: {
|
||||
name?: string, // name displayed in settings
|
||||
label?: string, // name displayed in ui (can be overridden in scope/playerUi)
|
||||
cmd?: {
|
||||
action: string,
|
||||
arg: any,
|
||||
customArg?: any,
|
||||
persistent?: boolean, // optional, false by default. If true, change doesn't take effect immediately.
|
||||
// Instead, this action saves stuff to settings
|
||||
}[],
|
||||
scopes?: {
|
||||
global?: ActionScopeInterface,
|
||||
site?: ActionScopeInterface,
|
||||
page?: ActionScopeInterface
|
||||
},
|
||||
playerUi?: {
|
||||
show: boolean,
|
||||
path?: string,
|
||||
},
|
||||
userAdded?: boolean,
|
||||
}[],
|
||||
// This object fulfills the same purpose as 'actions', but is written in less retarded and overly
|
||||
// complicated way. Hopefully it'll be easier to maintain it that way.
|
||||
commands?: {
|
||||
@ -306,9 +312,7 @@ export interface SiteSettingsInterface {
|
||||
enable: ExtensionEnvironmentSettingsInterface;
|
||||
enableAard: ExtensionEnvironmentSettingsInterface;
|
||||
enableKeyboard: ExtensionEnvironmentSettingsInterface;
|
||||
enableUI: ExtensionEnvironmentSettingsInterface; // Lies! enableUI doesn't use 'theater' property (but uses the other two)
|
||||
|
||||
autocreated?: boolean;
|
||||
type?: 'official' | 'community' | 'user-defined' | 'testing' | 'officially-disabled' | 'unknown' | 'modified';
|
||||
defaultType: 'official' | 'community' | 'user-defined' | 'testing' | 'officially-disabled' | 'unknown' | 'modified';
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="uw-clickthrough relative w-full h-full">
|
||||
<div class="uw-clickthrough relative w-100 h-100">
|
||||
<template v-for="rectangle of drawnRectangles" :key="rectangle.id ?? rectangle">
|
||||
|
||||
<!-- Player element overlays -->
|
||||
@ -44,6 +44,13 @@ export default {
|
||||
},
|
||||
|
||||
async created() {
|
||||
this.logger = new Logger();
|
||||
|
||||
// this prolly needs to be taken out
|
||||
await this.logger.init({
|
||||
allowLogging: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* Setup the "companion" onMouseMove handler to the one in the content script.
|
||||
* We can handle events with the same function we use to handle events from
|
||||
|
@ -24,7 +24,20 @@
|
||||
@mouseleave="allowContextMenuHide()"
|
||||
>
|
||||
<template v-slot:activator>
|
||||
<div class="context-item uw-clickable uw-menu-trigger relative">
|
||||
<div v-if="hoverStats.isOverTriggerMenu && !hoverStats.hasMouse" class="absolute ui-warning">
|
||||
<b>Video player is not being detected correctly</b><br/>
|
||||
<p>
|
||||
That's why this menu doesn't work correctly.
|
||||
</p>
|
||||
<p>
|
||||
It may start working if you move your mouse over the button a few times. If it doesn't, open the UI from the extension popup.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Ultrawidify
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<slot>
|
||||
<!--
|
||||
@ -64,7 +77,7 @@
|
||||
</GhettoContextMenuOption>
|
||||
</slot>
|
||||
</GhettoContextMenu>
|
||||
<GhettoContextMenu alignment="right">
|
||||
<!-- <GhettoContextMenu alignment="right">
|
||||
<template v-slot:activator>
|
||||
Zoom
|
||||
</template>
|
||||
@ -86,7 +99,7 @@
|
||||
/>
|
||||
</GhettoContextMenuItem>
|
||||
</slot>
|
||||
</GhettoContextMenu>
|
||||
</GhettoContextMenu> -->
|
||||
<GhettoContextMenu alignment="right">
|
||||
<template v-slot:activator>
|
||||
<div class="context-item">
|
||||
@ -144,13 +157,11 @@
|
||||
Site compatibility:
|
||||
<SupportLevelIndicator
|
||||
:siteSupportLevel="siteSupportLevel"
|
||||
supportLevelStyle="font-size: 0.69rem !important;"
|
||||
tooltipStyle="font-size: 0.8rem;"
|
||||
>
|
||||
</SupportLevelIndicator>
|
||||
<div v-if="statusFlags.hasDrm" class="aard-blocked">
|
||||
Autodetection blocked<br/>
|
||||
by <a style="color: #fff" href="https://en.wikipedia.org/wiki/Digital_rights_management" target="_blank">DRM</a>.
|
||||
Autodetection potentially<br/>
|
||||
unavailable due to <a style="color: #fff" href="https://en.wikipedia.org/wiki/Digital_rights_management" target="_blank">DRM</a>.
|
||||
</div>
|
||||
<div v-else-if="statusFlags.aardErrors?.cors" class="aard-blocked">
|
||||
Autodetection blocked<br/>
|
||||
@ -158,7 +169,7 @@
|
||||
</div>
|
||||
<div v-else-if="statusFlags.aardErrors?.webglError" class="aard-blocked">
|
||||
Autodetection unavailable<br/>
|
||||
(webgl error)
|
||||
due to webgl error.
|
||||
</div>
|
||||
</GhettoContextMenuItem>
|
||||
</div>
|
||||
@ -205,6 +216,7 @@ import GhettoContextMenuItem from '@csui/src/components/GhettoContextMenuItem.vu
|
||||
import GhettoContextMenuOption from '@csui/src/components/GhettoContextMenuOption.vue';
|
||||
import AlignmentOptionsControlComponent from '@csui/src/PlayerUiPanels/AlignmentOptionsControlComponent.vue';
|
||||
import BrowserDetect from '@src/ext/conf/BrowserDetect';
|
||||
import Logger from '@src/ext/lib/Logger';
|
||||
import Settings from '@src/ext/lib/Settings';
|
||||
import EventBus from '@src/ext/lib/EventBus';
|
||||
import UIProbeMixin from '@csui/src/utils/UIProbeMixin';
|
||||
@ -213,8 +225,6 @@ import CommsMixin from '@csui/src/utils/CommsMixin';
|
||||
import SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue';
|
||||
import TriggerZoneEditor from '@csui/src/components/TriggerZoneEditor.vue';
|
||||
import ZoomControl from '@csui/src/popup/player-menu/ZoomControl.vue';
|
||||
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator';
|
||||
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -256,7 +266,6 @@ export default {
|
||||
BrowserDetect: BrowserDetect,
|
||||
settingsInitialized: false,
|
||||
eventBus: new EventBus(),
|
||||
logAggregator: null,
|
||||
logger: null,
|
||||
|
||||
// NOTE: chromium doesn't allow us to access window.parent.location
|
||||
@ -328,8 +337,12 @@ export default {
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.logAggregator = new LogAggregator('player-overlay');
|
||||
this.logger = new ComponentLogger(this.logAggregator, 'PlayerOverlay.vue');
|
||||
this.logger = new Logger();
|
||||
|
||||
// this prolly needs to be taken out
|
||||
await this.logger.init({
|
||||
allowLogging: true,
|
||||
});
|
||||
|
||||
this.settings = new Settings({afterSettingsSaved: this.updateConfig, logger: this.logger});
|
||||
this.settings.listenAfterChange(() => this.updateTriggerZones());
|
||||
@ -483,8 +496,7 @@ export default {
|
||||
},
|
||||
|
||||
acknowledgeNewFeature(featureKey) {
|
||||
this.settings.active.newFeatureTracker[featureKey].show = 0;
|
||||
this.settings.active.newFeatureTracker[featureKey].acknowledged = true;
|
||||
delete this.settings.active.newFeatureTracker[featureKey];
|
||||
this.settings.saveWithoutReload();
|
||||
},
|
||||
newFeatureViewUpdate(featureKey) {
|
||||
@ -572,22 +584,7 @@ export default {
|
||||
},
|
||||
|
||||
handleBusTunnelIn(payload) {
|
||||
this.eventBus.send(
|
||||
payload.action,
|
||||
payload.config,
|
||||
{
|
||||
...payload.context,
|
||||
borderCrossings: {
|
||||
...payload.context?.borderCrossings,
|
||||
iframe: true
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
updateConfig() {
|
||||
this.settings.init();
|
||||
this.$nextTick( () => this.$forceUpdate());
|
||||
this.eventBus.send(payload.action, payload.config, payload.routingData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -714,10 +711,8 @@ export default {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
|
||||
font-size: 0.9rem;
|
||||
|
||||
// width: 112.25%;
|
||||
// transform: translate(-12.5%, 12.5%) scale(0.75);
|
||||
width: 112.25%;
|
||||
transform: translate(-12.5%, 12.5%) scale(0.75);
|
||||
|
||||
> * {
|
||||
margin-top: 0.5rem;
|
||||
@ -732,7 +727,6 @@ export default {
|
||||
}
|
||||
|
||||
.aard-blocked {
|
||||
font-size: 0.8rem;
|
||||
color: #fa6;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="popup-panel" style="height: 100vh">
|
||||
<div class="popup-panel">
|
||||
<!--
|
||||
NOTE — the code that makes ultrawidify popup work in firefox regardless of whether the
|
||||
extension is being displayed in a normal or a small/overflow popup breaks the popup
|
||||
@ -10,43 +10,24 @@
|
||||
further than that.
|
||||
-->
|
||||
<div v-if="settingsInitialized"
|
||||
style="height: 100vh"
|
||||
class="popup flex flex-col no-overflow"
|
||||
:class="{'popup-chrome': ! BrowserDetect?.firefox}"
|
||||
>
|
||||
<div class="flex flex-col w-full relative header"
|
||||
<div class="flex-row flex-nogrow flex-noshrink relative header"
|
||||
>
|
||||
<div class="flex flex-row w-full" style="height: 42px">
|
||||
<h1 class="flex-grow">
|
||||
<div class="grow shrink">
|
||||
<h1>
|
||||
<span class="smallcaps">Ultrawidify</span>: <small>Quick settings</small>
|
||||
</h1>
|
||||
<button
|
||||
class="settings-header-button"
|
||||
style="align-self: stretch"
|
||||
@click="showInPlayerUi()"
|
||||
>
|
||||
Show settings window
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row w-full">
|
||||
<div v-if="site && siteSettings" style="transform: scale(0.75) translateX(-12.5%); margin-bottom: -0.5rem; align-content: center" class="flex flex-row flex-grow items-center">
|
||||
<div>site: {{site.host}}</div>
|
||||
<SupportLevelIndicator
|
||||
:siteSupportLevel="siteSupportLevel"
|
||||
>
|
||||
</SupportLevelIndicator>
|
||||
</div>
|
||||
|
||||
<!-- Version info -->
|
||||
<div v-if="BrowserDetect?.processEnvChannel !== 'stable'" class="absolute channel-info version-info">
|
||||
<label>Version:</label> <br/>
|
||||
{{ settings.getExtensionVersion() }} (non-stable)
|
||||
</div>
|
||||
<div v-else class="version-info">
|
||||
<label>Version:</label> <br/>
|
||||
{{ settings.getExtensionVersion() }}
|
||||
</div>
|
||||
<div v-if="BrowserDetect?.processEnvChannel !== 'stable'" class="absolute channel-info version-info">
|
||||
Build channel: {{BrowserDetect?.processEnvChannel}} <br/>
|
||||
<label>Version:</label> <br/>
|
||||
{{ settings.getExtensionVersion() }}
|
||||
</div>
|
||||
<div v-else class="version-info">
|
||||
<label>Version:</label> <br/>
|
||||
{{ settings.getExtensionVersion() }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -60,10 +41,7 @@
|
||||
v-for="tab of tabs"
|
||||
:key="tab.id"
|
||||
class="tab flex flex-row"
|
||||
:class="{
|
||||
'active': tab.id === selectedTab,
|
||||
'highlight-tab': tab.highlight,
|
||||
}"
|
||||
:class="{'active': tab.id === selectedTab}"
|
||||
@click="selectTab(tab.id)"
|
||||
>
|
||||
<div class="icon-container">
|
||||
@ -79,28 +57,35 @@
|
||||
</div>
|
||||
|
||||
<!-- CONTENT -->
|
||||
<div class="scrollable window-content" style="flex: 7 7; padding: 1rem;">
|
||||
<div class="scrollable" style="flex: 7 7; padding: 1rem;">
|
||||
<template v-if="settings && siteSettings">
|
||||
<InPlayerUIAdvertisement
|
||||
v-if="selectedTab === 'playerUiCtl'"
|
||||
:eventBus="eventBus"
|
||||
/>
|
||||
<PopupVideoSettings
|
||||
v-if="selectedTab === 'videoSettings'"
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
:siteSettings="siteSettings"
|
||||
:hosts="activeHosts"
|
||||
></PopupVideoSettings>
|
||||
<!-- <PlayerDetectionPanel
|
||||
v-if="selectedTab === 'playerDetection'"
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
:siteSettings="siteSettings"
|
||||
:site="site.host"
|
||||
>
|
||||
</PlayerDetectionPanel> -->
|
||||
<BaseExtensionSettings
|
||||
v-if="selectedTab === 'extensionSettings'"
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
:siteSettings="siteSettings"
|
||||
:site="site.host"
|
||||
:hosts="activeHosts"
|
||||
>
|
||||
</BaseExtensionSettings>
|
||||
<ChangelogPanel
|
||||
v-if="selectedTab === 'changelog'"
|
||||
:settings="settings"
|
||||
></ChangelogPanel>
|
||||
|
||||
<AboutPanel
|
||||
v-if="selectedTab === 'about'"
|
||||
>
|
||||
@ -117,18 +102,17 @@
|
||||
<script>
|
||||
import BaseExtensionSettings from './src/PlayerUiPanels/BaseExtensionSettings.vue'
|
||||
import PlayerDetectionPanel from './src/PlayerUiPanels/PlayerDetectionPanel.vue'
|
||||
import ChangelogPanel from './src/PlayerUiPanels/ChangelogPanel.vue'
|
||||
import PopupVideoSettings from './src/popup/panels/PopupVideoSettings.vue'
|
||||
import InPlayerUIAdvertisement from './src/PlayerUiPanels/InPlayerUiAdvertisement.vue';
|
||||
import AboutPanel from '@csui/src/popup/panels/AboutPanel.vue'
|
||||
import Debug from '../ext/conf/Debug';
|
||||
import BrowserDetect from '../ext/conf/BrowserDetect';
|
||||
import Comms from '../ext/lib/comms/Comms';
|
||||
import CommsClient, {CommsOrigin} from '../ext/lib/comms/CommsClient';
|
||||
import Settings from '../ext/lib/Settings';
|
||||
import Logger from '../ext/lib/Logger';
|
||||
import EventBus from '../ext/lib/EventBus';
|
||||
import {ChromeShittinessMitigations as CSM} from '../common/js/ChromeShittinessMitigations';
|
||||
import SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue'
|
||||
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator';
|
||||
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -137,8 +121,7 @@ export default {
|
||||
PopupVideoSettings,
|
||||
PlayerDetectionPanel,
|
||||
BaseExtensionSettings,
|
||||
SupportLevelIndicator,
|
||||
ChangelogPanel,
|
||||
InPlayerUIAdvertisement,
|
||||
AboutPanel
|
||||
},
|
||||
data () {
|
||||
@ -149,112 +132,89 @@ export default {
|
||||
settingsInitialized: false,
|
||||
narrowPopup: null,
|
||||
sideMenuVisible: null,
|
||||
logAggregator: undefined,
|
||||
logger: undefined,
|
||||
site: undefined,
|
||||
siteSettings: undefined,
|
||||
selectedTab: 'videoSettings',
|
||||
selectedTab: 'playerUiCtl',
|
||||
tabs: [
|
||||
// see this for icons: https://pictogrammers.com/library/mdi/
|
||||
// {id: 'playerUiCtl', label: 'In-player UI', icon: 'artboard'},
|
||||
{id: 'playerUiCtl', label: 'In-player UI', icon: 'artboard'},
|
||||
{id: 'videoSettings', label: 'Video settings', icon: 'crop'},
|
||||
// {id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
|
||||
{id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' },
|
||||
{id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
|
||||
{id: 'about', label: 'About', icon: 'information-outline'},
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
siteSupportLevel() {
|
||||
return (this.site && this.siteSettings) ? this.siteSettings.data.type || 'no-support' : 'waiting';
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.tabs.find(x => x.id === 'changelog').highlight = !this.settings.active?.whatsNewChecked;
|
||||
this.requestSite();
|
||||
},
|
||||
async created() {
|
||||
try {
|
||||
this.logAggregator = new LogAggregator('🔵ext-popup🔵');
|
||||
this.logger = new ComponentLogger(this.logAggregator, 'Popup');
|
||||
this.logger = new Logger();
|
||||
await this.logger.init({
|
||||
allowLogging: true,
|
||||
});
|
||||
|
||||
this.settings = new Settings({afterSettingsSaved: () => this.updateConfig(), logAggregator: this.logAggregator});
|
||||
await this.settings.init();
|
||||
this.settingsInitialized = true;
|
||||
this.settings = new Settings({afterSettingsSaved: () => this.updateConfig(), logger: this.logger});
|
||||
await this.settings.init();
|
||||
this.settingsInitialized = true;
|
||||
|
||||
// const port = chrome.runtime.connect({name: 'popup-port'});
|
||||
// port.onMessage.addListener( (m,p) => this.processReceivedMessage(m,p));
|
||||
// CSM.setProperty('port', port);
|
||||
// const port = chrome.runtime.connect({name: 'popup-port'});
|
||||
// port.onMessage.addListener( (m,p) => this.processReceivedMessage(m,p));
|
||||
// CSM.setProperty('port', port);
|
||||
|
||||
this.eventBus = new EventBus();
|
||||
this.eventBus.subscribe(
|
||||
'set-current-site',
|
||||
{
|
||||
source: this,
|
||||
function: (config, context) => {
|
||||
console.log('set-current-site | this.site:', this.site, 'config.site:', config.site);
|
||||
|
||||
if (this.site) {
|
||||
if (!this.site.host) {
|
||||
// dunno why this fix is needed, but sometimes it is
|
||||
this.site.host = config.site.host;
|
||||
}
|
||||
this.eventBus = new EventBus();
|
||||
this.eventBus.subscribe(
|
||||
'set-current-site',
|
||||
{
|
||||
source: this,
|
||||
function: (config, context) => {
|
||||
if (this.site) {
|
||||
if (!this.site.host) {
|
||||
// dunno why this fix is needed, but sometimes it is
|
||||
this.site.host = config.site.host;
|
||||
}
|
||||
this.site = config.site;
|
||||
// this.selectedSite = this.selectedSite || config.site.host;
|
||||
this.siteSettings = this.settings.getSiteSettings(this.site.host);
|
||||
this.eventBus.setupPopupTunnelWorkaround({
|
||||
origin: CommsOrigin.Popup,
|
||||
comms: {
|
||||
forwardTo: 'active'
|
||||
}
|
||||
});
|
||||
}
|
||||
this.site = config.site;
|
||||
// this.selectedSite = this.selectedSite || config.site.host;
|
||||
this.siteSettings = this.settings.getSiteSettings(this.site.host);
|
||||
|
||||
this.eventBus.setupPopupTunnelWorkaround({
|
||||
origin: CommsOrigin.Popup,
|
||||
comms: {
|
||||
forwardTo: 'active'
|
||||
}
|
||||
});
|
||||
|
||||
this.loadFrames(this.site);
|
||||
|
||||
this.loadHostnames();
|
||||
this.loadFrames();
|
||||
}
|
||||
},
|
||||
);
|
||||
this.eventBus.subscribe(
|
||||
'open-popup-settings',
|
||||
{
|
||||
source: this,
|
||||
function: (config) => {
|
||||
this.selectTab(config.tab)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
this.comms = new CommsClient('popup-port', this.logger, this.eventBus);
|
||||
this.eventBus.setComms(this.comms);
|
||||
this.eventBus.setupPopupTunnelWorkaround({
|
||||
origin: CommsOrigin.Popup,
|
||||
comms: {forwardTo: 'active'}
|
||||
this.comms = new CommsClient('popup-port', this.logger, this.eventBus);
|
||||
this.eventBus.setComms(this.comms);
|
||||
this.eventBus.setupPopupTunnelWorkaround({
|
||||
origin: CommsOrigin.Popup,
|
||||
comms: {forwardTo: 'active'}
|
||||
});
|
||||
|
||||
|
||||
// ensure we'll clean player markings on popup close
|
||||
window.addEventListener("unload", () => {
|
||||
CSM.port.postMessage({
|
||||
cmd: 'unmark-player',
|
||||
forwardToAll: true,
|
||||
});
|
||||
// if (BrowserDetect.anyChromium) {
|
||||
// chrome.extension.getBackgroundPage().sendUnmarkPlayer({
|
||||
// cmd: 'unmark-player',
|
||||
// forwardToAll: true,
|
||||
// });
|
||||
// }
|
||||
});
|
||||
|
||||
|
||||
// ensure we'll clean player markings on popup close
|
||||
window.addEventListener("unload", () => {
|
||||
CSM.port.postMessage({
|
||||
cmd: 'unmark-player',
|
||||
forwardToAll: true,
|
||||
});
|
||||
// if (BrowserDetect.anyChromium) {
|
||||
// chrome.extension.getBackgroundPage().sendUnmarkPlayer({
|
||||
// cmd: 'unmark-player',
|
||||
// forwardToAll: true,
|
||||
// });
|
||||
// }
|
||||
});
|
||||
|
||||
// get info about current site from background script
|
||||
while (true) {
|
||||
this.requestSite();
|
||||
await this.sleep(5000);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[Popup.vue::created()] An error happened:', e)
|
||||
// get info about current site from background script
|
||||
while (true) {
|
||||
this.requestSite();
|
||||
await this.sleep(5000);
|
||||
}
|
||||
},
|
||||
async updated() {
|
||||
@ -273,9 +233,6 @@ export default {
|
||||
},
|
||||
|
||||
methods: {
|
||||
showInPlayerUi() {
|
||||
this.eventBus.send('uw-set-ui-state', {globalUiVisible: true}, {comms: {forwardTo: 'active'}});
|
||||
},
|
||||
async sleep(t) {
|
||||
return new Promise( (resolve,reject) => {
|
||||
setTimeout(() => resolve(), t);
|
||||
@ -290,7 +247,6 @@ export default {
|
||||
// CSM.port.postMessage({command: 'get-current-site'});
|
||||
this.eventBus.send(
|
||||
'get-current-site',
|
||||
{},
|
||||
{
|
||||
comms: {forwardTo: 'active'}
|
||||
}
|
||||
@ -305,36 +261,65 @@ export default {
|
||||
selectTab(tab) {
|
||||
this.selectedTab = tab;
|
||||
},
|
||||
processReceivedMessage(message, port) {
|
||||
this.logger.log('info', 'popup', '[popup::processReceivedMessage] received message:', message)
|
||||
|
||||
if (message.command === 'set-current-site'){
|
||||
if (this.site) {
|
||||
if (!this.site.host) {
|
||||
// dunno why this fix is needed, but sometimes it is
|
||||
this.site.host = site.tabHostname;
|
||||
}
|
||||
}
|
||||
this.site = message.site;
|
||||
|
||||
// update activeSites
|
||||
// this.activeSites = this.activeSites.filter(x => x.host !== message.site);
|
||||
|
||||
// add current site
|
||||
// this.activeSites = unshift({
|
||||
// host: message.site.host,
|
||||
// isIFrame: false, // currently unused
|
||||
// });
|
||||
this.selectedSite = this.selectedSite || message.site.host;
|
||||
|
||||
this.loadFrames(this.site);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
isDefaultFrame(frameId) {
|
||||
return frameId === '__playing' || frameId === '__all';
|
||||
},
|
||||
loadHostnames() {
|
||||
this.activeHosts = this.site.hostnames;
|
||||
},
|
||||
loadFrames() {
|
||||
this.activeFrames = [{
|
||||
this.activeSites = [{
|
||||
host: this.site.host,
|
||||
isIFrame: false, // not used tho. Maybe one day
|
||||
}];
|
||||
this.selectedSite = this.selectedSite || this.site.host;
|
||||
|
||||
for (const frame in this.site.frames) {
|
||||
if (!this.activeFrames.find(x => x.host === this.site.frames[frame].host)) {
|
||||
this.activeFrames.push({
|
||||
id: `${this.site.id}-${frame}`,
|
||||
label: this.site.frames[frame].host,
|
||||
host: this.site.frames[frame].host,
|
||||
...this.site.frames[frame],
|
||||
...this.settings.active.sites[this.site.frames[frame].host]
|
||||
})
|
||||
};
|
||||
}
|
||||
// for (const frame in videoTab.frames) {
|
||||
// this.activeFrames.push({
|
||||
// id: `${this.site.id}-${frame}`,
|
||||
// label: videoTab.frames[frame].host,
|
||||
// ...this.frameStore[frame],
|
||||
// })
|
||||
|
||||
// // only add each host once at most
|
||||
// if (!this.activeSites.find(x => x.host === videoTab.frames[frame].host)) {
|
||||
// this.activeSites.push({
|
||||
// host: videoTab.frames[frame].host,
|
||||
// isIFrame: undefined // maybe one day
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
// update whether video tab can be shown
|
||||
// this.updateCanShowVideoTab();
|
||||
},
|
||||
getRandomColor() {
|
||||
return `rgb(${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)})`;
|
||||
},
|
||||
updateConfig() {
|
||||
this.settings.init();
|
||||
this.$nextTick( () => this.$forceUpdate());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -350,15 +335,12 @@ export default {
|
||||
|
||||
.header {
|
||||
background-color: rgb(90, 28, 13);
|
||||
background-color: rgb(0,0,0);
|
||||
color: #fff;
|
||||
padding: 8px;
|
||||
|
||||
// display: flex;
|
||||
// flex-direction: row;
|
||||
// justify-content: space-between;
|
||||
|
||||
border-bottom: 1px dotted #fa6;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
|
||||
|
||||
h1 {
|
||||
@ -376,21 +358,6 @@ export default {
|
||||
|
||||
}
|
||||
|
||||
.settings-header-button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
padding: 0.5rem 2rem;
|
||||
text-transform: lowercase;
|
||||
font-variant: small-caps;
|
||||
|
||||
background-color: #000;
|
||||
border: 1px solid #fa68;
|
||||
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
.site-support-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -559,15 +526,6 @@ export default {
|
||||
flex-shrink: 1;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
&.highlight-tab {
|
||||
opacity: 0.9;
|
||||
color: #eee;
|
||||
|
||||
// .label {
|
||||
// color: rgb(239, 192, 152);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -587,10 +545,4 @@ pre {
|
||||
h1 {
|
||||
margin: 0; padding: 0; font-weight: 400; font-size:24px;
|
||||
}
|
||||
|
||||
.window-content {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
|
@ -3,8 +3,6 @@ import GlobalFrame from './GlobalFrame';
|
||||
import mdiVue from 'mdi-vue/v3';
|
||||
import * as mdijs from '@mdi/js';
|
||||
|
||||
import './src/res-common/common.scss';
|
||||
|
||||
// NOTE — this is in-player interface for ultrawidify
|
||||
// it is injected into the page in UI.init()
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" style="position: relative; height: 600px">
|
||||
<html lang="en" style="position: relative">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Title</title>
|
||||
@ -10,9 +10,9 @@
|
||||
<% } %>
|
||||
</head>
|
||||
<body
|
||||
style="width: 100%; height: 100vh; overflow-x: hidden; min-width: 750px;"
|
||||
style="width: 100%; height: 100%; overflow-x: hidden; min-width: 750px"
|
||||
>
|
||||
<div id="app" style="max-height: 100vh;">
|
||||
<div id="app">
|
||||
|
||||
</div>
|
||||
<script src="csui-popup.js"></script>
|
||||
|
@ -3,8 +3,6 @@ import PlayerOverlay from './PlayerOverlay';
|
||||
import mdiVue from 'mdi-vue/v3';
|
||||
import * as mdijs from '@mdi/js';
|
||||
|
||||
import './src/res-common/common.scss';
|
||||
|
||||
// NOTE — this is in-player interface for ultrawidify
|
||||
// it is injected into the page in UI.init()
|
||||
|
||||
|
@ -23,7 +23,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import Logger from '../../../ext/lib/Logger';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -5,10 +5,6 @@
|
||||
|
||||
// @import "form.scss";
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: $background-primary;
|
||||
color: $text-normal;
|
||||
@ -22,21 +18,8 @@ body {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.main-window::before {
|
||||
content: ' ';
|
||||
color: #000;
|
||||
position: absolute;
|
||||
left: -0.5rem;
|
||||
top: -0.5rem;
|
||||
z-index: -1;
|
||||
width: calc(100% + 1rem);
|
||||
height: calc(100% + 1rem);
|
||||
background-image: url('/csui/res/img/uw-window-bg-texture-3.webp');
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
/* STANDARD WIDTHS AND HEIGHTS */
|
||||
.w100, .w-full {
|
||||
.w100 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -57,6 +40,8 @@ body {
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* overflow stuff */
|
||||
.overflow-y-auto {
|
||||
overflow: auto;
|
||||
@ -67,19 +52,6 @@ body {
|
||||
scrollbar-color: rgba($primary-color, 0.5) $background-primary;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(#fa6, 0.5);
|
||||
border-radius: 20px;
|
||||
border: transparent;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* scrollbars for chrome/webkit */
|
||||
.overflow-y-auto::-webkit-scrollbar {
|
||||
@ -131,6 +103,7 @@ body {
|
||||
}
|
||||
|
||||
.label {
|
||||
padding-top: 1.5rem;
|
||||
font-size: 1.5rem;
|
||||
font-variant: small-caps;
|
||||
font-weight: 600;
|
||||
@ -252,7 +225,6 @@ small {
|
||||
background-color: #410 !important;
|
||||
}
|
||||
|
||||
button,
|
||||
.button {
|
||||
/*display: inline-block;*/
|
||||
// padding-top: 8px;
|
||||
@ -266,27 +238,6 @@ button,
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
user-select: none;;
|
||||
|
||||
background-color: rgba(11,11,11,0.75);
|
||||
|
||||
|
||||
padding: 0.5rem 2rem;
|
||||
background-color: rgba(11, 11, 11, 0.5);
|
||||
border: 1px solid transparent;
|
||||
user-select: none !important;
|
||||
|
||||
&.primary {
|
||||
background-color: #fa6;
|
||||
color: #000;
|
||||
}
|
||||
&.danger {
|
||||
background-color: #ff2211 !important;
|
||||
color:#000;
|
||||
}
|
||||
&.disabled {
|
||||
filter: saturate(0%);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -367,10 +318,6 @@ button,
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.info-button {
|
||||
color: $info-color;
|
||||
border: 1px solid $info-color;
|
||||
}
|
||||
.info {
|
||||
color: $info-color;
|
||||
padding-left: 35px;
|
||||
|
@ -14,25 +14,19 @@
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.flex-grow, .grow {
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.flex-nogrow, .grow-0 {
|
||||
.flex-nogrow {
|
||||
flex-grow: 0;
|
||||
}
|
||||
.flex-shrink, .shrink {
|
||||
.flex-shrink {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
.flex-noshrink, .shrink-0 {
|
||||
.flex-noshrink {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
.items-baseline {
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 10 KiB |
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div
|
||||
class="popup-panel flex flex-col uw-clickable h-full main-window relative"
|
||||
class="popup-panel flex flex-col uw-clickable h-full"
|
||||
>
|
||||
<div class="popup-window-header">
|
||||
<div class="header-title">
|
||||
@ -30,26 +30,22 @@
|
||||
<div
|
||||
v-for="tab of tabs"
|
||||
:key="tab.id"
|
||||
class="tab"
|
||||
:class="{
|
||||
'active': tab.id === selectedTab,
|
||||
'highlight-tab': tab.highlight,
|
||||
}"
|
||||
@click="selectTab(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 class="icon-container">
|
||||
<mdicon
|
||||
v-if="tab.icon"
|
||||
:name="tab.icon"
|
||||
:size="32"
|
||||
/>
|
||||
</div>
|
||||
<div class="label">
|
||||
{{tab.label}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -90,7 +86,6 @@
|
||||
<PlayerUiSettings
|
||||
v-if="selectedTab === 'playerUiSettings'"
|
||||
:settings="settings"
|
||||
:siteSettings="siteSettings"
|
||||
:eventBus="eventBus"
|
||||
>
|
||||
</PlayerUiSettings>
|
||||
@ -99,7 +94,6 @@
|
||||
:settings="settings"
|
||||
:siteSettings="siteSettings"
|
||||
:site="site"
|
||||
:enableSettingsEditor="true"
|
||||
></BaseExtensionSettings>
|
||||
<AutodetectionSettingsPanel
|
||||
v-if="selectedTab === 'autodetectionSettings'"
|
||||
@ -144,7 +138,8 @@ import ChangelogPanel from './PlayerUiPanels/ChangelogPanel.vue'
|
||||
import AboutPanel from '@csui/src/PlayerUiPanels/AboutPanel.vue'
|
||||
import PlayerUiSettings from './PlayerUiPanels/PlayerUiSettings.vue'
|
||||
import ResetBackupPanel from './PlayerUiPanels/ResetBackupPanel.vue'
|
||||
import SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue'
|
||||
|
||||
import SupportLevelIndicator from './components/SupportLevelIndicator.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@ -173,9 +168,10 @@ export default {
|
||||
{id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
|
||||
{id: 'autodetectionSettings', label: 'Autodetection options', icon: 'auto-fix'},
|
||||
// {id: 'advancedOptions', label: 'Advanced options', icon: 'cogs' },
|
||||
// {id: 'debugging', label: 'Debugging', icon: 'bug-outline' }
|
||||
{id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
|
||||
{id: 'about', label: 'About', icon: 'information-outline'},
|
||||
{id: 'debugging', label: 'Debugging', icon: 'bug-outline', hidden: true},
|
||||
// {id: 'resetBackup', label: 'Reset and backup', icon: 'file-restore-outline'},
|
||||
],
|
||||
selectedTab: 'extensionSettings',
|
||||
BrowserDetect: BrowserDetect,
|
||||
@ -200,13 +196,11 @@ export default {
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.settings.listenAfterChange(this.setDebugTabVisibility);
|
||||
|
||||
if (this.defaultTab) {
|
||||
this.selectedTab = this.defaultTab;
|
||||
}
|
||||
this.siteSettings = this.settings.getSiteSettings(this.site);
|
||||
this.tabs.find(x => x.id === 'changelog').highlight = !this.settings.active?.whatsNewChecked;
|
||||
this.tabs.find(x => x.id === 'changelog').highlight = !this.settings.active.whatsNewChecked;
|
||||
|
||||
this.eventBus.subscribe(
|
||||
'uw-show-ui',
|
||||
@ -219,10 +213,8 @@ export default {
|
||||
},
|
||||
}
|
||||
)
|
||||
this.setDebugTabVisibility();
|
||||
},
|
||||
destroyed() {
|
||||
this.settings.removeListenerAfterChange(this.setDebugTabVisibility);
|
||||
this.eventBus.unsubscribeAll(this);
|
||||
},
|
||||
methods: {
|
||||
@ -238,12 +230,6 @@ export default {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -262,8 +248,6 @@ export default {
|
||||
// height: 100%;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
.tab-row {
|
||||
width: 22rem;
|
||||
flex-grow: 0;
|
||||
@ -385,7 +369,7 @@ export default {
|
||||
|
||||
padding: 2rem;
|
||||
font-size: 1.5rem;
|
||||
height: 6rem;
|
||||
height: 4rem;
|
||||
|
||||
border-bottom: 1px solid rgba(128, 128, 128, 0.5);
|
||||
border-top: 1px solid rgba(128, 128, 128, 0.5);
|
||||
|
@ -10,7 +10,7 @@
|
||||
<div class="w-[1/2]" style="width: 50%">
|
||||
<h2>Report a problem</h2>
|
||||
<p>
|
||||
Please report <strike>undocumented features</strike> bugs using one of the following options (in order of preference):
|
||||
You may report <strike>undocumented features</strike> bugs using one of the following options (in order of preference):
|
||||
</p>
|
||||
<ul>
|
||||
<li> <a target="_blank" href="https://github.com/tamius-han/ultrawidify/issues"><b>Github (preferred)</b></a><br/></li>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="alignment-box" :class="{large: large}">
|
||||
<div class="alignment-box">
|
||||
<div
|
||||
class="col top left"
|
||||
@click="align(VideoAlignment.Left, VideoAlignment.Top)"
|
||||
@ -60,10 +60,10 @@
|
||||
<script>
|
||||
import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum';
|
||||
|
||||
|
||||
export default {
|
||||
props: [
|
||||
'eventBus',
|
||||
'large',
|
||||
'eventBus'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
@ -89,14 +89,6 @@ export default {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 0.5rem;
|
||||
|
||||
&.large {
|
||||
max-width: 15rem;
|
||||
|
||||
.col {
|
||||
width: 4rem;
|
||||
height: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.col {
|
||||
display: flex;
|
||||
|
@ -234,7 +234,6 @@
|
||||
/>
|
||||
<input
|
||||
v-model="settings.active.arDetect.timers.playing"
|
||||
@change="setArCheckFrequency($event.target.value)"
|
||||
class="input"
|
||||
type="text"
|
||||
>
|
||||
@ -245,10 +244,10 @@
|
||||
<div class="field">
|
||||
<div class="label">Frame extraction canvas type:</div>
|
||||
<div class="select">
|
||||
<select v-model="settings.active.arDetect.aardType" @change="settings.saveWithoutReload">
|
||||
<select v-model="settings.active.arDetect.aardType">
|
||||
<option value="auto">Automatic</option>
|
||||
<option value="webgl">WebGL only</option>
|
||||
<option value="legacy">Legacy / fallback</option>
|
||||
<option value="fallback">Legacy / fallback</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -265,34 +264,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="settings.active.ui.devMode" class="settings-segment">
|
||||
<p>
|
||||
<b>Debug options</b>
|
||||
</p>
|
||||
<div class="flex flex-row">
|
||||
<div>
|
||||
<div>
|
||||
<button @click="eventBus.sendToTunnel('aard-enable-debug', true)">Show debug overlay</button>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">Show debug overlay on startup</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="settings.active.ui.dev.aardDebugOverlay.showOnStartup"
|
||||
@change="settings.saveWithoutReload"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<JsonEditor
|
||||
v-model="settingsJson"
|
||||
>
|
||||
</JsonEditor>
|
||||
<button @click="saveDebugUiSettings">Save debug UI settings</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -309,15 +280,14 @@ import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
||||
import StretchType from '../../../common/enums/StretchType.enum';
|
||||
import CropModePersistence from '../../../common/enums/CropModePersistence.enum';
|
||||
import AlignmentOptionsControlComponent from './AlignmentOptionsControlComponent.vue';
|
||||
import JsonEditor from '@csui/src/components/JsonEditor';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ShortcutButton,
|
||||
EditShortcutButton,
|
||||
Button,
|
||||
AlignmentOptionsControlComponent,
|
||||
JsonEditor
|
||||
data() {
|
||||
return {
|
||||
exec: null,
|
||||
performanceData: {},
|
||||
graphRefreshInterval: undefined,
|
||||
}
|
||||
},
|
||||
mixins: [
|
||||
],
|
||||
@ -327,16 +297,6 @@ export default {
|
||||
'eventBus',
|
||||
'site'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
exec: null,
|
||||
performanceData: {},
|
||||
graphRefreshInterval: undefined,
|
||||
settingsJson: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
created() {
|
||||
this.eventBus.subscribe(
|
||||
'uw-config-broadcast',
|
||||
@ -346,15 +306,24 @@ export default {
|
||||
}
|
||||
);
|
||||
},
|
||||
destroyed() {
|
||||
this.eventBus.unsubscribeAll(this);
|
||||
},
|
||||
mounted() {
|
||||
this.eventBus.sendToTunnel('get-aard-timing');
|
||||
this.graphRefreshInterval = setInterval(() => this.eventBus.sendToTunnel('get-aard-timing'), 500);
|
||||
this.resetSettingsEditor();
|
||||
},
|
||||
destroyed() {
|
||||
this.eventBus.unsubscribeAll(this);
|
||||
clearInterval(this.graphRefreshInterval);
|
||||
},
|
||||
components: {
|
||||
ShortcutButton,
|
||||
EditShortcutButton,
|
||||
Button,
|
||||
AlignmentOptionsControlComponent
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
async openOptionsPage() {
|
||||
BrowserDetect.runtime.openOptionsPage();
|
||||
@ -371,15 +340,7 @@ export default {
|
||||
this.$nextTick( () => this.$forceUpdate() );
|
||||
}
|
||||
},
|
||||
resetSettingsEditor() {
|
||||
this.settingsJson = JSON.parse(JSON.stringify(this.settings?.active.ui.dev.aardDebugOverlay ?? {}));
|
||||
},
|
||||
saveDebugUiSettings() {
|
||||
this.settings.active.ui.dev.aardDebugOverlay = JSON.parse(JSON.stringify(this.settingsJson));
|
||||
this.settings.saveWithoutReload();
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,195 +1,100 @@
|
||||
<template>
|
||||
<div class="flex flex-row w-full h-full">
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="flex flex-col w-100">
|
||||
|
||||
<!-- 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>
|
||||
<!-- TAB ROW -->
|
||||
<div class="flex flex-row">
|
||||
<div
|
||||
class="tab"
|
||||
:class="{'active': tab === 'siteSettings'}"
|
||||
@click="setTab('siteSettings')"
|
||||
>
|
||||
Settings for current site<br/>
|
||||
<small>{{ site }}</small>
|
||||
</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"
|
||||
: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
|
||||
class="tab"
|
||||
:class="{'active': tab === 'extensionSettings'}"
|
||||
@click="setTab(tab = 'extensionSettings')"
|
||||
>
|
||||
Default settings for extension
|
||||
</div>
|
||||
<div
|
||||
class="tab"
|
||||
:class="{'active': tab === 'otherSites'}"
|
||||
@click="setTab(tab = 'otherSites')"
|
||||
>
|
||||
Settings for other sites
|
||||
</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>
|
||||
<template v-if="tab === 'siteSettings' && siteSettings">
|
||||
<!-- <div class="button">
|
||||
Reset settings for site
|
||||
</div> -->
|
||||
<SiteExtensionSettings
|
||||
v-if="settings"
|
||||
:settings="settings"
|
||||
:siteSettings="siteSettings"
|
||||
:isDefaultConfiguration="false"
|
||||
></SiteExtensionSettings>
|
||||
</template>
|
||||
|
||||
<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>
|
||||
<template v-if="tab === 'extensionSettings' && globalSettings">
|
||||
<SiteExtensionSettings
|
||||
v-if="settings"
|
||||
:settings="settings"
|
||||
:siteSettings="globalSettings"
|
||||
:isDefaultConfiguration="true"
|
||||
></SiteExtensionSettings>
|
||||
</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>
|
||||
Pressing the button will reset settings to default without asking.
|
||||
</p>
|
||||
<button
|
||||
class="danger"
|
||||
@click="resetSettings"
|
||||
>
|
||||
Reset settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import SiteExtensionSettings from './PanelComponents/ExtensionSettings/SiteExtensionSettings.vue';
|
||||
import FrameSiteSettings from './PanelComponents/ExtensionSettings/FrameSiteSettings.vue';
|
||||
import OtherSiteSettings from './PanelComponents/ExtensionSettings/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,
|
||||
data() {
|
||||
return {
|
||||
tab: 'siteSettings'
|
||||
}
|
||||
},
|
||||
mixins: [],
|
||||
mixins: [
|
||||
|
||||
],
|
||||
props: [
|
||||
'settings',
|
||||
'site',
|
||||
'enableSettingsEditor',
|
||||
'hosts',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
tab: 'siteSettings',
|
||||
importSettingDialogConfig: {visible: false},
|
||||
allowSettingsEditing: false,
|
||||
editorSaveFinished: false,
|
||||
settingsJson: {},
|
||||
settingsSnapshots: []
|
||||
}
|
||||
},
|
||||
components: {
|
||||
SiteExtensionSettings,
|
||||
OtherSiteSettings
|
||||
},
|
||||
computed: {
|
||||
globalSettings() {
|
||||
return this.settings?.getSiteSettings('@global') ?? null;
|
||||
@ -200,113 +105,17 @@ export default {
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.resetSettingsEditor();
|
||||
this.loadSettingsSnapshots();
|
||||
resetSettings() {
|
||||
this.settings.active = JSON.parse(JSON.stringify(this.settings.default));
|
||||
this.settings.saveWithoutReload();
|
||||
}
|
||||
},
|
||||
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>
|
||||
|
||||
|
@ -1,36 +1,98 @@
|
||||
<template>
|
||||
<div class="flex flex-col w-full h-full gap-2">
|
||||
<div class="flex flex-row gap-8 bg-black flex-wrap w-full">
|
||||
<div class="min-w-[400px] max-w-[520px] grow shrink">
|
||||
<div class="flex flex-row gap-2 bg-black">
|
||||
<div class="w-[1/2]" style="width: 50%">
|
||||
<h1>What's new</h1>
|
||||
<!-- <p>Full changelog for older versions <a href="https://github.com/tamius-han/ultrawidify/blob/master/CHANGELOG.md" target="_blank">is available here</a>.</p> -->
|
||||
<p>Full changelog for older versions <a href="https://github.com/tamius-han/ultrawidify/blob/master/CHANGELOG.md" target="_blank">is available here</a>.</p>
|
||||
|
||||
<h2>6.3.0</h2>
|
||||
<h2>6.2.0</h2>
|
||||
<p>
|
||||
There's been another major change that skips a version.
|
||||
</p>
|
||||
<ul>
|
||||
<li>Automatic aspect ratio detection: do not apply negative aspect ratios</li>
|
||||
<li>Keyboard zoom now works</li>
|
||||
<li><code>www.youtube-nocookie.com</code> has been added to the "officially supported" list</li>
|
||||
<li>Fixed the bug where UI would sometimes refuse to stay hidden</li>
|
||||
<li>New experimental zoom options</li>
|
||||
<li>Subdomains now inherit same settings as their parent domain by default</li>
|
||||
<li>Extension attempts to detect embedded content.</li>
|
||||
<li>Parts of automatic aspect ratio detection use WebGL now. This should result in improved performance. Slightly underwhelming blog post about this issue can be found <a href="https://stuff.tamius.net/sacred-texts/2024/10/02/hot-dang-i-shouldnt-have-given-on-webgl/" target="_blank">here</a>.</li>
|
||||
<li>In-player UI now indicates whether a site is having a problem with automatic aspect ratio detection.</li>
|
||||
<li>UI improvements</li>
|
||||
</ul>
|
||||
<p>
|
||||
Since automatic aspect ratio detection had to be rewritten completely and since the new version needs to be out 5 days ago, there are some regressions in the automatic aspect ratio detection.
|
||||
</p>
|
||||
|
||||
Minor revisions:
|
||||
|
||||
<h3>6.2.1</h3>
|
||||
<ul>
|
||||
<li>Fixed offset issue on Netflix</li>
|
||||
<li>Fixed issue with overlay iframe not being transparent on twitter</li>
|
||||
</ul>
|
||||
|
||||
<h3>6.2.2</h3>
|
||||
<ul>
|
||||
<li>Fixed the issue where stretching was not applied if no cropping was used</li>
|
||||
<li>Half-fixed in-player UI on sites where player is not detected correctly</li>
|
||||
</ul>
|
||||
|
||||
<h3>6.2.3</h3>
|
||||
<ul>
|
||||
<li>Fixed default persistent mode</li>
|
||||
</ul>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>6.0.0</h2>
|
||||
<p>
|
||||
I don't think I need to write a changelog for this one. I've also been working on this, on and (mostly) off, for ... a long time, so I might have "kinda forgot" some minor things.
|
||||
</p>
|
||||
<ul>
|
||||
<li><b>Manifest v3</b>. Bit late, but still.</li>
|
||||
<li>
|
||||
<b>In-player UI.</b><br/>
|
||||
This one took major effort to pull off. Required some changes under the hood, required me to de-spaghettify some code. The UI will only show
|
||||
if the video takes up sufficient amount of space (currently determined as 960 pixels wide).
|
||||
</li>
|
||||
<li>
|
||||
<b>New alignment options.</b> Video can be aligned vertically as well as horizontally.
|
||||
</li>
|
||||
<!-- <li>
|
||||
By default, Ultrawidify will not kick in on "small videos". You have to be either in full screen or theater mode. Ultrawidify assumes that any
|
||||
video that takes up more than 85% of the window width is being viewed in theater mode. This value may be tweaked later on. This feature can be
|
||||
configured (or turned off entirely) via 'Advanced options' menu on the in-player UI.
|
||||
</li> -->
|
||||
<li>
|
||||
The in-player extension UI will do a better job differentiating between the various level of support: "official" for the sites that Tam can check
|
||||
on his own, "fingers crossed" for sites that Tam can't check because my load-bearing credit card can't support the weight of a dozen subscription
|
||||
services, and "community support" for sites that enjoy support through the helping hand of people willing to help.
|
||||
</li>
|
||||
<li>
|
||||
The in-player UI should now display a warning whenever Ultrawidify detects automatic aspect ratio detection isn't happening because the site uses DRM.
|
||||
</li>
|
||||
<!-- <li>
|
||||
Better zooming. The slider is back, baby.
|
||||
</li> -->
|
||||
<!-- <li>
|
||||
Panning option that's a bit more intuitive.
|
||||
</li> -->
|
||||
</ul>
|
||||
<h3>Regressions</h3>
|
||||
<ul>
|
||||
<li>
|
||||
Due to major changes under the hood, custom settings from older versions were NOT been migrated (probably).
|
||||
</li>
|
||||
<li>
|
||||
It is unclear how extension acts on sites with more than one video.
|
||||
</li>
|
||||
<li>
|
||||
Extension lost the ability to discriminate between iframes. Actions taken through the popup will be affect <b>all</b> iframes.
|
||||
</li>
|
||||
<li>
|
||||
Hope the UI is worth the regressions, because getting UI to work has been a MAJOR pain in the ass.
|
||||
</li>
|
||||
</ul>
|
||||
<p>Minor revisions:</p>
|
||||
<h2>6.0.1</h2>
|
||||
<p>Fixed external links.</p>
|
||||
</div>
|
||||
<div style="width: 1rem; height: 0px;"></div>
|
||||
<div class="min-w-[400px] max-w-[520px] grow shrink">
|
||||
<h2>Report a problem</h2>
|
||||
<p>
|
||||
Please report <strike>undocumented features</strike> bugs using one of the following options (in order of preference):
|
||||
</p>
|
||||
<ul>
|
||||
<li> <a target="_blank" href="https://github.com/tamius-han/ultrawidify/issues"><b>Github (preferred)</b></a><br/></li>
|
||||
<li>Email: <a target="_blank" :href="mailtoLink">tamius.han@gmail.com</a></li>
|
||||
</ul>
|
||||
<p>
|
||||
When reporting bugs, please include extension version, whether you installed the extension from, and description of your problem.
|
||||
</p>
|
||||
<p> </p>
|
||||
<p> </p>
|
||||
<div class="w-[1/2]" style="width: 50%; padding-left: 1rem; padding-top: 5rem;">
|
||||
<h2>Thank you monies</h2>
|
||||
<p>
|
||||
If you think I deserve money for the work I did up to this point, you can bankroll my caffeine addiction.
|
||||
@ -38,28 +100,17 @@
|
||||
<p class="text-center">
|
||||
<a class="donate" href="https://www.paypal.com/paypalme/tamius" target="_blank">Donate on Paypal</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import BrowserDetect from '@src/ext/conf/BrowserDetect';
|
||||
|
||||
export default({
|
||||
props: [
|
||||
'settings'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
BrowserDetect: BrowserDetect,
|
||||
// reminder — webextension-polyfill doesn't seem to work in vue!
|
||||
addonVersion: BrowserDetect.firefox ? chrome.runtime.getManifest().version : chrome.runtime.getManifest().version,
|
||||
addonSource: BrowserDetect.processEnvVersion,
|
||||
mailtoLink: '',
|
||||
redditLink: '',
|
||||
showEasterEgg: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.settings.active.whatsNewChecked = true;
|
||||
this.settings.saveWithoutReload();
|
||||
@ -74,10 +125,6 @@ export default({
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
p, li {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
@ -6,12 +6,20 @@
|
||||
|
||||
<div>Logger configuration:</div>
|
||||
|
||||
<JsonEditor
|
||||
v-model="lastSettings"
|
||||
></JsonEditor>
|
||||
<template v-if="loggerPanel.pasteConfMode">
|
||||
|
||||
</template>
|
||||
<template v-else>
|
||||
<JsonObject
|
||||
label="logger-settings"
|
||||
:value="currentSettings"
|
||||
:ignoreKeys="{'allowLogging': false}"
|
||||
@change="updateSettingsUi"
|
||||
></JsonObject>
|
||||
</template>
|
||||
|
||||
<div class="flex flex-row flex-end">
|
||||
<div class="button" @click="getLoggerSettings()">
|
||||
<div class="button" @click="restoreLoggerSettings()">
|
||||
Revert
|
||||
</div>
|
||||
<div class="button button-primary" @click="saveLoggerSettings()">
|
||||
@ -27,12 +35,12 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { LogAggregator, BLANK_LOGGER_CONFIG } from '@src/ext/lib/logging/LogAggregator';
|
||||
import JsonEditor from '@csui/src/components/JsonEditor';
|
||||
import JsonObject from '../components/JsonEditor/JsonObject.vue'
|
||||
import Logger, { baseLoggingOptions } from '../../../ext/lib/Logger';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
JsonEditor
|
||||
JsonObject
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -79,23 +87,38 @@ export default {
|
||||
...this.loggerPanelRotation[Math.floor(Math.random() * this.loggerPanelRotation.length)],
|
||||
pasteConfMode: false,
|
||||
};
|
||||
this.getLoggerSettings();
|
||||
this.loadDefaultConfig();
|
||||
},
|
||||
methods: {
|
||||
loadDefaultConfig() {
|
||||
this.lastSettings = JSON.parse(JSON.stringify(BLANK_LOGGER_CONFIG));
|
||||
this.lastSettings = baseLoggingOptions;
|
||||
this.parsedSettings = JSON.stringify(this.lastSettings, null, 2) || '';
|
||||
this.currentSettings = JSON.parse(JSON.stringify(this.lastSettings));
|
||||
},
|
||||
async getLoggerSettings() {
|
||||
this.lastSettings = await LogAggregator.getConfig() || BLANK_LOGGER_CONFIG;
|
||||
this.lastSettings = await Logger.getConfig() || baseLoggingOptions;
|
||||
this.parsedSettings = JSON.stringify(this.lastSettings, null, 2) || '';
|
||||
this.currentSettings = JSON.parse(JSON.stringify(this.lastSettings));
|
||||
},
|
||||
async saveLoggerSettings() {
|
||||
console.log('Saving logger settings', this.lastSettings);
|
||||
await LogAggregator.saveConfig({...this.lastSettings});
|
||||
console.log('[ok] logger settings saved');
|
||||
updateSettingsUi(val) {
|
||||
try {
|
||||
this.parsedSettings = JSON.stringify(val, null, 2);
|
||||
this.lastSettings = val;
|
||||
this.currentSettings = JSON.parse(JSON.stringify(this.lastSettings));
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
},
|
||||
saveLoggerSettings() {
|
||||
Logger.saveConfig({...this.lastSettings});
|
||||
},
|
||||
restoreLoggerSettings() {
|
||||
this.getLoggerSettings();
|
||||
this.confHasError = false;
|
||||
},
|
||||
async startLogging(){
|
||||
this.logStringified = undefined;
|
||||
await LogAggregator.saveConfig({...this.lastSettings});
|
||||
await Logger.saveConfig({...this.lastSettings, allowLogging: true});
|
||||
window.location.reload();
|
||||
},
|
||||
}
|
||||
|
79
src/csui/src/PlayerUiPanels/InPlayerUiAdvertisement.vue
Normal file
79
src/csui/src/PlayerUiPanels/InPlayerUiAdvertisement.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<div>
|
||||
<h1>In-player UI</h1>
|
||||
|
||||
<div
|
||||
class="button b3"
|
||||
style="margin: 16px; padding: 4px;"
|
||||
@click="showInPlayerUi()"
|
||||
>
|
||||
Show settings window
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<b>Is your screen entirely white or entirely black?</b>
|
||||
|
||||
<p>This appears to be a rare issue that happens to some people. If you're experiencing this issue, please consider contacting me and sharing the following data:</p>
|
||||
|
||||
<ul>
|
||||
<li>Which sites this problem appears on and whether it happens on youtube. If you use youtube premium, please try signing out of youtube (or use a new profile in Google Chrome) in order to see whether youtube premium is required.</li>
|
||||
<li>your browser. if using browsers other than Chrome, please try to reproduce this issue in Chrome</li>
|
||||
<li>your operating system</li>
|
||||
<li>your graphics card</li>
|
||||
<li>the following line:<br/>
|
||||
<pre>prefers-color-scheme dark: {{pageData.pcsDark}}; prefers-color-scheme light: {{pageData.pcsLight}}; color-scheme: {{pageData.colorScheme}}</pre>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p>Please post this info to <a href="https://github.com/tamius-han/ultrawidify/issues/262" target="_blank">this thread</a>, or message me via e-mail.</p>
|
||||
|
||||
<p>Then, disable the in-player UI.</p>
|
||||
</div>
|
||||
|
||||
<!-- <p></p>
|
||||
<p></p>
|
||||
<p>In-player UI should show and hide automatically as you start or stop moving your mouse inside the player window.</p>
|
||||
<p>Note that by default, in-player UI may not show if player window is not big enough.</p> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UIProbeMixin from '../utils/UIProbeMixin';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
UIProbeMixin
|
||||
],
|
||||
props: [
|
||||
'eventBus',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
pageData: {
|
||||
pcsDark: 'x',
|
||||
pcsLight: 'x',
|
||||
colorScheme: 'x'
|
||||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.eventBus.subscribeMulti(
|
||||
{
|
||||
'uw-page-stats': {
|
||||
function: (data) => {
|
||||
console.log('got page statss:', data);
|
||||
this.pageData = data;
|
||||
}
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
this.eventBus.send('uw-get-page-stats', {}, {comms: {forwardTo: 'active'}});
|
||||
},
|
||||
methods: {
|
||||
showInPlayerUi() {
|
||||
this.eventBus.send('uw-set-ui-state', {globalUiVisible: true}, {comms: {forwardTo: 'active'}});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,107 +0,0 @@
|
||||
<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
|
||||
: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',
|
||||
'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(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>
|
@ -1,51 +1,16 @@
|
||||
<template>
|
||||
<div class="">
|
||||
<template v-if="!selectedSite">
|
||||
|
||||
<div class="flex flex-col container pointer hoverable" style="margin-top: 1rem; padding: 0.5rem 1rem;" @click="selectedSite = '@global'" >
|
||||
<div class="flex flex-row">
|
||||
<div class="flex-grow pointer">
|
||||
<b style="color: #fa6">Default settings</b>
|
||||
<!-- <span :style="getSiteTypeColor('@global')"> -->
|
||||
<!-- (config: {{site.type ?? 'unknown'}}) -->
|
||||
<!-- </span> -->
|
||||
</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> <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="info" style="float: none">
|
||||
<b>NOTE:</b> Sites not on this list use default extension settings.
|
||||
</div>
|
||||
<b>NOTE:</b> Sites not on this list use default extension settings.
|
||||
</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 v-for="site of sites" :key="site.key" @click="selectedSite = site.key" class="flex flex-col container pointer hoverable" style="margin-top: 4px; padding: 0.5rem 1rem;">
|
||||
<div v-for="site of sites" :key="site.key" @click="selectedSite = site.key" class="flex flex-col container pointer" style="margin-top: 4px; padding: 0.5rem 1rem;">
|
||||
<div class="flex flex-row">
|
||||
<div class="flex-grow pointer">
|
||||
<b>{{ site.key }}</b>
|
||||
<span :style="getSiteTypeColor(site.type)">
|
||||
(config: {{site.type ?? 'unknown'}})
|
||||
</span>
|
||||
</div>
|
||||
</span></div>
|
||||
<div>Edit</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
@ -53,7 +18,6 @@
|
||||
Enabled: <span :style="getSiteEnabledColor(site.key, 'enable')"><small>{{ getSiteEnabledModes(site.key, 'enable') }}</small></span>;
|
||||
Aard <span :style="getSiteEnabledColor(site.key, 'enableAard')"><small>{{ getSiteEnabledModes(site.key, 'enableAard') }}</small></span>;
|
||||
kbd: <span :style="getSiteEnabledColor(site.key, 'enableKeyboard')"><small>{{ getSiteEnabledModes(site.key, 'enableKeyboard') }}</small></span>
|
||||
UI: <span :style="getSiteEnabledColor(site.key, 'enableUI')"><small>{{ getSiteEnabledModes(site.key, 'enableUI') }}</small></span>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
@ -64,7 +28,7 @@
|
||||
←
|
||||
</div>
|
||||
<div>
|
||||
Editing {{ selectedSite === '@global' ? 'default settings' : selectedSite }}
|
||||
Editing {{ selectedSite }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@ -72,7 +36,7 @@
|
||||
v-if="selectedSiteSettings"
|
||||
:settings="settings"
|
||||
:siteSettings="selectedSiteSettings"
|
||||
:isDefaultConfiguration="selectedSite === '@global'"
|
||||
:isDefaultConfiguration="false"
|
||||
></SiteExtensionSettings>
|
||||
</div>
|
||||
</template>
|
||||
@ -87,8 +51,6 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
selectedSite: null,
|
||||
siteFilter: '',
|
||||
filteredSites: []
|
||||
}
|
||||
},
|
||||
props: [
|
||||
@ -104,7 +66,7 @@ export default {
|
||||
} else {
|
||||
const sites = [];
|
||||
for (const siteKey in this.settings.active.sites) {
|
||||
if (!siteKey.startsWith('@') && (!this.siteFilter.trim() || siteKey.includes(this.siteFilter))) {
|
||||
if (!siteKey.startsWith('@')) {
|
||||
sites.push({
|
||||
key: siteKey,
|
||||
...this.settings.active.sites[siteKey]
|
||||
@ -158,15 +120,3 @@ export default {
|
||||
<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>
|
||||
|
@ -3,12 +3,11 @@
|
||||
<!-- 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>
|
||||
Enable extension under the following conditions:
|
||||
</div>
|
||||
<div class="select">
|
||||
<select
|
||||
:value="simpleExtensionSettings.enable"
|
||||
v-model="simpleExtensionSettings.enable"
|
||||
@click="setExtensionMode('enable', $event)"
|
||||
>
|
||||
<option
|
||||
@ -19,12 +18,12 @@
|
||||
</option>
|
||||
<template v-if="isDefaultConfiguration">
|
||||
<option value="disabled">
|
||||
Disabled by default
|
||||
Disabled (unless enabled for specific site)
|
||||
</option>
|
||||
</template>
|
||||
<template v-else>
|
||||
<option value="default">
|
||||
Use default ({{simpleDefaultSettings.enable}})
|
||||
Use default ()
|
||||
</option>
|
||||
<option value="disabled">
|
||||
Never
|
||||
@ -43,239 +42,199 @@
|
||||
</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>
|
||||
<!-- Enable AARD -->
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Enable automatic aspect ratio detection under the following conditions:
|
||||
</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)"
|
||||
<div class="select">
|
||||
<select
|
||||
v-model="simpleExtensionSettings.enableAard"
|
||||
@click="setExtensionMode('enableAard', $event)"
|
||||
>
|
||||
<option
|
||||
v-if="simpleExtensionSettings.enable === 'complex'"
|
||||
value="complex"
|
||||
>
|
||||
<option
|
||||
v-if="simpleExtensionSettings.enable === 'complex'"
|
||||
value="complex"
|
||||
>
|
||||
(Site uses advanced settings)
|
||||
(Site uses advanced settings)
|
||||
</option>
|
||||
<template v-if="isDefaultConfiguration">
|
||||
<option value="disabled">
|
||||
Disabled (unless enabled for specific site)
|
||||
</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
|
||||
</template>
|
||||
<template v-else>
|
||||
<option value="default">
|
||||
Use default ()
|
||||
</option>
|
||||
<option value="theater">
|
||||
Fullscreen and theater mode
|
||||
<option value="disabled">
|
||||
Never
|
||||
</option>
|
||||
<option value="enabled">
|
||||
Always
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</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>
|
||||
<!-- Enable keyboard -->
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Enable keyboard shortcuts under the following conditions
|
||||
</div>
|
||||
|
||||
<!-- Default crop -->
|
||||
<div class="field">
|
||||
<div class="label">Default crop:</div>
|
||||
<div class="select">
|
||||
<select
|
||||
:value="siteDefaultCrop"
|
||||
@change="setOption('defaults.crop', $event)"
|
||||
<div class="select">
|
||||
<select
|
||||
v-model="simpleExtensionSettings.enableKeyboard"
|
||||
@click="setExtensionMode('enableKeyboard', $event)"
|
||||
>
|
||||
<option
|
||||
v-if="simpleExtensionSettings.enable === 'complex'"
|
||||
value="complex"
|
||||
>
|
||||
<option
|
||||
v-if="!isDefaultConfiguration"
|
||||
:value="JSON.stringify({useDefault: true})"
|
||||
>
|
||||
Use default ({{getCommandValue(settings?.active.commands.crop, siteSettings.data.defaults.crop)}})
|
||||
(Site uses advanced settings)
|
||||
</option>
|
||||
<template v-if="isDefaultConfiguration">
|
||||
<option value="disabled">
|
||||
Disabled (unless enabled for specific site)
|
||||
</option>
|
||||
<option
|
||||
v-for="(command, index) of settings?.active.commands.crop"
|
||||
:key="index"
|
||||
:value="JSON.stringify(command.arguments)"
|
||||
>
|
||||
{{command.label}}
|
||||
</template>
|
||||
<template v-else>
|
||||
<option value="default">
|
||||
Use default ()
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<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>
|
||||
|
||||
<!-- Default crop -->
|
||||
<div class="field">
|
||||
<div class="label">Default crop:</div>
|
||||
<div class="select">
|
||||
<select
|
||||
v-model="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 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>
|
||||
</div>
|
||||
|
||||
<!-- Default stretch -->
|
||||
<div class="field">
|
||||
<div class="label">Default stretch:</div>
|
||||
<div class="select">
|
||||
<select
|
||||
v-model="siteDefaultStretch"
|
||||
@change="setOption('defaults.stretch', $event)"
|
||||
<!-- 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})"
|
||||
>
|
||||
<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>
|
||||
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)"
|
||||
<!-- 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})"
|
||||
>
|
||||
<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>
|
||||
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)"
|
||||
<!-- 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"
|
||||
>
|
||||
<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>
|
||||
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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
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 ExtensionMode from '../../../../../common/enums/ExtensionMode.enum';
|
||||
import VideoAlignmentType from '../../../../../common/enums/VideoAlignmentType.enum';
|
||||
import CropModePersistence from './../../../../../common/enums/CropModePersistence.enum';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
CropModePersistence: CropModePersistence,
|
||||
ExtensionMode,
|
||||
alignmentOptions: [
|
||||
{label: 'Top left', arguments: {x: VideoAlignmentType.Left, y: VideoAlignmentType.Top}},
|
||||
{label: 'Top center', arguments: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Top}},
|
||||
@ -306,25 +265,8 @@ export default {
|
||||
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')
|
||||
};
|
||||
},
|
||||
siteDefaultCrop() {
|
||||
return this.siteSettings.raw?.defaults?.crop ? JSON.stringify(this.siteSettings.raw?.defaults?.crop) : JSON.stringify({useDefault: true});
|
||||
},
|
||||
@ -352,71 +294,44 @@ export default {
|
||||
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;
|
||||
}
|
||||
|
||||
// console.log('getting data from:', settingsData);
|
||||
|
||||
compileSimpleSettings(component) {
|
||||
try {
|
||||
if (
|
||||
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI')
|
||||
&& settingsData?.[component]?.theater === ExtensionMode.Disabled
|
||||
&& settingsData?.[component]?.fullscreen === ExtensionMode.Disabled
|
||||
this.siteSettings?.data?.[component]?.normal === ExtensionMode.Disabled
|
||||
&& this.siteSettings?.data?.[component]?.theater === ExtensionMode.Disabled
|
||||
&& this.siteSettings?.data?.[component]?.fullscreen === ExtensionMode.Disabled
|
||||
) {
|
||||
return 'disabled';
|
||||
}
|
||||
if (
|
||||
( settingsData?.[component]?.normal === ExtensionMode.Default || component === 'enableUI')
|
||||
&& settingsData?.[component]?.theater === ExtensionMode.Default
|
||||
&& settingsData?.[component]?.fullscreen === ExtensionMode.Default
|
||||
this.siteSettings?.data?.[component]?.normal === ExtensionMode.Default
|
||||
&& this.siteSettings?.data?.[component]?.theater === ExtensionMode.Default
|
||||
&& this.siteSettings?.data?.[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
|
||||
this.siteSettings?.data?.[component]?.normal === ExtensionMode.Disabled
|
||||
&& this.siteSettings?.data?.[component]?.theater === ExtensionMode.Disabled
|
||||
&& this.siteSettings?.data?.[component]?.fullscreen === ExtensionMode.Enabled
|
||||
) {
|
||||
return 'fs';
|
||||
}
|
||||
if (
|
||||
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI')
|
||||
&& settingsData?.[component]?.theater === ExtensionMode.Enabled
|
||||
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled
|
||||
this.siteSettings?.data?.[component]?.normal === ExtensionMode.Disabled
|
||||
&& this.siteSettings?.data?.[component]?.theater === ExtensionMode.Enabled
|
||||
&& this.siteSettings?.data?.[component]?.fullscreen === ExtensionMode.Enabled
|
||||
) {
|
||||
return 'theater';
|
||||
}
|
||||
if (
|
||||
( settingsData?.[component]?.normal === ExtensionMode.Enabled || component === 'enableUI')
|
||||
&& settingsData?.[component]?.theater === ExtensionMode.Enabled
|
||||
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled
|
||||
this.siteSettings?.data?.[component]?.normal === ExtensionMode.Enabled
|
||||
&& this.siteSettings?.data?.[component]?.theater === ExtensionMode.Enabled
|
||||
&& this.siteSettings?.data?.[component]?.fullscreen === ExtensionMode.Enabled
|
||||
) {
|
||||
return 'enabled';
|
||||
}
|
||||
@ -427,37 +342,6 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
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)) {
|
||||
@ -519,43 +403,21 @@ export default {
|
||||
|
||||
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();
|
||||
// changing alignment options doesn't trigger re-compute, so we need to do it ourselves.
|
||||
// note that this re-computes siteDefaultAlignment even when setting other options, but
|
||||
// it's _too late AM_ and hit to performance probably isn't bad enough to warrant
|
||||
// spending time on a more correct solution tomorrow
|
||||
this._computedWatchers.siteDefaultAlignment.run();
|
||||
|
||||
this.$nextTick( () => this.$forceUpdate());
|
||||
},
|
||||
|
||||
setExtensionMode(component, event) {
|
||||
const option = event.target.value;
|
||||
|
||||
console.log('SET EXTENSION MODE — OPTIONS:', option);
|
||||
|
||||
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,
|
||||
|
@ -1,71 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="flex flex-row">
|
||||
<div class="flex-grow pointer">
|
||||
<b>{{ host }}</b>
|
||||
<span :style="getSiteTypeColor(siteSettings?.data?.type)">
|
||||
(config: {{siteSettings?.data?.type ?? 'unknown'}})
|
||||
</span>
|
||||
</div>
|
||||
<div>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: {{this.siteSettings.usesSettingsFor}}</div>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<small>
|
||||
Enabled: <span :style="getSiteEnabledColor(host, 'enable')"><small>{{ getSiteEnabledModes(host, 'enable') }}</small></span>;
|
||||
Aard <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 '../../../../../common/enums/ExtensionMode.enum';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
siteSettings: undefined,
|
||||
supportType: undefined
|
||||
}
|
||||
},
|
||||
props: [
|
||||
'settings',
|
||||
'host',
|
||||
],
|
||||
created() {
|
||||
this.siteSettings = this.settings.getSiteSettings(this.host);
|
||||
},
|
||||
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 'disabled';
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
@ -3,12 +3,8 @@
|
||||
|
||||
<ShortcutButton
|
||||
v-for="(command, index) of settings?.active.commands.crop"
|
||||
class="flex button"
|
||||
:class="{
|
||||
active: editMode ? index === editModeOptions?.crop?.selectedIndex : isActiveCrop(command),
|
||||
'b3-compact': compact,
|
||||
b3: !compact
|
||||
}"
|
||||
class="flex b3 button"
|
||||
:class="{active: editMode ? index === editModeOptions?.crop?.selectedIndex : isActiveCrop(command)}"
|
||||
:key="index"
|
||||
:label="command.label"
|
||||
:shortcut="getKeyboardShortcutLabel(command)"
|
||||
@ -20,7 +16,7 @@
|
||||
<ShortcutButton
|
||||
v-if="editMode"
|
||||
class="button b3"
|
||||
:class="{active: editMode ? editModeOptions?.crop?.selectedIndex === null : isActiveCrop(command)}"
|
||||
:class="{active: editMode ? editModeOptions?.crop?.selectedIndex === null : isActiveCrop(command)}"
|
||||
label="Add new"
|
||||
@click="editAction(
|
||||
{action: 'set-ar', label: 'New aspect ratio', arguments: {type: AspectRatioType.Fixed}},
|
||||
@ -58,13 +54,12 @@
|
||||
@blur="editModeOptions.crop.selected.label === 'New aspect ratio' ? editModeOptions.crop.selected.label = editModeOptions.crop.selected.arguments.ratio : null"
|
||||
>
|
||||
</div>
|
||||
<div class="hint">
|
||||
You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor
|
||||
(in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter
|
||||
your numbers without quote marks. Number will be converted to factor form on save.
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor
|
||||
(in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter
|
||||
your numbers without quote marks. Number will be converted to factor form on save.
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Label:
|
||||
@ -72,9 +67,9 @@
|
||||
<div class="input">
|
||||
<input v-model="editModeOptions.crop.selected.label">
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
Label for the button. You can make it say something other than ratio.
|
||||
<div class="hint">
|
||||
Label for the button. You can make it say something other than ratio.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -88,11 +83,11 @@
|
||||
>
|
||||
</EditShortcutButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
<b>Note:</b> Your browser and OS already use certain key combinations that involve Ctrl and Meta (Windows) keys — and, to a lesser extent, Alt.
|
||||
The extension doesn't (and cannot) check whether the keyboard shortcut you enter is actually free for you to use. The extension also won't override
|
||||
any keyboard shortcuts defined by the site itself.
|
||||
<div class="hint">
|
||||
<b>Note:</b> Your browser and OS already use certain key combinations that involve Ctrl and Meta (Windows) keys — and, to a lesser extent, Alt.
|
||||
The extension doesn't (and cannot) check whether the keyboard shortcut you enter is actually free for you to use. The extension also won't override
|
||||
any keyboard shortcuts defined by the site itself.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row flex-end">
|
||||
@ -116,7 +111,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="siteSettings && allowSettingSiteDefault" class="edit-action-area">
|
||||
<div v-if="siteSettings" class="edit-action-area">
|
||||
<div class="field">
|
||||
<div class="label">Default for this site</div>
|
||||
<div class="select">
|
||||
@ -138,25 +133,16 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ShortcutButton from '@csui/src/components/ShortcutButton.vue';
|
||||
import EditShortcutButton from '@csui/src/components/EditShortcutButton';
|
||||
import EditModeMixin from '@csui/src/utils/EditModeMixin';
|
||||
import KeyboardShortcutParserMixin from '@csui/src/utils/KeyboardShortcutParserMixin';
|
||||
import CommsMixin from '@csui/src/utils/CommsMixin';
|
||||
import AspectRatioType from '@src/common/enums/AspectRatioType.enum';
|
||||
import ShortcutButton from '../../../components/ShortcutButton.vue';
|
||||
import EditShortcutButton from '../../../components/EditShortcutButton';
|
||||
import EditModeMixin from '../../../utils/EditModeMixin';
|
||||
import KeyboardShortcutParserMixin from '../../../utils/KeyboardShortcutParserMixin';
|
||||
import CommsMixin from '../../../utils/CommsMixin';
|
||||
import AspectRatioType from '../../../../../common/enums/AspectRatioType.enum';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ShortcutButton,
|
||||
EditShortcutButton,
|
||||
},
|
||||
mixins: [
|
||||
// ComputeActionsMixin,
|
||||
EditModeMixin,
|
||||
KeyboardShortcutParserMixin,
|
||||
CommsMixin
|
||||
],
|
||||
data() {
|
||||
|
||||
return {
|
||||
AspectRatioType: AspectRatioType,
|
||||
|
||||
@ -169,14 +155,22 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
mixins: [
|
||||
// ComputeActionsMixin,
|
||||
EditModeMixin,
|
||||
KeyboardShortcutParserMixin,
|
||||
CommsMixin
|
||||
],
|
||||
props: [
|
||||
'settings', // required for buttons and actions, which are global
|
||||
'siteSettings',
|
||||
'eventBus',
|
||||
'isEditing',
|
||||
'allowSettingSiteDefault',
|
||||
'compact',
|
||||
'isEditing'
|
||||
],
|
||||
components: {
|
||||
ShortcutButton,
|
||||
EditShortcutButton,
|
||||
},
|
||||
computed: {
|
||||
siteDefaultCrop() {
|
||||
if (!this.siteSettings) {
|
||||
|
@ -3,10 +3,7 @@
|
||||
<ShortcutButton
|
||||
v-for="(command, index) of settings?.active.commands.stretch"
|
||||
class="b3 button"
|
||||
:class="{active: editMode ? index === editModeOptions?.stretch?.selectedIndex : isActiveStretch(command),
|
||||
'b3-compact': compact,
|
||||
b3: !compact
|
||||
}"
|
||||
:class="{active: editMode ? index === editModeOptions?.stretch?.selectedIndex : isActiveStretch(command)}"
|
||||
:key="index"
|
||||
:label="command.label"
|
||||
:shortcut="getKeyboardShortcutLabel(command)"
|
||||
@ -53,10 +50,10 @@
|
||||
v-model="editModeOptions.stretch.selected.arguments.limit"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
If vertical borders would take up less than this much of screen width, the image will be stretched. If the borders are too thick, image will not be stretched.
|
||||
Value of 1 means 100%. Value of 0.1 means vertical black bars can take up 10% of the width at most. There's no validation on this, use common sense.
|
||||
<div class="hint">
|
||||
If vertical borders would take up less than this much of screen width, the image will be stretched. If the borders are too thick, image will not be stretched.
|
||||
Value of 1 means 100%. Value of 0.1 means vertical black bars can take up 10% of the width at most. There's no validation on this, use common sense.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -73,12 +70,12 @@
|
||||
@blur="editModeOptions.stretch.selected.label === 'Stretch to ...' ? editModeOptions.stretch.selected.label = `Stretch to ${editModeOptions.stretch.selected.arguments.ratio}` : null"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
<div class="hint">
|
||||
You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor
|
||||
(in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter
|
||||
your numbers without quote marks. Number will be converted to factor form on save.
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Label:
|
||||
@ -86,9 +83,9 @@
|
||||
<div class="input">
|
||||
<input v-model="editModeOptions.stretch.selected.label">
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
Label for the button. You can make it say something other than ratio.
|
||||
<div class="hint">
|
||||
Label for the button. You can make it say something other than ratio.
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -102,11 +99,11 @@
|
||||
>
|
||||
</EditShortcutButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
<b>Note:</b> Your browser and OS already use certain key combinations that involve Ctrl and Meta (Windows) keys — and, to a lesser extent, Alt.
|
||||
The extension doesn't (and cannot) check whether the keyboard shortcut you enter is actually free for you to use. The extension also won't override
|
||||
any keyboard shortcuts defined by the site itself.
|
||||
<div class="hint">
|
||||
<b>Note:</b> Your browser and OS already use certain key combinations that involve Ctrl and Meta (Windows) keys — and, to a lesser extent, Alt.
|
||||
The extension doesn't (and cannot) check whether the keyboard shortcut you enter is actually free for you to use. The extension also won't override
|
||||
any keyboard shortcuts defined by the site itself.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row flex-end">
|
||||
@ -130,7 +127,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="siteSettings && allowSettingSiteDefault" class="edit-action-area">
|
||||
<div v-if="siteSettings" class="edit-action-area">
|
||||
<div class="field">
|
||||
<div class="label">Default for this site:</div>
|
||||
<div class="select">
|
||||
@ -186,9 +183,7 @@ export default {
|
||||
'settings', // required for buttons and actions, which are global
|
||||
'siteSettings',
|
||||
'eventBus',
|
||||
'isEditing',
|
||||
'allowSettingSiteDefault',
|
||||
'compact',
|
||||
'isEditing'
|
||||
],
|
||||
components: {
|
||||
ShortcutButton,
|
||||
@ -249,6 +244,6 @@ export default {
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" src="@csui/res/css/flex.scss" scoped></style>
|
||||
<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>
|
||||
|
@ -1,224 +1,86 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
|
||||
<div class="sub-panel-content flex flex-row flex-wrap">
|
||||
|
||||
<ShortcutButton
|
||||
v-for="(command, index) of settings?.active.commands.zoom"
|
||||
class="flex button"
|
||||
:class="{active: editMode ? index === editModeOptions?.zoom?.selectedIndex : isActiveZoom(command),
|
||||
'b3-compact': compact,
|
||||
b3: !compact
|
||||
}"
|
||||
:key="index"
|
||||
:label="command.label"
|
||||
:shortcut="getKeyboardShortcutLabel(command)"
|
||||
@click="editMode ? editAction(command, index, 'zoom') : execAction(command)"
|
||||
<!--
|
||||
min, max and value need to be implemented in js as this slider
|
||||
should use logarithmic scale
|
||||
-->
|
||||
<div class="flex flex-row flex-end">
|
||||
<Button
|
||||
v-if="zoomAspectRatioLocked"
|
||||
label="Unlock aspect ratio"
|
||||
icon="lock-open"
|
||||
:fixedWidth="true"
|
||||
@click="toggleZoomAr()"
|
||||
>
|
||||
</ShortcutButton>
|
||||
|
||||
<!-- "Add new" button -->
|
||||
<ShortcutButton
|
||||
v-if="editMode"
|
||||
class="button b3"
|
||||
:class="{active: editMode ? editModeOptions?.crop?.selectedIndex === null : isActiveCrop(command)}"
|
||||
label="Add new"
|
||||
@click="editAction(
|
||||
{action: 'set-ar-zoom', label: 'New aspect ratio', arguments: {type: AspectRatioType.Fixed}},
|
||||
null,
|
||||
'zoom'
|
||||
)"
|
||||
></ShortcutButton>
|
||||
</Button>
|
||||
<Button
|
||||
v-else
|
||||
label="Lock aspect ratio"
|
||||
icon="lock"
|
||||
:fixedWidth="true"
|
||||
@click="toggleZoomAr()"
|
||||
>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<template v-if="isEditing">
|
||||
<div v-if="editMode && editModeOptions?.zoom?.selected" class="sub-panel-content">
|
||||
<div class="edit-action-area-header">
|
||||
<span class="text-primary">Editing options for:</span> <b>{{editModeOptions?.zoom?.selected?.label}}</b>
|
||||
<template v-if="editModeOptions?.zoom?.selectedIndex === null && editModeOptions?.zoom?.selected?.label !== 'New aspect ratio'">(New ratio)</template>
|
||||
<template v-if="zoomAspectRatioLocked">
|
||||
<input id="_input_zoom_slider"
|
||||
class="input-slider"
|
||||
type="range"
|
||||
step="any"
|
||||
min="-1"
|
||||
max="3"
|
||||
:value="zoom.x"
|
||||
@input="changeZoom($event.target.value)"
|
||||
/>
|
||||
<div style="overflow: auto" class="flex flex-row">
|
||||
<div class="flex flex-grow medium-small x-pad-1em">
|
||||
Zoom: {{getZoomForDisplay('x')}}
|
||||
</div>
|
||||
<div class="edit-action-area">
|
||||
<!-- Some options are only shown for type 4 (fixed) zooms -->
|
||||
<template v-if="editModeOptions?.zoom?.selected?.arguments?.type === AspectRatioType.Fixed">
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Ratio:
|
||||
</div>
|
||||
<div class="input">
|
||||
<!-- We do an ugly in order to avoid spamming functions down at the bottom -->
|
||||
<input
|
||||
v-model="editModeOptions.zoom.selected.arguments.ratio"
|
||||
@blur="editModeOptions.zoom.selected.label === 'New aspect ratio' ? editModeOptions.zoom.selected.label = editModeOptions.zoom.selected.arguments.ratio : null"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
You can enter a ratio in width:height format (e.g. "21:9" or "1:2.39"), or just the factor
|
||||
(in this case, "1:2.39" would become "2.39" and "21:9" would become "2.33"). You should enter
|
||||
your numbers without quote marks. Number will be converted to factor form on save.
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label">
|
||||
Label:
|
||||
</div>
|
||||
<div class="input">
|
||||
<input v-model="editModeOptions.zoom.selected.label">
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
Label for the button. You can make it say something other than ratio.
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- editing keyboard shortcuts is always allowed -->
|
||||
<div class="field">
|
||||
<div class="label">Shortcut:</div>
|
||||
<div class="">
|
||||
<EditShortcutButton
|
||||
:shortcut="editModeOptions?.zoom?.selected?.shortcut"
|
||||
@shortcutChanged="updateSelectedShortcut($event, 'zoom')"
|
||||
>
|
||||
</EditShortcutButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
<b>Note:</b> Your browser and OS already use certain key combinations that involve Ctrl and Meta (Windows) keys — and, to a lesser extent, Alt.
|
||||
The extension doesn't (and cannot) check whether the keyboard shortcut you enter is actually free for you to use. The extension also won't override
|
||||
any keyboard shortcuts defined by the site itself.
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row flex-end">
|
||||
<div
|
||||
v-if="editModeOptions?.zoom?.selected?.arguments?.type === AspectRatioType.Fixed && editModeOptions?.zoom?.selectedIndex !== null"
|
||||
class="button"
|
||||
@click="deleteAction('zoom')"
|
||||
>
|
||||
<mdicon name="delete"></mdicon> Delete
|
||||
</div>
|
||||
<div class="flex-grow"></div>
|
||||
<div class="button" @click="cancelEdit('zoom')">Cancel</div>
|
||||
<div class="button" @click="saveShortcut('zoom')">
|
||||
<mdicon name="floppy"></mdicon>
|
||||
|
||||
<template v-if="editModeOptions?.zoom?.selectedIndex === null">Add</template>
|
||||
<template v-else>Save</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-nogrow flex-noshrink medium-small">
|
||||
<a class="_zoom_reset x-pad-1em" @click="resetZoom()">reset</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<!--
|
||||
min, max and value need to be implemented in js as this slider
|
||||
should use logarithmic scale
|
||||
-->
|
||||
<div class="flex flex-row w-full" style="margin-top: 0.66rem">
|
||||
<div style="position:relative;" class="grow">
|
||||
<template v-if="zoomAspectRatioLocked">
|
||||
<div class="slider-label">
|
||||
Zoom: {{getZoomForDisplay('x')}}
|
||||
</div>
|
||||
<input id="_input_zoom_slider"
|
||||
class="input-slider"
|
||||
type="range"
|
||||
step="any"
|
||||
min="-1"
|
||||
max="3"
|
||||
:value="zoom.x"
|
||||
@input="changeZoom($event.target.value)"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="slider-label">Horizontal zoom: {{getZoomForDisplay('x')}}</div>
|
||||
<input id="_input_zoom_slider"
|
||||
class="input-slider"
|
||||
type="range"
|
||||
step="any"
|
||||
min="-1"
|
||||
max="4"
|
||||
:value="zoom.x"
|
||||
@input="changeZoom($event.target.value, 'x')"
|
||||
/>
|
||||
<div>Horizontal zoom</div>
|
||||
<input id="_input_zoom_slider"
|
||||
class="input-slider"
|
||||
type="range"
|
||||
step="any"
|
||||
min="-1"
|
||||
max="4"
|
||||
:value="zoom.x"
|
||||
@input="changeZoom($event.target.value, 'x')"
|
||||
/>
|
||||
|
||||
<div class="slider-label">Vertical zoom: {{getZoomForDisplay('y')}}</div>
|
||||
<input id="_input_zoom_slider_2"
|
||||
class="input-slider"
|
||||
type="range"
|
||||
step="any"
|
||||
min="-1"
|
||||
max="3"
|
||||
:value="zoom.y"
|
||||
@input="changeZoom($event.target.value, 'y')"
|
||||
/>
|
||||
</template>
|
||||
<div>Vertical zoom</div>
|
||||
<input id="_input_zoom_slider"
|
||||
class="input-slider"
|
||||
type="range"
|
||||
step="any"
|
||||
min="-1"
|
||||
max="3"
|
||||
:value="zoom.y"
|
||||
@input="changeZoom($event.target.value, 'y')"
|
||||
/>
|
||||
|
||||
<div style="overflow: auto" class="flex flex-row">
|
||||
<div class="flex flex-grow medium-small x-pad-1em">
|
||||
Zoom: {{getZoomForDisplay('x')}} x {{getZoomForDisplay('y')}}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row items-center justify-center" style="padding-left: 1rem">
|
||||
<Button
|
||||
v-if="zoomAspectRatioLocked"
|
||||
icon="lock"
|
||||
:iconSize="16"
|
||||
:fixedWidth="true"
|
||||
:noPad="true"
|
||||
@click="toggleZoomAr()"
|
||||
>
|
||||
</Button>
|
||||
<Button
|
||||
v-else
|
||||
icon="lock-open-variant"
|
||||
:iconSize="16"
|
||||
:fixedWidth="true"
|
||||
:noPad="true"
|
||||
@click="toggleZoomAr()"
|
||||
>
|
||||
</Button>
|
||||
<Button
|
||||
icon="restore"
|
||||
:iconSize="16"
|
||||
:noPad="true"
|
||||
@click="resetZoom()"
|
||||
></Button>
|
||||
<div class="flex flex-nogrow flex-noshrink medium-small">
|
||||
<a class="_zoom_reset x-pad-1em" @click="resetZoom()">reset</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Button from '@csui/src/components/Button.vue';
|
||||
import ShortcutButton from '@csui/src/components/ShortcutButton.vue';
|
||||
import EditShortcutButton from '@csui/src/components/EditShortcutButton';
|
||||
import EditModeMixin from '@csui/src/utils/EditModeMixin';
|
||||
import KeyboardShortcutParserMixin from '@csui/src/utils/KeyboardShortcutParserMixin';
|
||||
import CommsMixin from '@csui/src/utils/CommsMixin';
|
||||
import AspectRatioType from '@src/common/enums/AspectRatioType.enum';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Button,
|
||||
ShortcutButton,
|
||||
EditShortcutButton,
|
||||
},
|
||||
mixins: [
|
||||
// ComputeActionsMixin,
|
||||
EditModeMixin,
|
||||
KeyboardShortcutParserMixin,
|
||||
CommsMixin
|
||||
],
|
||||
props: [
|
||||
'settings', // required for buttons and actions, which are global
|
||||
'siteSettings',
|
||||
'eventBus',
|
||||
'isEditing',
|
||||
'compact',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
AspectRatioType,
|
||||
|
||||
zoomAspectRatioLocked: true,
|
||||
zoom: {
|
||||
x: 0,
|
||||
@ -234,24 +96,20 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (this.isEditing) {
|
||||
this.enableEditMode();
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
isEditing(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
this.enableEditMode();
|
||||
} else {
|
||||
this.disableEditMode();
|
||||
}
|
||||
}
|
||||
},
|
||||
mixins: [
|
||||
|
||||
],
|
||||
props: [
|
||||
'settings', // required for buttons and actions, which are global
|
||||
'siteSettings',
|
||||
'eventBus',
|
||||
'isEditing'
|
||||
],
|
||||
methods: {
|
||||
getZoomForDisplay(axis) {
|
||||
// zoom is internally handled logarithmically, because we want to have x0.5, x1, x2, x4 ... magnifications
|
||||
// spaced out at regular intervals. When displaying, we need to convert that to non-logarithmic values.
|
||||
|
||||
return `${(Math.pow(2, this.zoom[axis]) * 100).toFixed()}%`
|
||||
},
|
||||
toggleZoomAr() {
|
||||
@ -267,7 +125,8 @@ export default {
|
||||
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'y'});
|
||||
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'x'});
|
||||
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: 1});
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: 1, axis: 'y'});
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: 1, axis: 'x'});
|
||||
},
|
||||
changeZoom(newZoom, axis) {
|
||||
// we store zoom logarithmically on this compnent
|
||||
@ -281,14 +140,12 @@ export default {
|
||||
newZoom = Math.pow(2, newZoom);
|
||||
|
||||
if (this.zoomAspectRatioLocked) {
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom});
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom, axis: 'y'});
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom, axis: 'x'});
|
||||
} else {
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: {[axis ?? 'x']: newZoom}});
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom, axis: axis ?? 'x'});
|
||||
}
|
||||
},
|
||||
isActiveZoom(command) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -297,17 +154,3 @@ export default {
|
||||
<style lang="scss" src="../../../../res/css/flex.scss" scoped></style>
|
||||
<style lang="scss" src="@csui/src/res-common/panels.scss" scoped></style>
|
||||
<style lang="scss" src="@csui/src/res-common/common.scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
.input-slider {
|
||||
width: 100%;
|
||||
box-sizing:border-box;
|
||||
margin-right: 1rem;
|
||||
margin-left: 0rem;
|
||||
}
|
||||
.slider-label {
|
||||
margin-bottom: -0.5rem;
|
||||
color: #aaa;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
||||
|
@ -5,14 +5,23 @@
|
||||
<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 class="field">
|
||||
<div class="label">Enable in-player UI</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="settings.active.ui.inPlayer.enabled"
|
||||
@change="saveSettings()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex flex-col field-group compact-form"
|
||||
:class="{disabled: !siteSettings.data.enableUI.fullscreen}"
|
||||
:class="{disabled: !settings.active.ui.inPlayer.enabled}"
|
||||
>
|
||||
<div class="field">
|
||||
<div class="label">Enable only in full screen</div>
|
||||
<input type="checkbox" v-model="settings.active.ui.inPlayer.enabledFullscreenOnly" />
|
||||
</div>
|
||||
|
||||
<div class="field disabled">
|
||||
<div class="label">
|
||||
Popup activator position:
|
||||
@ -135,19 +144,6 @@
|
||||
: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>
|
||||
@ -155,18 +151,16 @@
|
||||
</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'
|
||||
import Button from '../components/Button.vue'
|
||||
import BrowserDetect from '../../../ext/conf/BrowserDetect';
|
||||
import CropOptionsPanel from './PanelComponents/VideoSettings/CropOptionsPanel.vue'
|
||||
import StretchOptionsPanel from './PanelComponents/VideoSettings/StretchOptionsPanel.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Button,
|
||||
CropOptionsPanel,
|
||||
StretchOptionsPanel,
|
||||
ZoomOptionsPanel,
|
||||
StretchOptionsPanel
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -177,7 +171,6 @@ export default {
|
||||
],
|
||||
props: [
|
||||
'settings', // required for buttons and actions, which are global
|
||||
'siteSettings',
|
||||
'eventBus',
|
||||
],
|
||||
mounted() {
|
||||
@ -282,7 +275,7 @@ export default {
|
||||
gap: 1rem;
|
||||
|
||||
> * {
|
||||
width: calc(33% - 0.5rem);
|
||||
width: calc(50% - 0.5rem);
|
||||
}
|
||||
}
|
||||
|
||||
|
9
src/csui/src/PlayerUiPanels/SiteSettings.vue
Normal file
9
src/csui/src/PlayerUiPanels/SiteSettings.vue
Normal file
@ -0,0 +1,9 @@
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
|
||||
|
||||
<div class="">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -38,7 +38,7 @@
|
||||
<!-- CROP OPTIONS -->
|
||||
<div v-if="settings" class="sub-panel">
|
||||
<div class="flex flex-row">
|
||||
<mdicon name="crop" :size="16" />
|
||||
<mdicon name="crop" :size="32" />
|
||||
<h1>Crop video:</h1>
|
||||
</div>
|
||||
|
||||
|
@ -1,11 +1,7 @@
|
||||
<template>
|
||||
<div class="button center-text"
|
||||
:class="{
|
||||
'setting-selected': selected,
|
||||
'no-pad': noPad,
|
||||
}"
|
||||
:class="{'setting-selected': selected }"
|
||||
>
|
||||
<mdicon v-if="icon" :name="icon" :size="iconSize ?? 24"></mdicon>
|
||||
<div class="label">
|
||||
{{label}}
|
||||
</div>
|
||||
@ -19,8 +15,6 @@ export default {
|
||||
selected: Boolean,
|
||||
label: String,
|
||||
icon: String,
|
||||
iconSize: Number,
|
||||
noPad: Boolean
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -46,7 +40,4 @@ export default {
|
||||
}
|
||||
|
||||
}
|
||||
.no-pad {
|
||||
padding: 0.5rem 1rem !important;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,108 +0,0 @@
|
||||
<template>
|
||||
<div v-if="popupVisible" class="popup">
|
||||
<div class="popup-window">
|
||||
<div
|
||||
class="header"
|
||||
:class="{
|
||||
'danger': dialogType === 'danger',
|
||||
'warning': dialogType === 'warning',
|
||||
'info': dialogType === 'info',
|
||||
'normal': dialogType === 'normal'
|
||||
}"
|
||||
>
|
||||
{{ dialogText || 'Confirm action' }}
|
||||
</div>
|
||||
<div class="body">
|
||||
{{ dialogText || 'Are you sure you want to do that?' }}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<button
|
||||
class="button confirm"
|
||||
:class="{
|
||||
'danger': dialogType === 'danger',
|
||||
'warning': dialogType === 'warning',
|
||||
'info': dialogType === 'info',
|
||||
'normal': dialogType === 'normal'
|
||||
}"
|
||||
@click="confirmAction"
|
||||
>
|
||||
{{ confirmText || 'Confirm' }}
|
||||
</button>
|
||||
<button class="button" @click="popupVisible = false">{{ cancelText || 'Cancel' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
:class="[
|
||||
{
|
||||
'danger': dialogType === 'danger',
|
||||
'warning': dialogType === 'warning',
|
||||
},
|
||||
btnClass
|
||||
]"
|
||||
@click="popupVisible = true"
|
||||
>
|
||||
<slot></slot>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
popupVisible: false,
|
||||
}
|
||||
},
|
||||
props: [
|
||||
'btnClass',
|
||||
'dialogTitle',
|
||||
'dialogText',
|
||||
'confirmText',
|
||||
'cancelText',
|
||||
'dialogType'
|
||||
],
|
||||
methods: {
|
||||
confirmAction() {
|
||||
this.popupVisible = false;
|
||||
this.$emit('onConfirmed');
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.popup {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
// background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: saturate(50%) brightness(50%) blur(1rem);
|
||||
z-index: 99999;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.header {
|
||||
font-size: 1.33rem;
|
||||
|
||||
color: #fff;
|
||||
|
||||
&.danger {
|
||||
color: rgb(251, 107, 63);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.body {
|
||||
min-height: 5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -76,7 +76,6 @@ export default {
|
||||
left: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
.activator {
|
||||
position: relative;
|
||||
@ -84,12 +83,12 @@ export default {
|
||||
|
||||
font-size: .95rem;
|
||||
padding: 1rem 1.6rem;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(16px) saturate(120%);
|
||||
|
||||
white-space: nowrap;
|
||||
&:hover {
|
||||
background-color: rgba(255, 128, 64, 0.95);
|
||||
background-color: rgba(255, 128, 64, 0.5);
|
||||
}
|
||||
|
||||
&.expand-left {
|
||||
|
@ -24,17 +24,16 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.item {
|
||||
position: relative;
|
||||
font-size: .95rem;
|
||||
font-family: 'Overpass';
|
||||
padding: 1rem 1.6rem;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(16px) saturate(120%);
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
&.can-hover:hover {
|
||||
background-color: rgba(0,0,0,0.98);
|
||||
background-color: rgba(0,0,0,0.8);
|
||||
border-bottom: 1px solid #fa6;
|
||||
}
|
||||
|
||||
|
@ -1,120 +0,0 @@
|
||||
<template>
|
||||
<div class="flex flex-row w-full">
|
||||
<button
|
||||
v-if="editorMode === 'tree'"
|
||||
@click="() => setEditorMode('text')"
|
||||
>
|
||||
Edit as text
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
@click="() => setEditorMode('tree')"
|
||||
>
|
||||
Show as tree
|
||||
</button>
|
||||
</div>
|
||||
<div ref="refContainer" class="w-full h-full"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { createJSONEditor, Mode } from "vanilla-jsoneditor";
|
||||
|
||||
export default {
|
||||
props: {
|
||||
modelValue: Object // v-model binding
|
||||
},
|
||||
emits: ["update:modelValue"],
|
||||
data() {
|
||||
return {
|
||||
refContainer: null,
|
||||
editor: null,
|
||||
editorMode: Mode.tree,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
modelValue: {
|
||||
deep: true,
|
||||
handler(newValue) {
|
||||
if (this.editor) {
|
||||
this.editor.updateProps({ content: { json: newValue || {} } });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.$refs.refContainer) {
|
||||
this.editor = createJSONEditor({
|
||||
target: this.$refs.refContainer,
|
||||
props: {
|
||||
mode: Mode.tree,
|
||||
content: { json: this.modelValue || {} },
|
||||
onChange: (updatedContent) => {
|
||||
this.$emit("update:modelValue", updatedContent.json);
|
||||
},
|
||||
onRenderContextMenu: false,
|
||||
navigationBar: false,
|
||||
statusBar: false,
|
||||
mainMenuBar: false
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setEditorMode(mode) {
|
||||
this.editorMode = mode
|
||||
this.editor.updateProps({ mode });
|
||||
}
|
||||
},
|
||||
beforeUnmount() {
|
||||
if (this.editor) {
|
||||
this.editor.destroy();
|
||||
this.editor = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
:root {
|
||||
--jse-panel-background: #111;
|
||||
--jse-background-color: #000;
|
||||
--jse-text-color: #ccc;
|
||||
--jse-key-color: #8c8bef;
|
||||
--jse-selection-background-color: rgba(255, 171, 102, 0.177);
|
||||
--jse-context-menu-pointer-background: rgba(255, 171, 102, 0.177);
|
||||
--jse-delimiter-color: #fa6;
|
||||
--jse-value-color-boolean: rgb(132, 132, 137);
|
||||
}
|
||||
|
||||
.jse-boolean-toggle[role="checkbox"] {
|
||||
&[aria-checked="true"] {
|
||||
color: rgb(218, 244, 238);
|
||||
}
|
||||
|
||||
&[aria-checked="false"] {
|
||||
border: 0px transparent;
|
||||
outline: 0px transparent;
|
||||
color: rgb(241, 107, 25);
|
||||
background-color: rgb(241, 107, 25);
|
||||
|
||||
position: relative;
|
||||
|
||||
&:after {
|
||||
position: absolute;
|
||||
|
||||
content: '×';
|
||||
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
color: black;
|
||||
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
83
src/csui/src/components/JsonEditor/JsonArray.vue
Normal file
83
src/csui/src/components/JsonEditor/JsonArray.vue
Normal file
@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<div class="flex flex-col json-level-indent">
|
||||
<div class="flex flex-row" @click="expanded_internal = !expanded_internal">
|
||||
<div v-if="value_internal.key" class="item-key">
|
||||
"{{value_internal.key}}" <b>:</b>
|
||||
<span v-if="!expanded_internal"><b> [</b> ... <b>]</b>,</span>
|
||||
<template v-else><b>[</b></template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(row, key) of value_internal.value"
|
||||
:key="key"
|
||||
>
|
||||
<JsonArray v-if="Array.isArray(row)"
|
||||
:value="row"
|
||||
:ignoreKeys="ignoreKeys"
|
||||
@change="changeItem(rowKey, $event)"
|
||||
>
|
||||
</JsonArray>
|
||||
<JsonObject v-else-if="typeof row === 'object' && row !== null"
|
||||
:value="row"
|
||||
:label="rowKey"
|
||||
:ignoreKeys="ignoreKeys"
|
||||
@change="changeItem(rowKey, $event)"
|
||||
>
|
||||
</JsonObject>
|
||||
<JsonElement v-else
|
||||
:value="row"
|
||||
:label="rowKey"
|
||||
@change="changeItem(rowKey, $event)"
|
||||
>
|
||||
</JsonElement>
|
||||
</div>
|
||||
<div v-if="expanded_internal"><b>],</b></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JsonObject from './JsonObject';
|
||||
import JsonElement from './JsonElement';
|
||||
|
||||
export default {
|
||||
name: 'JsonArray',
|
||||
props: [
|
||||
'value',
|
||||
'expanded',
|
||||
'ignoreKeys', // this prop is passthrough for JsonArray
|
||||
],
|
||||
components: {
|
||||
JsonObject,
|
||||
JsonElement,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value_internal: undefined,
|
||||
expanded_internal: true,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.value_internal = this.value;
|
||||
},
|
||||
watch: {
|
||||
value: function(val) {
|
||||
this.value_internal = val;
|
||||
},
|
||||
expanded: function(val) {
|
||||
if (val !== undefined && val !== null) {
|
||||
this.expanded_internal = !!val;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeItem(key, value) {
|
||||
this.value_internal[key] = value;
|
||||
this.$emit('change', this.value_internal);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped src="./json.scss">
|
||||
</style>
|
||||
<style lang="scss" scoped src="../../../res/css/flex.scss">
|
||||
</style>
|
90
src/csui/src/components/JsonEditor/JsonElement.vue
Normal file
90
src/csui/src/components/JsonEditor/JsonElement.vue
Normal file
@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="flex flex-row json-level-indent">
|
||||
<div>
|
||||
<b>
|
||||
<span v-if="label" class="item-key"
|
||||
:class="{'item-key-boolean-false': typeof value_internal === 'boolean' && !value_internal}"
|
||||
>
|
||||
"{{label}}"
|
||||
</span>
|
||||
:
|
||||
</b>
|
||||
</div>
|
||||
<div v-if="typeof value_internal === 'boolean'"
|
||||
:class="{'json-value-boolean-true': value_internal, 'json-value-boolean-false': !value_internal}"
|
||||
@click="toggleBoolean"
|
||||
>
|
||||
<template v-if="value_internal">true</template>
|
||||
<template v-else>false</template>
|
||||
</div>
|
||||
<div v-else
|
||||
class="flex flex-row inline-edit"
|
||||
:class="{
|
||||
'json-value-number': typeof value_internal === 'number',
|
||||
'json-value-string': typeof value_internal === 'string'
|
||||
}"
|
||||
>
|
||||
<template v-if="typeof value_internal === 'string'">
|
||||
"<div ref="element-edit-area"
|
||||
contenteditable="true"
|
||||
@keydown.enter="changeValue"
|
||||
>
|
||||
{{value_internal}}
|
||||
</div>"
|
||||
</template>
|
||||
<template v-else>
|
||||
<div ref="element-edit-area"
|
||||
contenteditable="true"
|
||||
@keydown.enter="changeValue"
|
||||
>
|
||||
{{value_internal}}
|
||||
</div>
|
||||
</template>,
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'json-element',
|
||||
props: [
|
||||
'value',
|
||||
'label',
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
value_internal: undefined,
|
||||
editing: false,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.value_internal = this.value;
|
||||
},
|
||||
watch: {
|
||||
value: function(val) {
|
||||
this.value_internal = val;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toggleBoolean() {
|
||||
this.value_internal = !this.value_internal;
|
||||
this.$emit('change', this.value_internal);
|
||||
},
|
||||
changeValue() {
|
||||
this.$refs['element-edit-area'].blur();
|
||||
const newValue = this.$refs['element-edit-area'].textContent.replace(/\n/g, '');
|
||||
if (isNaN(newValue)) {
|
||||
this.value_internal = newValue;
|
||||
this.$emit('change', newValue);
|
||||
} else {
|
||||
this.value_internal = +newValue;
|
||||
this.$emit('change', +newValue);
|
||||
}
|
||||
this.editing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped src="./json.scss">
|
||||
</style>
|
91
src/csui/src/components/JsonEditor/JsonObject.vue
Normal file
91
src/csui/src/components/JsonEditor/JsonObject.vue
Normal file
@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div class="flex flex-col json-level-indent">
|
||||
<div class="flex flex-row" @click="expanded_internal = !expanded_internal">
|
||||
<div class="item-key-line">
|
||||
<template v-if="label">
|
||||
<b>
|
||||
<span class="item-key">"{{label}}"</span>
|
||||
:
|
||||
</b>
|
||||
</template>
|
||||
<span v-if="!expanded_internal"><b> {</b> ... <b>}</b>,</span>
|
||||
<template v-else><b>{</b></template>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="expanded_internal">
|
||||
<div v-for="(row, rowKey) of value_internal"
|
||||
:key="rowKey"
|
||||
>
|
||||
<template v-if="(ignoreKeys || {})[rowKey] !== true">
|
||||
<JsonArray v-if="Array.isArray(row)"
|
||||
:value="row"
|
||||
:ignoreKeys="(ignoreKeys || {})[rowKey]"
|
||||
@change="changeItem(rowKey, $event)"
|
||||
>
|
||||
</JsonArray>
|
||||
<JsonObject v-else-if="typeof row === 'object' && row !== null"
|
||||
:value="row"
|
||||
:label="rowKey"
|
||||
:ignoreKeys="(ignoreKeys || {})[rowKey]"
|
||||
@change="changeItem(rowKey, $event)"
|
||||
>
|
||||
</JsonObject>
|
||||
<JsonElement v-else
|
||||
:value="row"
|
||||
:label="rowKey"
|
||||
@change="changeItem(rowKey, $event)"
|
||||
>
|
||||
</JsonElement>
|
||||
</template>
|
||||
</div>
|
||||
<div><b>},</b></div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import JsonArray from './JsonArray';
|
||||
import JsonElement from './JsonElement';
|
||||
|
||||
export default {
|
||||
name: 'JsonObject',
|
||||
props: [
|
||||
'value',
|
||||
'label',
|
||||
'expanded',
|
||||
'ignoreKeys',
|
||||
],
|
||||
components: {
|
||||
JsonArray,
|
||||
JsonElement,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
value_internal: undefined,
|
||||
expanded_internal: true,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.value_internal = this.value;
|
||||
},
|
||||
watch: {
|
||||
value: function(val) {
|
||||
this.value_internal = val;
|
||||
},
|
||||
expanded: function(val) {
|
||||
if (val !== undefined && val !== null) {
|
||||
this.expanded_internal = !!val;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
changeItem(key, value) {
|
||||
this.value_internal[key] = value;
|
||||
this.$emit('change', this.value_internal);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped src="./json.scss">
|
||||
</style>
|
23
src/csui/src/components/JsonEditor/json.scss
Normal file
23
src/csui/src/components/JsonEditor/json.scss
Normal file
@ -0,0 +1,23 @@
|
||||
.json-level-indent {
|
||||
padding-left: 2em !important;
|
||||
font-family: 'Overpass Mono', monospace;
|
||||
}
|
||||
.item-key {
|
||||
color: rgb(255, 196, 148);
|
||||
}
|
||||
.item-key-boolean-false {
|
||||
color: rgb(207, 149, 101)
|
||||
}
|
||||
|
||||
.json-value-boolean-true {
|
||||
color: rgb(150, 240, 198);
|
||||
}
|
||||
.json-value-boolean-false {
|
||||
color: rgb(241, 21, 21);
|
||||
}
|
||||
.json-value-number {
|
||||
color: rgb(121, 121, 238);
|
||||
}
|
||||
.json-value-string {
|
||||
color: rgb(226, 175, 7);
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="message"
|
||||
class="popup-overlay"
|
||||
:class="{'dim': dimOverlay}"
|
||||
>
|
||||
<div class="popup-content" >
|
||||
<div v-if="title" class="header" :class="type">
|
||||
{{title}}
|
||||
</div>
|
||||
<p>
|
||||
{{ message }}
|
||||
</p>
|
||||
<div class="flex flex-row justify-end">
|
||||
<button
|
||||
class="primary"
|
||||
v-if="confirmButtonText"
|
||||
:class="type"
|
||||
@click="$emit('onConfirm')"
|
||||
>
|
||||
{{ confirmButtonText }}
|
||||
</button>
|
||||
<button
|
||||
:class="type"
|
||||
@click="$emit('onCancel')"
|
||||
>
|
||||
{{ cancelButtonText }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
// onConfirm: {
|
||||
// type: Function,
|
||||
// required: false
|
||||
// },
|
||||
// onCancel: {
|
||||
// type: Function,
|
||||
// default: () => this.$emit('close')
|
||||
// },
|
||||
title: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
message: {
|
||||
type: String,
|
||||
required: false, // technically prolly should be true, but we don't have to guard against skill issue
|
||||
},
|
||||
dimOverlay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'info'
|
||||
},
|
||||
confirmButtonText: {
|
||||
type: String,
|
||||
required: false,
|
||||
},
|
||||
cancelButtonText: {
|
||||
type: String,
|
||||
default: 'Cancel'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.popup-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
// background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: saturate(50%) brightness(50%) blur(1rem);
|
||||
z-index: 99999;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.header {
|
||||
font-size: 1.33rem;
|
||||
|
||||
color: #fff;
|
||||
|
||||
&.danger {
|
||||
color: rgb(251, 107, 63);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.body {
|
||||
min-height: 5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,44 +1,44 @@
|
||||
<template>
|
||||
<div v-if="siteSupportLevel === 'official'" class="site-support official" :style="supportLevelStyle">
|
||||
<div v-if="siteSupportLevel === 'official'" class="site-support official">
|
||||
<mdicon name="check-decagram" />
|
||||
<div v-if="!small">Verified</div>
|
||||
<div class="tooltip" :style="tooltipStyle">
|
||||
<div class="tooltip">
|
||||
<template v-if="small">Verified — </template>
|
||||
The extension is being tested and should work on this site.
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="siteSupportLevel === 'community'" class="site-support community" :style="supportLevelStyle">
|
||||
<div v-if="siteSupportLevel === 'community'" class="site-support community">
|
||||
<mdicon name="account-group" />
|
||||
<div v-if="!small">Community</div>
|
||||
<div class="tooltip" :style="tooltipStyle">
|
||||
<div class="tooltip">
|
||||
<template v-if="small">Community — </template>
|
||||
People say extension works on this site (or have provided help getting the extension to work if it didn't).<br/><br/>
|
||||
Tamius (the dev) does not test the extension on this site, probably because it requires a subscription or
|
||||
is geoblocked.
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="siteSupportLevel === 'no-support' || siteSupportLevel === 'unknown'" class="site-support no-support" :style="supportLevelStyle">
|
||||
<div v-if="siteSupportLevel === 'no-support'" class="site-support no-support">
|
||||
<mdicon name="help-circle-outline" />
|
||||
<div v-if="!small">Unknown</div>
|
||||
<div class="tooltip" :style="tooltipStyle">
|
||||
<div class="tooltip">
|
||||
<template v-if="small">Unknown — </template>
|
||||
Not officially supported. Extension will try to fix things, but no promises.<br/><br/>
|
||||
Tamius (the dev) does not test the extension on this site for various reasons
|
||||
(unaware, not using the site, language barrier, geoblocking, paid services Tam doesn't use).
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="siteSupportLevel === 'user-added' || siteSupportLevel === 'user-defined'" class="site-support user-added" :style="supportLevelStyle">
|
||||
<div v-if="siteSupportLevel === 'user-added' || siteSupportLevel === 'user-defined'" class="site-support user-added">
|
||||
<mdicon name="account" />
|
||||
<div v-if="!small">Modified by you</div>
|
||||
<div class="tooltip" :style="tooltipStyle">
|
||||
<div class="tooltip">
|
||||
<template v-if="small">Modified by you — </template>
|
||||
You have manually changed settings for this site. The extension is doing what you told it to do.
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="siteSupportLevel === 'officially-disabled'" class="site-support officially-disabled" :style="supportLevelStyle">
|
||||
<div v-if="siteSupportLevel === 'officially-disabled'" class="site-support officially-disabled">
|
||||
<mdicon class="site-support no-support" name="checkbox-marked-circle" />
|
||||
<div v-if="!small">Not supported</div>
|
||||
<div class="tooltip" :style="tooltipStyle">
|
||||
<div class="tooltip">
|
||||
<template v-if="small">Not supported — </template>
|
||||
Extension is known to not work with this site.
|
||||
</div>
|
||||
@ -50,8 +50,6 @@ export default {
|
||||
props: {
|
||||
siteSupportLevel: String,
|
||||
small: Boolean,
|
||||
supportLevelStyle: String,
|
||||
tooltipStyle: String,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -73,11 +73,10 @@
|
||||
@change="(event) => updateSettings(true)"
|
||||
>
|
||||
</div>
|
||||
<div class="hint">
|
||||
Width of the trigger zone (% of player area).
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
Width of the trigger zone (% of player area).
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label">Trigger zone height:</div>
|
||||
<div class="input range-input">
|
||||
@ -96,11 +95,10 @@
|
||||
@change="(event) => updateSettings(true)"
|
||||
>
|
||||
</div>
|
||||
<div class="hint">
|
||||
Height of the trigger zone (% of player area).
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
Height of the trigger zone (% of player area).
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label">Trigger zone horizontal offset:</div>
|
||||
<div class="input range-input">
|
||||
@ -118,11 +116,10 @@
|
||||
@change="(event) => updateSettings(true)"
|
||||
>
|
||||
</div>
|
||||
<div class="hint">
|
||||
By default, trigger zone is centered around the button. This option moves trigger zone left and right.
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
By default, trigger zone is centered around the button. This option moves trigger zone left and right.
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="label">Trigger zone vertical offset:</div>
|
||||
<div class="input range-input">
|
||||
@ -140,9 +137,9 @@
|
||||
@change="(event) => updateSettings(true)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hint">
|
||||
By default, trigger zone is centered around the button. This option moves trigger zone up and down.
|
||||
<div class="hint">
|
||||
By default, trigger zone is centered around the button. This option moves trigger zone up and down.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="action-row">
|
||||
|
@ -1,77 +0,0 @@
|
||||
<template>
|
||||
<button
|
||||
class="button drop-zone"
|
||||
:class="[{'drag-over': isDragOver}, className]"
|
||||
@click="onClick"
|
||||
@dragover="isDragOver = true"
|
||||
@dragleave="isDragOver = false"
|
||||
@drop="onDrop"
|
||||
>
|
||||
<slot></slot>
|
||||
<input type="file" ref="fileInput" @change="onFileChange" accept=".json" />
|
||||
</button>
|
||||
|
||||
</template>
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
className: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isDragOver: false
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$refs.fileInput.click();
|
||||
},
|
||||
onDrop(event) {
|
||||
event.preventDefault();
|
||||
this.isDragOver = false;
|
||||
|
||||
if (event.dataTransfer.files.length > 0) {
|
||||
this.handleFile(event.dataTransfer.files[0]);
|
||||
}
|
||||
},
|
||||
onFileChange(event) {
|
||||
if (event.target.files.length > 0) {
|
||||
this.handleFile(event.target.files[0]);
|
||||
}
|
||||
},
|
||||
handleFile(file) {
|
||||
if (file.type !== "application/json") {
|
||||
this.$emit('error', 'NOT_JSON_FILE');
|
||||
return;
|
||||
}
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onload = async (event) => {
|
||||
try {
|
||||
const importedData = JSON.parse(event.target.result);
|
||||
this.$emit('importedJson', importedData);
|
||||
} catch (error) {
|
||||
this.$emit('error', 'INVALID_JSON');
|
||||
}
|
||||
};
|
||||
reader.readAsText(file);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
input {
|
||||
display: none;
|
||||
}
|
||||
.drop-zone {
|
||||
&.drag-over {
|
||||
background-color: #fa6;
|
||||
color: black;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,158 +1,62 @@
|
||||
<template>
|
||||
<div class="flex flex-col relative h-full" style="padding-bottom: 20px">
|
||||
<!--
|
||||
Extension is disabled for a given site when it's disabled in full screen, since
|
||||
current settings do not allow the extension to only be disabled while in full screen
|
||||
-->
|
||||
<template v-if="siteSettings.isEnabledForEnvironment(false, true) === ExtensionMode.Disabled && !enabledHosts?.length">
|
||||
<div class="h-full flex flex-col items-center justify-center" style="margin-top: 8rem">
|
||||
<div class="info">
|
||||
Extension is not enabled for this site.
|
||||
</div>
|
||||
<div>
|
||||
Please enable extension for this site.
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="flex flex-row items-center"
|
||||
style="background-color: transparent; padding: 0.25rem 0.5rem; margin-top: 1rem;"
|
||||
@click="openSettings()"
|
||||
>
|
||||
Open settings <mdicon style="margin-left: 0.5rem;" name="open-in-new" size="16"></mdicon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-col" style="padding-bottom: 20px">
|
||||
<div class="flex flex-row">
|
||||
<mdicon name="crop" :size="24" />
|
||||
<h1>Crop video:</h1>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div
|
||||
v-if="siteSettings.isEnabledForEnvironment(false, true) === ExtensionMode.Disabled"
|
||||
class="warning-compact"
|
||||
>
|
||||
<div class="w-full flex flex-row">
|
||||
<div class="grow">
|
||||
<b>Extension is disabled for this site.</b>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="flex flex-row items-center"
|
||||
style="border: 1px solid black; background-color: transparent; color: black; padding: 0.25rem 0.5rem; margin-top: -0.25rem; margin-right: -0.5rem;"
|
||||
@click="openSettings()"
|
||||
>
|
||||
Open settings <mdicon style="margin-left: 0.5rem;" name="open-in-new" size="16"></mdicon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<small>Controls will only work on content embedded from the following sites:</small><br/>
|
||||
<div class="w-full flex flex-row justify-center">
|
||||
<span v-for="host of enabledHosts" :key="host" class="website-name">{{host}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<CropOptionsPanel
|
||||
style="margin-top: -2rem"
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
:siteSettings="siteSettings"
|
||||
:isEditing="false"
|
||||
>
|
||||
</CropOptionsPanel>
|
||||
|
||||
<div class="flex flex-row">
|
||||
<mdicon name="crop" :size="16" />
|
||||
<span>CROP</span>
|
||||
</div>
|
||||
<div
|
||||
style="margin-top: -0.69rem; margin-bottom: 0.88rem;"
|
||||
>
|
||||
<CropOptionsPanel
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
:siteSettings="siteSettings"
|
||||
:isEditing="false"
|
||||
:compact="true"
|
||||
>
|
||||
</CropOptionsPanel>
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<mdicon name="crop" :size="24" />
|
||||
<h1>Stretch video:</h1>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row">
|
||||
<mdicon name="crop" :size="16" />
|
||||
<span>STRETCH</span>
|
||||
</div>
|
||||
<div
|
||||
style="margin-top: -0.69rem; margin-bottom: 0.88rem;"
|
||||
>
|
||||
<StretchOptionsPanel
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
:siteSettings="siteSettings"
|
||||
:isEditing="false"
|
||||
:compact="true"
|
||||
></StretchOptionsPanel>
|
||||
</div>
|
||||
<StretchOptionsPanel
|
||||
style="margin-top: -2rem"
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
:siteSettings="siteSettings"
|
||||
:isEditing="false"
|
||||
></StretchOptionsPanel>
|
||||
|
||||
<div class="flex flex-row">
|
||||
<mdicon name="crop" :size="16" />
|
||||
<span>ZOOM</span>
|
||||
</div>
|
||||
<div
|
||||
style="margin-top: -0.69rem; margin-bottom: 0.88rem;"
|
||||
>
|
||||
<ZoomOptionsPanel
|
||||
:settings="settings"
|
||||
:eventBus="eventBus"
|
||||
:siteSettings="siteSettings"
|
||||
:isEditing="false"
|
||||
:compact="true"
|
||||
>
|
||||
</ZoomOptionsPanel>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row">
|
||||
<mdicon name="crop" :size="16" />
|
||||
<span>ALIGN</span>
|
||||
</div>
|
||||
<div
|
||||
style="margin-bottom: 0.88rem;"
|
||||
>
|
||||
<AlignmentOptionsControlComponent
|
||||
:eventBus="eventBus"
|
||||
:large="true"
|
||||
> </AlignmentOptionsControlComponent>
|
||||
</div>
|
||||
</template>
|
||||
<div class="flex flex-row">
|
||||
<mdicon name="crop" :size="24" />
|
||||
<h1>Zoom:</h1>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CropOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/CropOptionsPanel';
|
||||
import StretchOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/StretchOptionsPanel.vue';
|
||||
import ZoomOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/ZoomOptionsPanel.vue';
|
||||
import ExtensionMode from '@src/common/enums/ExtensionMode.enum.ts';
|
||||
import AlignmentOptionsControlComponent from '@csui/src/PlayerUiPanels/AlignmentOptionsControlComponent.vue';
|
||||
import { SiteSettings } from '../../../../ext/lib/settings/SiteSettings';
|
||||
import CropOptionsPanel from '../../PlayerUiPanels/PanelComponents/VideoSettings/CropOptionsPanel';
|
||||
import StretchOptionsPanel from '../../PlayerUiPanels/PanelComponents/VideoSettings/StretchOptionsPanel.vue';
|
||||
import ZoomOptionsPanel from '../../PlayerUiPanels/PanelComponents/VideoSettings/ZoomOptionsPanel.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CropOptionsPanel,
|
||||
StretchOptionsPanel,
|
||||
ZoomOptionsPanel,
|
||||
AlignmentOptionsControlComponent
|
||||
data() {
|
||||
return {
|
||||
exec: null,
|
||||
};
|
||||
},
|
||||
mixins: [
|
||||
|
||||
],
|
||||
props: [
|
||||
'site',
|
||||
'settings',
|
||||
'siteSettings',
|
||||
'eventBus',
|
||||
'hosts'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
exec: null,
|
||||
ExtensionMode: ExtensionMode,
|
||||
enabledHosts: [],
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
hosts(val) {
|
||||
this.filterActiveSites(val);
|
||||
}
|
||||
components: {
|
||||
CropOptionsPanel, StretchOptionsPanel, ZoomOptionsPanel
|
||||
},
|
||||
created() {
|
||||
this.eventBus.subscribe(
|
||||
@ -162,7 +66,6 @@ export default {
|
||||
function: (config) => this.handleConfigBroadcast(config)
|
||||
}
|
||||
);
|
||||
this.filterActiveSites(this.hosts);
|
||||
},
|
||||
mounted() {
|
||||
this.eventBus.sendToTunnel('get-ar');
|
||||
@ -171,37 +74,8 @@ export default {
|
||||
this.eventBus.unsubscribeAll(this);
|
||||
},
|
||||
methods: {
|
||||
filterActiveSites(val) {
|
||||
this.enabledHosts = [];
|
||||
|
||||
for (const host of val) {
|
||||
const siteSettings = new SiteSettings(this.settings, host);
|
||||
|
||||
if (siteSettings.isEnabledForEnvironment(false, true) === ExtensionMode.Enabled) {
|
||||
this.enabledHosts.push(host);
|
||||
}
|
||||
}
|
||||
},
|
||||
openSettings() {
|
||||
this.eventBus.send('open-popup-settings', {tab: 'extensionSettings'})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.warning-compact {
|
||||
background-color: #d6ba4a;
|
||||
color: #000;
|
||||
padding: 0.5rem 1rem;
|
||||
margin-top: -0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
.website-name {
|
||||
font-size: 0.85rem;
|
||||
|
||||
&:not(:last-of-type)::after {
|
||||
content: ','
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -117,9 +117,9 @@
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<textarea
|
||||
v-model="playerCss"
|
||||
@change="updatePlayerCss"
|
||||
@blur="updatePlayerCss"
|
||||
v-model="playerCss"
|
||||
@change="updatePlayerCss"
|
||||
@blur="updatePlayerCss"
|
||||
>
|
||||
</textarea>
|
||||
</div>
|
||||
|
58
src/csui/src/popup/panels/WhatsNewPanel.vue
Normal file
58
src/csui/src/popup/panels/WhatsNewPanel.vue
Normal file
@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div>
|
||||
<h2>What's new</h2>
|
||||
<p>Full changelog for older versions <a href="https://github.com/tamius-han/ultrawidify/blob/master/CHANGELOG.md">is available here</a>.</p>
|
||||
<p class="label">6.0.0</p>
|
||||
<ul>
|
||||
<li>Fixed laginess in Chromium-based browsers on Windows. Details in <a href="https://github.com/tamius-han/ultrawidify/issues/199#issuecomment-1221383134" target="blank">#199</a>.</li>
|
||||
</ul>
|
||||
<p class="label">5.1.2</p>
|
||||
<ul>
|
||||
<li>
|
||||
<b>In-player UI.</b><br/>
|
||||
This one took major effort to pull off. Required some changes under the hood, required me to de-spaghettify some code. The UI will only show
|
||||
if the video takes up sufficient amount of space (currently determined as 960 pixels wide).
|
||||
</li>
|
||||
<li>
|
||||
By default, Ultrawidify will not kick in on "small videos". You have to be either in full screen or theater mode. Ultrawidify assumes that any
|
||||
video that takes up more than 85% of the window width is being viewed in theater mode. This value may be tweaked later on. This feature can be
|
||||
configured (or turned off entirely) via 'Advanced options' menu on the in-player UI.
|
||||
</li>
|
||||
<li>
|
||||
The in-player extension UI will do a better job differentiating between the various level of support: "official" for the sites that Tam can check
|
||||
on his own, "fingers crossed" for sites that Tam can't check because my load-bearing credit card can't support the weight of a dozen subscription
|
||||
services, and "community support" for sites that enjoy support through the helping hand of people willing to help.
|
||||
</li>
|
||||
<li>
|
||||
The in-player UI now displays a warning whenever Ultrawidify detects automatic aspect ratio detection isn't happening because the site uses DRM.
|
||||
</li>
|
||||
<li>
|
||||
Made the UI for fixing problems with Ultrawidify not working a bit more intuitive.
|
||||
</li>
|
||||
<li>
|
||||
Better zooming. The slider is back, baby.
|
||||
</li>
|
||||
<li>
|
||||
New alignment options. Video can be aligned vertically as well as horizontally.
|
||||
</li>
|
||||
<li>
|
||||
Panning option that's a bit more intuitive.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import BrowserDetect from '../../ext/conf/BrowserDetect';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
BrowserDetect: BrowserDetect
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
@ -1,109 +1,42 @@
|
||||
<template>
|
||||
<div class="flex flex-row" style="width: 250px;">
|
||||
<div class="flex-grow">
|
||||
Custom zoom
|
||||
</div>
|
||||
<div class="flex flex-row">
|
||||
<Button
|
||||
v-if="zoomAspectRatioLocked"
|
||||
icon="lock"
|
||||
:iconSize="16"
|
||||
:fixedWidth="true"
|
||||
:noPad="true"
|
||||
@click="toggleZoomAr()"
|
||||
>
|
||||
</Button>
|
||||
<Button
|
||||
v-else
|
||||
icon="lock-open-variant"
|
||||
:iconSize="16"
|
||||
:fixedWidth="true"
|
||||
:noPad="true"
|
||||
@click="toggleZoomAr()"
|
||||
>
|
||||
</Button>
|
||||
<Button
|
||||
icon="restore"
|
||||
:iconSize="16"
|
||||
:noPad="true"
|
||||
@click="resetZoom()"
|
||||
></Button>
|
||||
</div>
|
||||
<div>
|
||||
Custom zoom
|
||||
</div>
|
||||
|
||||
<template v-if="zoomAspectRatioLocked">
|
||||
<div class="input range-input no-bg">
|
||||
<input
|
||||
type="range"
|
||||
class="slider"
|
||||
min="-1"
|
||||
max="3"
|
||||
step="0.01"
|
||||
:value="zoom.x"
|
||||
@input="changeZoom($event.target.value)"
|
||||
/>
|
||||
<input
|
||||
class="disabled"
|
||||
style="width: 2rem;"
|
||||
:value="getZoomForDisplay('x')"
|
||||
/>
|
||||
</div>
|
||||
<div class="top-label">Zoom:</div>
|
||||
<div class="input range-input">
|
||||
<input
|
||||
type="range"
|
||||
class="slider"
|
||||
min="0"
|
||||
max="3"
|
||||
step="0.01"
|
||||
/>
|
||||
<input
|
||||
/>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="top-label">Horizontal zoom:</div>
|
||||
<div class="input range-input no-bg">
|
||||
<input
|
||||
type="range"
|
||||
class="slider"
|
||||
min="-1"
|
||||
max="3"
|
||||
step="0.01"
|
||||
:value="zoom.x"
|
||||
@input="changeZoom($event.target.value, 'x')"
|
||||
/>
|
||||
<input
|
||||
class="disabled"
|
||||
style="width: 2rem;"
|
||||
:value="getZoomForDisplay('x')"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="true">
|
||||
<div class="top-label">Vertical zoom:</div>
|
||||
<div class="input range-input no-bg">
|
||||
<div class="input range-input">
|
||||
<input
|
||||
type="range"
|
||||
class="slider"
|
||||
min="-1"
|
||||
min="0"
|
||||
max="3"
|
||||
step="0.01"
|
||||
:value="zoom.y"
|
||||
@input="changeZoom($event.target.value, 'y')"
|
||||
/>
|
||||
<input
|
||||
class="disabled"
|
||||
style="width: 2rem;"
|
||||
:value="getZoomForDisplay('y')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div><input type="checkbox"/> Control vertical and horizontal zoom independently.</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Button from '@csui/src/components/Button.vue';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Button,
|
||||
|
||||
},
|
||||
mixins: [
|
||||
|
||||
],
|
||||
props: [
|
||||
'settings', // required for buttons and actions, which are global
|
||||
'eventBus'
|
||||
],
|
||||
data() {
|
||||
return {
|
||||
zoomAspectRatioLocked: true,
|
||||
@ -118,42 +51,17 @@ export default {
|
||||
stretch: null,
|
||||
zoom: null,
|
||||
pan: null
|
||||
},
|
||||
pollingInterval: undefined,
|
||||
debouncedGetEffectiveZoom: undefined,
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.eventBus?.subscribeMulti(
|
||||
{
|
||||
'announce-zoom': {
|
||||
function: (data) => {
|
||||
this.zoom = {
|
||||
x: Math.log2(data.x),
|
||||
y: Math.log2(data.y)
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
this
|
||||
);
|
||||
this.debouncedGetEffectiveZoom = _.debounce(
|
||||
() => {
|
||||
this.getEffectiveZoom();
|
||||
},
|
||||
250
|
||||
),
|
||||
this.getEffectiveZoom();
|
||||
this.pollingInterval = setInterval(this.debouncedGetEffectiveZoom, 2000);
|
||||
},
|
||||
destroyed() {
|
||||
this.eventBus.unsubscribe(this);
|
||||
clearInterval(this.pollingInterval);
|
||||
},
|
||||
mixins: [
|
||||
|
||||
],
|
||||
props: [
|
||||
'settings', // required for buttons and actions, which are global
|
||||
'eventBus'
|
||||
],
|
||||
methods: {
|
||||
getEffectiveZoom() {
|
||||
this.eventBus?.sendToTunnel('get-effective-zoom', {});
|
||||
},
|
||||
getZoomForDisplay(axis) {
|
||||
// zoom is internally handled logarithmically, because we want to have x0.5, x1, x2, x4 ... magnifications
|
||||
// spaced out at regular intervals. When displaying, we need to convert that to non-logarithmic values.
|
||||
@ -173,36 +81,25 @@ export default {
|
||||
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'y'});
|
||||
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'x'});
|
||||
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: 1});
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: 1, axis: 'y'});
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: 1, axis: 'x'});
|
||||
},
|
||||
changeZoom(newZoom, axis, isLinear) {
|
||||
if (isNaN(+newZoom)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let logZoom, linZoom;
|
||||
if (isLinear) {
|
||||
newZoom /= 100;
|
||||
logZoom = Math.log2(newZoom);
|
||||
linZoom = newZoom;
|
||||
} else {
|
||||
logZoom = newZoom;
|
||||
linZoom = Math.pow(2, newZoom);
|
||||
}
|
||||
|
||||
// we store zoom logarithmically on this component
|
||||
changeZoom(newZoom, axis) {
|
||||
// we store zoom logarithmically on this compnent
|
||||
if (!axis) {
|
||||
this.zoom.x = logZoom;
|
||||
this.zoom.x = newZoom;
|
||||
} else {
|
||||
this.zoom[axis] = logZoom;
|
||||
this.zoom[axis] = newZoom;
|
||||
}
|
||||
|
||||
// we do not use logarithmic zoom elsewhere, therefore we need to convert
|
||||
newZoom = Math.pow(2, newZoom);
|
||||
|
||||
if (this.zoomAspectRatioLocked) {
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: linZoom});
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom, axis: 'y'});
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom, axis: 'x'});
|
||||
} else {
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: {[axis ?? 'x']: linZoom}});
|
||||
this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom, axis: axis ?? 'x'});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -4,12 +4,6 @@ div, p, span {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
/* color: #666; */
|
||||
filter: contrast(50%) brightness(40%) grayscale(100%);
|
||||
}
|
||||
|
||||
.warning-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -58,19 +52,10 @@ button, .button {
|
||||
border-color: rgba($primary, .5);
|
||||
}
|
||||
|
||||
|
||||
&.primary {
|
||||
background-color: #fa6;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
&.danger {
|
||||
background-color: #ff2211 !important;
|
||||
color:#000;
|
||||
}
|
||||
&.disabled {
|
||||
filter: saturate(0%);
|
||||
}
|
||||
}
|
||||
.b3 {
|
||||
margin: 0.25rem;
|
||||
@ -89,12 +74,6 @@ button, .button {
|
||||
border-bottom: 1px solid rgba($primary, 0.5);
|
||||
}
|
||||
|
||||
&.no-bg {
|
||||
background-color: transparent;
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
outline: none;
|
||||
@ -136,9 +115,7 @@ button, .button {
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
flex-wrap: wrap;
|
||||
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
@ -152,47 +129,28 @@ button, .button {
|
||||
|
||||
.label {
|
||||
flex: 0 0 25%;
|
||||
min-width: 16rem;
|
||||
text-align: right;
|
||||
padding-right: 1rem;
|
||||
|
||||
.color-emphasis {
|
||||
color: #fa6;
|
||||
}
|
||||
.sub-label {
|
||||
font-size: 0.9em;
|
||||
opacity: 0.69;
|
||||
}
|
||||
}
|
||||
|
||||
.input, .range-input {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
flex: 0 0 70%;
|
||||
max-width: 24rem;
|
||||
min-width: 16rem;
|
||||
}
|
||||
|
||||
.has-hint {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
.hint {
|
||||
padding-left: calc(25% + 1rem);
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
margin-top: 0.25rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.select {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
|
||||
min-width: 12px;
|
||||
max-width: 24rem;
|
||||
|
||||
select {
|
||||
background: rgba($blackBg, $hoverTransparentOpacity);
|
||||
|
||||
min-width: 12px;
|
||||
max-width: 100%;
|
||||
|
||||
color: #fff;
|
||||
// border: 0px solid transparent;
|
||||
border: 1px solid rgba(255, 171, 102, 0.42);
|
||||
border: 0px solid transparent;
|
||||
|
||||
padding: 0.5rem 1rem 0.25rem;
|
||||
outline: none;
|
||||
@ -202,15 +160,6 @@ button, .button {
|
||||
}
|
||||
}
|
||||
}
|
||||
.hint {
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0.75rem;
|
||||
margin-left: 5rem;
|
||||
// width: 100%;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
|
||||
.options-bar {
|
||||
position: absolute;
|
||||
@ -231,15 +180,9 @@ button, .button {
|
||||
padding-left: 0.33rem;
|
||||
padding-right: 0.33rem;
|
||||
}
|
||||
.b3-compact {
|
||||
width: 7rem;
|
||||
padding-left: 0.25rem;
|
||||
padding-right: 0.25rem;
|
||||
.input-slider {
|
||||
width: 480px;
|
||||
}
|
||||
|
||||
// .input-slider {
|
||||
// width: 480px;
|
||||
// }
|
||||
.warning-lite {
|
||||
padding-right: 16px;
|
||||
padding-bottom: 16px;
|
||||
|
@ -36,7 +36,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
playerDimensionsUpdate(dimensions) {
|
||||
if (!dimensions?.width || !dimensions?.height) {
|
||||
if (!dimensions.width || !dimensions.height) {
|
||||
this.playerDimensions = undefined;
|
||||
}
|
||||
if (dimensions?.width !== this.playerDimensions?.width || dimensions?.height !== this.playerDimensions?.height) {
|
||||
|
@ -1,13 +1,15 @@
|
||||
import Debug from './conf/Debug';
|
||||
import ExtensionMode from '../common/enums/ExtensionMode.enum';
|
||||
import Settings from './lib/Settings';
|
||||
import Comms from './lib/comms/Comms';
|
||||
import CommsClient from './lib/comms/CommsClient';
|
||||
import PageInfo from './lib/video-data/PageInfo';
|
||||
import Logger, { baseLoggingOptions } from './lib/Logger';
|
||||
import UWGlobals from './lib/UWGlobals';
|
||||
import EventBus from './lib/EventBus';
|
||||
import KeyboardHandler from './lib/kbm/KeyboardHandler';
|
||||
import { SiteSettings } from './lib/settings/SiteSettings';
|
||||
import UI from './lib/uwui/UI';
|
||||
import { BLANK_LOGGER_CONFIG, LogAggregator } from './lib/logging/LogAggregator';
|
||||
import { ComponentLogger } from './lib/logging/ComponentLogger';
|
||||
|
||||
export default class UWContent {
|
||||
pageInfo: PageInfo;
|
||||
@ -15,8 +17,7 @@ export default class UWContent {
|
||||
settings: Settings;
|
||||
siteSettings: SiteSettings;
|
||||
keyboardHandler: KeyboardHandler;
|
||||
logAggregator: LogAggregator;
|
||||
logger: ComponentLogger;
|
||||
logger: Logger;
|
||||
eventBus: EventBus;
|
||||
isIframe: boolean = false;
|
||||
|
||||
@ -33,7 +34,7 @@ export default class UWContent {
|
||||
|
||||
reloadSettings() {
|
||||
try {
|
||||
this.logger.debug('reloadSettings', 'Things happened in the popup. Will reload extension settings.');
|
||||
this.logger.log('info', 'debug', 'Things happened in the popup. Will reload extension settings.');
|
||||
this.init();
|
||||
} catch (e) {
|
||||
console.warn('Ultrawidify: settings reload failed. This probably shouldn\'t outright kill the extension, but page reload is recommended.');
|
||||
@ -49,9 +50,8 @@ export default class UWContent {
|
||||
// logger init is the first thing that needs to run
|
||||
try {
|
||||
if (!this.logger) {
|
||||
this.logAggregator = new LogAggregator('◈');
|
||||
this.logger = new ComponentLogger(this.logAggregator, 'UWContent');
|
||||
await this.logAggregator.init(BLANK_LOGGER_CONFIG);
|
||||
this.logger = new Logger();
|
||||
await this.logger.init(baseLoggingOptions);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("logger init failed!", e)
|
||||
@ -67,7 +67,7 @@ export default class UWContent {
|
||||
if (!this.settings) {
|
||||
this.settings = new Settings({
|
||||
onSettingsChanged: () => this.reloadSettings(),
|
||||
logAggregator: this.logAggregator
|
||||
logger: this.logger
|
||||
});
|
||||
await this.settings.init();
|
||||
this.siteSettings = this.settings.getSiteSettings();
|
||||
@ -81,13 +81,13 @@ export default class UWContent {
|
||||
function: () => this.initPhase2()
|
||||
}
|
||||
);
|
||||
this.comms = new CommsClient('content-main-port', this.logAggregator, this.eventBus);
|
||||
this.comms = new CommsClient('content-main-port', this.logger, this.eventBus);
|
||||
this.eventBus.setComms(this.comms);
|
||||
|
||||
|
||||
this.initPhase2();
|
||||
} catch (e) {
|
||||
console.error('Ultrawidify initialization failed for some reason:', e);
|
||||
console.error('Ultrawidify initalization failed for some reason:', e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,21 +95,21 @@ export default class UWContent {
|
||||
initPhase2() {
|
||||
try {
|
||||
if (this.pageInfo) {
|
||||
this.logger.info('setup', 'An instance of pageInfo already exists and will be destroyed.');
|
||||
this.logger.log('info', 'debug', '[uw.js::setup] An instance of pageInfo already exists and will be destroyed.');
|
||||
this.pageInfo.destroy();
|
||||
}
|
||||
this.pageInfo = new PageInfo(this.eventBus, this.siteSettings, this.settings, this.logAggregator);
|
||||
this.logger.debug('setup', "pageInfo initialized.");
|
||||
this.pageInfo = new PageInfo(this.eventBus, this.siteSettings, this.settings, this.logger);
|
||||
this.logger.log('info', 'debug', "[uw.js::setup] pageInfo initialized.");
|
||||
|
||||
this.logger.debug('setup', "will try to initate KeyboardHandler.");
|
||||
this.logger.log('info', 'debug', "[uw.js::setup] will try to initate KeyboardHandler.");
|
||||
|
||||
if (this.keyboardHandler) {
|
||||
this.keyboardHandler.destroy();
|
||||
}
|
||||
this.keyboardHandler = new KeyboardHandler(this.eventBus, this.siteSettings, this.settings, this.logAggregator);
|
||||
this.keyboardHandler = new KeyboardHandler(this.eventBus, this.siteSettings, this.settings, this.logger);
|
||||
this.keyboardHandler.init();
|
||||
|
||||
this.logger.debug('setup', "KeyboardHandler initiated.");
|
||||
this.logger.log('info', 'debug', "[uw.js::setup] KeyboardHandler initiated.");
|
||||
|
||||
this.globalUi = new UI('ultrawidify-global-ui', {eventBus: this.eventBus, isGlobal: true});
|
||||
this.globalUi.enable();
|
||||
@ -117,7 +117,7 @@ export default class UWContent {
|
||||
|
||||
} catch (e) {
|
||||
console.error('Ultrawidify: failed to start extension. Error:', e)
|
||||
this.logger.error('setup', "FAILED TO START EXTENSION. Error:", e);
|
||||
this.logger.log('error', 'debug', "[uw::init] FAILED TO START EXTENSION. Error:", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,20 +2,13 @@ import Debug from './conf/Debug.js';
|
||||
import BrowserDetect from './conf/BrowserDetect';
|
||||
import CommsServer from './lib/comms/CommsServer';
|
||||
import Settings from './lib/Settings';
|
||||
import Logger, { baseLoggingOptions } from './lib/Logger';
|
||||
import { sleep } from '../common/js/utils';
|
||||
import EventBus, { EventBusCommand } from './lib/EventBus';
|
||||
import { ComponentLogger } from './lib/logging/ComponentLogger';
|
||||
import { BLANK_LOGGER_CONFIG, LogAggregator } from './lib/logging/LogAggregator';
|
||||
|
||||
|
||||
const BASE_LOGGING_STYLES = {
|
||||
'log': 'background-color: #243; color: #4a8',
|
||||
}
|
||||
|
||||
export default class UWServer {
|
||||
settings: Settings;
|
||||
logger: ComponentLogger;
|
||||
logAggregator: LogAggregator;
|
||||
logger: Logger;
|
||||
comms: CommsServer;
|
||||
eventBus: EventBus;
|
||||
|
||||
@ -71,13 +64,22 @@ export default class UWServer {
|
||||
async setup() {
|
||||
try {
|
||||
// logger is the first thing that goes up
|
||||
const loggingOptions = BLANK_LOGGER_CONFIG;
|
||||
const loggingOptions = {
|
||||
isBackgroundScript: true,
|
||||
allowLogging: false,
|
||||
useConfFromStorage: true,
|
||||
logAll: true,
|
||||
fileOptions: {
|
||||
enabled: false,
|
||||
},
|
||||
consoleOptions: {
|
||||
enabled: false
|
||||
}
|
||||
};
|
||||
this.logger = new Logger();
|
||||
await this.logger.init(loggingOptions);
|
||||
|
||||
this.logAggregator = new LogAggregator('🔶bg-script🔶');
|
||||
this.logger = new ComponentLogger(this.logAggregator, 'UwServer', {styles: BASE_LOGGING_STYLES});
|
||||
await this.logAggregator.init(loggingOptions);
|
||||
|
||||
this.settings = new Settings({logAggregator: this.logAggregator});
|
||||
this.settings = new Settings({logger: this.logger});
|
||||
await this.settings.init();
|
||||
|
||||
this.eventBus = new EventBus({isUWServer: true});
|
||||
@ -93,16 +95,12 @@ export default class UWServer {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async _promisifyTabsGet(browserObj, tabId){
|
||||
return new Promise( (resolve, reject) => {
|
||||
browserObj.tabs.get(tabId, (tab) => resolve(tab));
|
||||
});
|
||||
}
|
||||
|
||||
//#region CSS managemeent
|
||||
|
||||
async injectCss(css, sender) {
|
||||
if (!css) {
|
||||
return;
|
||||
@ -132,7 +130,7 @@ export default class UWServer {
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error('injectCss', 'Error while injecting css:', {error: e, css, sender});
|
||||
this.logger.log('error','debug', '[UwServer::injectCss] Error while injecting css:', {error: e, css, sender});
|
||||
}
|
||||
}
|
||||
async removeCss(css, sender) {
|
||||
@ -162,16 +160,16 @@ export default class UWServer {
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error('injectCss', 'Error while removing css:', {error: e, css, sender});
|
||||
this.logger.log('error','debug', '[UwServer::injectCss] Error while removing css:', {error: e, css, sender});
|
||||
}
|
||||
}
|
||||
|
||||
async replaceCss(oldCss, newCss, sender) {
|
||||
if (oldCss !== newCss) {
|
||||
this.removeCss(oldCss, sender);
|
||||
this.injectCss(newCss, sender);
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
extractHostname(url){
|
||||
var hostname;
|
||||
@ -208,21 +206,20 @@ export default class UWServer {
|
||||
}
|
||||
|
||||
this.currentSite = this.extractHostname(tab.url);
|
||||
this.logger.info('onTabSwitched', 'user switched tab. New site:', this.currentSite);
|
||||
this.logger.log('info', 'debug', '[UwServer::onTabSwitched] user switched tab. New site:', this.currentSite);
|
||||
} catch(e) {
|
||||
this.logger.info('onTabSwitched', 'there was a problem getting current site:', e)
|
||||
this.logger.log('error', 'debug', '[UwServer::onTabSwitched] there was a problem getting currnet site:', e)
|
||||
}
|
||||
|
||||
this.selectedSubitem = {
|
||||
'siteSettings': undefined,
|
||||
'videoSettings': undefined,
|
||||
}
|
||||
|
||||
//TODO: change extension icon based on whether there's any videos on current page
|
||||
}
|
||||
|
||||
registerVideo(sender) {
|
||||
this.logger.info('registerVideo', 'Registering video.\nsender:', sender);
|
||||
this.logger.log('info', 'comms', '[UWServer::registerVideo] Registering video.\nsender:', sender);
|
||||
|
||||
const tabHostname = this.extractHostname(sender.tab.url);
|
||||
const frameHostname = this.extractHostname(sender.url);
|
||||
@ -256,11 +253,11 @@ export default class UWServer {
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.info('registerVideo', 'Video registered. current videoTabs:', this.videoTabs);
|
||||
this.logger.log('info', 'comms', '[UWServer::registerVideo] Video registered. current videoTabs:', this.videoTabs);
|
||||
}
|
||||
|
||||
unregisterVideo(sender) {
|
||||
this.logger.info('unregisterVideo', 'Unregistering video.\nsender:', sender);
|
||||
this.logger.log('info', 'comms', '[UwServer::unregisterVideo] Unregistering video.\nsender:', sender);
|
||||
if (this.videoTabs[sender.tab.id]) {
|
||||
if ( Object.keys(this.videoTabs[sender.tab.id].frames).length <= 1) {
|
||||
delete this.videoTabs[sender.tab.id]
|
||||
@ -270,33 +267,20 @@ export default class UWServer {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.logger.info('unregisterVideo', 'Video has been unregistered. Current videoTabs:', this.videoTabs);
|
||||
this.logger.log('info', 'comms', '[UwServer::unregisterVideo] Video has been unregistered. Current videoTabs:', this.videoTabs);
|
||||
}
|
||||
|
||||
setSelectedTab(menu, subitem) {
|
||||
this.logger.info('setSelectedTab', 'saving selected tab for', menu, ':', subitem);
|
||||
this.logger.log('info', 'comms', '[UwServer::setSelectedTab] saving selected tab for', menu, ':', subitem);
|
||||
this.selectedSubitem[menu] = subitem;
|
||||
}
|
||||
|
||||
async getCurrentSite() {
|
||||
this.logger.info('getCurrentSite', 'received get-current-site ...');
|
||||
|
||||
const site = await this.getVideoTab();
|
||||
|
||||
// Don't propagate 'INVALID SITE' to the popup.
|
||||
if (site.host === 'INVALID SITE') {
|
||||
this.logger.info('getCurrentSite', 'Host is not valid — no info for current tab.');
|
||||
return;
|
||||
}
|
||||
|
||||
const tabHostname = await this.getCurrentTabHostname();
|
||||
this.logger.info('getCurrentSite', 'Returning data:', {site, tabHostname});
|
||||
|
||||
this.eventBus.send(
|
||||
'set-current-site',
|
||||
{
|
||||
site,
|
||||
tabHostname,
|
||||
site: await this.getVideoTab(),
|
||||
tabHostname: await this.getCurrentTabHostname(),
|
||||
},
|
||||
{
|
||||
comms: {
|
||||
@ -321,12 +305,9 @@ export default class UWServer {
|
||||
return {
|
||||
host: 'INVALID SITE',
|
||||
frames: [],
|
||||
hostnames: [],
|
||||
}
|
||||
}
|
||||
|
||||
const hostnames = await this.comms.listUniqueFrameHosts();
|
||||
|
||||
if (this.videoTabs[ctab.id]) {
|
||||
// if video is older than PageInfo's video rescan period (+ 4000ms of grace),
|
||||
// we clean it up from videoTabs[tabId].frames array.
|
||||
@ -349,7 +330,6 @@ export default class UWServer {
|
||||
return {
|
||||
...this.videoTabs[ctab.id],
|
||||
host: this.extractHostname(ctab.url),
|
||||
hostnames,
|
||||
selected: this.selectedSubitem
|
||||
};
|
||||
}
|
||||
@ -359,8 +339,7 @@ export default class UWServer {
|
||||
return {
|
||||
host: this.extractHostname(ctab.url),
|
||||
frames: [],
|
||||
hostnames,
|
||||
selected: this.selectedSubitem,
|
||||
selected: this.selectedSubitem
|
||||
}
|
||||
}
|
||||
|
||||
@ -368,15 +347,12 @@ export default class UWServer {
|
||||
const activeTab = await this.activeTab;
|
||||
|
||||
if (!activeTab || activeTab.length < 1) {
|
||||
this.logger.log('warn', 'comms', 'There is no active tab for some reason. activeTab:', activeTab);
|
||||
return null;
|
||||
}
|
||||
|
||||
const url = activeTab[0].url;
|
||||
|
||||
if (!url) {
|
||||
console.log('no URL for active tab:', activeTab[0].url);
|
||||
}
|
||||
|
||||
var hostname;
|
||||
|
||||
if (url.indexOf("://") > -1) { //find & remove protocol (http, ftp, etc.) and get hostname
|
||||
|
@ -8,239 +8,202 @@ import SettingsInterface from '../../common/interfaces/SettingsInterface';
|
||||
import { _cp } from '../../common/js/utils';
|
||||
import CropModePersistence from '../../common/enums/CropModePersistence.enum';
|
||||
import AspectRatioType from '../../common/enums/AspectRatioType.enum';
|
||||
import { update } from 'lodash';
|
||||
|
||||
const ExtensionConfPatch = Object.freeze([
|
||||
const ExtensionConfPatch = [
|
||||
{
|
||||
forVersion: '6.2.4',
|
||||
forVersion: '6.1.1',
|
||||
updateFn: (userOptions: SettingsInterface, defaultOptions) => {
|
||||
for (const site in userOptions.sites) {
|
||||
userOptions.sites[site].enableUI = {
|
||||
fullscreen: ExtensionMode.Default,
|
||||
theater: ExtensionMode.Default,
|
||||
normal: ExtensionMode.Default,
|
||||
// add new commands
|
||||
userOptions.commands = defaultOptions.commands;
|
||||
userOptions.actions = defaultOptions.actions;
|
||||
}
|
||||
}, {
|
||||
// NOTE - when releasing shit, ensure ALL alpha migrations are combined together in one function
|
||||
forVersion: '6.1.2',
|
||||
updateFn: (userOptions, defaultOptions) => {
|
||||
userOptions.commands = defaultOptions.commands;
|
||||
|
||||
// migrates old settings regarding whether extension is enabled or not
|
||||
const copyEnabled = (site) => {
|
||||
userOptions.sites[site].enable = {
|
||||
fullscreen: userOptions.sites[site].mode,
|
||||
theater: userOptions.sites[site].mode,
|
||||
normal: ExtensionMode.Disabled
|
||||
};
|
||||
userOptions.sites[site].enableKeyboard = {
|
||||
fullscreen: userOptions.sites[site].keyboardShortcutsEnabled,
|
||||
theater: userOptions.sites[site].keyboardShortcutsEnabled,
|
||||
normal: ExtensionMode.Disabled
|
||||
};
|
||||
userOptions.sites[site].enableAard = {
|
||||
fullscreen: userOptions.sites[site].autoar,
|
||||
theater: userOptions.sites[site].autoar,
|
||||
normal: ExtensionMode.Disabled
|
||||
};
|
||||
|
||||
userOptions.sites[site].stretchModePersistence = userOptions.sites[site].cropModePersistence;
|
||||
|
||||
// remove old options
|
||||
delete userOptions.sites[site].mode;
|
||||
delete userOptions.sites[site].keyboardShortcutsEnabled;
|
||||
delete userOptions.sites[site].autoar;
|
||||
}
|
||||
|
||||
// globals get carried over before other sites:
|
||||
copyEnabled('@global');
|
||||
|
||||
// we make another guess about a new option we just added
|
||||
|
||||
|
||||
for (const key in userOptions.sites) {
|
||||
// we already had this
|
||||
if (key === '@global') {
|
||||
continue;
|
||||
}
|
||||
|
||||
copyEnabled(key);
|
||||
|
||||
userOptions.sites[key].DOMConfig = _cp(defaultOptions.sites[key].DOMConfig)
|
||||
|
||||
// convert old site.DOM to site.DOMConfig[]
|
||||
if (userOptions.sites[key].type === 'user-defined') {
|
||||
const DOM = userOptions.sites[key].DOM;
|
||||
if (DOM) {
|
||||
userOptions.sites[key].DOMConfig['user-defined'] = {
|
||||
type: 'user-1',
|
||||
customCss: DOM?.css,
|
||||
periodicallyRefreshPlayerElement: DOM?.player?.periodicallyRefreshPlayerElement,
|
||||
elements: !(DOM?.player) ? undefined : {
|
||||
player: {
|
||||
manual: DOM?.player?.manual,
|
||||
querySelectors: DOM?.player?.useRelativeAncestor ? undefined : DOM?.player?.querySelectors,
|
||||
index: DOM?.player?.useRelativeAncestor ? DOM?.player?.videoAncestor : undefined,
|
||||
}
|
||||
}
|
||||
}
|
||||
userOptions.sites[key].activeDOMConfig = 'user-1';
|
||||
|
||||
// remove old configuration
|
||||
delete userOptions.sites[key].DOM;
|
||||
}
|
||||
}
|
||||
}
|
||||
userOptions.sites['@global'].enableUI = {
|
||||
fullscreen: userOptions.ui.inPlayer.enabled ? ExtensionMode.Enabled : ExtensionMode.Disabled,
|
||||
theater: ExtensionMode.Enabled,
|
||||
normal: (userOptions.ui.inPlayer.enabled && !userOptions.ui.inPlayer.enabledFullscreenOnly) ? ExtensionMode.Enabled : ExtensionMode.Disabled
|
||||
}
|
||||
userOptions.sites['@empty'].enableUI = {
|
||||
fullscreen: ExtensionMode.Default,
|
||||
theater: ExtensionMode.Default,
|
||||
normal: ExtensionMode.Default,
|
||||
}
|
||||
}
|
||||
}, {
|
||||
forVersion: '6.2.6',
|
||||
forVersion: '6.0.3',
|
||||
updateFn: (userOptions: SettingsInterface, defaultOptions) => {
|
||||
delete (userOptions as any).sites['@global'].persistOption;
|
||||
delete (userOptions as any).sites['@empty'].persistOption;
|
||||
|
||||
userOptions.sites['@global'].persistCSA = CropModePersistence.Disabled;
|
||||
userOptions.sites['@empty'].persistCSA = CropModePersistence.Disabled;
|
||||
}
|
||||
}, {
|
||||
forVersion: '6.0.4',
|
||||
updateFn: (userOptions: SettingsInterface, defaultOptions) => {
|
||||
|
||||
console.warn('[ultrawidify] STARTING SETTINGS MIGRATION TO 6.2.6');
|
||||
|
||||
if (!userOptions.commands) {
|
||||
userOptions.commands = {
|
||||
zoom: [],
|
||||
crop: [],
|
||||
stretch: [],
|
||||
pan: [],
|
||||
internal: []
|
||||
// deprecated much?
|
||||
userOptions.actions.push({
|
||||
name: 'Cycle aspect ratio',
|
||||
label: 'Cycle',
|
||||
cmd: [{
|
||||
action: 'set-ar',
|
||||
arg: AspectRatioType.Cycle
|
||||
}]
|
||||
});
|
||||
// userOptions.commands.crop.push({
|
||||
// action: 'set-ar',
|
||||
// label: 'Cycle',
|
||||
// comment: 'Cycle through crop options',
|
||||
// arguments: {
|
||||
// type: AspectRatioType.Cycle
|
||||
// },
|
||||
// shortcut: {
|
||||
// key: 'c',
|
||||
// code: 'KeyC',
|
||||
// ctrlKey: false,
|
||||
// metaKey: false,
|
||||
// altKey: false,
|
||||
// shiftKey: false,
|
||||
// onKeyUp: true,
|
||||
// onKeyDown: false,
|
||||
// }
|
||||
// });
|
||||
// userOptions.commands.crop.push({
|
||||
// action: 'set-ar',
|
||||
// label: '32:9',
|
||||
// comment: 'Crop for 32:9 aspect ratio',
|
||||
// arguments: {
|
||||
// type: AspectRatioType.Fixed,
|
||||
// ratio: 3.56
|
||||
// },
|
||||
// })
|
||||
}
|
||||
}, {
|
||||
forVersion: '6.1.5',
|
||||
updateFn: (userOptions: SettingsInterface, defaultOptions) => {
|
||||
if (!userOptions.sites['@global'].defaults.alignment || !userOptions.sites['@global'].defaults.alignment.x || !userOptions.sites['@global'].defaults.alignment.y) {
|
||||
userOptions.sites['@global'].defaults.alignment = {
|
||||
x: VideoAlignmentType.Center,
|
||||
y: VideoAlignmentType.Center
|
||||
};
|
||||
}
|
||||
|
||||
userOptions.commands.zoom = [{
|
||||
action: 'change-zoom',
|
||||
label: 'Zoom +5%',
|
||||
arguments: {
|
||||
zoom: 0.05
|
||||
},
|
||||
shortcut: {
|
||||
key: 'z',
|
||||
code: 'KeyY',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
onKeyUp: true,
|
||||
onKeyDown: false,
|
||||
},
|
||||
internalOnly: true,
|
||||
actionId: 'change-zoom-10in'
|
||||
}, {
|
||||
action: 'change-zoom',
|
||||
label: 'Zoom -5%',
|
||||
arguments: {
|
||||
zoom: -0.05
|
||||
},
|
||||
shortcut: {
|
||||
key: 'u',
|
||||
code: 'KeyU',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: false,
|
||||
onKeyUp: true,
|
||||
onKeyDown: false,
|
||||
},
|
||||
internalOnly: true,
|
||||
actionId: 'change-zoom-10out'
|
||||
}, {
|
||||
action: 'set-zoom',
|
||||
label: 'Reset zoom',
|
||||
arguments: {
|
||||
zoom: 1,
|
||||
},
|
||||
internalOnly: true,
|
||||
actionId: 'set-zoom-reset'
|
||||
}];
|
||||
|
||||
delete (userOptions as any).actions;
|
||||
|
||||
userOptions.dev = {
|
||||
loadFromSnapshot: false
|
||||
};
|
||||
userOptions.ui.dev = {
|
||||
aardDebugOverlay: {
|
||||
showOnStartup: false,
|
||||
showDetectionDetails: true
|
||||
}
|
||||
userOptions.sites['@empty'].defaults.alignment = {x: VideoAlignmentType.Default, y: VideoAlignmentType.Default};
|
||||
}
|
||||
}, {
|
||||
forVersion: '6.1.1-6',
|
||||
updateFn: (userOptions: SettingsInterface, defaultOptions) => {
|
||||
for (const site in userOptions.sites) {
|
||||
userOptions.sites[site].defaultType = userOptions.sites[site].type as any;
|
||||
}
|
||||
|
||||
const newZoomActions = [{
|
||||
action: 'set-zoom',
|
||||
label: 'Reset zoom',
|
||||
shortcut: {
|
||||
key: 'r',
|
||||
code: 'KeyR',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: true,
|
||||
onKeyUp: true,
|
||||
onKeyDown: false,
|
||||
},
|
||||
arguments: {
|
||||
zoom: 1
|
||||
},
|
||||
internalOnly: true,
|
||||
actionId: 'set-zoom-reset'
|
||||
}, {
|
||||
action: 'set-ar-zoom',
|
||||
label: 'Automatic',
|
||||
comment: 'Automatically detect aspect ratio and zoom accordingly',
|
||||
arguments: {
|
||||
type: AspectRatioType.Automatic
|
||||
},
|
||||
shortcut: {
|
||||
key: 'a',
|
||||
code: 'KeyA',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: true,
|
||||
onKeyUp: true,
|
||||
onKeyDown: false,
|
||||
}
|
||||
}, {
|
||||
action: 'set-ar-zoom',
|
||||
label: 'Cycle',
|
||||
comment: 'Cycle through zoom options',
|
||||
arguments: {
|
||||
type: AspectRatioType.Cycle
|
||||
},
|
||||
shortcut: {
|
||||
key: 'c',
|
||||
code: 'KeyC',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: true,
|
||||
onKeyUp: true,
|
||||
onKeyDown: false,
|
||||
}
|
||||
}, {
|
||||
action: 'set-ar-zoom',
|
||||
label: '21:9',
|
||||
comment: 'Zoom for 21:9 aspect ratio (1:2.39)',
|
||||
arguments: {
|
||||
type: AspectRatioType.Fixed,
|
||||
ratio: 2.39
|
||||
},
|
||||
shortcut: {
|
||||
key: 'd',
|
||||
code: 'KeyD',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: true,
|
||||
onKeyUp: false,
|
||||
onKeyDown: true,
|
||||
}
|
||||
}, {
|
||||
action: 'set-ar-zoom',
|
||||
label: '18:9',
|
||||
comment: 'Zoom for 18:9 aspect ratio (1:2)',
|
||||
arguments: {
|
||||
type: AspectRatioType.Fixed,
|
||||
ratio: 1.78
|
||||
},
|
||||
shortcut: {
|
||||
key: 's',
|
||||
code: 'KeyS',
|
||||
ctrlKey: false,
|
||||
metaKey: false,
|
||||
altKey: false,
|
||||
shiftKey: true,
|
||||
onKeyUp: false,
|
||||
onKeyDown: true,
|
||||
}
|
||||
}, {
|
||||
action: 'set-ar-zoom',
|
||||
label: '32:9',
|
||||
comment: 'Zoom for 32:9 aspect ratio',
|
||||
arguments: {
|
||||
type: AspectRatioType.Fixed,
|
||||
ratio: 3.56
|
||||
},
|
||||
}];
|
||||
|
||||
const compareShortcuts = (a: any, b: any) => {
|
||||
if (!a || !b) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return a.key === b.key && b.code === b.code && a.ctrlKey == b.ctrlKey && a.shiftKey == b.shiftKey && a.metaKey == a.metaKey && a.altKey == b.altKey;
|
||||
userOptions.sites['@global'].defaultType = 'unknown';
|
||||
userOptions.sites['@empty'].defaultType = 'modified';
|
||||
}
|
||||
}, {
|
||||
forVersion: '6.1.2-0',
|
||||
updateFn: (userOptions: SettingsInterface, defaultOptions) => {
|
||||
// remove custom CSS, as it is no longer needed
|
||||
for (const site in userOptions.sites) {
|
||||
for (const domOption in userOptions.sites[site].DOMConfig)
|
||||
userOptions.sites[site].DOMConfig[domOption].customCss;
|
||||
}
|
||||
|
||||
const hasConflict = (shortcut: any) => {
|
||||
for (const ct in userOptions.commands) {
|
||||
for (const command of userOptions.commands[ct]) {
|
||||
if (compareShortcuts(shortcut, command.shortcut)) {
|
||||
return true;
|
||||
}
|
||||
userOptions.arDetect.aardType = 'auto';
|
||||
userOptions.ui = {
|
||||
inPlayer: {
|
||||
enabled: true, // enable by default on new installs
|
||||
enabledFullscreenOnly: false,
|
||||
minEnabledWidth: 0.75,
|
||||
minEnabledHeight: 0.75,
|
||||
activation: 'player',
|
||||
popupAlignment: 'left',
|
||||
triggerZoneDimensions: {
|
||||
width: 0.5,
|
||||
height: 0.5,
|
||||
offsetX: -50,
|
||||
offsetY: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const zoomAction of newZoomActions) {
|
||||
if (
|
||||
!userOptions.commands.zoom.find(
|
||||
x => x.action === zoomAction.action
|
||||
&& x.arguments?.type === zoomAction.arguments?.type
|
||||
&& x.arguments?.ratio === zoomAction.arguments?.ratio
|
||||
)
|
||||
) {
|
||||
userOptions.commands.zoom.push({
|
||||
...zoomAction,
|
||||
shortcut: hasConflict(zoomAction.shortcut) ? undefined : zoomAction.shortcut
|
||||
});
|
||||
},
|
||||
userOptions.newFeatureTracker['uw6.ui-popup'] = {show: 10};
|
||||
}
|
||||
}, {
|
||||
forVersion: '6.2.1',
|
||||
updateFn: (userOptions: SettingsInterface, defaultOptions) => {
|
||||
userOptions.ui = defaultOptions.ui;
|
||||
userOptions.arDetect = defaultOptions.arDetect;
|
||||
userOptions.newFeatureTracker = defaultOptions.newFeatureTracker;
|
||||
}
|
||||
}, {
|
||||
forVersion: '6.2.3',
|
||||
updateFn: (userOptions: SettingsInterface, defaultOptions) => {
|
||||
for (const site in userOptions.sites) {
|
||||
if (userOptions.sites[site].defaults?.stretch && !userOptions.sites[site].defaults?.stretch.type) {
|
||||
userOptions.sites[site].defaults.stretch = {type: userOptions.sites[site].defaults?.stretch as any as StretchType};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]);
|
||||
];
|
||||
|
||||
|
||||
export default ExtensionConfPatch;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -19,10 +19,6 @@ export interface EventBusContext {
|
||||
frame?: any,
|
||||
sourceFrame?: IframeData
|
||||
forwardTo?: 'all' | 'active' | 'contentScript' | 'server' | 'sameOrigin' | 'popup' | 'all-frames',
|
||||
};
|
||||
borderCrossings?: {
|
||||
commsServer?: boolean,
|
||||
iframe?: boolean,
|
||||
}
|
||||
}
|
||||
|
||||
@ -33,9 +29,6 @@ export default class EventBus {
|
||||
|
||||
private disableTunnel: boolean = false;
|
||||
private popupContext: any = {};
|
||||
|
||||
private iframeForwardingList: {iframe: any, fn: (action, payload, context?) => void}[] = [];
|
||||
|
||||
// private uiUri = window.location.href;
|
||||
|
||||
constructor(options?: {isUWServer?: boolean}) {
|
||||
@ -90,18 +83,6 @@ export default class EventBus {
|
||||
}
|
||||
}
|
||||
|
||||
forwardToIframe(iframe: any, fn: (action: string, payload: any, context?: EventBusContext) => void) {
|
||||
this.cancelIframeForwarding(iframe);
|
||||
this.iframeForwardingList.push({iframe, fn});
|
||||
}
|
||||
|
||||
cancelIframeForwarding(iframe: any) {
|
||||
const existingForwarding = this.iframeForwardingList.findIndex((x: any) => x.iframe === iframe);
|
||||
if (existingForwarding !== -1) {
|
||||
this.iframeForwardingList.splice(existingForwarding, 1);
|
||||
}
|
||||
}
|
||||
|
||||
send(command: string, commandData: any, context?: EventBusContext) {
|
||||
// execute commands we have subscriptions for
|
||||
|
||||
@ -113,27 +94,8 @@ export default class EventBus {
|
||||
|
||||
// preventing messages from flowing back to their original senders is
|
||||
// CommsServer's job. EventBus does not have enough data for this decision.
|
||||
// We do, however, have enough data to prevent backflow of messages that
|
||||
// crossed CommsServer once already.
|
||||
if (this.comms && context?.origin !== CommsOrigin.Server && !context?.borderCrossings?.commsServer) {
|
||||
if (this.comms) {
|
||||
this.comms.sendMessage({command, config: commandData, context}, context);
|
||||
};
|
||||
|
||||
// call forwarding functions if they exist
|
||||
if (!context?.borderCrossings?.iframe) {
|
||||
for (const forwarding of this.iframeForwardingList) {
|
||||
forwarding.fn(
|
||||
command,
|
||||
commandData,
|
||||
{
|
||||
...context,
|
||||
borderCrossings: {
|
||||
...context?.borderCrossings,
|
||||
iframe: true
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (context?.stopPropagation) {
|
||||
|
@ -1,33 +1,22 @@
|
||||
import Debug from '../conf/Debug';
|
||||
import currentBrowser from '../conf/BrowserDetect';
|
||||
import ExtensionConf from '../conf/ExtensionConf';
|
||||
import ExtensionMode from '../../common/enums/ExtensionMode.enum';
|
||||
import ObjectCopy from './ObjectCopy';
|
||||
import StretchType from '../../common/enums/StretchType.enum';
|
||||
import VideoAlignmentType from '../../common/enums/VideoAlignmentType.enum';
|
||||
import ExtensionConfPatch from '../conf/ExtConfPatches';
|
||||
import CropModePersistence from '../../common/enums/CropModePersistence.enum';
|
||||
import BrowserDetect from '../conf/BrowserDetect';
|
||||
import Logger from './Logger';
|
||||
import SettingsInterface from '../../common/interfaces/SettingsInterface';
|
||||
import AspectRatioType from '../../common/enums/AspectRatioType.enum';
|
||||
import { SiteSettings } from './settings/SiteSettings';
|
||||
import { SettingsSnapshotManager } from './settings/SettingsSnapshotManager';
|
||||
import { ComponentLogger } from './logging/ComponentLogger';
|
||||
import { LogAggregator } from './logging/LogAggregator';
|
||||
|
||||
if(process.env.CHANNEL !== 'stable'){
|
||||
console.info("Loading Settings");
|
||||
}
|
||||
|
||||
interface SettingsOptions {
|
||||
logAggregator: LogAggregator,
|
||||
onSettingsChanged?: () => void,
|
||||
afterSettingsSaved?: () => void,
|
||||
activeSettings?: SettingsInterface,
|
||||
}
|
||||
|
||||
interface SetSettingsOptions {
|
||||
forcePreserveVersion?: boolean,
|
||||
}
|
||||
|
||||
const SETTINGS_LOGGER_STYLES = {
|
||||
log: 'color: #81d288',
|
||||
}
|
||||
|
||||
class Settings {
|
||||
//#region flags
|
||||
@ -36,9 +25,7 @@ class Settings {
|
||||
//#endregion
|
||||
|
||||
//#region helper classes
|
||||
|
||||
logAggregator: LogAggregator;
|
||||
logger: ComponentLogger;
|
||||
logger: Logger;
|
||||
//#endregion
|
||||
|
||||
//#region data
|
||||
@ -50,23 +37,17 @@ class Settings {
|
||||
onSettingsChanged: any;
|
||||
afterSettingsSaved: any;
|
||||
|
||||
onChangedCallbacks: (() => void)[] = [];
|
||||
afterSettingsChangedCallbacks: (() => void)[] = [];
|
||||
|
||||
|
||||
public snapshotManager: SettingsSnapshotManager;
|
||||
|
||||
onChangedCallbacks: any[] = [];
|
||||
afterSettingsChangedCallbacks: any[] = [];
|
||||
//#endregion
|
||||
|
||||
constructor(options: SettingsOptions) {
|
||||
constructor(options) {
|
||||
// Options: activeSettings, updateCallback, logger
|
||||
this.logger = options.logAggregator && new ComponentLogger(options.logAggregator, 'Settings', {styles: SETTINGS_LOGGER_STYLES}) || undefined;;
|
||||
this.onSettingsChanged = options.onSettingsChanged;
|
||||
this.afterSettingsSaved = options.afterSettingsSaved;
|
||||
this.active = options.activeSettings ?? undefined;
|
||||
this.logger = options?.logger;
|
||||
this.onSettingsChanged = options?.onSettingsChanged;
|
||||
this.afterSettingsSaved = options?.afterSettingsSaved;
|
||||
this.active = options?.activeSettings ?? undefined;
|
||||
this.default = ExtensionConf;
|
||||
this.snapshotManager = new SettingsSnapshotManager();
|
||||
|
||||
this.default['version'] = this.getExtensionVersion();
|
||||
|
||||
chrome.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)});
|
||||
@ -76,14 +57,14 @@ class Settings {
|
||||
if (!changes.uwSettings) {
|
||||
return;
|
||||
}
|
||||
this.logger?.info('storageOnChange', "Settings have been changed outside of here. Updating active settings. Changes:", changes, "storage area:", area);
|
||||
this.logger?.log('info', 'settings', "[Settings::<storage/on change>] Settings have been changed outside of here. Updating active settings. Changes:", changes, "storage area:", area);
|
||||
// if (changes['uwSettings'] && changes['uwSettings'].newValue) {
|
||||
// this.logger?.log('info', 'settings',"[Settings::<storage/on change>] new settings object:", JSON.parse(changes.uwSettings.newValue));
|
||||
// }
|
||||
const parsedSettings = JSON.parse(changes.uwSettings.newValue);
|
||||
this.setActive(parsedSettings);
|
||||
|
||||
this.logger?.info('storageOnChange', 'Does parsedSettings.preventReload exist?', parsedSettings.preventReload, "Does callback exist?", !!this.onSettingsChanged);
|
||||
this.logger?.log('info', 'debug', 'Does parsedSettings.preventReload exist?', parsedSettings.preventReload, "Does callback exist?", !!this.onSettingsChanged);
|
||||
|
||||
if (!parsedSettings.preventReload) {
|
||||
try {
|
||||
@ -91,23 +72,23 @@ class Settings {
|
||||
try {
|
||||
fn();
|
||||
} catch (e) {
|
||||
this.logger?.warn('storageOnChange', "afterSettingsChanged fallback failed. It's possible that a vue component got destroyed, and this function is nothing more than vestigal remains. It would be nice if we implemented something that allows us to remove callback functions from array, and remove vue callbacks from the callback array when their respective UI component gets destroyed. Or this could be an error with the function itself. IDK, here's the error.", e)
|
||||
this.logger?.log('warn', 'settings', "[Settings] afterSettingsChanged fallback failed. It's possible that a vue component got destroyed, and this function is nothing more than vestigal remains. It would be nice if we implemented something that allows us to remove callback functions from array, and remove vue callbacks from the callback array when their respective UI component gets destroyed. Or this could be an error with the function itself. IDK, here's the error.", e)
|
||||
}
|
||||
}
|
||||
if (this.onSettingsChanged) {
|
||||
this.onSettingsChanged();
|
||||
}
|
||||
|
||||
this.logger?.info('storageOnChange', 'Update callback finished.')
|
||||
this.logger?.log('info', 'settings', '[Settings] Update callback finished.')
|
||||
} catch (e) {
|
||||
this.logger?.error('storageOnChange', "CALLING UPDATE CALLBACK FAILED. Reason:", e)
|
||||
this.logger?.log('error', 'settings', "[Settings] CALLING UPDATE CALLBACK FAILED. Reason:", e)
|
||||
}
|
||||
}
|
||||
for (const fn of this.afterSettingsChangedCallbacks) {
|
||||
try {
|
||||
fn();
|
||||
} catch (e) {
|
||||
this.logger?.warn('storageOnChange', "afterSettingsChanged fallback failed. It's possible that a vue component got destroyed, and this function is nothing more than vestigal remains. It would be nice if we implemented something that allows us to remove callback functions from array, and remove vue callbacks from the callback array when their respective UI component gets destroyed. Or this could be an error with the function itself. IDK, here's the error.", e)
|
||||
this.logger?.log('warn', 'settings', "[Settings] afterSettingsChanged fallback failed. It's possible that a vue component got destroyed, and this function is nothing more than vestigal remains. It would be nice if we implemented something that allows us to remove callback functions from array, and remove vue callbacks from the callback array when their respective UI component gets destroyed. Or this could be an error with the function itself. IDK, here's the error.", e)
|
||||
}
|
||||
}
|
||||
if (this.afterSettingsSaved) {
|
||||
@ -190,75 +171,78 @@ class Settings {
|
||||
return patchesIn.sort( (a, b) => this.compareExtensionVersions(a.forVersion, b.forVersion));
|
||||
}
|
||||
|
||||
private findFirstNecessaryPatch(version) {
|
||||
return ExtensionConfPatch.findIndex(x => this.compareExtensionVersions(x.forVersion, version) > 0);
|
||||
private findFirstNecessaryPatch(version, extconfPatches) {
|
||||
const sorted = this.sortConfPatches(extconfPatches);
|
||||
return sorted.findIndex(x => this.compareExtensionVersions(x.forVersion, version) > 0);
|
||||
}
|
||||
private applySettingsPatches(oldVersion) {
|
||||
let index = this.findFirstNecessaryPatch(oldVersion);
|
||||
|
||||
private applySettingsPatches(oldVersion, patches) {
|
||||
let index = this.findFirstNecessaryPatch(oldVersion, patches);
|
||||
if (index === -1) {
|
||||
this.logger?.info('applySettingsPatches','There are no pending conf patches.');
|
||||
this.logger?.log('info','settings','[Settings::applySettingsPatches] There are no pending conf patches.');
|
||||
return;
|
||||
}
|
||||
|
||||
// save current settings object
|
||||
const currentSettings = this.active;
|
||||
|
||||
this.snapshotManager.createSnapshot(
|
||||
JSON.parse(JSON.stringify(currentSettings)),
|
||||
{
|
||||
label: 'Pre-migration snapshot',
|
||||
isAutomatic: true
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// apply all remaining patches
|
||||
this.logger?.info('applySettingsPatches', `There are ${ExtensionConfPatch.length - index} settings patches to apply`);
|
||||
while (index < ExtensionConfPatch.length) {
|
||||
const updateFn = ExtensionConfPatch[index].updateFn;
|
||||
if (updateFn) {
|
||||
try {
|
||||
updateFn(this.active, this.getDefaultSettings());
|
||||
} catch (e) {
|
||||
this.logger?.error('applySettingsPatches', 'Failed to execute update function. Keeping settings object as-is. Error:', e);
|
||||
}
|
||||
}
|
||||
try {
|
||||
this.logger?.log('info', 'settings', `[Settings::applySettingsPatches] There are ${patches.length - index} settings patches to apply`);
|
||||
while (index < patches.length) {
|
||||
const updateFn = patches[index].updateFn;
|
||||
delete patches[index].forVersion;
|
||||
delete patches[index].updateFn;
|
||||
|
||||
index++;
|
||||
if (Object.keys(patches[index]).length > 0) {
|
||||
ObjectCopy.overwrite(this.active, patches[index]);
|
||||
}
|
||||
if (updateFn) {
|
||||
|
||||
try {
|
||||
updateFn(this.active, this.getDefaultSettings());
|
||||
} catch (e) {
|
||||
this.logger?.log('error', 'settings', '[Settings::applySettingsPatches] Failed to execute update function. Keeping settings object as-is. Error:', e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to upgrade settings.', e);
|
||||
this.setActive(this.getDefaultSettings());
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
|
||||
async init() {
|
||||
let settings = await this.get();
|
||||
|
||||
if (settings?.dev?.loadFromSnapshot) {
|
||||
this.logger?.info('init', 'Dev mode is enabled, Loading settings from snapshot:', settings.dev.loadFromSnapshot);
|
||||
const snapshot = await this.snapshotManager.getSnapshot();
|
||||
if (snapshot) {
|
||||
settings = snapshot.settings;
|
||||
}
|
||||
}
|
||||
|
||||
const settings = await this.get();
|
||||
this.version = this.getExtensionVersion();
|
||||
|
||||
// |—> on first setup, settings is undefined & settings.version is haram
|
||||
// | since new installs ship with updates by default, no patching is
|
||||
// | needed. In this case, we assume we're on the current version
|
||||
const oldVersion = settings?.version ?? this.version;
|
||||
const oldVersion = (settings && settings.version) || this.version;
|
||||
|
||||
if (settings) {
|
||||
this.logger?.info('init', "Configuration fetched from storage:", settings,
|
||||
this.logger?.log('info', 'settings', "[Settings::init] Configuration fetched from storage:", settings,
|
||||
"\nlast saved with:", settings.version,
|
||||
"\ncurrent version:", this.version
|
||||
);
|
||||
}
|
||||
|
||||
// if (Debug.flushStoredSettings) {
|
||||
// this.logger?.log('info', 'settings', "%c[Settings::init] Debug.flushStoredSettings is true. Using default settings", "background: #d00; color: #ffd");
|
||||
// Debug.flushStoredSettings = false; // don't do it again this session
|
||||
// this.active = this.getDefaultSettings();
|
||||
// this.active.version = this.version;
|
||||
// this.set(this.active);
|
||||
// return this.active;
|
||||
// }
|
||||
|
||||
// if there's no settings saved, return default settings.
|
||||
if(! settings || (Object.keys(settings).length === 0 && settings.constructor === Object)) {
|
||||
this.logger?.info(
|
||||
'init',
|
||||
'settings don\'t exist. Using defaults.\n#keys:',
|
||||
this.logger?.log(
|
||||
'info',
|
||||
'settings',
|
||||
'[Settings::init] settings don\'t exist. Using defaults.\n#keys:',
|
||||
settings ? Object.keys(settings).length : 0,
|
||||
'\nsettings:',
|
||||
settings
|
||||
@ -266,7 +250,6 @@ class Settings {
|
||||
this.active = this.getDefaultSettings();
|
||||
this.active.version = this.version;
|
||||
await this.save();
|
||||
|
||||
return this.active;
|
||||
}
|
||||
|
||||
@ -283,7 +266,7 @@ class Settings {
|
||||
|
||||
// check if extension has been updated. If not, return settings as they were retrieved
|
||||
if (this.active.version === this.version) {
|
||||
this.logger?.info('init', "extension was saved with current version of ultrawidify. Returning object as-is.");
|
||||
this.logger?.log('info', 'settings', "[Settings::init] extension was saved with current version of ultrawidify. Returning object as-is.");
|
||||
return this.active;
|
||||
}
|
||||
|
||||
@ -294,14 +277,14 @@ class Settings {
|
||||
// if extension has been updated, update existing settings with any options added in the
|
||||
// new version. In addition to that, we remove old keys that are no longer used.
|
||||
const patched = ObjectCopy.addNew(settings, this.default);
|
||||
this.logger?.info('init',"Results from ObjectCopy.addNew()?", patched, "\n\nSettings from storage", settings, "\ndefault?", this.default);
|
||||
this.logger?.log('info', 'settings',"[Settings.init] Results from ObjectCopy.addNew()?", patched, "\n\nSettings from storage", settings, "\ndefault?", this.default);
|
||||
|
||||
if (patched) {
|
||||
this.active = patched;
|
||||
}
|
||||
|
||||
// in case settings in previous version contained a fucky wucky, we overwrite existing settings with a patch
|
||||
this.applySettingsPatches(oldVersion);
|
||||
this.applySettingsPatches(oldVersion, ExtensionConfPatch);
|
||||
|
||||
// set 'whatsNewChecked' flag to false when updating, always
|
||||
this.active.whatsNewChecked = false;
|
||||
@ -312,25 +295,26 @@ class Settings {
|
||||
return this.active;
|
||||
}
|
||||
|
||||
async get(): Promise<SettingsInterface | undefined> {
|
||||
async get() {
|
||||
let ret;
|
||||
|
||||
ret = await chrome.storage.local.get('uwSettings');
|
||||
|
||||
this.logger?.info('get', 'Got settings:', ret && ret.uwSettings && JSON.parse(ret.uwSettings));
|
||||
this.logger?.log('info', 'settings', 'Got settings:', ret && ret.uwSettings && JSON.parse(ret.uwSettings));
|
||||
|
||||
try {
|
||||
return JSON.parse(ret.uwSettings) as SettingsInterface;
|
||||
return JSON.parse(ret.uwSettings);
|
||||
} catch(e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
async set(extensionConf, options?: SetSettingsOptions) {
|
||||
async set(extensionConf, options?) {
|
||||
if (!options || !options.forcePreserveVersion) {
|
||||
extensionConf.version = this.version;
|
||||
}
|
||||
this.logger?.info('set', "setting new settings:", extensionConf)
|
||||
|
||||
this.logger?.log('info', 'settings', "[Settings::set] setting new settings:", extensionConf)
|
||||
|
||||
return chrome.storage.local.set( {'uwSettings': JSON.stringify(extensionConf)});
|
||||
}
|
||||
@ -373,20 +357,18 @@ class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
async save(options?: SetSettingsOptions) {
|
||||
async save(options?) {
|
||||
if (Debug.debug && Debug.storage) {
|
||||
console.log("[Settings::save] Saving active settings — save options", options, "; settings:", this.active);
|
||||
console.log("[Settings::save] Saving active settings:", this.active);
|
||||
}
|
||||
this.active.preventReload = undefined;
|
||||
this.active.lastModified = new Date();
|
||||
await this.set(this.active, options);
|
||||
}
|
||||
|
||||
|
||||
async saveWithoutReload(options?: SetSettingsOptions) {
|
||||
async saveWithoutReload() {
|
||||
this.active.preventReload = true;
|
||||
this.active.lastModified = new Date();
|
||||
await this.set(this.active, options);
|
||||
await this.set(this.active);
|
||||
}
|
||||
|
||||
async rollback() {
|
||||
@ -420,15 +402,9 @@ class Settings {
|
||||
listenOnChange(fn: () => void): void {
|
||||
this.onChangedCallbacks.push(fn);
|
||||
}
|
||||
removeOnChangeListener(fn: () => void): void {
|
||||
this.onChangedCallbacks = this.afterSettingsChangedCallbacks.filter(x => x !== fn);
|
||||
}
|
||||
listenAfterChange(fn: () => void): void {
|
||||
this.afterSettingsChangedCallbacks.push(fn);
|
||||
}
|
||||
removeAfterChangeListener(fn: () => void): void {
|
||||
this.afterSettingsChangedCallbacks = this.afterSettingsChangedCallbacks.filter(x => x !== fn);
|
||||
}
|
||||
}
|
||||
|
||||
export default Settings;
|
||||
|
@ -1,24 +1,17 @@
|
||||
import AspectRatioType from '@src/common/enums/AspectRatioType.enum';
|
||||
import ExtensionMode from '@src/common/enums/ExtensionMode.enum';
|
||||
import { ArVariant } from '@src/common/interfaces/ArInterface';
|
||||
import { ExtensionEnvironment } from '@src/common/interfaces/SettingsInterface';
|
||||
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
||||
import EventBus from '../EventBus';
|
||||
import Logger from '../Logger';
|
||||
import Settings from '../Settings';
|
||||
import { SiteSettings } from '../settings/SiteSettings';
|
||||
import VideoData from '../video-data/VideoData';
|
||||
import { AardDebugUi } from './AardDebugUi';
|
||||
import { AardTimer } from './AardTimers';
|
||||
import { Corner } from './enums/corner.enum';
|
||||
import { VideoPlaybackState } from './enums/video-playback-state.enum';
|
||||
import { FallbackCanvas } from './gl/FallbackCanvas';
|
||||
import { GlCanvas } from './gl/GlCanvas';
|
||||
import { GlDebugCanvas, GlDebugType } from './gl/GlDebugCanvas';
|
||||
import { AardCanvasStore } from './interfaces/aard-canvas-store.interface';
|
||||
import { AardDetectionSample, generateSampleArray, resetSamples } from './interfaces/aard-detection-sample.interface';
|
||||
import { AardStatus, initAardStatus } from './interfaces/aard-status.interface';
|
||||
import { AardTestResults, initAardTestResults, resetAardTestResults, resetGuardLine } from './interfaces/aard-test-results.interface';
|
||||
import { AardTimers, initAardTimers } from './interfaces/aard-timers.interface';
|
||||
import { ComponentLogger } from '../logging/ComponentLogger';
|
||||
|
||||
|
||||
/**
|
||||
@ -220,31 +213,15 @@ import { ComponentLogger } from '../logging/ComponentLogger';
|
||||
*
|
||||
*/
|
||||
export class Aard {
|
||||
|
||||
//#region configuration parameters
|
||||
private logger: ComponentLogger;
|
||||
private logger: Logger;
|
||||
private videoData: VideoData;
|
||||
private settings: Settings;
|
||||
private siteSettings: SiteSettings;
|
||||
private eventBus: EventBus;
|
||||
private arid: string;
|
||||
private arVariant: ArVariant;
|
||||
|
||||
private eventBusCommands = {
|
||||
'uw-environment-change': {
|
||||
function: (newEnvironment: ExtensionEnvironment) => {
|
||||
console.log('received extension environment:', newEnvironment, 'player env:', this.videoData?.player?.environment);
|
||||
this.startCheck();
|
||||
}
|
||||
},
|
||||
'aard-enable-debug': {
|
||||
function: (enabled: boolean) => {
|
||||
if (enabled) {
|
||||
this.showDebugCanvas();
|
||||
} else {
|
||||
this.hideDebugCanvas();
|
||||
}
|
||||
}
|
||||
}
|
||||
// 'get-aard-timing': {
|
||||
// function: () => this.handlePerformanceDataRequest()
|
||||
// }
|
||||
@ -262,15 +239,9 @@ export class Aard {
|
||||
private fallbackReason: any;
|
||||
private canvasStore: AardCanvasStore;
|
||||
private testResults: AardTestResults;
|
||||
private verticalTestResults: AardTestResults;
|
||||
private canvasSamples: AardDetectionSample;
|
||||
|
||||
|
||||
private forceFullRecheck: boolean = true;
|
||||
|
||||
private debugConfig: any = {};
|
||||
private timer: AardTimer;
|
||||
private lastAnimationFrameTime: number = Infinity;
|
||||
//#endregion
|
||||
|
||||
//#region getters
|
||||
@ -292,11 +263,10 @@ export class Aard {
|
||||
|
||||
//#region lifecycle
|
||||
constructor(videoData: VideoData){
|
||||
this.logger = new ComponentLogger(videoData.logAggregator, 'Aard', {});
|
||||
this.logger = videoData.logger;
|
||||
this.videoData = videoData;
|
||||
this.video = videoData.video;
|
||||
this.settings = videoData.settings;
|
||||
this.siteSettings = videoData.siteSettings;
|
||||
this.eventBus = videoData.eventBus;
|
||||
|
||||
this.eventBus.subscribeMulti(this.eventBusCommands, this);
|
||||
@ -304,9 +274,8 @@ export class Aard {
|
||||
this.arid = (Math.random()*100).toFixed();
|
||||
|
||||
// we can tick manually, for debugging
|
||||
this.logger.log('ctor', `creating new ArDetector. arid: ${this.arid}`);
|
||||
this.logger.log('info', 'init', `[ArDetector::ctor] creating new ArDetector. arid: ${this.arid}`);
|
||||
|
||||
this.timer = new AardTimer();
|
||||
this.init();
|
||||
}
|
||||
|
||||
@ -315,6 +284,7 @@ export class Aard {
|
||||
* This method should only ever be called from constructor.
|
||||
*/
|
||||
private init() {
|
||||
|
||||
this.canvasStore = {
|
||||
main: this.createCanvas('main-gl')
|
||||
};
|
||||
@ -331,31 +301,16 @@ export class Aard {
|
||||
),
|
||||
};
|
||||
|
||||
|
||||
// try {
|
||||
// this.showDebugCanvas();
|
||||
// } catch (e) {
|
||||
// console.error('FALIED TO CREATE DEBUGG CANVAS', e);
|
||||
// }
|
||||
|
||||
try {
|
||||
if (this.settings.active.ui.dev?.aardDebugOverlay?.showOnStartup) {
|
||||
this.showDebugCanvas();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`[uw::aard] failed to create debug UI:`, e);
|
||||
}
|
||||
|
||||
this.startCheck();
|
||||
this.start();
|
||||
}
|
||||
|
||||
private createCanvas(canvasId: string, canvasType?: 'webgl' | 'legacy') {
|
||||
private createCanvas(canvasId: string, canvasType?: 'webgl' | 'fallback') {
|
||||
if (canvasType) {
|
||||
if (canvasType === this.settings.active.arDetect.aardType || this.settings.active.arDetect.aardType === 'auto') {
|
||||
if (canvasType === 'webgl') {
|
||||
return new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'});
|
||||
} else if (canvasType === 'legacy') {
|
||||
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-legacy'});
|
||||
} else if (canvasType === 'fallback') {
|
||||
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-fallback'});
|
||||
} else {
|
||||
// TODO: throw error
|
||||
}
|
||||
@ -370,66 +325,22 @@ export class Aard {
|
||||
return new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'});
|
||||
} catch (e) {
|
||||
if (this.settings.active.arDetect.aardType !== 'webgl') {
|
||||
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-legacy'});
|
||||
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-fallback'});
|
||||
}
|
||||
this.logger.error('createCanvas', 'could not create webgl canvas:', e);
|
||||
console.error('[ultrawidify|Aard::createCanvas] could not create webgl canvas:', e);
|
||||
this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: {webglError: true}});
|
||||
throw e;
|
||||
}
|
||||
} else if (this.settings.active.arDetect.aardType === 'legacy') {
|
||||
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-legacy'});
|
||||
return new FallbackCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-fallback'});
|
||||
} else {
|
||||
this.logger.error('createCanvas', 'invalid value in settings.arDetect.aardType:', this.settings.active.arDetect.aardType);
|
||||
console.error('[ultrawidify|Aard::createCanvas] invalid value in settings.arDetect.aardType:', this.settings.active.arDetect.aardType);
|
||||
this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: {invalidSettings: true}});
|
||||
throw 'AARD_INVALID_SETTINGS';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and shows debug canvas
|
||||
* @param canvasId
|
||||
*/
|
||||
private showDebugCanvas() {
|
||||
if (!this.canvasStore.debug) {
|
||||
this.canvasStore.debug = new GlDebugCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'uw-debug-gl'});
|
||||
}
|
||||
this.canvasStore.debug.enableFx();
|
||||
if (!this.debugConfig.debugUi) {
|
||||
this.debugConfig.debugUi = new AardDebugUi(this);
|
||||
this.debugConfig.debugUi.initContainer();
|
||||
this.debugConfig.debugUi.attachCanvases(this.canvasStore.main.canvas, this.canvasStore.debug.canvas);
|
||||
|
||||
// if we don't draw a dummy frame from _real_ sources, we can't update buffer later
|
||||
this.canvasStore.debug.drawVideoFrame(this.canvasStore.main.canvas);
|
||||
}
|
||||
}
|
||||
|
||||
private hideDebugCanvas() {
|
||||
if (this.debugConfig.debugUi) {
|
||||
this.debugConfig?.debugUi.destroyContainer();
|
||||
this.debugConfig.debugUi = undefined;
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* Checks whether autodetection can run
|
||||
*/
|
||||
startCheck(arVariant?: ArVariant) {
|
||||
this.arVariant = arVariant;
|
||||
|
||||
if (!this.videoData.player) {
|
||||
// console.warn('Player not detected!');
|
||||
// console.log('--- video data: ---\n', this.videoData);
|
||||
return;
|
||||
}
|
||||
if (this.siteSettings.data.enableAard[this.videoData.player.environment] === ExtensionMode.Enabled) {
|
||||
this.start();
|
||||
} else {
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts autodetection loop.
|
||||
*/
|
||||
@ -442,7 +353,6 @@ export class Aard {
|
||||
|
||||
// do full reset of test samples
|
||||
this.testResults = initAardTestResults(this.settings.active.arDetect);
|
||||
this.verticalTestResults = initAardTestResults(this.settings.active.arDetect);
|
||||
|
||||
if (this.animationFrame) {
|
||||
window.cancelAnimationFrame(this.animationFrame);
|
||||
@ -456,14 +366,8 @@ export class Aard {
|
||||
* Runs autodetection ONCE.
|
||||
* If autodetection loop is running, this will also stop autodetection loop.
|
||||
*/
|
||||
step(options?: {noCache?: boolean}) {
|
||||
step() {
|
||||
this.stop();
|
||||
|
||||
if (options?.noCache) {
|
||||
this.testResults = initAardTestResults(this.settings.active.arDetect);
|
||||
this.verticalTestResults = initAardTestResults(this.settings.active.arDetect);
|
||||
}
|
||||
|
||||
this.main();
|
||||
}
|
||||
|
||||
@ -525,20 +429,14 @@ export class Aard {
|
||||
*/
|
||||
private async main() {
|
||||
try {
|
||||
this.timer.next();
|
||||
|
||||
let imageData: Uint8Array;
|
||||
this.timer.current.start = performance.now();
|
||||
|
||||
// We abuse a do-while loop to eat our cake (get early returns)
|
||||
// and have it, too (if we return early, we still execute code
|
||||
// at the end of this function)
|
||||
do {
|
||||
imageData = await new Promise<Uint8Array>(
|
||||
const imageData = await new Promise<Uint8Array>(
|
||||
resolve => {
|
||||
try {
|
||||
this.canvasStore.main.drawVideoFrame(this.video);
|
||||
this.timer.current.draw = performance.now() - this.timer.current.start;
|
||||
resolve(this.canvasStore.main.getImageData());
|
||||
} catch (e) {
|
||||
if (e.name === 'SecurityError') {
|
||||
@ -556,7 +454,7 @@ export class Aard {
|
||||
} else {
|
||||
if (this.settings.active.arDetect.aardType === 'auto') {
|
||||
this.canvasStore.main.destroy();
|
||||
this.canvasStore.main = this.createCanvas('main-gl', 'legacy');
|
||||
this.canvasStore.main = this.createCanvas('main-gl', 'fallback');
|
||||
}
|
||||
this.inFallback = true;
|
||||
this.fallbackReason = {cors: true};
|
||||
@ -568,7 +466,6 @@ export class Aard {
|
||||
}
|
||||
}
|
||||
);
|
||||
this.timer.current.getImage = performance.now() - this.timer.current.start;
|
||||
|
||||
// STEP 1:
|
||||
// Test if corners are black. If they're not, we can immediately quit the loop.
|
||||
@ -577,8 +474,6 @@ export class Aard {
|
||||
this.settings.active.arDetect.canvasDimensions.sampleCanvas.width,
|
||||
this.settings.active.arDetect.canvasDimensions.sampleCanvas.height
|
||||
);
|
||||
this.timer.current.fastBlackLevel = performance.now() - this.timer.current.start;
|
||||
|
||||
if (this.testResults.notLetterbox) {
|
||||
// TODO: reset aspect ratio to "AR not applied"
|
||||
this.testResults.lastStage = 1;
|
||||
@ -624,7 +519,6 @@ export class Aard {
|
||||
);
|
||||
}
|
||||
}
|
||||
this.timer.current.guardLine = performance.now() - this.timer.current.start; // guardLine is for both guardLine and imageLine checks
|
||||
|
||||
// Both need to be checked
|
||||
if (! (this.testResults.imageLine.invalidated || this.testResults.guardLine.invalidated)) {
|
||||
@ -651,68 +545,47 @@ export class Aard {
|
||||
|
||||
// If forceFullRecheck is set, then 'not letterbox' should always force-reset the aspect ratio
|
||||
// (as aspect ratio may have been set manually while autodetection was off)
|
||||
|
||||
// If debugging is enable,
|
||||
this.canvasStore.debug?.drawBuffer(imageData);
|
||||
|
||||
do {
|
||||
if (this.testResults.notLetterbox) {
|
||||
// console.log('————not letterbox')
|
||||
// console.warn('DETECTED NOT LETTERBOX! (resetting)')
|
||||
this.timer.arChanged();
|
||||
this.updateAspectRatio(this.defaultAr);
|
||||
break;
|
||||
}
|
||||
|
||||
// if detection is uncertain, we don't do anything at all (unless if guardline was broken, in which case we reset)
|
||||
if (this.testResults.aspectRatioUncertain && this.testResults.guardLine.invalidated) {
|
||||
// console.info('aspect ratio not certain:', this.testResults.aspectRatioUncertainReason);
|
||||
// console.warn('check finished:', JSON.parse(JSON.stringify(this.testResults)), JSON.parse(JSON.stringify(this.canvasSamples)), '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
|
||||
|
||||
// console.warn('ASPECT RATIO UNCERTAIN, GUARD LINE INVALIDATED (resetting)')
|
||||
this.timer.arChanged();
|
||||
this.updateAspectRatio(this.defaultAr);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: emit debug values if debugging is enabled
|
||||
this.testResults.isFinished = true;
|
||||
|
||||
// console.warn(
|
||||
// `[${(+new Date() % 10000) / 100} | ${this.arid}]`,'check finished — aspect ratio updated:', this.testResults.aspectRatioUpdated,
|
||||
// '\ndetected ar:', this.testResults.activeAspectRatio, '->', this.getAr(),
|
||||
// '\nis video playing?', this.getVideoPlaybackState() === VideoPlaybackState.Playing,
|
||||
// '\n\n', JSON.parse(JSON.stringify(this.testResults)), JSON.parse(JSON.stringify(this.canvasSamples)), '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
|
||||
|
||||
// if edge width changed, emit update event.
|
||||
// except aspectRatioUpdated doesn't get set reliably, so we just call update every time, and update
|
||||
// if detected aspect ratio is different from the current aspect ratio
|
||||
// if (this.testResults.aspectRatioUpdated) {
|
||||
// this.timer.arChanged();
|
||||
const finalAr = this.getAr();
|
||||
if (finalAr > 0) {
|
||||
this.updateAspectRatio(finalAr);
|
||||
} else {
|
||||
this.testResults.aspectRatioInvalid = true;
|
||||
this.testResults.aspectRatioInvalidReason = finalAr.toFixed(3);
|
||||
}
|
||||
// }
|
||||
|
||||
// if we got "no letterbox" OR aspectRatioUpdated
|
||||
} while (false)
|
||||
|
||||
if (this.canvasStore.debug) {
|
||||
// this.canvasStore.debug.drawBuffer(imageData);
|
||||
this.timer.getAverage();
|
||||
this.debugConfig?.debugUi?.updateTestResults(this.testResults);
|
||||
if (this.testResults.notLetterbox) {
|
||||
// console.log('————not letterbox')
|
||||
this.updateAspectRatio(this.defaultAr);
|
||||
}
|
||||
|
||||
// if detection is uncertain, we don't do anything at all (unless if guardline was broken, in which case we reset)
|
||||
if (this.testResults.aspectRatioUncertain && this.testResults.guardLine.invalidated) {
|
||||
// console.info('aspect ratio not certain:', this.testResults.aspectRatioUncertainReason);
|
||||
// console.warn('check finished:', JSON.parse(JSON.stringify(this.testResults)), JSON.parse(JSON.stringify(this.canvasSamples)), '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
|
||||
|
||||
if (this.testResults.guardLine.invalidated) {
|
||||
this.updateAspectRatio(this.defaultAr);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: emit debug values if debugging is enabled
|
||||
this.testResults.isFinished = true;
|
||||
|
||||
// console.warn(
|
||||
// `[${(+new Date() % 10000) / 100} | ${this.arid}]`,'check finished — aspect ratio updated:', this.testResults.aspectRatioUpdated,
|
||||
// '\ndetected ar:', this.testResults.activeAspectRatio, '->', this.getAr(),
|
||||
// '\nis video playing?', this.getVideoPlaybackState() === VideoPlaybackState.Playing,
|
||||
// '\n\n', JSON.parse(JSON.stringify(this.testResults)), JSON.parse(JSON.stringify(this.canvasSamples)), '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n');
|
||||
|
||||
// if edge width changed, emit update event.
|
||||
// except aspectRatioUpdated doesn't get set reliably, so we just call update every time, and update
|
||||
// if detected aspect ratio is different from the current aspect ratio
|
||||
// if (this.testResults.aspectRatioUpdated) {
|
||||
this.updateAspectRatio();
|
||||
// }
|
||||
|
||||
// if we got "no letterbox" OR aspectRatioUpdated
|
||||
} catch (e) {
|
||||
console.warn('[Ultrawidify] Aspect ratio autodetection crashed for some reason.\n\nsome reason:', e);
|
||||
this.videoData.resizer.setAr({type: AspectRatioType.AutomaticUpdate, ratio: this.defaultAr, variant: this.arVariant});
|
||||
this.videoData.resizer.setAr({type: AspectRatioType.AutomaticUpdate, ratio: this.defaultAr});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private getVideoPlaybackState(): VideoPlaybackState {
|
||||
try {
|
||||
if (this.video.ended) {
|
||||
@ -725,7 +598,7 @@ export class Aard {
|
||||
return VideoPlaybackState.Playing;
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.warn('getVideoPlaybackState]', `There was an error while determining video playback state.`, e);
|
||||
this.logger.log('warn', 'debug', `[ArDetect::getVideoPlaybackState] There was an error while determining video playback state.`, e);
|
||||
return VideoPlaybackState.Error;
|
||||
}
|
||||
}
|
||||
@ -785,13 +658,11 @@ export class Aard {
|
||||
pixelValues[pvi++] = imageData[px_r];
|
||||
pixelValues[pvi++] = imageData[px_r + 1];
|
||||
pixelValues[pvi++] = imageData[px_r + 2];
|
||||
imageData[px_r + 3] = GlDebugType.BlackLevelSample;
|
||||
|
||||
const endpx_r = px_r + (width * 4) - (i * 8) - 4; // -4 because 4 bytes per pixel, and - twice the offset to mirror the diagonal
|
||||
pixelValues[pvi++] = imageData[endpx_r];
|
||||
pixelValues[pvi++] = imageData[endpx_r + 1];
|
||||
pixelValues[pvi++] = imageData[endpx_r + 2];
|
||||
imageData[endpx_r + 3] = GlDebugType.BlackLevelSample;
|
||||
}
|
||||
|
||||
// now let's populate the bottom two corners
|
||||
@ -802,13 +673,11 @@ export class Aard {
|
||||
pixelValues[pvi++] = imageData[px_r];
|
||||
pixelValues[pvi++] = imageData[px_r + 1];
|
||||
pixelValues[pvi++] = imageData[px_r + 2];
|
||||
imageData[px_r + 3] = GlDebugType.BlackLevelSample;
|
||||
|
||||
const endpx_r = px_r + (width * 4) - (i * 8) - 4; // -4 because 4 bytes per pixel, and - twice the offset to mirror the diagonal
|
||||
pixelValues[pvi++] = imageData[endpx_r];
|
||||
pixelValues[pvi++] = imageData[endpx_r + 1];
|
||||
pixelValues[pvi++] = imageData[endpx_r + 2];
|
||||
imageData[endpx_r + 3] = GlDebugType.BlackLevelSample;
|
||||
}
|
||||
|
||||
let min = 255;
|
||||
@ -903,10 +772,7 @@ export class Aard {
|
||||
|| imageData[i + 1] > this.testResults.blackThreshold
|
||||
|| imageData[i + 2] > this.testResults.blackThreshold
|
||||
) {
|
||||
imageData[i + 3] = GlDebugType.GuardLineCornerViolation;
|
||||
this.testResults.guardLine.cornerPixelsViolated[Corner.TopLeft]++;
|
||||
} else {
|
||||
imageData[i + 3] = GlDebugType.GuardLineCornerOk;
|
||||
}
|
||||
i += 4;
|
||||
}
|
||||
@ -916,15 +782,12 @@ export class Aard {
|
||||
|| imageData[i + 1] > this.testResults.blackThreshold
|
||||
|| imageData[i + 2] > this.testResults.blackThreshold
|
||||
) {
|
||||
imageData[i + 3] = GlDebugType.GuardLineViolation;
|
||||
// DONT FORGET TO INVALIDATE GUARDL LINE
|
||||
this.testResults.guardLine.top = -1;
|
||||
this.testResults.guardLine.bottom = -1;
|
||||
this.testResults.guardLine.invalidated = true;
|
||||
return;
|
||||
} else {
|
||||
imageData[i + 3] = GlDebugType.GuardLineOk;
|
||||
}
|
||||
};
|
||||
i += 4;
|
||||
}
|
||||
while (i < rowEnd) {
|
||||
@ -933,10 +796,7 @@ export class Aard {
|
||||
|| imageData[i + 1] > this.testResults.blackThreshold
|
||||
|| imageData[i + 2] > this.testResults.blackThreshold
|
||||
) {
|
||||
imageData[i + 3] = GlDebugType.GuardLineCornerViolation;
|
||||
this.testResults.guardLine.cornerPixelsViolated[Corner.TopRight]++;
|
||||
} else {
|
||||
imageData[i + 3] = GlDebugType.GuardLineCornerOk;
|
||||
}
|
||||
i += 4; // skip over alpha channel
|
||||
}
|
||||
@ -958,10 +818,7 @@ export class Aard {
|
||||
|| imageData[i + 1] > this.testResults.blackThreshold
|
||||
|| imageData[i + 2] > this.testResults.blackThreshold
|
||||
) {
|
||||
imageData[i + 3] = GlDebugType.GuardLineCornerViolation;
|
||||
this.testResults.guardLine.cornerPixelsViolated[Corner.BottomLeft]++;
|
||||
} else {
|
||||
imageData[i + 3] = GlDebugType.GuardLineCornerOk;
|
||||
}
|
||||
i += 4; // skip over alpha channel
|
||||
}
|
||||
@ -974,15 +831,12 @@ export class Aard {
|
||||
|| imageData[i + 1] > this.testResults.blackThreshold
|
||||
|| imageData[i + 2] > this.testResults.blackThreshold
|
||||
) {
|
||||
imageData[i + 3] = GlDebugType.GuardLineViolation;
|
||||
// DONT FORGET TO INVALIDATE GUARDL LINE
|
||||
this.testResults.guardLine.top = -1;
|
||||
this.testResults.guardLine.bottom = -1;
|
||||
this.testResults.guardLine.invalidated = true;
|
||||
return;
|
||||
} else {
|
||||
imageData[i + 3] = GlDebugType.GuardLineOk;
|
||||
}
|
||||
};
|
||||
i += 4;
|
||||
}
|
||||
if (i % 4) {
|
||||
@ -994,10 +848,7 @@ export class Aard {
|
||||
|| imageData[i + 1] > this.testResults.blackThreshold
|
||||
|| imageData[i + 2] > this.testResults.blackThreshold
|
||||
) {
|
||||
imageData[i + 3] = GlDebugType.GuardLineCornerViolation;
|
||||
this.testResults.guardLine.cornerPixelsViolated[Corner.BottomRight]++;
|
||||
} else {
|
||||
imageData[i + 3] = GlDebugType.GuardLineCornerOk;
|
||||
}
|
||||
i += 4; // skip over alpha channel
|
||||
}
|
||||
@ -1078,60 +929,8 @@ export class Aard {
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
|
||||
if (imagePixel && ++pixelCount > detectionThreshold) {
|
||||
imageData[i] = GlDebugType.ImageLineThresholdReached;
|
||||
return;
|
||||
} else {
|
||||
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
|
||||
}
|
||||
i++; // skip over alpha channel
|
||||
}
|
||||
}
|
||||
while (i < secondSegment) {
|
||||
imagePixel = false;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
|
||||
if (imagePixel && ++pixelCount > detectionThreshold) {
|
||||
imageData[i] = GlDebugType.ImageLineThresholdReached;
|
||||
return;
|
||||
} else {
|
||||
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
|
||||
}
|
||||
i++; // skip over alpha channel
|
||||
}
|
||||
if (! this.testResults.guardLine.cornerViolated[Corner.TopRight]) {
|
||||
while (i < rowEnd) {
|
||||
imagePixel = false;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
|
||||
if (imagePixel && ++pixelCount > detectionThreshold) {
|
||||
imageData[i] = GlDebugType.ImageLineThresholdReached;
|
||||
return;
|
||||
} else {
|
||||
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
|
||||
}
|
||||
i++; // skip over alpha channel
|
||||
}
|
||||
}
|
||||
|
||||
// we don't run image detection in corners that may contain logos, as such corners
|
||||
// may not be representative
|
||||
if (! this.testResults.guardLine.cornerViolated[Corner.TopLeft]) {
|
||||
while (i < firstSegment) {
|
||||
imagePixel = false;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
|
||||
if (imagePixel && ++pixelCount > detectionThreshold) {
|
||||
imageData[i] = GlDebugType.ImageLineThresholdReached;
|
||||
return;
|
||||
} else {
|
||||
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
|
||||
}
|
||||
};
|
||||
i++; // skip over alpha channel
|
||||
}
|
||||
}
|
||||
@ -1154,11 +953,48 @@ export class Aard {
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
|
||||
if (imagePixel && ++pixelCount > detectionThreshold) {
|
||||
imageData[i] = GlDebugType.ImageLineThresholdReached;
|
||||
return;
|
||||
} else {
|
||||
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
|
||||
}
|
||||
};
|
||||
i++; // skip over alpha channel
|
||||
}
|
||||
}
|
||||
|
||||
// we don't run image detection in corners that may contain logos, as such corners
|
||||
// may not be representative
|
||||
if (! this.testResults.guardLine.cornerViolated[Corner.TopLeft]) {
|
||||
while (i < firstSegment) {
|
||||
imagePixel = false;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
|
||||
if (imagePixel && ++pixelCount > detectionThreshold) {
|
||||
return;
|
||||
};
|
||||
i++; // skip over alpha channel
|
||||
}
|
||||
}
|
||||
while (i < secondSegment) {
|
||||
imagePixel = false;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
|
||||
if (imagePixel && ++pixelCount > detectionThreshold) {
|
||||
return;
|
||||
};
|
||||
i++; // skip over alpha channel
|
||||
}
|
||||
if (! this.testResults.guardLine.cornerViolated[Corner.TopRight]) {
|
||||
while (i < rowEnd) {
|
||||
imagePixel = false;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
|
||||
if (imagePixel && ++pixelCount > detectionThreshold) {
|
||||
return;
|
||||
};
|
||||
i++; // skip over alpha channel
|
||||
}
|
||||
}
|
||||
@ -1183,11 +1019,8 @@ export class Aard {
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
|
||||
if (imagePixel && ++pixelCount > detectionThreshold) {
|
||||
imageData[i] = GlDebugType.ImageLineThresholdReached;
|
||||
return;
|
||||
} else {
|
||||
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
|
||||
}
|
||||
};
|
||||
i++; // skip over alpha channel
|
||||
}
|
||||
}
|
||||
@ -1198,11 +1031,8 @@ export class Aard {
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
|
||||
if (imagePixel && ++pixelCount > detectionThreshold) {
|
||||
imageData[i] = GlDebugType.ImageLineThresholdReached;
|
||||
return;
|
||||
} else {
|
||||
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
|
||||
}
|
||||
};
|
||||
i++; // skip over alpha channel
|
||||
}
|
||||
if (! this.testResults.guardLine.cornerViolated[Corner.TopRight]) {
|
||||
@ -1213,11 +1043,8 @@ export class Aard {
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
|
||||
if (imagePixel && ++pixelCount > detectionThreshold) {
|
||||
imageData[i] = GlDebugType.ImageLineThresholdReached;
|
||||
return;
|
||||
} else {
|
||||
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
|
||||
}
|
||||
};
|
||||
i++; // skip over alpha channel
|
||||
}
|
||||
}
|
||||
@ -1232,11 +1059,8 @@ export class Aard {
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
|
||||
if (imagePixel && ++pixelCount > detectionThreshold) {
|
||||
imageData[i] = GlDebugType.ImageLineThresholdReached;
|
||||
return;
|
||||
} else {
|
||||
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
|
||||
}
|
||||
};
|
||||
i++; // skip over alpha channel
|
||||
}
|
||||
}
|
||||
@ -1247,11 +1071,8 @@ export class Aard {
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
|
||||
if (imagePixel && ++pixelCount > detectionThreshold) {
|
||||
imageData[i] = GlDebugType.ImageLineThresholdReached;
|
||||
return;
|
||||
} else {
|
||||
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
|
||||
}
|
||||
};
|
||||
i++; // skip over alpha channel
|
||||
}
|
||||
if (! this.testResults.guardLine.cornerViolated[Corner.TopRight]) {
|
||||
@ -1262,11 +1083,8 @@ export class Aard {
|
||||
imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
|
||||
|
||||
if (imagePixel && ++pixelCount > detectionThreshold) {
|
||||
imageData[i] = GlDebugType.ImageLineThresholdReached;
|
||||
return;
|
||||
} else {
|
||||
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
|
||||
}
|
||||
};
|
||||
i++; // skip over alpha channel
|
||||
}
|
||||
}
|
||||
@ -1308,14 +1126,12 @@ export class Aard {
|
||||
// bit more nicely visible (instead of hidden among spagheti)
|
||||
this.edgeScan(imageData, width, height);
|
||||
this.validateEdgeScan(imageData, width, height);
|
||||
this.timer.current.edgeScan = performance.now() - this.timer.current.start;
|
||||
|
||||
// TODO: _if gradient detection is enabled, then:
|
||||
this.sampleForGradient(imageData, width, height);
|
||||
this.timer.current.gradient = performance.now() - this.timer.current.start;
|
||||
|
||||
this.processScanResults(imageData, width, height);
|
||||
this.timer.current.scanResults = performance.now() - this.timer.current.start;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1382,13 +1198,11 @@ export class Aard {
|
||||
|| imageData[rowOffset + x + 2] > this.testResults.blackLevel;
|
||||
|
||||
if (!isImage) {
|
||||
imageData[rowOffset + x + 3] = GlDebugType.EdgeScanProbe;
|
||||
// TODO: maybe some day mark this pixel as checked by writing to alpha channel
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (this.canvasSamples.top[i] === -1) {
|
||||
imageData[rowOffset + x + 3] = GlDebugType.EdgeScanHit;
|
||||
this.canvasSamples.top[i] = row;
|
||||
finishedRows++;
|
||||
}
|
||||
@ -1432,14 +1246,12 @@ export class Aard {
|
||||
|| imageData[rowOffset + x + 2] > this.testResults.blackLevel;
|
||||
|
||||
if (!isImage) {
|
||||
imageData[rowOffset + x + 3] = GlDebugType.EdgeScanProbe;
|
||||
// console.log('(row:', row, ')', 'val:', imageData[rowOffset + x], 'col', x >> 2, x, 'pxoffset:', rowOffset + x, 'len:', imageData.length)
|
||||
// TODO: maybe some day mark this pixel as checked by writing to alpha channel
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if (this.canvasSamples.bottom[i] === -1) {
|
||||
imageData[rowOffset + x + 3] = GlDebugType.EdgeScanHit;
|
||||
this.canvasSamples.bottom[i] = row;
|
||||
finishedRows++;
|
||||
}
|
||||
@ -1479,9 +1291,6 @@ export class Aard {
|
||||
const slopeTestSample = this.settings.active.arDetect.edgeDetection.slopeTestWidth * 4;
|
||||
|
||||
while (i < this.canvasSamples.top.length) {
|
||||
// if (this.canvasSamples.top[i] < 0) {
|
||||
// continue;
|
||||
// }
|
||||
// calculate row offset:
|
||||
row = (this.canvasSamples.top[i + 1] - 1) * width * 4;
|
||||
xs = row + this.canvasSamples.top[i] - slopeTestSample;
|
||||
@ -1493,11 +1302,8 @@ export class Aard {
|
||||
|| imageData[xs + 1] > this.testResults.blackThreshold
|
||||
|| imageData[xs + 2] > this.testResults.blackThreshold
|
||||
) {
|
||||
imageData[xs + 3] = GlDebugType.SlopeTestDarkViolation;
|
||||
this.canvasSamples.top[i + 1] = -1;
|
||||
break;
|
||||
} else {
|
||||
imageData[xs + 3] = GlDebugType.SlopeTestDarkOk;
|
||||
}
|
||||
xs += 4;
|
||||
}
|
||||
@ -1507,13 +1313,9 @@ export class Aard {
|
||||
i = 0;
|
||||
let i1 = 0;
|
||||
while (i < this.canvasSamples.bottom.length) {
|
||||
// if (this.canvasSamples.bottom[i] < 0) {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
// calculate row offset:
|
||||
i1 = i + 1;
|
||||
row = (this.canvasSamples.bottom[i1] + 1) * width * 4;
|
||||
row = (this.canvasSamples.bottom[i1] - 1) * width * 4;
|
||||
xs = row + this.canvasSamples.bottom[i] - slopeTestSample;
|
||||
xe = row + this.canvasSamples.bottom[i] + slopeTestSample;
|
||||
|
||||
@ -1523,17 +1325,15 @@ export class Aard {
|
||||
|| imageData[xs + 1] > this.testResults.blackThreshold
|
||||
|| imageData[xs + 2] > this.testResults.blackThreshold
|
||||
) {
|
||||
imageData[xs + 3] = GlDebugType.SlopeTestDarkViolation;
|
||||
this.canvasSamples.bottom[i1] = -1;
|
||||
i += 2;
|
||||
break;
|
||||
}
|
||||
imageData[xs + 3] = GlDebugType.SlopeTestDarkOk;
|
||||
xs += 4;
|
||||
}
|
||||
|
||||
if (this.canvasSamples.bottom[i1]) {
|
||||
this.canvasSamples.bottom[i1] = this.canvasSamples.bottom[i1];
|
||||
this.canvasSamples.bottom[i1] = height - this.canvasSamples.bottom[i1];
|
||||
}
|
||||
|
||||
i += 2;
|
||||
@ -1557,10 +1357,6 @@ export class Aard {
|
||||
|
||||
upperEdgeCheck:
|
||||
for (let i = 1; i < this.canvasSamples.top.length; i += 2) {
|
||||
if (this.canvasSamples.top[i] < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pixelOffset = this.canvasSamples.top[i] * realWidth + this.canvasSamples.top[i - 1] * 4;
|
||||
|
||||
lastSubpixel = imageData[pixelOffset] > imageData[pixelOffset + 1] ? imageData[pixelOffset] : imageData[pixelOffset + 1];
|
||||
@ -1603,9 +1399,6 @@ export class Aard {
|
||||
|
||||
lowerEdgeCheck:
|
||||
for (let i = 1; i < this.canvasSamples.bottom.length; i += 2) {
|
||||
if (this.canvasSamples.bottom[i] < 0) {
|
||||
continue;
|
||||
}
|
||||
pixelOffset = (height - this.canvasSamples.bottom[i]) * realWidth + this.canvasSamples.bottom[i - 1] * 4;
|
||||
|
||||
lastSubpixel = imageData[pixelOffset] > imageData[pixelOffset + 1] ? imageData[pixelOffset] : imageData[pixelOffset + 1];
|
||||
@ -1644,6 +1437,7 @@ export class Aard {
|
||||
if (lastSubpixel - firstSubpixel > this.settings.active.arDetect.edgeDetection.gradientTestMinDelta) {
|
||||
this.canvasSamples.bottom[i] = -1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -2000,21 +1794,10 @@ export class Aard {
|
||||
return;
|
||||
}
|
||||
if (maxOffset > 2) {
|
||||
if (this.testResults.aspectRatioCheck.topCandidate === Infinity) {
|
||||
this.testResults.imageLine.top = -1;
|
||||
this.testResults.guardLine.top = -1;
|
||||
} else {
|
||||
this.testResults.imageLine.top = this.testResults.aspectRatioCheck.topCandidate = this.testResults.aspectRatioCheck.topCandidate;
|
||||
this.testResults.guardLine.top = Math.max(this.testResults.imageLine.top - 2, 0);
|
||||
}
|
||||
|
||||
if (this.testResults.aspectRatioCheck.bottomCandidate === Infinity) {
|
||||
this.testResults.imageLine.bottom = -1;
|
||||
this.testResults.guardLine.bottom = -1;
|
||||
} else {
|
||||
this.testResults.imageLine.bottom = this.testResults.aspectRatioCheck.bottomCandidate;
|
||||
this.testResults.guardLine.bottom = Math.min(this.testResults.imageLine.bottom + 2, this.canvasStore.main.height - 1);
|
||||
}
|
||||
this.testResults.imageLine.top = this.testResults.aspectRatioCheck.topCandidate === Infinity ? -1 : this.testResults.aspectRatioCheck.topCandidate;
|
||||
this.testResults.imageLine.bottom = this.testResults.aspectRatioCheck.bottomCandidate === Infinity ? -1 : this.testResults.aspectRatioCheck.bottomCandidate;
|
||||
this.testResults.guardLine.top = Math.max(this.testResults.imageLine.top - 2, 0);
|
||||
this.testResults.guardLine.bottom = Math.min(this.testResults.imageLine.bottom + 2, this.canvasStore.main.height - 1);
|
||||
}
|
||||
|
||||
this.testResults.aspectRatioUncertain = false;
|
||||
@ -2038,8 +1821,7 @@ export class Aard {
|
||||
this.videoData.resizer.updateAr({
|
||||
type: AspectRatioType.AutomaticUpdate,
|
||||
ratio: this.getAr(),
|
||||
offset: this.testResults.letterboxOffset,
|
||||
variant: this.arVariant
|
||||
offset: this.testResults.letterboxOffset
|
||||
});
|
||||
this.testResults.activeAspectRatio = ar;
|
||||
}
|
||||
@ -2053,7 +1835,7 @@ export class Aard {
|
||||
const fileAr = this.video.videoWidth / this.video.videoHeight;
|
||||
const canvasAr = this.canvasStore.main.width / this.canvasStore.main.height;
|
||||
|
||||
const compensatedWidth = fileAr === canvasAr ? this.canvasStore.main.width : this.video.videoWidth * this.canvasStore.main.height / (this.video.videoHeight);
|
||||
const compensatedWidth = fileAr === canvasAr ? this.canvasStore.main.width : this.canvasStore.main.width * fileAr;
|
||||
|
||||
// console.log(`
|
||||
// ———— ASPECT RATIO CALCULATION: —————
|
||||
|
@ -1,418 +0,0 @@
|
||||
import { AardPerformanceData } from './AardTimers';
|
||||
|
||||
export class AardDebugUi {
|
||||
|
||||
aard: any;
|
||||
|
||||
uiAnchorElement: HTMLDivElement;
|
||||
pauseOnArCheck: boolean = false;
|
||||
|
||||
uiVisibility: any = {};
|
||||
|
||||
constructor(aard: any) {
|
||||
this.aard = aard;
|
||||
|
||||
this.uiVisibility = {
|
||||
detectionDetails: aard.settings.active.ui.dev.aardDebugOverlay.showDetectionDetails
|
||||
};
|
||||
|
||||
(window as any).ultrawidify_uw_aard_debug_tools = {
|
||||
enableStopOnChange: () => this.changePauseOnCheck(true),
|
||||
disableStopOnChange: () => this.changePauseOnCheck(false),
|
||||
resumeVideo: () => this.resumeVideo(),
|
||||
step: () => this.aard.step()
|
||||
}
|
||||
}
|
||||
|
||||
initContainer() {
|
||||
const div = document.createElement('div');
|
||||
div.id = 'uw-aard-debug-ui-container';
|
||||
div.innerHTML = `
|
||||
<div style="
|
||||
position: fixed; top: 0; left: 0; width: 100vw; height: 100dvh; display: flex; flex-direction: column; pointer-events: none; z-index: 9999; font-size: 16px; font-family: 'Overpass Mono', monospace;
|
||||
">
|
||||
<div style="width: 100%; display: flex; flex-direction: row; justify-content: space-between; backdrop-filter: blur(0.5rem) brightness(0.5);">
|
||||
<div style="padding: 1rem; color: #fff">
|
||||
<h1>Aard debug overlay</h1>
|
||||
</div>
|
||||
<div style="pointer-events: all; display: flex; flex-direction: column; margin-right: 1rem;">
|
||||
<button id="uw-aard-debug_show-detection-details">Show det. details</button>
|
||||
<button id="uw-aard-debug_hide-detection-details">Hide det. details</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#uw-aard-debug_performance-container #uw-aard-debug_performance-popup {
|
||||
display: none;
|
||||
}
|
||||
#uw-aard-debug_performance-container:hover #uw-aard-debug_performance-popup {
|
||||
display: block;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 3rem;
|
||||
width: 100vw;
|
||||
|
||||
color: #aaa;
|
||||
background-color: #000;
|
||||
border: 2px solid #fa6;
|
||||
padding: 16px;
|
||||
padding-right: 32px;
|
||||
box-sizing: border-box;
|
||||
|
||||
backdrop-filter: blur(1rem) brightness(0.5);
|
||||
|
||||
pointer-events: none;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="uw-aard-debug_performance-container" style="flex-grow: 1; position: relative; pointer-events: auto;">
|
||||
<div id="uw-aard-debug_performance-mini" style="width: 100%; color: #aaa; display: flex; flex-direction: column; font-size: 12px;"></div>
|
||||
<div id="uw-aard-debug_performance-popup" style="position: fixed; top: 3rem; z-index: 3000;">
|
||||
</div>
|
||||
</div>
|
||||
<div style="pointer-events: all">
|
||||
<button id="uw-aard-debug-ui_close-overlay">Close overlay</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="uw-aard-debug-ui_body" style="display: flex; flex-direction: row; width: 100%">
|
||||
<div style="">
|
||||
<div id="uw-aard-debug_aard-sample-canvas" style="min-width: 640px"></div>
|
||||
<div style="background: black; color: #fff"; font-size: 24px;">AARD IN</div>
|
||||
|
||||
<div style="background: black; color: #ccc; padding: 1rem">
|
||||
<div>
|
||||
<span style="color: rgb(0.1, 0.1, 0.35)">■</span>
|
||||
Black level sample
|
||||
</div>
|
||||
<div>
|
||||
<span style="color: rgb(0.3, 1.0, 0.6)">■</span>
|
||||
<span style="color: rgb(0.1, 0.5, 0.3)">■</span>
|
||||
Guard line (middle/corner) OK
|
||||
</div>
|
||||
<div>
|
||||
<span style="color: rgb(1.0, 0.1, 0.1)">■</span>
|
||||
<span style="color: rgb(0.5, 0.0, 0.0)">■</span>
|
||||
Guard line (middle/corner) violation
|
||||
</div>
|
||||
<div>
|
||||
Image line — <span style="color: rgb(0.7, 0.7, 0.7)">■</span> image, <span style="color: rgb(0.2, 0.2, 0.6)">■</span> no image
|
||||
</div>
|
||||
<div>
|
||||
Edge scan — <span style="color: rgb(0.1, 0.1, 0.4)">■</span> probe, <span style="color: rgb(0.4, 0.4, 1.0)">■</span> hit
|
||||
</div>
|
||||
<div>
|
||||
Slope test — <span style="color: rgb(0.4, 0.4, 1.0)">■</span> ok, <span style="color: rgb(1.0, 0.0, 0.0)">■</span> fail
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="pointer-events: all">
|
||||
<button id="uw-aard-debug-ui_enable-stop-on-change" style="">Pause video on aspect ratio change</button>
|
||||
<button id="uw-aard-debug-ui_disable-stop-on-change" style="display: none">Stop pausing video on aspect ratio change</button>
|
||||
<button id="uw-aard-debug-ui_resume-video" >Resume video</button>
|
||||
<button id="uw-aard-debug-ui_enable-step" >Run ARD once</button>
|
||||
<button id="uw-aard-debug-ui_enable-step-nocache" >Run ARD (bypass cache)</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="flex-grow: 1"></div>
|
||||
<div>
|
||||
<div style="background: black; color: #ccc;">
|
||||
<div style="font-size: 24px; padding: 1rem;">
|
||||
Debug results:
|
||||
</div>
|
||||
<pre id="uw-aard-results"></pre>
|
||||
</div>
|
||||
</div>
|
||||
<div style="width: 1920px">
|
||||
<div id="uw-aard-debug_aard-output" style="zoom: 3; image-rendering: pixelated;"></div>
|
||||
<div style="background: black; color: #fff; font-size: 24px;">AARD RESULT</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(div);
|
||||
this.uiAnchorElement = div;
|
||||
|
||||
document.getElementById('uw-aard-debug-ui_enable-stop-on-change').onclick = () => this.changePauseOnCheck(true);
|
||||
document.getElementById('uw-aard-debug-ui_disable-stop-on-change').onclick = () => this.changePauseOnCheck(false);
|
||||
document.getElementById('uw-aard-debug-ui_resume-video').onclick = () => this.resumeVideo();
|
||||
document.getElementById('uw-aard-debug-ui_enable-step').onclick = () => this.aard.step();
|
||||
document.getElementById('uw-aard-debug-ui_enable-step-nocache').onclick = () => this.aard.step({noCache: true});
|
||||
document.getElementById('uw-aard-debug-ui_close-overlay').onclick = () => (this.aard as any).hideDebugCanvas();
|
||||
document.getElementById('uw-aard-debug_show-detection-details').onclick = () => {this.uiVisibility.detectionDetails = true; this.setOverlayVisibility();};
|
||||
document.getElementById('uw-aard-debug_hide-detection-details').onclick = () => {this.uiVisibility.detectionDetails = false; this.setOverlayVisibility();};
|
||||
|
||||
this.setOverlayVisibility();
|
||||
}
|
||||
|
||||
changePauseOnCheck(pauseOnChange: boolean) {
|
||||
this.pauseOnArCheck = pauseOnChange;
|
||||
|
||||
document.getElementById("uw-aard-debug-ui_enable-stop-on-change").style.display = pauseOnChange ? "none" : "";
|
||||
document.getElementById("uw-aard-debug-ui_disable-stop-on-change").style.display = pauseOnChange ? "" : "none";
|
||||
}
|
||||
|
||||
destroyContainer() {
|
||||
this.uiAnchorElement.remove();
|
||||
}
|
||||
|
||||
attachCanvases(sample: HTMLCanvasElement, debug: HTMLCanvasElement) {
|
||||
const sampleCanvasParent = document.getElementById('uw-aard-debug_aard-sample-canvas');
|
||||
sampleCanvasParent.appendChild(sample);
|
||||
const debugCanvasParent = document.getElementById('uw-aard-debug_aard-output');
|
||||
debugCanvasParent.appendChild(debug);
|
||||
}
|
||||
|
||||
resumeVideo() {
|
||||
(this.aard as any).video.play();
|
||||
this.aard.start();
|
||||
}
|
||||
|
||||
private updatePerformanceResults() {
|
||||
const previewDiv = document.getElementById('uw-aard-debug_performance-mini');
|
||||
const popupDiv = document.getElementById('uw-aard-debug_performance-popup');
|
||||
|
||||
const previewContent = `
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<div style="width: 120px">Current:</div>
|
||||
<div style="flex-grow: 1;">${this.generateMiniGraphBar(this.aard.timer.current)}</div>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<div style="width: 120px">Average:</div>
|
||||
<div style="flex-grow: 1">${this.generateMiniGraphBar(this.aard.timer.average)}</div>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction: row">
|
||||
<div style="width: 120px">Last chg.:</div>
|
||||
<div></div>
|
||||
<div style="flex-grow: 1">${this.generateMiniGraphBar(this.aard.timer.lastChange)}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const popupContent = `
|
||||
<h2 style="color: #fa6; margin-bottom: 1rem">Detailed performance analysis:</h2>
|
||||
<div style="width: 100%; display: flex; flex-direction: column">
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<span style="color: #fff">Raw times (cumulative; -1 = test was skipped):</span><br/>
|
||||
draw <span style="color: #fa6; margin-right: 0.5rem;">${this.aard.timer.current.draw.toFixed(2)}ms</span>
|
||||
get data <span style="color: #fa6; margin-right: 0.5rem;">${this.aard.timer.current.getImage.toFixed(2)}ms</span>
|
||||
black lv. <span style="color: #fa6; margin-right: 0.5rem;">${this.aard.timer.current.fastBlackLevel.toFixed(2)}ms</span>
|
||||
guard/image <span style="color: #fa6; margin-right: 0.5rem;">${this.aard.timer.current.guardLine.toFixed(3)}ms</span>
|
||||
edge <span style="color: #fa6; margin-right: 0.5rem;">${this.aard.timer.current.edgeScan.toFixed(2)}ms</span>
|
||||
gradient <span style="color: #fa6; margin-right: 0.5rem;">${this.aard.timer.current.gradient.toFixed(3)}ms</span>
|
||||
post <span style="color: #fa6; margin-right: 0.5rem;">${this.aard.timer.current.scanResults.toFixed(2)}ms</span>
|
||||
</div>
|
||||
<div style="color: #fff">Stage times (not cumulative):</div>
|
||||
<div style="display: flex; flex-direction: row; width: 100%; height: 150px">
|
||||
<div style="width: 160px; text-align: right; padding-right: 4px;">Current:</div>
|
||||
<div style="flex-grow: 1;">${this.generateMiniGraphBar(this.aard.timer.current, true)}</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<span style="color: #fff">Raw times (cumulative; -1 = test was skipped):</span><br/>
|
||||
draw <span style="color: #fa6; margin-right: 0.5rem;">${this.aard.timer.average.draw.toFixed(2)}ms</span>
|
||||
get data <span style="color: #fa6; margin-right: 0.5rem;">${this.aard.timer.average.getImage.toFixed(2)}ms</span>
|
||||
black lv. <span style="color: #fa6; margin-right: 0.5rem;">${this.aard.timer.average.fastBlackLevel.toFixed(2)}ms</span>
|
||||
guard/image <span style="color: #fa6; margin-right: 0.5rem;">${this.aard.timer.average.guardLine.toFixed(3)}ms</span>
|
||||
edge <span style="color: #fa6; margin-right: 0.5rem;">${this.aard.timer.average.edgeScan.toFixed(2)}ms</span>
|
||||
gradient <span style="color: #fa6; margin-right: 0.5rem;">${this.aard.timer.average.gradient.toFixed(3)}ms</span>
|
||||
post <span style="color: #fa6; margin-right: 0.5rem;">${this.aard.timer.average.scanResults.toFixed(2)}ms</span>
|
||||
</div>
|
||||
<div style="color: #fff">Stage times (not cumulative):</div>
|
||||
<div style="display: flex; flex-direction: row; width: 100%; height: 150px">
|
||||
<div style="width: 160px; text-align: right; padding-right: 4px;">Average:</div>
|
||||
<div style="flex-grow: 1;">${this.generateMiniGraphBar(this.aard.timer.average, true)}</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: row; width: 100%; height: 150px">
|
||||
<div style="width: 160px; text-align: right; padding-right: 4px;">Last change:</div>
|
||||
<div style="flex-grow: 1;">${this.generateMiniGraphBar(this.aard.timer.lastChange, true)}</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
|
||||
previewDiv.innerHTML = previewContent;
|
||||
popupDiv.innerHTML = popupContent;
|
||||
}
|
||||
|
||||
private getBarLabel(width: number, leftOffset: number, topOffset: number, label: string, detailed: boolean, extraStyles?: string) {
|
||||
if (!detailed) {
|
||||
return '';
|
||||
}
|
||||
|
||||
let offsets: string;
|
||||
let text: string = '';
|
||||
|
||||
if (leftOffset + width < 80) {
|
||||
// at the end of the bar
|
||||
offsets = `left: ${leftOffset + width}%;`;
|
||||
} else {
|
||||
if (width < 15 && leftOffset < 100) {
|
||||
// before the bar
|
||||
offsets = `right: ${100 - leftOffset}%;`;
|
||||
text = 'color: #fff;';
|
||||
} else {
|
||||
// inside the bar, aligned to right
|
||||
offsets = `right: ${Math.max(100 - (leftOffset + width), 0)}%;`
|
||||
}
|
||||
}
|
||||
|
||||
return `
|
||||
<div style="
|
||||
position: absolute;
|
||||
${offsets} top: ${topOffset}px;
|
||||
padding-left: 0.5rem; padding-right: 0.5rem;đ
|
||||
${text}
|
||||
text-shadow: 0 0 2px #000, 0 0 2px #000; 0 0 2px #000; 0 0 2px #000;
|
||||
${extraStyles}
|
||||
">
|
||||
${label}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private generateMiniGraphBar(perf: AardPerformanceData, detailed: boolean = false) {
|
||||
if (!perf) {
|
||||
return `
|
||||
n/a
|
||||
`;
|
||||
}
|
||||
|
||||
let total = 0;
|
||||
|
||||
const draw = Math.max(perf.draw, 0);
|
||||
total += draw;
|
||||
|
||||
const getImageStart = draw;
|
||||
const getImage = Math.max(perf.getImage - total, 0);
|
||||
total += getImage;
|
||||
|
||||
const fastBlackLevelStart = getImageStart + getImage;
|
||||
const fastBlackLevel = Math.max(perf.fastBlackLevel - total, 0);
|
||||
total += fastBlackLevel;
|
||||
|
||||
const guardLineStart = fastBlackLevelStart + fastBlackLevel;
|
||||
const guardLine = Math.max(perf.guardLine - total, 0);
|
||||
total += guardLine;
|
||||
|
||||
const edgeScanStart = guardLineStart + guardLine;
|
||||
const edgeScan = Math.max(perf.edgeScan - total, 0);
|
||||
total += edgeScan;
|
||||
|
||||
const gradientStart = edgeScanStart + edgeScan;
|
||||
const gradient = Math.max(perf.gradient - total, 0);
|
||||
total += gradient;
|
||||
|
||||
const scanResultsStart = gradientStart + gradient;
|
||||
const scanResults = Math.max(perf.scanResults - total, 0);
|
||||
total += scanResults;
|
||||
|
||||
return `
|
||||
<div style="width: 100%; position: relative; text-align: right;">
|
||||
${detailed ? '' : `${total.toFixed()} ms`}
|
||||
<div style="position: absolute; top: 0; left: 0; width: ${total}%; background: #fff; height: 2px;"></div>
|
||||
${detailed ? `<div style="position: absolute; top: 0; left: ${total}%; height: 100px; width: 1px; border-left: 1px dotted rgba(255,255,255,0.5)"></div> ` : ''}
|
||||
|
||||
<div style="position: absolute; top: ${detailed ? '2px' : '2px'}; left: 0; min-width: 1px; width: ${draw}%; background: #007; height: 12px;"></div>
|
||||
${this.getBarLabel(draw, 0, 2, `draw: ${draw.toFixed(2)} ms`, detailed)}
|
||||
|
||||
<div style="position: absolute; top: ${detailed ? '14px' : '2px'}; left: ${getImageStart}%; min-width: 1px; width: ${getImage}%; background: #00a; height: 12px;"></div>
|
||||
${this.getBarLabel(getImage, getImageStart, 14, `get data: ${getImage.toFixed(2)} ms`, detailed)}
|
||||
|
||||
<div style="position: absolute; top: ${detailed ? '26px' : '2px'}; left: ${fastBlackLevelStart}%; min-width: 1px; width: ${fastBlackLevel}%; background: #03a; height: 12px;"></div>
|
||||
${this.getBarLabel(fastBlackLevel, fastBlackLevelStart, 26, `black level: ${fastBlackLevel.toFixed(2)} ms`, detailed)};
|
||||
|
||||
<div style="position: absolute; top: ${detailed ? '38px' : '2px'}; left: ${guardLineStart}%; min-width: 1px; width: ${guardLine}%; background: #0f3; height: 12px;"></div>
|
||||
${this.getBarLabel(guardLine, guardLineStart, 38, `guard/image line: ${guardLine.toFixed(2)} ms`, detailed)}
|
||||
|
||||
<div style="position: absolute; top: ${detailed ? '50px' : '2px'}; left: ${edgeScanStart}%; min-width: 1px; width: ${edgeScan}%; background: #f51; height: 12px;"></div>
|
||||
${this.getBarLabel(edgeScan, edgeScanStart, 50, `edge scan (/w validation): ${edgeScan.toFixed(2)} ms`, detailed)}
|
||||
|
||||
<div style="position: absolute; top: ${detailed ? '62px' : '2px'}; left: ${gradientStart}%; min-width: 1px; width: ${gradient}%; background: #fa6; height: 12px;"></div>
|
||||
${this.getBarLabel(gradient, gradientStart, 62, `gradient: ${gradient.toFixed(2)} ms`, detailed)}
|
||||
|
||||
<div style="position: absolute; top: ${detailed ? '74px' : '2px'}; left: ${scanResultsStart}%; min-width: 1px; width: ${scanResults}%; background: #80f; height: 12px;"></div>
|
||||
${this.getBarLabel(scanResults, scanResultsStart, 74, `scan results processing: ${scanResults.toFixed(2)} ms`, detailed)}
|
||||
|
||||
${this.getBarLabel(0, scanResults + scanResultsStart, 88, `total: ${total.toFixed(2)} ms`, detailed, 'color: #fff;')}
|
||||
|
||||
<!-- 60/30 fps markers -->
|
||||
<div style="position: absolute; top: ${detailed ? '-12px' : '0'}; left: 16.666%; width: 1px; border-left: 1px dashed #4f9; height: ${detailed ? '112px' : '12px'}; padding-left: 2px; background-color: rgba(0,0,0,0.5); z-index: ${detailed ? '5' : '2'}000;">60fps</div>
|
||||
<div style="position: absolute; top: ${detailed ? '-12px' : '0'}; left: 33.333%; width: 1px; border-left: 1px dashed #ff0; height: ${detailed ? '112px' : '12px'}; padding-left: 2px; background-color: #000; z-index: ${detailed ? '5' : '2'}000;">30fps</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
updateTestResults(testResults) {
|
||||
this.updatePerformanceResults();
|
||||
|
||||
if (testResults.aspectRatioUpdated && this.pauseOnArCheck) {
|
||||
(this.aard as any).video.pause();
|
||||
this.aard.stop();
|
||||
}
|
||||
|
||||
const resultsDiv = document.getElementById('uw-aard-results');
|
||||
|
||||
let out = `
|
||||
LAST STAGE: ${testResults.lastStage} | black level: ${testResults.blackLevel}, threshold: ${testResults.blackThreshold}
|
||||
|
||||
-- ASPECT RATIO
|
||||
Active: ${testResults.activeAspectRatio.toFixed(3)}, changed since last check? ${testResults.aspectRatioUpdated} letterbox width: ${testResults.letterboxWidth} offset ${testResults.letterboxOffset}
|
||||
|
||||
image in black level probe (aka "not letterbox"): ${testResults.notLetterbox}
|
||||
|
||||
`;
|
||||
if (testResults.notLetterbox) {
|
||||
resultsDiv.textContent = out;
|
||||
return;
|
||||
}
|
||||
out = `${out}
|
||||
|
||||
-- UNCERTAIN FLAGS
|
||||
AR: ${testResults.aspectRatioUncertain} (reason: ${testResults.aspectRatioUncertainReason ?? 'n/a'}); top row: ${testResults.topRowUncertain}; bottom row: ${testResults.bottomRowUncertain}${
|
||||
testResults.aspectRatioInvalid ? `\nINVALID_AR (reason: ${testResults.aspectRatioInvalidReason ?? 'n/a'})` : ''}
|
||||
|
||||
-- GUARD & IMAGE LINE
|
||||
bottom guard: ${testResults.guardLine.bottom} image: ${testResults.guardLine.invalidated ? 'n/a' : testResults.imageLine.bottom}
|
||||
top guard: ${testResults.guardLine.top} image: ${testResults.guardLine.invalidated ? 'n/a' : testResults.imageLine.top}
|
||||
|
||||
guard line ${testResults.guardLine.invalidated ? 'INVALIDATED' : 'valid'} image line ${testResults.guardLine.invalidated ? '<skipped test>' : testResults.imageLine.invalidated ? 'INVALIDATED' : 'valid'}
|
||||
|
||||
corner invalidations (invalid pixels -> verdict)
|
||||
|
||||
LEFT CENTER RIGHT
|
||||
bottom: ${testResults.guardLine.cornerPixelsViolated[0]} → ${testResults.guardLine.cornerViolated[0] ? '❌' : '◽'} ${testResults.guardLine.cornerPixelsViolated[1]} → ${testResults.guardLine.cornerViolated[1] ? '❌' : '◽'}
|
||||
top: ${testResults.guardLine.cornerPixelsViolated[2]} → ${testResults.guardLine.cornerViolated[2] ? '❌' : '◽'} ${testResults.guardLine.cornerPixelsViolated[3]} → ${testResults.guardLine.cornerViolated[3] ? '❌' : '◽'}
|
||||
|
||||
-- AR SCAN ${testResults.lastStage < 1 ? `
|
||||
DID NOT RUN THIS FRAME` : `
|
||||
|
||||
LEFT CENTER RIGHT CANDIDATE
|
||||
BOTTOM
|
||||
distance: ${testResults.aspectRatioCheck.bottomRows[0]} ${testResults.aspectRatioCheck.bottomRows[1]} ${testResults.aspectRatioCheck.bottomRows[2]} ${testResults.aspectRatioCheck.bottomCandidate}
|
||||
quality: ${testResults.aspectRatioCheck.bottomQuality[0]} ${testResults.aspectRatioCheck.bottomQuality[1]} ${testResults.aspectRatioCheck.bottomQuality[2]} ${testResults.aspectRatioCheck.bottomCandidateQuality}
|
||||
|
||||
TOP
|
||||
distance: ${testResults.aspectRatioCheck.topRows[0]} ${testResults.aspectRatioCheck.topRows[1]} ${testResults.aspectRatioCheck.topRows[2]} ${testResults.aspectRatioCheck.topCandidate}
|
||||
quality: ${testResults.aspectRatioCheck.topQuality[0]} ${testResults.aspectRatioCheck.topQuality[1]} ${testResults.aspectRatioCheck.topQuality[2]} ${testResults.aspectRatioCheck.topCandidateQuality}
|
||||
|
||||
Diff matrix:
|
||||
R-L C-R C-L
|
||||
bottom: ${testResults.aspectRatioCheck.bottomRowsDifferenceMatrix[0]} ${testResults.aspectRatioCheck.bottomRowsDifferenceMatrix[1]} ${testResults.aspectRatioCheck.bottomRowsDifferenceMatrix[2]}
|
||||
top: ${testResults.aspectRatioCheck.topRowsDifferenceMatrix[0]} ${testResults.aspectRatioCheck.topRowsDifferenceMatrix[1]} ${testResults.aspectRatioCheck.topRowsDifferenceMatrix[2]}
|
||||
`}
|
||||
`;
|
||||
|
||||
resultsDiv.textContent = out;
|
||||
}
|
||||
|
||||
private setOverlayVisibility() {
|
||||
document.getElementById('uw-aard-debug-ui_body').style.display = this.uiVisibility.detectionDetails ? 'flex' : 'none';
|
||||
document.getElementById('uw-aard-debug_hide-detection-details').style.display = this.uiVisibility.detectionDetails ? '' : 'none';
|
||||
document.getElementById('uw-aard-debug_show-detection-details').style.display = this.uiVisibility.detectionDetails ? 'none' : '';
|
||||
}
|
||||
}
|
||||
|
@ -1,112 +0,0 @@
|
||||
export interface AardPerformanceData {
|
||||
start: number;
|
||||
draw: number;
|
||||
getImage: number;
|
||||
|
||||
fastBlackLevel: number;
|
||||
guardLine: number; // actually times both guard line and image line checks
|
||||
edgeScan: number; // includes validation step
|
||||
gradient: number;
|
||||
scanResults: number;
|
||||
}
|
||||
|
||||
|
||||
export class AardTimer {
|
||||
private currentIndex: number = -1;
|
||||
|
||||
private aardPerformanceDataBuffer: AardPerformanceData[];
|
||||
|
||||
current: AardPerformanceData;
|
||||
previous: AardPerformanceData;
|
||||
average: AardPerformanceData;
|
||||
lastChange: AardPerformanceData;
|
||||
|
||||
|
||||
constructor() {
|
||||
// we need to deep clone, otherwise all buffer objects will point to the same object
|
||||
// (this makes calculating averages impossible)
|
||||
this.aardPerformanceDataBuffer = JSON.parse(JSON.stringify(new Array<AardPerformanceData>(64).fill(this.getEmptyMeasurement())));
|
||||
this.current = this.aardPerformanceDataBuffer[0];
|
||||
this.previous = undefined;
|
||||
this.lastChange = this.getEmptyMeasurement();
|
||||
this.average = this.getEmptyMeasurement();
|
||||
}
|
||||
|
||||
private getEmptyMeasurement(): AardPerformanceData {
|
||||
return {
|
||||
start: -1,
|
||||
draw: -1,
|
||||
getImage: -1,
|
||||
fastBlackLevel: -1,
|
||||
guardLine: -1,
|
||||
edgeScan: -1,
|
||||
gradient: -1,
|
||||
scanResults: -1
|
||||
}
|
||||
};
|
||||
|
||||
private clearMeasurement(index: number) {
|
||||
this.aardPerformanceDataBuffer[index].draw = -1;
|
||||
this.aardPerformanceDataBuffer[index].getImage = -1;
|
||||
this.aardPerformanceDataBuffer[index].fastBlackLevel = -1;
|
||||
this.aardPerformanceDataBuffer[index].guardLine = -1;
|
||||
this.aardPerformanceDataBuffer[index].edgeScan = -1;
|
||||
this.aardPerformanceDataBuffer[index].gradient = -1;
|
||||
this.aardPerformanceDataBuffer[index].scanResults = -1;
|
||||
}
|
||||
|
||||
next() {
|
||||
// go to next buffer position;
|
||||
this.currentIndex = (this.currentIndex + 1) % this.aardPerformanceDataBuffer.length;
|
||||
this.previous = this.current;
|
||||
this.clearMeasurement(this.currentIndex);
|
||||
|
||||
// TODO: reset values
|
||||
this.current = this.aardPerformanceDataBuffer[this.currentIndex];
|
||||
}
|
||||
|
||||
arChanged() { // copy numbers over
|
||||
this.lastChange.draw = this.current.draw;
|
||||
this.lastChange.getImage = this.current.getImage;
|
||||
this.lastChange.fastBlackLevel = this.current.fastBlackLevel;
|
||||
this.lastChange.guardLine = this.current.guardLine;
|
||||
this.lastChange.edgeScan = this.current.edgeScan;
|
||||
this.lastChange.gradient = this.current.gradient;
|
||||
this.lastChange.scanResults = this.current.scanResults;
|
||||
}
|
||||
|
||||
getAverage() {
|
||||
for (let i = 0; i < this.aardPerformanceDataBuffer.length; i++) {
|
||||
const sample = this.aardPerformanceDataBuffer[i];
|
||||
if (sample.draw !== -1) {
|
||||
this.average.draw += sample.draw;
|
||||
}
|
||||
if (sample.getImage !== -1) {
|
||||
this.average.getImage += sample.getImage;
|
||||
}
|
||||
if (sample.fastBlackLevel !== -1) {
|
||||
this.average.fastBlackLevel += sample.fastBlackLevel;
|
||||
}
|
||||
if (sample.guardLine !== -1) {
|
||||
this.average.guardLine += sample.guardLine;
|
||||
}
|
||||
if (sample.edgeScan !== -1) {
|
||||
this.average.edgeScan += sample.edgeScan;
|
||||
}
|
||||
if (sample.gradient !== -1) {
|
||||
this.average.gradient += sample.gradient;
|
||||
}
|
||||
if (sample.scanResults !== -1) {
|
||||
this.average.scanResults += sample.scanResults;
|
||||
}
|
||||
}
|
||||
|
||||
this.average.draw /= this.aardPerformanceDataBuffer.length;
|
||||
this.average.getImage /= this.aardPerformanceDataBuffer.length;
|
||||
this.average.fastBlackLevel /= this.aardPerformanceDataBuffer.length;
|
||||
this.average.guardLine /= this.aardPerformanceDataBuffer.length;
|
||||
this.average.edgeScan /= this.aardPerformanceDataBuffer.length;
|
||||
this.average.gradient /= this.aardPerformanceDataBuffer.length;
|
||||
this.average.scanResults /= this.aardPerformanceDataBuffer.length;
|
||||
}
|
||||
}
|
@ -2,9 +2,7 @@ import { GlCanvas, GlCanvasOptions } from './GlCanvas';
|
||||
|
||||
|
||||
export class FallbackCanvas extends GlCanvas {
|
||||
get type() {
|
||||
return 'legacy';
|
||||
}
|
||||
|
||||
context: CanvasRenderingContext2D;
|
||||
|
||||
constructor(options: GlCanvasOptions) {
|
||||
@ -26,7 +24,7 @@ export class FallbackCanvas extends GlCanvas {
|
||||
protected initWebgl() { }
|
||||
|
||||
drawVideoFrame(video: HTMLVideoElement) {
|
||||
this.context.drawImage(video, 0, 0, this.context.canvas.width, this.context.canvas.height);
|
||||
this.context.drawImage(video, this.context.canvas.width, this.context.canvas.height);
|
||||
}
|
||||
|
||||
getImageData() {
|
||||
|
@ -52,9 +52,6 @@ interface GlCanvasProgramInfo {
|
||||
}
|
||||
|
||||
export class GlCanvas {
|
||||
get type() {
|
||||
return 'webgl';
|
||||
}
|
||||
|
||||
private _canvas: HTMLCanvasElement;
|
||||
private set canvas(x: HTMLCanvasElement) {
|
||||
@ -68,7 +65,7 @@ export class GlCanvas {
|
||||
private set gl(x: WebGLRenderingContext) {
|
||||
this._context = x;
|
||||
};
|
||||
protected get gl(): WebGLRenderingContext {
|
||||
private get gl(): WebGLRenderingContext {
|
||||
return this._context;
|
||||
}
|
||||
|
||||
@ -83,7 +80,7 @@ export class GlCanvas {
|
||||
|
||||
private buffers: GlCanvasBuffers;
|
||||
private texture: WebGLTexture;
|
||||
protected programInfo: GlCanvasProgramInfo;
|
||||
private programInfo: GlCanvasProgramInfo;
|
||||
private projectionMatrix: mat4;
|
||||
|
||||
get width() {
|
||||
@ -106,7 +103,7 @@ export class GlCanvas {
|
||||
* Draws video frame to the GL canvas
|
||||
* @param video video to extract a frame from
|
||||
*/
|
||||
drawVideoFrame(video: HTMLVideoElement | HTMLCanvasElement): void {
|
||||
drawVideoFrame(video: HTMLVideoElement): void {
|
||||
this.updateTexture(video);
|
||||
this.drawScene();
|
||||
}
|
||||
@ -159,16 +156,7 @@ export class GlCanvas {
|
||||
);
|
||||
|
||||
if (!this.gl) {
|
||||
try {
|
||||
this.gl = this.canvas.getContext(
|
||||
"webgl",
|
||||
{
|
||||
preserveDrawingBuffer: true
|
||||
}
|
||||
);
|
||||
} catch (e) {
|
||||
throw new Error('WebGL not supported');
|
||||
}
|
||||
throw new Error('WebGL not supported');
|
||||
}
|
||||
if(options.id) {
|
||||
this.canvas.setAttribute('id', options.id);
|
||||
@ -224,30 +212,24 @@ export class GlCanvas {
|
||||
this.frameBuffer = new Uint8Array(this.frameBufferSize);
|
||||
}
|
||||
|
||||
protected loadShader(type, source) {
|
||||
private loadShader(type, source) {
|
||||
const shader = this.gl.createShader(type);
|
||||
this.gl.shaderSource(shader, source);
|
||||
this.gl.compileShader(shader);
|
||||
|
||||
// TODO: warn if shader failed to compile
|
||||
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
|
||||
console.warn('DEBUG: Shader Compilation Error: ', type, this.gl.getShaderInfoLog(shader), '(cheat sheet: vertex shaders:', this.gl.VERTEX_SHADER, ')');
|
||||
this.gl.deleteShader(shader);
|
||||
return null;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
protected loadShaders() {
|
||||
private initShaderProgram() {
|
||||
const vertexShader = this.loadShader(this.gl.VERTEX_SHADER, vsSource);
|
||||
const fragmentShader = this.loadShader(this.gl.FRAGMENT_SHADER, fsSource);
|
||||
|
||||
return {vertexShader, fragmentShader};
|
||||
}
|
||||
|
||||
private initShaderProgram() {
|
||||
const {vertexShader, fragmentShader} = this.loadShaders();
|
||||
|
||||
// Create the shader program
|
||||
const shaderProgram = this.gl.createProgram();
|
||||
this.gl.attachShader(shaderProgram, vertexShader);
|
||||
@ -256,7 +238,6 @@ export class GlCanvas {
|
||||
|
||||
// TODO: maybe give a warning if program failed to initialize
|
||||
if (!this.gl.getProgramParameter(shaderProgram, this.gl.LINK_STATUS)) {
|
||||
console.warn('DEBUG — FAILED TO LINK SHADER PROGRAM', this.gl.getProgramInfoLog(shaderProgram))
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -298,7 +279,7 @@ export class GlCanvas {
|
||||
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
|
||||
}
|
||||
|
||||
protected updateTexture(video: HTMLVideoElement | HTMLCanvasElement | null) {
|
||||
private updateTexture(video: HTMLVideoElement) {
|
||||
const level = 0;
|
||||
const internalFormat = this.gl.RGBA;
|
||||
const srcFormat = this.gl.RGBA;
|
||||
@ -352,7 +333,7 @@ export class GlCanvas {
|
||||
this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition);
|
||||
}
|
||||
|
||||
protected drawScene(): void {
|
||||
private drawScene(): void {
|
||||
/**
|
||||
* Since we are drawing our frames in a way such that the entire canvas is
|
||||
* always covered by rendered video, and given our video is the only object
|
||||
|
@ -1,162 +0,0 @@
|
||||
import { GlCanvas, GlCanvasOptions } from './GlCanvas';
|
||||
|
||||
|
||||
const vsSource = `
|
||||
attribute vec4 aVertexPosition;
|
||||
attribute vec3 aVertexNormal;
|
||||
attribute vec2 aTextureCoord;
|
||||
|
||||
uniform mat4 uNormalMatrix;
|
||||
uniform mat4 uModelViewMatrix;
|
||||
uniform mat4 uProjectionMatrix;
|
||||
|
||||
varying highp vec2 vTextureCoord;
|
||||
|
||||
void main(void) {
|
||||
gl_Position = uProjectionMatrix * aVertexPosition;
|
||||
vTextureCoord = vec2(aTextureCoord.x, 1.0 - aTextureCoord.y);
|
||||
// vTextureCoord = aTextureCoord;
|
||||
}
|
||||
`;
|
||||
|
||||
const fSource = `
|
||||
precision mediump float;
|
||||
|
||||
uniform sampler2D u_texture;
|
||||
// uniform sampler1D u_colorTexture; // Array of replacement colors
|
||||
uniform vec3 u_colors[16];
|
||||
varying vec2 vTextureCoord;
|
||||
|
||||
void main() {
|
||||
vec4 color = texture2D(u_texture, vTextureCoord);
|
||||
int alphaIndex = int(color.a * 255.0);
|
||||
|
||||
if (alphaIndex == 255) { // convert to grayscale on normal alpha
|
||||
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
|
||||
gl_FragColor = vec4(vec3(gray), 1.0);
|
||||
} else if (alphaIndex < 16) { // use custom color where possible
|
||||
// no 1D textures in webgl, only webgl2 maybe
|
||||
// vec3 selectedColor = texture1D(u_colorTexture, float(alphaIndex) / 15.0).rgb;
|
||||
// gl_FragColor = vec4(selectedColor, 1.0);
|
||||
|
||||
vec3 selectedColor;
|
||||
if (alphaIndex == 0) selectedColor = u_colors[0];
|
||||
else if (alphaIndex == 1) selectedColor = u_colors[1];
|
||||
else if (alphaIndex == 2) selectedColor = u_colors[2];
|
||||
else if (alphaIndex == 3) selectedColor = u_colors[3];
|
||||
else if (alphaIndex == 4) selectedColor = u_colors[4];
|
||||
else if (alphaIndex == 5) selectedColor = u_colors[5];
|
||||
else if (alphaIndex == 6) selectedColor = u_colors[6];
|
||||
else if (alphaIndex == 7) selectedColor = u_colors[7];
|
||||
else if (alphaIndex == 8) selectedColor = u_colors[8];
|
||||
else if (alphaIndex == 9) selectedColor = u_colors[9];
|
||||
else if (alphaIndex == 10) selectedColor = u_colors[10];
|
||||
else if (alphaIndex == 11) selectedColor = u_colors[11];
|
||||
else if (alphaIndex == 12) selectedColor = u_colors[12];
|
||||
else if (alphaIndex == 13) selectedColor = u_colors[13];
|
||||
else if (alphaIndex == 14) selectedColor = u_colors[14];
|
||||
else selectedColor = u_colors[15];
|
||||
|
||||
gl_FragColor = vec4(selectedColor, 1.0);
|
||||
} else { // red channel only as fallback
|
||||
gl_FragColor = vec4(color.r, 0.0, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export enum GlDebugType {
|
||||
BlackLevelSample = 0,
|
||||
GuardLineOk = 1,
|
||||
GuardLineViolation = 2,
|
||||
GuardLineCornerOk = 3,
|
||||
GuardLineCornerViolation = 4,
|
||||
ImageLineThresholdReached = 5,
|
||||
ImageLineOk = 6,
|
||||
ImageLineFail = 7,
|
||||
EdgeScanProbe = 8,
|
||||
EdgeScanHit = 9,
|
||||
SlopeTestDarkOk = 10,
|
||||
SlopeTestDarkViolation = 11,
|
||||
|
||||
}
|
||||
|
||||
export class GlDebugCanvas extends GlCanvas {
|
||||
|
||||
private debugColors = [
|
||||
0.1, 0.1, 0.35, // 0 - black level sample
|
||||
0.3, 1.0, 0.6, // 1 - guard line ok
|
||||
1.0, 0.1, 0.1, // 2 - guard line violation
|
||||
0.1, 0.5, 0.3, // 3 - guard line corner ok
|
||||
0.5, 0.0, 0.0, // 4 - guard line corner violation
|
||||
1.0, 1.0, 1.0, // 5 - image line threshold reached (stop checking)
|
||||
0.7, 0.7, 0.7, // 6 - image line ok
|
||||
0.2, 0.2, 0.6, // 7 - image line fail
|
||||
0.1, 0.1, 0.4, // 8 - edge scan probe
|
||||
0.4, 0.4, 1.0, // 9 - edge scan hit
|
||||
0.2, 0.4, 0.6, // 10 - slope test ok
|
||||
1.0, 0.0, 0.0, // 11 - slope test fail
|
||||
0.0, 0.0, 0.0, // 12
|
||||
0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0,
|
||||
0.0, 0.0, 0.0,
|
||||
];
|
||||
|
||||
constructor (options: GlCanvasOptions) {
|
||||
super(options);
|
||||
this.canvas.id = options.id;
|
||||
}
|
||||
|
||||
protected loadShaders() {
|
||||
const vertexShader = this.loadShader(this.gl.VERTEX_SHADER, vsSource);
|
||||
const fragmentShader = this.loadShader(this.gl.FRAGMENT_SHADER, fSource);
|
||||
|
||||
return {vertexShader, fragmentShader};
|
||||
}
|
||||
|
||||
show() {
|
||||
this.enableFx();
|
||||
|
||||
// this.canvas.style.position = 'fixed';
|
||||
// this.canvas.style.top = '0';
|
||||
// this.canvas.style.right = '0';
|
||||
// this.canvas.style.zIndex = '99999999';
|
||||
// this.canvas.style.transform = 'scale(3)';
|
||||
// this.canvas.style.transformOrigin = 'top right';
|
||||
// this.canvas.style.imageRendering = 'pixelated';
|
||||
|
||||
// document.body.appendChild(
|
||||
// this.canvas
|
||||
// );
|
||||
}
|
||||
|
||||
enableFx() {
|
||||
this.gl.useProgram(this.programInfo.program)
|
||||
this.gl.uniform3fv((this.programInfo.uniformLocations as any).debugColors, this.debugColors);
|
||||
}
|
||||
|
||||
drawBuffer(buffer: Uint8Array) {
|
||||
this.updateTextureBuffer(buffer);
|
||||
}
|
||||
|
||||
protected initWebgl() {
|
||||
super.initWebgl();
|
||||
|
||||
(this.programInfo.uniformLocations as any).debugColors = this.gl.getUniformLocation(this.programInfo.program, 'u_colors');
|
||||
}
|
||||
|
||||
protected updateTextureBuffer(buffer: Uint8Array) {
|
||||
// this.updateTexture(null);
|
||||
this.gl.texSubImage2D(
|
||||
this.gl.TEXTURE_2D,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
this.width,
|
||||
this.height,
|
||||
this.gl.RGBA,
|
||||
this.gl.UNSIGNED_BYTE,
|
||||
buffer
|
||||
);
|
||||
this.drawScene();
|
||||
};
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
export interface GlCanvasBuffers {
|
||||
position: WebGLBuffer,
|
||||
normal: WebGLBuffer,
|
||||
textureCoord: WebGLBuffer,
|
||||
indices: WebGLBuffer,
|
||||
};
|
||||
|
||||
export function initBuffers(gl: WebGLRenderingContext): GlCanvasBuffers {
|
||||
const positionBuffer = initPositionBuffer(gl);
|
||||
const textureCoordBuffer = initTextureBuffer(gl);
|
||||
const indexBuffer = initIndexBuffer(gl);
|
||||
const normalBuffer = initNormalBuffer(gl);
|
||||
|
||||
return {
|
||||
position: positionBuffer,
|
||||
normal: normalBuffer,
|
||||
textureCoord: textureCoordBuffer,
|
||||
indices: indexBuffer,
|
||||
};
|
||||
}
|
||||
|
||||
function initPositionBuffer(gl: WebGLRenderingContext) {
|
||||
const positionBuffer = gl.createBuffer();
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
const positions = [
|
||||
-1, -1, 1, -1, -1, 1,
|
||||
-1, 1, 1, -1, 1, 1
|
||||
];
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
|
||||
|
||||
return positionBuffer;
|
||||
}
|
||||
|
||||
function initIndexBuffer(gl: WebGLRenderingContext) {
|
||||
const indexBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
||||
|
||||
const indices = [
|
||||
0, 1, 2,
|
||||
0, 2, 3,
|
||||
];
|
||||
|
||||
gl.bufferData(
|
||||
gl.ELEMENT_ARRAY_BUFFER,
|
||||
new Uint16Array(indices),
|
||||
gl.STATIC_DRAW
|
||||
);
|
||||
|
||||
return indexBuffer;
|
||||
}
|
||||
|
||||
function initTextureBuffer(gl: WebGLRenderingContext) {
|
||||
const textureCoordBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, textureCoordBuffer);
|
||||
|
||||
const texCoordBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
|
||||
const texCoords = [
|
||||
0, 1, 1, 1, 0, 0,
|
||||
0, 0, 1, 1, 1, 0
|
||||
];
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW);
|
||||
|
||||
return textureCoordBuffer;
|
||||
}
|
||||
|
||||
function initNormalBuffer(gl: WebGLRenderingContext) {
|
||||
const normalBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
|
||||
|
||||
const vertexNormals = [
|
||||
0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
|
||||
];
|
||||
|
||||
gl.bufferData(
|
||||
gl.ARRAY_BUFFER,
|
||||
new Float32Array(vertexNormals),
|
||||
gl.STATIC_DRAW
|
||||
);
|
||||
|
||||
return normalBuffer;
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
import { GlCanvas } from '../gl/GlCanvas';
|
||||
import { GlDebugCanvas } from '../gl/GlDebugCanvas';
|
||||
|
||||
export interface AardCanvasStore {
|
||||
main: GlCanvas;
|
||||
debug?: GlDebugCanvas;
|
||||
}
|
||||
|
@ -39,9 +39,7 @@ export interface AardTestResults {
|
||||
letterboxWidth: number,
|
||||
letterboxOffset: number,
|
||||
logoDetected: [boolean, boolean, boolean, boolean]
|
||||
aspectRatioInvalid: boolean
|
||||
aspectRatioUncertainReason?: string
|
||||
aspectRatioInvalidReason?: string
|
||||
}
|
||||
|
||||
export function initAardTestResults(settings: AardSettings): AardTestResults {
|
||||
@ -83,8 +81,7 @@ export function initAardTestResults(settings: AardSettings): AardTestResults {
|
||||
activeAspectRatio: 0,
|
||||
letterboxWidth: 0,
|
||||
letterboxOffset: 0,
|
||||
logoDetected: [false, false, false, false],
|
||||
aspectRatioInvalid: false,
|
||||
logoDetected: [false, false, false, false]
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,5 +120,4 @@ export function resetAardTestResults(results: AardTestResults): void {
|
||||
results.aspectRatioUncertainReason = null;
|
||||
results.topRowUncertain = false;
|
||||
results.bottomRowUncertain = false;
|
||||
results.aspectRatioInvalid = false;
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
import Logger from '../Logger';
|
||||
import Settings from '../Settings';
|
||||
import EventBus, { EventBusContext } from '../EventBus';
|
||||
import { ComponentLogger } from '../logging/ComponentLogger';
|
||||
import { LogAggregator } from '../logging/LogAggregator';
|
||||
|
||||
if (process.env.CHANNEL !== 'stable'){
|
||||
console.info("Loading CommsClient");
|
||||
@ -72,7 +73,7 @@ class CommsClient {
|
||||
name: string;
|
||||
origin: CommsOrigin;
|
||||
|
||||
logger: ComponentLogger;
|
||||
logger: Logger;
|
||||
settings: any; // sus?
|
||||
|
||||
eventBus: EventBus;
|
||||
@ -81,10 +82,10 @@ class CommsClient {
|
||||
port: chrome.runtime.Port;
|
||||
|
||||
//#region lifecycle
|
||||
constructor(name: string, logAggregator: LogAggregator, eventBus: EventBus) {
|
||||
constructor(name: string, logger: Logger, eventBus: EventBus) {
|
||||
this.name = name;
|
||||
try {
|
||||
this.logger = new ComponentLogger(logAggregator, 'CommsClient', {});
|
||||
this.logger = logger;
|
||||
this.eventBus = eventBus;
|
||||
|
||||
if (name === 'popup-port') {
|
||||
@ -100,16 +101,16 @@ class CommsClient {
|
||||
this.port = chrome.runtime.connect(null, {name: name});
|
||||
// }
|
||||
|
||||
// this.logger.onLogEnd(
|
||||
// (history) => {
|
||||
// this.logger.log('info', 'comms', 'Sending logging-stop-and-save to background script ...');
|
||||
// try {
|
||||
// this.port.postMessage({cmd: 'logging-stop-and-save', host: window.location.hostname, history})
|
||||
// } catch (e) {
|
||||
// this.logger.log('error', 'comms', 'Failed to send message to background script. Error:', e);
|
||||
// }
|
||||
// }
|
||||
// );
|
||||
this.logger.onLogEnd(
|
||||
(history) => {
|
||||
this.logger.log('info', 'comms', 'Sending logging-stop-and-save to background script ...');
|
||||
try {
|
||||
this.port.postMessage({cmd: 'logging-stop-and-save', host: window.location.hostname, history})
|
||||
} catch (e) {
|
||||
this.logger.log('error', 'comms', 'Failed to send message to background script. Error:', e);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this._listener = m => this.processReceivedMessage(m);
|
||||
this.port.onMessage.addListener(this._listener);
|
||||
@ -117,7 +118,7 @@ class CommsClient {
|
||||
this.commsId = (Math.random() * 20).toFixed(0);
|
||||
|
||||
} catch (e) {
|
||||
console.error("CONSTRUCTOR FAILED:", e)
|
||||
console.error("CONSTRUCOTR FAILED:", e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,9 +129,7 @@ class CommsClient {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
async sendMessage(message, context?: EventBusContext, borderCrossings?){
|
||||
this.logger.info('sendMessage', ' <<< Sending message to background script:', message);
|
||||
|
||||
async sendMessage(message, context?: EventBusContext){
|
||||
message = JSON.parse(JSON.stringify(message)); // vue quirk. We should really use vue store instead
|
||||
|
||||
// content script client and popup client differ in this one thing
|
||||
@ -144,11 +143,8 @@ class CommsClient {
|
||||
return port.postMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
// send to server
|
||||
if (!context?.borderCrossings?.commsServer) {
|
||||
return chrome.runtime.sendMessage(null, message, null);
|
||||
}
|
||||
return chrome.runtime.sendMessage(null, message, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -173,10 +169,7 @@ class CommsClient {
|
||||
message.config,
|
||||
{
|
||||
comms,
|
||||
origin: CommsOrigin.Server,
|
||||
borderCrossings: {
|
||||
commsServer: true
|
||||
}
|
||||
origin: CommsOrigin.Server
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { ComponentLogger } from './../logging/ComponentLogger';
|
||||
import { EventBusContext } from './../EventBus';
|
||||
import Debug from '../../conf/Debug';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
import Logger from '../Logger';
|
||||
import Settings from '../Settings';
|
||||
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
|
||||
import EventBus from '../EventBus';
|
||||
import { CommsOrigin } from './CommsClient';
|
||||
|
||||
|
||||
const BASE_LOGGING_STYLES = {
|
||||
log: "background-color: #11D; color: #aad",
|
||||
};
|
||||
|
||||
class CommsServer {
|
||||
server: any;
|
||||
logger: ComponentLogger;
|
||||
logger: Logger;
|
||||
settings: Settings;
|
||||
eventBus: EventBus;
|
||||
|
||||
@ -52,33 +52,16 @@ class CommsServer {
|
||||
} = {};
|
||||
popupPort: any;
|
||||
|
||||
private _lastActiveTab: chrome.tabs.Tab | undefined;
|
||||
//#region getters
|
||||
get activeTab(): Promise<chrome.tabs.Tab | undefined> {
|
||||
return new Promise((resolve, reject) => {
|
||||
chrome.tabs
|
||||
.query({currentWindow: true, active: true})
|
||||
.then((tabs) => {
|
||||
if (tabs.length === 0) {
|
||||
this.logger.warn('<getter-activeTab>', 'no active tab found, returning last valid active tab instead ...', this._lastActiveTab);
|
||||
resolve(this._lastActiveTab);
|
||||
} else {
|
||||
this.logger.log('<getter-activeTab>', 'getting active tab', tabs[0]);
|
||||
this._lastActiveTab = tabs[0];
|
||||
resolve(tabs[0]);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.error('<getter-activeTab>', 'error while getting active tab — returned last valid active tab instead ...', err, this._lastActiveTab);
|
||||
});
|
||||
});
|
||||
get activeTab() {
|
||||
return chrome.tabs.query({currentWindow: true, active: true});
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region lifecycle
|
||||
constructor(server) {
|
||||
this.server = server;
|
||||
this.logger = new ComponentLogger(server.logAggregator, 'CommsServer', {styles: BASE_LOGGING_STYLES});
|
||||
this.logger = server.logger;
|
||||
this.settings = server.settings;
|
||||
this.eventBus = server.eventBus;
|
||||
|
||||
@ -122,45 +105,10 @@ class CommsServer {
|
||||
|
||||
//#endregion
|
||||
|
||||
/**
|
||||
* Lists all unique hosts that are present in all the frames of a given tab.
|
||||
* This includes both hostname of the tab, as well as of all iframes embedded in it.
|
||||
* @returns
|
||||
*/
|
||||
async listUniqueFrameHosts() {
|
||||
const aTab = await this.activeTab;
|
||||
|
||||
const tabPort = this.ports[aTab.id];
|
||||
const hosts = [];
|
||||
|
||||
for (const frame in tabPort) {
|
||||
for (const portName in tabPort[frame]) {
|
||||
const port = tabPort[frame][portName];
|
||||
|
||||
const host = port.sender.origin.split('://')[1];
|
||||
|
||||
// if host is invalid or already exists in our list, skip adding it
|
||||
if (!host || hosts.includes(host)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hosts.push(host);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('uniq hosts:', hosts)
|
||||
return hosts;
|
||||
}
|
||||
|
||||
sendMessage(message, context?) {
|
||||
this.logger.debug('sendMessage', `preparing to send message ${message.command ?? ''} ...`, {message, context});
|
||||
// stop messages from returning where they came from, and prevent
|
||||
// cross-pollination between content scripts running in different
|
||||
// tabs.
|
||||
if (!context) {
|
||||
this.logger.debug('sendMessage', 'context was not passed in as parameter - does message have context?', message.context);
|
||||
context = message.context;
|
||||
}
|
||||
|
||||
if (context?.origin !== CommsOrigin.ContentScript) {
|
||||
if (context?.comms.forwardTo === 'all') {
|
||||
@ -181,16 +129,11 @@ class CommsServer {
|
||||
|
||||
// okay I lied! Messages originating from content script can be forwarded to
|
||||
// content scripts running in _other_ frames of the tab
|
||||
let forwarded = false;
|
||||
if (context?.origin === CommsOrigin.ContentScript) {
|
||||
if (context?.comms.forwardTo === 'all-frames') {
|
||||
forwarded = true;
|
||||
this.sendToOtherFrames(message, context);
|
||||
}
|
||||
}
|
||||
if (!forwarded) {
|
||||
this.logger.warn('sendMessage', `message ${message.command ?? ''} was not forwarded to any destination!`, {message, context});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -205,13 +148,10 @@ class CommsServer {
|
||||
* Does NOT send a message to popup.
|
||||
**/
|
||||
private sendToAll(message){
|
||||
this.logger.info('sendToAll', "sending message to all content scripts", message);
|
||||
|
||||
for(const tid in this.ports){
|
||||
const tab = this.ports[tid];
|
||||
for(const frame in tab){
|
||||
for (const port in tab[frame]) {
|
||||
this.logger.info('sendToAll', ` <——— attempting to send message ${message.command ?? ''} to tab ${tab}, frame ${frame}, port ${port}`, message);
|
||||
tab[frame][port].postMessage(message);
|
||||
}
|
||||
}
|
||||
@ -228,11 +168,9 @@ class CommsServer {
|
||||
private async sendToFrameContentScripts(message, tab, frame, port?) {
|
||||
if (port !== undefined) {
|
||||
this.ports[tab][frame][port].postMessage(message);
|
||||
this.logger.info('sendToOtherFrames', ` <——— attempting to send message ${message.command ?? ''} to tab ${tab}, frame ${frame}, port ${port}`, message);
|
||||
return;
|
||||
}
|
||||
for (const framePort in this.ports[tab][frame]) {
|
||||
this.logger.info('sendToOtherFrames', ` <——— attempting to send message ${message.command ?? ''} to tab ${tab}, frame ${frame}`, message);
|
||||
this.ports[tab][frame][framePort].postMessage(JSON.parse(JSON.stringify(message)));
|
||||
}
|
||||
}
|
||||
@ -260,7 +198,7 @@ class CommsServer {
|
||||
}
|
||||
|
||||
private async sendToFrame(message, tab, frame, port?) {
|
||||
this.logger.info('sendToFrame', ` <——— attempting to send message ${message.command ?? ''} to tab ${tab}, frame ${frame}`, message);
|
||||
this.logger.log('info', 'comms', `%c[CommsServer::sendToFrame] attempting to send message to tab ${tab}, frame ${frame}`, "background: #dda; color: #11D", message);
|
||||
|
||||
if (isNaN(tab)) {
|
||||
if (frame === '__playing') {
|
||||
@ -274,32 +212,33 @@ class CommsServer {
|
||||
[tab, frame] = frame.split('-');
|
||||
}
|
||||
|
||||
this.logger.info('sendToFrame', ` <——— attempting to send message ${message.command ?? ''} to tab ${tab}, frame ${frame}`, message);
|
||||
this.logger.log('info', 'comms', `%c[CommsServer::sendToFrame] attempting to send message to tab ${tab}, frame ${frame}`, "background: #dda; color: #11D", message);
|
||||
|
||||
try {
|
||||
|
||||
this.sendToFrameContentScripts(message, tab, frame, port);
|
||||
} catch (e) {
|
||||
this.logger.error('sendToFrame', ` Sending message failed. Reason:`, e);
|
||||
this.logger.log('error', 'comms', `%c[CommsServer::sendToFrame] Sending message failed. Reason:`, "background: #dda; color: #11D", e);
|
||||
}
|
||||
}
|
||||
|
||||
private async sendToActive(message) {
|
||||
this.logger.info('sendToActive', ` <——— trying to send a message ${message.command ?? ''} to active tab. Message:`, message);
|
||||
this.logger.log('info', 'comms', "%c[CommsServer::sendToActive] trying to send a message to active tab. Message:", "background: #dda; color: #11D", message);
|
||||
|
||||
const tab = await this.activeTab;
|
||||
const tabs = await this.activeTab;
|
||||
|
||||
this.logger.info('sendToActive', "currently active tab?", tab);
|
||||
this.logger.log('info', 'comms', "[CommsServer::_sendToActive] currently active tab(s)?", tabs);
|
||||
for (const frame in this.ports[tabs[0].id]) {
|
||||
this.logger.log('info', 'comms', "key?", frame, this.ports[tabs[0].id]);
|
||||
}
|
||||
|
||||
for (const frame in this.ports[tab.id]) {
|
||||
this.logger.info('sendToActive', "sending message to frame:", frame, this.ports[tab.id][frame], '; message:', message);
|
||||
this.sendToFrameContentScripts(message, tab.id, frame);
|
||||
for (const frame in this.ports[tabs[0].id]) {
|
||||
this.sendToFrameContentScripts(message, tabs[0].id, frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async processReceivedMessage(message, port, sender?: {frameId: string, tabId: string}){
|
||||
this.logger.info('processMessage', ` ==> Received message ${message.command ?? ''} from content script or port`, "background-color: #11D; color: #aad", message, port, sender);
|
||||
// this triggers events
|
||||
this.eventBus.send(
|
||||
message.command,
|
||||
@ -320,7 +259,7 @@ class CommsServer {
|
||||
}
|
||||
|
||||
private processReceivedMessage_nonpersistent(message, sender){
|
||||
this.logger.info('processMessage_nonpersistent', ` ==> Received message from background script!`, message, sender);
|
||||
this.logger.log('info', 'comms', "%c[CommsServer.js::processMessage_nonpersistent] Received message from background script!", "background-color: #11D; color: #aad", message, sender);
|
||||
|
||||
this.eventBus.send(
|
||||
message.command,
|
||||
|
@ -1,11 +1,11 @@
|
||||
import EventBus, { EventBusCommand } from '../EventBus';
|
||||
import { ComponentLogger } from '../logging/ComponentLogger';
|
||||
import Logger from '../Logger';
|
||||
import Settings from '../Settings';
|
||||
import { SiteSettings } from '../settings/SiteSettings';
|
||||
|
||||
export class KbmBase {
|
||||
listenFor: string[] = [];
|
||||
logger: ComponentLogger;
|
||||
logger: Logger;
|
||||
settings: Settings;
|
||||
siteSettings: SiteSettings;
|
||||
eventBus: EventBus;
|
||||
@ -28,7 +28,7 @@ export class KbmBase {
|
||||
},
|
||||
}
|
||||
|
||||
constructor(eventBus: EventBus, siteSettings: SiteSettings, settings: Settings, logger: ComponentLogger) {
|
||||
constructor(eventBus: EventBus, siteSettings: SiteSettings, settings: Settings, logger: Logger) {
|
||||
this.logger = logger;
|
||||
this.settings = settings;
|
||||
this.eventBus = eventBus;
|
||||
|
@ -1,19 +1,18 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import PlayerData from '../video-data/PlayerData';
|
||||
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
|
||||
import Logger from '../Logger';
|
||||
import PageInfo from '../video-data/PageInfo';
|
||||
import Settings from '../Settings';
|
||||
import VideoData from '../video-data/VideoData';
|
||||
import EventBus, { EventBusCommand } from '../EventBus';
|
||||
import KbmBase from './KbmBase';
|
||||
import { SiteSettings } from '../settings/SiteSettings';
|
||||
import { LogAggregator } from '../logging/LogAggregator';
|
||||
import { ComponentLogger } from '../logging/ComponentLogger';
|
||||
|
||||
if(process.env.CHANNEL !== 'stable'){
|
||||
console.info("Loading KeyboardHandler");
|
||||
}
|
||||
|
||||
const BASE_LOGGING_STYLES = {
|
||||
log: "color: #ff0"
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles keypresses and mouse movement.
|
||||
*
|
||||
@ -24,6 +23,7 @@ const BASE_LOGGING_STYLES = {
|
||||
*/
|
||||
export class KeyboardHandler extends KbmBase {
|
||||
listenFor: string[] = ['keyup'];
|
||||
logger: Logger;
|
||||
settings: Settings;
|
||||
siteSettings: SiteSettings;
|
||||
eventBus: EventBus;
|
||||
@ -45,18 +45,14 @@ export class KeyboardHandler extends KbmBase {
|
||||
}
|
||||
|
||||
//#region lifecycle
|
||||
constructor(eventBus: EventBus, siteSettings: SiteSettings, settings: Settings, logAggregator: LogAggregator) {
|
||||
const tmpLogger = new ComponentLogger(logAggregator, 'KeyboardHandler', {styles: BASE_LOGGING_STYLES});
|
||||
super(eventBus, siteSettings, settings, tmpLogger);
|
||||
constructor(eventBus: EventBus, siteSettings: SiteSettings, settings: Settings, logger: Logger) {
|
||||
super(eventBus, siteSettings, settings, logger);
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.logger.debug("init", "starting init");
|
||||
|
||||
// reset keypressActions when re-initializing, otherwise keypressActions will
|
||||
// multiply in an unwanted way
|
||||
this.keypressActions = [];
|
||||
this.logger.log('info', 'debug', "[KeyboardHandler::init] starting init");
|
||||
|
||||
// build the action list — but only from actions that have shortcuts assigned
|
||||
for (const key in this.settings.active.commands) {
|
||||
@ -119,28 +115,28 @@ export class KeyboardHandler extends KbmBase {
|
||||
preventAction(event) {
|
||||
var activeElement = document.activeElement;
|
||||
|
||||
// if (this.logger.canLog('keyboard')) {
|
||||
// this.logger.pause(); // temp disable to avoid recursing;
|
||||
// const preventAction = this.preventAction(event);
|
||||
// this.logger.resume(); // undisable
|
||||
if (this.logger.canLog('keyboard')) {
|
||||
this.logger.pause(); // temp disable to avoid recursing;
|
||||
const preventAction = this.preventAction(event);
|
||||
this.logger.resume(); // undisable
|
||||
|
||||
// this.logger.log('info', 'keyboard', "[KeyboardHandler::preventAction] Testing whether we're in a textbox or something. Detailed rundown of conditions:\n" +
|
||||
// "\nis tag one of defined inputs? (yes->prevent):", this.inputs.indexOf(activeElement.tagName.toLocaleLowerCase()) !== -1,
|
||||
// "\nis role = textbox? (yes -> prevent):", activeElement.getAttribute("role") === "textbox",
|
||||
// "\nis type === 'text'? (yes -> prevent):", activeElement.getAttribute("type") === "text",
|
||||
// "\nevent.target.isContentEditable? (yes -> prevent):", event.target.isContentEditable,
|
||||
// "\nis keyboard local disabled? (yes -> prevent):", this.keyboardLocalDisabled,
|
||||
// // "\nis keyboard enabled in settings? (no -> prevent)", this.settings.keyboardShortcutsEnabled(window.location.hostname),
|
||||
// "\nwill the action be prevented? (yes -> prevent)", preventAction,
|
||||
// "\n-----------------{ extra debug info }-------------------",
|
||||
// "\ntag name? (lowercase):", activeElement.tagName, activeElement.tagName.toLocaleLowerCase(),
|
||||
// "\nrole:", activeElement.getAttribute('role'),
|
||||
// "\ntype:", activeElement.getAttribute('type'),
|
||||
// "\ninsta-fail inputs:", this.inputs,
|
||||
// "\nevent:", event,
|
||||
// "\nevent.target:", event.target
|
||||
// );
|
||||
// }
|
||||
this.logger.log('info', 'keyboard', "[KeyboardHandler::preventAction] Testing whether we're in a textbox or something. Detailed rundown of conditions:\n" +
|
||||
"\nis tag one of defined inputs? (yes->prevent):", this.inputs.indexOf(activeElement.tagName.toLocaleLowerCase()) !== -1,
|
||||
"\nis role = textbox? (yes -> prevent):", activeElement.getAttribute("role") === "textbox",
|
||||
"\nis type === 'text'? (yes -> prevent):", activeElement.getAttribute("type") === "text",
|
||||
"\nevent.target.isContentEditable? (yes -> prevent):", event.target.isContentEditable,
|
||||
"\nis keyboard local disabled? (yes -> prevent):", this.keyboardLocalDisabled,
|
||||
// "\nis keyboard enabled in settings? (no -> prevent)", this.settings.keyboardShortcutsEnabled(window.location.hostname),
|
||||
"\nwill the action be prevented? (yes -> prevent)", preventAction,
|
||||
"\n-----------------{ extra debug info }-------------------",
|
||||
"\ntag name? (lowercase):", activeElement.tagName, activeElement.tagName.toLocaleLowerCase(),
|
||||
"\nrole:", activeElement.getAttribute('role'),
|
||||
"\ntype:", activeElement.getAttribute('type'),
|
||||
"\ninsta-fail inputs:", this.inputs,
|
||||
"\nevent:", event,
|
||||
"\nevent.target:", event.target
|
||||
);
|
||||
}
|
||||
|
||||
if (this.keyboardLocalDisabled) {
|
||||
return true;
|
||||
@ -213,24 +209,29 @@ export class KeyboardHandler extends KbmBase {
|
||||
|
||||
|
||||
handleKeyup(event) {
|
||||
this.logger.info('handleKeyup', "we pressed a key: ", event.key , " | keyup: ", event.keyup, "event:", event);
|
||||
// if (!this.keyboardEnabled) {
|
||||
// this.logger.log('info', 'keyboard', "%c[KeyboardHandler::handleKeyup] kbmHandler.keyboardEnabled is set to false. Doing nothing.");
|
||||
// return;
|
||||
// }
|
||||
this.logger.log('info', 'keyboard', "%c[KeyboardHandler::handleKeyup] we pressed a key: ", "color: #ff0", event.key , " | keyup: ", event.keyup, "event:", event);
|
||||
|
||||
try {
|
||||
if (this.preventAction(event)) {
|
||||
this.logger.info('handleKeyup', "we are in a text box or something. Doing nothing.");
|
||||
this.logger.log('info', 'keyboard', "[KeyboardHandler::handleKeyup] we are in a text box or something. Doing nothing.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.info('handleKeyup', "Trying to find and execute action for event. Actions/event:", this.keypressActions, event);
|
||||
this.logger.log('info', 'keyboard', "%c[KeyboardHandler::handleKeyup] Trying to find and execute action for event. Actions/event: ", "color: #ff0", this.keypressActions, event);
|
||||
|
||||
const isLatin = this.isLatin(event.key);
|
||||
|
||||
for (const command of this.keypressActions) {
|
||||
if (this.isActionMatch(command.shortcut, event, isLatin)) {
|
||||
this.eventBus.send(command.action, command.arguments);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.debug('handleKeyup', 'Failed to handle keyup!', e);
|
||||
this.logger.log('info', 'debug', '[KeyboardHandler::handleKeyup] Failed to handle keyup!', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { LogAggregator } from './../logging/LogAggregator';
|
||||
import EventBus, { EventBusCommand } from '../EventBus';
|
||||
import { ComponentLogger } from '../logging/ComponentLogger';
|
||||
import Logger from '../Logger';
|
||||
import Settings from '../Settings';
|
||||
import { SiteSettings } from '../settings/SiteSettings';
|
||||
import KbmBase from './KbmBase';
|
||||
@ -10,10 +9,6 @@ if(process.env.CHANNEL !== 'stable'){
|
||||
}
|
||||
|
||||
|
||||
const BASE_LOGGING_STYLES = {
|
||||
log: "color: #ff0"
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles keypress
|
||||
*/
|
||||
@ -41,21 +36,21 @@ export class MouseHandler extends KbmBase {
|
||||
}
|
||||
|
||||
//#region lifecycle
|
||||
constructor(playerElement: HTMLElement, eventBus: EventBus, siteSettings: SiteSettings, settings: Settings, logAggregator: LogAggregator) {
|
||||
const tmpLogger = new ComponentLogger(logAggregator, 'MouseHandler', {styles: BASE_LOGGING_STYLES});
|
||||
|
||||
super(eventBus, siteSettings, settings, tmpLogger);
|
||||
constructor(playerElement: HTMLElement, eventBus: EventBus, siteSettings: SiteSettings, settings: Settings, logger: Logger) {
|
||||
super(eventBus, siteSettings, settings, logger);
|
||||
|
||||
this.logger = logger;
|
||||
this.settings = settings;
|
||||
this.siteSettings = siteSettings;
|
||||
this.eventBus = eventBus;
|
||||
this.playerElement = playerElement;
|
||||
|
||||
this.init();
|
||||
|
||||
}
|
||||
|
||||
init() {
|
||||
// this.logger.debug('init', 'starting init');
|
||||
this.logger.log('info', 'debug', '[MouseHandler::init] starting init');
|
||||
}
|
||||
|
||||
load() {
|
||||
|
@ -1,74 +0,0 @@
|
||||
import { LogAggregator, LogSourceOptions } from './LogAggregator';
|
||||
|
||||
export enum LogLevel {
|
||||
Debug = 'debug',
|
||||
Info = 'info',
|
||||
Log = 'log',
|
||||
Warn = 'warn',
|
||||
Error = 'error',
|
||||
}
|
||||
|
||||
export type ComponentLoggerOptions = {
|
||||
styles?: {
|
||||
[x in LogLevel]?: string;
|
||||
}
|
||||
}
|
||||
|
||||
export class ComponentLogger {
|
||||
private logAggregator: LogAggregator;
|
||||
private component: string;
|
||||
private componentOptions?: ComponentLoggerOptions;
|
||||
|
||||
constructor(logAggregator: LogAggregator, component: string, componentOptions?: ComponentLoggerOptions) {
|
||||
this.logAggregator = logAggregator;
|
||||
this.component = component;
|
||||
this.componentOptions = componentOptions;
|
||||
}
|
||||
|
||||
private handleLog(logLevel: LogLevel, sourceFunction: string | LogSourceOptions, ...message: any) {
|
||||
let functionSource = typeof sourceFunction === 'string' ? sourceFunction : sourceFunction?.src;
|
||||
|
||||
let consoleMessageString = `[${this.component}${functionSource ? `::${functionSource}` : ''}]`;
|
||||
const consoleMessageData = []
|
||||
|
||||
for (const m of message) {
|
||||
if (typeof m === 'string') {
|
||||
consoleMessageString = `${consoleMessageString} ${m}`;
|
||||
} else if (typeof m === 'number') {
|
||||
consoleMessageString = `${consoleMessageString} %f`;
|
||||
consoleMessageData.unshift(m);
|
||||
} else if (typeof HTMLElement !== 'undefined' && m instanceof HTMLElement) { // HTMLElement does not exist in background script, but this class may
|
||||
consoleMessageString = `${consoleMessageString} %o`;
|
||||
consoleMessageData.unshift(m);
|
||||
} else {
|
||||
consoleMessageString = `${consoleMessageString} %O`;
|
||||
consoleMessageData.unshift(m);
|
||||
}
|
||||
}
|
||||
|
||||
const style = this.componentOptions?.styles?.[logLevel] ?? this.componentOptions?.styles?.[LogLevel.Log];
|
||||
if (style) {
|
||||
consoleMessageString = `%c${consoleMessageString}`;
|
||||
consoleMessageData.unshift(style);
|
||||
}
|
||||
|
||||
this.logAggregator.log(this.component, logLevel, typeof sourceFunction === 'object' ? sourceFunction : undefined, consoleMessageString, ...consoleMessageData);
|
||||
}
|
||||
|
||||
debug(sourceFunction: string | LogSourceOptions, ...message: any[]) {
|
||||
this.handleLog(LogLevel.Debug, sourceFunction, ...message);
|
||||
}
|
||||
info(sourceFunction: string | LogSourceOptions, ...message: any[]) {
|
||||
this.handleLog(LogLevel.Info, sourceFunction, ...message);
|
||||
}
|
||||
log(sourceFunction: string | LogSourceOptions, ...message: any[]) {
|
||||
this.handleLog(LogLevel.Log, sourceFunction, ...message);
|
||||
}
|
||||
warn(sourceFunction: string | LogSourceOptions, ...message: any[]) {
|
||||
this.handleLog(LogLevel.Warn, sourceFunction, ...message);
|
||||
}
|
||||
error(sourceFunction: string | LogSourceOptions, ...message: any[]) {
|
||||
this.handleLog(LogLevel.Error, sourceFunction, ...message);
|
||||
}
|
||||
|
||||
}
|
@ -1,232 +0,0 @@
|
||||
import { log } from 'console';
|
||||
|
||||
export const BLANK_LOGGER_CONFIG: LogConfig = {
|
||||
logToConsole: false,
|
||||
logToFile: false,
|
||||
component: {
|
||||
},
|
||||
origins: {
|
||||
videoRescan: { disabled: true},
|
||||
}
|
||||
}
|
||||
|
||||
export interface LogSourceOptions {
|
||||
src?: string;
|
||||
origin?: string;
|
||||
}
|
||||
|
||||
export interface LogComponentConfig {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface LogConfig {
|
||||
logToConsole?: boolean;
|
||||
logToFile?: boolean;
|
||||
stopAfter?: number;
|
||||
component?: {[x: string]: LogComponentConfig};
|
||||
origins?: {
|
||||
videoRescan?: {
|
||||
disabled: boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const STORAGE_LOG_SETTINGS_KEY = 'uw-log-config';
|
||||
|
||||
export class LogAggregator {
|
||||
private segment: string;
|
||||
private config: LogConfig;
|
||||
|
||||
private startTime: number;
|
||||
|
||||
history: any[];
|
||||
|
||||
static async getConfig() {
|
||||
let ret = await chrome.storage.local.get(STORAGE_LOG_SETTINGS_KEY);
|
||||
|
||||
if (process.env.CHANNEL === 'dev') {
|
||||
try {
|
||||
console.info("[LogAggregator::getSaved] Got settings:", JSON.parse(ret[STORAGE_LOG_SETTINGS_KEY]));
|
||||
} catch (e) {
|
||||
console.info("[LogAggregator::getSaved] No settings.", ret)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(ret[STORAGE_LOG_SETTINGS_KEY]);
|
||||
} catch (e) {
|
||||
return JSON.parse(JSON.stringify(BLANK_LOGGER_CONFIG));
|
||||
}
|
||||
}
|
||||
|
||||
static async saveConfig(conf: LogConfig) {
|
||||
try {
|
||||
const confCp = JSON.parse(JSON.stringify(conf));
|
||||
if (process.env.CHANNEL === 'dev') {
|
||||
console.info('Saving logger conf:', confCp)
|
||||
}
|
||||
|
||||
await chrome.storage.local.set({[STORAGE_LOG_SETTINGS_KEY]: JSON.stringify(confCp)} );
|
||||
} catch (e) {
|
||||
console.warn('[LogAggregator::saveConfig] Error while trying to save logger config:', e);
|
||||
}
|
||||
}
|
||||
|
||||
static syncConfig(callback: (x) => void) {
|
||||
chrome.storage.onChanged.addListener( (changes, area) => {
|
||||
if (changes.uwLogger) {
|
||||
const newLoggerConf = JSON.parse(changes.uwLogger.newValue)
|
||||
if (process.env.CHANNEL === 'dev') {
|
||||
console.info('Logger settings reloaded. New conf:', newLoggerConf);
|
||||
}
|
||||
callback(newLoggerConf);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
constructor(segment: string) {
|
||||
this.segment = segment;
|
||||
|
||||
chrome.storage.onChanged.addListener((changes, area) => {
|
||||
this.storageChangeListener(changes, area)
|
||||
});
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
private canLog(component: string, sourceOptions?: LogSourceOptions): boolean {
|
||||
// component is not in config, so we add a blank entry
|
||||
if (this.config && !this.config.component[component]) {
|
||||
this.config.component[component] = {enabled: false};
|
||||
LogAggregator.saveConfig(this.config);
|
||||
return false;
|
||||
}
|
||||
if (this.config?.component?.[component]?.enabled) {
|
||||
if (sourceOptions?.origin && this.config?.origins?.[sourceOptions.origin]?.disabled) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private storageChangeListener(changes, area) {
|
||||
if (!changes[STORAGE_LOG_SETTINGS_KEY]) {
|
||||
console.info('We dont have any logging settings, not processing frther', changes);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.config = JSON.parse(changes[STORAGE_LOG_SETTINGS_KEY].newValue);
|
||||
} catch (e) {
|
||||
console.warn('[uwLogger] Error while trying to parse new conf for logger:', e, '\nWe received the following changes:', changes, 'for area:', area);
|
||||
}
|
||||
|
||||
// This code can only execute if user tried to enable or disable logging
|
||||
// through the popup. In cases like this, we do not gate the console.log
|
||||
// behind a check, since we _always_ want to have this feedback in response
|
||||
// to an action.
|
||||
console.info(
|
||||
'[uwLogger] logger config changed! New configuration:',
|
||||
this.config, '\nraw changes:', changes, 'area?', area,
|
||||
'\n————————————————————————————————————————————————————————————————————————\n\n\n\n\n\n\n\n\n\n\n\n-----\nLogging with new settings starts now.'
|
||||
);
|
||||
|
||||
this.init(this.config);
|
||||
}
|
||||
|
||||
private parseStack() {
|
||||
const trace = (new Error()).stack;
|
||||
|
||||
const stackInfo: any = {};
|
||||
// we turn our stack into array and remove the "file::line" part of the trace,
|
||||
// since that is useless because minification/webpack
|
||||
stackInfo['stack'] = {trace: trace.split('\n').map(a => a.split('@')[0])};
|
||||
|
||||
// here's possible sources that led to this log entry
|
||||
stackInfo['periodicPlayerCheck'] = false;
|
||||
stackInfo['periodicVideoStyleChangeCheck'] = false;
|
||||
stackInfo['aard'] = false;
|
||||
stackInfo['keyboard'] = false;
|
||||
stackInfo['popup'] = false;
|
||||
stackInfo['mousemove'] = false;
|
||||
stackInfo['exitLogs'] = false;
|
||||
|
||||
// here we check which source triggered the action. There can be more
|
||||
// than one source, too, so we don't break when we find the first one
|
||||
for (const line of stackInfo.stack.trace) {
|
||||
if (line === 'doPeriodicPlayerElementChangeCheck') {
|
||||
stackInfo['periodicPlayerCheck'] = true;
|
||||
} else if (line === 'doPeriodicFallbackChangeDetectionCheck') {
|
||||
stackInfo['periodicVideoStyleChangeCheck'] = true;
|
||||
} else if (line === 'frameCheck') {
|
||||
stackInfo['aard'] = true;
|
||||
} else if (line === 'execAction') {
|
||||
stackInfo['keyboard'] = true;
|
||||
} else if (line === 'processReceivedMessage') {
|
||||
stackInfo['popup'] = true;
|
||||
} else if (line === 'handleMouseMove') {
|
||||
stackInfo['mousemove'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// exitLog overrides any other exclusions, so we look for it separately.
|
||||
// we also remove some of the unnecessary messages to reduce log file size
|
||||
for(let i = 0; i < stackInfo.stack.trace.length; i++) {
|
||||
if (stackInfo.stack.trace[i] === 'finish') {
|
||||
stackInfo['exitLogs'] = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// if we hit one of these, we remove the rest of the array and call it a
|
||||
// day. Chances are there's nothing of value past this point.
|
||||
if (stackInfo.stack.trace[i].indexOf('promise callback') !== -1
|
||||
|| stackInfo.stack.trace[i].indexOf('asyncGeneratorStep') !== -1
|
||||
|| stackInfo.stack.trace[i].indexOf('_asyncToGenerator') !== -1
|
||||
|| stackInfo.stack.trace[i].startsWith('_next')) {
|
||||
stackInfo.stack.trace.splice(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return stackInfo;
|
||||
}
|
||||
|
||||
// TODO: implement this
|
||||
private stopLogging() {
|
||||
|
||||
}
|
||||
|
||||
async init(config?: LogConfig) {
|
||||
if (!config) {
|
||||
config = await LogAggregator.getConfig();
|
||||
}
|
||||
|
||||
this.config = config;
|
||||
this.startTime = performance.now();
|
||||
|
||||
if (this.config?.stopAfter) {
|
||||
setTimeout(
|
||||
() => {
|
||||
this.stopLogging();
|
||||
},
|
||||
this.config.stopAfter * 1000
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
log(component: string, logLevel: string, sourceOptions: LogSourceOptions, message: string, ...data: any[]) {
|
||||
if (! this.canLog(component, sourceOptions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.config.logToFile) {
|
||||
|
||||
}
|
||||
|
||||
if (this.config.logToConsole){
|
||||
console[logLevel](`[${this.segment}]>>${message}`, ...data, {stack: this.parseStack()});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
|
||||
|
||||
export default interface LogConfig {
|
||||
outputs: {
|
||||
console: boolean,
|
||||
buffer: boolean,
|
||||
},
|
||||
components: {
|
||||
settings?: boolean,
|
||||
aard?: boolean,
|
||||
videoData?: boolean,
|
||||
resizer?: boolean,
|
||||
comms?: boolean,
|
||||
},
|
||||
environments: {
|
||||
page: boolean,
|
||||
popup: boolean,
|
||||
ui: boolean,
|
||||
uwServer: boolean,
|
||||
}
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
export interface LogMessageOrigin {
|
||||
component: string,
|
||||
environment: string,
|
||||
}
|
||||
|
||||
export interface LogMessage {
|
||||
time: Date;
|
||||
message: any,
|
||||
origin: LogMessageOrigin
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
import { settings } from 'cluster'
|
||||
import SettingsInterface from '@src/common/interfaces/SettingsInterface';
|
||||
|
||||
|
||||
export interface SettingsSnapshot {
|
||||
isAutomatic?: boolean;
|
||||
isProtected?: boolean;
|
||||
isDefault?: boolean;
|
||||
forVersion: string;
|
||||
label: string;
|
||||
settings: SettingsInterface;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
export interface SettingsSnapshotOptions {
|
||||
isAutomatic?: boolean,
|
||||
isProtected?: boolean,
|
||||
isDefault?: boolean,
|
||||
label?: string,
|
||||
forVersion?: string
|
||||
}
|
||||
|
||||
export class SettingsSnapshotManager {
|
||||
private MAX_AUTOMATIC_SNAPSHOTS = 5;
|
||||
|
||||
async getSnapshot(index?: number) {
|
||||
const snapshots = await this.listSnapshots();
|
||||
|
||||
if (!index) {
|
||||
return snapshots.find(x => x.isDefault);
|
||||
} else {
|
||||
if (index < 0 || index >= snapshots.length) {
|
||||
throw new Error('Invalid index');
|
||||
}
|
||||
return snapshots[index];
|
||||
}
|
||||
}
|
||||
|
||||
async createSnapshot(settings: SettingsInterface, options?: SettingsSnapshotOptions) {
|
||||
const snapshot = {
|
||||
...options,
|
||||
label: options.label ?? 'Automatic snapshot',
|
||||
forVersion: options.forVersion || settings.version,
|
||||
settings: JSON.parse(JSON.stringify(settings)),
|
||||
createdAt: new Date(),
|
||||
};
|
||||
|
||||
const snapshots = await this.listSnapshots();
|
||||
const automaticSnapshots = snapshots.filter((s) => s.isAutomatic && !s.isProtected);
|
||||
|
||||
if (options.isAutomatic && automaticSnapshots.length >= this.MAX_AUTOMATIC_SNAPSHOTS) {
|
||||
const firstAutomaticIndex = snapshots.findIndex((s) => s.isAutomatic && !s.isProtected);
|
||||
snapshots.splice(firstAutomaticIndex, 1);
|
||||
}
|
||||
|
||||
snapshots.push(snapshot);
|
||||
this.set(snapshots);
|
||||
}
|
||||
|
||||
async setDefaultSnapshot(index: number, isDefault: boolean) {
|
||||
const snapshots = await this.listSnapshots();
|
||||
if (index < 0 || index >= snapshots.length) {
|
||||
throw new Error('Invalid index');
|
||||
}
|
||||
if (isDefault) {
|
||||
for (const snapshot of snapshots) {
|
||||
snapshot.isDefault = false;
|
||||
}
|
||||
}
|
||||
snapshots[index].isDefault = isDefault;
|
||||
this.set(snapshots);
|
||||
}
|
||||
|
||||
async markSnapshotAsProtected(index: number, isProtected: boolean) {
|
||||
const snapshots = await this.listSnapshots();
|
||||
if (index < 0 || index >= snapshots.length) {
|
||||
throw new Error('Invalid index');
|
||||
}
|
||||
snapshots[index].isProtected = isProtected;
|
||||
this.set(snapshots);
|
||||
}
|
||||
|
||||
async deleteSnapshot(index: number) {
|
||||
const snapshots = await this.listSnapshots();
|
||||
if (index < 0 || index >= snapshots.length) {
|
||||
throw new Error('Invalid index');
|
||||
}
|
||||
snapshots.splice(index, 1);
|
||||
this.set(snapshots);
|
||||
}
|
||||
|
||||
async listSnapshots(): Promise<SettingsSnapshot[]> {
|
||||
const ret = await chrome.storage.local.get('uwSettings-snapshots');
|
||||
try {
|
||||
const json = JSON.parse(ret['uwSettings-snapshots']) as SettingsSnapshot[];
|
||||
return json;
|
||||
} catch (e) {
|
||||
return [] as SettingsSnapshot[];
|
||||
}
|
||||
}
|
||||
|
||||
private async set(snapshots: SettingsSnapshot[] = []) {
|
||||
await chrome.storage.local.set({
|
||||
'uwSettings-snapshots': JSON.stringify(snapshots),
|
||||
});
|
||||
}
|
||||
}
|
@ -15,17 +15,10 @@ import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum';
|
||||
*/
|
||||
export class SiteSettings {
|
||||
private settings: Settings;
|
||||
private _site: string;
|
||||
private set site(x: string) {
|
||||
this._site = x;
|
||||
}
|
||||
public get site() {
|
||||
return this._site;
|
||||
}
|
||||
private site: string;
|
||||
|
||||
raw: SiteSettingsInterface; // actual settings
|
||||
data: SiteSettingsInterface; // effective settings
|
||||
usesSettingsFor: string | undefined;
|
||||
temporaryData: SiteSettingsInterface;
|
||||
sessionData: SiteSettingsInterface;
|
||||
readonly defaultSettings: SiteSettingsInterface;
|
||||
@ -35,9 +28,9 @@ export class SiteSettings {
|
||||
//#region lifecycle
|
||||
constructor(settings: Settings, site: string) {
|
||||
this.settings = settings;
|
||||
this.raw = settings.active.sites[site];
|
||||
this.data = settings.active.sites[site];
|
||||
this.site = site;
|
||||
this.defaultSettings = settings.active.sites['@global'];
|
||||
this.defaultSettings = settings.default.sites['@global'];
|
||||
|
||||
this.compileSettingsObject();
|
||||
|
||||
@ -54,63 +47,12 @@ export class SiteSettings {
|
||||
chrome.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)})
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to match websites, even if we're on a different subdomain.
|
||||
* @returns
|
||||
*/
|
||||
private getSettingsForSite() {
|
||||
if (!this.site) {
|
||||
return {
|
||||
siteSettings: this.settings.active.sites['@global'],
|
||||
usesSettingsFor: '@global'
|
||||
};
|
||||
}
|
||||
|
||||
if (this.settings.active.sites[this.site]) {
|
||||
return {
|
||||
siteSettings: this.settings.active.sites[this.site],
|
||||
usesSettingsFor: undefined
|
||||
};
|
||||
}
|
||||
|
||||
const urlSegments = this.site.split('.').reverse();
|
||||
|
||||
siteLoop:
|
||||
for (const cs in this.settings.active.sites) {
|
||||
const configUrlSegments = cs.split('.').reverse();
|
||||
|
||||
// Match site with wildcard site definitions
|
||||
// Also, if definition starts with 'www', match also other subdomains — e.g. if we have a configuration for
|
||||
// `www.example.com`, this will also match `example.com`, `subdomain.example.com`, `nested.subdomain.example.com` ...
|
||||
if (configUrlSegments[configUrlSegments.length - 1] === '*' || (configUrlSegments[configUrlSegments.length - 1] === 'www')) {
|
||||
|
||||
console.log('ss: comparing', configUrlSegments, urlSegments);
|
||||
for (let i = 0; i < configUrlSegments.length - 1 && i < urlSegments.length; i++) {
|
||||
if (configUrlSegments[i] !== urlSegments[i]) {
|
||||
continue siteLoop;
|
||||
}
|
||||
}
|
||||
return {
|
||||
siteSettings: this.settings.active.sites[cs],
|
||||
usesSettingsFor: cs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
siteSettings: this.settings.active.sites['@global'],
|
||||
usesSettingsFor: '@global'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges defaultSettings into site settings where appropriate.
|
||||
* Alan pls ensure default settings object follows the correct structure
|
||||
*/
|
||||
private compileSettingsObject() {
|
||||
const {siteSettings, usesSettingsFor} = this.getSettingsForSite();
|
||||
this.data = _cp(siteSettings);
|
||||
this.usesSettingsFor = usesSettingsFor;
|
||||
this.raw = _cp(this.settings.active.sites[this.site] ?? {})
|
||||
|
||||
if (!this.data) {
|
||||
this.data = _cp(this.defaultSettings);
|
||||
@ -139,7 +81,7 @@ export class SiteSettings {
|
||||
}
|
||||
}
|
||||
|
||||
for (const enableSegment of ['enable', 'enableAard', 'enableKeyboard', 'enableUI']) {
|
||||
for (const enableSegment of ['enable', 'enableAard', 'enableKeyboard']) {
|
||||
if (!this.data[enableSegment]) {
|
||||
this.data[enableSegment] = {};
|
||||
}
|
||||
@ -208,8 +150,8 @@ export class SiteSettings {
|
||||
|
||||
// we aren't stepping on any other toes by doing this, since everyone
|
||||
// gets the first change
|
||||
// this.settings.active._updateFlags = undefined;
|
||||
// this.settings.saveWithoutReload();
|
||||
this.settings.active._updateFlags = undefined;
|
||||
this.settings.saveWithoutReload();
|
||||
}
|
||||
}
|
||||
|
||||
@ -282,7 +224,7 @@ export class SiteSettings {
|
||||
* @param isFullscreen
|
||||
* @returns ExtensionMode
|
||||
*/
|
||||
isEnabledForEnvironment(isTheater: boolean, isFullscreen: boolean): ExtensionMode {
|
||||
isEnabledForEnvironment(isTheater: boolean, isFullscreen: boolean) {
|
||||
const env = this._getEnvironment(isTheater, isFullscreen);
|
||||
return this.data.enable[env];
|
||||
}
|
||||
@ -293,7 +235,7 @@ export class SiteSettings {
|
||||
* @param isFullscreen
|
||||
* @returns
|
||||
*/
|
||||
isAardEnabledForEnvironment(isTheater: boolean, isFullscreen: boolean): ExtensionMode {
|
||||
isAardEnabledForEnvironment(isTheater: boolean, isFullscreen: boolean) {
|
||||
const env = this._getEnvironment(isTheater, isFullscreen);
|
||||
return this.data.enableAard[env];
|
||||
}
|
||||
@ -304,7 +246,7 @@ export class SiteSettings {
|
||||
* @param isFullscreen
|
||||
* @returns
|
||||
*/
|
||||
isKeyboardEnabledForEnvironment(isTheater: boolean, isFullscreen: boolean): ExtensionMode {
|
||||
isKeyboardEnabledForEnvironment(isTheater: boolean, isFullscreen: boolean) {
|
||||
const env = this._getEnvironment(isTheater, isFullscreen);
|
||||
return this.data.enableKeyboard[env];
|
||||
}
|
||||
@ -365,19 +307,10 @@ export class SiteSettings {
|
||||
* @param optionValue new value of option
|
||||
* @param reload whether we should trigger a reload in components that require it
|
||||
*/
|
||||
async set(optionPath: string, optionValue: any, options: {reload?: boolean, noSave?: boolean, scripted?: boolean} = {reload: false}) {
|
||||
// if no settings exist for this site, create an empty object.
|
||||
// If this function is not being called in response to user actin,
|
||||
// create fake settings object.
|
||||
if (options.scripted && !this.settings.active.sites[this.site]) {
|
||||
this.settings.active.sites[this.site] = _cp(this.settings.active.sites['@global']);
|
||||
this.settings.active.sites[this.site].autocreated = true;
|
||||
this.settings.active.sites[this.site].type = 'unknown';
|
||||
} else {
|
||||
if (!this.settings.active.sites[this.site] || this.settings.active.sites[this.site].autocreated) {
|
||||
this.settings.active.sites[this.site] = _cp(this.data);
|
||||
this.settings.active.sites[this.site].type = 'user-defined';
|
||||
}
|
||||
async set(optionPath: string, optionValue: any, options: {reload?: boolean, noSave?: boolean} = {reload: false}) {
|
||||
// if no settings exist for this site, create an empty object
|
||||
if (!this.settings.active.sites[this.site]) {
|
||||
this.settings.active.sites[this.site] = _cp(this.settings.active.sites['@empty']);
|
||||
}
|
||||
|
||||
const pathParts = optionPath.split('.');
|
||||
|
@ -1,4 +1,3 @@
|
||||
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
|
||||
import { EventBusConnector } from '../EventBus';
|
||||
|
||||
if (process.env.CHANNEL !== 'stable'){
|
||||
@ -23,7 +22,6 @@ class UI {
|
||||
this.lastProbeResponseTs = null;
|
||||
|
||||
this.isGlobal = uiConfig.isGlobal ?? false;
|
||||
this.isIframe = window.self !== window.top;
|
||||
|
||||
this.eventBus = uiConfig.eventBus;
|
||||
this.disablePointerEvents = false;
|
||||
@ -31,48 +29,24 @@ class UI {
|
||||
this.saveState = undefined;
|
||||
this.playerData = uiConfig.playerData;
|
||||
this.uiSettings = uiConfig.uiSettings;
|
||||
this.siteSettings = uiConfig.siteSettings;
|
||||
|
||||
this.iframeErrorCount = 0;
|
||||
this.iframeConfirmed = false;
|
||||
this.iframeRejected = false;
|
||||
|
||||
this.delayedDestroyTimer = null;
|
||||
|
||||
// TODO: at some point, UI should be different for global popup and in-player UI
|
||||
this.csuiScheme = this.getCsuiScheme();
|
||||
const csuiVersion = this.getCsuiVersion(this.csuiScheme.contentScheme);
|
||||
this.uiURI = chrome.runtime.getURL(`/csui/${csuiVersion}.html`);
|
||||
this.extensionBase = chrome.runtime.getURL('').replace(/\/$/, "");
|
||||
|
||||
// UI will be initialized when setUiVisibility is called
|
||||
console.log('ui config:', uiConfig);
|
||||
this.init();
|
||||
}
|
||||
|
||||
canRun() {
|
||||
if (this.isGlobal) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.siteSettings?.data.enableUI.fullscreen === ExtensionMode.Enabled
|
||||
|| this.siteSettings?.data.enableUI.theater === ExtensionMode.Enabled
|
||||
|| this.siteSettings?.data.enableUI.normal === ExtensionMode.Enabled;
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!this.canRun()) {
|
||||
console.log('ui config: canRun returned false', this.siteSettings?.data.enableUI.fullscreen === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.theater === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.normal === ExtensionMode.Enabled)
|
||||
return;
|
||||
}
|
||||
console.log('ui config: canRun returned truie', this.siteSettings?.data.enableUI.fullscreen === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.theater === ExtensionMode.Enabled, this.siteSettings?.data.enableUI.normal === ExtensionMode.Enabled)
|
||||
|
||||
|
||||
this.initUIContainer();
|
||||
this.loadIframe();
|
||||
this.initIframes();
|
||||
this.initMessaging();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns color scheme we need to use.
|
||||
*
|
||||
@ -96,7 +70,7 @@ class UI {
|
||||
return csuiVersions[preferredScheme] ?? csuiVersions.normal;
|
||||
}
|
||||
|
||||
initUIContainer() {
|
||||
initIframes() {
|
||||
const random = Math.round(Math.random() * 69420);
|
||||
const uwid = `uw-ultrawidify-${this.interfaceId}-root-${random}`
|
||||
|
||||
@ -107,8 +81,8 @@ class UI {
|
||||
}
|
||||
rootDiv.setAttribute('id', uwid);
|
||||
rootDiv.classList.add('uw-ultrawidify-container-root');
|
||||
rootDiv.style.width = "100%";
|
||||
rootDiv.style.height = "100%";
|
||||
// rootDiv.style.width = "100%";
|
||||
// rootDiv.style.height = "100%";
|
||||
rootDiv.style.position = this.isGlobal ? "fixed" : "absolute";
|
||||
rootDiv.style.zIndex = this.isGlobal ? '90009' : '90000';
|
||||
rootDiv.style.border = 0;
|
||||
@ -121,10 +95,8 @@ class UI {
|
||||
document.body.appendChild(rootDiv);
|
||||
}
|
||||
|
||||
this.rootDiv = rootDiv;
|
||||
}
|
||||
this.element = rootDiv;
|
||||
|
||||
loadIframe() {
|
||||
// in onMouseMove, we currently can't access this because we didn't
|
||||
// do things the most properly
|
||||
const uiURI = this.uiURI;
|
||||
@ -160,7 +132,7 @@ class UI {
|
||||
this.uiIframe = iframe;
|
||||
|
||||
// set not visible by default
|
||||
// this.setUiVisibility(false);
|
||||
this.setUiVisibility(false);
|
||||
|
||||
const fn = (event) => {
|
||||
// remove self on fucky wuckies
|
||||
@ -206,28 +178,14 @@ class UI {
|
||||
document.addEventListener('mousemove', fn, true);
|
||||
}
|
||||
|
||||
this.eventBus.forwardToIframe(
|
||||
this.uiIframe,
|
||||
(action, payload) => {
|
||||
this.sendToIframe(action, payload, {})
|
||||
}
|
||||
);
|
||||
|
||||
this.rootDiv.appendChild(iframe);
|
||||
rootDiv.appendChild(iframe);
|
||||
}
|
||||
|
||||
unloadIframe() {
|
||||
this.eventBus.cancelIframeForwarding(this.uiIframe);
|
||||
window.removeEventListener('message', this.messageHandlerFn);
|
||||
this.uiIframe?.remove();
|
||||
delete this.uiIframe;
|
||||
}
|
||||
|
||||
|
||||
initMessaging() {
|
||||
// subscribe to events coming back to us. Unsubscribe if iframe vanishes.
|
||||
window.addEventListener('message', this.messageHandlerFn);
|
||||
|
||||
|
||||
/* set up event bus tunnel from content script to UI — necessary if we want to receive
|
||||
* like current zoom levels & current aspect ratio & stuff. Some of these things are
|
||||
* necessary for UI display in the popup.
|
||||
@ -242,7 +200,7 @@ class UI {
|
||||
},
|
||||
'uw-set-ui-state': {
|
||||
function: (config, routingData) => {
|
||||
if (config.globalUiVisible !== undefined && !this.isIframe) {
|
||||
if (config.globalUiVisible !== undefined) {
|
||||
if (this.isGlobal) {
|
||||
this.setUiVisibility(config.globalUiVisible);
|
||||
} else {
|
||||
@ -292,40 +250,28 @@ class UI {
|
||||
}
|
||||
|
||||
setUiVisibility(visible) {
|
||||
// console.log('uwui - setting ui visibility!', visible, this.isGlobal ? 'global' : 'page', this.uiIframe, this.rootDiv);
|
||||
// if (!this.uiIframe || !this.rootDiv) {
|
||||
// this.init();
|
||||
// }
|
||||
|
||||
if (visible) {
|
||||
this.rootDiv.style.width = '100%';
|
||||
this.rootDiv.style.height = '100%';
|
||||
this.element.style.width = '100%';
|
||||
this.element.style.height = '100%';
|
||||
this.uiIframe.style.width = '100%';
|
||||
this.uiIframe.style.height = '100%';
|
||||
|
||||
// if (this.delayedDestroyTimer) {
|
||||
// clearTimeout(this.delayedDestroyTimer);
|
||||
// }
|
||||
} else {
|
||||
this.rootDiv.style.width = '0px';
|
||||
this.rootDiv.style.height = '0px';
|
||||
this.element.style.width = '0px';
|
||||
this.element.style.height = '0px';
|
||||
this.uiIframe.style.width = '0px';
|
||||
this.uiIframe.style.height = '0px';
|
||||
|
||||
// destroy after 30 seconds of UI being hidden
|
||||
// this.delayedDestroyTimer = setTimeout( () => this.unloadIframe(), 30000);
|
||||
}
|
||||
}
|
||||
|
||||
async enable() {
|
||||
// if root element is not present, we need to init the UI.
|
||||
if (!this.rootDiv) {
|
||||
if (!this.element) {
|
||||
await this.init();
|
||||
}
|
||||
// otherwise, we don't have to do anything
|
||||
}
|
||||
disable() {
|
||||
if (this.rootDiv) {
|
||||
if (this.element) {
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
@ -344,10 +290,6 @@ class UI {
|
||||
canShowUI: false,
|
||||
}
|
||||
|
||||
if (this.playerData?.environment && this.siteSettings.data.enableUI[this.playerData?.environment] !== ExtensionMode.Enabled) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (this.playerData?.dimensions) {
|
||||
result.playerDimensions = this.playerData.dimensions;
|
||||
}
|
||||
@ -364,7 +306,6 @@ class UI {
|
||||
}
|
||||
|
||||
result.canShowUI = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -425,17 +366,7 @@ class UI {
|
||||
break;
|
||||
case 'uw-bus-tunnel':
|
||||
const busCommand = event.data.payload;
|
||||
this.eventBus.send(
|
||||
busCommand.action,
|
||||
busCommand.config,
|
||||
{
|
||||
...busCommand?.context,
|
||||
borderCrossings: {
|
||||
...busCommand?.context?.borderCrossings,
|
||||
iframe: true,
|
||||
}
|
||||
}
|
||||
);
|
||||
this.eventBus.send(busCommand.action, busCommand.config, busCommand.routingData);
|
||||
break;
|
||||
case 'uwui-get-role':
|
||||
this.sendToIframeLowLevel('uwui-set-role', {role: this.isGlobal ? 'global' : 'player'});
|
||||
@ -467,7 +398,7 @@ class UI {
|
||||
// because existence of UI is not guaranteed — UI is not shown when extension is inactive.
|
||||
// If extension is inactive due to "player element isn't big enough to justify it", however,
|
||||
// we can still receive eventBus messages.
|
||||
if (this.rootDiv && this.uiIframe) {
|
||||
if (this.element && this.uiIframe) {
|
||||
this.uiIframe.contentWindow?.postMessage(
|
||||
{
|
||||
action,
|
||||
@ -514,21 +445,21 @@ class UI {
|
||||
replace(newUiConfig) {
|
||||
this.uiConfig = newUiConfig;
|
||||
|
||||
if (this.rootDiv) {
|
||||
this.destroy();
|
||||
if (this.element) {
|
||||
this.element?.remove();
|
||||
this.init();
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.unloadIframe();
|
||||
|
||||
window.removeEventListener('message', this.messageHandlerFn);
|
||||
this.eventBus.unsubscribeAll(this);
|
||||
// this.comms?.destroy();
|
||||
this.rootDiv?.remove();
|
||||
this.uiIframe?.remove();
|
||||
this.element?.remove();
|
||||
|
||||
delete this.uiIframe;
|
||||
delete this.rootDiv;
|
||||
this.uiIframe = undefined;
|
||||
this.element = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import VideoData from './VideoData';
|
||||
import RescanReason from './enums/RescanReason.enum';
|
||||
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
||||
import CropModePersistence from '../../../common/enums/CropModePersistence.enum';
|
||||
import Logger from '../Logger';
|
||||
import Settings from '../Settings';
|
||||
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
|
||||
import CommsClient from '../comms/CommsClient';
|
||||
import EventBus from '../EventBus';
|
||||
import { SiteSettings } from '../settings/SiteSettings';
|
||||
import IframeManager from './IframeManager';
|
||||
import { LogAggregator } from '../logging/LogAggregator';
|
||||
import { ComponentLogger } from '../logging/ComponentLogger';
|
||||
|
||||
if (process.env.CHANNEL !== 'stable'){
|
||||
console.info("Loading PageInfo");
|
||||
@ -49,8 +52,7 @@ class PageInfo {
|
||||
//#endregion
|
||||
|
||||
//#region helper objects
|
||||
logAggregator: LogAggregator;
|
||||
logger: ComponentLogger;
|
||||
logger: Logger;
|
||||
settings: Settings;
|
||||
siteSettings: SiteSettings;
|
||||
comms: CommsClient;
|
||||
@ -69,7 +71,6 @@ class PageInfo {
|
||||
keyboardHandler: any;
|
||||
|
||||
fsStatus = {fullscreen: true}; // fsStatus needs to be passed to VideoData, so fullScreen property is shared between videoData instances
|
||||
isIframe: boolean = false;
|
||||
//#endregion
|
||||
|
||||
fsEventListener = {
|
||||
@ -79,11 +80,8 @@ class PageInfo {
|
||||
}
|
||||
};
|
||||
|
||||
constructor(eventBus: EventBus, siteSettings: SiteSettings, settings: Settings, logAggregator: LogAggregator, readOnly = false) {
|
||||
this.isIframe = window.self !== window.top;
|
||||
|
||||
this.logAggregator = logAggregator;
|
||||
this.logger = new ComponentLogger(logAggregator, 'PageInfo', {});
|
||||
constructor(eventBus: EventBus, siteSettings: SiteSettings, settings: Settings, logger: Logger, readOnly = false){
|
||||
this.logger = logger;
|
||||
this.settings = settings;
|
||||
this.siteSettings = siteSettings;
|
||||
|
||||
@ -111,19 +109,10 @@ class PageInfo {
|
||||
this.scheduleUrlCheck();
|
||||
|
||||
document.addEventListener('fullscreenchange', this.fsEventListener);
|
||||
|
||||
this.eventBus.subscribeMulti({
|
||||
'probe-video': {
|
||||
function: () => {
|
||||
console.log(`[${window.location}] probe-video received.`)
|
||||
this.rescan();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
destroy() {
|
||||
// this.logger.debug('destroy', 'destroying all videos!")
|
||||
// this.logger.log('info', ['debug', 'init'], "[PageInfo::destroy] destroying all videos!")
|
||||
if(this.rescanTimer){
|
||||
clearTimeout(this.rescanTimer);
|
||||
}
|
||||
@ -132,7 +121,7 @@ class PageInfo {
|
||||
this.eventBus.send('noVideo', undefined);
|
||||
video.videoData.destroy();
|
||||
} catch (e) {
|
||||
this.logger.error('destroy', 'unable to destroy video! Error:', e);
|
||||
this.logger.log('error', ['debug', 'init'], '[PageInfo::destroy] unable to destroy video! Error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,87 +179,28 @@ class PageInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all videos on the page.
|
||||
*
|
||||
* If minSize is provided, it only returns <video> elements that are
|
||||
* equal or bigger than desired size:
|
||||
*
|
||||
* * sm: 320 x 180
|
||||
* * md: 720 x 400
|
||||
* * lg: 1280 x 720
|
||||
*
|
||||
* If minSize is omitted, it returns all <video> elements.
|
||||
* @param minSize
|
||||
* @returns
|
||||
*/
|
||||
getAllVideos(minSize?: 'sm' | 'md' | 'lg') {
|
||||
getVideos(): HTMLVideoElement[] {
|
||||
const videoQs = this.siteSettings.getCustomDOMQuerySelector('video');
|
||||
let videos: HTMLVideoElement[] = [];
|
||||
|
||||
if (videoQs){
|
||||
videos = Array.from(document.querySelectorAll(videoQs) as NodeListOf<HTMLVideoElement> ?? []);
|
||||
} else {
|
||||
} else{
|
||||
videos = Array.from(document.getElementsByTagName('video') ?? []);
|
||||
}
|
||||
|
||||
if (!minSize) {
|
||||
return videos;
|
||||
}
|
||||
|
||||
return this.filterVideos(videos, minSize);
|
||||
}
|
||||
|
||||
filterVideos(videos: HTMLVideoElement[], minSize: 'sm' | 'md' | 'lg') {
|
||||
// minimums are determined by vibes and shit.
|
||||
// 'sm' is based on "slightly smaller than embeds on old.reddit"
|
||||
const minX = { sm: 320, md: 720, lg: 1280 };
|
||||
const minY = { sm: 180, md: 400, lg: 720 };
|
||||
|
||||
// filter out videos that aren't big enough
|
||||
return videos.filter(
|
||||
(v: HTMLVideoElement) => v.clientHeight >= minY[minSize] && v.clientWidth >= minX[minSize]
|
||||
videos = videos.filter(
|
||||
(v: HTMLVideoElement) => v.clientHeight > 720 && v.clientWidth > 1208
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets videos on the page that are big enough for extension to trigger
|
||||
* @returns
|
||||
*/
|
||||
getVideos(): HTMLVideoElement[] {
|
||||
return this.getAllVideos('lg');
|
||||
return videos;
|
||||
}
|
||||
|
||||
hasVideo() {
|
||||
return this.readOnly ? this.hasVideos : this.videos.length;
|
||||
}
|
||||
|
||||
private emitVideoStatus(videosDetected?: boolean) {
|
||||
// if we're left without videos on the current page, we unregister the page.
|
||||
// if we have videos, we call register.
|
||||
if (this.eventBus) {
|
||||
// We used to send "register video" requests only on the first load, or if the number of
|
||||
// videos on the page has changed. However, since Chrome Web Store started to require every
|
||||
// extension requiring "broad permissions" to undergo manual review
|
||||
// ... and since Chrome Web Store is known for taking their sweet ass time reviewing extensions,
|
||||
// with review times north of an entire fucking month
|
||||
// ... and since the legacy way of checking whether our frames-with-videos cache in background
|
||||
// script contains any frames that no longer exist required us to use webNavigation.getFrame()/
|
||||
// webNavigation.getAllFrames(), which requires a permission that triggers a review.
|
||||
//
|
||||
// While the extension uses some other permissions that trigger manual review, it's said that
|
||||
// less is better / has a positive effect on your manual review times ... So I guess we'll do
|
||||
// things in the less-than-optimal. more-than-retarded way.
|
||||
//
|
||||
// no but honestly fuck Chrome.
|
||||
if (videosDetected || this.hasVideo()) {
|
||||
this.eventBus.send('has-video', null);
|
||||
} else {
|
||||
this.eventBus.send('noVideo', null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-scans the page for videos. Removes any videos that no longer exist from our list
|
||||
* of videos. Destroys all videoData objects for all the videos that don't have their
|
||||
@ -279,8 +209,6 @@ class PageInfo {
|
||||
* @returns
|
||||
*/
|
||||
rescan(rescanReason?: RescanReason){
|
||||
let videosDetected = false;
|
||||
|
||||
// is there any video data objects that had their HTML elements removed but not yet
|
||||
// destroyed? We clean that up here.
|
||||
const orphans = this.videos.filter(x => !document.body.contains(x.element));
|
||||
@ -292,33 +220,24 @@ class PageInfo {
|
||||
// remove all destroyed videos.
|
||||
this.videos = this.videos.filter(x => !x.videoData.destroyed);
|
||||
|
||||
|
||||
// add new videos
|
||||
try {
|
||||
// in iframes, emit registerIframe even if video is smaller than required
|
||||
let vids = this.getAllVideos('sm');
|
||||
|
||||
if (this.isIframe && this.eventBus) {
|
||||
videosDetected ||= vids?.length > 0;
|
||||
};
|
||||
|
||||
// for normal operations, use standard size limits
|
||||
vids = this.filterVideos(vids, 'lg');
|
||||
try{
|
||||
let vids = this.getVideos();
|
||||
|
||||
if(!vids || vids.length == 0){
|
||||
this.hasVideos = false;
|
||||
|
||||
if(rescanReason == RescanReason.PERIODIC){
|
||||
this.logger.info({src: 'rescan', origin: 'videoRescan'}, "Scheduling normal rescan.")
|
||||
this.logger.log('info', 'videoRescan', "[PageInfo::rescan] Scheduling normal rescan.")
|
||||
this.scheduleRescan(RescanReason.PERIODIC);
|
||||
}
|
||||
|
||||
this.emitVideoStatus(videosDetected);
|
||||
return;
|
||||
}
|
||||
|
||||
// add new videos
|
||||
this.hasVideos = false;
|
||||
let videoExists = false;
|
||||
|
||||
for (const videoElement of vids) {
|
||||
// do not re-add videos that we already track:
|
||||
if (this.videos.find(x => x.element.isEqualNode(videoElement))) {
|
||||
@ -333,7 +252,6 @@ class PageInfo {
|
||||
|
||||
// at this point, we're certain that we found new videos. Let's update some properties:
|
||||
this.hasVideos = true;
|
||||
videosDetected ||= true;
|
||||
|
||||
// if PageInfo is marked as "readOnly", we actually aren't adding any videos to anything because
|
||||
// that's super haram. We're only interested in whether
|
||||
@ -344,29 +262,58 @@ class PageInfo {
|
||||
if(rescanReason == RescanReason.PERIODIC){
|
||||
this.scheduleRescan(RescanReason.PERIODIC);
|
||||
}
|
||||
this.emitVideoStatus(videosDetected);
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.info({src: 'rescan', origin: 'videoRescan'}, "found new video candidate:", videoElement, "NOTE:: Video initialization starts here:\n--------------------------------\n")
|
||||
this.logger.log('info', 'videoRescan', "[PageInfo::rescan] found new video candidate:", videoElement, "NOTE:: Video initialization starts here:\n--------------------------------\n")
|
||||
|
||||
try {
|
||||
const newVideo = new VideoData(videoElement, this.settings, this.siteSettings, this);
|
||||
this.videos.push({videoData: newVideo, element: videoElement});
|
||||
} catch (e) {
|
||||
this.logger.error('rescan', "rescan error: failed to initialize videoData. Skipping this video.",e);
|
||||
this.logger.log('error', 'debug', "rescan error: failed to initialize videoData. Skipping this video.",e);
|
||||
}
|
||||
|
||||
this.logger.info({src: 'rescan', origin: 'videoRescan'}, "END VIDEO INITIALIZATION\n\n\n-------------------------------------\nvideos[] is now this:", this.videos,"\n\n\n\n\n\n\n\n")
|
||||
this.logger.log('info', 'videoRescan', "END VIDEO INITIALIZATION\n\n\n-------------------------------------\nvideos[] is now this:", this.videos,"\n\n\n\n\n\n\n\n")
|
||||
}
|
||||
|
||||
this.removeDestroyed();
|
||||
this.emitVideoStatus(videosDetected);
|
||||
|
||||
// if we're left without videos on the current page, we unregister the page.
|
||||
// if we have videos, we call register.
|
||||
if (this.eventBus) {
|
||||
// We used to send "register video" requests only on the first load, or if the number of
|
||||
// videos on the page has changed. However, since Chrome Web Store started to require every
|
||||
// extension requiring "broad permissions" to undergo manual review
|
||||
// ... and since Chrome Web Store is known for taking their sweet ass time reviewing extensions,
|
||||
// with review times north of an entire fucking month
|
||||
// ... and since the legacy way of checking whether our frames-with-videos cache in background
|
||||
// script contains any frames that no longer exist required us to use webNavigation.getFrame()/
|
||||
// webNavigation.getAllFrames(), which requires a permission that triggers a review.
|
||||
//
|
||||
// While the extension uses some other permissions that trigger manual review, it's said that
|
||||
// less is better / has a positive effect on your manual review times ... So I guess we'll do
|
||||
// things in the less-than-optimal. more-than-retarded way.
|
||||
//
|
||||
// no but honestly fuck Chrome.
|
||||
|
||||
// if (this.videos.length != oldVideoCount) {
|
||||
// }
|
||||
|
||||
if (this.videos.length > 0) {
|
||||
// this.comms.registerVideo({host: window.location.hostname, location: window.location});
|
||||
this.eventBus.send('has-video', null);
|
||||
} else {
|
||||
// this.comms.unregisterVideo({host: window.location.hostname, location: window.location});
|
||||
this.eventBus.send('noVideo', null);
|
||||
}
|
||||
}
|
||||
|
||||
} catch(e) {
|
||||
// if we encounter a fuckup, we can assume that no videos were found on the page. We destroy all videoData
|
||||
// objects to prevent multiple initialization (which happened, but I don't know why). No biggie if we destroyed
|
||||
// videoData objects in error — they'll be back in the next rescan
|
||||
this.logger.error('rescan', "rescan error: — destroying all videoData objects",e);
|
||||
this.logger.log('error', 'debug', "rescan error: — destroying all videoData objects",e);
|
||||
for (const v of this.videos) {
|
||||
v.videoData.destroy();
|
||||
}
|
||||
@ -401,7 +348,7 @@ class PageInfo {
|
||||
ths = null;
|
||||
}, this.settings.active.pageInfo.timeouts.rescan, RescanReason.PERIODIC)
|
||||
} catch(e) {
|
||||
this.logger.error('scheduleRescan', "scheduling rescan failed. Here's why:",e)
|
||||
this.logger.log('error', 'debug', "[PageInfo::scheduleRescan] scheduling rescan failed. Here's why:",e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -419,13 +366,13 @@ class PageInfo {
|
||||
ths = null;
|
||||
}, this.settings.active.pageInfo.timeouts.urlCheck)
|
||||
} catch(e){
|
||||
this.logger.log('scheduleUrlCheck', "scheduling URL check failed. Here's why:",e)
|
||||
this.logger.log('error', 'debug', "[PageInfo::scheduleUrlCheck] scheduling URL check failed. Here's why:",e)
|
||||
}
|
||||
}
|
||||
|
||||
ghettoUrlCheck() {
|
||||
if (this.lastUrl != window.location.href){
|
||||
this.logger.warn('ghettoUrlCheck', "URL has changed. Triggering a rescan!");
|
||||
this.logger.log('error', 'videoRescan', "[PageInfo::ghettoUrlCheck] URL has changed. Triggering a rescan!");
|
||||
|
||||
this.rescan(RescanReason.URL_CHANGE);
|
||||
this.lastUrl = window.location.href;
|
||||
|
@ -1,16 +1,18 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import ExtensionMode from '../../../common/enums/ExtensionMode.enum'
|
||||
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
||||
import PlayerNotificationUi from '../uwui/PlayerNotificationUI';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
import * as _ from 'lodash';
|
||||
import { sleep } from '../../../common/js/utils';
|
||||
import VideoData from './VideoData';
|
||||
import Settings from '../Settings';
|
||||
import Logger from '../Logger';
|
||||
import EventBus from '../EventBus';
|
||||
import UI from '../uwui/UI';
|
||||
import { SiteSettings } from '../settings/SiteSettings';
|
||||
import PageInfo from './PageInfo';
|
||||
import { RunLevel } from '../../enum/run-level.enum';
|
||||
import { ExtensionEnvironment } from '../../../common/interfaces/SettingsInterface';
|
||||
import { ComponentLogger } from '../logging/ComponentLogger';
|
||||
|
||||
if (process.env.CHANNEL !== 'stable'){
|
||||
console.info("Loading: PlayerData.js");
|
||||
@ -66,7 +68,7 @@ class PlayerData {
|
||||
private playerCssClass = 'uw-ultrawidify-player-css';
|
||||
|
||||
//#region helper objects
|
||||
logger: ComponentLogger;
|
||||
logger: Logger;
|
||||
videoData: VideoData;
|
||||
pageInfo: PageInfo;
|
||||
siteSettings: SiteSettings;
|
||||
@ -89,10 +91,10 @@ class PlayerData {
|
||||
isTheaterMode: boolean = false; // note: fullscreen mode will count as theaterMode if player was in theater mode before fs switch. This is desired, so far.
|
||||
isTooSmall: boolean = true;
|
||||
|
||||
//#endregion
|
||||
//#region misc stuff
|
||||
extensionMode: any;
|
||||
dimensions: PlayerDimensions;
|
||||
private playerIdElement: any;
|
||||
private observer: ResizeObserver;
|
||||
|
||||
private trackChangesTimeout: any;
|
||||
@ -100,8 +102,6 @@ class PlayerData {
|
||||
|
||||
private ui: UI;
|
||||
|
||||
private _isTrackDimensionChangesActive: boolean = false;
|
||||
|
||||
elementStack: ElementStack = [] as ElementStack;
|
||||
//#endregion
|
||||
|
||||
@ -122,7 +122,7 @@ class PlayerData {
|
||||
function: (data) => this.markElement(data)
|
||||
}],
|
||||
'update-player': [{
|
||||
function: () => this.updatePlayer()
|
||||
function: () => this.getPlayer()
|
||||
}],
|
||||
'set-run-level': [{
|
||||
function: (runLevel) => this.setRunLevel(runLevel)
|
||||
@ -138,7 +138,6 @@ class PlayerData {
|
||||
private dimensionChangeListener = {
|
||||
that: this,
|
||||
handleEvent: function(event: Event) {
|
||||
this.that.trackEnvironmentChanges(event);
|
||||
this.that.trackDimensionChanges()
|
||||
}
|
||||
}
|
||||
@ -153,7 +152,6 @@ class PlayerData {
|
||||
}
|
||||
if (!this.dimensions) {
|
||||
this.trackDimensionChanges();
|
||||
this.trackEnvironmentChanges();
|
||||
}
|
||||
|
||||
return this.dimensions.width / this.dimensions.height;
|
||||
@ -163,34 +161,11 @@ class PlayerData {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current environment (needed when determining whether extension runs in fulls screen, theater, or normal)
|
||||
*/
|
||||
private lastEnvironment: ExtensionEnvironment;
|
||||
|
||||
get environment(): ExtensionEnvironment {
|
||||
if (this.isFullscreen) {
|
||||
return ExtensionEnvironment.Fullscreen;
|
||||
}
|
||||
if (this.isTheaterMode) {
|
||||
return ExtensionEnvironment.Theater;
|
||||
}
|
||||
return ExtensionEnvironment.Normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* ————————————————————————————————————————————————————————————————————————
|
||||
* END OF PROPERTIES
|
||||
* ————————————————————————————————————————————————————————————————————————
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
//#region lifecycle
|
||||
constructor(videoData) {
|
||||
try {
|
||||
// set all our helper objects
|
||||
this.logger = new ComponentLogger(videoData.logAggregator, 'PlayerData', {styles: {}});
|
||||
this.logger = videoData.logger;
|
||||
this.videoData = videoData;
|
||||
this.videoElement = videoData.video;
|
||||
this.pageInfo = videoData.pageInfo;
|
||||
@ -199,10 +174,14 @@ class PlayerData {
|
||||
|
||||
// do the rest
|
||||
this.invalid = false;
|
||||
this.updatePlayer();
|
||||
this.element = this.getPlayer();
|
||||
this.isTooSmall = (this.element.clientWidth < 1208 || this.element.clientHeight < 720);
|
||||
|
||||
this.initEventBus();
|
||||
|
||||
// we defer UI creation until player element is big enough
|
||||
// this happens in trackDimensionChanges!
|
||||
|
||||
this.dimensions = undefined;
|
||||
|
||||
this.periodicallyRefreshPlayerElement = false;
|
||||
@ -218,6 +197,7 @@ class PlayerData {
|
||||
return;
|
||||
}
|
||||
|
||||
this.trackDimensionChanges();
|
||||
this.startChangeDetection();
|
||||
|
||||
document.addEventListener('fullscreenchange', this.dimensionChangeListener);
|
||||
@ -232,7 +212,7 @@ class PlayerData {
|
||||
|
||||
private reloadPlayerDataConfig(siteConfUpdate) {
|
||||
// this.siteSettings = siteConfUpdate;
|
||||
this.updatePlayer();
|
||||
this.element = this.getPlayer();
|
||||
|
||||
this.periodicallyRefreshPlayerElement = false;
|
||||
try {
|
||||
@ -268,28 +248,24 @@ class PlayerData {
|
||||
//#endregion
|
||||
|
||||
deferredUiInitialization(playerDimensions) {
|
||||
if (this.ui || this.siteSettings.data.enableUI.fullscreen === ExtensionMode.Disabled) {
|
||||
if (this.ui || ! this.videoData.settings.active.ui?.inPlayer?.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
this.isFullscreen
|
||||
|| (
|
||||
this.siteSettings.data.enableUI.theater !== ExtensionMode.Disabled
|
||||
&& playerDimensions.width > 1208
|
||||
playerDimensions.width > 1208
|
||||
&& playerDimensions.height > 720
|
||||
)
|
||||
) {
|
||||
|
||||
this.ui = new UI(
|
||||
'ultrawidifyUi',
|
||||
{
|
||||
parentElement: this.element,
|
||||
eventBus: this.eventBus,
|
||||
playerData: this,
|
||||
uiSettings: this.videoData.settings.active.ui,
|
||||
siteSettings: this.siteSettings,
|
||||
uiSettings: this.videoData.settings.active.ui
|
||||
}
|
||||
);
|
||||
|
||||
@ -360,75 +336,53 @@ class PlayerData {
|
||||
return newTheaterMode;
|
||||
}
|
||||
|
||||
trackEnvironmentChanges() {
|
||||
if (this.environment !== this.lastEnvironment) {
|
||||
this.lastEnvironment = this.environment;
|
||||
this.eventBus.send('uw-environment-change', {newEnvironment: this.environment});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
trackDimensionChanges() {
|
||||
if (this._isTrackDimensionChangesActive) {
|
||||
// this shouldn't really get called, _ever_ ... but sometimes it happens
|
||||
console.warn('[PlayerData::trackDimensionChanges] trackDimensionChanges is already active!');
|
||||
// get player dimensions _once_
|
||||
let currentPlayerDimensions;
|
||||
this.isFullscreen = !!document.fullscreenElement;
|
||||
|
||||
if (this.isFullscreen) {
|
||||
currentPlayerDimensions = {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
};
|
||||
} else {
|
||||
currentPlayerDimensions = {
|
||||
width: this.element.offsetWidth,
|
||||
height: this.element.offsetHeight
|
||||
};
|
||||
|
||||
this.detectTheaterMode();
|
||||
}
|
||||
|
||||
// defer creating UI
|
||||
this.deferredUiInitialization(currentPlayerDimensions);
|
||||
|
||||
// if dimensions of the player box are the same as the last known
|
||||
// dimensions, we don't have to do anything ... in theory. In practice,
|
||||
// sometimes restore-ar doesn't appear to register the first time, and
|
||||
// this function doesn't really run often enough to warrant finding a
|
||||
// real, optimized fix.
|
||||
if (
|
||||
this.dimensions?.width == currentPlayerDimensions.width
|
||||
&& this.dimensions?.height == currentPlayerDimensions.height
|
||||
) {
|
||||
this.eventBus.send('restore-ar', null);
|
||||
this.eventBus.send('delayed-restore-ar', {delay: 500});
|
||||
this.dimensions = currentPlayerDimensions;
|
||||
return;
|
||||
}
|
||||
|
||||
this._isTrackDimensionChangesActive = true;
|
||||
// in every other case, we need to check if the player is still
|
||||
// big enough to warrant our extension running.
|
||||
this.handleSizeConstraints(currentPlayerDimensions);
|
||||
// this.handleDimensionChanges(currentPlayerDimensions, this.dimensions);
|
||||
|
||||
try {
|
||||
// get player dimensions _once_
|
||||
let currentPlayerDimensions;
|
||||
this.isFullscreen = !!document.fullscreenElement;
|
||||
|
||||
if (this.isFullscreen) {
|
||||
currentPlayerDimensions = {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
};
|
||||
} else {
|
||||
currentPlayerDimensions = {
|
||||
width: this.element.offsetWidth,
|
||||
height: this.element.offsetHeight
|
||||
};
|
||||
|
||||
this.detectTheaterMode();
|
||||
}
|
||||
|
||||
// defer creating UI
|
||||
this.deferredUiInitialization(currentPlayerDimensions);
|
||||
|
||||
// if dimensions of the player box are the same as the last known
|
||||
// dimensions, we don't have to do anything ... in theory. In practice,
|
||||
// sometimes restore-ar doesn't appear to register the first time, and
|
||||
// this function doesn't really run often enough to warrant finding a
|
||||
// real, optimized fix.
|
||||
if (
|
||||
this.dimensions?.width == currentPlayerDimensions.width
|
||||
&& this.dimensions?.height == currentPlayerDimensions.height
|
||||
) {
|
||||
this.eventBus.send('restore-ar', null);
|
||||
this.eventBus.send('delayed-restore-ar', {delay: 500});
|
||||
this.dimensions = currentPlayerDimensions;
|
||||
this._isTrackDimensionChangesActive = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// in every other case, we need to check if the player is still
|
||||
// big enough to warrant our extension running.
|
||||
this.handleSizeConstraints(currentPlayerDimensions);
|
||||
// this.handleDimensionChanges(currentPlayerDimensions, this.dimensions);
|
||||
|
||||
// Save current dimensions to avoid triggering this function pointlessly
|
||||
this.dimensions = currentPlayerDimensions;
|
||||
} catch (e) {
|
||||
|
||||
}
|
||||
|
||||
this._isTrackDimensionChangesActive = false;
|
||||
// Save current dimensions to avoid triggering this function pointlessly
|
||||
this.dimensions = currentPlayerDimensions;
|
||||
}
|
||||
|
||||
|
||||
@ -454,13 +408,13 @@ class PlayerData {
|
||||
|
||||
private handleDimensionChanges(newDimensions: PlayerDimensions, oldDimensions: PlayerDimensions) {
|
||||
if (this.runLevel === RunLevel.Off ) {
|
||||
this.logger.info('handleDimensionChanges', "player size changed, but PlayerDetect is in disabled state. The player element is probably too small.");
|
||||
this.logger.log('info', 'debug', "[PlayerDetect] player size changed, but PlayerDetect is in disabled state. The player element is probably too small.");
|
||||
return;
|
||||
}
|
||||
|
||||
// this 'if' is just here for debugging — real code starts later. It's safe to collapse and
|
||||
// ignore the contents of this if (unless we need to change how logging works)
|
||||
this.logger.info('handleDimensionChanges', "player size potentially changed.\n\nold dimensions:", oldDimensions, '\nnew dimensions:', newDimensions);
|
||||
this.logger.log('info', 'debug', "[PlayerDetect] player size potentially changed.\n\nold dimensions:", oldDimensions, '\nnew dimensions:', newDimensions);
|
||||
|
||||
// if size doesn't match, trigger onPlayerDimensionChange
|
||||
if (
|
||||
@ -482,17 +436,9 @@ class PlayerData {
|
||||
}
|
||||
}
|
||||
|
||||
onPlayerDimensionsChanged: ResizeObserverCallback = _.debounce(
|
||||
(mutationList?, observer?) => {
|
||||
this.trackDimensionChanges();
|
||||
this.trackEnvironmentChanges();
|
||||
},
|
||||
250, // do it once per this many ms
|
||||
{
|
||||
leading: true, // do it when we call this fallback first
|
||||
trailing: true // do it after the timeout if we call this callback few more times
|
||||
}
|
||||
);
|
||||
onPlayerDimensionsChanged(mutationList?, observer?) {
|
||||
this.trackDimensionChanges();
|
||||
}
|
||||
|
||||
|
||||
//#region player element change detection
|
||||
@ -506,19 +452,29 @@ class PlayerData {
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.observer) {
|
||||
this.observer.disconnect();
|
||||
}
|
||||
|
||||
this.observer = new ResizeObserver(
|
||||
this.onPlayerDimensionsChanged
|
||||
_.debounce( // don't do this too much:
|
||||
(m,o) => {
|
||||
this.onPlayerDimensionsChanged(m,o)
|
||||
},
|
||||
250, // do it once per this many ms
|
||||
{
|
||||
leading: true, // do it when we call this fallback first
|
||||
trailing: true // do it after the timeout if we call this callback few more times
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
const observerConf = {
|
||||
attributes: true,
|
||||
// attributeFilter: ['style', 'class'],
|
||||
attributeOldValue: true,
|
||||
};
|
||||
|
||||
this.observer.observe(this.element);
|
||||
} catch (e) {
|
||||
console.error("failed to set observer",e );
|
||||
console.error("failed to set observer",e )
|
||||
}
|
||||
|
||||
// legacy mode still exists, but acts as a fallback for observers and is triggered less
|
||||
// frequently in order to avoid too many pointless checks
|
||||
this.legacyChangeDetection();
|
||||
@ -590,39 +546,12 @@ class PlayerData {
|
||||
return this.elementStack;
|
||||
}
|
||||
|
||||
|
||||
updatePlayer(options?: {verbose?: boolean, newElement?: HTMLElement}) {
|
||||
const newPlayer = options?.newElement ?? this.getPlayer(options);
|
||||
|
||||
if (newPlayer === this.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
// clean up and re-initialize UI
|
||||
this.ui?.destroy();
|
||||
delete this.ui;
|
||||
|
||||
this.element = newPlayer;
|
||||
|
||||
this.ui = new UI(
|
||||
'ultrawidifyUi',
|
||||
{
|
||||
parentElement: this.element,
|
||||
eventBus: this.eventBus,
|
||||
playerData: this,
|
||||
uiSettings: this.videoData.settings.active.ui,
|
||||
siteSettings: this.siteSettings,
|
||||
}
|
||||
);
|
||||
|
||||
this.trackDimensionChanges();
|
||||
this.trackEnvironmentChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns HTML element of the player
|
||||
*/
|
||||
private getPlayer(options?: {verbose?: boolean}): HTMLElement {
|
||||
getPlayer(options?: {verbose?: boolean}): HTMLElement {
|
||||
const host = window.location.hostname;
|
||||
let element = this.videoElement.parentNode;
|
||||
const videoWidth = this.videoElement.offsetWidth;
|
||||
const videoHeight = this.videoElement.offsetHeight;
|
||||
let playerCandidate;
|
||||
@ -643,6 +572,7 @@ class PlayerData {
|
||||
}
|
||||
|
||||
// if mode is given, we follow the preference
|
||||
|
||||
if (this.siteSettings.data.currentDOMConfig?.elements?.player?.manual && this.siteSettings.data.currentDOMConfig?.elements?.player?.mode) {
|
||||
if (this.siteSettings.data.currentDOMConfig?.elements?.player?.mode === 'qs') {
|
||||
playerCandidate = this.getPlayerQs(playerQs, elementStack, videoWidth, videoHeight);
|
||||
@ -689,12 +619,16 @@ class PlayerData {
|
||||
this.equalish(elementStack[currentIndex].element.offsetWidth, elementStack[nextIndex].element.offsetWidth, 2)
|
||||
&& this.equalish(elementStack[currentIndex].element.offsetHeight, elementStack[nextIndex].element.offsetHeight, 2)
|
||||
) {
|
||||
// this.siteSettings.set('playerAutoConfig.initialIndex', this.siteSettings.data.playerAutoConfig.initialIndex + 1, {noSave: true});
|
||||
// this.siteSettings.set('playerAutoConfig.modified', true);
|
||||
// console.log('updated site settings:', this.siteSettings.data.playerAutoConfig);
|
||||
// this.videoData.settings.saveWithoutReload();
|
||||
this.siteSettings.set('playerAutoConfig.initialIndex', this.siteSettings.data.playerAutoConfig.initialIndex + 1, {noSave: true});
|
||||
this.siteSettings.set('playerAutoConfig.modified', true);
|
||||
console.log('updated site settings:', this.siteSettings.data.playerAutoConfig);
|
||||
this.videoData.settings.saveWithoutReload();
|
||||
|
||||
this.updatePlayer({newElement: elementStack[nextIndex].element});
|
||||
this.ui?.destroy();
|
||||
this.ui = undefined;
|
||||
|
||||
this.element = elementStack[nextIndex].element;
|
||||
this.trackDimensionChanges();
|
||||
}
|
||||
}
|
||||
|
||||
@ -709,7 +643,6 @@ class PlayerData {
|
||||
let penaltyMultiplier = 1;
|
||||
const sizePenaltyMultiplier = 0.1;
|
||||
const perLevelScorePenalty = 10;
|
||||
let sameSizeBonus = 0;
|
||||
|
||||
for (const [index, element] of elementStack.entries()) {
|
||||
element.index = index;
|
||||
@ -758,13 +691,7 @@ class PlayerData {
|
||||
|
||||
// we prefer elements closer to the video, so the score of each potential
|
||||
// candidate gets dinked a bit
|
||||
// score -= perLevelScorePenalty * penaltyMultiplier;
|
||||
|
||||
if (element.width === elementStack[index - 1].width && element.height === elementStack[index - 1].height) {
|
||||
score += ++sameSizeBonus;
|
||||
} else {
|
||||
sameSizeBonus = 0;
|
||||
}
|
||||
score -= perLevelScorePenalty * penaltyMultiplier;
|
||||
|
||||
element.autoScore = score;
|
||||
element.heuristics['autoScoreDetails'] = {
|
||||
@ -779,37 +706,19 @@ class PlayerData {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
let bestCandidate: any = {autoScore: -99999999, initialValue: true};
|
||||
for (const element of elementStack) {
|
||||
if (element.autoScore > bestCandidate.autoScore) {
|
||||
bestCandidate = element;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestCandidate.initialValue) {
|
||||
bestCandidate = null;
|
||||
} else {
|
||||
bestCandidate.heuristics['autoMatch'] = true;
|
||||
// if (this.siteSettings.data.playerAutoConfig?.initialIndex !== bestCandidate.index) {
|
||||
// this.siteSettings.set('playerAutoConfig.initialIndex', bestCandidate.index, {reload: false, scripted: true});
|
||||
// }
|
||||
}
|
||||
|
||||
// BUT WAIT! THERE'S MORE
|
||||
// Some sites (youtube) can re-parent elements, causing current player element to vanish from DOM
|
||||
if (bestCandidate) {
|
||||
const observer = new MutationObserver( (mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
mutation.removedNodes.forEach((node) => {
|
||||
if (node === bestCandidate.element) {
|
||||
observer.disconnect();
|
||||
this.updatePlayer();
|
||||
}
|
||||
})
|
||||
});
|
||||
});
|
||||
observer.observe(bestCandidate.element.parentNode, {childList: true});
|
||||
if (this.siteSettings.data.playerAutoConfig?.initialIndex !== bestCandidate.index) {
|
||||
this.siteSettings.set('playerAutoConfig.initialIndex', bestCandidate.index, {reload: false});
|
||||
}
|
||||
}
|
||||
|
||||
return bestCandidate;
|
||||
@ -879,7 +788,7 @@ class PlayerData {
|
||||
*/
|
||||
private handlePlayerTreeRequest() {
|
||||
// this populates this.elementStack fully
|
||||
this.updatePlayer({verbose: true});
|
||||
this.getPlayer({verbose: true});
|
||||
console.log('tree:', JSON.parse(JSON.stringify(this.elementStack)));
|
||||
console.log('————————————————————— handling player tree request!')
|
||||
this.eventBus.send('uw-config-broadcast', {type: 'player-tree', config: JSON.parse(JSON.stringify(this.elementStack))});
|
||||
@ -920,7 +829,11 @@ class PlayerData {
|
||||
}
|
||||
|
||||
forceRefreshPlayerElement() {
|
||||
this.updatePlayer();
|
||||
this.ui?.destroy();
|
||||
this.ui = undefined;
|
||||
this.element = this.getPlayer();
|
||||
// this.notificationService?.replace(this.element);
|
||||
this.trackDimensionChanges();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,11 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import PlayerData from './PlayerData';
|
||||
import Resizer from '../video-transform/Resizer';
|
||||
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
||||
import CropModePersistence from '../../../common/enums/CropModePersistence.enum';
|
||||
import * as _ from 'lodash';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
import Logger from '../Logger';
|
||||
import Settings from '../Settings';
|
||||
import PageInfo from './PageInfo';
|
||||
import { sleep } from '../../../common/js/utils';
|
||||
@ -13,10 +17,6 @@ import { ExtensionStatus } from './ExtensionStatus';
|
||||
import { RunLevel } from '../../enum/run-level.enum';
|
||||
import { Aard } from '../aard/Aard';
|
||||
import { Stretch } from '../../../common/interfaces/StretchInterface';
|
||||
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
|
||||
import { ExtensionEnvironment } from '../../../common/interfaces/SettingsInterface';
|
||||
import { LogAggregator } from '../logging/LogAggregator';
|
||||
import { ComponentLogger } from '../logging/ComponentLogger';
|
||||
|
||||
/**
|
||||
* VideoData — handles CSS for the video element.
|
||||
@ -65,8 +65,7 @@ class VideoData {
|
||||
//#endregion
|
||||
|
||||
//#region helper objects
|
||||
logger: ComponentLogger;
|
||||
logAggregator: LogAggregator
|
||||
logger: Logger;
|
||||
settings: Settings; // AARD needs it
|
||||
siteSettings: SiteSettings;
|
||||
pageInfo: PageInfo;
|
||||
@ -77,8 +76,6 @@ class VideoData {
|
||||
|
||||
eventBus: EventBus;
|
||||
extensionStatus: ExtensionStatus;
|
||||
|
||||
private currentEnvironment: ExtensionEnvironment;
|
||||
//#endregion
|
||||
|
||||
|
||||
@ -100,11 +97,6 @@ class VideoData {
|
||||
},
|
||||
'set-run-level': {
|
||||
function: (runLevel: RunLevel) => this.setRunLevel(runLevel)
|
||||
},
|
||||
'uw-environment-change': {
|
||||
function: () => {
|
||||
this.onEnvironmentChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,9 +108,7 @@ class VideoData {
|
||||
* @param pageInfo
|
||||
*/
|
||||
constructor(video, settings: Settings, siteSettings: SiteSettings, pageInfo: PageInfo){
|
||||
this.logAggregator = pageInfo.logAggregator;
|
||||
this.logger = new ComponentLogger(this.logAggregator, 'VideoData', {});
|
||||
|
||||
this.logger = pageInfo.logger;
|
||||
this.arSetupComplete = false;
|
||||
this.video = video;
|
||||
this.destroyed = false;
|
||||
@ -168,7 +158,7 @@ class VideoData {
|
||||
if (!this.video?.videoWidth || !this.video?.videoHeight || this.video.readyState < 2) {
|
||||
return; // onVideoLoaded is a lie in this case
|
||||
}
|
||||
this.logger.info('onVideoLoaded', '%c ——————————— Initiating phase two of videoData setup ———————————', 'color: #0f9');
|
||||
this.logger.log('info', 'init', '%c[VideoData::onVideoLoaded] ——————————— Initiating phase two of videoData setup ———————————', 'color: #0f9');
|
||||
|
||||
this.hasDrm = hasDrm(this.video);
|
||||
this.eventBus.send(
|
||||
@ -180,12 +170,12 @@ class VideoData {
|
||||
this.videoDimensionsLoaded = true;
|
||||
try {
|
||||
await this.setupStageTwo();
|
||||
this.logger.info('onVideoLoaded', '%c——————————— videoData setup stage two complete ———————————', 'color: #0f9');
|
||||
this.logger.log('info', 'init', '%c[VideoData::onVideoLoaded] ——————————— videoData setup stage two complete ———————————', 'color: #0f9');
|
||||
} catch (e) {
|
||||
this.logger.error('onVideoLoaded', '%c ——————————— Setup stage two failed. ———————————\n', 'color: #f00', e);
|
||||
this.logger.log('error', 'init', '%c[VideoData::onVideoLoaded] ——————————— Setup stage two failed. ———————————\n', 'color: #f00', e);
|
||||
}
|
||||
} else if (!this.videoDimensionsLoaded) {
|
||||
this.logger.debug('onVideoLoaded', "%cRecovering from illegal video dimensions. Resetting aspect ratio.", "background: #afd, color: #132");
|
||||
this.logger.log('info', 'debug', "%c[VideoData::restoreCrop] Recovering from illegal video dimensions. Resetting aspect ratio.", "background: #afd, color: #132");
|
||||
|
||||
this.restoreCrop();
|
||||
this.videoDimensionsLoaded = true;
|
||||
@ -201,7 +191,7 @@ class VideoData {
|
||||
if (!this.mutationObserver) {
|
||||
this.setupMutationObserver();
|
||||
}
|
||||
// this.eventBus.send('inject-css', this.baseVideoCss);
|
||||
this.eventBus.send('inject-css', this.baseVideoCss);
|
||||
} catch (e) {
|
||||
console.error('Failed to inject base css!', e);
|
||||
}
|
||||
@ -214,11 +204,11 @@ class VideoData {
|
||||
|
||||
//#region <video> event handlers
|
||||
onLoadedData() {
|
||||
this.logger.info('onLoadedData', 'Video fired event "loaded data!"');
|
||||
this.logger.log('info', 'init', '[VideoData::ctor->video.onloadeddata] Video fired event "loaded data!"');
|
||||
this.onVideoLoaded();
|
||||
}
|
||||
onLoadedMetadata() {
|
||||
this.logger.log('onLoadedData', 'Video fired event "loaded metadata!"');
|
||||
this.logger.log('info', 'init', '[VideoData::ctor->video.onloadedmetadata] Video fired event "loaded metadata!"');
|
||||
this.onVideoLoaded();
|
||||
}
|
||||
onTimeUpdate() {
|
||||
@ -232,7 +222,7 @@ class VideoData {
|
||||
* Sets up event listeners for this video
|
||||
*/
|
||||
async setupEventListeners() {
|
||||
this.logger.info('setupEventListeners', '%c——————————— Starting event listener setup! ———————————', 'color: #0f9');
|
||||
this.logger.log('info', 'init', '%c[VideoData::setupEventListeners] ——————————— Starting event listener setup! ———————————', 'color: #0f9');
|
||||
|
||||
// this is in case extension loads before the video
|
||||
this.video.addEventListener('loadeddata', this.onLoadedData.bind(this));
|
||||
@ -241,7 +231,7 @@ class VideoData {
|
||||
// this one is in case extension loads after the video is loaded
|
||||
this.video.addEventListener('timeupdate', this.onTimeUpdate.bind(this));
|
||||
|
||||
this.logger.info('setupEventListeners', '%c——————————— Event listeners setup complete! ———————————', 'color: #0f9');
|
||||
this.logger.log('info', 'init', '%c[VideoData::setupEventListeners] ——————————— Event listeners setup complete! ———————————', 'color: #0f9');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -265,7 +255,7 @@ class VideoData {
|
||||
}
|
||||
|
||||
|
||||
this.logger.info('setupStageTwo', 'Created videoData with vdid', this.vdid);
|
||||
this.logger.log('info', ['debug', 'init'], '[VideoData::ctor] Created videoData with vdid', this.vdid);
|
||||
|
||||
|
||||
// Everything is set up at this point. However, we are still purely "read-only" at this point. Player CSS should not be changed until
|
||||
@ -364,7 +354,7 @@ class VideoData {
|
||||
* cleans up handlers and stuff when the show is over
|
||||
*/
|
||||
destroy() {
|
||||
this.logger.info('destroy', `<vdid:${this.vdid}> received destroy command`);
|
||||
this.logger.log('info', ['debug', 'init'], `[VideoData::destroy] <vdid:${this.vdid}> received destroy command`);
|
||||
|
||||
if (this.video) {
|
||||
this.video.classList.remove(this.userCssClassName);
|
||||
@ -399,27 +389,8 @@ class VideoData {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
onEnvironmentChanged() {
|
||||
if (!this.player) {
|
||||
return;
|
||||
}
|
||||
if (this.currentEnvironment !== this.player.environment) {
|
||||
this.logger.warn('onEnvironmentChanged', 'environment changed from:', this.currentEnvironment, 'to:', this.player.environment);
|
||||
|
||||
this.currentEnvironment = this.player.environment;
|
||||
if (this.siteSettings.data.enable[this.player.environment] === ExtensionMode.Disabled) {
|
||||
this.setRunLevel(RunLevel.Off);
|
||||
} else {
|
||||
this.restoreAr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setRunLevel(runLevel: RunLevel, options?: {fromPlayer?: boolean}) {
|
||||
if (this.player && this.siteSettings.data.enable[this.player.environment] !== ExtensionMode.Enabled) {
|
||||
runLevel = RunLevel.Off;
|
||||
}
|
||||
|
||||
if (this.runLevel === runLevel) {
|
||||
return; // also no need to propagate to the player
|
||||
}
|
||||
@ -475,10 +446,10 @@ class VideoData {
|
||||
|
||||
restoreCrop() {
|
||||
if (!this.resizer) {
|
||||
this.logger.warn('restoreCrop', 'Resizer has not been initialized yet. Crop will not be restored.');
|
||||
this.logger.log('warn', 'debug', '[VideoData::restoreCrop] Resizer has not been initialized yet. Crop will not be restored.');
|
||||
return;
|
||||
}
|
||||
this.logger.info('restoreCrop', 'Attempting to reset aspect ratio.');
|
||||
this.logger.log('info', 'debug', '[VideoData::restoreCrop] Attempting to reset aspect ratio.')
|
||||
// if we have default crop set for this page, apply this.
|
||||
// otherwise, reset crop
|
||||
|
||||
@ -491,7 +462,7 @@ class VideoData {
|
||||
this.stopArDetection();
|
||||
this.startArDetection();
|
||||
} catch (e) {
|
||||
this.logger.warn('restoreCrop', 'Autodetection not resumed. Reason:', e);
|
||||
this.logger.log('warn', 'debug', '[VideoData::restoreCrop] Autodetection not resumed. Reason:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -518,13 +489,13 @@ class VideoData {
|
||||
let confirmAspectRatioRestore = false;
|
||||
|
||||
if (!this.video) {
|
||||
this.logger.error('onVideoMutation', 'mutation was triggered, but video element is missing. Something is fishy. Terminating this uw instance.');
|
||||
this.logger.log('error', 'debug', '[VideoData::onVideoMutation] mutation was triggered, but video element is missing. Something is fishy. Terminating this uw instance.');
|
||||
this.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.enabled) {
|
||||
this.logger.info('onVideoMutation', 'mutation was triggered, but the extension is disabled. Is the player window too small?');
|
||||
this.logger.log('info', 'info', '[VideoData::onVideoMutation] mutation was triggered, but the extension is disabled. Is the player window too small?');
|
||||
return;
|
||||
}
|
||||
|
||||
@ -554,8 +525,8 @@ class VideoData {
|
||||
onVideoDimensionsChanged(mutationList, observer) {
|
||||
if (!mutationList || this.video === undefined) { // something's wrong
|
||||
if (observer && this.video) {
|
||||
this.logger.warn(
|
||||
'onVideoDimensionChanged',
|
||||
this.logger.log(
|
||||
'warn', 'debug',
|
||||
'onVideoDimensionChanged encountered a weird state. video and observer exist, but mutationlist does not.\n\nmutationList:', mutationList,
|
||||
'\nobserver:', observer,
|
||||
'\nvideo:', this.video,
|
||||
@ -575,15 +546,13 @@ class VideoData {
|
||||
*/
|
||||
private _processDimensionsChanged() {
|
||||
if (!this.player) {
|
||||
this.logger.warn('_processDimensionsChanged', `Player is not defined. This is super haram.`, this.player);
|
||||
this.logger.log('warn', 'debug', `[VideoData::_processDimensionsChanged] Player is not defined. This is super haram.`, this.player);
|
||||
return;
|
||||
}
|
||||
// adding player observer taught us that if element size gets triggered by a class, then
|
||||
// the 'style' attributes don't necessarily trigger. This means we also need to trigger
|
||||
// restoreAr here, in case video size was changed this way
|
||||
this.player.forceRefreshPlayerElement();
|
||||
this.eventBus.send('uw-environment-change', {newEnvironment: this.player.environment});
|
||||
|
||||
this.restoreAr();
|
||||
|
||||
// sometimes something fucky wucky happens and mutations aren't detected correctly, so we
|
||||
@ -602,7 +571,7 @@ class VideoData {
|
||||
this._processDimensionsChanged,
|
||||
250,
|
||||
{
|
||||
// leading: true,
|
||||
leading: true,
|
||||
trailing: true
|
||||
}
|
||||
);
|
||||
@ -708,7 +677,7 @@ class VideoData {
|
||||
|
||||
|
||||
startArDetection() {
|
||||
this.logger.info('startArDetection', 'starting AR detection');
|
||||
this.logger.log('info', 'debug', "[VideoData::startArDetection] starting AR detection")
|
||||
if(this.destroyed || this.invalid) {
|
||||
// throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
|
||||
return;
|
||||
@ -725,9 +694,9 @@ class VideoData {
|
||||
if (!this.aard) {
|
||||
this.initArDetection();
|
||||
}
|
||||
this.aard.startCheck();
|
||||
this.aard.start();
|
||||
} catch (e) {
|
||||
this.logger.warn('startArDetection', 'Could not start aard for some reason. Was the function was called too early?', e);
|
||||
this.logger.log('warn', 'debug', '[VideoData::startArDetection()] Could not start aard for some reason. Was the function was called too early?', e);
|
||||
}
|
||||
}
|
||||
|
||||
@ -756,16 +725,17 @@ class VideoData {
|
||||
checkVideoSizeChange(){
|
||||
const videoWidth = this.video.offsetWidth;
|
||||
const videoHeight = this.video.offsetHeight;
|
||||
|
||||
{
|
||||
// this 'if' is just here for debugging — real code starts later. It's safe to collapse and
|
||||
// ignore the contents of this if (unless we need to change how logging works)
|
||||
if (this.logger.canLog('debug')){
|
||||
if(! this.video) {
|
||||
this.logger.warn('checkVideoSizeChange', "player element isn't defined");
|
||||
this.logger.log('info', 'videoDetect', "[VideoDetect] player element isn't defined");
|
||||
}
|
||||
if ( this.video &&
|
||||
( this.dimensions?.width != videoWidth ||
|
||||
this.dimensions?.height != videoHeight )
|
||||
( this.dimensions?.width != videoWidth ||
|
||||
this.dimensions?.height != videoHeight )
|
||||
) {
|
||||
this.logger.debug('checkVideoSizeChange', "player size changed. reason: dimension change. Old dimensions?", this.dimensions.width, this.dimensions.height, "new dimensions:", this.video.offsetWidth, this.video.offsetHeight);
|
||||
this.logger.log('info', 'debug', "[VideoDetect] player size changed. reason: dimension change. Old dimensions?", this.dimensions.width, this.dimensions.height, "new dimensions:", this.video.offsetWidth, this.video.offsetHeight);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,29 +3,29 @@ import Debug from '../../conf/Debug';
|
||||
import Scaler, { CropStrategy, VideoDimensions } from './Scaler';
|
||||
import Stretcher from './Stretcher';
|
||||
import Zoom from './Zoom';
|
||||
import PlayerData from '../video-data/PlayerData';
|
||||
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
|
||||
import StretchType from '../../../common/enums/StretchType.enum';
|
||||
import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum';
|
||||
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
||||
import CropModePersistance from '../../../common/enums/CropModePersistence.enum';
|
||||
import { sleep } from '../Util';
|
||||
import Logger from '../Logger';
|
||||
import siteSettings from '../Settings';
|
||||
import VideoData from '../video-data/VideoData';
|
||||
import EventBus from '../EventBus';
|
||||
import { _cp } from '../../../common/js/utils';
|
||||
import Settings from '../Settings';
|
||||
import { Ar, ArVariant } from '../../../common/interfaces/ArInterface';
|
||||
import { Ar } from '../../../common/interfaces/ArInterface';
|
||||
import { RunLevel } from '../../enum/run-level.enum';
|
||||
import * as _ from 'lodash';
|
||||
import getElementStyles from '../../util/getElementStyles';
|
||||
import { Stretch } from '../../../common/interfaces/StretchInterface';
|
||||
import { ComponentLogger } from '../logging/ComponentLogger';
|
||||
|
||||
if(Debug.debug) {
|
||||
console.log("Loading: Resizer.js");
|
||||
}
|
||||
|
||||
enum ResizerMode {
|
||||
Crop = 1,
|
||||
Zoom = 2
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizer is the top class and is responsible for figuring out which component needs to crop, which
|
||||
* component needs to zoom, and which component needs to stretch.
|
||||
@ -41,7 +41,7 @@ class Resizer {
|
||||
//#endregion
|
||||
|
||||
//#region helper objects
|
||||
logger: ComponentLogger;
|
||||
logger: Logger;
|
||||
settings: Settings;
|
||||
siteSettings: SiteSettings;
|
||||
scaler: Scaler;
|
||||
@ -63,8 +63,6 @@ class Resizer {
|
||||
currentCssValidFor: any;
|
||||
currentVideoSettings: any;
|
||||
|
||||
private effectiveZoom: {x: number, y: number} = {x: 1, y: 1};
|
||||
|
||||
_lastAr: Ar = {type: AspectRatioType.Initial};
|
||||
set lastAr(x: Ar) {
|
||||
// emit updates for UI when setting lastAr, but only if AR really changed
|
||||
@ -85,22 +83,17 @@ class Resizer {
|
||||
//#endregion
|
||||
|
||||
cycleableAspectRatios: Ar[];
|
||||
cycleableZoomAspectRatios: Ar[];
|
||||
nextCycleOptionIndex = 0;
|
||||
|
||||
|
||||
//#region event bus configuration
|
||||
private eventBusCommands = {
|
||||
'get-effective-zoom': [{
|
||||
function: () => {
|
||||
this.eventBus.send('announce-zoom', this.manualZoom ? {x: this.zoom.scale, y: this.zoom.scaleY} : this.zoom.effectiveZoom);
|
||||
}
|
||||
}],
|
||||
'set-ar': [{
|
||||
function: (config: any) => {
|
||||
this.manualZoom = false; // this only gets called from UI or keyboard shortcuts, making this action safe.
|
||||
|
||||
if (config.type !== AspectRatioType.Cycle) {
|
||||
this.setAr({...config, variant: ArVariant.Crop});
|
||||
this.setAr(config);
|
||||
} else {
|
||||
// if we manually switched to a different aspect ratio, cycle from that ratio forward
|
||||
const lastArIndex = this.cycleableAspectRatios.findIndex(x => x.type === this.lastAr.type && x.ratio === this.lastAr.ratio);
|
||||
@ -108,29 +101,11 @@ class Resizer {
|
||||
this.nextCycleOptionIndex = (lastArIndex + 1) % this.cycleableAspectRatios.length;
|
||||
}
|
||||
|
||||
this.setAr({...this.cycleableAspectRatios[this.nextCycleOptionIndex], variant: ArVariant.Crop});
|
||||
this.setAr(this.cycleableAspectRatios[this.nextCycleOptionIndex]);
|
||||
this.nextCycleOptionIndex = (this.nextCycleOptionIndex + 1) % this.cycleableAspectRatios.length;
|
||||
}
|
||||
}
|
||||
}],
|
||||
'set-ar-zoom': [{
|
||||
function: (config: any) => {
|
||||
this.manualZoom = false; // this only gets called from UI or keyboard shortcuts, making this action safe.
|
||||
|
||||
if (config.type !== AspectRatioType.Cycle) {
|
||||
this.setAr({...config, variant: ArVariant.Zoom});
|
||||
} else {
|
||||
// if we manually switched to a different aspect ratio, cycle from that ratio forward
|
||||
const lastArIndex = this.cycleableZoomAspectRatios.findIndex(x => x.type === this.lastAr.type && x.ratio === this.lastAr.ratio);
|
||||
if (lastArIndex !== -1) {
|
||||
this.nextCycleOptionIndex = (lastArIndex + 1) % this.cycleableZoomAspectRatios.length;
|
||||
}
|
||||
|
||||
this.setAr({...this.cycleableZoomAspectRatios[this.nextCycleOptionIndex], variant: ArVariant.Zoom});
|
||||
this.nextCycleOptionIndex = (this.nextCycleOptionIndex + 1) % this.cycleableZoomAspectRatios.length;
|
||||
}
|
||||
}
|
||||
}],
|
||||
'set-alignment': [{
|
||||
function: (config: any) => {
|
||||
this.setVideoAlignment(config.x, config.y);
|
||||
@ -143,12 +118,10 @@ class Resizer {
|
||||
}
|
||||
}],
|
||||
'set-zoom': [{
|
||||
function: (config: any) => {
|
||||
this.setZoom(config?.zoom ?? {zoom: 1});
|
||||
}
|
||||
function: (config: any) => this.setZoom(config.zoom, config.axis, config.noAnnounce)
|
||||
}],
|
||||
'change-zoom': [{
|
||||
function: (config: any) => this.zoomStep(config.zoom)
|
||||
function: (config: any) => this.zoomStep(config.step)
|
||||
}],
|
||||
'get-ar': [{
|
||||
function: () => this.eventBus.send('uw-config-broadcast', {type: 'ar', config: this.lastAr})
|
||||
@ -184,7 +157,7 @@ class Resizer {
|
||||
constructor(videoData) {
|
||||
this.resizerId = (Math.random()*100).toFixed(0);
|
||||
this.videoData = videoData;
|
||||
this.logger = new ComponentLogger(videoData.logAggregator, 'Resizer');
|
||||
this.logger = videoData.logger;
|
||||
this.video = videoData.video;
|
||||
this.settings = videoData.settings;
|
||||
this.siteSettings = videoData.siteSettings;
|
||||
@ -215,11 +188,6 @@ class Resizer {
|
||||
.filter(x => [AspectRatioType.FitHeight, AspectRatioType.FitWidth, AspectRatioType.Fixed, AspectRatioType.Reset].includes(x?.arguments?.type))
|
||||
.map(x => x.arguments) as Ar[];
|
||||
|
||||
this.cycleableZoomAspectRatios =
|
||||
(this.settings?.active?.commands?.zoom ?? [])
|
||||
.filter(x => x.action === 'set-ar-zoom' && x.arguments?.type !== AspectRatioType.Cycle)
|
||||
.map(x => x.arguments) as Ar[];
|
||||
|
||||
this.nextCycleOptionIndex = 0;
|
||||
this.userCssClassName = videoData.userCssClassName;
|
||||
}
|
||||
@ -238,7 +206,7 @@ class Resizer {
|
||||
}
|
||||
|
||||
destroy(){
|
||||
this.logger.info('destroy', `<rid:${this.resizerId}> received destroy command.`);
|
||||
this.logger.log('info', ['debug', 'init'], `[Resizer::destroy] <rid:${this.resizerId}> received destroy command.`);
|
||||
this.destroyed = true;
|
||||
}
|
||||
|
||||
@ -255,7 +223,7 @@ class Resizer {
|
||||
let ratioOut;
|
||||
|
||||
if (!this.videoData.video) {
|
||||
this.logger.info('calculateRatioForLegacyOptions', "No video??", this.videoData.video, "— killing videoData");
|
||||
this.logger.log('info', 'debug', "[Scaler.js::modeToAr] No video??",this.videoData.video, "killing videoData");
|
||||
this.videoData.destroy();
|
||||
return null;
|
||||
}
|
||||
@ -264,7 +232,7 @@ class Resizer {
|
||||
if (! this.videoData.player.dimensions) {
|
||||
ratioOut = screen.width / screen.height;
|
||||
} else {
|
||||
this.logger.info('calculateRatioForLegacyOptions', `<rid:${this.resizerId}> Player dimensions:`, this.videoData.player.dimensions.width ,'x', this.videoData.player.dimensions.height,'aspect ratio:', this.videoData.player.dimensions.width / this.videoData.player.dimensions.height)
|
||||
this.logger.log('info', 'debug', `[Resizer::calculateRatioForLegacyOptions] <rid:${this.resizerId}> Player dimensions:`, this.videoData.player.dimensions.width ,'x', this.videoData.player.dimensions.height,'aspect ratio:', this.videoData.player.dimensions.width / this.videoData.player.dimensions.height)
|
||||
ratioOut = this.videoData.player.dimensions.width / this.videoData.player.dimensions.height;
|
||||
}
|
||||
|
||||
@ -280,7 +248,7 @@ class Resizer {
|
||||
ar.ratio = ratioOut < fileAr ? ratioOut : fileAr;
|
||||
}
|
||||
else if(ar.type === AspectRatioType.Reset){
|
||||
this.logger.info('modeToAr', "Using original aspect ratio -", fileAr);
|
||||
this.logger.log('info', 'debug', "[Scaler.js::modeToAr] Using original aspect ratio -", fileAr);
|
||||
ar.ratio = fileAr;
|
||||
} else {
|
||||
return null;
|
||||
@ -289,7 +257,7 @@ class Resizer {
|
||||
return ar;
|
||||
}
|
||||
|
||||
updateAr(ar: Ar) {
|
||||
updateAr(ar) {
|
||||
if (!ar) {
|
||||
return;
|
||||
}
|
||||
@ -308,27 +276,8 @@ class Resizer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts and stops Aard as necessary. Returns 'true' if we can
|
||||
* stop setting aspect ratio early.
|
||||
* @param ar
|
||||
* @param resizerMode
|
||||
* @returns
|
||||
*/
|
||||
private handleAard(ar: Ar): boolean {
|
||||
if (ar.type === AspectRatioType.Automatic) {
|
||||
this.videoData.aard?.startCheck(ar.variant);
|
||||
return true;
|
||||
} else if (ar.type !== AspectRatioType.AutomaticUpdate) {
|
||||
this.videoData.aard?.stop();
|
||||
} else if (this.stretcher.stretch.type === StretchType.Basic) {
|
||||
this.videoData?.aard?.stop();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async setAr(ar: Ar, lastAr?: Ar) {
|
||||
if (this.destroyed || ar == null) {
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -345,8 +294,11 @@ class Resizer {
|
||||
}
|
||||
|
||||
// handle autodetection stuff
|
||||
if (this.handleAard(ar)) {
|
||||
if (ar.type === AspectRatioType.Automatic) {
|
||||
this.videoData.aard?.start();
|
||||
return;
|
||||
} else if (ar.type !== AspectRatioType.AutomaticUpdate) {
|
||||
this.videoData.aard?.stop();
|
||||
}
|
||||
|
||||
if (ar.type !== AspectRatioType.AutomaticUpdate) {
|
||||
@ -354,11 +306,15 @@ class Resizer {
|
||||
}
|
||||
|
||||
if (!this.video.videoWidth || !this.video.videoHeight) {
|
||||
this.logger.warn('setAr', `<rid:${this.resizerId}> Video has no width or no height. This is not allowed. Aspect ratio will not be set, and videoData will be uninitialized.`);
|
||||
this.logger.log('warning', 'debug', '[Resizer::setAr] <rid:'+this.resizerId+'> Video has no width or no height. This is not allowed. Aspect ratio will not be set, and videoData will be uninitialized.');
|
||||
this.videoData.videoUnloaded();
|
||||
}
|
||||
|
||||
this.logger.info('setAr', `<rid:${this.resizerId}> trying to set ar. New ar:`, ar);
|
||||
this.logger.log('info', 'debug', '%c[Resizer::setAr] <rid:'+this.resizerId+'> trying to set ar. New ar:', 'background-color: #4c3a2f, color: #ffa349', ar);
|
||||
|
||||
if (ar == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let stretchFactors: VideoDimensions | any;
|
||||
|
||||
@ -368,7 +324,6 @@ class Resizer {
|
||||
(ar.type !== AspectRatioType.Fixed && ar.type !== AspectRatioType.Manual) // anything not these two _always_ changes AR
|
||||
|| ar.type !== this.lastAr.type // this also means aspect ratio has changed
|
||||
|| ar.ratio !== this.lastAr.ratio // this also means aspect ratio has changed
|
||||
|| ar.variant !== this.lastAr.variant
|
||||
) {
|
||||
this.zoom.reset();
|
||||
this.resetPan();
|
||||
@ -395,7 +350,7 @@ class Resizer {
|
||||
// I'm not sure whether they do. Check that.
|
||||
ar = this.calculateRatioForLegacyOptions(ar);
|
||||
if (! ar) {
|
||||
this.logger.info('setAr', `<rid:${this.resizerId}> Something wrong with ar or the player. Doing nothing.`);
|
||||
this.logger.log('info', 'resizer', `[Resizer::setAr] <${this.resizerId}> Something wrong with ar or the player. Doing nothing.`);
|
||||
return;
|
||||
}
|
||||
this.lastAr = {type: ar.type, ratio: ar.ratio};
|
||||
@ -405,12 +360,27 @@ class Resizer {
|
||||
this.videoData.destroy();
|
||||
}
|
||||
|
||||
// pause AR on:
|
||||
// * ar.type NOT automatic
|
||||
// * ar.type is auto, but stretch is set to basic basic stretch
|
||||
//
|
||||
// unpause when using other modes
|
||||
if ((ar.type !== AspectRatioType.Automatic && ar.type !== AspectRatioType.AutomaticUpdate) || this.stretcher.stretch.type === StretchType.Basic) {
|
||||
this.videoData?.aard?.stop();
|
||||
} else {
|
||||
if (ar.type !== AspectRatioType.AutomaticUpdate) {
|
||||
if (this.lastAr.type === AspectRatioType.Automatic || this.lastAr.type === AspectRatioType.AutomaticUpdate) {
|
||||
this.videoData?.aard?.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// do stretch thingy
|
||||
if ([StretchType.NoStretch, StretchType.Conditional, StretchType.FixedSource].includes(this.stretcher.stretch.type)) {
|
||||
stretchFactors = this.scaler.calculateCrop(ar);
|
||||
|
||||
if (!stretchFactors || stretchFactors.error){
|
||||
this.logger.error('setAr', ` <rid:${this.resizerId}> failed to set AR due to problem with calculating crop. Error:`, stretchFactors?.error);
|
||||
if(! stretchFactors || stretchFactors.error){
|
||||
this.logger.log('error', 'debug', `[Resizer::setAr] <rid:${this.resizerId}> failed to set AR due to problem with calculating crop. Error:`, stretchFactors?.error);
|
||||
if (stretchFactors?.error === 'no_video'){
|
||||
this.videoData.destroy();
|
||||
return;
|
||||
@ -431,24 +401,25 @@ class Resizer {
|
||||
} else if (this.stretcher.stretch.type === StretchType.FixedSource) {
|
||||
this.stretcher.applyStretchFixedSource(stretchFactors);
|
||||
}
|
||||
this.logger.info('setAr', "Processed stretch factors for ",
|
||||
this.logger.log('info', 'debug', "[Resizer::setAr] Processed stretch factors for ",
|
||||
this.stretcher.stretch.type === StretchType.NoStretch ? 'stretch-free crop.' :
|
||||
this.stretcher.stretch.type === StretchType.Conditional ? 'crop with conditional StretchType.' : 'crop with fixed stretch',
|
||||
'Stretch factors are:', stretchFactors
|
||||
);
|
||||
|
||||
} else if (this.stretcher.stretch.type === StretchType.Hybrid) {
|
||||
stretchFactors = this.stretcher.calculateStretch(ar.ratio);
|
||||
this.logger.info('setAr', 'Processed stretch factors for hybrid stretch/crop. Stretch factors are:', stretchFactors);
|
||||
this.logger.log('info', 'debug', '[Resizer::setAr] Processed stretch factors for hybrid stretch/crop. Stretch factors are:', stretchFactors);
|
||||
} else if (this.stretcher.stretch.type === StretchType.Fixed) {
|
||||
stretchFactors = this.stretcher.calculateStretchFixed(ar.ratio);
|
||||
stretchFactors = this.stretcher.calculateStretchFixed(ar.ratio)
|
||||
} else if (this.stretcher.stretch.type === StretchType.Basic) {
|
||||
stretchFactors = this.stretcher.calculateBasicStretch();
|
||||
this.logger.log('setAr', 'Processed stretch factors for basic StretchType. Stretch factors are:', stretchFactors);
|
||||
this.logger.log('info', 'debug', '[Resizer::setAr] Processed stretch factors for basic StretchType. Stretch factors are:', stretchFactors);
|
||||
} else {
|
||||
stretchFactors = this.scaler.calculateCrop(ar);
|
||||
this.logger.error(
|
||||
'setAr',
|
||||
'Okay wtf happened? If you see this, something has gone wrong. Pretending stretchMode is set tu NoStretch. Stretch factors are:', stretchFactors,
|
||||
this.logger.log(
|
||||
'error', 'debug',
|
||||
'[Resizer::setAr] Okay wtf happened? If you see this, something has gone wrong. Pretending stretchMode is set tu NoStretch. Stretch factors are:', stretchFactors,
|
||||
"\n------[ i n f o d u m p ]------\nstretcher:", this.stretcher,
|
||||
'\nargs: ar (corrected for legacy):', ar, 'last ar (optional argument):', lastAr
|
||||
);
|
||||
@ -458,12 +429,12 @@ class Resizer {
|
||||
}
|
||||
|
||||
applyScaling(stretchFactors: VideoDimensions, options?: {noAnnounce?: boolean, ar?: Ar}) {
|
||||
this.zoom.effectiveZoom = {x: stretchFactors.xFactor, y: stretchFactors.yFactor};
|
||||
this.stretcher.chromeBugMitigation(stretchFactors);
|
||||
|
||||
// announcing zoom somehow keeps incorrectly resetting zoom sliders in UI — UI is now polling for effective zoom while visible
|
||||
// if(!options?.noAnnounce) {
|
||||
// this.videoData.eventBus.send('announce-zoom', this.manualZoom ? {x: this.zoom.scale, y: this.zoom.scaleY} : this.zoom.effectiveZoom);
|
||||
// }
|
||||
// let the UI know
|
||||
if(!options?.noAnnounce) {
|
||||
this.videoData.eventBus.send('announce-zoom', {x: stretchFactors.xFactor, y: stretchFactors.yFactor});
|
||||
}
|
||||
|
||||
let translate = this.computeOffsets(stretchFactors, options?.ar);
|
||||
this.applyCss(stretchFactors, translate);
|
||||
@ -505,7 +476,7 @@ class Resizer {
|
||||
const relativeX = (event.pageX - player.offsetLeft) / player.offsetWidth;
|
||||
const relativeY = (event.pageY - player.offsetTop) / player.offsetHeight;
|
||||
|
||||
this.logger.info({src: 'panHandler', origin: 'mousemove'}, "mousemove.pageX, pageY:", event.pageX, event.pageY, "\nrelativeX/Y:", relativeX, relativeY);
|
||||
this.logger.log('info', 'mousemove', "[Resizer::panHandler] mousemove.pageX, pageY:", event.pageX, event.pageY, "\nrelativeX/Y:", relativeX, relativeY)
|
||||
|
||||
this.setPan(relativeX, relativeY);
|
||||
}
|
||||
@ -562,13 +533,9 @@ class Resizer {
|
||||
this.restore();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores aspect ratio to last known aspect ratio
|
||||
* @returns
|
||||
*/
|
||||
restore() {
|
||||
if (!this.manualZoom) {
|
||||
this.logger.info('restore', `<rid:${this.resizerId}> attempting to restore aspect ratio`, {'lastAr': this.lastAr} );
|
||||
this.logger.log('info', 'debug', "[Resizer::restore] <rid:"+this.resizerId+"> attempting to restore aspect ratio", {'lastAr': this.lastAr} );
|
||||
|
||||
// this is true until we verify that css has actually been applied
|
||||
if(this.lastAr.type === AspectRatioType.Initial){
|
||||
@ -606,36 +573,9 @@ class Resizer {
|
||||
}
|
||||
}
|
||||
|
||||
private _setZoomTimeout;
|
||||
private _latestSetZoomArgs: any | undefined;
|
||||
private _SET_ZOOM_RATE_LIMIT_MS = 50;
|
||||
/**
|
||||
* Sets zoom level. This function is rate limited, because slider may spam the fuck out of this function call
|
||||
* @param zoomLevel
|
||||
* @param axis
|
||||
* @param noAnnounce
|
||||
* @returns
|
||||
*/
|
||||
setZoom(zoomLevel: number | {x: number, y: number}, noAnnounce?) {
|
||||
if (this._setZoomTimeout) {
|
||||
this._latestSetZoomArgs = {zoomLevel, noAnnounce};
|
||||
return;
|
||||
}
|
||||
setZoom(zoomLevel: number, axis?: 'x' | 'y', noAnnounce?) {
|
||||
this.manualZoom = true;
|
||||
this.zoom.setZoom(zoomLevel);
|
||||
|
||||
this._setZoomTimeout = setTimeout(
|
||||
() => {
|
||||
clearTimeout(this._setZoomTimeout);
|
||||
this._setZoomTimeout = undefined;
|
||||
|
||||
if (this._latestSetZoomArgs) {
|
||||
this.setZoom(this._latestSetZoomArgs.zoomLevel);
|
||||
}
|
||||
this._latestSetZoomArgs = undefined;
|
||||
},
|
||||
this._SET_ZOOM_RATE_LIMIT_MS
|
||||
);
|
||||
this.zoom.setZoom(zoomLevel, axis, noAnnounce);
|
||||
}
|
||||
|
||||
zoomStep(step){
|
||||
@ -753,7 +693,7 @@ class Resizer {
|
||||
|
||||
private _computeOffsetsRecursionGuard: boolean = false;
|
||||
computeOffsets(stretchFactors: VideoDimensions, ar?: Ar){
|
||||
this.logger.info('computeOffsets', `<rid:${this.resizerId}> video will be aligned to `, this.videoAlignment, '— stretch factors before processing:', stretchFactors);
|
||||
this.logger.log('info', 'debug', "[Resizer::computeOffsets] <rid:"+this.resizerId+"> video will be aligned to ", this.videoAlignment);
|
||||
|
||||
const {realVideoWidth, realVideoHeight, marginX, marginY} = this.computeVideoDisplayedDimensions();
|
||||
|
||||
@ -821,9 +761,8 @@ class Resizer {
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.info(
|
||||
'computeOffsets',
|
||||
`<rid:${this.resizerId}> calculated offsets:`,
|
||||
this.logger.log(
|
||||
'info', ['debug', 'resizer'], "[Resizer::_res_computeOffsets] <rid:"+this.resizerId+"> calculated offsets:",
|
||||
'\n\n---- elements ----',
|
||||
'\nplayer element: ', this.videoData.player.element,
|
||||
'\nvideo element: ', this.videoData.video,
|
||||
@ -851,15 +790,13 @@ class Resizer {
|
||||
// conditions are true at the same time, we need to go 'chiny reckon' and recheck our player
|
||||
// element. Chances are our video is not getting aligned correctly
|
||||
if (
|
||||
(
|
||||
(this.videoData.video.offsetWidth > this.videoData.player.dimensions.width && this.videoData.video.offsetHeight > this.videoData.player.dimensions.height) ||
|
||||
(this.videoData.video.offsetWidth < this.videoData.player.dimensions.width && this.videoData.video.offsetHeight < this.videoData.player.dimensions.height)
|
||||
) && ar?.variant !== ArVariant.Zoom
|
||||
(this.videoData.video.offsetWidth > this.videoData.player.dimensions.width && this.videoData.video.offsetHeight > this.videoData.player.dimensions.height) ||
|
||||
(this.videoData.video.offsetWidth < this.videoData.player.dimensions.width && this.videoData.video.offsetHeight < this.videoData.player.dimensions.height)
|
||||
) {
|
||||
this.logger.warn('computeOffsets', `<rid:${this.resizerId}> We are getting some incredibly funny results here.\n\n`,
|
||||
this.logger.log('warn', ['debugger', 'resizer'], `[Resizer::_res_computeOffsets] <rid:${this.resizerId}> We are getting some incredibly funny results here.\n\n`,
|
||||
`Video seems to be both wider and taller (or shorter and narrower) than player element at the same time. This is super duper not supposed to happen.\n\n`,
|
||||
`Player element needs to be checked.`
|
||||
);
|
||||
)
|
||||
|
||||
// sometimes this appears to randomly recurse.
|
||||
// There seems to be no way to reproduce it.
|
||||
@ -933,13 +870,13 @@ class Resizer {
|
||||
// apply extra CSS here. In case of duplicated properties, extraCss overrides
|
||||
// default styleString
|
||||
if (! this.video) {
|
||||
this.logger.warn('applyCss', `<rid:${this.resizerId}> Video went missing, doing nothing.`);
|
||||
this.logger.log('warn', 'debug', "[Resizer::applyCss] <rid:"+this.resizerId+"> Video went missing, doing nothing.");
|
||||
|
||||
this.videoData.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.info('applyCss', `<rid:${this.resizerId}> will apply css.`, {stretchFactors, translate});
|
||||
this.logger.log('info', ['debug', 'resizer'], "[Resizer::applyCss] <rid:"+this.resizerId+"> will apply css.", {stretchFactors, translate});
|
||||
|
||||
// save stuff for quick tests (before we turn numbers into css values):
|
||||
this.currentVideoSettings = {
|
||||
@ -974,18 +911,18 @@ class Resizer {
|
||||
|
||||
// inject new CSS or replace existing one
|
||||
if (!this.userCss) {
|
||||
this.logger.debug('setStyleString', `<rid:${this.resizerId}> Setting new css: `, newCssString);
|
||||
this.logger.log('info', ['debug', 'resizer'], "[Resizer::setStyleString] <rid:"+this.resizerId+"> Setting new css: ", newCssString);
|
||||
|
||||
this.eventBus.send('inject-css', {cssString: newCssString});
|
||||
this.userCss = newCssString;
|
||||
} else if (newCssString !== this.userCss) {
|
||||
this.logger.debug('setStyleString', `<rid:${this.resizerId}}> Replacing css.\nOld string:`, this.userCss, "\nNew string:", newCssString)
|
||||
this.logger.log('info', ['debug', 'resizer'], "[Resizer::setStyleString] <rid:"+this.resizerId+"> Replacing css.\nOld string:", this.userCss, "\nNew string:", newCssString);
|
||||
// we only replace css if it
|
||||
this.eventBus.send('replace-css', {oldCssString: this.userCss, newCssString});
|
||||
|
||||
this.userCss = newCssString;
|
||||
} else {
|
||||
this.logger.debug('setStyleString', `<rid:${this.resizerId}> Existing css is still valid, doing nothing.`);
|
||||
this.logger.log('info', ['debug', 'resizer'], "[Resizer::setStyleString] <rid:"+this.resizerId+"> Existing css is still valid, doing nothing.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import Debug from '../../conf/Debug';
|
||||
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
import VideoData from '../video-data/VideoData';
|
||||
import { Ar, ArVariant } from '../../../common/interfaces/ArInterface';
|
||||
import { ComponentLogger } from '../logging/ComponentLogger';
|
||||
import Logger from '../Logger';
|
||||
|
||||
|
||||
export enum CropStrategy {
|
||||
@ -44,13 +45,13 @@ export type VideoDimensions = {
|
||||
class Scaler {
|
||||
//#region helper objects
|
||||
conf: VideoData;
|
||||
logger: ComponentLogger;
|
||||
logger: Logger;
|
||||
//#endregion
|
||||
|
||||
// functions
|
||||
constructor(videoData) {
|
||||
this.conf = videoData;
|
||||
this.logger = new ComponentLogger(videoData.logAggregator, 'Scaler', {});
|
||||
this.logger = videoData.logger;
|
||||
}
|
||||
|
||||
|
||||
@ -63,7 +64,7 @@ class Scaler {
|
||||
let ratioOut;
|
||||
|
||||
if (!this.conf.video) {
|
||||
this.logger.error('modeToAr', "No video??",this.conf.video, "killing videoData");
|
||||
this.logger.log('error', 'debug', "[Scaler.js::modeToAr] No video??",this.conf.video, "killing videoData");
|
||||
this.conf.destroy();
|
||||
return null;
|
||||
}
|
||||
@ -91,7 +92,7 @@ class Scaler {
|
||||
return ratioOut;
|
||||
}
|
||||
else if (ar.type === AspectRatioType.Reset) {
|
||||
this.logger.info('modeToAr', "Using original aspect ratio -", fileAr)
|
||||
this.logger.log('info', 'debug', "[Scaler.js::modeToAr] Using original aspect ratio -", fileAr)
|
||||
ar.ar = fileAr;
|
||||
return fileAr;
|
||||
}
|
||||
@ -99,7 +100,7 @@ class Scaler {
|
||||
return null;
|
||||
}
|
||||
|
||||
calculateCrop(ar: Ar): VideoDimensions | {error: string, [x: string]: any} {
|
||||
calculateCrop(ar: {type: AspectRatioType, ratio?: number}): VideoDimensions | {error: string, [x: string]: any} {
|
||||
/**
|
||||
* STEP 1: NORMALIZE ASPECT RATIO
|
||||
*
|
||||
@ -135,7 +136,7 @@ class Scaler {
|
||||
}
|
||||
|
||||
if(!this.conf.video){
|
||||
this.logger.info('calculateCrop', "ERROR — no video detected. Conf:", this.conf, "video:", this.conf.video, "video dimensions:", this.conf.video && this.conf.video.videoWidth, '×', this.conf.video && this.conf.video.videoHeight);
|
||||
this.logger.log('info', 'debug', "[Scaler::calculateCrop] ERROR — no video detected. Conf:", this.conf, "video:", this.conf.video, "video dimensions:", this.conf.video && this.conf.video.videoWidth, '×', this.conf.video && this.conf.video.videoHeight);
|
||||
|
||||
this.conf.destroy();
|
||||
return {error: "no_video"};
|
||||
@ -143,7 +144,7 @@ class Scaler {
|
||||
if (this.conf.video.videoWidth == 0 || this.conf.video.videoHeight == 0) {
|
||||
// that's illegal, but not illegal enough to just blast our shit to high hell
|
||||
// mr officer will let you go with a warning this time around
|
||||
this.logger.error('calculateCrop', "Video has illegal dimensions. Video dimensions:", this.conf.video && this.conf.video.videoWidth, '×', this.conf.video && this.conf.video.videoHeight);
|
||||
this.logger.log('error', 'debug', "[Scaler::calculateCrop] Video has illegal dimensions. Video dimensions:", this.conf.video && this.conf.video.videoWidth, '×', this.conf.video && this.conf.video.videoHeight);
|
||||
|
||||
return {error: "illegal_video_dimensions"};
|
||||
}
|
||||
@ -163,21 +164,15 @@ class Scaler {
|
||||
|
||||
// handle fuckie-wuckies
|
||||
if (!ar.ratio){
|
||||
this.logger.error('calculateCrop', "no ar?", ar.ratio, " -- we were given this mode:", ar);
|
||||
this.logger.log('error', 'scaler', "[Scaler::calculateCrop] no ar?", ar.ratio, " -- we were given this mode:", ar);
|
||||
return {error: "no_ar", ratio: ar.ratio};
|
||||
}
|
||||
|
||||
this.logger.info('calculateCrop', "trying to set ar. args are: ar->",ar.ratio,"; this.conf.player.dimensions->",this.conf.player.dimensions.width, "×", this.conf.player.dimensions.height, "| obj:", this.conf.player.dimensions);
|
||||
this.logger.log('info', 'scaler', "[Scaler::calculateCrop] trying to set ar. args are: ar->",ar.ratio,"; this.conf.player.dimensions->",this.conf.player.dimensions.width, "×", this.conf.player.dimensions.height, "| obj:", this.conf.player.dimensions);
|
||||
|
||||
// If we encounter invalid players, we try to update its dimensions
|
||||
// ONCE before throwing an error
|
||||
if( (! this.conf.player.dimensions) || this.conf.player.dimensions.width === 0 || this.conf.player.dimensions.height === 0 ){
|
||||
this.logger.error('calculateCrop', "ERROR — no (or invalid) this.conf.player.dimensions:",this.conf.player.dimensions);
|
||||
this.conf.player.updatePlayer();
|
||||
|
||||
if( (! this.conf.player.dimensions) || this.conf.player.dimensions.width === 0 || this.conf.player.dimensions.height === 0 ){
|
||||
return {error: "this.conf.player.dimensions_error"};
|
||||
}
|
||||
this.logger.log('error', 'scaler', "[Scaler::calculateCrop] ERROR — no (or invalid) this.conf.player.dimensions:",this.conf.player.dimensions);
|
||||
return {error: "this.conf.player.dimensions_error"};
|
||||
}
|
||||
|
||||
// we can finally start computing required video dimensions now:
|
||||
@ -189,7 +184,7 @@ class Scaler {
|
||||
}
|
||||
|
||||
|
||||
this.logger.info('calculateCrop', "ar is " ,ar.ratio, ", file ar is", streamAr, ",ar variant", ar.variant ,"\nthis.conf.player.dimensions are ", this.conf.player.dimensions.width, "×", this.conf.player.dimensions.height, "| obj:", this.conf.player.dimensions, this.conf.player.element);
|
||||
this.logger.log('info', 'scaler', "[Scaler::calculateCrop] ar is " ,ar.ratio, ", file ar is", streamAr, ", this.conf.player.dimensions are ", this.conf.player.dimensions.width, "×", this.conf.player.dimensions.height, "| obj:", this.conf.player.dimensions);
|
||||
|
||||
const videoDimensions: VideoDimensions = {
|
||||
xFactor: 1,
|
||||
@ -204,7 +199,7 @@ class Scaler {
|
||||
}
|
||||
}
|
||||
|
||||
this.calculateCropCore(videoDimensions, ar.ratio, streamAr, playerAr, ar.variant)
|
||||
this.calculateCropCore(videoDimensions, ar.ratio, streamAr, playerAr)
|
||||
|
||||
return videoDimensions;
|
||||
}
|
||||
@ -217,11 +212,7 @@ class Scaler {
|
||||
* @param {*} streamAr
|
||||
* @param {*} playerAr
|
||||
*/
|
||||
calculateCropCore(videoDimensions: VideoDimensions, ar: number, streamAr: number, playerAr: number, variant?: ArVariant) {
|
||||
if (variant === ArVariant.Zoom) {
|
||||
playerAr = ar;
|
||||
}
|
||||
|
||||
calculateCropCore(videoDimensions: VideoDimensions, ar: number, streamAr: number, playerAr: number) {
|
||||
if (streamAr < playerAr) {
|
||||
if (streamAr < ar){
|
||||
// in this situation we have to crop letterbox on top/bottom of the player
|
||||
@ -263,8 +254,8 @@ class Scaler {
|
||||
const letterboxRatio = (1 - (playerAr / ar));
|
||||
|
||||
videoDimensions.relativeCropLimits = {
|
||||
top: ar > streamAr ? ( ar >= playerAr ? (letterboxRatio * -0.5) : 0) : 0,
|
||||
left: ar < streamAr ? ( ar <= playerAr ? (-0.5 / letterboxRatio) : 0) : 0,
|
||||
top: ar > streamAr ? ( ar > playerAr ? (letterboxRatio * -0.5) : 0) : 0,
|
||||
left: ar < streamAr ? ( ar < playerAr ? (-0.5 / letterboxRatio) : 0) : 0,
|
||||
}
|
||||
videoDimensions.preventAlignment = {
|
||||
x: ar > playerAr, // video is wider than player, so it's full width already
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { SiteSettings } from './../settings/SiteSettings';
|
||||
import StretchType from '../../../common/enums/StretchType.enum';
|
||||
import BrowserDetect from '../../conf/BrowserDetect';
|
||||
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
|
||||
import VideoData from '../video-data/VideoData';
|
||||
import Logger from '../Logger';
|
||||
import Settings from '../Settings';
|
||||
import { Stretch } from '../../../common/interfaces/StretchInterface';
|
||||
import { ComponentLogger } from '../logging/ComponentLogger';
|
||||
|
||||
// računa vrednosti za transform-scale (x, y)
|
||||
// transform: scale(x,y) se uporablja za raztegovanje videa, ne pa za približevanje
|
||||
@ -17,7 +19,7 @@ class Stretcher {
|
||||
|
||||
//#region helper objects
|
||||
conf: VideoData;
|
||||
logger: ComponentLogger;
|
||||
logger: Logger;
|
||||
settings: Settings;
|
||||
siteSettings: SiteSettings;
|
||||
//#endregion
|
||||
@ -27,7 +29,7 @@ class Stretcher {
|
||||
// functions
|
||||
constructor(videoData) {
|
||||
this.conf = videoData;
|
||||
this.logger = new ComponentLogger(videoData.logAggregator, 'Stretcher', {});;
|
||||
this.logger = videoData.logger;
|
||||
this.siteSettings = videoData.siteSettings;
|
||||
this.settings = videoData.settings;
|
||||
|
||||
@ -109,7 +111,7 @@ class Stretcher {
|
||||
// * we squeeze X axis, if target AR is narrower than player size
|
||||
// * we squeeze Y axis, if target AR is wider than the player size
|
||||
|
||||
this.logger.info('applyStretchFixedSource', `here's what we got:
|
||||
this.logger.log('info', 'stretcher', `[Stretcher::applyStretchFixedSource] here's what we got:
|
||||
postCropStretchFactors: x=${postCropStretchFactors.xFactor} y=${postCropStretchFactors.yFactor}
|
||||
fixedStretchRatio: ${this.stretch.ratio}
|
||||
videoAr: ${streamAr}
|
||||
@ -118,7 +120,7 @@ squeezeFactor: ${squeezeFactor}`, '\nvideo', this.conf.video);
|
||||
|
||||
postCropStretchFactors.xFactor *= squeezeFactor;
|
||||
|
||||
this.logger.info('applyStretchFixedSource', `here's what we'll apply:\npostCropStretchFactors: x=${postCropStretchFactors.x} y=${postCropStretchFactors.y}`);
|
||||
this.logger.log('info', 'stretcher', `[Stretcher::applyStretchFixedSource] here's what we'll apply:\npostCropStretchFactors: x=${postCropStretchFactors.x} y=${postCropStretchFactors.y}`);
|
||||
|
||||
return postCropStretchFactors;
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
import { ComponentLogger } from '../logging/ComponentLogger';
|
||||
import Debug from '../../conf/Debug';
|
||||
import Logger from '../Logger';
|
||||
import VideoData from '../video-data/VideoData';
|
||||
|
||||
// računa približevanje ter računa/popravlja odmike videa
|
||||
// calculates zooming and video offsets/panning
|
||||
const MIN_SCALE = 0.5;
|
||||
const MAX_SCALE = 8;
|
||||
const LOG_MAX_SCALE = Math.log2(MAX_SCALE);
|
||||
const LOG_MIN_SCALE = Math.log2(MIN_SCALE);
|
||||
|
||||
|
||||
class Zoom {
|
||||
//#region flags
|
||||
@ -15,7 +12,7 @@ class Zoom {
|
||||
|
||||
//#region helper objects
|
||||
conf: VideoData;
|
||||
logger: ComponentLogger;
|
||||
logger: Logger;
|
||||
//#endregion
|
||||
|
||||
//#region misc data
|
||||
@ -24,81 +21,86 @@ class Zoom {
|
||||
logScale: number = 0;
|
||||
logScaleY: number = 0;
|
||||
scaleStep: number = 0.1;
|
||||
logMinScale: number = -1; // 50% (log2(0.5) = -1)
|
||||
logMaxScale: number = 3; // 800% (log2(8) = 3)
|
||||
minScale = 0.5;
|
||||
maxScale = 8;
|
||||
minScale: number = -1; // 50% (log2(0.5) = -1)
|
||||
maxScale: number = 3; // 800% (log2(8) = 3)
|
||||
//#endregion
|
||||
|
||||
effectiveZoom: {x: number, y: number}; // we're setting this in Resizer based on Resizer data!
|
||||
|
||||
constructor(videoData) {
|
||||
this.conf = videoData;
|
||||
this.logger = new ComponentLogger(videoData.logAggregator, 'Zoom', {});
|
||||
this.logger = videoData.logger;
|
||||
}
|
||||
|
||||
reset(){
|
||||
this.scale = 1;
|
||||
this.scaleY = 1;
|
||||
this.logScale = 0;
|
||||
this.logScaleY = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases zoom by a given amount. Does not allow per-axis zoom.
|
||||
* Will set zoom level to x axis (+ given amount) if x and y zooms differ.
|
||||
* @param amount
|
||||
* @param axis — leave undefined to apply zoom to both axes
|
||||
*/
|
||||
zoomStep(amount: number, axis?: 'x' | 'y') {
|
||||
const effectiveLog = {
|
||||
x: Math.log2(this.effectiveZoom.x),
|
||||
y: Math.log2(this.effectiveZoom.y)
|
||||
};
|
||||
zoomStep(amount){
|
||||
this.logScale += amount;
|
||||
|
||||
let newLog = axis === 'y' ? effectiveLog.y : effectiveLog.x;
|
||||
newLog += amount;
|
||||
newLog = Math.min(Math.max(newLog, LOG_MIN_SCALE), LOG_MAX_SCALE);
|
||||
if (this.logScale <= this.minScale) {
|
||||
this.logScale = this.minScale;
|
||||
}
|
||||
if (this.logScale >= this.maxScale) {
|
||||
this.logScale = this.maxScale;
|
||||
}
|
||||
|
||||
// if axis is undefined, both of this statements should trigger)
|
||||
if (axis !== 'y') {
|
||||
this.logScale = newLog;
|
||||
}
|
||||
if (axis !== 'x') {
|
||||
this.logScaleY = newLog;
|
||||
}
|
||||
this.logScaleY = this.logScale;
|
||||
|
||||
this.scale = Math.pow(2, this.logScale);
|
||||
this.scaleY = Math.pow(2, this.logScaleY);
|
||||
|
||||
this.logger.log('info', 'debug', "[Zoom::zoomStep] changing zoom by", amount, ". New zoom level:", this.scale);
|
||||
this.processZoom();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets zoom to specific value
|
||||
* @param scale
|
||||
*/
|
||||
setZoom(scale: number | {x: number, y: number}){
|
||||
// NOTE: SCALE IS NOT LOGARITHMIC
|
||||
const scaleIn = (typeof scale === 'number') ?
|
||||
{
|
||||
x: scale,
|
||||
y: scale
|
||||
} : {
|
||||
x: scale.x ?? this.scale,
|
||||
y: scale.y ?? this.scaleY
|
||||
};
|
||||
setZoom(scale: number, axis?: 'x' |'y', noAnnounce?){
|
||||
this.logger.log('info', 'debug', "[Zoom::setZoom] Setting zoom to", scale, "!");
|
||||
|
||||
this.scale = Math.min(Math.max(scaleIn.x, MIN_SCALE), MAX_SCALE);
|
||||
this.scaleY = Math.min(Math.max(scaleIn.y, MIN_SCALE), MAX_SCALE);
|
||||
// NOTE: SCALE IS NOT LOGARITHMIC
|
||||
if(scale < Math.pow(2, this.minScale)) {
|
||||
scale = this.minScale;
|
||||
} else if (scale > Math.pow(2, this.maxScale)) {
|
||||
scale = this.maxScale;
|
||||
}
|
||||
|
||||
switch (axis) {
|
||||
case 'x':
|
||||
this.scale = scale;
|
||||
break;
|
||||
case 'y':
|
||||
this.scaleY = scale;
|
||||
break;
|
||||
default:
|
||||
this.scale = scale;
|
||||
this.scaleY = scale;
|
||||
}
|
||||
|
||||
this.processZoom();
|
||||
}
|
||||
|
||||
processZoom() {
|
||||
this.conf.resizer.toFixedAr();
|
||||
// this.conf.resizer.toFixedAr();
|
||||
|
||||
this.conf.resizer.applyScaling({xFactor: this.scale, yFactor: this.scaleY}, {noAnnounce: true});
|
||||
}
|
||||
|
||||
applyZoom(stretchFactors){
|
||||
if (!stretchFactors) {
|
||||
return;
|
||||
}
|
||||
this.logger.log('info', 'debug', "[Zoom::setZoom] Applying zoom. Stretch factors pre:", stretchFactors, " —> scale:", this.scale);
|
||||
|
||||
stretchFactors.xFactor *= this.scale;
|
||||
stretchFactors.yFactor *= this.scale;
|
||||
|
||||
this.logger.log('info', 'debug', "[Zoom::setZoom] Applying zoom. Stretch factors post:", stretchFactors);
|
||||
}
|
||||
}
|
||||
|
||||
export default Zoom;
|
||||
|
@ -5,18 +5,17 @@
|
||||
import UWContent from './UWContent';
|
||||
|
||||
if(process.env.CHANNEL !== 'stable'){
|
||||
|
||||
let isIframe;
|
||||
console.warn("\n\n\n\n\n\n ——— Sᴛλʀᴛɪɴɢ Uʟᴛʀᴀᴡɪᴅɪꜰʏ ———\n << ʟᴏᴀᴅɪɴɢ ᴍᴀɪɴ ꜰɪʟᴇ >>\n\n\n\n");
|
||||
try {
|
||||
isIframe = window.self !== window.top;
|
||||
if(window.self !== window.top){
|
||||
console.info("%cWe aren't in an iframe.", "color: #afc, background: #174");
|
||||
}
|
||||
else{
|
||||
console.info("%cWe are in an iframe!", "color: #fea, background: #d31", window.self, window.top);
|
||||
}
|
||||
} catch (e) {
|
||||
isIframe = true;
|
||||
console.info("%cWe are in an iframe!", "color: #fea, background: #d31");
|
||||
}
|
||||
|
||||
console.warn(
|
||||
"\n\n\n\n\n\n ——— Sᴛλʀᴛɪɴɢ Uʟᴛʀᴀᴡɪᴅɪꜰʏ ———\n << ʟᴏᴀᴅɪɴɢ ᴍᴀɪɴ ꜰɪʟᴇ >>\n\n\n\n",
|
||||
"\n - are we in iframe?", isIframe
|
||||
);
|
||||
}
|
||||
|
||||
const main = new UWContent();
|
||||
|
@ -53,9 +53,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Debug from '../../ext/conf/Debug';
|
||||
import BrowserDetect from '../../ext/conf/BrowserDetect';
|
||||
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator';
|
||||
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
|
||||
import Logger from '../../ext/lib/Logger';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
@ -75,7 +75,6 @@ export default {
|
||||
settings: {},
|
||||
settingsInitialized: false,
|
||||
logger: {},
|
||||
logAggregator: {},
|
||||
siteTabDisabled: false,
|
||||
videoTabDisabled: false,
|
||||
canShowVideoTab: {canShow: true, warning: true},
|
||||
@ -83,20 +82,18 @@ export default {
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.logAggregator = new LogAggregator('');
|
||||
this.logger = new ComponentLogger(this.logAggregator, 'App.vue');
|
||||
this.logger = new Logger();
|
||||
await this.logger.init({
|
||||
allowLogging: true,
|
||||
});
|
||||
|
||||
this.settings = new Settings({updateCallback: () => this.updateConfig(), logAggregator: this.logAggregator });
|
||||
this.settings = new Settings({updateCallback: () => this.updateConfig(), logger: this.logger});
|
||||
await this.settings.init();
|
||||
this.settingsInitialized = true;
|
||||
},
|
||||
components: {
|
||||
},
|
||||
methods: {
|
||||
updateConfig() {
|
||||
this.settings.init();
|
||||
this.$nextTick( () => this.$forceUpdate());
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -54,9 +54,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Debug from '../../ext/conf/Debug';
|
||||
import BrowserDetect from '../../ext/conf/BrowserDetect';
|
||||
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator';
|
||||
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
|
||||
import Logger from '../../ext/lib/Logger';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
@ -75,7 +75,6 @@ export default {
|
||||
currentZoom: 1,
|
||||
settings: {},
|
||||
settingsInitialized: false,
|
||||
logAggregator: {},
|
||||
logger: {},
|
||||
siteTabDisabled: false,
|
||||
videoTabDisabled: false,
|
||||
@ -84,20 +83,18 @@ export default {
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.logAggregator = new LogAggregator('');
|
||||
this.logger = new ComponentLogger(this.logAggregator, 'App.vue');
|
||||
this.logger = new Logger();
|
||||
await this.logger.init({
|
||||
allowLogging: true,
|
||||
});
|
||||
|
||||
this.settings = new Settings({updateCallback: () => this.updateConfig(), logAggregator: this.logAggregator});
|
||||
this.settings = new Settings({updateCallback: () => this.updateConfig(), logger: this.logger});
|
||||
await this.settings.init();
|
||||
this.settingsInitialized = true;
|
||||
},
|
||||
components: {
|
||||
},
|
||||
methods: {
|
||||
updateConfig() {
|
||||
this.settings.init();
|
||||
this.$nextTick( () => this.$forceUpdate());
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"name": "Ultrawidify",
|
||||
"description": "Removes black bars on ultrawide videos and offers advanced options to fix aspect ratio.",
|
||||
"version": "6.3.0",
|
||||
"version": "6.2.3",
|
||||
"icons": {
|
||||
"32":"res/icons/uw-32.png",
|
||||
"64":"res/icons/uw-64.png"
|
||||
|
@ -111,6 +111,10 @@
|
||||
<script>
|
||||
import Donate from '../common/misc/Donate.vue';
|
||||
import SuperAdvancedSettings from './SuperAdvancedSettings.vue';
|
||||
import Debug from '../ext/conf/Debug';
|
||||
import BrowserDetect from '../ext/conf/BrowserDetect';
|
||||
import ExtensionConf from '../ext/conf/ExtensionConf';
|
||||
import ObjectCopy from '../ext/lib/ObjectCopy';
|
||||
import Settings from '../ext/lib/Settings';
|
||||
import GeneralSettings from './GeneralSettings';
|
||||
import ControlsSettings from './controls-settings/ControlsSettings';
|
||||
@ -118,8 +122,8 @@ import AddEditActionPopup from './controls-settings/AddEditActionPopup';
|
||||
import ConfirmPopup from './common/ConfirmationPopup';
|
||||
import About from './about'
|
||||
import AutodetectionSettings from './AutodetectionSettings';
|
||||
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator';
|
||||
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
|
||||
// import SuperAdvancedSettings from './'
|
||||
import Logger from '../ext/lib/Logger';
|
||||
|
||||
export default {
|
||||
name: "Ultrawidify",
|
||||
@ -128,7 +132,6 @@ export default {
|
||||
selectedTab: "general",
|
||||
selectedTabTitle: "General settings",
|
||||
settings: {},
|
||||
logAggregator: {},
|
||||
logger: {},
|
||||
settingsInitialized: false,
|
||||
editActionPopupVisible: false,
|
||||
@ -142,10 +145,12 @@ export default {
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
this.logAggregator = new LogAggregator('');
|
||||
this.logger = new ComponentLogger(this.logAggregator, 'App.vue');
|
||||
this.logger = new Logger();
|
||||
await this.logger.init({
|
||||
allowLogging: true,
|
||||
});
|
||||
|
||||
this.settings = new Settings({updateCallback: this.updateSettings, logAggregator: this.logAggregator});
|
||||
this.settings = new Settings({updateCallback: this.updateSettings, logger: this.logger});
|
||||
await this.settings.init();
|
||||
|
||||
this.settingsInitialized = true;
|
||||
|
@ -601,6 +601,17 @@ export default {
|
||||
},
|
||||
created() {
|
||||
this.sensitivity = this.getSensitivity();
|
||||
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 10;
|
||||
canvas.height = 10;
|
||||
const ctx = canvas.getContext('2d');
|
||||
try {
|
||||
ctx.drawWindow(window,0, 0, 10, 10, "rgba(0,0,0,0)");
|
||||
this.fallbackModeAvailable = true;
|
||||
} catch (e) {
|
||||
this.fallbackModeAvailable = false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setArCheckFrequency(event) {
|
||||
|
Loading…
Reference in New Issue
Block a user