Compare commits

..

No commits in common. "master" and "hotfix/iframe-fix" have entirely different histories.

95 changed files with 3248 additions and 6296 deletions

View File

@ -13,7 +13,6 @@
"com", "com",
"comms", "comms",
"csui", "csui",
"cycleable",
"decycle", "decycle",
"dinked", "dinked",
"dinks", "dinks",
@ -47,7 +46,6 @@
"reddit", "reddit",
"rescan", "rescan",
"resizer", "resizer",
"resizers",
"scrollbar", "scrollbar",
"shitiness", "shitiness",
"smallcaps", "smallcaps",

View File

@ -1,57 +1,18 @@
# Changelog # 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.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 ### v6.0.1
* Fixed external links * Fixed external links

View File

@ -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.

View File

@ -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
View File

@ -1,6 +1,6 @@
{ {
"name": "ultrawidify", "name": "ultrawidify",
"version": "6.3.0", "version": "6.2.3",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -28,6 +28,7 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"requires": { "requires": {
"@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24" "@jridgewell/trace-mapping": "^0.3.24"
@ -1375,109 +1376,6 @@
"to-fast-properties": "^2.0.0" "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": { "@hapi/address": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz", "resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
@ -1557,57 +1455,6 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@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": { "@mdi/font": {
"version": "6.9.96", "version": "6.9.96",
"resolved": "https://registry.npmjs.org/@mdi/font/-/font-6.9.96.tgz", "resolved": "https://registry.npmjs.org/@mdi/font/-/font-6.9.96.tgz",
@ -1735,27 +1582,12 @@
"integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==",
"dev": true "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": { "@sindresorhus/is": {
"version": "0.7.0", "version": "0.7.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz",
"integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==", "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==",
"dev": true "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": { "@types/accepts": {
"version": "1.3.7", "version": "1.3.7",
"resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz", "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.7.tgz",
@ -1839,11 +1671,6 @@
"es6-promise": "*" "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": { "@types/express": {
"version": "4.17.21", "version": "4.17.21",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz",
@ -3205,11 +3032,6 @@
"sprintf-js": "~1.0.2" "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": { "arr-diff": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz",
@ -3553,11 +3375,6 @@
"integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==",
"dev": true "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": { "babel-code-frame": {
"version": "6.26.0", "version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@ -4943,11 +4760,6 @@
"mimic-response": "^1.0.0" "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": { "cmd-shim": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-3.0.3.tgz", "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-3.0.3.tgz",
@ -4973,11 +4785,6 @@
"integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==",
"dev": true "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": { "collection-visit": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@ -5559,11 +5366,6 @@
"sha.js": "^2.4.8" "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": { "cross-env": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.1.tgz", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-5.2.1.tgz",
@ -6213,11 +6015,6 @@
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true "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": { "diffie-hellman": {
"version": "5.0.3", "version": "5.0.3",
"resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz",
@ -6569,25 +6366,12 @@
"estraverse": "^4.1.1" "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": { "esprima": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true "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": { "esrecurse": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
@ -6947,7 +6731,8 @@
"fast-deep-equal": { "fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "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": { "fast-glob": {
"version": "2.2.7", "version": "2.2.7",
@ -6969,11 +6754,6 @@
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true "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": { "fastparse": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
@ -8335,11 +8115,6 @@
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true "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": { "import-fresh": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@ -8843,14 +8618,6 @@
"integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
"dev": true "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": { "is-regex": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "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==", "integrity": "sha512-fnjC0up+0SjEJtgmmG+teeel68kutkvzfctO/KxE3qJlbunkJYAshgH3boU++gSBHP8z5/r0ts0qRIrHf0RTQQ==",
"dev": true "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": { "js-base64": {
"version": "2.6.4", "version": "2.6.4",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz", "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": { "jsesc": {
"version": "2.5.2", "version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@ -9147,11 +8904,6 @@
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true "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": { "json-stringify-safe": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
@ -9174,21 +8926,6 @@
"graceful-fs": "^4.1.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": { "jsprim": {
"version": "1.4.2", "version": "1.4.2",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz",
@ -9293,11 +9030,6 @@
"json5": "^1.0.1" "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": { "locate-path": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "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", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "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": { "lodash.camelcase": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
@ -9536,11 +9263,6 @@
"fs-monkey": "^1.0.4" "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": { "memory-fs": {
"version": "0.5.0", "version": "0.5.0",
"resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz",
@ -9841,11 +9563,6 @@
"to-regex": "^3.0.1" "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": { "ndjson": {
"version": "1.5.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/ndjson/-/ndjson-1.5.0.tgz", "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", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" "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": { "require-main-filename": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", "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" "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": { "subscriptions-transport-ws": {
"version": "0.9.19", "version": "0.9.19",
"resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.19.tgz", "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", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" "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": { "svgo": {
"version": "0.7.2", "version": "0.7.2",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz",
@ -15809,65 +15475,6 @@
"builtins": "^1.0.3" "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": { "vary": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "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", "resolved": "https://registry.npmjs.org/vuex-webextensions/-/vuex-webextensions-1.3.3.tgz",
"integrity": "sha512-Qz+KmF4CYLfomAIuEjY4A9DYqzwXZo18TkvTyO84DkW/s274iI2IyuOSFZoLcVHMS5BjUhRbyFclqqAwz58zgw==" "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": { "watch": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/watch/-/watch-1.0.2.tgz", "resolved": "https://registry.npmjs.org/watch/-/watch-1.0.2.tgz",
@ -16986,11 +16588,6 @@
"zen-observable": "^0.8.0" "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": { "zip-stream": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-2.1.3.tgz",

View File

@ -1,6 +1,6 @@
{ {
"name": "ultrawidify", "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.", "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>", "author": "Tamius Han <tamius.han@gmail.com>",
"scripts": { "scripts": {
@ -36,7 +36,6 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mdi-vue": "^3.0.11", "mdi-vue": "^3.0.11",
"typescript": "^4.4.4", "typescript": "^4.4.4",
"vanilla-jsoneditor": "^3.3.0",
"vue": "^3.2.21", "vue": "^3.2.21",
"vue-style-loader": "^4.1.3", "vue-style-loader": "^4.1.3",
"vuex": "^4.0.2", "vuex": "^4.0.2",

View File

@ -1,13 +1,6 @@
import AspectRatioType from '../enums/AspectRatioType.enum'; import AspectRatioType from '../enums/AspectRatioType.enum';
export enum ArVariant {
Crop = undefined,
Zoom = 1
}
export interface Ar { export interface Ar {
type: AspectRatioType, type: AspectRatioType,
ratio?: number, ratio?: number
variant?: ArVariant,
offset?: number,
} }

View File

@ -6,19 +6,6 @@ import ExtensionMode from '../enums/ExtensionMode.enum'
import StretchType from '../enums/StretchType.enum' import StretchType from '../enums/StretchType.enum'
import VideoAlignmentType from '../enums/VideoAlignmentType.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 { export interface KeyboardShortcutInterface {
key?: string, key?: string,
code?: string, code?: string,
@ -168,23 +155,18 @@ export interface AardSettings {
} }
} }
interface DevSettings {
loadFromSnapshot: boolean,
}
interface SettingsInterface { interface SettingsInterface {
_updateFlags?: { _updateFlags?: {
requireReload?: SettingsReloadFlags, requireReload?: SettingsReloadFlags,
forSite?: string forSite?: string
} }
dev: DevSettings,
arDetect: AardSettings, arDetect: AardSettings,
ui: { ui: {
inPlayer: { inPlayer: {
enabled: boolean, // Deprecated — moved to site settings enabled: boolean,
enabledFullscreenOnly: boolean, // Deprecated — moved to site settings enabledFullscreenOnly: boolean,
popupAlignment: 'left' | 'right', popupAlignment: 'left' | 'right',
minEnabledWidth: number, // don't show UI if player is narrower than % of screen width 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 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] offsetX: number, // fed to translateX(offsetX + '%'). Valid range [-100, 0]
offsetY: number // fed to translateY(offsetY + '%'). Valid range [-100, 100] offsetY: number // fed to translateY(offsetY + '%'). Valid range [-100, 100]
}, },
}, }
devMode?: boolean,
dev: DevUiConfig,
} }
restrictions?: RestrictionsSettings; restrictions?: RestrictionsSettings;
@ -245,7 +225,6 @@ interface SettingsInterface {
pan?: any, pan?: any,
version?: string, version?: string,
preventReload?: boolean, preventReload?: boolean,
lastModified?: Date,
// ----------------------------------------- // -----------------------------------------
// ::: MITIGATIONS ::: // ::: MITIGATIONS :::
@ -258,6 +237,33 @@ interface SettingsInterface {
limit?: number, 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 // 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. // complicated way. Hopefully it'll be easier to maintain it that way.
commands?: { commands?: {
@ -306,9 +312,7 @@ export interface SiteSettingsInterface {
enable: ExtensionEnvironmentSettingsInterface; enable: ExtensionEnvironmentSettingsInterface;
enableAard: ExtensionEnvironmentSettingsInterface; enableAard: ExtensionEnvironmentSettingsInterface;
enableKeyboard: 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'; type?: 'official' | 'community' | 'user-defined' | 'testing' | 'officially-disabled' | 'unknown' | 'modified';
defaultType: 'official' | 'community' | 'user-defined' | 'testing' | 'officially-disabled' | 'unknown' | 'modified'; defaultType: 'official' | 'community' | 'user-defined' | 'testing' | 'officially-disabled' | 'unknown' | 'modified';

View File

@ -1,5 +1,5 @@
<template> <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"> <template v-for="rectangle of drawnRectangles" :key="rectangle.id ?? rectangle">
<!-- Player element overlays --> <!-- Player element overlays -->
@ -44,6 +44,13 @@ export default {
}, },
async created() { 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. * 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 * We can handle events with the same function we use to handle events from

View File

@ -24,7 +24,20 @@
@mouseleave="allowContextMenuHide()" @mouseleave="allowContextMenuHide()"
> >
<template v-slot:activator> <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 Ultrawidify
</div>
</template> </template>
<slot> <slot>
<!-- <!--
@ -64,7 +77,7 @@
</GhettoContextMenuOption> </GhettoContextMenuOption>
</slot> </slot>
</GhettoContextMenu> </GhettoContextMenu>
<GhettoContextMenu alignment="right"> <!-- <GhettoContextMenu alignment="right">
<template v-slot:activator> <template v-slot:activator>
Zoom Zoom
</template> </template>
@ -86,7 +99,7 @@
/> />
</GhettoContextMenuItem> </GhettoContextMenuItem>
</slot> </slot>
</GhettoContextMenu> </GhettoContextMenu> -->
<GhettoContextMenu alignment="right"> <GhettoContextMenu alignment="right">
<template v-slot:activator> <template v-slot:activator>
<div class="context-item"> <div class="context-item">
@ -144,13 +157,11 @@
Site compatibility: Site compatibility:
<SupportLevelIndicator <SupportLevelIndicator
:siteSupportLevel="siteSupportLevel" :siteSupportLevel="siteSupportLevel"
supportLevelStyle="font-size: 0.69rem !important;"
tooltipStyle="font-size: 0.8rem;"
> >
</SupportLevelIndicator> </SupportLevelIndicator>
<div v-if="statusFlags.hasDrm" class="aard-blocked"> <div v-if="statusFlags.hasDrm" class="aard-blocked">
Autodetection blocked<br/> Autodetection potentially<br/>
by <a style="color: #fff" href="https://en.wikipedia.org/wiki/Digital_rights_management" target="_blank">DRM</a>. unavailable due to <a style="color: #fff" href="https://en.wikipedia.org/wiki/Digital_rights_management" target="_blank">DRM</a>.
</div> </div>
<div v-else-if="statusFlags.aardErrors?.cors" class="aard-blocked"> <div v-else-if="statusFlags.aardErrors?.cors" class="aard-blocked">
Autodetection blocked<br/> Autodetection blocked<br/>
@ -158,7 +169,7 @@
</div> </div>
<div v-else-if="statusFlags.aardErrors?.webglError" class="aard-blocked"> <div v-else-if="statusFlags.aardErrors?.webglError" class="aard-blocked">
Autodetection unavailable<br/> Autodetection unavailable<br/>
(webgl error) due to webgl error.
</div> </div>
</GhettoContextMenuItem> </GhettoContextMenuItem>
</div> </div>
@ -205,6 +216,7 @@ import GhettoContextMenuItem from '@csui/src/components/GhettoContextMenuItem.vu
import GhettoContextMenuOption from '@csui/src/components/GhettoContextMenuOption.vue'; import GhettoContextMenuOption from '@csui/src/components/GhettoContextMenuOption.vue';
import AlignmentOptionsControlComponent from '@csui/src/PlayerUiPanels/AlignmentOptionsControlComponent.vue'; import AlignmentOptionsControlComponent from '@csui/src/PlayerUiPanels/AlignmentOptionsControlComponent.vue';
import BrowserDetect from '@src/ext/conf/BrowserDetect'; import BrowserDetect from '@src/ext/conf/BrowserDetect';
import Logger from '@src/ext/lib/Logger';
import Settings from '@src/ext/lib/Settings'; import Settings from '@src/ext/lib/Settings';
import EventBus from '@src/ext/lib/EventBus'; import EventBus from '@src/ext/lib/EventBus';
import UIProbeMixin from '@csui/src/utils/UIProbeMixin'; 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 SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue';
import TriggerZoneEditor from '@csui/src/components/TriggerZoneEditor.vue'; import TriggerZoneEditor from '@csui/src/components/TriggerZoneEditor.vue';
import ZoomControl from '@csui/src/popup/player-menu/ZoomControl.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 { export default {
components: { components: {
@ -256,7 +266,6 @@ export default {
BrowserDetect: BrowserDetect, BrowserDetect: BrowserDetect,
settingsInitialized: false, settingsInitialized: false,
eventBus: new EventBus(), eventBus: new EventBus(),
logAggregator: null,
logger: null, logger: null,
// NOTE: chromium doesn't allow us to access window.parent.location // NOTE: chromium doesn't allow us to access window.parent.location
@ -328,8 +337,12 @@ export default {
} }
}, },
async created() { async created() {
this.logAggregator = new LogAggregator('player-overlay'); this.logger = new Logger();
this.logger = new ComponentLogger(this.logAggregator, 'PlayerOverlay.vue');
// 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 = new Settings({afterSettingsSaved: this.updateConfig, logger: this.logger});
this.settings.listenAfterChange(() => this.updateTriggerZones()); this.settings.listenAfterChange(() => this.updateTriggerZones());
@ -483,8 +496,7 @@ export default {
}, },
acknowledgeNewFeature(featureKey) { acknowledgeNewFeature(featureKey) {
this.settings.active.newFeatureTracker[featureKey].show = 0; delete this.settings.active.newFeatureTracker[featureKey];
this.settings.active.newFeatureTracker[featureKey].acknowledged = true;
this.settings.saveWithoutReload(); this.settings.saveWithoutReload();
}, },
newFeatureViewUpdate(featureKey) { newFeatureViewUpdate(featureKey) {
@ -572,22 +584,7 @@ export default {
}, },
handleBusTunnelIn(payload) { handleBusTunnelIn(payload) {
this.eventBus.send( this.eventBus.send(payload.action, payload.config, payload.routingData);
payload.action,
payload.config,
{
...payload.context,
borderCrossings: {
...payload.context?.borderCrossings,
iframe: true
}
}
);
},
updateConfig() {
this.settings.init();
this.$nextTick( () => this.$forceUpdate());
} }
} }
} }
@ -714,10 +711,8 @@ export default {
flex-direction: column; flex-direction: column;
text-align: center; 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; margin-top: 0.5rem;
@ -732,7 +727,6 @@ export default {
} }
.aard-blocked { .aard-blocked {
font-size: 0.8rem;
color: #fa6; color: #fa6;
} }

View File

@ -1,5 +1,5 @@
<template> <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 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 extension is being displayed in a normal or a small/overflow popup breaks the popup
@ -10,45 +10,26 @@
further than that. further than that.
--> -->
<div v-if="settingsInitialized" <div v-if="settingsInitialized"
style="height: 100vh"
class="popup flex flex-col no-overflow" class="popup flex flex-col no-overflow"
:class="{'popup-chrome': ! BrowserDetect?.firefox}" :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"> <div class="grow shrink">
<h1 class="flex-grow"> <h1>
<span class="smallcaps">Ultrawidify</span>: <small>Quick settings</small> <span class="smallcaps">Ultrawidify</span>: <small>Quick settings</small>
</h1> </h1>
<button
class="settings-header-button"
style="align-self: stretch"
@click="showInPlayerUi()"
>
Show settings window
</button>
</div> </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"> <div v-if="BrowserDetect?.processEnvChannel !== 'stable'" class="absolute channel-info version-info">
Build channel: {{BrowserDetect?.processEnvChannel}} <br/>
<label>Version:</label> <br/> <label>Version:</label> <br/>
{{ settings.getExtensionVersion() }} (non-stable) {{ settings.getExtensionVersion() }}
</div> </div>
<div v-else class="version-info"> <div v-else class="version-info">
<label>Version:</label> <br/> <label>Version:</label> <br/>
{{ settings.getExtensionVersion() }} {{ settings.getExtensionVersion() }}
</div> </div>
</div> </div>
</div>
<!-- CONTAINER ROOT --> <!-- CONTAINER ROOT -->
@ -60,10 +41,7 @@
v-for="tab of tabs" v-for="tab of tabs"
:key="tab.id" :key="tab.id"
class="tab flex flex-row" class="tab flex flex-row"
:class="{ :class="{'active': tab.id === selectedTab}"
'active': tab.id === selectedTab,
'highlight-tab': tab.highlight,
}"
@click="selectTab(tab.id)" @click="selectTab(tab.id)"
> >
<div class="icon-container"> <div class="icon-container">
@ -79,28 +57,35 @@
</div> </div>
<!-- CONTENT --> <!-- 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"> <template v-if="settings && siteSettings">
<InPlayerUIAdvertisement
v-if="selectedTab === 'playerUiCtl'"
:eventBus="eventBus"
/>
<PopupVideoSettings <PopupVideoSettings
v-if="selectedTab === 'videoSettings'" v-if="selectedTab === 'videoSettings'"
:settings="settings" :settings="settings"
:eventBus="eventBus" :eventBus="eventBus"
:siteSettings="siteSettings" :siteSettings="siteSettings"
:hosts="activeHosts"
></PopupVideoSettings> ></PopupVideoSettings>
<!-- <PlayerDetectionPanel
v-if="selectedTab === 'playerDetection'"
:settings="settings"
:eventBus="eventBus"
:siteSettings="siteSettings"
:site="site.host"
>
</PlayerDetectionPanel> -->
<BaseExtensionSettings <BaseExtensionSettings
v-if="selectedTab === 'extensionSettings'" v-if="selectedTab === 'extensionSettings'"
:settings="settings" :settings="settings"
:eventBus="eventBus" :eventBus="eventBus"
:siteSettings="siteSettings" :siteSettings="siteSettings"
:site="site.host" :site="site.host"
:hosts="activeHosts"
> >
</BaseExtensionSettings> </BaseExtensionSettings>
<ChangelogPanel
v-if="selectedTab === 'changelog'"
:settings="settings"
></ChangelogPanel>
<AboutPanel <AboutPanel
v-if="selectedTab === 'about'" v-if="selectedTab === 'about'"
> >
@ -117,18 +102,17 @@
<script> <script>
import BaseExtensionSettings from './src/PlayerUiPanels/BaseExtensionSettings.vue' import BaseExtensionSettings from './src/PlayerUiPanels/BaseExtensionSettings.vue'
import PlayerDetectionPanel from './src/PlayerUiPanels/PlayerDetectionPanel.vue' import PlayerDetectionPanel from './src/PlayerUiPanels/PlayerDetectionPanel.vue'
import ChangelogPanel from './src/PlayerUiPanels/ChangelogPanel.vue'
import PopupVideoSettings from './src/popup/panels/PopupVideoSettings.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 AboutPanel from '@csui/src/popup/panels/AboutPanel.vue'
import Debug from '../ext/conf/Debug'; import Debug from '../ext/conf/Debug';
import BrowserDetect from '../ext/conf/BrowserDetect'; import BrowserDetect from '../ext/conf/BrowserDetect';
import Comms from '../ext/lib/comms/Comms';
import CommsClient, {CommsOrigin} from '../ext/lib/comms/CommsClient'; import CommsClient, {CommsOrigin} from '../ext/lib/comms/CommsClient';
import Settings from '../ext/lib/Settings'; import Settings from '../ext/lib/Settings';
import Logger from '../ext/lib/Logger';
import EventBus from '../ext/lib/EventBus'; import EventBus from '../ext/lib/EventBus';
import {ChromeShittinessMitigations as CSM} from '../common/js/ChromeShittinessMitigations'; 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 { export default {
components: { components: {
@ -137,8 +121,7 @@ export default {
PopupVideoSettings, PopupVideoSettings,
PlayerDetectionPanel, PlayerDetectionPanel,
BaseExtensionSettings, BaseExtensionSettings,
SupportLevelIndicator, InPlayerUIAdvertisement,
ChangelogPanel,
AboutPanel AboutPanel
}, },
data () { data () {
@ -149,37 +132,27 @@ export default {
settingsInitialized: false, settingsInitialized: false,
narrowPopup: null, narrowPopup: null,
sideMenuVisible: null, sideMenuVisible: null,
logAggregator: undefined,
logger: undefined, logger: undefined,
site: undefined, site: undefined,
siteSettings: undefined, siteSettings: undefined,
selectedTab: 'videoSettings', selectedTab: 'playerUiCtl',
tabs: [ tabs: [
// see this for icons: https://pictogrammers.com/library/mdi/ // 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: 'videoSettings', label: 'Video settings', icon: 'crop'},
// {id: 'playerDetection', label: 'Player detection', icon: 'television-play'}, // {id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
{id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' }, {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'}, {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() { async created() {
try { this.logger = new Logger();
this.logAggregator = new LogAggregator('🔵ext-popup🔵'); await this.logger.init({
this.logger = new ComponentLogger(this.logAggregator, 'Popup'); allowLogging: true,
});
this.settings = new Settings({afterSettingsSaved: () => this.updateConfig(), logAggregator: this.logAggregator}); this.settings = new Settings({afterSettingsSaved: () => this.updateConfig(), logger: this.logger});
await this.settings.init(); await this.settings.init();
this.settingsInitialized = true; this.settingsInitialized = true;
@ -193,8 +166,6 @@ export default {
{ {
source: this, source: this,
function: (config, context) => { function: (config, context) => {
console.log('set-current-site | this.site:', this.site, 'config.site:', config.site);
if (this.site) { if (this.site) {
if (!this.site.host) { if (!this.site.host) {
// dunno why this fix is needed, but sometimes it is // dunno why this fix is needed, but sometimes it is
@ -204,6 +175,7 @@ export default {
this.site = config.site; this.site = config.site;
// this.selectedSite = this.selectedSite || config.site.host; // this.selectedSite = this.selectedSite || config.site.host;
this.siteSettings = this.settings.getSiteSettings(this.site.host); this.siteSettings = this.settings.getSiteSettings(this.site.host);
this.eventBus.setupPopupTunnelWorkaround({ this.eventBus.setupPopupTunnelWorkaround({
origin: CommsOrigin.Popup, origin: CommsOrigin.Popup,
comms: { comms: {
@ -211,20 +183,11 @@ export default {
} }
}); });
this.loadHostnames(); this.loadFrames(this.site);
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.comms = new CommsClient('popup-port', this.logger, this.eventBus);
this.eventBus.setComms(this.comms); this.eventBus.setComms(this.comms);
@ -253,9 +216,6 @@ export default {
this.requestSite(); this.requestSite();
await this.sleep(5000); await this.sleep(5000);
} }
} catch (e) {
console.error('[Popup.vue::created()] An error happened:', e)
}
}, },
async updated() { async updated() {
const body = document.getElementsByTagName('body')[0]; const body = document.getElementsByTagName('body')[0];
@ -273,9 +233,6 @@ export default {
}, },
methods: { methods: {
showInPlayerUi() {
this.eventBus.send('uw-set-ui-state', {globalUiVisible: true}, {comms: {forwardTo: 'active'}});
},
async sleep(t) { async sleep(t) {
return new Promise( (resolve,reject) => { return new Promise( (resolve,reject) => {
setTimeout(() => resolve(), t); setTimeout(() => resolve(), t);
@ -290,7 +247,6 @@ export default {
// CSM.port.postMessage({command: 'get-current-site'}); // CSM.port.postMessage({command: 'get-current-site'});
this.eventBus.send( this.eventBus.send(
'get-current-site', 'get-current-site',
{},
{ {
comms: {forwardTo: 'active'} comms: {forwardTo: 'active'}
} }
@ -305,36 +261,65 @@ export default {
selectTab(tab) { selectTab(tab) {
this.selectedTab = 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) { isDefaultFrame(frameId) {
return frameId === '__playing' || frameId === '__all'; return frameId === '__playing' || frameId === '__all';
}, },
loadHostnames() {
this.activeHosts = this.site.hostnames;
},
loadFrames() { loadFrames() {
this.activeFrames = [{ this.activeSites = [{
host: this.site.host, host: this.site.host,
isIFrame: false, // not used tho. Maybe one day isIFrame: false, // not used tho. Maybe one day
}]; }];
this.selectedSite = this.selectedSite || this.site.host;
for (const frame in this.site.frames) { // for (const frame in videoTab.frames) {
if (!this.activeFrames.find(x => x.host === this.site.frames[frame].host)) { // this.activeFrames.push({
this.activeFrames.push({ // id: `${this.site.id}-${frame}`,
id: `${this.site.id}-${frame}`, // label: videoTab.frames[frame].host,
label: this.site.frames[frame].host, // ...this.frameStore[frame],
host: this.site.frames[frame].host, // })
...this.site.frames[frame],
...this.settings.active.sites[this.site.frames[frame].host] // // 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() { getRandomColor() {
return `rgb(${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)})`; 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 { .header {
background-color: rgb(90, 28, 13); background-color: rgb(90, 28, 13);
background-color: rgb(0,0,0);
color: #fff; color: #fff;
padding: 8px; padding: 8px;
// display: flex; display: flex;
// flex-direction: row; flex-direction: row;
// justify-content: space-between; justify-content: space-between;
border-bottom: 1px dotted #fa6;
h1 { 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 { .site-support-info {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -559,15 +526,6 @@ export default {
flex-shrink: 1; flex-shrink: 1;
padding: 0 !important; padding: 0 !important;
} }
&.highlight-tab {
opacity: 0.9;
color: #eee;
// .label {
// color: rgb(239, 192, 152);
// }
}
} }
} }
@ -587,10 +545,4 @@ pre {
h1 { h1 {
margin: 0; padding: 0; font-weight: 400; font-size:24px; margin: 0; padding: 0; font-weight: 400; font-size:24px;
} }
.window-content {
height: 100%;
width: 100%;
overflow: auto;
}
</style> </style>

View File

@ -3,8 +3,6 @@ import GlobalFrame from './GlobalFrame';
import mdiVue from 'mdi-vue/v3'; import mdiVue from 'mdi-vue/v3';
import * as mdijs from '@mdi/js'; import * as mdijs from '@mdi/js';
import './src/res-common/common.scss';
// NOTE — this is in-player interface for ultrawidify // NOTE — this is in-player interface for ultrawidify
// it is injected into the page in UI.init() // it is injected into the page in UI.init()

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" style="position: relative; height: 600px"> <html lang="en" style="position: relative">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Title</title> <title>Title</title>
@ -10,9 +10,9 @@
<% } %> <% } %>
</head> </head>
<body <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> </div>
<script src="csui-popup.js"></script> <script src="csui-popup.js"></script>

View File

@ -3,8 +3,6 @@ import PlayerOverlay from './PlayerOverlay';
import mdiVue from 'mdi-vue/v3'; import mdiVue from 'mdi-vue/v3';
import * as mdijs from '@mdi/js'; import * as mdijs from '@mdi/js';
import './src/res-common/common.scss';
// NOTE — this is in-player interface for ultrawidify // NOTE — this is in-player interface for ultrawidify
// it is injected into the page in UI.init() // it is injected into the page in UI.init()

View File

@ -23,7 +23,7 @@
</template> </template>
<script> <script>
import Logger from '../../../ext/lib/Logger';
export default { export default {
components: { components: {

View File

@ -5,10 +5,6 @@
// @import "form.scss"; // @import "form.scss";
* {
box-sizing: border-box;
}
body { body {
background-color: $background-primary; background-color: $background-primary;
color: $text-normal; color: $text-normal;
@ -22,21 +18,8 @@ body {
padding: 0px; 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 */ /* STANDARD WIDTHS AND HEIGHTS */
.w100, .w-full { .w100 {
width: 100%; width: 100%;
} }
@ -57,6 +40,8 @@ body {
padding-right: 1em; padding-right: 1em;
} }
/* overflow stuff */ /* overflow stuff */
.overflow-y-auto { .overflow-y-auto {
overflow: auto; overflow: auto;
@ -67,19 +52,6 @@ body {
scrollbar-color: rgba($primary-color, 0.5) $background-primary; 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 */ /* scrollbars for chrome/webkit */
.overflow-y-auto::-webkit-scrollbar { .overflow-y-auto::-webkit-scrollbar {
@ -131,6 +103,7 @@ body {
} }
.label { .label {
padding-top: 1.5rem;
font-size: 1.5rem; font-size: 1.5rem;
font-variant: small-caps; font-variant: small-caps;
font-weight: 600; font-weight: 600;
@ -252,7 +225,6 @@ small {
background-color: #410 !important; background-color: #410 !important;
} }
button,
.button { .button {
/*display: inline-block;*/ /*display: inline-block;*/
// padding-top: 8px; // padding-top: 8px;
@ -266,27 +238,6 @@ button,
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
user-select: none;; 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; padding-right: 10px;
} }
.info-button {
color: $info-color;
border: 1px solid $info-color;
}
.info { .info {
color: $info-color; color: $info-color;
padding-left: 35px; padding-left: 35px;

View File

@ -14,25 +14,19 @@
flex: 1 1 auto; flex: 1 1 auto;
} }
.flex-grow, .grow { .flex-grow {
flex-grow: 1; flex-grow: 1;
} }
.flex-nogrow, .grow-0 { .flex-nogrow {
flex-grow: 0; flex-grow: 0;
} }
.flex-shrink, .shrink { .flex-shrink {
flex-shrink: 1; flex-shrink: 1;
} }
.flex-noshrink, .shrink-0 { .flex-noshrink {
flex-shrink: 0; flex-shrink: 0;
} }
.items-center {
align-items: center;
}
.items-baseline {
align-items: baseline;
}
.flex-wrap { .flex-wrap {
flex-wrap: wrap; flex-wrap: wrap;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,6 +1,6 @@
<template> <template>
<div <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="popup-window-header">
<div class="header-title"> <div class="header-title">
@ -30,9 +30,6 @@
<div <div
v-for="tab of tabs" v-for="tab of tabs"
:key="tab.id" :key="tab.id"
>
<div
v-if="!tab.hidden"
class="tab" class="tab"
:class="{ :class="{
'active': tab.id === selectedTab, 'active': tab.id === selectedTab,
@ -52,7 +49,6 @@
</div> </div>
</div> </div>
</div> </div>
</div>
<div class="content flex flex-col"> <div class="content flex flex-col">
<!-- autodetection warning --> <!-- autodetection warning -->
@ -90,7 +86,6 @@
<PlayerUiSettings <PlayerUiSettings
v-if="selectedTab === 'playerUiSettings'" v-if="selectedTab === 'playerUiSettings'"
:settings="settings" :settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus" :eventBus="eventBus"
> >
</PlayerUiSettings> </PlayerUiSettings>
@ -99,7 +94,6 @@
:settings="settings" :settings="settings"
:siteSettings="siteSettings" :siteSettings="siteSettings"
:site="site" :site="site"
:enableSettingsEditor="true"
></BaseExtensionSettings> ></BaseExtensionSettings>
<AutodetectionSettingsPanel <AutodetectionSettingsPanel
v-if="selectedTab === 'autodetectionSettings'" v-if="selectedTab === 'autodetectionSettings'"
@ -144,7 +138,8 @@ import ChangelogPanel from './PlayerUiPanels/ChangelogPanel.vue'
import AboutPanel from '@csui/src/PlayerUiPanels/AboutPanel.vue' import AboutPanel from '@csui/src/PlayerUiPanels/AboutPanel.vue'
import PlayerUiSettings from './PlayerUiPanels/PlayerUiSettings.vue' import PlayerUiSettings from './PlayerUiPanels/PlayerUiSettings.vue'
import ResetBackupPanel from './PlayerUiPanels/ResetBackupPanel.vue' import ResetBackupPanel from './PlayerUiPanels/ResetBackupPanel.vue'
import SupportLevelIndicator from '@csui/src/components/SupportLevelIndicator.vue'
import SupportLevelIndicator from './components/SupportLevelIndicator.vue'
export default { export default {
components: { components: {
@ -173,9 +168,10 @@ export default {
{id: 'playerDetection', label: 'Player detection', icon: 'television-play'}, {id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
{id: 'autodetectionSettings', label: 'Autodetection options', icon: 'auto-fix'}, {id: 'autodetectionSettings', label: 'Autodetection options', icon: 'auto-fix'},
// {id: 'advancedOptions', label: 'Advanced options', icon: 'cogs' }, // {id: 'advancedOptions', label: 'Advanced options', icon: 'cogs' },
// {id: 'debugging', label: 'Debugging', icon: 'bug-outline' }
{id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' }, {id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
{id: 'about', label: 'About', icon: 'information-outline'}, {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', selectedTab: 'extensionSettings',
BrowserDetect: BrowserDetect, BrowserDetect: BrowserDetect,
@ -200,13 +196,11 @@ export default {
} }
}, },
created() { created() {
this.settings.listenAfterChange(this.setDebugTabVisibility);
if (this.defaultTab) { if (this.defaultTab) {
this.selectedTab = this.defaultTab; this.selectedTab = this.defaultTab;
} }
this.siteSettings = this.settings.getSiteSettings(this.site); 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( this.eventBus.subscribe(
'uw-show-ui', 'uw-show-ui',
@ -219,10 +213,8 @@ export default {
}, },
} }
) )
this.setDebugTabVisibility();
}, },
destroyed() { destroyed() {
this.settings.removeListenerAfterChange(this.setDebugTabVisibility);
this.eventBus.unsubscribeAll(this); this.eventBus.unsubscribeAll(this);
}, },
methods: { methods: {
@ -238,12 +230,6 @@ export default {
setPreventClose(bool) { setPreventClose(bool) {
this.preventClose = bool; this.preventClose = bool;
this.$emit('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%; // height: 100%;
// } // }
.tab-row { .tab-row {
width: 22rem; width: 22rem;
flex-grow: 0; flex-grow: 0;
@ -385,7 +369,7 @@ export default {
padding: 2rem; padding: 2rem;
font-size: 1.5rem; font-size: 1.5rem;
height: 6rem; height: 4rem;
border-bottom: 1px solid rgba(128, 128, 128, 0.5); border-bottom: 1px solid rgba(128, 128, 128, 0.5);
border-top: 1px solid rgba(128, 128, 128, 0.5); border-top: 1px solid rgba(128, 128, 128, 0.5);

View File

@ -10,7 +10,7 @@
<div class="w-[1/2]" style="width: 50%"> <div class="w-[1/2]" style="width: 50%">
<h2>Report a problem</h2> <h2>Report a problem</h2>
<p> <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> </p>
<ul> <ul>
<li> <a target="_blank" href="https://github.com/tamius-han/ultrawidify/issues"><b>Github (preferred)</b></a><br/></li> <li> <a target="_blank" href="https://github.com/tamius-han/ultrawidify/issues"><b>Github (preferred)</b></a><br/></li>

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="alignment-box" :class="{large: large}"> <div class="alignment-box">
<div <div
class="col top left" class="col top left"
@click="align(VideoAlignment.Left, VideoAlignment.Top)" @click="align(VideoAlignment.Left, VideoAlignment.Top)"
@ -60,10 +60,10 @@
<script> <script>
import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum'; import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum';
export default { export default {
props: [ props: [
'eventBus', 'eventBus'
'large',
], ],
data() { data() {
return { return {
@ -89,14 +89,6 @@ export default {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, 1fr);
gap: 0.5rem; gap: 0.5rem;
&.large {
max-width: 15rem;
.col {
width: 4rem;
height: 4rem;
}
}
.col { .col {
display: flex; display: flex;

View File

@ -234,7 +234,6 @@
/> />
<input <input
v-model="settings.active.arDetect.timers.playing" v-model="settings.active.arDetect.timers.playing"
@change="setArCheckFrequency($event.target.value)"
class="input" class="input"
type="text" type="text"
> >
@ -245,10 +244,10 @@
<div class="field"> <div class="field">
<div class="label">Frame extraction canvas type:</div> <div class="label">Frame extraction canvas type:</div>
<div class="select"> <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="auto">Automatic</option>
<option value="webgl">WebGL only</option> <option value="webgl">WebGL only</option>
<option value="legacy">Legacy / fallback</option> <option value="fallback">Legacy / fallback</option>
</select> </select>
</div> </div>
</div> </div>
@ -265,34 +264,6 @@
</div> </div>
</div> </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> </div>
</div> </div>
@ -309,15 +280,14 @@ import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
import StretchType from '../../../common/enums/StretchType.enum'; import StretchType from '../../../common/enums/StretchType.enum';
import CropModePersistence from '../../../common/enums/CropModePersistence.enum'; import CropModePersistence from '../../../common/enums/CropModePersistence.enum';
import AlignmentOptionsControlComponent from './AlignmentOptionsControlComponent.vue'; import AlignmentOptionsControlComponent from './AlignmentOptionsControlComponent.vue';
import JsonEditor from '@csui/src/components/JsonEditor';
export default { export default {
components: { data() {
ShortcutButton, return {
EditShortcutButton, exec: null,
Button, performanceData: {},
AlignmentOptionsControlComponent, graphRefreshInterval: undefined,
JsonEditor }
}, },
mixins: [ mixins: [
], ],
@ -327,16 +297,6 @@ export default {
'eventBus', 'eventBus',
'site' 'site'
], ],
data() {
return {
exec: null,
performanceData: {},
graphRefreshInterval: undefined,
settingsJson: {},
}
},
computed: {
},
created() { created() {
this.eventBus.subscribe( this.eventBus.subscribe(
'uw-config-broadcast', 'uw-config-broadcast',
@ -346,15 +306,24 @@ export default {
} }
); );
}, },
destroyed() {
this.eventBus.unsubscribeAll(this);
},
mounted() { mounted() {
this.eventBus.sendToTunnel('get-aard-timing'); this.eventBus.sendToTunnel('get-aard-timing');
this.graphRefreshInterval = setInterval(() => this.eventBus.sendToTunnel('get-aard-timing'), 500); this.graphRefreshInterval = setInterval(() => this.eventBus.sendToTunnel('get-aard-timing'), 500);
this.resetSettingsEditor();
}, },
destroyed() { destroyed() {
this.eventBus.unsubscribeAll(this);
clearInterval(this.graphRefreshInterval); clearInterval(this.graphRefreshInterval);
}, },
components: {
ShortcutButton,
EditShortcutButton,
Button,
AlignmentOptionsControlComponent
},
computed: {
},
methods: { methods: {
async openOptionsPage() { async openOptionsPage() {
BrowserDetect.runtime.openOptionsPage(); BrowserDetect.runtime.openOptionsPage();
@ -371,15 +340,7 @@ export default {
this.$nextTick( () => this.$forceUpdate() ); 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> </script>

View File

@ -1,6 +1,5 @@
<template> <template>
<div class="flex flex-row w-full h-full"> <div class="flex flex-col w-100">
<div class="flex flex-col w-full">
<!-- TAB ROW --> <!-- TAB ROW -->
<div class="flex flex-row"> <div class="flex flex-row">
@ -9,27 +8,29 @@
:class="{'active': tab === 'siteSettings'}" :class="{'active': tab === 'siteSettings'}"
@click="setTab('siteSettings')" @click="setTab('siteSettings')"
> >
Current site<br/> Settings for current site<br/>
<small>{{ site }}</small> <small>{{ site }}</small>
</div> </div>
<div <div
v-if="hosts"
class="tab" class="tab"
:class="{'active': tab === 'embeddedSites'}" :class="{'active': tab === 'extensionSettings'}"
@click="setTab(tab = 'embeddedSites')" @click="setTab(tab = 'extensionSettings')"
> >
Embedded content ({{hosts?.length}} {{hosts?.length === 1 ? 'site' : 'sites'}}) Default settings for extension
</div> </div>
<div <div
class="tab" class="tab"
:class="{'active': tab === 'otherSites'}" :class="{'active': tab === 'otherSites'}"
@click="setTab(tab = 'otherSites')" @click="setTab(tab = 'otherSites')"
> >
Defaults & other sites Settings for other sites
</div> </div>
</div> </div>
<template v-if="tab === 'siteSettings' && siteSettings"> <template v-if="tab === 'siteSettings' && siteSettings">
<!-- <div class="button">
Reset settings for site
</div> -->
<SiteExtensionSettings <SiteExtensionSettings
v-if="settings" v-if="settings"
:settings="settings" :settings="settings"
@ -38,12 +39,13 @@
></SiteExtensionSettings> ></SiteExtensionSettings>
</template> </template>
<template v-if="hosts && tab === 'embeddedSites' && globalSettings"> <template v-if="tab === 'extensionSettings' && globalSettings">
<FrameSiteSettings <SiteExtensionSettings
v-if="settings" v-if="settings"
:hosts="hosts"
:settings="settings" :settings="settings"
></FrameSiteSettings> :siteSettings="globalSettings"
:isDefaultConfiguration="true"
></SiteExtensionSettings>
</template> </template>
<template v-if="tab === 'otherSites'"> <template v-if="tab === 'otherSites'">
@ -54,141 +56,44 @@
</OtherSiteSettings> </OtherSiteSettings>
</template> </template>
<!-- Reset options --> <!-- Reset options -->
<div class="flex flex-col" style="margin-top: 2rem"> <div class="flex flex-col" style="margin-top: 2rem">
<h2>Reset and backup</h2> <h2>Reset and backup</h2>
<p><small>Settings import must be done from in-player UI.</small></p> <p>
<div class="flex flex-row w-full"> Pressing the button will reset settings to default without asking.
<UploadJsonFileButton </p>
class="flex-grow" <button
@importedJson="handleImportedSettings" class="danger"
@error="handleSettingsImportError" @click="resetSettings"
>
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 Reset settings
</ConfirmButton>
<div v-if="enableSettingsEditor" class="field">
<div class="label">Show developer options</div>
<input
type="checkbox"
v-model="settings.active.ui.devMode"
@change="settings.saveWithoutReload"
>
</div>
</div>
</div>
<div v-if="enableSettingsEditor && settings.active.ui.devMode" class="h-full grow">
<h2>Settings editor</h2>
<div class="flex flex-row w-full">
<div class="flex flex-row items-center">
<div>Enable save button:</div>
<input v-model="allowSettingsEditing" type="checkbox">
</div>
<div class="grow">
</div>
<div>
<div v-if="editorSaveFinished">Settings saved ...</div>
<button v-else
class="danger"
:class="{'disabled': !allowSettingsEditing}"
:disabled="!allowSettingsEditing"
@click="() => saveSettingsChanges()"
>
Save
</button> </button>
</div> </div>
<button @click="resetSettingsEditor">
Cancel
</button>
</div>
<div>
<JsonEditor
v-model="settingsJson"
>
</JsonEditor>
</div>
<h2>Settings snapshots</h2>
<div class="flex flex-col">
<div v-for="(snapshot, index) of settingsSnapshots" :key="snapshot.createdAt">
<small>{{new Date(snapshot.createdAt).toISOString()}}</small>
<div class="flex flex-row">
<div class="grow">
{{snapshot.name}}
</div>
<div v-if="settings.isAutomatic">(auto)</div>
<div v-if="settings.isAutomatic && settings.isProtected">(protected)</div>
<div v-if="settings.default">(default)</div>
</div>
<div>
<button @click="() => markDefaultSnapshot(index)"><template v-if="settings.isDefault">Revoke default</template><template v-else>Make default</template></button>
<button v-if="settings.isAutomatic" @click="() => toggleSnapshotProtection(index)">Toggle protection</button>
<button @click="() => deleteSnapshot(index)">Delete snapshot</button>
</div>
</div>
</div>
</div>
</div> </div>
</template> </template>
<script> <script>
import SiteExtensionSettings from './PanelComponents/ExtensionSettings/SiteExtensionSettings.vue'; import SiteExtensionSettings from './PanelComponents/ExtensionSettings/SiteExtensionSettings.vue';
import FrameSiteSettings from './PanelComponents/ExtensionSettings/FrameSiteSettings.vue';
import OtherSiteSettings from './PanelComponents/ExtensionSettings/OtherSiteSettings.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 { export default {
data() {
components: { return {
SiteExtensionSettings, tab: 'siteSettings'
OtherSiteSettings, }
Popup,
ConfirmButton,
UploadJsonFileButton,
JsonEditor,
FrameSiteSettings,
}, },
mixins: [], mixins: [
],
props: [ props: [
'settings', 'settings',
'site', 'site',
'enableSettingsEditor',
'hosts',
], ],
data() { components: {
return { SiteExtensionSettings,
tab: 'siteSettings', OtherSiteSettings
importSettingDialogConfig: {visible: false},
allowSettingsEditing: false,
editorSaveFinished: false,
settingsJson: {},
settingsSnapshots: []
}
}, },
computed: { computed: {
globalSettings() { globalSettings() {
@ -200,113 +105,17 @@ export default {
} }
return null; return null;
}, },
}, resetSettings() {
mounted() { this.settings.active = JSON.parse(JSON.stringify(this.settings.default));
this.resetSettingsEditor(); this.settings.saveWithoutReload();
this.loadSettingsSnapshots(); }
}, },
methods: { methods: {
setTab(tab) { setTab(tab) {
this.tab = 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> </script>

View File

@ -1,36 +1,98 @@
<template> <template>
<div class="flex flex-col w-full h-full gap-2"> <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="flex flex-row gap-2 bg-black">
<div class="min-w-[400px] max-w-[520px] grow shrink"> <div class="w-[1/2]" style="width: 50%">
<h1>What's new</h1> <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> <ul>
<li>Automatic aspect ratio detection: do not apply negative aspect ratios</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>Keyboard zoom now works</li> <li>In-player UI now indicates whether a site is having a problem with automatic aspect ratio detection.</li>
<li><code>www.youtube-nocookie.com</code> has been added to the "officially supported" list</li> <li>UI improvements</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>
</ul> </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>
<div style="width: 1rem; height: 0px;"></div> <div class="w-[1/2]" style="width: 50%; padding-left: 1rem; padding-top: 5rem;">
<div class="min-w-[400px] max-w-[520px] grow shrink">
<h2>Report a problem</h2>
<p>
Please report <strike>undocumented features</strike> bugs using one of the following options (in order of preference):
</p>
<ul>
<li> <a target="_blank" href="https://github.com/tamius-han/ultrawidify/issues"><b>Github (preferred)</b></a><br/></li>
<li>Email: <a target="_blank" :href="mailtoLink">tamius.han@gmail.com</a></li>
</ul>
<p>
When reporting bugs, please include extension version, whether you installed the extension from, and description of your problem.
</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<h2>Thank you monies</h2> <h2>Thank you monies</h2>
<p> <p>
If you think I deserve money for the work I did up to this point, you can bankroll my caffeine addiction. 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"> <p class="text-center">
<a class="donate" href="https://www.paypal.com/paypalme/tamius" target="_blank">Donate on Paypal</a> <a class="donate" href="https://www.paypal.com/paypalme/tamius" target="_blank">Donate on Paypal</a>
</p> </p>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import BrowserDetect from '@src/ext/conf/BrowserDetect';
export default({ export default({
props: [ props: [
'settings' '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() { mounted() {
this.settings.active.whatsNewChecked = true; this.settings.active.whatsNewChecked = true;
this.settings.saveWithoutReload(); this.settings.saveWithoutReload();
@ -74,10 +125,6 @@ export default({
flex-direction: row; flex-direction: row;
} }
.grow {
flex-grow: 1;
}
p, li { p, li {
font-size: 1rem; font-size: 1rem;
} }

View File

@ -6,12 +6,20 @@
<div>Logger configuration:</div> <div>Logger configuration:</div>
<JsonEditor <template v-if="loggerPanel.pasteConfMode">
v-model="lastSettings"
></JsonEditor> </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="flex flex-row flex-end">
<div class="button" @click="getLoggerSettings()"> <div class="button" @click="restoreLoggerSettings()">
Revert Revert
</div> </div>
<div class="button button-primary" @click="saveLoggerSettings()"> <div class="button button-primary" @click="saveLoggerSettings()">
@ -27,12 +35,12 @@
</template> </template>
<script> <script>
import { LogAggregator, BLANK_LOGGER_CONFIG } from '@src/ext/lib/logging/LogAggregator'; import JsonObject from '../components/JsonEditor/JsonObject.vue'
import JsonEditor from '@csui/src/components/JsonEditor'; import Logger, { baseLoggingOptions } from '../../../ext/lib/Logger';
export default { export default {
components: { components: {
JsonEditor JsonObject
}, },
data() { data() {
return { return {
@ -79,23 +87,38 @@ export default {
...this.loggerPanelRotation[Math.floor(Math.random() * this.loggerPanelRotation.length)], ...this.loggerPanelRotation[Math.floor(Math.random() * this.loggerPanelRotation.length)],
pasteConfMode: false, pasteConfMode: false,
}; };
this.getLoggerSettings(); this.loadDefaultConfig();
}, },
methods: { methods: {
loadDefaultConfig() { 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() { 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() { updateSettingsUi(val) {
console.log('Saving logger settings', this.lastSettings); try {
await LogAggregator.saveConfig({...this.lastSettings}); this.parsedSettings = JSON.stringify(val, null, 2);
console.log('[ok] logger settings saved'); 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(){ async startLogging(){
this.logStringified = undefined; this.logStringified = undefined;
await LogAggregator.saveConfig({...this.lastSettings}); await Logger.saveConfig({...this.lastSettings, allowLogging: true});
window.location.reload(); window.location.reload();
}, },
} }

View 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>

View File

@ -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>

View File

@ -1,51 +1,16 @@
<template> <template>
<div class=""> <div class="">
<template v-if="!selectedSite"> <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>&nbsp;<br/>
Autodetection: <span :style="getSiteEnabledColor('@global', 'enableAard')"><small>{{ getSiteEnabledModes('@global', 'enableAard') }}</small></span>;&nbsp;
Keyboard shortcuts: <span :style="getSiteEnabledColor('@global', 'enableKeyboard')"><small>{{ getSiteEnabledModes('@global', 'enableKeyboard') }}</small></span>;
In-player UI: <span :style="getSiteEnabledColor('@global', 'enableUI')"><small>{{ getSiteEnabledModes('@global', 'enableUI') }}</small></span>;
</small>
</div>
</div>
<div style="margin-top: 1rem; margin-bottom: 1rem;"> <div 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. <b>NOTE:</b> Sites not on this list use default extension settings.
</div> </div>
</div> <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="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 class="flex flex-row"> <div class="flex flex-row">
<div class="flex-grow pointer"> <div class="flex-grow pointer">
<b>{{ site.key }}</b> <b>{{ site.key }}</b>
<span :style="getSiteTypeColor(site.type)"> <span :style="getSiteTypeColor(site.type)">
(config: {{site.type ?? 'unknown'}}) (config: {{site.type ?? 'unknown'}})
</span> </span></div>
</div>
<div>Edit</div> <div>Edit</div>
</div> </div>
<div class="flex flex-row"> <div class="flex flex-row">
@ -53,7 +18,6 @@
Enabled: <span :style="getSiteEnabledColor(site.key, 'enable')"><small>{{ getSiteEnabledModes(site.key, 'enable') }}</small></span>;&nbsp; Enabled: <span :style="getSiteEnabledColor(site.key, 'enable')"><small>{{ getSiteEnabledModes(site.key, 'enable') }}</small></span>;&nbsp;
Aard <span :style="getSiteEnabledColor(site.key, 'enableAard')"><small>{{ getSiteEnabledModes(site.key, 'enableAard') }}</small></span>;&nbsp; Aard <span :style="getSiteEnabledColor(site.key, 'enableAard')"><small>{{ getSiteEnabledModes(site.key, 'enableAard') }}</small></span>;&nbsp;
kbd: <span :style="getSiteEnabledColor(site.key, 'enableKeyboard')"><small>{{ getSiteEnabledModes(site.key, 'enableKeyboard') }}</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> </small>
</div> </div>
</div> </div>
@ -64,7 +28,7 @@
</div> </div>
<div> <div>
Editing {{ selectedSite === '@global' ? 'default settings' : selectedSite }} Editing {{ selectedSite }}
</div> </div>
</div> </div>
<div> <div>
@ -72,7 +36,7 @@
v-if="selectedSiteSettings" v-if="selectedSiteSettings"
:settings="settings" :settings="settings"
:siteSettings="selectedSiteSettings" :siteSettings="selectedSiteSettings"
:isDefaultConfiguration="selectedSite === '@global'" :isDefaultConfiguration="false"
></SiteExtensionSettings> ></SiteExtensionSettings>
</div> </div>
</template> </template>
@ -87,8 +51,6 @@ export default {
data() { data() {
return { return {
selectedSite: null, selectedSite: null,
siteFilter: '',
filteredSites: []
} }
}, },
props: [ props: [
@ -104,7 +66,7 @@ export default {
} else { } else {
const sites = []; const sites = [];
for (const siteKey in this.settings.active.sites) { for (const siteKey in this.settings.active.sites) {
if (!siteKey.startsWith('@') && (!this.siteFilter.trim() || siteKey.includes(this.siteFilter))) { if (!siteKey.startsWith('@')) {
sites.push({ sites.push({
key: siteKey, key: siteKey,
...this.settings.active.sites[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="../../../../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/panels.scss" scoped></style>
<style lang="scss" src="@csui/src/res-common/common.scss" scoped></style> <style lang="scss" src="@csui/src/res-common/common.scss" scoped></style>
<style lang="scss" scoped>
.hoverable {
border: 1px solid #333;
&:hover {
border: 1px solid #fa6;
color: rgb(255, 231, 212);
background-color: rgba(#fa6, 0.125);
}
}
</style>

View File

@ -3,12 +3,11 @@
<!-- Enable extension --> <!-- Enable extension -->
<div class="field"> <div class="field">
<div class="label"> <div class="label">
Enable <span class="color-emphasis">extension</span> Enable extension under the following conditions:
<span class="sub-label"><br/>under the following conditions:</span>
</div> </div>
<div class="select"> <div class="select">
<select <select
:value="simpleExtensionSettings.enable" v-model="simpleExtensionSettings.enable"
@click="setExtensionMode('enable', $event)" @click="setExtensionMode('enable', $event)"
> >
<option <option
@ -19,12 +18,12 @@
</option> </option>
<template v-if="isDefaultConfiguration"> <template v-if="isDefaultConfiguration">
<option value="disabled"> <option value="disabled">
Disabled by default Disabled (unless enabled for specific site)
</option> </option>
</template> </template>
<template v-else> <template v-else>
<option value="default"> <option value="default">
Use default ({{simpleDefaultSettings.enable}}) Use default ()
</option> </option>
<option value="disabled"> <option value="disabled">
Never Never
@ -43,17 +42,14 @@
</div> </div>
</div> </div>
<!-- The rest of the menu is disabled when extension is disabled -->
<div :class="{disabled: simpleEffectiveSettings.enable === 'disabled' && !isDefaultConfiguration}">
<!-- Enable AARD --> <!-- Enable AARD -->
<div class="field"> <div class="field">
<div class="label"> <div class="label">
Enable <span class="color-emphasis">automatic aspect ratio detection</span> Enable automatic aspect ratio detection under the following conditions:
<span class="sub-label"><br/>under the following conditions:</span>
</div> </div>
<div class="select"> <div class="select">
<select <select
:value="simpleExtensionSettings.enableAard" v-model="simpleExtensionSettings.enableAard"
@click="setExtensionMode('enableAard', $event)" @click="setExtensionMode('enableAard', $event)"
> >
<option <option
@ -64,12 +60,12 @@
</option> </option>
<template v-if="isDefaultConfiguration"> <template v-if="isDefaultConfiguration">
<option value="disabled"> <option value="disabled">
Disabled by default Disabled (unless enabled for specific site)
</option> </option>
</template> </template>
<template v-else> <template v-else>
<option value="default"> <option value="default">
Use default ({{simpleDefaultSettings.enableAard}}) Use default ()
</option> </option>
<option value="disabled"> <option value="disabled">
Never Never
@ -91,12 +87,11 @@
<!-- Enable keyboard --> <!-- Enable keyboard -->
<div class="field"> <div class="field">
<div class="label"> <div class="label">
Enable <span class="color-emphasis">keyboard shortcuts</span> Enable keyboard shortcuts under the following conditions
<span class="sub-label"><br/>under the following conditions:</span>
</div> </div>
<div class="select"> <div class="select">
<select <select
:value="simpleExtensionSettings.enableKeyboard" v-model="simpleExtensionSettings.enableKeyboard"
@click="setExtensionMode('enableKeyboard', $event)" @click="setExtensionMode('enableKeyboard', $event)"
> >
<option <option
@ -107,12 +102,12 @@
</option> </option>
<template v-if="isDefaultConfiguration"> <template v-if="isDefaultConfiguration">
<option value="disabled"> <option value="disabled">
Disabled by default Disabled (unless enabled for specific site)
</option> </option>
</template> </template>
<template v-else> <template v-else>
<option value="default"> <option value="default">
Use default ({{simpleDefaultSettings.enableKeyboard}}) Use default ()
</option> </option>
<option value="disabled"> <option value="disabled">
Never Never
@ -131,46 +126,12 @@
</div> </div>
</div> </div>
<!-- Enable UI -->
<div class="field">
<div class="label">
Enable <span class="color-emphasis">in-player UI</span>
<span class="sub-label"><br/>under the following conditions:</span>
</div>
<div class="select">
<select
:value="simpleExtensionSettings.enableUI"
@click="setExtensionMode('enableUI', $event)"
>
<template v-if="isDefaultConfiguration">
<option value="disabled">
Disabled by default
</option>
</template>
<template v-else>
<option value="default">
Use default ({{simpleDefaultSettings.enableUI}})
</option>
<option value="disabled">
Never
</option>
</template>
<option value="fs">
Fullscreen only
</option>
<option value="theater">
Always where possible
</option>
</select>
</div>
</div>
<!-- Default crop --> <!-- Default crop -->
<div class="field"> <div class="field">
<div class="label">Default crop:</div> <div class="label">Default crop:</div>
<div class="select"> <div class="select">
<select <select
:value="siteDefaultCrop" v-model="siteDefaultCrop"
@change="setOption('defaults.crop', $event)" @change="setOption('defaults.crop', $event)"
> >
<option <option
@ -188,8 +149,8 @@
</option> </option>
</select> </select>
</div> </div>
</div>
<div class="hint">This is how extension will crop video if/when autodetection is disabled. Pick 'Reset' option to keep aspect ratio as-is by default.</div> <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 --> <!-- Default stretch -->
<div class="field"> <div class="field">
@ -263,19 +224,17 @@
</div> </div>
</div> </div>
</div> </div>
</div>
</template> </template>
<script> <script>
import ExtensionMode from '@src/common/enums/ExtensionMode.enum'; import ExtensionMode from '../../../../../common/enums/ExtensionMode.enum';
import VideoAlignmentType from '@src/common/enums/VideoAlignmentType.enum'; import VideoAlignmentType from '../../../../../common/enums/VideoAlignmentType.enum';
import CropModePersistence from '@src/common/enums/CropModePersistence.enum'; import CropModePersistence from './../../../../../common/enums/CropModePersistence.enum';
export default { export default {
data() { data() {
return { return {
CropModePersistence: CropModePersistence, CropModePersistence: CropModePersistence,
ExtensionMode,
alignmentOptions: [ alignmentOptions: [
{label: 'Top left', arguments: {x: VideoAlignmentType.Left, y: VideoAlignmentType.Top}}, {label: 'Top left', arguments: {x: VideoAlignmentType.Left, y: VideoAlignmentType.Top}},
{label: 'Top center', arguments: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Top}}, {label: 'Top center', arguments: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Top}},
@ -306,25 +265,8 @@ export default {
enable: this.compileSimpleSettings('enable'), enable: this.compileSimpleSettings('enable'),
enableAard: this.compileSimpleSettings('enableAard'), enableAard: this.compileSimpleSettings('enableAard'),
enableKeyboard: this.compileSimpleSettings('enableKeyboard'), 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() { siteDefaultCrop() {
return this.siteSettings.raw?.defaults?.crop ? JSON.stringify(this.siteSettings.raw?.defaults?.crop) : JSON.stringify({useDefault: true}); return this.siteSettings.raw?.defaults?.crop ? JSON.stringify(this.siteSettings.raw?.defaults?.crop) : JSON.stringify({useDefault: true});
}, },
@ -352,71 +294,44 @@ export default {
return '??'; return '??';
} }
}, },
mounted() {
this.forceRefreshPage();
},
methods: { methods: {
/** /**
* Compiles our extension settings into more user-friendly options * Compiles our extension settings into more user-friendly options
*/ */
compileSimpleSettings(component, getFor = 'site') { compileSimpleSettings(component) {
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);
try { try {
if ( if (
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI') this.siteSettings?.data?.[component]?.normal === ExtensionMode.Disabled
&& settingsData?.[component]?.theater === ExtensionMode.Disabled && this.siteSettings?.data?.[component]?.theater === ExtensionMode.Disabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Disabled && this.siteSettings?.data?.[component]?.fullscreen === ExtensionMode.Disabled
) { ) {
return 'disabled'; return 'disabled';
} }
if ( if (
( settingsData?.[component]?.normal === ExtensionMode.Default || component === 'enableUI') this.siteSettings?.data?.[component]?.normal === ExtensionMode.Default
&& settingsData?.[component]?.theater === ExtensionMode.Default && this.siteSettings?.data?.[component]?.theater === ExtensionMode.Default
&& settingsData?.[component]?.fullscreen === 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'; return 'default';
} }
if ( if (
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI') this.siteSettings?.data?.[component]?.normal === ExtensionMode.Disabled
&& settingsData?.[component]?.theater === ExtensionMode.Disabled && this.siteSettings?.data?.[component]?.theater === ExtensionMode.Disabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled && this.siteSettings?.data?.[component]?.fullscreen === ExtensionMode.Enabled
) { ) {
return 'fs'; return 'fs';
} }
if ( if (
( settingsData?.[component]?.normal === ExtensionMode.Disabled || component === 'enableUI') this.siteSettings?.data?.[component]?.normal === ExtensionMode.Disabled
&& settingsData?.[component]?.theater === ExtensionMode.Enabled && this.siteSettings?.data?.[component]?.theater === ExtensionMode.Enabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled && this.siteSettings?.data?.[component]?.fullscreen === ExtensionMode.Enabled
) { ) {
return 'theater'; return 'theater';
} }
if ( if (
( settingsData?.[component]?.normal === ExtensionMode.Enabled || component === 'enableUI') this.siteSettings?.data?.[component]?.normal === ExtensionMode.Enabled
&& settingsData?.[component]?.theater === ExtensionMode.Enabled && this.siteSettings?.data?.[component]?.theater === ExtensionMode.Enabled
&& settingsData?.[component]?.fullscreen === ExtensionMode.Enabled && this.siteSettings?.data?.[component]?.fullscreen === ExtensionMode.Enabled
) { ) {
return '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) { getCommandValue(availableCommands, command) {
for (const cmd of availableCommands) { for (const cmd of availableCommands) {
if (JSON.stringify(cmd.arguments) === JSON.stringify(command)) { if (JSON.stringify(cmd.arguments) === JSON.stringify(command)) {
@ -519,43 +403,21 @@ export default {
await this.siteSettings.set(option, commandArguments, {reload: false}); await this.siteSettings.set(option, commandArguments, {reload: false});
// we also need to force re-compute all watchers, otherwise UI will lag behind // changing alignment options doesn't trigger re-compute, so we need to do it ourselves.
// actual state of settings until reload // note that this re-computes siteDefaultAlignment even when setting other options, but
this.forceRefreshPage(); // it's _too late AM_ and hit to performance probably isn't bad enough to warrant
}, // spending time on a more correct solution tomorrow
forceRefreshPage() { this._computedWatchers.siteDefaultAlignment.run();
this._computedWatchers?.simpleExtensionSettings?.run();
this._computedWatchers?.simpleDefaultSettings?.run();
this._computedWatchers?.siteDefaultCrop?.run();
this._computedWatchers?.siteDefaultStretch?.run();
this._computedWatchers?.siteDefaultAlignment?.run();
this._computedWatchers?.siteDefaultCropPersistence?.run();
this._computedWatchers?.defaultPersistanceLabel?.run();
this.$nextTick( () => this.$forceUpdate()); this.$nextTick( () => this.$forceUpdate());
}, },
setExtensionMode(component, event) { setExtensionMode(component, event) {
const option = event.target.value; const option = event.target.value;
console.log('SET EXTENSION MODE — OPTIONS:', option);
if (option === 'complex') { if (option === 'complex') {
return; 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') { if (option === 'default') {
return this.siteSettings.set(component, { return this.siteSettings.set(component, {
normal: ExtensionMode.Default, normal: ExtensionMode.Default,

View File

@ -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>;&nbsp;
Aard <span :style="getSiteEnabledColor(host, 'enableAard')"><small>{{ getSiteEnabledModes(host, 'enableAard') }}</small></span>;&nbsp;
kbd: <span :style="getSiteEnabledColor(host, 'enableKeyboard')"><small>{{ getSiteEnabledModes(host, 'enableKeyboard') }}</small></span>
UI: <span :style="getSiteEnabledColor(host, 'enableUI')"><small>{{ getSiteEnabledModes(host, 'enableUI') }}</small></span>
</small>
</div>
</div>
</template>
<script>
import ExtensionMode from '../../../../../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>

View File

@ -3,12 +3,8 @@
<ShortcutButton <ShortcutButton
v-for="(command, index) of settings?.active.commands.crop" v-for="(command, index) of settings?.active.commands.crop"
class="flex button" class="flex b3 button"
:class="{ :class="{active: editMode ? index === editModeOptions?.crop?.selectedIndex : isActiveCrop(command)}"
active: editMode ? index === editModeOptions?.crop?.selectedIndex : isActiveCrop(command),
'b3-compact': compact,
b3: !compact
}"
:key="index" :key="index"
:label="command.label" :label="command.label"
:shortcut="getKeyboardShortcutLabel(command)" :shortcut="getKeyboardShortcutLabel(command)"
@ -58,13 +54,12 @@
@blur="editModeOptions.crop.selected.label === 'New aspect ratio' ? editModeOptions.crop.selected.label = editModeOptions.crop.selected.arguments.ratio : null" @blur="editModeOptions.crop.selected.label === 'New aspect ratio' ? editModeOptions.crop.selected.label = editModeOptions.crop.selected.arguments.ratio : null"
> >
</div> </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 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 (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. your numbers without quote marks. Number will be converted to factor form on save.
</div> </div>
</div>
<div class="field"> <div class="field">
<div class="label"> <div class="label">
Label: Label:
@ -72,10 +67,10 @@
<div class="input"> <div class="input">
<input v-model="editModeOptions.crop.selected.label"> <input v-model="editModeOptions.crop.selected.label">
</div> </div>
</div>
<div class="hint"> <div class="hint">
Label for the button. You can make it say something other than ratio. Label for the button. You can make it say something other than ratio.
</div> </div>
</div>
</template> </template>
<!-- editing keyboard shortcuts is always allowed --> <!-- editing keyboard shortcuts is always allowed -->
@ -88,12 +83,12 @@
> >
</EditShortcutButton> </EditShortcutButton>
</div> </div>
</div>
<div class="hint"> <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. <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 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. any keyboard shortcuts defined by the site itself.
</div> </div>
</div>
<div class="flex flex-row flex-end"> <div class="flex flex-row flex-end">
<div <div
@ -116,7 +111,7 @@
</div> </div>
</div> </div>
<div v-if="siteSettings && allowSettingSiteDefault" class="edit-action-area"> <div v-if="siteSettings" class="edit-action-area">
<div class="field"> <div class="field">
<div class="label">Default for this site</div> <div class="label">Default for this site</div>
<div class="select"> <div class="select">
@ -138,25 +133,16 @@
</template> </template>
<script> <script>
import ShortcutButton from '@csui/src/components/ShortcutButton.vue'; import ShortcutButton from '../../../components/ShortcutButton.vue';
import EditShortcutButton from '@csui/src/components/EditShortcutButton'; import EditShortcutButton from '../../../components/EditShortcutButton';
import EditModeMixin from '@csui/src/utils/EditModeMixin'; import EditModeMixin from '../../../utils/EditModeMixin';
import KeyboardShortcutParserMixin from '@csui/src/utils/KeyboardShortcutParserMixin'; import KeyboardShortcutParserMixin from '../../../utils/KeyboardShortcutParserMixin';
import CommsMixin from '@csui/src/utils/CommsMixin'; import CommsMixin from '../../../utils/CommsMixin';
import AspectRatioType from '@src/common/enums/AspectRatioType.enum'; import AspectRatioType from '../../../../../common/enums/AspectRatioType.enum';
export default { export default {
components: {
ShortcutButton,
EditShortcutButton,
},
mixins: [
// ComputeActionsMixin,
EditModeMixin,
KeyboardShortcutParserMixin,
CommsMixin
],
data() { data() {
return { return {
AspectRatioType: AspectRatioType, AspectRatioType: AspectRatioType,
@ -169,14 +155,22 @@ export default {
} }
} }
}, },
mixins: [
// ComputeActionsMixin,
EditModeMixin,
KeyboardShortcutParserMixin,
CommsMixin
],
props: [ props: [
'settings', // required for buttons and actions, which are global 'settings', // required for buttons and actions, which are global
'siteSettings', 'siteSettings',
'eventBus', 'eventBus',
'isEditing', 'isEditing'
'allowSettingSiteDefault',
'compact',
], ],
components: {
ShortcutButton,
EditShortcutButton,
},
computed: { computed: {
siteDefaultCrop() { siteDefaultCrop() {
if (!this.siteSettings) { if (!this.siteSettings) {

View File

@ -3,10 +3,7 @@
<ShortcutButton <ShortcutButton
v-for="(command, index) of settings?.active.commands.stretch" v-for="(command, index) of settings?.active.commands.stretch"
class="b3 button" class="b3 button"
:class="{active: editMode ? index === editModeOptions?.stretch?.selectedIndex : isActiveStretch(command), :class="{active: editMode ? index === editModeOptions?.stretch?.selectedIndex : isActiveStretch(command)}"
'b3-compact': compact,
b3: !compact
}"
:key="index" :key="index"
:label="command.label" :label="command.label"
:shortcut="getKeyboardShortcutLabel(command)" :shortcut="getKeyboardShortcutLabel(command)"
@ -53,11 +50,11 @@
v-model="editModeOptions.stretch.selected.arguments.limit" v-model="editModeOptions.stretch.selected.arguments.limit"
> >
</div> </div>
</div>
<div class="hint"> <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. 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. 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>
</div>
</template> </template>
<!-- Some options are only shown for type 5 (fixed) stretch --> <!-- Some options are only shown for type 5 (fixed) stretch -->
@ -73,12 +70,12 @@
@blur="editModeOptions.stretch.selected.label === 'Stretch to ...' ? editModeOptions.stretch.selected.label = `Stretch to ${editModeOptions.stretch.selected.arguments.ratio}` : null" @blur="editModeOptions.stretch.selected.label === 'Stretch to ...' ? editModeOptions.stretch.selected.label = `Stretch to ${editModeOptions.stretch.selected.arguments.ratio}` : null"
> >
</div> </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 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 (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. your numbers without quote marks. Number will be converted to factor form on save.
</div> </div>
</div>
<div class="field"> <div class="field">
<div class="label"> <div class="label">
Label: Label:
@ -86,10 +83,10 @@
<div class="input"> <div class="input">
<input v-model="editModeOptions.stretch.selected.label"> <input v-model="editModeOptions.stretch.selected.label">
</div> </div>
</div>
<div class="hint"> <div class="hint">
Label for the button. You can make it say something other than ratio. Label for the button. You can make it say something other than ratio.
</div> </div>
</div>
</template> </template>
<!-- editing keyboard shortcuts is always allowed --> <!-- editing keyboard shortcuts is always allowed -->
@ -102,12 +99,12 @@
> >
</EditShortcutButton> </EditShortcutButton>
</div> </div>
</div>
<div class="hint"> <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. <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 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. any keyboard shortcuts defined by the site itself.
</div> </div>
</div>
<div class="flex flex-row flex-end"> <div class="flex flex-row flex-end">
<div <div
@ -130,7 +127,7 @@
</div> </div>
</div> </div>
<div v-if="siteSettings && allowSettingSiteDefault" class="edit-action-area"> <div v-if="siteSettings" class="edit-action-area">
<div class="field"> <div class="field">
<div class="label">Default for this site:</div> <div class="label">Default for this site:</div>
<div class="select"> <div class="select">
@ -186,9 +183,7 @@ export default {
'settings', // required for buttons and actions, which are global 'settings', // required for buttons and actions, which are global
'siteSettings', 'siteSettings',
'eventBus', 'eventBus',
'isEditing', 'isEditing'
'allowSettingSiteDefault',
'compact',
], ],
components: { components: {
ShortcutButton, ShortcutButton,
@ -249,6 +244,6 @@ export default {
} }
</script> </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/panels.scss" scoped></style>
<style lang="scss" src="@csui/src/res-common/common.scss" scoped></style> <style lang="scss" src="@csui/src/res-common/common.scss" scoped></style>

View File

@ -1,125 +1,28 @@
<template> <template>
<div class="flex flex-col"> <div class="flex flex-col">
<div class="sub-panel-content flex flex-row flex-wrap">
<ShortcutButton
v-for="(command, index) of settings?.active.commands.zoom"
class="flex button"
:class="{active: editMode ? index === editModeOptions?.zoom?.selectedIndex : isActiveZoom(command),
'b3-compact': compact,
b3: !compact
}"
:key="index"
:label="command.label"
:shortcut="getKeyboardShortcutLabel(command)"
@click="editMode ? editAction(command, index, 'zoom') : execAction(command)"
>
</ShortcutButton>
<!-- "Add new" button -->
<ShortcutButton
v-if="editMode"
class="button b3"
:class="{active: editMode ? editModeOptions?.crop?.selectedIndex === null : isActiveCrop(command)}"
label="Add new"
@click="editAction(
{action: 'set-ar-zoom', label: 'New aspect ratio', arguments: {type: AspectRatioType.Fixed}},
null,
'zoom'
)"
></ShortcutButton>
</div>
<template v-if="isEditing">
<div v-if="editMode && editModeOptions?.zoom?.selected" class="sub-panel-content">
<div class="edit-action-area-header">
<span class="text-primary">Editing options for:</span> <b>{{editModeOptions?.zoom?.selected?.label}}</b>&nbsp;
<template v-if="editModeOptions?.zoom?.selectedIndex === null && editModeOptions?.zoom?.selected?.label !== 'New aspect ratio'">(New ratio)</template>
</div>
<div class="edit-action-area">
<!-- Some options are only shown for type 4 (fixed) zooms -->
<template v-if="editModeOptions?.zoom?.selected?.arguments?.type === AspectRatioType.Fixed">
<div class="field">
<div class="label">
Ratio:
</div>
<div class="input">
<!-- We do an ugly in order to avoid spamming functions down at the bottom -->
<input
v-model="editModeOptions.zoom.selected.arguments.ratio"
@blur="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>
&nbsp;
<template v-if="editModeOptions?.zoom?.selectedIndex === null">Add</template>
<template v-else>Save</template>
</div>
</div>
</div>
</div>
</template>
<template v-else>
<!-- <!--
min, max and value need to be implemented in js as this slider min, max and value need to be implemented in js as this slider
should use logarithmic scale should use logarithmic scale
--> -->
<div class="flex flex-row w-full" style="margin-top: 0.66rem"> <div class="flex flex-row flex-end">
<div style="position:relative;" class="grow"> <Button
<template v-if="zoomAspectRatioLocked"> v-if="zoomAspectRatioLocked"
<div class="slider-label"> label="Unlock aspect ratio"
Zoom: {{getZoomForDisplay('x')}} icon="lock-open"
:fixedWidth="true"
@click="toggleZoomAr()"
>
</Button>
<Button
v-else
label="Lock aspect ratio"
icon="lock"
:fixedWidth="true"
@click="toggleZoomAr()"
>
</Button>
</div> </div>
<template v-if="zoomAspectRatioLocked">
<input id="_input_zoom_slider" <input id="_input_zoom_slider"
class="input-slider" class="input-slider"
type="range" type="range"
@ -129,9 +32,17 @@
:value="zoom.x" :value="zoom.x"
@input="changeZoom($event.target.value)" @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="flex flex-nogrow flex-noshrink medium-small">
<a class="_zoom_reset x-pad-1em" @click="resetZoom()">reset</a>
</div>
</div>
</template> </template>
<template v-else> <template v-else>
<div class="slider-label">Horizontal zoom: {{getZoomForDisplay('x')}}</div> <div>Horizontal zoom</div>
<input id="_input_zoom_slider" <input id="_input_zoom_slider"
class="input-slider" class="input-slider"
type="range" type="range"
@ -142,8 +53,8 @@
@input="changeZoom($event.target.value, 'x')" @input="changeZoom($event.target.value, 'x')"
/> />
<div class="slider-label">Vertical zoom: {{getZoomForDisplay('y')}}</div> <div>Vertical zoom</div>
<input id="_input_zoom_slider_2" <input id="_input_zoom_slider"
class="input-slider" class="input-slider"
type="range" type="range"
step="any" step="any"
@ -152,73 +63,24 @@
:value="zoom.y" :value="zoom.y"
@input="changeZoom($event.target.value, 'y')" @input="changeZoom($event.target.value, 'y')"
/> />
</template>
</div>
<div class="flex flex-row items-center justify-center" style="padding-left: 1rem"> <div style="overflow: auto" class="flex flex-row">
<Button <div class="flex flex-grow medium-small x-pad-1em">
v-if="zoomAspectRatioLocked" Zoom: {{getZoomForDisplay('x')}} x {{getZoomForDisplay('y')}}
icon="lock" </div>
:iconSize="16" <div class="flex flex-nogrow flex-noshrink medium-small">
:fixedWidth="true" <a class="_zoom_reset x-pad-1em" @click="resetZoom()">reset</a>
: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>
</div> </div>
</template> </template>
</div> </div>
</template> </template>
<script> <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 { 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() { data() {
return { return {
AspectRatioType,
zoomAspectRatioLocked: true, zoomAspectRatioLocked: true,
zoom: { zoom: {
x: 0, x: 0,
@ -234,24 +96,20 @@ export default {
} }
} }
}, },
created() { mixins: [
if (this.isEditing) {
this.enableEditMode(); ],
} props: [
}, 'settings', // required for buttons and actions, which are global
watch: { 'siteSettings',
isEditing(newValue, oldValue) { 'eventBus',
if (newValue) { 'isEditing'
this.enableEditMode(); ],
} else {
this.disableEditMode();
}
}
},
methods: { methods: {
getZoomForDisplay(axis) { getZoomForDisplay(axis) {
// zoom is internally handled logarithmically, because we want to have x0.5, x1, x2, x4 ... magnifications // 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. // 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()}%` return `${(Math.pow(2, this.zoom[axis]) * 100).toFixed()}%`
}, },
toggleZoomAr() { toggleZoomAr() {
@ -267,7 +125,8 @@ export default {
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'y'}); // this.eventBus.send('set-zoom', {zoom: 1, axis: 'y'});
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'x'}); // 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) { changeZoom(newZoom, axis) {
// we store zoom logarithmically on this compnent // we store zoom logarithmically on this compnent
@ -281,14 +140,12 @@ export default {
newZoom = Math.pow(2, newZoom); newZoom = Math.pow(2, newZoom);
if (this.zoomAspectRatioLocked) { 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 { } 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="../../../../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/panels.scss" scoped></style>
<style lang="scss" src="@csui/src/res-common/common.scss" scoped></style> <style lang="scss" src="@csui/src/res-common/common.scss" scoped></style>
<style lang="scss" scoped>
.input-slider {
width: 100%;
box-sizing:border-box;
margin-right: 1rem;
margin-left: 0rem;
}
.slider-label {
margin-bottom: -0.5rem;
color: #aaa;
font-size: 0.75rem;
text-transform: uppercase;
}
</style>

View File

@ -5,14 +5,23 @@
<h2>Player UI options</h2> <h2>Player UI options</h2>
<div class="flex flex-col compact-form"> <div class="flex flex-col compact-form">
<div v-if="!siteSettings.data.enableUI.fullscreen"> <div class="field">
UI is disabled for this site. <div class="label">Enable in-player UI</div>
<input
type="checkbox"
v-model="settings.active.ui.inPlayer.enabled"
@change="saveSettings()"
/>
</div> </div>
<div <div
class="flex flex-col field-group compact-form" 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="field disabled">
<div class="label"> <div class="label">
Popup activator position: Popup activator position:
@ -135,19 +144,6 @@
:isEditing="true" :isEditing="true"
></StretchOptionsPanel> ></StretchOptionsPanel>
</div> </div>
<!-- ZOOM OPTIONS -->
<div>
<div class="flex flex-row">
<h3 class="mth3">ZOOM OPTIONS</h3>
</div>
<ZoomOptionsPanel
:settings="settings"
:eventBus="eventBus"
:isEditing="true"
></ZoomOptionsPanel>
</div>
</div> </div>
</div> </div>
@ -155,18 +151,16 @@
</template> </template>
<script> <script>
import Button from '@csui/src/components/Button.vue' import Button from '../components/Button.vue'
import BrowserDetect from '@src/ext/conf/BrowserDetect'; import BrowserDetect from '../../../ext/conf/BrowserDetect';
import CropOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/CropOptionsPanel.vue' import CropOptionsPanel from './PanelComponents/VideoSettings/CropOptionsPanel.vue'
import StretchOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/StretchOptionsPanel.vue' import StretchOptionsPanel from './PanelComponents/VideoSettings/StretchOptionsPanel.vue'
import ZoomOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/ZoomOptionsPanel.vue'
export default { export default {
components: { components: {
Button, Button,
CropOptionsPanel, CropOptionsPanel,
StretchOptionsPanel, StretchOptionsPanel
ZoomOptionsPanel,
}, },
data() { data() {
return { return {
@ -177,7 +171,6 @@ export default {
], ],
props: [ props: [
'settings', // required for buttons and actions, which are global 'settings', // required for buttons and actions, which are global
'siteSettings',
'eventBus', 'eventBus',
], ],
mounted() { mounted() {
@ -282,7 +275,7 @@ export default {
gap: 1rem; gap: 1rem;
> * { > * {
width: calc(33% - 0.5rem); width: calc(50% - 0.5rem);
} }
} }

View File

@ -0,0 +1,9 @@
<template>
<div class="flex flex-col">
<div class="">
</div>
</div>
</template>

View File

@ -38,7 +38,7 @@
<!-- CROP OPTIONS --> <!-- CROP OPTIONS -->
<div v-if="settings" class="sub-panel"> <div v-if="settings" class="sub-panel">
<div class="flex flex-row"> <div class="flex flex-row">
<mdicon name="crop" :size="16" /> <mdicon name="crop" :size="32" />
<h1>Crop video:</h1> <h1>Crop video:</h1>
</div> </div>

View File

@ -1,11 +1,7 @@
<template> <template>
<div class="button center-text" <div class="button center-text"
:class="{ :class="{'setting-selected': selected }"
'setting-selected': selected,
'no-pad': noPad,
}"
> >
<mdicon v-if="icon" :name="icon" :size="iconSize ?? 24"></mdicon>
<div class="label"> <div class="label">
{{label}} {{label}}
</div> </div>
@ -19,8 +15,6 @@ export default {
selected: Boolean, selected: Boolean,
label: String, label: String,
icon: String, icon: String,
iconSize: Number,
noPad: Boolean
} }
} }
</script> </script>
@ -46,7 +40,4 @@ export default {
} }
} }
.no-pad {
padding: 0.5rem 1rem !important;
}
</style> </style>

View File

@ -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>

View File

@ -76,7 +76,6 @@ export default {
left: 100%; left: 100%;
} }
</style> </style>
<style lang="scss"> <style lang="scss">
.activator { .activator {
position: relative; position: relative;
@ -84,12 +83,12 @@ export default {
font-size: .95rem; font-size: .95rem;
padding: 1rem 1.6rem; 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%); backdrop-filter: blur(16px) saturate(120%);
white-space: nowrap; white-space: nowrap;
&:hover { &:hover {
background-color: rgba(255, 128, 64, 0.95); background-color: rgba(255, 128, 64, 0.5);
} }
&.expand-left { &.expand-left {

View File

@ -24,17 +24,16 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.item { .item {
position: relative;
font-size: .95rem; font-size: .95rem;
font-family: 'Overpass'; font-family: 'Overpass';
padding: 1rem 1.6rem; 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%); backdrop-filter: blur(16px) saturate(120%);
white-space: nowrap; white-space: nowrap;
&.can-hover:hover { &.can-hover:hover {
background-color: rgba(0,0,0,0.98); background-color: rgba(0,0,0,0.8);
border-bottom: 1px solid #fa6; border-bottom: 1px solid #fa6;
} }

View File

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

View File

@ -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>

View 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>
:&nbsp;
</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>

View 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>

View 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);
}

View File

@ -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>

View File

@ -1,44 +1,44 @@
<template> <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" /> <mdicon name="check-decagram" />
<div v-if="!small">Verified</div> <div v-if="!small">Verified</div>
<div class="tooltip" :style="tooltipStyle"> <div class="tooltip">
<template v-if="small">Verified&nbsp;&nbsp;</template> <template v-if="small">Verified&nbsp;&nbsp;</template>
The extension is being tested and should work on this site. The extension is being tested and should work on this site.
</div> </div>
</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" /> <mdicon name="account-group" />
<div v-if="!small">Community</div> <div v-if="!small">Community</div>
<div class="tooltip" :style="tooltipStyle"> <div class="tooltip">
<template v-if="small">Community&nbsp;&nbsp;</template> <template v-if="small">Community&nbsp;&nbsp;</template>
People say extension works on this site (or have provided help getting the extension to work if it didn't).<br/><br/> 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 Tamius (the dev) does not test the extension on this site, probably because it requires a subscription or
is geoblocked. is geoblocked.
</div> </div>
</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" /> <mdicon name="help-circle-outline" />
<div v-if="!small">Unknown</div> <div v-if="!small">Unknown</div>
<div class="tooltip" :style="tooltipStyle"> <div class="tooltip">
<template v-if="small">Unknown&nbsp;&nbsp;</template> <template v-if="small">Unknown&nbsp;&nbsp;</template>
Not officially supported. Extension will try to fix things, but no promises.<br/><br/> 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 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). (unaware, not using the site, language barrier, geoblocking, paid services Tam doesn't use).
</div> </div>
</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" /> <mdicon name="account" />
<div v-if="!small">Modified by you</div> <div v-if="!small">Modified by you</div>
<div class="tooltip" :style="tooltipStyle"> <div class="tooltip">
<template v-if="small">Modified by you&nbsp;&nbsp;</template> <template v-if="small">Modified by you&nbsp;&nbsp;</template>
You have manually changed settings for this site. The extension is doing what you told it to do. You have manually changed settings for this site. The extension is doing what you told it to do.
</div> </div>
</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" /> <mdicon class="site-support no-support" name="checkbox-marked-circle" />
<div v-if="!small">Not supported</div> <div v-if="!small">Not supported</div>
<div class="tooltip" :style="tooltipStyle"> <div class="tooltip">
<template v-if="small">Not supported&nbsp;&nbsp;</template> <template v-if="small">Not supported&nbsp;&nbsp;</template>
Extension is known to not work with this site. Extension is known to not work with this site.
</div> </div>
@ -50,8 +50,6 @@ export default {
props: { props: {
siteSupportLevel: String, siteSupportLevel: String,
small: Boolean, small: Boolean,
supportLevelStyle: String,
tooltipStyle: String,
} }
} }
</script> </script>

View File

@ -73,11 +73,10 @@
@change="(event) => updateSettings(true)" @change="(event) => updateSettings(true)"
> >
</div> </div>
</div>
<div class="hint"> <div class="hint">
Width of the trigger zone (% of player area). Width of the trigger zone (% of player area).
</div> </div>
</div>
<div class="field"> <div class="field">
<div class="label">Trigger zone height:</div> <div class="label">Trigger zone height:</div>
<div class="input range-input"> <div class="input range-input">
@ -96,11 +95,10 @@
@change="(event) => updateSettings(true)" @change="(event) => updateSettings(true)"
> >
</div> </div>
</div>
<div class="hint"> <div class="hint">
Height of the trigger zone (% of player area). Height of the trigger zone (% of player area).
</div> </div>
</div>
<div class="field"> <div class="field">
<div class="label">Trigger zone horizontal offset:</div> <div class="label">Trigger zone horizontal offset:</div>
<div class="input range-input"> <div class="input range-input">
@ -118,11 +116,10 @@
@change="(event) => updateSettings(true)" @change="(event) => updateSettings(true)"
> >
</div> </div>
</div>
<div class="hint"> <div class="hint">
By default, trigger zone is centered around the button. This option moves trigger zone left and right. By default, trigger zone is centered around the button. This option moves trigger zone left and right.
</div> </div>
</div>
<div class="field"> <div class="field">
<div class="label">Trigger zone vertical offset:</div> <div class="label">Trigger zone vertical offset:</div>
<div class="input range-input"> <div class="input range-input">
@ -140,10 +137,10 @@
@change="(event) => updateSettings(true)" @change="(event) => updateSettings(true)"
> >
</div> </div>
</div>
<div class="hint"> <div class="hint">
By default, trigger zone is centered around the button. This option moves trigger zone up and down. By default, trigger zone is centered around the button. This option moves trigger zone up and down.
</div> </div>
</div>
<div class="action-row"> <div class="action-row">
<button @click="finishTriggerZoneEdit">Finish editing</button> <button @click="finishTriggerZoneEdit">Finish editing</button>

View File

@ -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>

View File

@ -1,158 +1,62 @@
<template> <template>
<div class="flex flex-col relative h-full" style="padding-bottom: 20px"> <div class="flex flex-col" 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>
</template>
<template v-else>
<div
v-if="siteSettings.isEnabledForEnvironment(false, true) === ExtensionMode.Disabled"
class="warning-compact"
>
<div class="w-full flex flex-row">
<div class="grow">
<b>Extension is disabled for this site.</b>
</div>
<div>
<button
class="flex flex-row items-center"
style="border: 1px solid black; background-color: transparent; color: black; padding: 0.25rem 0.5rem; margin-top: -0.25rem; margin-right: -0.5rem;"
@click="openSettings()"
>
Open settings <mdicon style="margin-left: 0.5rem;" name="open-in-new" size="16"></mdicon>
</button>
</div>
</div>
<small>Controls will only work on content embedded from the following sites:</small><br/>
<div class="w-full flex flex-row justify-center">
<span v-for="host of enabledHosts" :key="host" class="website-name">{{host}}</span>
</div>
</div>
<div class="flex flex-row"> <div class="flex flex-row">
<mdicon name="crop" :size="16" />&nbsp;&nbsp; <mdicon name="crop" :size="24" />&nbsp;&nbsp;
<span>CROP</span> <h1>Crop video:</h1>
</div> </div>
<div
style="margin-top: -0.69rem; margin-bottom: 0.88rem;"
>
<CropOptionsPanel <CropOptionsPanel
style="margin-top: -2rem"
:settings="settings" :settings="settings"
:eventBus="eventBus" :eventBus="eventBus"
:siteSettings="siteSettings" :siteSettings="siteSettings"
:isEditing="false" :isEditing="false"
:compact="true"
> >
</CropOptionsPanel> </CropOptionsPanel>
</div>
<div class="flex flex-row"> <div class="flex flex-row">
<mdicon name="crop" :size="16" />&nbsp;&nbsp; <mdicon name="crop" :size="24" />&nbsp;&nbsp;
<span>STRETCH</span> <h1>Stretch video:</h1>
</div> </div>
<div
style="margin-top: -0.69rem; margin-bottom: 0.88rem;"
>
<StretchOptionsPanel <StretchOptionsPanel
style="margin-top: -2rem"
:settings="settings" :settings="settings"
:eventBus="eventBus" :eventBus="eventBus"
:siteSettings="siteSettings" :siteSettings="siteSettings"
:isEditing="false" :isEditing="false"
:compact="true"
></StretchOptionsPanel> ></StretchOptionsPanel>
</div>
<div class="flex flex-row"> <div class="flex flex-row">
<mdicon name="crop" :size="16" />&nbsp;&nbsp; <mdicon name="crop" :size="24" />&nbsp;&nbsp;
<span>ZOOM</span> <h1>Zoom:</h1>
</div> </div>
<div
style="margin-top: -0.69rem; margin-bottom: 0.88rem;"
>
<ZoomOptionsPanel
:settings="settings"
:eventBus="eventBus"
:siteSettings="siteSettings"
:isEditing="false"
:compact="true"
>
</ZoomOptionsPanel>
</div>
<div class="flex flex-row">
<mdicon name="crop" :size="16" />&nbsp;&nbsp;
<span>ALIGN</span>
</div>
<div
style="margin-bottom: 0.88rem;"
>
<AlignmentOptionsControlComponent
:eventBus="eventBus"
:large="true"
> </AlignmentOptionsControlComponent>
</div>
</template>
</div> </div>
</template> </template>
<script> <script>
import CropOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/CropOptionsPanel'; import CropOptionsPanel from '../../PlayerUiPanels/PanelComponents/VideoSettings/CropOptionsPanel';
import StretchOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/StretchOptionsPanel.vue'; import StretchOptionsPanel from '../../PlayerUiPanels/PanelComponents/VideoSettings/StretchOptionsPanel.vue';
import ZoomOptionsPanel from '@csui/src/PlayerUiPanels/PanelComponents/VideoSettings/ZoomOptionsPanel.vue'; import ZoomOptionsPanel from '../../PlayerUiPanels/PanelComponents/VideoSettings/ZoomOptionsPanel.vue';
import ExtensionMode from '@src/common/enums/ExtensionMode.enum.ts';
import AlignmentOptionsControlComponent from '@csui/src/PlayerUiPanels/AlignmentOptionsControlComponent.vue';
import { SiteSettings } from '../../../../ext/lib/settings/SiteSettings';
export default { export default {
components: { data() {
CropOptionsPanel, return {
StretchOptionsPanel, exec: null,
ZoomOptionsPanel, };
AlignmentOptionsControlComponent
}, },
mixins: [ mixins: [
], ],
props: [ props: [
'site',
'settings', 'settings',
'siteSettings', 'siteSettings',
'eventBus', 'eventBus',
'hosts'
], ],
data() { components: {
return { CropOptionsPanel, StretchOptionsPanel, ZoomOptionsPanel
exec: null,
ExtensionMode: ExtensionMode,
enabledHosts: [],
};
},
watch: {
hosts(val) {
this.filterActiveSites(val);
}
}, },
created() { created() {
this.eventBus.subscribe( this.eventBus.subscribe(
@ -162,7 +66,6 @@ export default {
function: (config) => this.handleConfigBroadcast(config) function: (config) => this.handleConfigBroadcast(config)
} }
); );
this.filterActiveSites(this.hosts);
}, },
mounted() { mounted() {
this.eventBus.sendToTunnel('get-ar'); this.eventBus.sendToTunnel('get-ar');
@ -171,37 +74,8 @@ export default {
this.eventBus.unsubscribeAll(this); this.eventBus.unsubscribeAll(this);
}, },
methods: { 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> </script>
<style lang="scss" scoped>
.warning-compact {
background-color: #d6ba4a;
color: #000;
padding: 0.5rem 1rem;
margin-top: -0.5rem;
margin-bottom: 0.5rem;
.website-name {
font-size: 0.85rem;
&:not(:last-of-type)::after {
content: ','
}
}
}
</style>

View File

@ -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>

View File

@ -1,109 +1,42 @@
<template> <template>
<div class="flex flex-row" style="width: 250px;"> <div>
<div class="flex-grow">
Custom zoom Custom zoom
</div> </div>
<div class="flex flex-row">
<Button
v-if="zoomAspectRatioLocked"
icon="lock"
:iconSize="16"
:fixedWidth="true"
:noPad="true"
@click="toggleZoomAr()"
>
</Button>
<Button
v-else
icon="lock-open-variant"
:iconSize="16"
:fixedWidth="true"
:noPad="true"
@click="toggleZoomAr()"
>
</Button>
<Button
icon="restore"
:iconSize="16"
:noPad="true"
@click="resetZoom()"
></Button>
</div>
</div>
<template v-if="zoomAspectRatioLocked"> <div class="top-label">Zoom:</div>
<div class="input range-input no-bg"> <div class="input range-input">
<input <input
type="range" type="range"
class="slider" class="slider"
min="-1" min="0"
max="3" max="3"
step="0.01" step="0.01"
:value="zoom.x"
@input="changeZoom($event.target.value)"
/> />
<input <input
class="disabled"
style="width: 2rem;"
:value="getZoomForDisplay('x')"
/> />
</div> </div>
</template> <template v-if="true">
<template v-else>
<div class="top-label">Horizontal zoom:</div>
<div class="input range-input no-bg">
<input
type="range"
class="slider"
min="-1"
max="3"
step="0.01"
:value="zoom.x"
@input="changeZoom($event.target.value, 'x')"
/>
<input
class="disabled"
style="width: 2rem;"
:value="getZoomForDisplay('x')"
/>
</div>
<div class="top-label">Vertical zoom:</div> <div class="top-label">Vertical zoom:</div>
<div class="input range-input no-bg"> <div class="input range-input">
<input <input
type="range" type="range"
class="slider" class="slider"
min="-1" min="0"
max="3" max="3"
step="0.01" step="0.01"
:value="zoom.y"
@input="changeZoom($event.target.value, 'y')"
/> />
<input <input
class="disabled"
style="width: 2rem;"
:value="getZoomForDisplay('y')"
/> />
</div> </div>
</template> </template>
<div><input type="checkbox"/> Control vertical and horizontal zoom independently.</div>
</template> </template>
<script> <script>
import Button from '@csui/src/components/Button.vue';
import * as _ from 'lodash';
export default { export default {
components: {
Button,
},
mixins: [
],
props: [
'settings', // required for buttons and actions, which are global
'eventBus'
],
data() { data() {
return { return {
zoomAspectRatioLocked: true, zoomAspectRatioLocked: true,
@ -118,42 +51,17 @@ export default {
stretch: null, stretch: null,
zoom: null, zoom: null,
pan: 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 mixins: [
);
this.debouncedGetEffectiveZoom = _.debounce( ],
() => { props: [
this.getEffectiveZoom(); 'settings', // required for buttons and actions, which are global
}, 'eventBus'
250 ],
),
this.getEffectiveZoom();
this.pollingInterval = setInterval(this.debouncedGetEffectiveZoom, 2000);
},
destroyed() {
this.eventBus.unsubscribe(this);
clearInterval(this.pollingInterval);
},
methods: { methods: {
getEffectiveZoom() {
this.eventBus?.sendToTunnel('get-effective-zoom', {});
},
getZoomForDisplay(axis) { getZoomForDisplay(axis) {
// zoom is internally handled logarithmically, because we want to have x0.5, x1, x2, x4 ... magnifications // 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. // 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: 'y'});
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'x'}); // 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) { changeZoom(newZoom, axis) {
if (isNaN(+newZoom)) { // we store zoom logarithmically on this compnent
return;
}
let logZoom, linZoom;
if (isLinear) {
newZoom /= 100;
logZoom = Math.log2(newZoom);
linZoom = newZoom;
} else {
logZoom = newZoom;
linZoom = Math.pow(2, newZoom);
}
// we store zoom logarithmically on this component
if (!axis) { if (!axis) {
this.zoom.x = logZoom; this.zoom.x = newZoom;
} else { } else {
this.zoom[axis] = logZoom; this.zoom[axis] = newZoom;
} }
// we do not use logarithmic zoom elsewhere, therefore we need to convert // we do not use logarithmic zoom elsewhere, therefore we need to convert
newZoom = Math.pow(2, newZoom);
if (this.zoomAspectRatioLocked) { 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 { } else {
this.eventBus?.sendToTunnel('set-zoom', {zoom: {[axis ?? 'x']: linZoom}}); this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom, axis: axis ?? 'x'});
} }
}, },
} }

View File

@ -4,12 +4,6 @@ div, p, span {
font-size: 16px; font-size: 16px;
} }
.disabled {
pointer-events: none;
/* color: #666; */
filter: contrast(50%) brightness(40%) grayscale(100%);
}
.warning-box { .warning-box {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -58,19 +52,10 @@ button, .button {
border-color: rgba($primary, .5); border-color: rgba($primary, .5);
} }
&.primary {
background-color: #fa6;
color: #000;
}
&.danger { &.danger {
background-color: #ff2211 !important; background-color: #ff2211 !important;
color:#000; color:#000;
} }
&.disabled {
filter: saturate(0%);
}
} }
.b3 { .b3 {
margin: 0.25rem; margin: 0.25rem;
@ -89,12 +74,6 @@ button, .button {
border-bottom: 1px solid rgba($primary, 0.5); border-bottom: 1px solid rgba($primary, 0.5);
} }
&.no-bg {
background-color: transparent;
border-color: transparent;
}
input { input {
width: 100%; width: 100%;
outline: none; outline: none;
@ -136,9 +115,7 @@ button, .button {
.field { .field {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
box-sizing: border-box; flex-wrap: wrap;
width: 100%;
max-width: 100%;
margin-top: 0.5rem; margin-top: 0.5rem;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
@ -152,47 +129,28 @@ button, .button {
.label { .label {
flex: 0 0 25%; flex: 0 0 25%;
min-width: 16rem;
text-align: right; text-align: right;
padding-right: 1rem; padding-right: 1rem;
.color-emphasis {
color: #fa6;
}
.sub-label {
font-size: 0.9em;
opacity: 0.69;
}
} }
.input, .range-input { .input, .range-input {
flex-grow: 1; flex: 0 0 70%;
flex-shrink: 1;
max-width: 24rem; max-width: 24rem;
min-width: 16rem;
} }
.has-hint { .hint {
display: flex; padding-left: calc(25% + 1rem);
flex-direction: column; font-size: 0.8rem;
opacity: 0.7;
margin-top: 0.25rem;
width: 100%;
} }
.select { .select {
flex-grow: 1;
flex-shrink: 1;
min-width: 12px;
max-width: 24rem;
select { select {
background: rgba($blackBg, $hoverTransparentOpacity); background: rgba($blackBg, $hoverTransparentOpacity);
min-width: 12px;
max-width: 100%;
color: #fff; color: #fff;
// border: 0px solid transparent; border: 0px solid transparent;
border: 1px solid rgba(255, 171, 102, 0.42);
padding: 0.5rem 1rem 0.25rem; padding: 0.5rem 1rem 0.25rem;
outline: none; 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 { .options-bar {
position: absolute; position: absolute;
@ -231,15 +180,9 @@ button, .button {
padding-left: 0.33rem; padding-left: 0.33rem;
padding-right: 0.33rem; padding-right: 0.33rem;
} }
.b3-compact { .input-slider {
width: 7rem; width: 480px;
padding-left: 0.25rem;
padding-right: 0.25rem;
} }
// .input-slider {
// width: 480px;
// }
.warning-lite { .warning-lite {
padding-right: 16px; padding-right: 16px;
padding-bottom: 16px; padding-bottom: 16px;

View File

@ -36,7 +36,7 @@ export default {
}, },
methods: { methods: {
playerDimensionsUpdate(dimensions) { playerDimensionsUpdate(dimensions) {
if (!dimensions?.width || !dimensions?.height) { if (!dimensions.width || !dimensions.height) {
this.playerDimensions = undefined; this.playerDimensions = undefined;
} }
if (dimensions?.width !== this.playerDimensions?.width || dimensions?.height !== this.playerDimensions?.height) { if (dimensions?.width !== this.playerDimensions?.width || dimensions?.height !== this.playerDimensions?.height) {

View File

@ -1,13 +1,15 @@
import Debug from './conf/Debug'; import Debug from './conf/Debug';
import ExtensionMode from '../common/enums/ExtensionMode.enum';
import Settings from './lib/Settings'; import Settings from './lib/Settings';
import Comms from './lib/comms/Comms';
import CommsClient from './lib/comms/CommsClient'; import CommsClient from './lib/comms/CommsClient';
import PageInfo from './lib/video-data/PageInfo'; import PageInfo from './lib/video-data/PageInfo';
import Logger, { baseLoggingOptions } from './lib/Logger';
import UWGlobals from './lib/UWGlobals';
import EventBus from './lib/EventBus'; import EventBus from './lib/EventBus';
import KeyboardHandler from './lib/kbm/KeyboardHandler'; import KeyboardHandler from './lib/kbm/KeyboardHandler';
import { SiteSettings } from './lib/settings/SiteSettings'; import { SiteSettings } from './lib/settings/SiteSettings';
import UI from './lib/uwui/UI'; 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 { export default class UWContent {
pageInfo: PageInfo; pageInfo: PageInfo;
@ -15,8 +17,7 @@ export default class UWContent {
settings: Settings; settings: Settings;
siteSettings: SiteSettings; siteSettings: SiteSettings;
keyboardHandler: KeyboardHandler; keyboardHandler: KeyboardHandler;
logAggregator: LogAggregator; logger: Logger;
logger: ComponentLogger;
eventBus: EventBus; eventBus: EventBus;
isIframe: boolean = false; isIframe: boolean = false;
@ -33,7 +34,7 @@ export default class UWContent {
reloadSettings() { reloadSettings() {
try { 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(); this.init();
} catch (e) { } catch (e) {
console.warn('Ultrawidify: settings reload failed. This probably shouldn\'t outright kill the extension, but page reload is recommended.'); 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 // logger init is the first thing that needs to run
try { try {
if (!this.logger) { if (!this.logger) {
this.logAggregator = new LogAggregator('◈'); this.logger = new Logger();
this.logger = new ComponentLogger(this.logAggregator, 'UWContent'); await this.logger.init(baseLoggingOptions);
await this.logAggregator.init(BLANK_LOGGER_CONFIG);
} }
} catch (e) { } catch (e) {
console.error("logger init failed!", e) console.error("logger init failed!", e)
@ -67,7 +67,7 @@ export default class UWContent {
if (!this.settings) { if (!this.settings) {
this.settings = new Settings({ this.settings = new Settings({
onSettingsChanged: () => this.reloadSettings(), onSettingsChanged: () => this.reloadSettings(),
logAggregator: this.logAggregator logger: this.logger
}); });
await this.settings.init(); await this.settings.init();
this.siteSettings = this.settings.getSiteSettings(); this.siteSettings = this.settings.getSiteSettings();
@ -81,13 +81,13 @@ export default class UWContent {
function: () => this.initPhase2() 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.eventBus.setComms(this.comms);
this.initPhase2(); this.initPhase2();
} catch (e) { } 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() { initPhase2() {
try { try {
if (this.pageInfo) { 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.destroy();
} }
this.pageInfo = new PageInfo(this.eventBus, this.siteSettings, this.settings, this.logAggregator); this.pageInfo = new PageInfo(this.eventBus, this.siteSettings, this.settings, this.logger);
this.logger.debug('setup', "pageInfo initialized."); 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) { if (this.keyboardHandler) {
this.keyboardHandler.destroy(); 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.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 = new UI('ultrawidify-global-ui', {eventBus: this.eventBus, isGlobal: true});
this.globalUi.enable(); this.globalUi.enable();
@ -117,7 +117,7 @@ export default class UWContent {
} catch (e) { } catch (e) {
console.error('Ultrawidify: failed to start extension. Error:', 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);
} }
} }

View File

@ -2,20 +2,13 @@ import Debug from './conf/Debug.js';
import BrowserDetect from './conf/BrowserDetect'; import BrowserDetect from './conf/BrowserDetect';
import CommsServer from './lib/comms/CommsServer'; import CommsServer from './lib/comms/CommsServer';
import Settings from './lib/Settings'; import Settings from './lib/Settings';
import Logger, { baseLoggingOptions } from './lib/Logger';
import { sleep } from '../common/js/utils'; import { sleep } from '../common/js/utils';
import EventBus, { EventBusCommand } from './lib/EventBus'; 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 { export default class UWServer {
settings: Settings; settings: Settings;
logger: ComponentLogger; logger: Logger;
logAggregator: LogAggregator;
comms: CommsServer; comms: CommsServer;
eventBus: EventBus; eventBus: EventBus;
@ -71,13 +64,22 @@ export default class UWServer {
async setup() { async setup() {
try { try {
// logger is the first thing that goes up // 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.settings = new Settings({logger: this.logger});
this.logger = new ComponentLogger(this.logAggregator, 'UwServer', {styles: BASE_LOGGING_STYLES});
await this.logAggregator.init(loggingOptions);
this.settings = new Settings({logAggregator: this.logAggregator});
await this.settings.init(); await this.settings.init();
this.eventBus = new EventBus({isUWServer: true}); this.eventBus = new EventBus({isUWServer: true});
@ -93,16 +95,12 @@ export default class UWServer {
} }
} }
async _promisifyTabsGet(browserObj, tabId){ async _promisifyTabsGet(browserObj, tabId){
return new Promise( (resolve, reject) => { return new Promise( (resolve, reject) => {
browserObj.tabs.get(tabId, (tab) => resolve(tab)); browserObj.tabs.get(tabId, (tab) => resolve(tab));
}); });
} }
//#region CSS managemeent
async injectCss(css, sender) { async injectCss(css, sender) {
if (!css) { if (!css) {
return; return;
@ -132,7 +130,7 @@ export default class UWServer {
}); });
} }
} catch (e) { } 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) { async removeCss(css, sender) {
@ -162,16 +160,16 @@ export default class UWServer {
}); });
} }
} catch (e) { } 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) { async replaceCss(oldCss, newCss, sender) {
if (oldCss !== newCss) { if (oldCss !== newCss) {
this.removeCss(oldCss, sender); this.removeCss(oldCss, sender);
this.injectCss(newCss, sender); this.injectCss(newCss, sender);
} }
} }
//#endregion
extractHostname(url){ extractHostname(url){
var hostname; var hostname;
@ -208,21 +206,20 @@ export default class UWServer {
} }
this.currentSite = this.extractHostname(tab.url); 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) { } 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 = { this.selectedSubitem = {
'siteSettings': undefined, 'siteSettings': undefined,
'videoSettings': undefined, 'videoSettings': undefined,
} }
//TODO: change extension icon based on whether there's any videos on current page //TODO: change extension icon based on whether there's any videos on current page
} }
registerVideo(sender) { 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 tabHostname = this.extractHostname(sender.tab.url);
const frameHostname = this.extractHostname(sender.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) { 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 (this.videoTabs[sender.tab.id]) {
if ( Object.keys(this.videoTabs[sender.tab.id].frames).length <= 1) { if ( Object.keys(this.videoTabs[sender.tab.id].frames).length <= 1) {
delete this.videoTabs[sender.tab.id] 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) { 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; this.selectedSubitem[menu] = subitem;
} }
async getCurrentSite() { 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( this.eventBus.send(
'set-current-site', 'set-current-site',
{ {
site, site: await this.getVideoTab(),
tabHostname, tabHostname: await this.getCurrentTabHostname(),
}, },
{ {
comms: { comms: {
@ -321,12 +305,9 @@ export default class UWServer {
return { return {
host: 'INVALID SITE', host: 'INVALID SITE',
frames: [], frames: [],
hostnames: [],
} }
} }
const hostnames = await this.comms.listUniqueFrameHosts();
if (this.videoTabs[ctab.id]) { if (this.videoTabs[ctab.id]) {
// if video is older than PageInfo's video rescan period (+ 4000ms of grace), // if video is older than PageInfo's video rescan period (+ 4000ms of grace),
// we clean it up from videoTabs[tabId].frames array. // we clean it up from videoTabs[tabId].frames array.
@ -349,7 +330,6 @@ export default class UWServer {
return { return {
...this.videoTabs[ctab.id], ...this.videoTabs[ctab.id],
host: this.extractHostname(ctab.url), host: this.extractHostname(ctab.url),
hostnames,
selected: this.selectedSubitem selected: this.selectedSubitem
}; };
} }
@ -359,8 +339,7 @@ export default class UWServer {
return { return {
host: this.extractHostname(ctab.url), host: this.extractHostname(ctab.url),
frames: [], frames: [],
hostnames, selected: this.selectedSubitem
selected: this.selectedSubitem,
} }
} }
@ -368,15 +347,12 @@ export default class UWServer {
const activeTab = await this.activeTab; const activeTab = await this.activeTab;
if (!activeTab || activeTab.length < 1) { if (!activeTab || activeTab.length < 1) {
this.logger.log('warn', 'comms', 'There is no active tab for some reason. activeTab:', activeTab);
return null; return null;
} }
const url = activeTab[0].url; const url = activeTab[0].url;
if (!url) {
console.log('no URL for active tab:', activeTab[0].url);
}
var hostname; var hostname;
if (url.indexOf("://") > -1) { //find & remove protocol (http, ftp, etc.) and get hostname if (url.indexOf("://") > -1) { //find & remove protocol (http, ftp, etc.) and get hostname

View File

@ -8,239 +8,202 @@ import SettingsInterface from '../../common/interfaces/SettingsInterface';
import { _cp } from '../../common/js/utils'; import { _cp } from '../../common/js/utils';
import CropModePersistence from '../../common/enums/CropModePersistence.enum'; import CropModePersistence from '../../common/enums/CropModePersistence.enum';
import AspectRatioType from '../../common/enums/AspectRatioType.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) => {
// 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;
}
}
}
}
}, {
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) => {
// 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.sites['@empty'].defaults.alignment = {x: VideoAlignmentType.Default, y: VideoAlignmentType.Default};
}
}, {
forVersion: '6.1.1-6',
updateFn: (userOptions: SettingsInterface, defaultOptions) => { updateFn: (userOptions: SettingsInterface, defaultOptions) => {
for (const site in userOptions.sites) { for (const site in userOptions.sites) {
userOptions.sites[site].enableUI = { userOptions.sites[site].defaultType = userOptions.sites[site].type as any;
fullscreen: ExtensionMode.Default,
theater: ExtensionMode.Default,
normal: ExtensionMode.Default,
}
}
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,
} }
userOptions.sites['@global'].defaultType = 'unknown';
userOptions.sites['@empty'].defaultType = 'modified';
} }
}, { }, {
forVersion: '6.2.6', forVersion: '6.1.2-0',
updateFn: (userOptions: SettingsInterface, defaultOptions) => { updateFn: (userOptions: SettingsInterface, defaultOptions) => {
// remove custom CSS, as it is no longer needed
console.warn('[ultrawidify] STARTING SETTINGS MIGRATION TO 6.2.6'); for (const site in userOptions.sites) {
for (const domOption in userOptions.sites[site].DOMConfig)
if (!userOptions.commands) { userOptions.sites[site].DOMConfig[domOption].customCss;
userOptions.commands = {
zoom: [],
crop: [],
stretch: [],
pan: [],
internal: []
};
} }
userOptions.arDetect.aardType = 'auto';
userOptions.commands.zoom = [{ userOptions.ui = {
action: 'change-zoom', inPlayer: {
label: 'Zoom +5%', enabled: true, // enable by default on new installs
arguments: { enabledFullscreenOnly: false,
zoom: 0.05 minEnabledWidth: 0.75,
}, minEnabledHeight: 0.75,
shortcut: { activation: 'player',
key: 'z', popupAlignment: 'left',
code: 'KeyY', triggerZoneDimensions: {
ctrlKey: false, width: 0.5,
metaKey: false, height: 0.5,
altKey: false, offsetX: -50,
shiftKey: false, offsetY: 0,
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
} }
} }
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: { userOptions.newFeatureTracker['uw6.ui-popup'] = {show: 10};
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', forVersion: '6.2.1',
label: 'Cycle', updateFn: (userOptions: SettingsInterface, defaultOptions) => {
comment: 'Cycle through zoom options', userOptions.ui = defaultOptions.ui;
arguments: { userOptions.arDetect = defaultOptions.arDetect;
type: AspectRatioType.Cycle userOptions.newFeatureTracker = defaultOptions.newFeatureTracker;
},
shortcut: {
key: 'c',
code: 'KeyC',
ctrlKey: false,
metaKey: false,
altKey: false,
shiftKey: true,
onKeyUp: true,
onKeyDown: false,
} }
}, { }, {
action: 'set-ar-zoom', forVersion: '6.2.3',
label: '21:9', updateFn: (userOptions: SettingsInterface, defaultOptions) => {
comment: 'Zoom for 21:9 aspect ratio (1:2.39)', for (const site in userOptions.sites) {
arguments: { if (userOptions.sites[site].defaults?.stretch && !userOptions.sites[site].defaults?.stretch.type) {
type: AspectRatioType.Fixed, userOptions.sites[site].defaults.stretch = {type: userOptions.sites[site].defaults?.stretch as any as StretchType};
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;
}
const hasConflict = (shortcut: any) => {
for (const ct in userOptions.commands) {
for (const command of userOptions.commands[ct]) {
if (compareShortcuts(shortcut, command.shortcut)) {
return true;
}
}
}
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
});
} }
} }
} }
} }
]); ];
export default ExtensionConfPatch; export default ExtensionConfPatch;

File diff suppressed because it is too large Load Diff

View File

@ -19,10 +19,6 @@ export interface EventBusContext {
frame?: any, frame?: any,
sourceFrame?: IframeData sourceFrame?: IframeData
forwardTo?: 'all' | 'active' | 'contentScript' | 'server' | 'sameOrigin' | 'popup' | 'all-frames', 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 disableTunnel: boolean = false;
private popupContext: any = {}; private popupContext: any = {};
private iframeForwardingList: {iframe: any, fn: (action, payload, context?) => void}[] = [];
// private uiUri = window.location.href; // private uiUri = window.location.href;
constructor(options?: {isUWServer?: boolean}) { 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) { send(command: string, commandData: any, context?: EventBusContext) {
// execute commands we have subscriptions for // execute commands we have subscriptions for
@ -113,27 +94,8 @@ export default class EventBus {
// preventing messages from flowing back to their original senders is // preventing messages from flowing back to their original senders is
// CommsServer's job. EventBus does not have enough data for this decision. // CommsServer's job. EventBus does not have enough data for this decision.
// We do, however, have enough data to prevent backflow of messages that if (this.comms) {
// crossed CommsServer once already.
if (this.comms && context?.origin !== CommsOrigin.Server && !context?.borderCrossings?.commsServer) {
this.comms.sendMessage({command, config: commandData, context}, context); 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) { if (context?.stopPropagation) {

View File

@ -1,33 +1,22 @@
import Debug from '../conf/Debug'; import Debug from '../conf/Debug';
import currentBrowser from '../conf/BrowserDetect';
import ExtensionConf from '../conf/ExtensionConf'; import ExtensionConf from '../conf/ExtensionConf';
import ExtensionMode from '../../common/enums/ExtensionMode.enum';
import ObjectCopy from './ObjectCopy'; import ObjectCopy from './ObjectCopy';
import StretchType from '../../common/enums/StretchType.enum'; import StretchType from '../../common/enums/StretchType.enum';
import VideoAlignmentType from '../../common/enums/VideoAlignmentType.enum';
import ExtensionConfPatch from '../conf/ExtConfPatches'; 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 SettingsInterface from '../../common/interfaces/SettingsInterface';
import AspectRatioType from '../../common/enums/AspectRatioType.enum'; import AspectRatioType from '../../common/enums/AspectRatioType.enum';
import { SiteSettings } from './settings/SiteSettings'; 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'){ if(process.env.CHANNEL !== 'stable'){
console.info("Loading Settings"); 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 { class Settings {
//#region flags //#region flags
@ -36,9 +25,7 @@ class Settings {
//#endregion //#endregion
//#region helper classes //#region helper classes
logger: Logger;
logAggregator: LogAggregator;
logger: ComponentLogger;
//#endregion //#endregion
//#region data //#region data
@ -50,23 +37,17 @@ class Settings {
onSettingsChanged: any; onSettingsChanged: any;
afterSettingsSaved: any; afterSettingsSaved: any;
onChangedCallbacks: (() => void)[] = []; onChangedCallbacks: any[] = [];
afterSettingsChangedCallbacks: (() => void)[] = []; afterSettingsChangedCallbacks: any[] = [];
public snapshotManager: SettingsSnapshotManager;
//#endregion //#endregion
constructor(options: SettingsOptions) { constructor(options) {
// Options: activeSettings, updateCallback, logger // Options: activeSettings, updateCallback, logger
this.logger = options.logAggregator && new ComponentLogger(options.logAggregator, 'Settings', {styles: SETTINGS_LOGGER_STYLES}) || undefined;; this.logger = options?.logger;
this.onSettingsChanged = options.onSettingsChanged; this.onSettingsChanged = options?.onSettingsChanged;
this.afterSettingsSaved = options.afterSettingsSaved; this.afterSettingsSaved = options?.afterSettingsSaved;
this.active = options.activeSettings ?? undefined; this.active = options?.activeSettings ?? undefined;
this.default = ExtensionConf; this.default = ExtensionConf;
this.snapshotManager = new SettingsSnapshotManager();
this.default['version'] = this.getExtensionVersion(); this.default['version'] = this.getExtensionVersion();
chrome.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)}); chrome.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)});
@ -76,14 +57,14 @@ class Settings {
if (!changes.uwSettings) { if (!changes.uwSettings) {
return; 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) { // if (changes['uwSettings'] && changes['uwSettings'].newValue) {
// this.logger?.log('info', 'settings',"[Settings::<storage/on change>] new settings object:", JSON.parse(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); const parsedSettings = JSON.parse(changes.uwSettings.newValue);
this.setActive(parsedSettings); 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) { if (!parsedSettings.preventReload) {
try { try {
@ -91,23 +72,23 @@ class Settings {
try { try {
fn(); fn();
} catch (e) { } 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) { if (this.onSettingsChanged) {
this.onSettingsChanged(); this.onSettingsChanged();
} }
this.logger?.info('storageOnChange', 'Update callback finished.') this.logger?.log('info', 'settings', '[Settings] Update callback finished.')
} catch (e) { } 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) { for (const fn of this.afterSettingsChangedCallbacks) {
try { try {
fn(); fn();
} catch (e) { } 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) { if (this.afterSettingsSaved) {
@ -190,75 +171,78 @@ class Settings {
return patchesIn.sort( (a, b) => this.compareExtensionVersions(a.forVersion, b.forVersion)); return patchesIn.sort( (a, b) => this.compareExtensionVersions(a.forVersion, b.forVersion));
} }
private findFirstNecessaryPatch(version) { private findFirstNecessaryPatch(version, extconfPatches) {
return ExtensionConfPatch.findIndex(x => this.compareExtensionVersions(x.forVersion, version) > 0); const sorted = this.sortConfPatches(extconfPatches);
return sorted.findIndex(x => this.compareExtensionVersions(x.forVersion, version) > 0);
} }
private applySettingsPatches(oldVersion) { private applySettingsPatches(oldVersion, patches) {
let index = this.findFirstNecessaryPatch(oldVersion); let index = this.findFirstNecessaryPatch(oldVersion, patches);
if (index === -1) { 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; 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 // apply all remaining patches
this.logger?.info('applySettingsPatches', `There are ${ExtensionConfPatch.length - index} settings patches to apply`); try {
while (index < ExtensionConfPatch.length) { this.logger?.log('info', 'settings', `[Settings::applySettingsPatches] There are ${patches.length - index} settings patches to apply`);
const updateFn = ExtensionConfPatch[index].updateFn; while (index < patches.length) {
const updateFn = patches[index].updateFn;
delete patches[index].forVersion;
delete patches[index].updateFn;
if (Object.keys(patches[index]).length > 0) {
ObjectCopy.overwrite(this.active, patches[index]);
}
if (updateFn) { if (updateFn) {
try { try {
updateFn(this.active, this.getDefaultSettings()); updateFn(this.active, this.getDefaultSettings());
} catch (e) { } catch (e) {
this.logger?.error('applySettingsPatches', 'Failed to execute update function. Keeping settings object as-is. Error:', e); this.logger?.log('error', 'settings', '[Settings::applySettingsPatches] Failed to execute update function. Keeping settings object as-is. Error:', e);
} }
} }
index++; index++;
} }
} catch (e) {
console.error('Failed to upgrade settings.', e);
this.setActive(this.getDefaultSettings());
this.save();
}
} }
async init() { async init() {
let settings = await this.get(); const 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;
}
}
this.version = this.getExtensionVersion(); this.version = this.getExtensionVersion();
// |—> on first setup, settings is undefined & settings.version is haram // |—> on first setup, settings is undefined & settings.version is haram
// | since new installs ship with updates by default, no patching is // | since new installs ship with updates by default, no patching is
// | needed. In this case, we assume we're on the current version // | 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) { 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, "\nlast saved with:", settings.version,
"\ncurrent version:", this.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 there's no settings saved, return default settings.
if(! settings || (Object.keys(settings).length === 0 && settings.constructor === Object)) { if(! settings || (Object.keys(settings).length === 0 && settings.constructor === Object)) {
this.logger?.info( this.logger?.log(
'init', 'info',
'settings don\'t exist. Using defaults.\n#keys:', 'settings',
'[Settings::init] settings don\'t exist. Using defaults.\n#keys:',
settings ? Object.keys(settings).length : 0, settings ? Object.keys(settings).length : 0,
'\nsettings:', '\nsettings:',
settings settings
@ -266,7 +250,6 @@ class Settings {
this.active = this.getDefaultSettings(); this.active = this.getDefaultSettings();
this.active.version = this.version; this.active.version = this.version;
await this.save(); await this.save();
return this.active; return this.active;
} }
@ -283,7 +266,7 @@ class Settings {
// check if extension has been updated. If not, return settings as they were retrieved // check if extension has been updated. If not, return settings as they were retrieved
if (this.active.version === this.version) { 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; return this.active;
} }
@ -294,14 +277,14 @@ class Settings {
// if extension has been updated, update existing settings with any options added in the // 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. // new version. In addition to that, we remove old keys that are no longer used.
const patched = ObjectCopy.addNew(settings, this.default); 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) { if (patched) {
this.active = patched; this.active = patched;
} }
// in case settings in previous version contained a fucky wucky, we overwrite existing settings with a patch // 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 // set 'whatsNewChecked' flag to false when updating, always
this.active.whatsNewChecked = false; this.active.whatsNewChecked = false;
@ -312,25 +295,26 @@ class Settings {
return this.active; return this.active;
} }
async get(): Promise<SettingsInterface | undefined> { async get() {
let ret; let ret;
ret = await chrome.storage.local.get('uwSettings'); 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 { try {
return JSON.parse(ret.uwSettings) as SettingsInterface; return JSON.parse(ret.uwSettings);
} catch(e) { } catch(e) {
return undefined; return undefined;
} }
} }
async set(extensionConf, options?: SetSettingsOptions) { async set(extensionConf, options?) {
if (!options || !options.forcePreserveVersion) { if (!options || !options.forcePreserveVersion) {
extensionConf.version = this.version; 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)}); 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) { 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.preventReload = undefined;
this.active.lastModified = new Date();
await this.set(this.active, options); await this.set(this.active, options);
} }
async saveWithoutReload(options?: SetSettingsOptions) { async saveWithoutReload() {
this.active.preventReload = true; this.active.preventReload = true;
this.active.lastModified = new Date(); await this.set(this.active);
await this.set(this.active, options);
} }
async rollback() { async rollback() {
@ -420,15 +402,9 @@ class Settings {
listenOnChange(fn: () => void): void { listenOnChange(fn: () => void): void {
this.onChangedCallbacks.push(fn); this.onChangedCallbacks.push(fn);
} }
removeOnChangeListener(fn: () => void): void {
this.onChangedCallbacks = this.afterSettingsChangedCallbacks.filter(x => x !== fn);
}
listenAfterChange(fn: () => void): void { listenAfterChange(fn: () => void): void {
this.afterSettingsChangedCallbacks.push(fn); this.afterSettingsChangedCallbacks.push(fn);
} }
removeAfterChangeListener(fn: () => void): void {
this.afterSettingsChangedCallbacks = this.afterSettingsChangedCallbacks.filter(x => x !== fn);
}
} }
export default Settings; export default Settings;

View File

@ -1,24 +1,17 @@
import AspectRatioType from '@src/common/enums/AspectRatioType.enum'; import AspectRatioType from '../../../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 EventBus from '../EventBus'; import EventBus from '../EventBus';
import Logger from '../Logger';
import Settings from '../Settings'; import Settings from '../Settings';
import { SiteSettings } from '../settings/SiteSettings';
import VideoData from '../video-data/VideoData'; import VideoData from '../video-data/VideoData';
import { AardDebugUi } from './AardDebugUi';
import { AardTimer } from './AardTimers';
import { Corner } from './enums/corner.enum'; import { Corner } from './enums/corner.enum';
import { VideoPlaybackState } from './enums/video-playback-state.enum'; import { VideoPlaybackState } from './enums/video-playback-state.enum';
import { FallbackCanvas } from './gl/FallbackCanvas'; import { FallbackCanvas } from './gl/FallbackCanvas';
import { GlCanvas } from './gl/GlCanvas'; import { GlCanvas } from './gl/GlCanvas';
import { GlDebugCanvas, GlDebugType } from './gl/GlDebugCanvas';
import { AardCanvasStore } from './interfaces/aard-canvas-store.interface'; import { AardCanvasStore } from './interfaces/aard-canvas-store.interface';
import { AardDetectionSample, generateSampleArray, resetSamples } from './interfaces/aard-detection-sample.interface'; import { AardDetectionSample, generateSampleArray, resetSamples } from './interfaces/aard-detection-sample.interface';
import { AardStatus, initAardStatus } from './interfaces/aard-status.interface'; import { AardStatus, initAardStatus } from './interfaces/aard-status.interface';
import { AardTestResults, initAardTestResults, resetAardTestResults, resetGuardLine } from './interfaces/aard-test-results.interface'; import { AardTestResults, initAardTestResults, resetAardTestResults, resetGuardLine } from './interfaces/aard-test-results.interface';
import { AardTimers, initAardTimers } from './interfaces/aard-timers.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 { export class Aard {
//#region configuration parameters //#region configuration parameters
private logger: ComponentLogger; private logger: Logger;
private videoData: VideoData; private videoData: VideoData;
private settings: Settings; private settings: Settings;
private siteSettings: SiteSettings;
private eventBus: EventBus; private eventBus: EventBus;
private arid: string; private arid: string;
private arVariant: ArVariant;
private eventBusCommands = { 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': { // 'get-aard-timing': {
// function: () => this.handlePerformanceDataRequest() // function: () => this.handlePerformanceDataRequest()
// } // }
@ -262,15 +239,9 @@ export class Aard {
private fallbackReason: any; private fallbackReason: any;
private canvasStore: AardCanvasStore; private canvasStore: AardCanvasStore;
private testResults: AardTestResults; private testResults: AardTestResults;
private verticalTestResults: AardTestResults;
private canvasSamples: AardDetectionSample; private canvasSamples: AardDetectionSample;
private forceFullRecheck: boolean = true; private forceFullRecheck: boolean = true;
private debugConfig: any = {};
private timer: AardTimer;
private lastAnimationFrameTime: number = Infinity;
//#endregion //#endregion
//#region getters //#region getters
@ -292,11 +263,10 @@ export class Aard {
//#region lifecycle //#region lifecycle
constructor(videoData: VideoData){ constructor(videoData: VideoData){
this.logger = new ComponentLogger(videoData.logAggregator, 'Aard', {}); this.logger = videoData.logger;
this.videoData = videoData; this.videoData = videoData;
this.video = videoData.video; this.video = videoData.video;
this.settings = videoData.settings; this.settings = videoData.settings;
this.siteSettings = videoData.siteSettings;
this.eventBus = videoData.eventBus; this.eventBus = videoData.eventBus;
this.eventBus.subscribeMulti(this.eventBusCommands, this); this.eventBus.subscribeMulti(this.eventBusCommands, this);
@ -304,9 +274,8 @@ export class Aard {
this.arid = (Math.random()*100).toFixed(); this.arid = (Math.random()*100).toFixed();
// we can tick manually, for debugging // 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(); this.init();
} }
@ -315,6 +284,7 @@ export class Aard {
* This method should only ever be called from constructor. * This method should only ever be called from constructor.
*/ */
private init() { private init() {
this.canvasStore = { this.canvasStore = {
main: this.createCanvas('main-gl') main: this.createCanvas('main-gl')
}; };
@ -331,31 +301,16 @@ export class Aard {
), ),
}; };
this.start();
// 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(); private createCanvas(canvasId: string, canvasType?: 'webgl' | 'fallback') {
}
private createCanvas(canvasId: string, canvasType?: 'webgl' | 'legacy') {
if (canvasType) { if (canvasType) {
if (canvasType === this.settings.active.arDetect.aardType || this.settings.active.arDetect.aardType === 'auto') { if (canvasType === this.settings.active.arDetect.aardType || this.settings.active.arDetect.aardType === 'auto') {
if (canvasType === 'webgl') { if (canvasType === 'webgl') {
return new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'}); return new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'});
} else if (canvasType === 'legacy') { } else if (canvasType === 'fallback') {
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 { } else {
// TODO: throw error // TODO: throw error
} }
@ -370,66 +325,22 @@ export class Aard {
return new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'}); return new GlCanvas({...this.settings.active.arDetect.canvasDimensions.sampleCanvas, id: 'main-gl'});
} catch (e) { } catch (e) {
if (this.settings.active.arDetect.aardType !== 'webgl') { 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}}); this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: {webglError: true}});
throw e; throw e;
} }
} else if (this.settings.active.arDetect.aardType === 'legacy') { } 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 { } 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}}); this.eventBus.send('uw-config-broadcast', {type: 'aard-error', aardErrors: {invalidSettings: true}});
throw 'AARD_INVALID_SETTINGS'; 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 //#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. * Starts autodetection loop.
*/ */
@ -442,7 +353,6 @@ export class Aard {
// do full reset of test samples // do full reset of test samples
this.testResults = initAardTestResults(this.settings.active.arDetect); this.testResults = initAardTestResults(this.settings.active.arDetect);
this.verticalTestResults = initAardTestResults(this.settings.active.arDetect);
if (this.animationFrame) { if (this.animationFrame) {
window.cancelAnimationFrame(this.animationFrame); window.cancelAnimationFrame(this.animationFrame);
@ -456,14 +366,8 @@ export class Aard {
* Runs autodetection ONCE. * Runs autodetection ONCE.
* If autodetection loop is running, this will also stop autodetection loop. * If autodetection loop is running, this will also stop autodetection loop.
*/ */
step(options?: {noCache?: boolean}) { step() {
this.stop(); this.stop();
if (options?.noCache) {
this.testResults = initAardTestResults(this.settings.active.arDetect);
this.verticalTestResults = initAardTestResults(this.settings.active.arDetect);
}
this.main(); this.main();
} }
@ -525,20 +429,14 @@ export class Aard {
*/ */
private async main() { private async main() {
try { 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) // 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 // and have it, too (if we return early, we still execute code
// at the end of this function) // at the end of this function)
do { do {
imageData = await new Promise<Uint8Array>( const imageData = await new Promise<Uint8Array>(
resolve => { resolve => {
try { try {
this.canvasStore.main.drawVideoFrame(this.video); this.canvasStore.main.drawVideoFrame(this.video);
this.timer.current.draw = performance.now() - this.timer.current.start;
resolve(this.canvasStore.main.getImageData()); resolve(this.canvasStore.main.getImageData());
} catch (e) { } catch (e) {
if (e.name === 'SecurityError') { if (e.name === 'SecurityError') {
@ -556,7 +454,7 @@ export class Aard {
} else { } else {
if (this.settings.active.arDetect.aardType === 'auto') { if (this.settings.active.arDetect.aardType === 'auto') {
this.canvasStore.main.destroy(); this.canvasStore.main.destroy();
this.canvasStore.main = this.createCanvas('main-gl', 'legacy'); this.canvasStore.main = this.createCanvas('main-gl', 'fallback');
} }
this.inFallback = true; this.inFallback = true;
this.fallbackReason = {cors: true}; this.fallbackReason = {cors: true};
@ -568,7 +466,6 @@ export class Aard {
} }
} }
); );
this.timer.current.getImage = performance.now() - this.timer.current.start;
// STEP 1: // STEP 1:
// Test if corners are black. If they're not, we can immediately quit the loop. // 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.width,
this.settings.active.arDetect.canvasDimensions.sampleCanvas.height this.settings.active.arDetect.canvasDimensions.sampleCanvas.height
); );
this.timer.current.fastBlackLevel = performance.now() - this.timer.current.start;
if (this.testResults.notLetterbox) { if (this.testResults.notLetterbox) {
// TODO: reset aspect ratio to "AR not applied" // TODO: reset aspect ratio to "AR not applied"
this.testResults.lastStage = 1; 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 // Both need to be checked
if (! (this.testResults.imageLine.invalidated || this.testResults.guardLine.invalidated)) { if (! (this.testResults.imageLine.invalidated || this.testResults.guardLine.invalidated)) {
@ -651,17 +545,9 @@ export class Aard {
// If forceFullRecheck is set, then 'not letterbox' should always force-reset the aspect ratio // 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) // (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) { if (this.testResults.notLetterbox) {
// console.log('————not letterbox') // console.log('————not letterbox')
// console.warn('DETECTED NOT LETTERBOX! (resetting)')
this.timer.arChanged();
this.updateAspectRatio(this.defaultAr); 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 detection is uncertain, we don't do anything at all (unless if guardline was broken, in which case we reset)
@ -669,11 +555,11 @@ export class Aard {
// console.info('aspect ratio not certain:', this.testResults.aspectRatioUncertainReason); // 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('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)') if (this.testResults.guardLine.invalidated) {
this.timer.arChanged();
this.updateAspectRatio(this.defaultAr); this.updateAspectRatio(this.defaultAr);
}
break; return;
} }
// TODO: emit debug values if debugging is enabled // TODO: emit debug values if debugging is enabled
@ -689,30 +575,17 @@ export class Aard {
// except aspectRatioUpdated doesn't get set reliably, so we just call update every time, and update // 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 detected aspect ratio is different from the current aspect ratio
// if (this.testResults.aspectRatioUpdated) { // if (this.testResults.aspectRatioUpdated) {
// this.timer.arChanged(); this.updateAspectRatio();
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 // 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);
}
} catch (e) { } catch (e) {
console.warn('[Ultrawidify] Aspect ratio autodetection crashed for some reason.\n\nsome reason:', 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 { private getVideoPlaybackState(): VideoPlaybackState {
try { try {
if (this.video.ended) { if (this.video.ended) {
@ -725,7 +598,7 @@ export class Aard {
return VideoPlaybackState.Playing; return VideoPlaybackState.Playing;
} }
} catch (e) { } 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; return VideoPlaybackState.Error;
} }
} }
@ -785,13 +658,11 @@ export class Aard {
pixelValues[pvi++] = imageData[px_r]; pixelValues[pvi++] = imageData[px_r];
pixelValues[pvi++] = imageData[px_r + 1]; pixelValues[pvi++] = imageData[px_r + 1];
pixelValues[pvi++] = imageData[px_r + 2]; 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 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];
pixelValues[pvi++] = imageData[endpx_r + 1]; pixelValues[pvi++] = imageData[endpx_r + 1];
pixelValues[pvi++] = imageData[endpx_r + 2]; pixelValues[pvi++] = imageData[endpx_r + 2];
imageData[endpx_r + 3] = GlDebugType.BlackLevelSample;
} }
// now let's populate the bottom two corners // 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];
pixelValues[pvi++] = imageData[px_r + 1]; pixelValues[pvi++] = imageData[px_r + 1];
pixelValues[pvi++] = imageData[px_r + 2]; 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 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];
pixelValues[pvi++] = imageData[endpx_r + 1]; pixelValues[pvi++] = imageData[endpx_r + 1];
pixelValues[pvi++] = imageData[endpx_r + 2]; pixelValues[pvi++] = imageData[endpx_r + 2];
imageData[endpx_r + 3] = GlDebugType.BlackLevelSample;
} }
let min = 255; let min = 255;
@ -903,10 +772,7 @@ export class Aard {
|| imageData[i + 1] > this.testResults.blackThreshold || imageData[i + 1] > this.testResults.blackThreshold
|| imageData[i + 2] > this.testResults.blackThreshold || imageData[i + 2] > this.testResults.blackThreshold
) { ) {
imageData[i + 3] = GlDebugType.GuardLineCornerViolation;
this.testResults.guardLine.cornerPixelsViolated[Corner.TopLeft]++; this.testResults.guardLine.cornerPixelsViolated[Corner.TopLeft]++;
} else {
imageData[i + 3] = GlDebugType.GuardLineCornerOk;
} }
i += 4; i += 4;
} }
@ -916,15 +782,12 @@ export class Aard {
|| imageData[i + 1] > this.testResults.blackThreshold || imageData[i + 1] > this.testResults.blackThreshold
|| imageData[i + 2] > this.testResults.blackThreshold || imageData[i + 2] > this.testResults.blackThreshold
) { ) {
imageData[i + 3] = GlDebugType.GuardLineViolation;
// DONT FORGET TO INVALIDATE GUARDL LINE // DONT FORGET TO INVALIDATE GUARDL LINE
this.testResults.guardLine.top = -1; this.testResults.guardLine.top = -1;
this.testResults.guardLine.bottom = -1; this.testResults.guardLine.bottom = -1;
this.testResults.guardLine.invalidated = true; this.testResults.guardLine.invalidated = true;
return; return;
} else { };
imageData[i + 3] = GlDebugType.GuardLineOk;
}
i += 4; i += 4;
} }
while (i < rowEnd) { while (i < rowEnd) {
@ -933,10 +796,7 @@ export class Aard {
|| imageData[i + 1] > this.testResults.blackThreshold || imageData[i + 1] > this.testResults.blackThreshold
|| imageData[i + 2] > this.testResults.blackThreshold || imageData[i + 2] > this.testResults.blackThreshold
) { ) {
imageData[i + 3] = GlDebugType.GuardLineCornerViolation;
this.testResults.guardLine.cornerPixelsViolated[Corner.TopRight]++; this.testResults.guardLine.cornerPixelsViolated[Corner.TopRight]++;
} else {
imageData[i + 3] = GlDebugType.GuardLineCornerOk;
} }
i += 4; // skip over alpha channel i += 4; // skip over alpha channel
} }
@ -958,10 +818,7 @@ export class Aard {
|| imageData[i + 1] > this.testResults.blackThreshold || imageData[i + 1] > this.testResults.blackThreshold
|| imageData[i + 2] > this.testResults.blackThreshold || imageData[i + 2] > this.testResults.blackThreshold
) { ) {
imageData[i + 3] = GlDebugType.GuardLineCornerViolation;
this.testResults.guardLine.cornerPixelsViolated[Corner.BottomLeft]++; this.testResults.guardLine.cornerPixelsViolated[Corner.BottomLeft]++;
} else {
imageData[i + 3] = GlDebugType.GuardLineCornerOk;
} }
i += 4; // skip over alpha channel i += 4; // skip over alpha channel
} }
@ -974,15 +831,12 @@ export class Aard {
|| imageData[i + 1] > this.testResults.blackThreshold || imageData[i + 1] > this.testResults.blackThreshold
|| imageData[i + 2] > this.testResults.blackThreshold || imageData[i + 2] > this.testResults.blackThreshold
) { ) {
imageData[i + 3] = GlDebugType.GuardLineViolation;
// DONT FORGET TO INVALIDATE GUARDL LINE // DONT FORGET TO INVALIDATE GUARDL LINE
this.testResults.guardLine.top = -1; this.testResults.guardLine.top = -1;
this.testResults.guardLine.bottom = -1; this.testResults.guardLine.bottom = -1;
this.testResults.guardLine.invalidated = true; this.testResults.guardLine.invalidated = true;
return; return;
} else { };
imageData[i + 3] = GlDebugType.GuardLineOk;
}
i += 4; i += 4;
} }
if (i % 4) { if (i % 4) {
@ -994,10 +848,7 @@ export class Aard {
|| imageData[i + 1] > this.testResults.blackThreshold || imageData[i + 1] > this.testResults.blackThreshold
|| imageData[i + 2] > this.testResults.blackThreshold || imageData[i + 2] > this.testResults.blackThreshold
) { ) {
imageData[i + 3] = GlDebugType.GuardLineCornerViolation;
this.testResults.guardLine.cornerPixelsViolated[Corner.BottomRight]++; this.testResults.guardLine.cornerPixelsViolated[Corner.BottomRight]++;
} else {
imageData[i + 3] = GlDebugType.GuardLineCornerOk;
} }
i += 4; // skip over alpha channel i += 4; // skip over alpha channel
} }
@ -1078,60 +929,8 @@ export class Aard {
imagePixel ||= imageData[i++] > this.testResults.blackThreshold; imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
if (imagePixel && ++pixelCount > detectionThreshold) { if (imagePixel && ++pixelCount > detectionThreshold) {
imageData[i] = GlDebugType.ImageLineThresholdReached;
return; 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 i++; // skip over alpha channel
} }
} }
@ -1154,11 +953,48 @@ export class Aard {
imagePixel ||= imageData[i++] > this.testResults.blackThreshold; imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
if (imagePixel && ++pixelCount > detectionThreshold) { if (imagePixel && ++pixelCount > detectionThreshold) {
imageData[i] = GlDebugType.ImageLineThresholdReached;
return; 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 i++; // skip over alpha channel
} }
} }
@ -1183,11 +1019,8 @@ export class Aard {
imagePixel ||= imageData[i++] > this.testResults.blackThreshold; imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
if (imagePixel && ++pixelCount > detectionThreshold) { if (imagePixel && ++pixelCount > detectionThreshold) {
imageData[i] = GlDebugType.ImageLineThresholdReached;
return; return;
} else { };
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
}
i++; // skip over alpha channel i++; // skip over alpha channel
} }
} }
@ -1198,11 +1031,8 @@ export class Aard {
imagePixel ||= imageData[i++] > this.testResults.blackThreshold; imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
if (imagePixel && ++pixelCount > detectionThreshold) { if (imagePixel && ++pixelCount > detectionThreshold) {
imageData[i] = GlDebugType.ImageLineThresholdReached;
return; return;
} else { };
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
}
i++; // skip over alpha channel i++; // skip over alpha channel
} }
if (! this.testResults.guardLine.cornerViolated[Corner.TopRight]) { if (! this.testResults.guardLine.cornerViolated[Corner.TopRight]) {
@ -1213,11 +1043,8 @@ export class Aard {
imagePixel ||= imageData[i++] > this.testResults.blackThreshold; imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
if (imagePixel && ++pixelCount > detectionThreshold) { if (imagePixel && ++pixelCount > detectionThreshold) {
imageData[i] = GlDebugType.ImageLineThresholdReached;
return; return;
} else { };
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
}
i++; // skip over alpha channel i++; // skip over alpha channel
} }
} }
@ -1232,11 +1059,8 @@ export class Aard {
imagePixel ||= imageData[i++] > this.testResults.blackThreshold; imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
if (imagePixel && ++pixelCount > detectionThreshold) { if (imagePixel && ++pixelCount > detectionThreshold) {
imageData[i] = GlDebugType.ImageLineThresholdReached;
return; return;
} else { };
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
}
i++; // skip over alpha channel i++; // skip over alpha channel
} }
} }
@ -1247,11 +1071,8 @@ export class Aard {
imagePixel ||= imageData[i++] > this.testResults.blackThreshold; imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
if (imagePixel && ++pixelCount > detectionThreshold) { if (imagePixel && ++pixelCount > detectionThreshold) {
imageData[i] = GlDebugType.ImageLineThresholdReached;
return; return;
} else { };
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
}
i++; // skip over alpha channel i++; // skip over alpha channel
} }
if (! this.testResults.guardLine.cornerViolated[Corner.TopRight]) { if (! this.testResults.guardLine.cornerViolated[Corner.TopRight]) {
@ -1262,11 +1083,8 @@ export class Aard {
imagePixel ||= imageData[i++] > this.testResults.blackThreshold; imagePixel ||= imageData[i++] > this.testResults.blackThreshold;
if (imagePixel && ++pixelCount > detectionThreshold) { if (imagePixel && ++pixelCount > detectionThreshold) {
imageData[i] = GlDebugType.ImageLineThresholdReached;
return; return;
} else { };
imageData[i] = imagePixel ? GlDebugType.ImageLineOk : GlDebugType.ImageLineFail;
}
i++; // skip over alpha channel i++; // skip over alpha channel
} }
} }
@ -1308,14 +1126,12 @@ export class Aard {
// bit more nicely visible (instead of hidden among spagheti) // bit more nicely visible (instead of hidden among spagheti)
this.edgeScan(imageData, width, height); this.edgeScan(imageData, width, height);
this.validateEdgeScan(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: // TODO: _if gradient detection is enabled, then:
this.sampleForGradient(imageData, width, height); this.sampleForGradient(imageData, width, height);
this.timer.current.gradient = performance.now() - this.timer.current.start;
this.processScanResults(imageData, width, height); 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; || imageData[rowOffset + x + 2] > this.testResults.blackLevel;
if (!isImage) { if (!isImage) {
imageData[rowOffset + x + 3] = GlDebugType.EdgeScanProbe;
// TODO: maybe some day mark this pixel as checked by writing to alpha channel // TODO: maybe some day mark this pixel as checked by writing to alpha channel
i++; i++;
continue; continue;
} }
if (this.canvasSamples.top[i] === -1) { if (this.canvasSamples.top[i] === -1) {
imageData[rowOffset + x + 3] = GlDebugType.EdgeScanHit;
this.canvasSamples.top[i] = row; this.canvasSamples.top[i] = row;
finishedRows++; finishedRows++;
} }
@ -1432,14 +1246,12 @@ export class Aard {
|| imageData[rowOffset + x + 2] > this.testResults.blackLevel; || imageData[rowOffset + x + 2] > this.testResults.blackLevel;
if (!isImage) { 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) // 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 // TODO: maybe some day mark this pixel as checked by writing to alpha channel
i++; i++;
continue; continue;
} }
if (this.canvasSamples.bottom[i] === -1) { if (this.canvasSamples.bottom[i] === -1) {
imageData[rowOffset + x + 3] = GlDebugType.EdgeScanHit;
this.canvasSamples.bottom[i] = row; this.canvasSamples.bottom[i] = row;
finishedRows++; finishedRows++;
} }
@ -1479,9 +1291,6 @@ export class Aard {
const slopeTestSample = this.settings.active.arDetect.edgeDetection.slopeTestWidth * 4; const slopeTestSample = this.settings.active.arDetect.edgeDetection.slopeTestWidth * 4;
while (i < this.canvasSamples.top.length) { while (i < this.canvasSamples.top.length) {
// if (this.canvasSamples.top[i] < 0) {
// continue;
// }
// calculate row offset: // calculate row offset:
row = (this.canvasSamples.top[i + 1] - 1) * width * 4; row = (this.canvasSamples.top[i + 1] - 1) * width * 4;
xs = row + this.canvasSamples.top[i] - slopeTestSample; xs = row + this.canvasSamples.top[i] - slopeTestSample;
@ -1493,11 +1302,8 @@ export class Aard {
|| imageData[xs + 1] > this.testResults.blackThreshold || imageData[xs + 1] > this.testResults.blackThreshold
|| imageData[xs + 2] > this.testResults.blackThreshold || imageData[xs + 2] > this.testResults.blackThreshold
) { ) {
imageData[xs + 3] = GlDebugType.SlopeTestDarkViolation;
this.canvasSamples.top[i + 1] = -1; this.canvasSamples.top[i + 1] = -1;
break; break;
} else {
imageData[xs + 3] = GlDebugType.SlopeTestDarkOk;
} }
xs += 4; xs += 4;
} }
@ -1507,13 +1313,9 @@ export class Aard {
i = 0; i = 0;
let i1 = 0; let i1 = 0;
while (i < this.canvasSamples.bottom.length) { while (i < this.canvasSamples.bottom.length) {
// if (this.canvasSamples.bottom[i] < 0) {
// continue;
// }
// calculate row offset: // calculate row offset:
i1 = i + 1; 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; xs = row + this.canvasSamples.bottom[i] - slopeTestSample;
xe = 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 + 1] > this.testResults.blackThreshold
|| imageData[xs + 2] > this.testResults.blackThreshold || imageData[xs + 2] > this.testResults.blackThreshold
) { ) {
imageData[xs + 3] = GlDebugType.SlopeTestDarkViolation;
this.canvasSamples.bottom[i1] = -1; this.canvasSamples.bottom[i1] = -1;
i += 2; i += 2;
break; break;
} }
imageData[xs + 3] = GlDebugType.SlopeTestDarkOk;
xs += 4; xs += 4;
} }
if (this.canvasSamples.bottom[i1]) { if (this.canvasSamples.bottom[i1]) {
this.canvasSamples.bottom[i1] = this.canvasSamples.bottom[i1]; this.canvasSamples.bottom[i1] = height - this.canvasSamples.bottom[i1];
} }
i += 2; i += 2;
@ -1557,10 +1357,6 @@ export class Aard {
upperEdgeCheck: upperEdgeCheck:
for (let i = 1; i < this.canvasSamples.top.length; i += 2) { 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; pixelOffset = this.canvasSamples.top[i] * realWidth + this.canvasSamples.top[i - 1] * 4;
lastSubpixel = imageData[pixelOffset] > imageData[pixelOffset + 1] ? imageData[pixelOffset] : imageData[pixelOffset + 1]; lastSubpixel = imageData[pixelOffset] > imageData[pixelOffset + 1] ? imageData[pixelOffset] : imageData[pixelOffset + 1];
@ -1603,9 +1399,6 @@ export class Aard {
lowerEdgeCheck: lowerEdgeCheck:
for (let i = 1; i < this.canvasSamples.bottom.length; i += 2) { 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; pixelOffset = (height - this.canvasSamples.bottom[i]) * realWidth + this.canvasSamples.bottom[i - 1] * 4;
lastSubpixel = imageData[pixelOffset] > imageData[pixelOffset + 1] ? imageData[pixelOffset] : imageData[pixelOffset + 1]; 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) { if (lastSubpixel - firstSubpixel > this.settings.active.arDetect.edgeDetection.gradientTestMinDelta) {
this.canvasSamples.bottom[i] = -1; this.canvasSamples.bottom[i] = -1;
} }
} }
} }
@ -2000,22 +1794,11 @@ export class Aard {
return; return;
} }
if (maxOffset > 2) { if (maxOffset > 2) {
if (this.testResults.aspectRatioCheck.topCandidate === Infinity) { this.testResults.imageLine.top = this.testResults.aspectRatioCheck.topCandidate === Infinity ? -1 : this.testResults.aspectRatioCheck.topCandidate;
this.testResults.imageLine.top = -1; this.testResults.imageLine.bottom = this.testResults.aspectRatioCheck.bottomCandidate === Infinity ? -1 : this.testResults.aspectRatioCheck.bottomCandidate;
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); 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.guardLine.bottom = Math.min(this.testResults.imageLine.bottom + 2, this.canvasStore.main.height - 1);
} }
}
this.testResults.aspectRatioUncertain = false; this.testResults.aspectRatioUncertain = false;
@ -2038,8 +1821,7 @@ export class Aard {
this.videoData.resizer.updateAr({ this.videoData.resizer.updateAr({
type: AspectRatioType.AutomaticUpdate, type: AspectRatioType.AutomaticUpdate,
ratio: this.getAr(), ratio: this.getAr(),
offset: this.testResults.letterboxOffset, offset: this.testResults.letterboxOffset
variant: this.arVariant
}); });
this.testResults.activeAspectRatio = ar; this.testResults.activeAspectRatio = ar;
} }
@ -2053,7 +1835,7 @@ export class Aard {
const fileAr = this.video.videoWidth / this.video.videoHeight; const fileAr = this.video.videoWidth / this.video.videoHeight;
const canvasAr = this.canvasStore.main.width / this.canvasStore.main.height; 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(` // console.log(`
// ———— ASPECT RATIO CALCULATION: ————— // ———— ASPECT RATIO CALCULATION: —————

View File

@ -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' : '';
}
}

View File

@ -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;
}
}

View File

@ -2,9 +2,7 @@ import { GlCanvas, GlCanvasOptions } from './GlCanvas';
export class FallbackCanvas extends GlCanvas { export class FallbackCanvas extends GlCanvas {
get type() {
return 'legacy';
}
context: CanvasRenderingContext2D; context: CanvasRenderingContext2D;
constructor(options: GlCanvasOptions) { constructor(options: GlCanvasOptions) {
@ -26,7 +24,7 @@ export class FallbackCanvas extends GlCanvas {
protected initWebgl() { } protected initWebgl() { }
drawVideoFrame(video: HTMLVideoElement) { 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() { getImageData() {

View File

@ -52,9 +52,6 @@ interface GlCanvasProgramInfo {
} }
export class GlCanvas { export class GlCanvas {
get type() {
return 'webgl';
}
private _canvas: HTMLCanvasElement; private _canvas: HTMLCanvasElement;
private set canvas(x: HTMLCanvasElement) { private set canvas(x: HTMLCanvasElement) {
@ -68,7 +65,7 @@ export class GlCanvas {
private set gl(x: WebGLRenderingContext) { private set gl(x: WebGLRenderingContext) {
this._context = x; this._context = x;
}; };
protected get gl(): WebGLRenderingContext { private get gl(): WebGLRenderingContext {
return this._context; return this._context;
} }
@ -83,7 +80,7 @@ export class GlCanvas {
private buffers: GlCanvasBuffers; private buffers: GlCanvasBuffers;
private texture: WebGLTexture; private texture: WebGLTexture;
protected programInfo: GlCanvasProgramInfo; private programInfo: GlCanvasProgramInfo;
private projectionMatrix: mat4; private projectionMatrix: mat4;
get width() { get width() {
@ -106,7 +103,7 @@ export class GlCanvas {
* Draws video frame to the GL canvas * Draws video frame to the GL canvas
* @param video video to extract a frame from * @param video video to extract a frame from
*/ */
drawVideoFrame(video: HTMLVideoElement | HTMLCanvasElement): void { drawVideoFrame(video: HTMLVideoElement): void {
this.updateTexture(video); this.updateTexture(video);
this.drawScene(); this.drawScene();
} }
@ -159,17 +156,8 @@ export class GlCanvas {
); );
if (!this.gl) { 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) { if(options.id) {
this.canvas.setAttribute('id', options.id); this.canvas.setAttribute('id', options.id);
} }
@ -224,30 +212,24 @@ export class GlCanvas {
this.frameBuffer = new Uint8Array(this.frameBufferSize); this.frameBuffer = new Uint8Array(this.frameBufferSize);
} }
protected loadShader(type, source) { private loadShader(type, source) {
const shader = this.gl.createShader(type); const shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source); this.gl.shaderSource(shader, source);
this.gl.compileShader(shader); this.gl.compileShader(shader);
// TODO: warn if shader failed to compile // TODO: warn if shader failed to compile
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { 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 null;
} }
return shader; return shader;
} }
protected loadShaders() { private initShaderProgram() {
const vertexShader = this.loadShader(this.gl.VERTEX_SHADER, vsSource); const vertexShader = this.loadShader(this.gl.VERTEX_SHADER, vsSource);
const fragmentShader = this.loadShader(this.gl.FRAGMENT_SHADER, fsSource); const fragmentShader = this.loadShader(this.gl.FRAGMENT_SHADER, fsSource);
return {vertexShader, fragmentShader};
}
private initShaderProgram() {
const {vertexShader, fragmentShader} = this.loadShaders();
// Create the shader program // Create the shader program
const shaderProgram = this.gl.createProgram(); const shaderProgram = this.gl.createProgram();
this.gl.attachShader(shaderProgram, vertexShader); this.gl.attachShader(shaderProgram, vertexShader);
@ -256,7 +238,6 @@ export class GlCanvas {
// TODO: maybe give a warning if program failed to initialize // TODO: maybe give a warning if program failed to initialize
if (!this.gl.getProgramParameter(shaderProgram, this.gl.LINK_STATUS)) { if (!this.gl.getProgramParameter(shaderProgram, this.gl.LINK_STATUS)) {
console.warn('DEBUG — FAILED TO LINK SHADER PROGRAM', this.gl.getProgramInfoLog(shaderProgram))
return null; return null;
} }
@ -298,7 +279,7 @@ export class GlCanvas {
this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR); 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 level = 0;
const internalFormat = this.gl.RGBA; const internalFormat = this.gl.RGBA;
const srcFormat = this.gl.RGBA; const srcFormat = this.gl.RGBA;
@ -352,7 +333,7 @@ export class GlCanvas {
this.gl.enableVertexAttribArray(this.programInfo.attribLocations.vertexPosition); 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 * 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 * always covered by rendered video, and given our video is the only object

View File

@ -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();
};
}

View File

@ -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;
}

View File

@ -1,7 +1,5 @@
import { GlCanvas } from '../gl/GlCanvas'; import { GlCanvas } from '../gl/GlCanvas';
import { GlDebugCanvas } from '../gl/GlDebugCanvas';
export interface AardCanvasStore { export interface AardCanvasStore {
main: GlCanvas; main: GlCanvas;
debug?: GlDebugCanvas;
} }

View File

@ -39,9 +39,7 @@ export interface AardTestResults {
letterboxWidth: number, letterboxWidth: number,
letterboxOffset: number, letterboxOffset: number,
logoDetected: [boolean, boolean, boolean, boolean] logoDetected: [boolean, boolean, boolean, boolean]
aspectRatioInvalid: boolean
aspectRatioUncertainReason?: string aspectRatioUncertainReason?: string
aspectRatioInvalidReason?: string
} }
export function initAardTestResults(settings: AardSettings): AardTestResults { export function initAardTestResults(settings: AardSettings): AardTestResults {
@ -83,8 +81,7 @@ export function initAardTestResults(settings: AardSettings): AardTestResults {
activeAspectRatio: 0, activeAspectRatio: 0,
letterboxWidth: 0, letterboxWidth: 0,
letterboxOffset: 0, letterboxOffset: 0,
logoDetected: [false, false, false, false], logoDetected: [false, false, false, false]
aspectRatioInvalid: false,
} }
} }
@ -123,5 +120,4 @@ export function resetAardTestResults(results: AardTestResults): void {
results.aspectRatioUncertainReason = null; results.aspectRatioUncertainReason = null;
results.topRowUncertain = false; results.topRowUncertain = false;
results.bottomRowUncertain = false; results.bottomRowUncertain = false;
results.aspectRatioInvalid = false;
} }

View File

@ -1,7 +1,8 @@
import Debug from '../../conf/Debug';
import BrowserDetect from '../../conf/BrowserDetect'; import BrowserDetect from '../../conf/BrowserDetect';
import Logger from '../Logger';
import Settings from '../Settings';
import EventBus, { EventBusContext } from '../EventBus'; import EventBus, { EventBusContext } from '../EventBus';
import { ComponentLogger } from '../logging/ComponentLogger';
import { LogAggregator } from '../logging/LogAggregator';
if (process.env.CHANNEL !== 'stable'){ if (process.env.CHANNEL !== 'stable'){
console.info("Loading CommsClient"); console.info("Loading CommsClient");
@ -72,7 +73,7 @@ class CommsClient {
name: string; name: string;
origin: CommsOrigin; origin: CommsOrigin;
logger: ComponentLogger; logger: Logger;
settings: any; // sus? settings: any; // sus?
eventBus: EventBus; eventBus: EventBus;
@ -81,10 +82,10 @@ class CommsClient {
port: chrome.runtime.Port; port: chrome.runtime.Port;
//#region lifecycle //#region lifecycle
constructor(name: string, logAggregator: LogAggregator, eventBus: EventBus) { constructor(name: string, logger: Logger, eventBus: EventBus) {
this.name = name; this.name = name;
try { try {
this.logger = new ComponentLogger(logAggregator, 'CommsClient', {}); this.logger = logger;
this.eventBus = eventBus; this.eventBus = eventBus;
if (name === 'popup-port') { if (name === 'popup-port') {
@ -100,16 +101,16 @@ class CommsClient {
this.port = chrome.runtime.connect(null, {name: name}); this.port = chrome.runtime.connect(null, {name: name});
// } // }
// this.logger.onLogEnd( this.logger.onLogEnd(
// (history) => { (history) => {
// this.logger.log('info', 'comms', 'Sending logging-stop-and-save to background script ...'); this.logger.log('info', 'comms', 'Sending logging-stop-and-save to background script ...');
// try { try {
// this.port.postMessage({cmd: 'logging-stop-and-save', host: window.location.hostname, history}) this.port.postMessage({cmd: 'logging-stop-and-save', host: window.location.hostname, history})
// } catch (e) { } catch (e) {
// this.logger.log('error', 'comms', 'Failed to send message to background script. Error:', e); this.logger.log('error', 'comms', 'Failed to send message to background script. Error:', e);
// } }
// } }
// ); );
this._listener = m => this.processReceivedMessage(m); this._listener = m => this.processReceivedMessage(m);
this.port.onMessage.addListener(this._listener); this.port.onMessage.addListener(this._listener);
@ -117,7 +118,7 @@ class CommsClient {
this.commsId = (Math.random() * 20).toFixed(0); this.commsId = (Math.random() * 20).toFixed(0);
} catch (e) { } catch (e) {
console.error("CONSTRUCTOR FAILED:", e) console.error("CONSTRUCOTR FAILED:", e)
} }
} }
@ -128,9 +129,7 @@ class CommsClient {
} }
//#endregion //#endregion
async sendMessage(message, context?: EventBusContext, borderCrossings?){ async sendMessage(message, context?: EventBusContext){
this.logger.info('sendMessage', ' <<< Sending message to background script:', message);
message = JSON.parse(JSON.stringify(message)); // vue quirk. We should really use vue store instead 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 // content script client and popup client differ in this one thing
@ -144,12 +143,9 @@ class CommsClient {
return port.postMessage(message); return port.postMessage(message);
} }
} }
// send to server // send to server
if (!context?.borderCrossings?.commsServer) {
return chrome.runtime.sendMessage(null, message, null); return chrome.runtime.sendMessage(null, message, null);
} }
}
/** /**
* Processes message we received from CommsServer, and forwards it to eventBus. * Processes message we received from CommsServer, and forwards it to eventBus.
@ -173,10 +169,7 @@ class CommsClient {
message.config, message.config,
{ {
comms, comms,
origin: CommsOrigin.Server, origin: CommsOrigin.Server
borderCrossings: {
commsServer: true
}
} }
); );
} }

View File

@ -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 Settings from '../Settings';
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
import EventBus from '../EventBus'; import EventBus from '../EventBus';
import { CommsOrigin } from './CommsClient'; import { CommsOrigin } from './CommsClient';
const BASE_LOGGING_STYLES = {
log: "background-color: #11D; color: #aad",
};
class CommsServer { class CommsServer {
server: any; server: any;
logger: ComponentLogger; logger: Logger;
settings: Settings; settings: Settings;
eventBus: EventBus; eventBus: EventBus;
@ -52,33 +52,16 @@ class CommsServer {
} = {}; } = {};
popupPort: any; popupPort: any;
private _lastActiveTab: chrome.tabs.Tab | undefined;
//#region getters //#region getters
get activeTab(): Promise<chrome.tabs.Tab | undefined> { get activeTab() {
return new Promise((resolve, reject) => { return chrome.tabs.query({currentWindow: true, active: true});
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);
});
});
} }
//#endregion //#endregion
//#region lifecycle //#region lifecycle
constructor(server) { constructor(server) {
this.server = server; this.server = server;
this.logger = new ComponentLogger(server.logAggregator, 'CommsServer', {styles: BASE_LOGGING_STYLES}); this.logger = server.logger;
this.settings = server.settings; this.settings = server.settings;
this.eventBus = server.eventBus; this.eventBus = server.eventBus;
@ -122,45 +105,10 @@ class CommsServer {
//#endregion //#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?) { 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 // stop messages from returning where they came from, and prevent
// cross-pollination between content scripts running in different // cross-pollination between content scripts running in different
// tabs. // 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?.origin !== CommsOrigin.ContentScript) {
if (context?.comms.forwardTo === 'all') { if (context?.comms.forwardTo === 'all') {
@ -181,16 +129,11 @@ class CommsServer {
// okay I lied! Messages originating from content script can be forwarded to // okay I lied! Messages originating from content script can be forwarded to
// content scripts running in _other_ frames of the tab // content scripts running in _other_ frames of the tab
let forwarded = false;
if (context?.origin === CommsOrigin.ContentScript) { if (context?.origin === CommsOrigin.ContentScript) {
if (context?.comms.forwardTo === 'all-frames') { if (context?.comms.forwardTo === 'all-frames') {
forwarded = true;
this.sendToOtherFrames(message, context); 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. * Does NOT send a message to popup.
**/ **/
private sendToAll(message){ private sendToAll(message){
this.logger.info('sendToAll', "sending message to all content scripts", message);
for(const tid in this.ports){ for(const tid in this.ports){
const tab = this.ports[tid]; const tab = this.ports[tid];
for(const frame in tab){ for(const frame in tab){
for (const port in tab[frame]) { 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); tab[frame][port].postMessage(message);
} }
} }
@ -228,11 +168,9 @@ class CommsServer {
private async sendToFrameContentScripts(message, tab, frame, port?) { private async sendToFrameContentScripts(message, tab, frame, port?) {
if (port !== undefined) { if (port !== undefined) {
this.ports[tab][frame][port].postMessage(message); 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; return;
} }
for (const framePort in this.ports[tab][frame]) { 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))); this.ports[tab][frame][framePort].postMessage(JSON.parse(JSON.stringify(message)));
} }
} }
@ -260,7 +198,7 @@ class CommsServer {
} }
private async sendToFrame(message, tab, frame, port?) { 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 (isNaN(tab)) {
if (frame === '__playing') { if (frame === '__playing') {
@ -274,32 +212,33 @@ class CommsServer {
[tab, frame] = frame.split('-'); [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 { try {
this.sendToFrameContentScripts(message, tab, frame, port); this.sendToFrameContentScripts(message, tab, frame, port);
} catch (e) { } 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) { 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]) { for (const frame in this.ports[tabs[0].id]) {
this.logger.info('sendToActive', "sending message to frame:", frame, this.ports[tab.id][frame], '; message:', message); this.sendToFrameContentScripts(message, tabs[0].id, frame);
this.sendToFrameContentScripts(message, tab.id, frame);
} }
} }
private async processReceivedMessage(message, port, sender?: {frameId: string, tabId: string}){ 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 triggers events
this.eventBus.send( this.eventBus.send(
message.command, message.command,
@ -320,7 +259,7 @@ class CommsServer {
} }
private processReceivedMessage_nonpersistent(message, sender){ 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( this.eventBus.send(
message.command, message.command,

View File

@ -1,11 +1,11 @@
import EventBus, { EventBusCommand } from '../EventBus'; import EventBus, { EventBusCommand } from '../EventBus';
import { ComponentLogger } from '../logging/ComponentLogger'; import Logger from '../Logger';
import Settings from '../Settings'; import Settings from '../Settings';
import { SiteSettings } from '../settings/SiteSettings'; import { SiteSettings } from '../settings/SiteSettings';
export class KbmBase { export class KbmBase {
listenFor: string[] = []; listenFor: string[] = [];
logger: ComponentLogger; logger: Logger;
settings: Settings; settings: Settings;
siteSettings: SiteSettings; siteSettings: SiteSettings;
eventBus: EventBus; 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.logger = logger;
this.settings = settings; this.settings = settings;
this.eventBus = eventBus; this.eventBus = eventBus;

View File

@ -1,19 +1,18 @@
import Debug from '../../conf/Debug';
import PlayerData from '../video-data/PlayerData';
import ExtensionMode from '../../../common/enums/ExtensionMode.enum'; import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
import Logger from '../Logger';
import PageInfo from '../video-data/PageInfo';
import Settings from '../Settings'; import Settings from '../Settings';
import VideoData from '../video-data/VideoData';
import EventBus, { EventBusCommand } from '../EventBus'; import EventBus, { EventBusCommand } from '../EventBus';
import KbmBase from './KbmBase'; import KbmBase from './KbmBase';
import { SiteSettings } from '../settings/SiteSettings'; import { SiteSettings } from '../settings/SiteSettings';
import { LogAggregator } from '../logging/LogAggregator';
import { ComponentLogger } from '../logging/ComponentLogger';
if(process.env.CHANNEL !== 'stable'){ if(process.env.CHANNEL !== 'stable'){
console.info("Loading KeyboardHandler"); console.info("Loading KeyboardHandler");
} }
const BASE_LOGGING_STYLES = {
log: "color: #ff0"
};
/** /**
* Handles keypresses and mouse movement. * Handles keypresses and mouse movement.
* *
@ -24,6 +23,7 @@ const BASE_LOGGING_STYLES = {
*/ */
export class KeyboardHandler extends KbmBase { export class KeyboardHandler extends KbmBase {
listenFor: string[] = ['keyup']; listenFor: string[] = ['keyup'];
logger: Logger;
settings: Settings; settings: Settings;
siteSettings: SiteSettings; siteSettings: SiteSettings;
eventBus: EventBus; eventBus: EventBus;
@ -45,18 +45,14 @@ export class KeyboardHandler extends KbmBase {
} }
//#region lifecycle //#region lifecycle
constructor(eventBus: EventBus, siteSettings: SiteSettings, settings: Settings, logAggregator: LogAggregator) { constructor(eventBus: EventBus, siteSettings: SiteSettings, settings: Settings, logger: Logger) {
const tmpLogger = new ComponentLogger(logAggregator, 'KeyboardHandler', {styles: BASE_LOGGING_STYLES}); super(eventBus, siteSettings, settings, logger);
super(eventBus, siteSettings, settings, tmpLogger);
this.init(); this.init();
} }
init() { init() {
this.logger.debug("init", "starting init"); this.logger.log('info', 'debug', "[KeyboardHandler::init] starting init");
// reset keypressActions when re-initializing, otherwise keypressActions will
// multiply in an unwanted way
this.keypressActions = [];
// build the action list — but only from actions that have shortcuts assigned // build the action list — but only from actions that have shortcuts assigned
for (const key in this.settings.active.commands) { for (const key in this.settings.active.commands) {
@ -119,28 +115,28 @@ export class KeyboardHandler extends KbmBase {
preventAction(event) { preventAction(event) {
var activeElement = document.activeElement; var activeElement = document.activeElement;
// if (this.logger.canLog('keyboard')) { if (this.logger.canLog('keyboard')) {
// this.logger.pause(); // temp disable to avoid recursing; this.logger.pause(); // temp disable to avoid recursing;
// const preventAction = this.preventAction(event); const preventAction = this.preventAction(event);
// this.logger.resume(); // undisable 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" + 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 tag one of defined inputs? (yes->prevent):", this.inputs.indexOf(activeElement.tagName.toLocaleLowerCase()) !== -1,
// "\nis role = textbox? (yes -> prevent):", activeElement.getAttribute("role") === "textbox", "\nis role = textbox? (yes -> prevent):", activeElement.getAttribute("role") === "textbox",
// "\nis type === 'text'? (yes -> prevent):", activeElement.getAttribute("type") === "text", "\nis type === 'text'? (yes -> prevent):", activeElement.getAttribute("type") === "text",
// "\nevent.target.isContentEditable? (yes -> prevent):", event.target.isContentEditable, "\nevent.target.isContentEditable? (yes -> prevent):", event.target.isContentEditable,
// "\nis keyboard local disabled? (yes -> prevent):", this.keyboardLocalDisabled, "\nis keyboard local disabled? (yes -> prevent):", this.keyboardLocalDisabled,
// // "\nis keyboard enabled in settings? (no -> prevent)", this.settings.keyboardShortcutsEnabled(window.location.hostname), // "\nis keyboard enabled in settings? (no -> prevent)", this.settings.keyboardShortcutsEnabled(window.location.hostname),
// "\nwill the action be prevented? (yes -> prevent)", preventAction, "\nwill the action be prevented? (yes -> prevent)", preventAction,
// "\n-----------------{ extra debug info }-------------------", "\n-----------------{ extra debug info }-------------------",
// "\ntag name? (lowercase):", activeElement.tagName, activeElement.tagName.toLocaleLowerCase(), "\ntag name? (lowercase):", activeElement.tagName, activeElement.tagName.toLocaleLowerCase(),
// "\nrole:", activeElement.getAttribute('role'), "\nrole:", activeElement.getAttribute('role'),
// "\ntype:", activeElement.getAttribute('type'), "\ntype:", activeElement.getAttribute('type'),
// "\ninsta-fail inputs:", this.inputs, "\ninsta-fail inputs:", this.inputs,
// "\nevent:", event, "\nevent:", event,
// "\nevent.target:", event.target "\nevent.target:", event.target
// ); );
// } }
if (this.keyboardLocalDisabled) { if (this.keyboardLocalDisabled) {
return true; return true;
@ -213,24 +209,29 @@ export class KeyboardHandler extends KbmBase {
handleKeyup(event) { 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 { try {
if (this.preventAction(event)) { 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; 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); const isLatin = this.isLatin(event.key);
for (const command of this.keypressActions) { for (const command of this.keypressActions) {
if (this.isActionMatch(command.shortcut, event, isLatin)) { if (this.isActionMatch(command.shortcut, event, isLatin)) {
this.eventBus.send(command.action, command.arguments); this.eventBus.send(command.action, command.arguments);
} }
} }
} catch (e) { } catch (e) {
this.logger.debug('handleKeyup', 'Failed to handle keyup!', e); this.logger.log('info', 'debug', '[KeyboardHandler::handleKeyup] Failed to handle keyup!', e);
} }
} }

View File

@ -1,6 +1,5 @@
import { LogAggregator } from './../logging/LogAggregator';
import EventBus, { EventBusCommand } from '../EventBus'; import EventBus, { EventBusCommand } from '../EventBus';
import { ComponentLogger } from '../logging/ComponentLogger'; import Logger from '../Logger';
import Settings from '../Settings'; import Settings from '../Settings';
import { SiteSettings } from '../settings/SiteSettings'; import { SiteSettings } from '../settings/SiteSettings';
import KbmBase from './KbmBase'; import KbmBase from './KbmBase';
@ -10,10 +9,6 @@ if(process.env.CHANNEL !== 'stable'){
} }
const BASE_LOGGING_STYLES = {
log: "color: #ff0"
};
/** /**
* Handles keypress * Handles keypress
*/ */
@ -41,21 +36,21 @@ export class MouseHandler extends KbmBase {
} }
//#region lifecycle //#region lifecycle
constructor(playerElement: HTMLElement, eventBus: EventBus, siteSettings: SiteSettings, settings: Settings, logAggregator: LogAggregator) { constructor(playerElement: HTMLElement, eventBus: EventBus, siteSettings: SiteSettings, settings: Settings, logger: Logger) {
const tmpLogger = new ComponentLogger(logAggregator, 'MouseHandler', {styles: BASE_LOGGING_STYLES}); super(eventBus, siteSettings, settings, logger);
super(eventBus, siteSettings, settings, tmpLogger);
this.logger = logger;
this.settings = settings; this.settings = settings;
this.siteSettings = siteSettings; this.siteSettings = siteSettings;
this.eventBus = eventBus; this.eventBus = eventBus;
this.playerElement = playerElement; this.playerElement = playerElement;
this.init(); this.init();
} }
init() { init() {
// this.logger.debug('init', 'starting init'); this.logger.log('info', 'debug', '[MouseHandler::init] starting init');
} }
load() { load() {

View File

@ -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);
}
}

View File

@ -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()});
}
}
}

View File

@ -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,
}
}

View File

@ -1,10 +0,0 @@
export interface LogMessageOrigin {
component: string,
environment: string,
}
export interface LogMessage {
time: Date;
message: any,
origin: LogMessageOrigin
}

View File

@ -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),
});
}
}

View File

@ -15,17 +15,10 @@ import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum';
*/ */
export class SiteSettings { export class SiteSettings {
private settings: Settings; private settings: Settings;
private _site: string; private site: string;
private set site(x: string) {
this._site = x;
}
public get site() {
return this._site;
}
raw: SiteSettingsInterface; // actual settings raw: SiteSettingsInterface; // actual settings
data: SiteSettingsInterface; // effective settings data: SiteSettingsInterface; // effective settings
usesSettingsFor: string | undefined;
temporaryData: SiteSettingsInterface; temporaryData: SiteSettingsInterface;
sessionData: SiteSettingsInterface; sessionData: SiteSettingsInterface;
readonly defaultSettings: SiteSettingsInterface; readonly defaultSettings: SiteSettingsInterface;
@ -35,9 +28,9 @@ export class SiteSettings {
//#region lifecycle //#region lifecycle
constructor(settings: Settings, site: string) { constructor(settings: Settings, site: string) {
this.settings = settings; this.settings = settings;
this.raw = settings.active.sites[site]; this.data = settings.active.sites[site];
this.site = site; this.site = site;
this.defaultSettings = settings.active.sites['@global']; this.defaultSettings = settings.default.sites['@global'];
this.compileSettingsObject(); this.compileSettingsObject();
@ -54,63 +47,12 @@ export class SiteSettings {
chrome.storage.onChanged.addListener((changes, area) => {this.storageChangeListener(changes, area)}) 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. * Merges defaultSettings into site settings where appropriate.
* Alan pls ensure default settings object follows the correct structure * Alan pls ensure default settings object follows the correct structure
*/ */
private compileSettingsObject() { private compileSettingsObject() {
const {siteSettings, usesSettingsFor} = this.getSettingsForSite(); this.raw = _cp(this.settings.active.sites[this.site] ?? {})
this.data = _cp(siteSettings);
this.usesSettingsFor = usesSettingsFor;
if (!this.data) { if (!this.data) {
this.data = _cp(this.defaultSettings); 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]) { if (!this.data[enableSegment]) {
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 // we aren't stepping on any other toes by doing this, since everyone
// gets the first change // gets the first change
// this.settings.active._updateFlags = undefined; this.settings.active._updateFlags = undefined;
// this.settings.saveWithoutReload(); this.settings.saveWithoutReload();
} }
} }
@ -282,7 +224,7 @@ export class SiteSettings {
* @param isFullscreen * @param isFullscreen
* @returns ExtensionMode * @returns ExtensionMode
*/ */
isEnabledForEnvironment(isTheater: boolean, isFullscreen: boolean): ExtensionMode { isEnabledForEnvironment(isTheater: boolean, isFullscreen: boolean) {
const env = this._getEnvironment(isTheater, isFullscreen); const env = this._getEnvironment(isTheater, isFullscreen);
return this.data.enable[env]; return this.data.enable[env];
} }
@ -293,7 +235,7 @@ export class SiteSettings {
* @param isFullscreen * @param isFullscreen
* @returns * @returns
*/ */
isAardEnabledForEnvironment(isTheater: boolean, isFullscreen: boolean): ExtensionMode { isAardEnabledForEnvironment(isTheater: boolean, isFullscreen: boolean) {
const env = this._getEnvironment(isTheater, isFullscreen); const env = this._getEnvironment(isTheater, isFullscreen);
return this.data.enableAard[env]; return this.data.enableAard[env];
} }
@ -304,7 +246,7 @@ export class SiteSettings {
* @param isFullscreen * @param isFullscreen
* @returns * @returns
*/ */
isKeyboardEnabledForEnvironment(isTheater: boolean, isFullscreen: boolean): ExtensionMode { isKeyboardEnabledForEnvironment(isTheater: boolean, isFullscreen: boolean) {
const env = this._getEnvironment(isTheater, isFullscreen); const env = this._getEnvironment(isTheater, isFullscreen);
return this.data.enableKeyboard[env]; return this.data.enableKeyboard[env];
} }
@ -365,19 +307,10 @@ export class SiteSettings {
* @param optionValue new value of option * @param optionValue new value of option
* @param reload whether we should trigger a reload in components that require it * @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}) { 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 no settings exist for this site, create an empty object
// If this function is not being called in response to user actin, if (!this.settings.active.sites[this.site]) {
// create fake settings object. this.settings.active.sites[this.site] = _cp(this.settings.active.sites['@empty']);
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';
}
} }
const pathParts = optionPath.split('.'); const pathParts = optionPath.split('.');

View File

@ -1,4 +1,3 @@
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
import { EventBusConnector } from '../EventBus'; import { EventBusConnector } from '../EventBus';
if (process.env.CHANNEL !== 'stable'){ if (process.env.CHANNEL !== 'stable'){
@ -23,7 +22,6 @@ class UI {
this.lastProbeResponseTs = null; this.lastProbeResponseTs = null;
this.isGlobal = uiConfig.isGlobal ?? false; this.isGlobal = uiConfig.isGlobal ?? false;
this.isIframe = window.self !== window.top;
this.eventBus = uiConfig.eventBus; this.eventBus = uiConfig.eventBus;
this.disablePointerEvents = false; this.disablePointerEvents = false;
@ -31,48 +29,24 @@ class UI {
this.saveState = undefined; this.saveState = undefined;
this.playerData = uiConfig.playerData; this.playerData = uiConfig.playerData;
this.uiSettings = uiConfig.uiSettings; this.uiSettings = uiConfig.uiSettings;
this.siteSettings = uiConfig.siteSettings;
this.iframeErrorCount = 0; this.iframeErrorCount = 0;
this.iframeConfirmed = false; this.iframeConfirmed = false;
this.iframeRejected = false; this.iframeRejected = false;
this.delayedDestroyTimer = null;
// TODO: at some point, UI should be different for global popup and in-player UI // TODO: at some point, UI should be different for global popup and in-player UI
this.csuiScheme = this.getCsuiScheme(); this.csuiScheme = this.getCsuiScheme();
const csuiVersion = this.getCsuiVersion(this.csuiScheme.contentScheme); const csuiVersion = this.getCsuiVersion(this.csuiScheme.contentScheme);
this.uiURI = chrome.runtime.getURL(`/csui/${csuiVersion}.html`); this.uiURI = chrome.runtime.getURL(`/csui/${csuiVersion}.html`);
this.extensionBase = chrome.runtime.getURL('').replace(/\/$/, ""); 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() { async init() {
if (!this.canRun()) { this.initIframes();
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.initMessaging(); this.initMessaging();
} }
/** /**
* Returns color scheme we need to use. * Returns color scheme we need to use.
* *
@ -96,7 +70,7 @@ class UI {
return csuiVersions[preferredScheme] ?? csuiVersions.normal; return csuiVersions[preferredScheme] ?? csuiVersions.normal;
} }
initUIContainer() { initIframes() {
const random = Math.round(Math.random() * 69420); const random = Math.round(Math.random() * 69420);
const uwid = `uw-ultrawidify-${this.interfaceId}-root-${random}` const uwid = `uw-ultrawidify-${this.interfaceId}-root-${random}`
@ -107,8 +81,8 @@ class UI {
} }
rootDiv.setAttribute('id', uwid); rootDiv.setAttribute('id', uwid);
rootDiv.classList.add('uw-ultrawidify-container-root'); rootDiv.classList.add('uw-ultrawidify-container-root');
rootDiv.style.width = "100%"; // rootDiv.style.width = "100%";
rootDiv.style.height = "100%"; // rootDiv.style.height = "100%";
rootDiv.style.position = this.isGlobal ? "fixed" : "absolute"; rootDiv.style.position = this.isGlobal ? "fixed" : "absolute";
rootDiv.style.zIndex = this.isGlobal ? '90009' : '90000'; rootDiv.style.zIndex = this.isGlobal ? '90009' : '90000';
rootDiv.style.border = 0; rootDiv.style.border = 0;
@ -121,10 +95,8 @@ class UI {
document.body.appendChild(rootDiv); document.body.appendChild(rootDiv);
} }
this.rootDiv = rootDiv; this.element = rootDiv;
}
loadIframe() {
// in onMouseMove, we currently can't access this because we didn't // in onMouseMove, we currently can't access this because we didn't
// do things the most properly // do things the most properly
const uiURI = this.uiURI; const uiURI = this.uiURI;
@ -160,7 +132,7 @@ class UI {
this.uiIframe = iframe; this.uiIframe = iframe;
// set not visible by default // set not visible by default
// this.setUiVisibility(false); this.setUiVisibility(false);
const fn = (event) => { const fn = (event) => {
// remove self on fucky wuckies // remove self on fucky wuckies
@ -206,28 +178,14 @@ class UI {
document.addEventListener('mousemove', fn, true); document.addEventListener('mousemove', fn, true);
} }
this.eventBus.forwardToIframe( rootDiv.appendChild(iframe);
this.uiIframe,
(action, payload) => {
this.sendToIframe(action, payload, {})
} }
);
this.rootDiv.appendChild(iframe);
}
unloadIframe() {
this.eventBus.cancelIframeForwarding(this.uiIframe);
window.removeEventListener('message', this.messageHandlerFn);
this.uiIframe?.remove();
delete this.uiIframe;
}
initMessaging() { initMessaging() {
// subscribe to events coming back to us. Unsubscribe if iframe vanishes. // subscribe to events coming back to us. Unsubscribe if iframe vanishes.
window.addEventListener('message', this.messageHandlerFn); window.addEventListener('message', this.messageHandlerFn);
/* set up event bus tunnel from content script to UI necessary if we want to receive /* 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 * like current zoom levels & current aspect ratio & stuff. Some of these things are
* necessary for UI display in the popup. * necessary for UI display in the popup.
@ -242,7 +200,7 @@ class UI {
}, },
'uw-set-ui-state': { 'uw-set-ui-state': {
function: (config, routingData) => { function: (config, routingData) => {
if (config.globalUiVisible !== undefined && !this.isIframe) { if (config.globalUiVisible !== undefined) {
if (this.isGlobal) { if (this.isGlobal) {
this.setUiVisibility(config.globalUiVisible); this.setUiVisibility(config.globalUiVisible);
} else { } else {
@ -292,40 +250,28 @@ class UI {
} }
setUiVisibility(visible) { 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) { if (visible) {
this.rootDiv.style.width = '100%'; this.element.style.width = '100%';
this.rootDiv.style.height = '100%'; this.element.style.height = '100%';
this.uiIframe.style.width = '100%'; this.uiIframe.style.width = '100%';
this.uiIframe.style.height = '100%'; this.uiIframe.style.height = '100%';
// if (this.delayedDestroyTimer) {
// clearTimeout(this.delayedDestroyTimer);
// }
} else { } else {
this.rootDiv.style.width = '0px'; this.element.style.width = '0px';
this.rootDiv.style.height = '0px'; this.element.style.height = '0px';
this.uiIframe.style.width = '0px'; this.uiIframe.style.width = '0px';
this.uiIframe.style.height = '0px'; this.uiIframe.style.height = '0px';
// destroy after 30 seconds of UI being hidden
// this.delayedDestroyTimer = setTimeout( () => this.unloadIframe(), 30000);
} }
} }
async enable() { async enable() {
// if root element is not present, we need to init the UI. // if root element is not present, we need to init the UI.
if (!this.rootDiv) { if (!this.element) {
await this.init(); await this.init();
} }
// otherwise, we don't have to do anything // otherwise, we don't have to do anything
} }
disable() { disable() {
if (this.rootDiv) { if (this.element) {
this.destroy(); this.destroy();
} }
} }
@ -344,10 +290,6 @@ class UI {
canShowUI: false, canShowUI: false,
} }
if (this.playerData?.environment && this.siteSettings.data.enableUI[this.playerData?.environment] !== ExtensionMode.Enabled) {
return result;
}
if (this.playerData?.dimensions) { if (this.playerData?.dimensions) {
result.playerDimensions = this.playerData.dimensions; result.playerDimensions = this.playerData.dimensions;
} }
@ -364,7 +306,6 @@ class UI {
} }
result.canShowUI = true; result.canShowUI = true;
return result; return result;
} }
@ -425,17 +366,7 @@ class UI {
break; break;
case 'uw-bus-tunnel': case 'uw-bus-tunnel':
const busCommand = event.data.payload; const busCommand = event.data.payload;
this.eventBus.send( this.eventBus.send(busCommand.action, busCommand.config, busCommand.routingData);
busCommand.action,
busCommand.config,
{
...busCommand?.context,
borderCrossings: {
...busCommand?.context?.borderCrossings,
iframe: true,
}
}
);
break; break;
case 'uwui-get-role': case 'uwui-get-role':
this.sendToIframeLowLevel('uwui-set-role', {role: this.isGlobal ? 'global' : 'player'}); 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. // 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, // If extension is inactive due to "player element isn't big enough to justify it", however,
// we can still receive eventBus messages. // we can still receive eventBus messages.
if (this.rootDiv && this.uiIframe) { if (this.element && this.uiIframe) {
this.uiIframe.contentWindow?.postMessage( this.uiIframe.contentWindow?.postMessage(
{ {
action, action,
@ -514,21 +445,21 @@ class UI {
replace(newUiConfig) { replace(newUiConfig) {
this.uiConfig = newUiConfig; this.uiConfig = newUiConfig;
if (this.rootDiv) { if (this.element) {
this.destroy(); this.element?.remove();
this.init(); this.init();
} }
} }
destroy() { destroy() {
this.unloadIframe(); window.removeEventListener('message', this.messageHandlerFn);
this.eventBus.unsubscribeAll(this); this.eventBus.unsubscribeAll(this);
// this.comms?.destroy(); // this.comms?.destroy();
this.rootDiv?.remove(); this.uiIframe?.remove();
this.element?.remove();
delete this.uiIframe; this.uiIframe = undefined;
delete this.rootDiv; this.element = undefined;
} }
} }

View File

@ -1,12 +1,15 @@
import Debug from '../../conf/Debug';
import VideoData from './VideoData'; import VideoData from './VideoData';
import RescanReason from './enums/RescanReason.enum'; 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 Settings from '../Settings';
import ExtensionMode from '../../../common/enums/ExtensionMode.enum';
import CommsClient from '../comms/CommsClient'; import CommsClient from '../comms/CommsClient';
import EventBus from '../EventBus'; import EventBus from '../EventBus';
import { SiteSettings } from '../settings/SiteSettings'; import { SiteSettings } from '../settings/SiteSettings';
import IframeManager from './IframeManager'; import IframeManager from './IframeManager';
import { LogAggregator } from '../logging/LogAggregator';
import { ComponentLogger } from '../logging/ComponentLogger';
if (process.env.CHANNEL !== 'stable'){ if (process.env.CHANNEL !== 'stable'){
console.info("Loading PageInfo"); console.info("Loading PageInfo");
@ -49,8 +52,7 @@ class PageInfo {
//#endregion //#endregion
//#region helper objects //#region helper objects
logAggregator: LogAggregator; logger: Logger;
logger: ComponentLogger;
settings: Settings; settings: Settings;
siteSettings: SiteSettings; siteSettings: SiteSettings;
comms: CommsClient; comms: CommsClient;
@ -69,7 +71,6 @@ class PageInfo {
keyboardHandler: any; keyboardHandler: any;
fsStatus = {fullscreen: true}; // fsStatus needs to be passed to VideoData, so fullScreen property is shared between videoData instances fsStatus = {fullscreen: true}; // fsStatus needs to be passed to VideoData, so fullScreen property is shared between videoData instances
isIframe: boolean = false;
//#endregion //#endregion
fsEventListener = { fsEventListener = {
@ -79,11 +80,8 @@ class PageInfo {
} }
}; };
constructor(eventBus: EventBus, siteSettings: SiteSettings, settings: Settings, logAggregator: LogAggregator, readOnly = false) { constructor(eventBus: EventBus, siteSettings: SiteSettings, settings: Settings, logger: Logger, readOnly = false){
this.isIframe = window.self !== window.top; this.logger = logger;
this.logAggregator = logAggregator;
this.logger = new ComponentLogger(logAggregator, 'PageInfo', {});
this.settings = settings; this.settings = settings;
this.siteSettings = siteSettings; this.siteSettings = siteSettings;
@ -111,19 +109,10 @@ class PageInfo {
this.scheduleUrlCheck(); this.scheduleUrlCheck();
document.addEventListener('fullscreenchange', this.fsEventListener); document.addEventListener('fullscreenchange', this.fsEventListener);
this.eventBus.subscribeMulti({
'probe-video': {
function: () => {
console.log(`[${window.location}] probe-video received.`)
this.rescan();
}
}
});
} }
destroy() { destroy() {
// this.logger.debug('destroy', 'destroying all videos!") // this.logger.log('info', ['debug', 'init'], "[PageInfo::destroy] destroying all videos!")
if(this.rescanTimer){ if(this.rescanTimer){
clearTimeout(this.rescanTimer); clearTimeout(this.rescanTimer);
} }
@ -132,7 +121,7 @@ class PageInfo {
this.eventBus.send('noVideo', undefined); this.eventBus.send('noVideo', undefined);
video.videoData.destroy(); video.videoData.destroy();
} catch (e) { } 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,21 +179,7 @@ class PageInfo {
} }
} }
/** getVideos(): HTMLVideoElement[] {
* 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') {
const videoQs = this.siteSettings.getCustomDOMQuerySelector('video'); const videoQs = this.siteSettings.getCustomDOMQuerySelector('video');
let videos: HTMLVideoElement[] = []; let videos: HTMLVideoElement[] = [];
@ -214,38 +189,96 @@ class PageInfo {
videos = Array.from(document.getElementsByTagName('video') ?? []); 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 // filter out videos that aren't big enough
return videos.filter( videos = videos.filter(
(v: HTMLVideoElement) => v.clientHeight >= minY[minSize] && v.clientWidth >= minX[minSize] (v: HTMLVideoElement) => v.clientHeight > 720 && v.clientWidth > 1208
); );
}
/** return videos;
* Gets videos on the page that are big enough for extension to trigger
* @returns
*/
getVideos(): HTMLVideoElement[] {
return this.getAllVideos('lg');
} }
hasVideo() { hasVideo() {
return this.readOnly ? this.hasVideos : this.videos.length; return this.readOnly ? this.hasVideos : this.videos.length;
} }
private emitVideoStatus(videosDetected?: boolean) { /**
* 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
* own <video> html element on the page.
* @param rescanReason Why was the rescan triggered. Mostly used for logging.
* @returns
*/
rescan(rescanReason?: RescanReason){
// 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));
for (const orphan of orphans) {
orphan.videoData.destroy();
}
// remove all destroyed videos.
this.videos = this.videos.filter(x => !x.videoData.destroyed);
// add new videos
try{
let vids = this.getVideos();
if(!vids || vids.length == 0){
this.hasVideos = false;
if(rescanReason == RescanReason.PERIODIC){
this.logger.log('info', 'videoRescan', "[PageInfo::rescan] Scheduling normal rescan.")
this.scheduleRescan(RescanReason.PERIODIC);
}
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))) {
continue;
}
// if we find even a single video with width and height, that means the page has valid videos
// if video lacks either of the two properties, we skip all further checks cos pointless
if(!videoElement.offsetWidth || !videoElement.offsetHeight) {
continue;
}
// at this point, we're certain that we found new videos. Let's update some properties:
this.hasVideos = 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
if (this.readOnly) {
// in lite mode, we're done. This is all the info we want, but we want to actually start doing
// things that interfere with the website. We still want to be running a rescan, tho.
if(rescanReason == RescanReason.PERIODIC){
this.scheduleRescan(RescanReason.PERIODIC);
}
return;
}
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.log('error', 'debug', "rescan error: failed to initialize videoData. Skipping this video.",e);
}
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();
// if we're left without videos on the current page, we unregister the page. // if we're left without videos on the current page, we unregister the page.
// if we have videos, we call register. // if we have videos, we call register.
if (this.eventBus) { if (this.eventBus) {
@ -263,110 +296,24 @@ class PageInfo {
// things in the less-than-optimal. more-than-retarded way. // things in the less-than-optimal. more-than-retarded way.
// //
// no but honestly fuck Chrome. // no but honestly fuck Chrome.
if (videosDetected || this.hasVideo()) {
// 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); this.eventBus.send('has-video', null);
} else { } else {
// this.comms.unregisterVideo({host: window.location.hostname, location: window.location});
this.eventBus.send('noVideo', null); 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
* own <video> html element on the page.
* @param rescanReason Why was the rescan triggered. Mostly used for logging.
* @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));
for (const orphan of orphans) {
orphan.videoData.destroy();
}
// 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');
if(!vids || vids.length == 0){
this.hasVideos = false;
if(rescanReason == RescanReason.PERIODIC){
this.logger.info({src: 'rescan', origin: 'videoRescan'}, "Scheduling normal rescan.")
this.scheduleRescan(RescanReason.PERIODIC);
}
this.emitVideoStatus(videosDetected);
return;
}
// add new videos
this.hasVideos = false;
for (const videoElement of vids) {
// do not re-add videos that we already track:
if (this.videos.find(x => x.element.isEqualNode(videoElement))) {
continue;
}
// if we find even a single video with width and height, that means the page has valid videos
// if video lacks either of the two properties, we skip all further checks cos pointless
if(!videoElement.offsetWidth || !videoElement.offsetHeight) {
continue;
}
// 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
if (this.readOnly) {
// in lite mode, we're done. This is all the info we want, but we want to actually start doing
// things that interfere with the website. We still want to be running a rescan, tho.
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")
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.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.removeDestroyed();
this.emitVideoStatus(videosDetected);
} catch(e) { } catch(e) {
// if we encounter a fuckup, we can assume that no videos were found on the page. We destroy all videoData // 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 // 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 // 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) { for (const v of this.videos) {
v.videoData.destroy(); v.videoData.destroy();
} }
@ -401,7 +348,7 @@ class PageInfo {
ths = null; ths = null;
}, this.settings.active.pageInfo.timeouts.rescan, RescanReason.PERIODIC) }, this.settings.active.pageInfo.timeouts.rescan, RescanReason.PERIODIC)
} catch(e) { } 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; ths = null;
}, this.settings.active.pageInfo.timeouts.urlCheck) }, this.settings.active.pageInfo.timeouts.urlCheck)
} catch(e){ } 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() { ghettoUrlCheck() {
if (this.lastUrl != window.location.href){ 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.rescan(RescanReason.URL_CHANGE);
this.lastUrl = window.location.href; this.lastUrl = window.location.href;

View File

@ -1,16 +1,18 @@
import Debug from '../../conf/Debug'; import Debug from '../../conf/Debug';
import ExtensionMode from '../../../common/enums/ExtensionMode.enum' import ExtensionMode from '../../../common/enums/ExtensionMode.enum'
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
import PlayerNotificationUi from '../uwui/PlayerNotificationUI'; import PlayerNotificationUi from '../uwui/PlayerNotificationUI';
import BrowserDetect from '../../conf/BrowserDetect';
import * as _ from 'lodash'; import * as _ from 'lodash';
import { sleep } from '../../../common/js/utils'; import { sleep } from '../../../common/js/utils';
import VideoData from './VideoData'; import VideoData from './VideoData';
import Settings from '../Settings';
import Logger from '../Logger';
import EventBus from '../EventBus'; import EventBus from '../EventBus';
import UI from '../uwui/UI'; import UI from '../uwui/UI';
import { SiteSettings } from '../settings/SiteSettings'; import { SiteSettings } from '../settings/SiteSettings';
import PageInfo from './PageInfo'; import PageInfo from './PageInfo';
import { RunLevel } from '../../enum/run-level.enum'; import { RunLevel } from '../../enum/run-level.enum';
import { ExtensionEnvironment } from '../../../common/interfaces/SettingsInterface';
import { ComponentLogger } from '../logging/ComponentLogger';
if (process.env.CHANNEL !== 'stable'){ if (process.env.CHANNEL !== 'stable'){
console.info("Loading: PlayerData.js"); console.info("Loading: PlayerData.js");
@ -66,7 +68,7 @@ class PlayerData {
private playerCssClass = 'uw-ultrawidify-player-css'; private playerCssClass = 'uw-ultrawidify-player-css';
//#region helper objects //#region helper objects
logger: ComponentLogger; logger: Logger;
videoData: VideoData; videoData: VideoData;
pageInfo: PageInfo; pageInfo: PageInfo;
siteSettings: SiteSettings; 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. 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; isTooSmall: boolean = true;
//#endregion
//#region misc stuff //#region misc stuff
extensionMode: any; extensionMode: any;
dimensions: PlayerDimensions; dimensions: PlayerDimensions;
private playerIdElement: any;
private observer: ResizeObserver; private observer: ResizeObserver;
private trackChangesTimeout: any; private trackChangesTimeout: any;
@ -100,8 +102,6 @@ class PlayerData {
private ui: UI; private ui: UI;
private _isTrackDimensionChangesActive: boolean = false;
elementStack: ElementStack = [] as ElementStack; elementStack: ElementStack = [] as ElementStack;
//#endregion //#endregion
@ -122,7 +122,7 @@ class PlayerData {
function: (data) => this.markElement(data) function: (data) => this.markElement(data)
}], }],
'update-player': [{ 'update-player': [{
function: () => this.updatePlayer() function: () => this.getPlayer()
}], }],
'set-run-level': [{ 'set-run-level': [{
function: (runLevel) => this.setRunLevel(runLevel) function: (runLevel) => this.setRunLevel(runLevel)
@ -138,7 +138,6 @@ class PlayerData {
private dimensionChangeListener = { private dimensionChangeListener = {
that: this, that: this,
handleEvent: function(event: Event) { handleEvent: function(event: Event) {
this.that.trackEnvironmentChanges(event);
this.that.trackDimensionChanges() this.that.trackDimensionChanges()
} }
} }
@ -153,7 +152,6 @@ class PlayerData {
} }
if (!this.dimensions) { if (!this.dimensions) {
this.trackDimensionChanges(); this.trackDimensionChanges();
this.trackEnvironmentChanges();
} }
return this.dimensions.width / this.dimensions.height; 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 //#region lifecycle
constructor(videoData) { constructor(videoData) {
try { try {
// set all our helper objects // set all our helper objects
this.logger = new ComponentLogger(videoData.logAggregator, 'PlayerData', {styles: {}}); this.logger = videoData.logger;
this.videoData = videoData; this.videoData = videoData;
this.videoElement = videoData.video; this.videoElement = videoData.video;
this.pageInfo = videoData.pageInfo; this.pageInfo = videoData.pageInfo;
@ -199,10 +174,14 @@ class PlayerData {
// do the rest // do the rest
this.invalid = false; this.invalid = false;
this.updatePlayer(); this.element = this.getPlayer();
this.isTooSmall = (this.element.clientWidth < 1208 || this.element.clientHeight < 720); this.isTooSmall = (this.element.clientWidth < 1208 || this.element.clientHeight < 720);
this.initEventBus(); this.initEventBus();
// we defer UI creation until player element is big enough
// this happens in trackDimensionChanges!
this.dimensions = undefined; this.dimensions = undefined;
this.periodicallyRefreshPlayerElement = false; this.periodicallyRefreshPlayerElement = false;
@ -218,6 +197,7 @@ class PlayerData {
return; return;
} }
this.trackDimensionChanges();
this.startChangeDetection(); this.startChangeDetection();
document.addEventListener('fullscreenchange', this.dimensionChangeListener); document.addEventListener('fullscreenchange', this.dimensionChangeListener);
@ -232,7 +212,7 @@ class PlayerData {
private reloadPlayerDataConfig(siteConfUpdate) { private reloadPlayerDataConfig(siteConfUpdate) {
// this.siteSettings = siteConfUpdate; // this.siteSettings = siteConfUpdate;
this.updatePlayer(); this.element = this.getPlayer();
this.periodicallyRefreshPlayerElement = false; this.periodicallyRefreshPlayerElement = false;
try { try {
@ -268,28 +248,24 @@ class PlayerData {
//#endregion //#endregion
deferredUiInitialization(playerDimensions) { deferredUiInitialization(playerDimensions) {
if (this.ui || this.siteSettings.data.enableUI.fullscreen === ExtensionMode.Disabled) { if (this.ui || ! this.videoData.settings.active.ui?.inPlayer?.enabled) {
return; return;
} }
if ( if (
this.isFullscreen this.isFullscreen
|| ( || (
this.siteSettings.data.enableUI.theater !== ExtensionMode.Disabled playerDimensions.width > 1208
&& playerDimensions.width > 1208
&& playerDimensions.height > 720 && playerDimensions.height > 720
) )
) { ) {
this.ui = new UI( this.ui = new UI(
'ultrawidifyUi', 'ultrawidifyUi',
{ {
parentElement: this.element, parentElement: this.element,
eventBus: this.eventBus, eventBus: this.eventBus,
playerData: this, playerData: this,
uiSettings: this.videoData.settings.active.ui, uiSettings: this.videoData.settings.active.ui
siteSettings: this.siteSettings,
} }
); );
@ -360,26 +336,10 @@ class PlayerData {
return newTheaterMode; return newTheaterMode;
} }
trackEnvironmentChanges() {
if (this.environment !== this.lastEnvironment) {
this.lastEnvironment = this.environment;
this.eventBus.send('uw-environment-change', {newEnvironment: this.environment});
}
}
/** /**
* *
*/ */
trackDimensionChanges() { trackDimensionChanges() {
if (this._isTrackDimensionChangesActive) {
// this shouldn't really get called, _ever_ ... but sometimes it happens
console.warn('[PlayerData::trackDimensionChanges] trackDimensionChanges is already active!');
return;
}
this._isTrackDimensionChangesActive = true;
try {
// get player dimensions _once_ // get player dimensions _once_
let currentPlayerDimensions; let currentPlayerDimensions;
this.isFullscreen = !!document.fullscreenElement; this.isFullscreen = !!document.fullscreenElement;
@ -413,7 +373,6 @@ class PlayerData {
this.eventBus.send('restore-ar', null); this.eventBus.send('restore-ar', null);
this.eventBus.send('delayed-restore-ar', {delay: 500}); this.eventBus.send('delayed-restore-ar', {delay: 500});
this.dimensions = currentPlayerDimensions; this.dimensions = currentPlayerDimensions;
this._isTrackDimensionChangesActive = false;
return; return;
} }
@ -424,11 +383,6 @@ class PlayerData {
// Save current dimensions to avoid triggering this function pointlessly // Save current dimensions to avoid triggering this function pointlessly
this.dimensions = currentPlayerDimensions; this.dimensions = currentPlayerDimensions;
} catch (e) {
}
this._isTrackDimensionChangesActive = false;
} }
@ -454,13 +408,13 @@ class PlayerData {
private handleDimensionChanges(newDimensions: PlayerDimensions, oldDimensions: PlayerDimensions) { private handleDimensionChanges(newDimensions: PlayerDimensions, oldDimensions: PlayerDimensions) {
if (this.runLevel === RunLevel.Off ) { 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; return;
} }
// this 'if' is just here for debugging — real code starts later. It's safe to collapse and // 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) // 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 size doesn't match, trigger onPlayerDimensionChange
if ( if (
@ -482,17 +436,9 @@ class PlayerData {
} }
} }
onPlayerDimensionsChanged: ResizeObserverCallback = _.debounce( onPlayerDimensionsChanged(mutationList?, observer?) {
(mutationList?, observer?) => {
this.trackDimensionChanges(); 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
} }
);
//#region player element change detection //#region player element change detection
@ -506,19 +452,29 @@ class PlayerData {
} }
try { try {
if (this.observer) {
this.observer.disconnect();
}
this.observer = new ResizeObserver( 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); this.observer.observe(this.element);
} catch (e) { } 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 // legacy mode still exists, but acts as a fallback for observers and is triggered less
// frequently in order to avoid too many pointless checks // frequently in order to avoid too many pointless checks
this.legacyChangeDetection(); this.legacyChangeDetection();
@ -590,39 +546,12 @@ class PlayerData {
return this.elementStack; 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 * 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 videoWidth = this.videoElement.offsetWidth;
const videoHeight = this.videoElement.offsetHeight; const videoHeight = this.videoElement.offsetHeight;
let playerCandidate; let playerCandidate;
@ -643,6 +572,7 @@ class PlayerData {
} }
// if mode is given, we follow the preference // 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?.manual && this.siteSettings.data.currentDOMConfig?.elements?.player?.mode) {
if (this.siteSettings.data.currentDOMConfig?.elements?.player?.mode === 'qs') { if (this.siteSettings.data.currentDOMConfig?.elements?.player?.mode === 'qs') {
playerCandidate = this.getPlayerQs(playerQs, elementStack, videoWidth, videoHeight); 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.offsetWidth, elementStack[nextIndex].element.offsetWidth, 2)
&& this.equalish(elementStack[currentIndex].element.offsetHeight, elementStack[nextIndex].element.offsetHeight, 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.initialIndex', this.siteSettings.data.playerAutoConfig.initialIndex + 1, {noSave: true});
// this.siteSettings.set('playerAutoConfig.modified', true); this.siteSettings.set('playerAutoConfig.modified', true);
// console.log('updated site settings:', this.siteSettings.data.playerAutoConfig); console.log('updated site settings:', this.siteSettings.data.playerAutoConfig);
// this.videoData.settings.saveWithoutReload(); 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; let penaltyMultiplier = 1;
const sizePenaltyMultiplier = 0.1; const sizePenaltyMultiplier = 0.1;
const perLevelScorePenalty = 10; const perLevelScorePenalty = 10;
let sameSizeBonus = 0;
for (const [index, element] of elementStack.entries()) { for (const [index, element] of elementStack.entries()) {
element.index = index; element.index = index;
@ -758,13 +691,7 @@ class PlayerData {
// we prefer elements closer to the video, so the score of each potential // we prefer elements closer to the video, so the score of each potential
// candidate gets dinked a bit // candidate gets dinked a bit
// score -= perLevelScorePenalty * penaltyMultiplier; score -= perLevelScorePenalty * penaltyMultiplier;
if (element.width === elementStack[index - 1].width && element.height === elementStack[index - 1].height) {
score += ++sameSizeBonus;
} else {
sameSizeBonus = 0;
}
element.autoScore = score; element.autoScore = score;
element.heuristics['autoScoreDetails'] = { element.heuristics['autoScoreDetails'] = {
@ -779,37 +706,19 @@ class PlayerData {
} }
} }
let bestCandidate: any = {autoScore: -99999999, initialValue: true}; let bestCandidate: any = {autoScore: -99999999, initialValue: true};
for (const element of elementStack) { for (const element of elementStack) {
if (element.autoScore > bestCandidate.autoScore) { if (element.autoScore > bestCandidate.autoScore) {
bestCandidate = element; bestCandidate = element;
} }
} }
if (bestCandidate.initialValue) { if (bestCandidate.initialValue) {
bestCandidate = null; bestCandidate = null;
} else { } else {
bestCandidate.heuristics['autoMatch'] = true; bestCandidate.heuristics['autoMatch'] = true;
// if (this.siteSettings.data.playerAutoConfig?.initialIndex !== bestCandidate.index) { if (this.siteSettings.data.playerAutoConfig?.initialIndex !== bestCandidate.index) {
// this.siteSettings.set('playerAutoConfig.initialIndex', bestCandidate.index, {reload: false, scripted: true}); this.siteSettings.set('playerAutoConfig.initialIndex', bestCandidate.index, {reload: false});
// }
} }
// 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});
} }
return bestCandidate; return bestCandidate;
@ -879,7 +788,7 @@ class PlayerData {
*/ */
private handlePlayerTreeRequest() { private handlePlayerTreeRequest() {
// this populates this.elementStack fully // this populates this.elementStack fully
this.updatePlayer({verbose: true}); this.getPlayer({verbose: true});
console.log('tree:', JSON.parse(JSON.stringify(this.elementStack))); console.log('tree:', JSON.parse(JSON.stringify(this.elementStack)));
console.log('————————————————————— handling player tree request!') console.log('————————————————————— handling player tree request!')
this.eventBus.send('uw-config-broadcast', {type: 'player-tree', config: JSON.parse(JSON.stringify(this.elementStack))}); this.eventBus.send('uw-config-broadcast', {type: 'player-tree', config: JSON.parse(JSON.stringify(this.elementStack))});
@ -920,7 +829,11 @@ class PlayerData {
} }
forceRefreshPlayerElement() { forceRefreshPlayerElement() {
this.updatePlayer(); this.ui?.destroy();
this.ui = undefined;
this.element = this.getPlayer();
// this.notificationService?.replace(this.element);
this.trackDimensionChanges();
} }
} }

View File

@ -1,7 +1,11 @@
import Debug from '../../conf/Debug';
import PlayerData from './PlayerData'; import PlayerData from './PlayerData';
import Resizer from '../video-transform/Resizer'; 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 * as _ from 'lodash';
import BrowserDetect from '../../conf/BrowserDetect'; import BrowserDetect from '../../conf/BrowserDetect';
import Logger from '../Logger';
import Settings from '../Settings'; import Settings from '../Settings';
import PageInfo from './PageInfo'; import PageInfo from './PageInfo';
import { sleep } from '../../../common/js/utils'; import { sleep } from '../../../common/js/utils';
@ -13,10 +17,6 @@ import { ExtensionStatus } from './ExtensionStatus';
import { RunLevel } from '../../enum/run-level.enum'; import { RunLevel } from '../../enum/run-level.enum';
import { Aard } from '../aard/Aard'; import { Aard } from '../aard/Aard';
import { Stretch } from '../../../common/interfaces/StretchInterface'; 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. * VideoData handles CSS for the video element.
@ -65,8 +65,7 @@ class VideoData {
//#endregion //#endregion
//#region helper objects //#region helper objects
logger: ComponentLogger; logger: Logger;
logAggregator: LogAggregator
settings: Settings; // AARD needs it settings: Settings; // AARD needs it
siteSettings: SiteSettings; siteSettings: SiteSettings;
pageInfo: PageInfo; pageInfo: PageInfo;
@ -77,8 +76,6 @@ class VideoData {
eventBus: EventBus; eventBus: EventBus;
extensionStatus: ExtensionStatus; extensionStatus: ExtensionStatus;
private currentEnvironment: ExtensionEnvironment;
//#endregion //#endregion
@ -100,11 +97,6 @@ class VideoData {
}, },
'set-run-level': { 'set-run-level': {
function: (runLevel: RunLevel) => this.setRunLevel(runLevel) function: (runLevel: RunLevel) => this.setRunLevel(runLevel)
},
'uw-environment-change': {
function: () => {
this.onEnvironmentChanged();
}
} }
} }
@ -116,9 +108,7 @@ class VideoData {
* @param pageInfo * @param pageInfo
*/ */
constructor(video, settings: Settings, siteSettings: SiteSettings, pageInfo: PageInfo){ constructor(video, settings: Settings, siteSettings: SiteSettings, pageInfo: PageInfo){
this.logAggregator = pageInfo.logAggregator; this.logger = pageInfo.logger;
this.logger = new ComponentLogger(this.logAggregator, 'VideoData', {});
this.arSetupComplete = false; this.arSetupComplete = false;
this.video = video; this.video = video;
this.destroyed = false; this.destroyed = false;
@ -168,7 +158,7 @@ class VideoData {
if (!this.video?.videoWidth || !this.video?.videoHeight || this.video.readyState < 2) { if (!this.video?.videoWidth || !this.video?.videoHeight || this.video.readyState < 2) {
return; // onVideoLoaded is a lie in this case 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.hasDrm = hasDrm(this.video);
this.eventBus.send( this.eventBus.send(
@ -180,12 +170,12 @@ class VideoData {
this.videoDimensionsLoaded = true; this.videoDimensionsLoaded = true;
try { try {
await this.setupStageTwo(); 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) { } 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) { } 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.restoreCrop();
this.videoDimensionsLoaded = true; this.videoDimensionsLoaded = true;
@ -201,7 +191,7 @@ class VideoData {
if (!this.mutationObserver) { if (!this.mutationObserver) {
this.setupMutationObserver(); this.setupMutationObserver();
} }
// this.eventBus.send('inject-css', this.baseVideoCss); this.eventBus.send('inject-css', this.baseVideoCss);
} catch (e) { } catch (e) {
console.error('Failed to inject base css!', e); console.error('Failed to inject base css!', e);
} }
@ -214,11 +204,11 @@ class VideoData {
//#region <video> event handlers //#region <video> event handlers
onLoadedData() { 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(); this.onVideoLoaded();
} }
onLoadedMetadata() { 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(); this.onVideoLoaded();
} }
onTimeUpdate() { onTimeUpdate() {
@ -232,7 +222,7 @@ class VideoData {
* Sets up event listeners for this video * Sets up event listeners for this video
*/ */
async setupEventListeners() { 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 is in case extension loads before the video
this.video.addEventListener('loadeddata', this.onLoadedData.bind(this)); 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 one is in case extension loads after the video is loaded
this.video.addEventListener('timeupdate', this.onTimeUpdate.bind(this)); 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 // 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 * cleans up handlers and stuff when the show is over
*/ */
destroy() { 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) { if (this.video) {
this.video.classList.remove(this.userCssClassName); this.video.classList.remove(this.userCssClassName);
@ -399,27 +389,8 @@ class VideoData {
} }
//#endregion //#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}) { 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) { if (this.runLevel === runLevel) {
return; // also no need to propagate to the player return; // also no need to propagate to the player
} }
@ -475,10 +446,10 @@ class VideoData {
restoreCrop() { restoreCrop() {
if (!this.resizer) { 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; 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. // if we have default crop set for this page, apply this.
// otherwise, reset crop // otherwise, reset crop
@ -491,7 +462,7 @@ class VideoData {
this.stopArDetection(); this.stopArDetection();
this.startArDetection(); this.startArDetection();
} catch (e) { } 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; let confirmAspectRatioRestore = false;
if (!this.video) { 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(); this.destroy();
return; return;
} }
if (!this.enabled) { 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; return;
} }
@ -554,8 +525,8 @@ class VideoData {
onVideoDimensionsChanged(mutationList, observer) { onVideoDimensionsChanged(mutationList, observer) {
if (!mutationList || this.video === undefined) { // something's wrong if (!mutationList || this.video === undefined) { // something's wrong
if (observer && this.video) { if (observer && this.video) {
this.logger.warn( this.logger.log(
'onVideoDimensionChanged', 'warn', 'debug',
'onVideoDimensionChanged encountered a weird state. video and observer exist, but mutationlist does not.\n\nmutationList:', mutationList, 'onVideoDimensionChanged encountered a weird state. video and observer exist, but mutationlist does not.\n\nmutationList:', mutationList,
'\nobserver:', observer, '\nobserver:', observer,
'\nvideo:', this.video, '\nvideo:', this.video,
@ -575,15 +546,13 @@ class VideoData {
*/ */
private _processDimensionsChanged() { private _processDimensionsChanged() {
if (!this.player) { 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; return;
} }
// adding player observer taught us that if element size gets triggered by a class, then // 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 // the 'style' attributes don't necessarily trigger. This means we also need to trigger
// restoreAr here, in case video size was changed this way // restoreAr here, in case video size was changed this way
this.player.forceRefreshPlayerElement(); this.player.forceRefreshPlayerElement();
this.eventBus.send('uw-environment-change', {newEnvironment: this.player.environment});
this.restoreAr(); this.restoreAr();
// sometimes something fucky wucky happens and mutations aren't detected correctly, so we // sometimes something fucky wucky happens and mutations aren't detected correctly, so we
@ -602,7 +571,7 @@ class VideoData {
this._processDimensionsChanged, this._processDimensionsChanged,
250, 250,
{ {
// leading: true, leading: true,
trailing: true trailing: true
} }
); );
@ -708,7 +677,7 @@ class VideoData {
startArDetection() { startArDetection() {
this.logger.info('startArDetection', 'starting AR detection'); this.logger.log('info', 'debug', "[VideoData::startArDetection] starting AR detection")
if(this.destroyed || this.invalid) { if(this.destroyed || this.invalid) {
// throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}}; // throw {error: 'VIDEO_DATA_DESTROYED', data: {videoData: this}};
return; return;
@ -725,9 +694,9 @@ class VideoData {
if (!this.aard) { if (!this.aard) {
this.initArDetection(); this.initArDetection();
} }
this.aard.startCheck(); this.aard.start();
} catch (e) { } 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(){ checkVideoSizeChange(){
const videoWidth = this.video.offsetWidth; const videoWidth = this.video.offsetWidth;
const videoHeight = this.video.offsetHeight; 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) { 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 && if ( this.video &&
( this.dimensions?.width != videoWidth || ( this.dimensions?.width != videoWidth ||
this.dimensions?.height != videoHeight ) 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);
} }
} }

View File

@ -3,29 +3,29 @@ import Debug from '../../conf/Debug';
import Scaler, { CropStrategy, VideoDimensions } from './Scaler'; import Scaler, { CropStrategy, VideoDimensions } from './Scaler';
import Stretcher from './Stretcher'; import Stretcher from './Stretcher';
import Zoom from './Zoom'; 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 StretchType from '../../../common/enums/StretchType.enum';
import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum'; import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum';
import AspectRatioType from '../../../common/enums/AspectRatioType.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 VideoData from '../video-data/VideoData';
import EventBus from '../EventBus'; import EventBus from '../EventBus';
import { _cp } from '../../../common/js/utils'; import { _cp } from '../../../common/js/utils';
import Settings from '../Settings'; 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 { RunLevel } from '../../enum/run-level.enum';
import * as _ from 'lodash'; import * as _ from 'lodash';
import getElementStyles from '../../util/getElementStyles'; import getElementStyles from '../../util/getElementStyles';
import { Stretch } from '../../../common/interfaces/StretchInterface'; import { Stretch } from '../../../common/interfaces/StretchInterface';
import { ComponentLogger } from '../logging/ComponentLogger';
if(Debug.debug) { if(Debug.debug) {
console.log("Loading: Resizer.js"); 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 * 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. * component needs to zoom, and which component needs to stretch.
@ -41,7 +41,7 @@ class Resizer {
//#endregion //#endregion
//#region helper objects //#region helper objects
logger: ComponentLogger; logger: Logger;
settings: Settings; settings: Settings;
siteSettings: SiteSettings; siteSettings: SiteSettings;
scaler: Scaler; scaler: Scaler;
@ -63,8 +63,6 @@ class Resizer {
currentCssValidFor: any; currentCssValidFor: any;
currentVideoSettings: any; currentVideoSettings: any;
private effectiveZoom: {x: number, y: number} = {x: 1, y: 1};
_lastAr: Ar = {type: AspectRatioType.Initial}; _lastAr: Ar = {type: AspectRatioType.Initial};
set lastAr(x: Ar) { set lastAr(x: Ar) {
// emit updates for UI when setting lastAr, but only if AR really changed // emit updates for UI when setting lastAr, but only if AR really changed
@ -85,22 +83,17 @@ class Resizer {
//#endregion //#endregion
cycleableAspectRatios: Ar[]; cycleableAspectRatios: Ar[];
cycleableZoomAspectRatios: Ar[];
nextCycleOptionIndex = 0; nextCycleOptionIndex = 0;
//#region event bus configuration //#region event bus configuration
private eventBusCommands = { 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': [{ 'set-ar': [{
function: (config: any) => { function: (config: any) => {
this.manualZoom = false; // this only gets called from UI or keyboard shortcuts, making this action safe. this.manualZoom = false; // this only gets called from UI or keyboard shortcuts, making this action safe.
if (config.type !== AspectRatioType.Cycle) { if (config.type !== AspectRatioType.Cycle) {
this.setAr({...config, variant: ArVariant.Crop}); this.setAr(config);
} else { } else {
// if we manually switched to a different aspect ratio, cycle from that ratio forward // 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); 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.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; 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': [{ 'set-alignment': [{
function: (config: any) => { function: (config: any) => {
this.setVideoAlignment(config.x, config.y); this.setVideoAlignment(config.x, config.y);
@ -143,12 +118,10 @@ class Resizer {
} }
}], }],
'set-zoom': [{ 'set-zoom': [{
function: (config: any) => { function: (config: any) => this.setZoom(config.zoom, config.axis, config.noAnnounce)
this.setZoom(config?.zoom ?? {zoom: 1});
}
}], }],
'change-zoom': [{ 'change-zoom': [{
function: (config: any) => this.zoomStep(config.zoom) function: (config: any) => this.zoomStep(config.step)
}], }],
'get-ar': [{ 'get-ar': [{
function: () => this.eventBus.send('uw-config-broadcast', {type: 'ar', config: this.lastAr}) function: () => this.eventBus.send('uw-config-broadcast', {type: 'ar', config: this.lastAr})
@ -184,7 +157,7 @@ class Resizer {
constructor(videoData) { constructor(videoData) {
this.resizerId = (Math.random()*100).toFixed(0); this.resizerId = (Math.random()*100).toFixed(0);
this.videoData = videoData; this.videoData = videoData;
this.logger = new ComponentLogger(videoData.logAggregator, 'Resizer'); this.logger = videoData.logger;
this.video = videoData.video; this.video = videoData.video;
this.settings = videoData.settings; this.settings = videoData.settings;
this.siteSettings = videoData.siteSettings; this.siteSettings = videoData.siteSettings;
@ -215,11 +188,6 @@ class Resizer {
.filter(x => [AspectRatioType.FitHeight, AspectRatioType.FitWidth, AspectRatioType.Fixed, AspectRatioType.Reset].includes(x?.arguments?.type)) .filter(x => [AspectRatioType.FitHeight, AspectRatioType.FitWidth, AspectRatioType.Fixed, AspectRatioType.Reset].includes(x?.arguments?.type))
.map(x => x.arguments) as Ar[]; .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.nextCycleOptionIndex = 0;
this.userCssClassName = videoData.userCssClassName; this.userCssClassName = videoData.userCssClassName;
} }
@ -238,7 +206,7 @@ class Resizer {
} }
destroy(){ 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; this.destroyed = true;
} }
@ -255,7 +223,7 @@ class Resizer {
let ratioOut; let ratioOut;
if (!this.videoData.video) { 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(); this.videoData.destroy();
return null; return null;
} }
@ -264,7 +232,7 @@ class Resizer {
if (! this.videoData.player.dimensions) { if (! this.videoData.player.dimensions) {
ratioOut = screen.width / screen.height; ratioOut = screen.width / screen.height;
} else { } 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; ratioOut = this.videoData.player.dimensions.width / this.videoData.player.dimensions.height;
} }
@ -280,7 +248,7 @@ class Resizer {
ar.ratio = ratioOut < fileAr ? ratioOut : fileAr; ar.ratio = ratioOut < fileAr ? ratioOut : fileAr;
} }
else if(ar.type === AspectRatioType.Reset){ 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; ar.ratio = fileAr;
} else { } else {
return null; return null;
@ -289,7 +257,7 @@ class Resizer {
return ar; return ar;
} }
updateAr(ar: Ar) { updateAr(ar) {
if (!ar) { if (!ar) {
return; 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) { async setAr(ar: Ar, lastAr?: Ar) {
if (this.destroyed || ar == null) { if (this.destroyed) {
return; return;
} }
@ -345,8 +294,11 @@ class Resizer {
} }
// handle autodetection stuff // handle autodetection stuff
if (this.handleAard(ar)) { if (ar.type === AspectRatioType.Automatic) {
this.videoData.aard?.start();
return; return;
} else if (ar.type !== AspectRatioType.AutomaticUpdate) {
this.videoData.aard?.stop();
} }
if (ar.type !== AspectRatioType.AutomaticUpdate) { if (ar.type !== AspectRatioType.AutomaticUpdate) {
@ -354,11 +306,15 @@ class Resizer {
} }
if (!this.video.videoWidth || !this.video.videoHeight) { 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.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; 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 !== 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.type !== this.lastAr.type // this also means aspect ratio has changed
|| ar.ratio !== this.lastAr.ratio // 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.zoom.reset();
this.resetPan(); this.resetPan();
@ -395,7 +350,7 @@ class Resizer {
// I'm not sure whether they do. Check that. // I'm not sure whether they do. Check that.
ar = this.calculateRatioForLegacyOptions(ar); ar = this.calculateRatioForLegacyOptions(ar);
if (! 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; return;
} }
this.lastAr = {type: ar.type, ratio: ar.ratio}; this.lastAr = {type: ar.type, ratio: ar.ratio};
@ -405,12 +360,27 @@ class Resizer {
this.videoData.destroy(); 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 // do stretch thingy
if ([StretchType.NoStretch, StretchType.Conditional, StretchType.FixedSource].includes(this.stretcher.stretch.type)) { if ([StretchType.NoStretch, StretchType.Conditional, StretchType.FixedSource].includes(this.stretcher.stretch.type)) {
stretchFactors = this.scaler.calculateCrop(ar); stretchFactors = this.scaler.calculateCrop(ar);
if(! stretchFactors || stretchFactors.error){ if(! stretchFactors || stretchFactors.error){
this.logger.error('setAr', ` <rid:${this.resizerId}> failed to set AR due to problem with calculating crop. Error:`, 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'){ if (stretchFactors?.error === 'no_video'){
this.videoData.destroy(); this.videoData.destroy();
return; return;
@ -431,24 +401,25 @@ class Resizer {
} else if (this.stretcher.stretch.type === StretchType.FixedSource) { } else if (this.stretcher.stretch.type === StretchType.FixedSource) {
this.stretcher.applyStretchFixedSource(stretchFactors); 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.NoStretch ? 'stretch-free crop.' :
this.stretcher.stretch.type === StretchType.Conditional ? 'crop with conditional StretchType.' : 'crop with fixed stretch', this.stretcher.stretch.type === StretchType.Conditional ? 'crop with conditional StretchType.' : 'crop with fixed stretch',
'Stretch factors are:', stretchFactors 'Stretch factors are:', stretchFactors
); );
} else if (this.stretcher.stretch.type === StretchType.Hybrid) { } else if (this.stretcher.stretch.type === StretchType.Hybrid) {
stretchFactors = this.stretcher.calculateStretch(ar.ratio); 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) { } 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) { } else if (this.stretcher.stretch.type === StretchType.Basic) {
stretchFactors = this.stretcher.calculateBasicStretch(); 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 { } else {
stretchFactors = this.scaler.calculateCrop(ar); stretchFactors = this.scaler.calculateCrop(ar);
this.logger.error( this.logger.log(
'setAr', 'error', 'debug',
'Okay wtf happened? If you see this, something has gone wrong. Pretending stretchMode is set tu NoStretch. Stretch factors are:', stretchFactors, '[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, "\n------[ i n f o d u m p ]------\nstretcher:", this.stretcher,
'\nargs: ar (corrected for legacy):', ar, 'last ar (optional argument):', lastAr '\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}) { 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 // let the UI know
// if(!options?.noAnnounce) { if(!options?.noAnnounce) {
// this.videoData.eventBus.send('announce-zoom', this.manualZoom ? {x: this.zoom.scale, y: this.zoom.scaleY} : this.zoom.effectiveZoom); this.videoData.eventBus.send('announce-zoom', {x: stretchFactors.xFactor, y: stretchFactors.yFactor});
// } }
let translate = this.computeOffsets(stretchFactors, options?.ar); let translate = this.computeOffsets(stretchFactors, options?.ar);
this.applyCss(stretchFactors, translate); this.applyCss(stretchFactors, translate);
@ -505,7 +476,7 @@ class Resizer {
const relativeX = (event.pageX - player.offsetLeft) / player.offsetWidth; const relativeX = (event.pageX - player.offsetLeft) / player.offsetWidth;
const relativeY = (event.pageY - player.offsetTop) / player.offsetHeight; 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); this.setPan(relativeX, relativeY);
} }
@ -562,13 +533,9 @@ class Resizer {
this.restore(); this.restore();
} }
/**
* Restores aspect ratio to last known aspect ratio
* @returns
*/
restore() { restore() {
if (!this.manualZoom) { 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 // this is true until we verify that css has actually been applied
if(this.lastAr.type === AspectRatioType.Initial){ if(this.lastAr.type === AspectRatioType.Initial){
@ -606,36 +573,9 @@ class Resizer {
} }
} }
private _setZoomTimeout; setZoom(zoomLevel: number, axis?: 'x' | 'y', noAnnounce?) {
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;
}
this.manualZoom = true; this.manualZoom = true;
this.zoom.setZoom(zoomLevel); this.zoom.setZoom(zoomLevel, axis, noAnnounce);
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
);
} }
zoomStep(step){ zoomStep(step){
@ -753,7 +693,7 @@ class Resizer {
private _computeOffsetsRecursionGuard: boolean = false; private _computeOffsetsRecursionGuard: boolean = false;
computeOffsets(stretchFactors: VideoDimensions, ar?: Ar){ 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(); const {realVideoWidth, realVideoHeight, marginX, marginY} = this.computeVideoDisplayedDimensions();
@ -821,9 +761,8 @@ class Resizer {
} }
} }
this.logger.info( this.logger.log(
'computeOffsets', 'info', ['debug', 'resizer'], "[Resizer::_res_computeOffsets] <rid:"+this.resizerId+"> calculated offsets:",
`<rid:${this.resizerId}> calculated offsets:`,
'\n\n---- elements ----', '\n\n---- elements ----',
'\nplayer element: ', this.videoData.player.element, '\nplayer element: ', this.videoData.player.element,
'\nvideo element: ', this.videoData.video, '\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 // 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 // element. Chances are our video is not getting aligned correctly
if ( 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) ||
(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.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`, `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.` `Player element needs to be checked.`
); )
// sometimes this appears to randomly recurse. // sometimes this appears to randomly recurse.
// There seems to be no way to reproduce it. // 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 // apply extra CSS here. In case of duplicated properties, extraCss overrides
// default styleString // default styleString
if (! this.video) { 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(); this.videoData.destroy();
return; 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): // save stuff for quick tests (before we turn numbers into css values):
this.currentVideoSettings = { this.currentVideoSettings = {
@ -974,18 +911,18 @@ class Resizer {
// inject new CSS or replace existing one // inject new CSS or replace existing one
if (!this.userCss) { 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.eventBus.send('inject-css', {cssString: newCssString});
this.userCss = newCssString; this.userCss = newCssString;
} else if (newCssString !== this.userCss) { } 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 // we only replace css if it
this.eventBus.send('replace-css', {oldCssString: this.userCss, newCssString}); this.eventBus.send('replace-css', {oldCssString: this.userCss, newCssString});
this.userCss = newCssString; this.userCss = newCssString;
} else { } 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.");
} }
} }

View File

@ -1,7 +1,8 @@
import Debug from '../../conf/Debug';
import AspectRatioType from '../../../common/enums/AspectRatioType.enum'; import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
import BrowserDetect from '../../conf/BrowserDetect';
import VideoData from '../video-data/VideoData'; import VideoData from '../video-data/VideoData';
import { Ar, ArVariant } from '../../../common/interfaces/ArInterface'; import Logger from '../Logger';
import { ComponentLogger } from '../logging/ComponentLogger';
export enum CropStrategy { export enum CropStrategy {
@ -44,13 +45,13 @@ export type VideoDimensions = {
class Scaler { class Scaler {
//#region helper objects //#region helper objects
conf: VideoData; conf: VideoData;
logger: ComponentLogger; logger: Logger;
//#endregion //#endregion
// functions // functions
constructor(videoData) { constructor(videoData) {
this.conf = videoData; this.conf = videoData;
this.logger = new ComponentLogger(videoData.logAggregator, 'Scaler', {}); this.logger = videoData.logger;
} }
@ -63,7 +64,7 @@ class Scaler {
let ratioOut; let ratioOut;
if (!this.conf.video) { 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(); this.conf.destroy();
return null; return null;
} }
@ -91,7 +92,7 @@ class Scaler {
return ratioOut; return ratioOut;
} }
else if (ar.type === AspectRatioType.Reset) { 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; ar.ar = fileAr;
return fileAr; return fileAr;
} }
@ -99,7 +100,7 @@ class Scaler {
return null; 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 * STEP 1: NORMALIZE ASPECT RATIO
* *
@ -135,7 +136,7 @@ class Scaler {
} }
if(!this.conf.video){ 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(); this.conf.destroy();
return {error: "no_video"}; return {error: "no_video"};
@ -143,7 +144,7 @@ class Scaler {
if (this.conf.video.videoWidth == 0 || this.conf.video.videoHeight == 0) { 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 // 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 // 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"}; return {error: "illegal_video_dimensions"};
} }
@ -163,22 +164,16 @@ class Scaler {
// handle fuckie-wuckies // handle fuckie-wuckies
if (!ar.ratio){ 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}; 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 ){ if( (! this.conf.player.dimensions) || this.conf.player.dimensions.width === 0 || this.conf.player.dimensions.height === 0 ){
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"}; return {error: "this.conf.player.dimensions_error"};
} }
}
// we can finally start computing required video dimensions now: // 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 = { const videoDimensions: VideoDimensions = {
xFactor: 1, 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; return videoDimensions;
} }
@ -217,11 +212,7 @@ class Scaler {
* @param {*} streamAr * @param {*} streamAr
* @param {*} playerAr * @param {*} playerAr
*/ */
calculateCropCore(videoDimensions: VideoDimensions, ar: number, streamAr: number, playerAr: number, variant?: ArVariant) { calculateCropCore(videoDimensions: VideoDimensions, ar: number, streamAr: number, playerAr: number) {
if (variant === ArVariant.Zoom) {
playerAr = ar;
}
if (streamAr < playerAr) { if (streamAr < playerAr) {
if (streamAr < ar){ if (streamAr < ar){
// in this situation we have to crop letterbox on top/bottom of the player // 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)); const letterboxRatio = (1 - (playerAr / ar));
videoDimensions.relativeCropLimits = { videoDimensions.relativeCropLimits = {
top: ar > streamAr ? ( ar >= playerAr ? (letterboxRatio * -0.5) : 0) : 0, top: ar > streamAr ? ( ar > playerAr ? (letterboxRatio * -0.5) : 0) : 0,
left: ar < streamAr ? ( ar <= playerAr ? (-0.5 / letterboxRatio) : 0) : 0, left: ar < streamAr ? ( ar < playerAr ? (-0.5 / letterboxRatio) : 0) : 0,
} }
videoDimensions.preventAlignment = { videoDimensions.preventAlignment = {
x: ar > playerAr, // video is wider than player, so it's full width already x: ar > playerAr, // video is wider than player, so it's full width already

View File

@ -1,9 +1,11 @@
import { SiteSettings } from './../settings/SiteSettings'; import { SiteSettings } from './../settings/SiteSettings';
import StretchType from '../../../common/enums/StretchType.enum';
import BrowserDetect from '../../conf/BrowserDetect'; import BrowserDetect from '../../conf/BrowserDetect';
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
import VideoData from '../video-data/VideoData'; import VideoData from '../video-data/VideoData';
import Logger from '../Logger';
import Settings from '../Settings'; import Settings from '../Settings';
import { Stretch } from '../../../common/interfaces/StretchInterface'; import { Stretch } from '../../../common/interfaces/StretchInterface';
import { ComponentLogger } from '../logging/ComponentLogger';
// računa vrednosti za transform-scale (x, y) // računa vrednosti za transform-scale (x, y)
// transform: scale(x,y) se uporablja za raztegovanje videa, ne pa za približevanje // transform: scale(x,y) se uporablja za raztegovanje videa, ne pa za približevanje
@ -17,7 +19,7 @@ class Stretcher {
//#region helper objects //#region helper objects
conf: VideoData; conf: VideoData;
logger: ComponentLogger; logger: Logger;
settings: Settings; settings: Settings;
siteSettings: SiteSettings; siteSettings: SiteSettings;
//#endregion //#endregion
@ -27,7 +29,7 @@ class Stretcher {
// functions // functions
constructor(videoData) { constructor(videoData) {
this.conf = videoData; this.conf = videoData;
this.logger = new ComponentLogger(videoData.logAggregator, 'Stretcher', {});; this.logger = videoData.logger;
this.siteSettings = videoData.siteSettings; this.siteSettings = videoData.siteSettings;
this.settings = videoData.settings; this.settings = videoData.settings;
@ -109,7 +111,7 @@ class Stretcher {
// * we squeeze X axis, if target AR is narrower than player size // * 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 // * 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} postCropStretchFactors: x=${postCropStretchFactors.xFactor} y=${postCropStretchFactors.yFactor}
fixedStretchRatio: ${this.stretch.ratio} fixedStretchRatio: ${this.stretch.ratio}
videoAr: ${streamAr} videoAr: ${streamAr}
@ -118,7 +120,7 @@ squeezeFactor: ${squeezeFactor}`, '\nvideo', this.conf.video);
postCropStretchFactors.xFactor *= squeezeFactor; 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; return postCropStretchFactors;
} }

View File

@ -1,12 +1,9 @@
import { ComponentLogger } from '../logging/ComponentLogger'; import Debug from '../../conf/Debug';
import Logger from '../Logger';
import VideoData from '../video-data/VideoData'; import VideoData from '../video-data/VideoData';
// računa približevanje ter računa/popravlja odmike videa
// calculates zooming and video offsets/panning // 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 { class Zoom {
//#region flags //#region flags
@ -15,7 +12,7 @@ class Zoom {
//#region helper objects //#region helper objects
conf: VideoData; conf: VideoData;
logger: ComponentLogger; logger: Logger;
//#endregion //#endregion
//#region misc data //#region misc data
@ -24,81 +21,86 @@ class Zoom {
logScale: number = 0; logScale: number = 0;
logScaleY: number = 0; logScaleY: number = 0;
scaleStep: number = 0.1; scaleStep: number = 0.1;
logMinScale: number = -1; // 50% (log2(0.5) = -1) minScale: number = -1; // 50% (log2(0.5) = -1)
logMaxScale: number = 3; // 800% (log2(8) = 3) maxScale: number = 3; // 800% (log2(8) = 3)
minScale = 0.5;
maxScale = 8;
//#endregion //#endregion
effectiveZoom: {x: number, y: number}; // we're setting this in Resizer based on Resizer data!
constructor(videoData) { constructor(videoData) {
this.conf = videoData; this.conf = videoData;
this.logger = new ComponentLogger(videoData.logAggregator, 'Zoom', {}); this.logger = videoData.logger;
} }
reset(){ reset(){
this.scale = 1; this.scale = 1;
this.scaleY = 1;
this.logScale = 0; this.logScale = 0;
this.logScaleY = 0;
} }
/** /**
* Increases zoom by a given amount. Does not allow per-axis zoom. * 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. * Will set zoom level to x axis (+ given amount) if x and y zooms differ.
* @param amount * @param amount
* @param axis leave undefined to apply zoom to both axes
*/ */
zoomStep(amount: number, axis?: 'x' | 'y') { zoomStep(amount){
const effectiveLog = { this.logScale += amount;
x: Math.log2(this.effectiveZoom.x),
y: Math.log2(this.effectiveZoom.y)
};
let newLog = axis === 'y' ? effectiveLog.y : effectiveLog.x; if (this.logScale <= this.minScale) {
newLog += amount; this.logScale = this.minScale;
newLog = Math.min(Math.max(newLog, LOG_MIN_SCALE), LOG_MAX_SCALE); }
if (this.logScale >= this.maxScale) {
this.logScale = this.maxScale;
}
// if axis is undefined, both of this statements should trigger) this.logScaleY = this.logScale;
if (axis !== 'y') {
this.logScale = newLog;
}
if (axis !== 'x') {
this.logScaleY = newLog;
}
this.scale = Math.pow(2, 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(); this.processZoom();
} }
/** setZoom(scale: number, axis?: 'x' |'y', noAnnounce?){
* Sets zoom to specific value this.logger.log('info', 'debug', "[Zoom::setZoom] Setting zoom to", scale, "!");
* @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
};
this.scale = Math.min(Math.max(scaleIn.x, MIN_SCALE), MAX_SCALE); // NOTE: SCALE IS NOT LOGARITHMIC
this.scaleY = Math.min(Math.max(scaleIn.y, MIN_SCALE), MAX_SCALE); 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(); this.processZoom();
} }
processZoom() { processZoom() {
this.conf.resizer.toFixedAr(); // this.conf.resizer.toFixedAr();
this.conf.resizer.applyScaling({xFactor: this.scale, yFactor: this.scaleY}, {noAnnounce: true}); 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; export default Zoom;

View File

@ -5,18 +5,17 @@
import UWContent from './UWContent'; import UWContent from './UWContent';
if(process.env.CHANNEL !== 'stable'){ if(process.env.CHANNEL !== 'stable'){
console.warn("\n\n\n\n\n\n ——— Sᴛλʀᴛɪɴɢ Uʟᴛʀᴀɪɪʏ ———\n << ʟᴏᴀᴅɪɴɢ ᴍᴀɪɴ ꜰɪʟᴇ >>\n\n\n\n");
let isIframe;
try { try {
isIframe = window.self !== window.top; if(window.self !== window.top){
} catch (e) { console.info("%cWe aren't in an iframe.", "color: #afc, background: #174");
isIframe = true; }
else{
console.info("%cWe are in an iframe!", "color: #fea, background: #d31", window.self, window.top);
}
} catch (e) {
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(); const main = new UWContent();

View File

@ -53,9 +53,9 @@
</template> </template>
<script> <script>
import Debug from '../../ext/conf/Debug';
import BrowserDetect from '../../ext/conf/BrowserDetect'; import BrowserDetect from '../../ext/conf/BrowserDetect';
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator'; import Logger from '../../ext/lib/Logger';
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
export default { export default {
data () { data () {
@ -75,7 +75,6 @@ export default {
settings: {}, settings: {},
settingsInitialized: false, settingsInitialized: false,
logger: {}, logger: {},
logAggregator: {},
siteTabDisabled: false, siteTabDisabled: false,
videoTabDisabled: false, videoTabDisabled: false,
canShowVideoTab: {canShow: true, warning: true}, canShowVideoTab: {canShow: true, warning: true},
@ -83,20 +82,18 @@ export default {
} }
}, },
async created() { async created() {
this.logAggregator = new LogAggregator(''); this.logger = new Logger();
this.logger = new ComponentLogger(this.logAggregator, 'App.vue'); 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(); await this.settings.init();
this.settingsInitialized = true; this.settingsInitialized = true;
}, },
components: { components: {
}, },
methods: { methods: {
updateConfig() {
this.settings.init();
this.$nextTick( () => this.$forceUpdate());
}
} }
} }
</script> </script>

View File

@ -54,9 +54,9 @@
</template> </template>
<script> <script>
import Debug from '../../ext/conf/Debug';
import BrowserDetect from '../../ext/conf/BrowserDetect'; import BrowserDetect from '../../ext/conf/BrowserDetect';
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator'; import Logger from '../../ext/lib/Logger';
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger';
export default { export default {
data () { data () {
@ -75,7 +75,6 @@ export default {
currentZoom: 1, currentZoom: 1,
settings: {}, settings: {},
settingsInitialized: false, settingsInitialized: false,
logAggregator: {},
logger: {}, logger: {},
siteTabDisabled: false, siteTabDisabled: false,
videoTabDisabled: false, videoTabDisabled: false,
@ -84,20 +83,18 @@ export default {
} }
}, },
async created() { async created() {
this.logAggregator = new LogAggregator(''); this.logger = new Logger();
this.logger = new ComponentLogger(this.logAggregator, 'App.vue'); 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(); await this.settings.init();
this.settingsInitialized = true; this.settingsInitialized = true;
}, },
components: { components: {
}, },
methods: { methods: {
updateConfig() {
this.settings.init();
this.$nextTick( () => this.$forceUpdate());
}
} }
} }
</script> </script>

View File

@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "Ultrawidify", "name": "Ultrawidify",
"description": "Removes black bars on ultrawide videos and offers advanced options to fix aspect ratio.", "description": "Removes black bars on ultrawide videos and offers advanced options to fix aspect ratio.",
"version": "6.3.0", "version": "6.2.3",
"icons": { "icons": {
"32":"res/icons/uw-32.png", "32":"res/icons/uw-32.png",
"64":"res/icons/uw-64.png" "64":"res/icons/uw-64.png"

View File

@ -111,6 +111,10 @@
<script> <script>
import Donate from '../common/misc/Donate.vue'; import Donate from '../common/misc/Donate.vue';
import SuperAdvancedSettings from './SuperAdvancedSettings.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 Settings from '../ext/lib/Settings';
import GeneralSettings from './GeneralSettings'; import GeneralSettings from './GeneralSettings';
import ControlsSettings from './controls-settings/ControlsSettings'; import ControlsSettings from './controls-settings/ControlsSettings';
@ -118,8 +122,8 @@ import AddEditActionPopup from './controls-settings/AddEditActionPopup';
import ConfirmPopup from './common/ConfirmationPopup'; import ConfirmPopup from './common/ConfirmationPopup';
import About from './about' import About from './about'
import AutodetectionSettings from './AutodetectionSettings'; import AutodetectionSettings from './AutodetectionSettings';
import { LogAggregator } from '@src/ext/lib/logging/LogAggregator'; // import SuperAdvancedSettings from './'
import { ComponentLogger } from '@src/ext/lib/logging/ComponentLogger'; import Logger from '../ext/lib/Logger';
export default { export default {
name: "Ultrawidify", name: "Ultrawidify",
@ -128,7 +132,6 @@ export default {
selectedTab: "general", selectedTab: "general",
selectedTabTitle: "General settings", selectedTabTitle: "General settings",
settings: {}, settings: {},
logAggregator: {},
logger: {}, logger: {},
settingsInitialized: false, settingsInitialized: false,
editActionPopupVisible: false, editActionPopupVisible: false,
@ -142,10 +145,12 @@ export default {
} }
}, },
async created () { async created () {
this.logAggregator = new LogAggregator(''); this.logger = new Logger();
this.logger = new ComponentLogger(this.logAggregator, 'App.vue'); 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(); await this.settings.init();
this.settingsInitialized = true; this.settingsInitialized = true;

View File

@ -601,6 +601,17 @@ export default {
}, },
created() { created() {
this.sensitivity = this.getSensitivity(); 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: { methods: {
setArCheckFrequency(event) { setArCheckFrequency(event) {