Compare commits

...

364 Commits

Author SHA1 Message Date
b2bfde4426 Fix sliders, but for real this time (pt 2) 2024-12-28 03:41:23 +01:00
8602b53260 Fix sliders, but for real 2024-12-28 03:14:53 +01:00
145b361581 Fix 2024-12-28 02:05:42 +01:00
7743dd8e7f Add sliders 2024-12-28 02:02:01 +01:00
ed2886d449 add minEnabledHeight to settings 2024-12-28 01:42:13 +01:00
002dfacc3e change timeout a bit 2024-12-28 00:42:53 +01:00
fd9fbc32a8 Remove some console.logs 2024-12-28 00:42:40 +01:00
e54deeb4f7 save settings when changing parameters 2024-12-28 00:41:07 +01:00
c85f211d5e Add reset settings to options 2024-12-27 23:13:12 +01:00
67f848a9ee Unbreak showing popup on hover 2024-12-27 23:12:41 +01:00
fe336122e0 Make crop iptions and stretch options panels editable by default without breaking the popup. Add them to UI and keyboard section of settings 2024-12-27 13:18:31 +01:00
6d8d8493a6 Zone editor mostly finished 2024-12-27 04:41:35 +01:00
5d7d249fff Fix overlay positioning 2024-12-27 04:40:30 +01:00
35ca030dfb Close window when opening zone edotir 2024-12-26 17:54:05 +01:00
fab6fb0f5b Fix mousemove position calculation 2024-12-26 17:41:00 +01:00
3b8fc807f7 Add corner markers to editor resizer area 2024-12-26 17:40:27 +01:00
13cfb9ff14 Checkpoint: aard status indicator + trigger zone editor now kinda works 2024-12-26 14:58:14 +01:00
aabd5e75d8 Update changelog 2024-12-22 23:29:43 +01:00
7be6fb9501 Add fallback option class (currently unused) 2024-12-22 23:20:07 +01:00
9fc63e5cd3 Add player UI settings panel 2024-12-22 23:17:32 +01:00
7a1e8be48a attempt to shut some warnings up (but fail) 2024-12-22 23:16:54 +01:00
d299a65e03 fix — open on correct tab 2024-12-22 23:07:55 +01:00
2337f7a3ba add new options to settings interface 2024-12-22 23:06:14 +01:00
4853d7e2fd allow opening of extension settings window on specific tab 2024-12-22 23:05:56 +01:00
ca1a375f2d Limit when in-player UI shows. Add self-expiring menu options that takes you to the proposed, not-yet-implemented UI settings screen 2024-12-17 02:02:49 +01:00
0d098815a2 fix aspect ratio calculations 2024-11-13 01:46:17 +01:00
30cbfb6ffd Make sure that guard line gets invalidated correctly 2024-11-13 01:45:45 +01:00
4c7deffdfe Do full reset of AARD test result every time aard restarts 2024-11-13 01:43:07 +01:00
15133e561f Get autodetection to work at least on a basic level 2024-11-07 02:26:19 +01:00
c235b52e3b fix canvas initialization, add add methods to attach canvas to main body for ez debug 2024-11-07 00:18:40 +01:00
903f356252 Make the context menu slightly less rudimentary 2024-10-24 01:47:46 +02:00
54c44b4379 Add very rudimentary context menu 2024-10-23 01:23:54 +02:00
dcbeecc8cf fix syntax 2024-10-23 01:23:37 +02:00
bf182c65ad Aard should be nominally complete. Probably. 2024-10-22 23:43:25 +02:00
7c5e4101b0 Finish aspect ratio check 2024-10-21 01:08:03 +02:00
2736ac418f WIP on aspect ratio detection 2024-10-19 16:04:20 +02:00
e2dac10501 Finish guardline/image line tests 2024-10-15 17:38:04 +02:00
c15d7ab28e Start re-implementing aspect ratio detection (now with more documentation) 2024-10-14 00:28:13 +02:00
de78f8b1d5 Switch to gl canvas 2024-10-10 00:45:28 +02:00
e56d32180c Add GL canvas wrapper 2024-10-09 23:54:21 +02:00
6c92837317 wip 2024-06-12 20:29:00 +02:00
d4015a86ef Version bump 2024-06-12 09:17:52 +02:00
4b4be96e26 Fix one more link 2024-06-12 09:09:25 +02:00
823e1d983c Remove currently inactive downloads permission 2024-06-12 09:06:12 +02:00
47fba5b81a Fix external links 2024-06-12 03:15:02 +02:00
ee23ebb705 Update permissions for mmanifest v3 2024-06-12 02:17:03 +02:00
d6d3a95daf Last pre-publish fixes 2024-06-12 01:55:12 +02:00
e839e632fc Merge branch 'feature/player-ui' 2024-06-11 02:39:09 +02:00
cb6f0a9a32 Changelog.MD update 2024-06-11 02:36:36 +02:00
c5515934d9 Do the changelog + highlight changelog tab 2024-06-11 02:22:53 +02:00
7dafe85c53 Do the version bump 2024-06-11 01:59:11 +02:00
3bc65c252d I think i disasbled size requirements for in-player UI. Changed wording in popup to reflect that 2024-06-11 01:56:21 +02:00
d4f9ab9f37 Fix player detector panel 2024-06-11 01:55:02 +02:00
b96227b07a plan ahead 2024-06-10 23:40:49 +02:00
da12ee452c Get aard to start reliably if it's set as default aspect ratio for the site 2024-06-10 23:40:33 +02:00
99df043b5f Fix Aard pausing/unpausing when not intended 2024-06-10 23:27:47 +02:00
54d53f1b91 Remove unused code to make Aard work 2024-06-10 23:27:00 +02:00
3645b9ffcf fix typo 2024-06-10 23:26:08 +02:00
9d0ce25be7 Ensure videos get aligned even when aspect ratio is not defined 2024-06-10 00:08:49 +02:00
1c860f74fb remove wiki link, as it's innacurrate 2024-06-09 21:28:57 +02:00
75e31dc3a4 Improve alignment ui 2024-06-09 21:25:11 +02:00
10a8452709 Include changelog 2024-06-09 21:24:26 +02:00
f824f73491 update some libraries 2024-06-09 21:23:25 +02:00
16c34e3d52 ui styling 2024-06-08 03:23:49 +02:00
76c4599dee prepare class names for a day in the future, when we'll move to tailwind 2024-06-08 03:23:34 +02:00
1af80096c7 Add changelog, about panel 2024-06-08 02:10:50 +02:00
cbb2bb4502 Shortcut editor alert 2024-06-06 00:51:47 +02:00
0f3bcaa8e6 Fix alignment/offset handling, make it as player-invariant as possible 2024-06-06 00:51:34 +02:00
ae6575043e fix keyboard shortcut handling for default shortcuts 2024-06-06 00:50:58 +02:00
0dbd5b456c Fix setting keyboard shortcuts 2024-06-06 00:50:08 +02:00
81f3e62446 Pin and close buttons 2024-06-05 23:52:50 +02:00
e0bb25466d UI/global UI mostly works 2024-06-05 01:08:50 +02:00
cb334aab0d Do a bad code, because chrome doesn't work when you try to write good code 2024-06-03 00:16:40 +02:00
9cef850d06 Make extension work on Chrome. Make extension NOT work on firefox. 2024-06-03 00:16:12 +02:00
79ecc87db4 WIP trying to convince in-player UI to show 2024-06-02 16:06:26 +02:00
12644bfbea get manifest v3 to approximately work 2024-05-30 01:02:09 +02:00
bc6b58aa3f Ensure transform offsets are is rounded 2024-05-07 21:02:22 +02:00
abeded1d03 Further migrations to runlevel 2024-05-07 21:02:04 +02:00
a466f77b6e Merge branch 'master' into feature/player-ui 2024-05-07 20:28:39 +02:00
968ce5432a RunLevel works, even though half-assed 2024-05-07 20:24:50 +02:00
277506b3c8 Start implementing run levels 2024-05-07 19:05:10 +02:00
1c48ea1908 Reorder panels in video settings 2024-05-04 01:08:42 +02:00
f2ac1557a4 Fix icon & heading sizes in popup 2024-05-04 01:08:32 +02:00
7213c1e35f Fix padding 2024-05-04 01:08:25 +02:00
9f5f953b46 'Edit shortcuts' bar banner now doesn't take up vertical space while inactive 2024-05-04 01:08:21 +02:00
3ff6054c20 Fix popup (css now finally workS!) 2024-05-04 01:08:14 +02:00
9fb25254a6 Import CSS in vue component instead of html, otherwise popup is without css 2024-05-03 23:13:55 +02:00
8c6a06d490 move fonts 2024-05-03 23:13:07 +02:00
dd37837848 Update node 2024-04-07 15:37:16 +02:00
5ca9325e16 fix 2024-01-28 13:08:31 +01:00
b8fdf0911a Update changelog 2024-01-28 13:01:45 +01:00
a7302fee4b fix angle detection 2024-01-28 13:01:16 +01:00
41bf4d2764 fix 2024-01-28 13:00:48 +01:00
0970140a3a Version bump 2024-01-27 23:29:03 +01:00
b8eb8ec052 rework about panel 2024-01-27 23:06:56 +01:00
46b7d6a25a Round pixel offset 2024-01-27 22:24:09 +01:00
aab2f5b014 fix ard performance display 2023-10-21 17:59:52 +02:00
6062094e6f Fix crashes in chrome 2023-10-21 17:41:09 +02:00
f7e36619df remove manifestv2 2023-10-21 14:04:02 +02:00
167f5173cd get popup to at least mildly work 2023-09-11 00:43:26 +02:00
c7ebd42fe9 some more moving around 2023-09-10 22:09:26 +02:00
94018c6ee3 Cleanup file/directory structure for the UI 2023-09-10 22:06:16 +02:00
388327ec1d fix fs handlers 2023-09-10 19:51:36 +02:00
0ba56c271d hardcode defautl css string somewhere else 2023-09-10 13:12:35 +02:00
bd1216c275 get player select UI to work 2023-07-15 04:17:38 +02:00
3180063fe3 attempt to get player selector to work 2023-07-15 04:03:32 +02:00
9cb9a6607b Remove unused components 2023-07-15 01:29:57 +02:00
42cdcd5c25 Site and extension options: done 2023-07-15 01:28:20 +02:00
8b1da27af7 remove UI when extension is disabled 2023-07-11 00:48:34 +02:00
638e228ce9 don't init UI by default 2023-07-10 22:56:26 +02:00
94469ccd0e mess around with launch.json, still didnt get debugger to work, decided i dont care 2023-07-10 22:05:37 +02:00
48f7e1f39c Merge branch 'feature/manifest-v3-v2' into feature/player-ui 2023-07-10 22:05:02 +02:00
60f7abfa42 get manifest v3 to approximately work 2023-07-10 22:04:30 +02:00
cd391db302 wip 2023-07-10 18:27:06 +02:00
8613d6971f Showing aard notifications is no longer implemented, so we don't call it 2023-04-16 02:54:30 +02:00
cc18153889 Minor update to site settings 2023-04-16 02:53:59 +02:00
be4bf9e2a3 detect whether extension is enabled on dimension changes. Trigger dimension check on fullscreen change 2023-04-16 02:43:50 +02:00
085bd4aab9 fix video alignment default settings 2023-04-16 02:16:57 +02:00
977d103c3a remove aard::halt() (is duplicate of aard::stop() anyway) 2023-04-16 01:55:11 +02:00
0778f07922 sensible variable naming is important + remove redundant code 2023-04-12 23:08:19 +02:00
e12154601b also add 32:9 button 2023-03-30 00:49:09 +02:00
b22d50f760 Firefox also stopped liking the -alpha# format, so version numbering was switched from [next major]-X.X-[alpha|beta|dev]# to [current-major].99.X-# 2023-03-30 00:45:34 +02:00
302f21b477 Cycle mode 2023-03-30 00:43:30 +02:00
65da515854 various fixes 2023-03-29 22:07:50 +02:00
e9bd3154a5 change event bus parameter names 2023-03-29 21:51:06 +02:00
1da147e110 remove redundant comments 2023-03-29 21:49:27 +02:00
e7dd85420c iframe manager 2023-03-02 01:08:25 +01:00
3e542871c6 Attach source frame info when communicating between frames on the same page 2023-03-02 01:08:16 +01:00
26b78f1225 EventBus: Allow forwarding between iframes of a page 2023-03-02 00:37:23 +01:00
57261b5094 Extension settings now holds both extension and site settings 2023-01-23 00:15:56 +01:00
c7f24133e5 fix ANGLE 2023-01-23 00:15:21 +01:00
87aa397a7c Version bump + changelog 2023-01-22 18:04:26 +01:00
e2a18ec765 Extconfpatch for disney+ 2023-01-22 17:58:39 +01:00
26ee2ffbb6 ANGLE popup 2023-01-22 17:45:33 +01:00
38dd1ddbe8 ANGLE detection 2023-01-22 16:06:33 +01:00
11bc79520a ANGLE detection 2023-01-22 16:05:07 +01:00
cc0ae60c83 Update settings object, finalize-ish site settings panel 2023-01-15 22:11:47 +01:00
30b028d836 remove redundant code 2023-01-15 22:10:17 +01:00
a30a70c6b5 get UI to somewhat work. TODO: get scripts to work 2023-01-07 19:19:44 +01:00
9503003a4a migration to site settings mostly done. TODO: check if things still work 2023-01-07 18:57:47 +01:00
a58edad8ea migrate settings to new and simplified SiteSettings 2023-01-07 03:06:37 +01:00
d0ba619b45 Redefine settings interface, write function to migrate some settings on update 2023-01-07 03:05:51 +01:00
64bad6758b start work on new panels 2023-01-07 03:05:03 +01:00
b902b8f9ba fix some warnings 2023-01-06 19:03:44 +01:00
7bb4ee96e7 return zoom slider to its rightful place 2023-01-06 19:03:35 +01:00
6c3e960096 Player is in a separate component 2023-01-06 18:47:42 +01:00
cacb0f1ea0 Put player window into its own component 2023-01-06 18:23:34 +01:00
5a2e33ccd2 fix 2023-01-06 18:23:12 +01:00
5d389a0796 Rename a file for future consistency 2023-01-06 18:22:15 +01:00
6db1d8af2a remove old dist before build 2023-01-06 18:19:59 +01:00
51d73e09ae ?? 🤔🤔🤔🤔 2023-01-06 18:17:08 +01:00
3c324aa1b2 Add global page (not loaded by extension yet), put mousemove probes in a separate file 2023-01-04 18:09:01 +01:00
a94092ac78 remove unnecessary components 2023-01-04 18:08:11 +01:00
f9d72d12b6 minor changes 2023-01-04 18:07:55 +01:00
81e11c2ae9 Extension options WIP 2022-11-22 01:28:04 +01:00
f7a82ccdf7 re-add missing method 2022-11-21 20:46:51 +01:00
44e3395835 Get basic messaging from popup to CS working. TODO: Forwarding to specific iframes/videos 2022-11-21 01:04:57 +01:00
d558717152 Try to recreate some UI in popup 2022-11-20 23:41:40 +01:00
b9b021f466 Spin off VideoSettings component into subcomponents 2022-11-20 22:45:34 +01:00
13bfd63dc2 Move common things between mousehandler and kbhandler to kbmBase 2022-11-20 17:26:57 +01:00
9600c4f1c9 fix kbm whoopsie 2022-11-20 15:23:23 +01:00
de9696471a kbm refactoring stuffs 2022-11-20 14:46:01 +01:00
c954f8b984 add playerMouseHandler 2022-09-28 01:18:58 +02:00
08479242f7 fix settings 2022-09-28 00:40:09 +02:00
d70783cc82 Rename canStartExtension to more descriptive isEnabledForSite 2022-09-28 00:39:20 +02:00
a2f0617d5a start splitting kbmHandler into keyboard and mouse handler 2022-09-28 00:38:36 +02:00
27b6ca824d fix action handler rename 2022-09-27 22:15:01 +02:00
e84f8ee541 start fixing KbmHandler (formerly: action handler) some more 2022-09-27 01:48:08 +02:00
ad87dbdcb9 remove unnecessary logging 2022-09-20 01:38:08 +02:00
9c42c120f5 Remove unnecessary components 2022-09-20 01:35:47 +02:00
740f2e8f6f fix forwarding data between server, content script, and popup 2022-09-20 01:34:59 +02:00
a0388689cf Continue slowly working on unified event bus 2022-08-21 22:46:06 +02:00
46cb5e1078 npm fix 2022-08-21 15:50:03 +02:00
abc53cf00f don't fuck ArDetector up 2022-08-21 15:49:28 +02:00
68f367bd39 Merge branch 'master' into feature/player-ui 2022-08-21 15:45:11 +02:00
d54097799c update cleanup script 2022-08-21 15:24:34 +02:00
ffc25520bb fix types 2022-08-21 15:24:24 +02:00
ccdc68870a Fix #199 2022-08-21 15:04:21 +02:00
7820da181e Readability fixes (I guess) 2022-08-20 16:04:31 +02:00
ec4d43d2ec unpause: Don't resume autodetection if not paused 2022-08-08 23:17:13 +02:00
c12a04b55f mostly whitespace 2022-08-08 23:16:55 +02:00
e0fe4c7103 Version bump + changelog 2022-08-06 01:56:12 +02:00
82f2983ac2 package.lock update 2022-08-06 01:31:40 +02:00
d2733cf486 Fix autodetection getting stuck in 21:9 2022-08-06 01:31:18 +02:00
3aabd298ec fix various fuckies wuckies 2022-07-31 01:12:54 +02:00
a0e375d2dc Cleanup — remove old uwui implementation 2022-07-31 00:18:21 +02:00
8806c8ea0c Integrate comms client/server into eventBus 2022-07-31 00:15:28 +02:00
f0120010fe do types 2022-07-31 00:14:40 +02:00
a5a121ee00 flag for whether autodetection is currently active 2022-07-30 23:07:33 +02:00
459a0eaa91 build lied? 2022-07-30 00:55:20 +02:00
84fefbaa14 remove some console.logs 2022-07-30 00:54:12 +02:00
023b8c370a fix tslint complaints 2022-07-30 00:52:14 +02:00
0a023ac6f2 fix command 2022-07-30 00:47:11 +02:00
83f7e4498a npm audit fix maybe? 2022-07-30 00:32:23 +02:00
d57040fc5b Merge branch 'master' into feature/player-ui 2022-07-30 00:29:23 +02:00
7df62ba530 Version bump + changelog 2022-07-30 00:15:15 +02:00
f51ec50113 fix aard, attempt 3 2022-07-29 00:42:35 +02:00
6748cbe52d update package.lock 2022-07-29 00:25:29 +02:00
fda2dcc484 fix aard: fix harder ? 2022-07-29 00:25:16 +02:00
07a5f06695 Merge branch 'dependabot/npm_and_yarn/shell-quote-1.7.3' 2022-07-29 00:21:52 +02:00
c9392911d9 fix aard? 2022-07-29 00:21:34 +02:00
88dec55116 get DRM status in player ui 2022-07-28 00:45:35 +02:00
f30de3f4a0 Add debug panel with logger 2022-07-27 01:16:14 +02:00
3c5aafee2d update package.lock 2022-07-26 23:55:39 +02:00
52b4e48217 start extension popup rework 2022-07-26 23:54:25 +02:00
58ed3b1ca4 Update changelog 2022-07-26 22:41:12 +02:00
bfef49bc33 fix stuff from merge 2022-07-26 22:40:45 +02:00
b7a61e5bc0 Rework blackframe to not use its own drawImage call 2022-07-26 22:15:39 +02:00
dependabot[bot]
15a3ffd406 Bump shell-quote from 1.7.2 to 1.7.3
Bumps [shell-quote](https://github.com/substack/node-shell-quote) from 1.7.2 to 1.7.3.
- [Release notes](https://github.com/substack/node-shell-quote/releases)
- [Changelog](https://github.com/substack/node-shell-quote/blob/master/CHANGELOG.md)
- [Commits](https://github.com/substack/node-shell-quote/compare/v1.7.2...1.7.3)

---
updated-dependencies:
- dependency-name: shell-quote
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-26 19:59:10 +00:00
66f934e46e fix version but reals 2022-07-26 21:57:22 +02:00
47b85cfebc Revert "Bump node-sass from 4.14.1 to 7.0.0"
This reverts commit 9a3bd3ff88.
2022-07-26 01:24:33 +02:00
d0b060f31a Changelog + fix version bump 2022-07-26 01:20:29 +02:00
a756a9dc16 Version bump 2022-07-26 01:13:25 +02:00
207b82cc8b Fix set-extension-commnand for people who installed extension while this command was misspelled 2022-07-26 01:12:42 +02:00
26df9633b4 Remove ancient migrations 2022-07-26 01:10:11 +02:00
2b749d6a3b fix set-extension-mode commands 2022-07-26 01:04:36 +02:00
8c403f106c Merge remote-tracking branch 'origin/dependabot/npm_and_yarn/terser-4.8.1' 2022-07-26 00:52:25 +02:00
36aa226e9c Reimplement blackframe. Start timing blackframe execution. Add some placeholder UI for autodetection settings 2022-07-26 00:52:06 +02:00
dependabot[bot]
1946b77285 Bump terser from 4.8.0 to 4.8.1
Bumps [terser](https://github.com/terser/terser) from 4.8.0 to 4.8.1.
- [Release notes](https://github.com/terser/terser/releases)
- [Changelog](https://github.com/terser/terser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/terser/terser/commits)

---
updated-dependencies:
- dependency-name: terser
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-07-20 14:48:52 +00:00
a09ea26b0e Rework blackframe to not use its own drawImage call 2022-07-13 00:22:16 +02:00
1556506bd7 monitor autodetection performance 2022-07-13 00:21:21 +02:00
f94f6ec956 Autodetect script can now report performance data 2022-06-28 22:34:47 +02:00
aae566b82a stuff we missed in previous cleanup 2022-06-28 22:34:20 +02:00
dd8aeefdfd Clean up some performance stuff for autodetection 2022-06-28 20:53:50 +02:00
7d951a5115 comment cleanup 2022-06-28 19:28:14 +02:00
556722693c Rearrange things a bit. No longer do fallback mode 2022-06-28 01:33:21 +02:00
78f350b72b UI design for player selection is mostly done 2022-06-16 00:07:03 +02:00
340ee05443 fix pointer events 2022-06-15 00:53:43 +02:00
561a1aa5a5 Add more styling, choose few quick actions 2022-06-15 00:43:36 +02:00
e2fa4f9cfc some things weren't as "no longer needed" as initially thought 2022-06-14 23:23:08 +02:00
ee56187c9d give player section an icon 2022-06-14 23:20:26 +02:00
d3dbceb749 fix 2022-06-14 23:20:07 +02:00
799c953b30 Remove code no longer needed 2022-06-14 00:27:37 +02:00
e766d6da69 Allow users to set player element from the popup 2022-06-14 00:26:06 +02:00
53ed81fa81 showing and hiding of ultrawidify UI 2022-06-11 15:40:36 +02:00
320d35d9e5 holy shit we did it 🅱️ois, we made some progress on improving manual player detection 2022-06-10 00:22:06 +02:00
a1bfaaaf92 wip 2022-06-09 17:41:04 +02:00
03ebc87aa5 Prepare rendering of basic element tree 2022-06-09 09:01:30 +02:00
d2447d2d8a player detect panel boilerplate 2022-06-09 01:29:26 +02:00
296f146835 Refactor player detection + have player detection log some data for use in settings window 2022-06-09 01:28:46 +02:00
9a7e38d897 dict update 2022-06-09 01:27:50 +02:00
f0fcf996c9 set default stretching and cropping for site and extension in UI 2022-05-25 00:18:54 +02:00
c1b14979a2 also get alignment to work from player UI 2022-05-24 23:48:26 +02:00
9e9fdfe37f get zoom to work again 2022-05-24 23:45:41 +02:00
135ce2dfdd Merge remote-tracking branch 'origin/dependabot/npm_and_yarn/async-2.6.4' 2022-05-06 00:28:52 +02:00
167d46ca76 Merge remote-tracking branch 'origin/dependabot/npm_and_yarn/minimist-1.2.6' 2022-05-06 00:28:47 +02:00
23d2c252fe Merge remote-tracking branch 'origin/dependabot/npm_and_yarn/object-path-0.11.8' 2022-05-06 00:28:43 +02:00
9100c1d9de Merge remote-tracking branch 'origin/dependabot/npm_and_yarn/node-fetch-2.6.7' 2022-05-06 00:28:38 +02:00
b45b232ecf Merge remote-tracking branch 'origin/dependabot/npm_and_yarn/path-parse-1.0.7' 2022-05-06 00:28:35 +02:00
9648d30aa2 If videoData is destroyed, remove its eventBus from pageInfo's downstream buses 2022-05-06 00:28:13 +02:00
5a04c2eeee Migrate actionHandler to new commands 2022-05-06 00:23:15 +02:00
74fe85f3a2 Put a parent event bus to pageInfo 2022-05-06 00:23:01 +02:00
4b44efe6fc Finish event bus propagation 2022-05-06 00:22:35 +02:00
dependabot[bot]
8922054c44 Bump async from 2.6.3 to 2.6.4
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-29 00:39:55 +00:00
302353f448 Style dropdowns 2022-04-13 23:03:55 +02:00
3891d946e0 Add new aspect ratios. Fix deleting old aspect ratios. 2022-04-13 21:15:33 +02:00
e92e2e22f5 Update and delete keyboard shortcuts 2022-04-13 01:17:25 +02:00
4f4eb46a60 Clear keyboard shortcut 2022-04-13 00:24:10 +02:00
0daef615c1 Sort out inputs, shortcut button 2022-04-12 00:48:33 +02:00
6ce8bba6b7 Start working on the edit box for crop settings 2022-04-09 15:58:40 +02:00
09cf3ce54e Reorder some methods around into logical sections 2022-04-09 15:37:23 +02:00
872b0dea6e Change 'official' to 'verified' 2022-03-29 02:01:03 +02:00
376f20aeab Show last selected aspect ratio in the UI 2022-03-29 01:53:16 +02:00
57aae96781 Update button css 2022-03-28 23:57:34 +02:00
7c27c33aa0 Prepare some things for edit mode 2022-03-28 23:57:25 +02:00
7dc354509b Remove some of dem pointless console.logs that are no longer needed 2022-03-28 23:56:52 +02:00
7c09440084 UI base: sort out header and tabs 2022-03-28 23:14:52 +02:00
dependabot[bot]
5538ec4d3d Bump minimist from 1.2.5 to 1.2.6
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-24 16:24:35 +00:00
89168b643c Connect popup (video settings only so far) to content script logic 2022-03-22 01:23:50 +01:00
7fefe255ea Add some things we missed in initial implementation of pointer-events toggling 2022-03-22 01:23:15 +01:00
3423aac49a remove some console.logs 2022-03-22 01:21:18 +01:00
dependabot[bot]
cc8d1d8a94 Bump node-fetch from 2.6.1 to 2.6.7
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-20 23:52:29 +00:00
dependabot[bot]
c0a863740a Bump object-path from 0.11.5 to 0.11.8
Bumps [object-path](https://github.com/mariocasciaro/object-path) from 0.11.5 to 0.11.8.
- [Release notes](https://github.com/mariocasciaro/object-path/releases)
- [Commits](https://github.com/mariocasciaro/object-path/commits/v0.11.8)

---
updated-dependencies:
- dependency-name: object-path
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-20 23:52:23 +00:00
dependabot[bot]
f006eed113 Bump path-parse from 1.0.6 to 1.0.7
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-20 23:52:22 +00:00
3f131f44d1 Merge remote-tracking branch 'origin/dependabot/npm_and_yarn/prismjs-1.27.0' 2022-03-21 00:51:22 +01:00
5cfc089bf1 Merge remote-tracking branch 'origin/dependabot/npm_and_yarn/ajv-6.12.6' 2022-03-21 00:51:20 +01:00
987b1ad655 Merge remote-tracking branch 'origin/dependabot/npm_and_yarn/node-sass-7.0.0' 2022-03-21 00:51:16 +01:00
1cd06040da Toggle 'pointer-events' based on whether mouse hovers over a csui element or not 2022-03-21 00:50:54 +01:00
f99220bf8b Process messages from content script, respond with results 2022-03-21 00:50:03 +01:00
8bef1d0c99 something's fucky with window.parent.location 2022-03-20 20:56:29 +01:00
47a73cfa6c Get page URL from window.parent.location instead of window.location for iframe 2022-03-20 20:44:50 +01:00
2dd901b1d5 Mark ui as clickable 2022-03-20 20:43:49 +01:00
46725c6fa4 Send mouse coordinates to the iframe 2022-03-20 20:40:11 +01:00
dependabot[bot]
adfc3fca5f Bump prismjs from 1.23.0 to 1.27.0
Bumps [prismjs](https://github.com/PrismJS/prism) from 1.23.0 to 1.27.0.
- [Release notes](https://github.com/PrismJS/prism/releases)
- [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PrismJS/prism/compare/v1.23.0...v1.27.0)

---
updated-dependencies:
- dependency-name: prismjs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-26 02:33:31 +00:00
dependabot[bot]
077610820c Bump ajv from 6.10.2 to 6.12.6
Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.10.2 to 6.12.6.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.10.2...v6.12.6)

---
updated-dependencies:
- dependency-name: ajv
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-12 13:52:45 +00:00
dependabot[bot]
9a3bd3ff88 Bump node-sass from 4.14.1 to 7.0.0
Bumps [node-sass](https://github.com/sass/node-sass) from 4.14.1 to 7.0.0.
- [Release notes](https://github.com/sass/node-sass/releases)
- [Changelog](https://github.com/sass/node-sass/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sass/node-sass/compare/v4.14.1...v7.0.0)

---
updated-dependencies:
- dependency-name: node-sass
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-10 19:16:57 +00:00
85fed9d4c5 Hot dang, icons now show up in Chrome as well.
Also cleaned up some of the mess.
2022-01-29 16:23:40 +01:00
5a2d8d22cb Move extension popup into an iframe, get it to display at least something 2022-01-07 00:50:58 +01:00
761f2c21a8 fixes 2021-11-25 22:16:48 +01:00
c34340d0d1 install mdi font 2021-11-25 22:16:16 +01:00
045cd5a528 Stop using some of the old code 2021-11-25 20:00:32 +01:00
27b0609d34 Let's try to avoid injecting ultrawidify-specific CSS before we actually need it. Part 1/? 2021-11-25 00:31:38 +01:00
65556d9cad We will need to distinguish between two automatic aspect ratio types
AspectRatioType.Automatic: "start autodetection pls"
AspectRatioType.AutomaticUpdate: "Hi I'm aard and I want to change this aspect ratio to something new"
2021-11-25 00:14:06 +01:00
30835e94c0 UI: can set default crop, stretch. (TODO: actually get and use those values) 2021-11-23 01:32:55 +01:00
ee8ac1d9ee Site support level works now ... for most values of work 2021-11-21 02:08:53 +01:00
2be44d54ae Try achieving css class rename some more 2021-11-21 02:08:32 +01:00
cf8bedfad9 fix an oversight 2021-11-20 20:56:49 +01:00
5dc9b498d4 sample jenkins test 2021-11-13 00:57:41 +01:00
fb7a168d31 whitespace 2021-11-13 00:55:17 +01:00
484db037c0 use event bus 2021-11-13 00:54:20 +01:00
15a89583a2 Package updates 2021-11-13 00:48:08 +01:00
75cc0d5a87 handle alignment along y 2021-11-03 00:08:34 +01:00
0e3b962a00 whitespace 2021-11-02 22:20:01 +01:00
afaf65a53d Fix stretch mode from ui 2021-11-02 22:19:50 +01:00
f00d3b5aaf Fix zoom, but for realses 2021-11-02 20:52:01 +01:00
03bd442783 do the version bumping thing again 2021-11-02 20:51:45 +01:00
787bd1a532 Prevent eventBus from crashing everything under certain conditions 2021-11-01 01:18:07 +01:00
bd5befc18c Get sliders to update with values from aspect ratio options 2021-11-01 01:13:13 +01:00
829047585a Get manual zoom to work 2021-11-01 00:11:14 +01:00
78da45d468 cosmetic stuff 2021-10-31 23:18:44 +01:00
7f7ab7b752 Add some things to mitigate windows/wsl suckiness 2021-10-31 19:03:22 +01:00
79fb10c556 Add new format for defining actions. They're called commands now. 2021-10-28 22:53:43 +02:00
ffd9c6fb7a ExtConfPatches to typescrtipt 2021-10-28 22:53:23 +02:00
4074774d3a Preemtive version bump and patch notes 2021-10-27 20:46:14 +02:00
e7d43c566f Fix aspect ratio not getting applied correctly 2021-10-27 20:10:55 +02:00
52b94003bf Fix popup not opening 2021-10-27 20:10:21 +02:00
13bd280062 Get event bus to work (for some values of work) 2021-10-26 23:16:11 +02:00
209e4221d2 Start adding event bus 2021-10-26 22:20:50 +02:00
c4832a9ef7 Comment out temporarily unused commands 2021-10-26 20:11:26 +02:00
6cb8cc174b Prevent multiple concurrent vue app instances 2021-10-26 20:11:03 +02:00
e9b882376c Simplify player data dimension change tracking a bit 2021-10-26 00:30:38 +02:00
a4948d3eef Whitespace 2021-10-25 23:13:35 +02:00
746a78577e UI for manual zoom and video alignment 2021-10-24 00:57:57 +02:00
268ef7dae3 push the popup thing a bit further 2021-10-22 00:31:04 +02:00
6c1d666fdf switch bootstrap icons with mdijs 2021-10-22 00:30:36 +02:00
53fb3e8f46 Add stretch actions to the popup, styling 2021-10-19 22:49:56 +02:00
2df0b1a0fb formatting 2021-10-19 22:49:28 +02:00
3846d1a926 Cropping now also works from in-player UI 2021-10-19 22:49:13 +02:00
2a747e11af Merge branch 'master' into feature/player-ui 2021-10-19 20:27:41 +02:00
cda6b4e10d Version bump n shit 2021-09-19 21:32:47 +02:00
502ce707e1 Style 2021-09-19 21:22:12 +02:00
5b1f73a69e Fix problem with autodetection 2021-09-19 21:22:05 +02:00
f7eba0974f Fix default settings 2021-09-19 21:21:23 +02:00
104ed85d72 Get UI to show again 2021-08-26 01:34:59 +02:00
84de5927cd Try initializing UI again 2021-08-26 01:07:39 +02:00
1b829e095b Merge branch 'master' into feature/player-ui 2021-08-26 00:54:07 +02:00
5847215097 Merge branch 'master' into feature/player-ui 2021-07-05 01:22:30 +02:00
d92b24a2f6 Merge branch 'master' into feature/player-ui 2021-06-13 02:19:39 +02:00
2ced591ed4 Fix build errors 2021-06-12 23:23:47 +02:00
56dcd0feb3 Merge branch 'master' into feature/player-ui 2021-06-12 23:22:54 +02:00
0bc1254ce0 Merge branch 'master' into feature/player-ui 2021-04-04 03:59:08 +02:00
190737b915 Merge branch 'master' into feature/player-ui 2021-04-01 23:50:08 +02:00
038191d38e Merge branch 'master' into feature/player-ui 2021-03-06 04:07:03 +01:00
84da78e2e8 expose videoData objects via window.ultrawidify 2021-01-27 00:41:42 +01:00
13b89eb2a9 Add sample button from old config 2021-01-27 00:41:13 +01:00
89c524a690 add video settings placeholder to the UI 2021-01-21 00:21:15 +01:00
d44d1b6e64 add getRuntime() to BrowserDetect 2021-01-21 00:20:43 +01:00
8ea5b3d5be add toString() method to an enum 2021-01-21 00:20:27 +01:00
f8c774700e Merge branch 'master' into feature/player-ui 2021-01-20 22:58:40 +01:00
5987fda0b2 Merge branch 'master' into feature/player-ui 2020-12-29 20:36:06 +01:00
0e0eb57fd9 Merge branch 'master' into feature/player-ui 2020-12-29 18:33:54 +01:00
93b8049b01 Move debug data into a separate component/panel 2020-12-17 01:49:28 +01:00
5cf74b4405 Fix typo in one of the mutations 2020-12-17 01:49:15 +01:00
2aa91df820 Add additional data to debug popup 2020-12-17 01:48:45 +01:00
85fedbfeaf Fix base css 2020-12-17 01:48:29 +01:00
1cbe74252c display some data 2020-12-16 01:40:09 +01:00
9fb9667dba fix base css 2020-12-16 01:36:00 +01:00
55b6ca7392 fix comms 2020-12-16 01:35:39 +01:00
758c2bf0bc Report data to the UI 2020-12-16 00:19:37 +01:00
6eed271814 Display such data in a very rudamentary way 2020-12-16 00:19:20 +01:00
7a5a4bddf8 support passing debug data between content script and UI 2020-12-16 00:19:02 +01:00
3f8ea683ef Add toString() method to selected enums 2020-12-16 00:18:23 +01:00
29dff35bb4 Merge branch 'master' into feature/player-ui 2020-12-15 21:57:03 +01:00
79462c320d Add anchor for player UI 2020-12-15 00:26:19 +01:00
213 changed files with 51429 additions and 40501 deletions

2
.nvmrc
View File

@ -1 +1 @@
14.4.0
14.21.3

18
.vscode/launch.json vendored
View File

@ -4,10 +4,19 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "attach",
"name": "Attach to Chrome",
"port": 9222,
"urlFilter": "http://*/*",
"webRoot": "${workspaceFolder}"
},
{
"type": "firefox",
"request": "attach",
"name": "Attach",
"name": "Attach (firefox)",
"pathMappings": [
{
"url": "webpack:///ext",
@ -16,7 +25,7 @@
]
},
{
"name": "Launch addon",
"name": "Launch addon (firefox)",
"type": "firefox",
"request": "launch",
"port": 6000,
@ -36,5 +45,8 @@
"firefoxArgs": [
"--start-debugger-server"
]
},
"chrome": {
"executable": "/usr/bin/google-chrome-stable --remote-debugging-port=9222",
}
}
}

View File

@ -9,10 +9,13 @@
"blackbars",
"blackframe",
"canvas",
"clickthrough",
"com",
"comms",
"csui",
"decycle",
"dinked",
"dinks",
"disneyplus",
"endregion",
"equalish",
@ -29,6 +32,8 @@
"insta",
"letterboxed",
"manjaro",
"mdicon",
"mdijs",
"minification",
"mitigations",
"nogrow",
@ -49,6 +54,7 @@
"tablist",
"tamius",
"textbox",
"ultrawide",
"ultrawidify",
"unmark",
"unmarking",
@ -77,4 +83,4 @@
"undisable",
"vdid"
]
}
}

View File

@ -11,12 +11,69 @@
## v7.0 (planned major)
* WebGL autodetection
## v6.0 (next major)
## v6.0 (current major)
* in-player GUI
* Fix UI logger
### v6.0.1
## v5.x (current major)
* Fixed external links
### v6.0.0
Chrome only, because I needed to rush manifest v3 migration before ensuring things work in Firefox.
* In player UI
* New alignment options (can align video both vertically, as well as horizontally)
* Extension does a little bit of a better job differentiating between levels of support for a given site
* Changed how cropping and panning works. This should lead to fewer problems and better generic support.
* REGRESSION: no manual panning with shift + mouse movement
* "Player select" screen, which provides a GUI way for picking HTML element that acts as the video player area
Regressions and potential issues:
* Settings were largely not migrated from previous versions. This is as intended, since changes to cropping (and some other aspects)
rendered certain old settings obsolete.
* Extension can no longer discriminate between iframes — extension popup commands get sent to ALL iframes on page
* Extension probably can't discriminate between multiple videos on a single page
## v5.x
### v5.1.7
Firefox-only.
* When cropping and panning video, CSS' `transform: translate(x,y)` now uses integers _always_. Before that fix, `translate(x,y)` could be offset by x.5 px, and that half of a pixel introduced a 1px thick line on the portion of the left edge of the video.
### v5.1.7
* When aligning videos, ensure video is never translated by a fractional value
* As of recent nVidia driver update in Edge, Angle detection fails in a way that prevents popup from showing up. This problem should be fixed now.
The angle detection problem was fixed by disabling the angle detection check for now, as Chrome, et al. appear to have fixed buggy video hardware acceleration.
### v5.1.6
* Added new config for disney+ (courtesy of @MStefan99)
* Chrome hardware acceleration is bugged and cannot be worked around in-extension. Only way to fix this is for users to change their Google Chrome settings. Added naggathon with instructions into the extension popup.
### v5.1.5
* Fixed laginess in Chromium-based browsers on Windows. Details in [#199](https://github.com/tamius-han/ultrawidify/issues/199#issuecomment-1221383134)
### v5.1.4
* Fixed some problems with autodetection not returning to 16:9 when necessary if autodetection already changed aspect ratio ([#198](https://github.com/tamius-han/ultrawidify/issues/198))
### v5.1.3
* Fixed some problems with autodetection sometimes briefly resetting on dark frames ([#195](https://github.com/tamius-han/ultrawidify/issues/195), [#196](https://github.com/tamius-han/ultrawidify/issues/196))
### v5.1.2
* `set-extension-mode` turned into `set-ExtensionMode` at some point. This caused in "Enable this extension" options to vanish on certain setups.
* Blackframe tests now run on same data as main algorithm as opposed on a smaller sample (`drawImage()` calls are _very_ expensive even for a 16x9 sample).
### v5.1.1
* Fixed autodetection
### v5.1.0
@ -24,7 +81,7 @@
* Move aspect ratio autodetection to requestAnimationFrame
* Fix netflix
### v5.0.7
### v5.0.7
* Videos of square-ish aspect ratios on 1440p (and lower) resolutions now no longer get misaligned ([#162](https://github.com/tamius-han/ultrawidify/issues/162))
* Alignment of featured videos on youtube channel page should now also be fixed

6
DOCUMENTATION.MD Normal file
View File

@ -0,0 +1,6 @@
# Implementation details
## Enabling/disabling aspect ratio corrections
* Aspect ratios are changed by proxy. Extension attaches **a custom CSS class** to `video` and `player` elements.
* To prevent extension from affecting the appearance of a webpage, **it's sufficient to remove our custom CSS classes from `video` and `player` elements.**

32
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,32 @@
// required jenkins plugins:
// * https://plugins.jenkins.io/git/
pipeline {
agent any
stages {
// stage('Check for changes') {
// sh "env.GIT_COMMIT != env.GIT_PREVIOUS_COMMIT"
// }
stage('Install dependencies') {
steps {
sh 'npm ci'
}
}
stage('Build') {
steps {
sh 'npm run build-all'
}
}
stage('Push to release server') {
steps {
sh "echo 'implement me pls!'"
}
}
}
}

40582
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "ultrawidify",
"version": "5.1.0",
"version": "6.0.2",
"description": "Aspect ratio fixer for youtube and other sites, with automatic aspect ratio detection. Supports ultrawide and other ratios.",
"author": "Tamius Han <tamius.han@gmail.com>",
"scripts": {
@ -16,44 +16,46 @@
"build-zip": "node scripts/build-zip.js",
"build:dev": "webpack --hide-modules",
"dev": "cross-env NODE_ENV=development CHANNEL=dev concurrently \"cross-env BROWSER=firefox npm run build:dev -- --watch\" \"cross-env BROWSER=chrome npm run build:dev -- --watch\" \"cross-env BROWSER=edge npm run build:dev -- --watch\"",
"dev-ff": "cross-env NODE_ENV=development CHANNEL=dev BROWSER=firefox npm run build:dev -- --watch",
"dev-chrome": "cross-env NODE_ENV=development CHANNEL=dev BROWSER=chrome npm run build:dev -- --watch",
"dev-edge": "cross-env NODE_ENV=development CHANNEL=dev BROWSER=edge npm run build:dev -- --watch",
"pre-build": "rm -rf ./dist-ff; rm -rf ./dist-chrome; rm -rf ./node_modules; npm ci",
"start": "npm run dev"
"dev:windows": "cross-env NODE_ENV=development CHANNEL=dev concurrently \"cross-env BROWSER=firefox npm run build:dev -- -w --watch-poll\" \"cross-env BROWSER=chrome npm run build:dev -- -w --watch-poll\" \"cross-env BROWSER=edge npm run build:dev -- -w --watch-poll\"",
"dev-ff": "rm -rf ./dist-ff && cross-env NODE_ENV=development CHANNEL=dev BROWSER=firefox npm run build:dev -- --watch",
"dev-chrome": "rm -rf ./dist-chrome && cross-env NODE_ENV=development CHANNEL=dev BROWSER=chrome npm run build:dev -- --watch",
"dev-edge": "rm -rf ./dist-edge && cross-env NODE_ENV=development CHANNEL=dev BROWSER=edge npm run build:dev -- --watch",
"pre-build": "rm -rf ./dist-ff; rm -rf ./dist-chrome; rm -rf ./dist-edge; rm -rf ./node_modules; npm ci",
"start": "npm run dev",
"start:windows": "npm run dev:windows"
},
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.12.1",
"@types/chrome": "0.0.129",
"@types/core-js": "^2.5.3",
"@types/es6-promise": "^3.3.0",
"@types/firefox": "0.0.30",
"@types/resize-observer-browser": "^0.1.5",
"@vue/cli": "^4.5.9",
"@vue/cli-plugin-typescript": "^4.5.11",
"bootstrap": "^4.5.3",
"bootstrap-icons": "^1.1.0",
"bootstrap-icons-vue": "^0.3.0",
"bootstrap-vue": "^2.20.1",
"concurrently": "^5.2.0",
"@babel/plugin-proposal-class-properties": "^7.16.0",
"@mdi/font": "^6.5.95",
"@mdi/js": "^6.4.95",
"@types/resize-observer-browser": "^0.1.6",
"concurrently": "^5.3.0",
"fs-extra": "^7.0.1",
"gl-matrix": "^3.4.3",
"json-cyclic": "0.0.3",
"lodash": "^4.17.21",
"typescript": "^4.2.3",
"vue": "^3.0.0-beta.1",
"vuex": "^4.0.0-alpha.1",
"vuex-webextensions": "^1.3.0",
"webextension-polyfill-ts": "^0.24.0"
"mdi-vue": "^3.0.11",
"typescript": "^4.4.4",
"vue": "^3.2.21",
"vue-style-loader": "^4.1.3",
"vuex": "^4.0.2",
"vuex-webextensions": "^1.3.3",
"webextension-polyfill": "^0.12.0"
},
"devDependencies": {
"@babel/core": "^7.12.13",
"@babel/plugin-proposal-optional-chaining": "^7.10.4",
"@babel/preset-env": "^7.12.13",
"@types/lodash": "^4.14.168",
"@types/node": "^14.14.25",
"@vue/compiler-sfc": "^3.0.3",
"@babel/core": "^7.16.0",
"@babel/preset-env": "^7.16.0",
"@types/chrome": "0.0.240",
"@types/core-js": "^2.5.5",
"@types/es6-promise": "^3.3.0",
"@types/firefox": "0.0.31",
"@types/lodash": "^4.14.176",
"@types/node": "^14.17.32",
"@vue/cli": "^4.5.15",
"@vue/cli-plugin-typescript": "^4.5.15",
"@vue/compiler-sfc": "^3.2.21",
"archiver": "^3.0.0",
"babel-loader": "^8.2.2",
"babel-loader": "^8.2.3",
"babel-preset-es2020": "^1.0.2",
"copy-webpack-plugin": "^4.5.3",
"cross-env": "^5.2.0",
@ -62,13 +64,13 @@
"file-loader": "^1.1.11",
"mini-css-extract-plugin": "^0.4.4",
"node-sass": "^4.14.1",
"resolve-url-loader": "^5.0.0",
"sass-loader": "^7.1.0",
"ts-loader": "^8.0.16",
"ts-loader": "^8.3.0",
"vue-cli-plugin-vue-next": "~0.1.4",
"vue-loader": "^16.0.0",
"vue-loader": "^16.8.2",
"web-ext-types": "^2.3.0",
"webextension-polyfill": "^0.6.0",
"webpack": "^4.44.0",
"webpack": "^4.46.0",
"webpack-chrome-extension-reloader": "^0.8.3",
"webpack-cli": "^3.3.12",
"webpack-shell-plugin": "^0.5.0"

View File

@ -4,7 +4,7 @@
# command from the root directory of the project. Running it in
# any other way probably isn't going to work.
# pre-build steps:
# pre-build steps:
mkdir -p ./build/old
npm run pre-build
rm ./dist-zip/uw-amo-source.zip
@ -14,7 +14,7 @@ mv -f ./dist-zip/*.zip ./build/old
# export NODE_OPTIONS=--max_old_space_size=4096
# build the version for each browser and create a zip afterwards
# step 1: define build functions
# step 1: define build functions
#function buildFF {
npm run build
node scripts/build-zip.js ff

View File

@ -5,7 +5,7 @@
#
# Script assumes we're in basedir of the repository and that extension has been built and zipped to /dist-zip
# using the crx name we want (same name as zip, except different extension)
#
#
# also this doesn't check for errors ever so
#
@ -42,4 +42,4 @@ sig_len_hex=$(byte_swap $(printf '%08x\n' $(ls -l "$sig" | awk '{print $5}')))
echo "Wrote $crx"
echo "exiting dist-zip"
cd ..
cd ..

View File

@ -24,7 +24,7 @@ const buildZip = (src, dist, zipFilename) => {
const archive = archiver('zip', { zlib: { level: 9 }});
const stream = fs.createWriteStream(path.join(dist, zipFilename));
return new Promise((resolve, reject) => {
archive
.directory(src, false)
@ -46,14 +46,14 @@ const main = () => {
browserPostfix = browser;
}
const destDir = path.join(__dirname, `../dist-${browserPostfix}`);
const zipDir = path.join(__dirname, '../dist-zip');
const zipDir = path.join(__dirname, '../dist-zip');
const {name, version} = extractExtensionData(browserPostfix);
// collapse spaces and dashes into single dash
const baseFilename = `${name.replace(/[ -]+/g, '-')}-${version}`;
let realZipDir;
if (!!testingOrNightly) {
realZipDir = path.join(zipDir, version);
} else {
@ -61,7 +61,7 @@ const main = () => {
}
const zipFilename = `${baseFilename}-${browser}.zip`;
try {
makeDirIfNotExists(realZipDir, {recursive: true});
} catch (e) {
@ -70,7 +70,7 @@ const main = () => {
}
buildZip(destDir, realZipDir, zipFilename)
.then(() => console.info('OK'))
.catch(console.err);
.catch(console.err);
};
main();

View File

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

View File

@ -1,17 +0,0 @@
<template>
<div class="button center-text flex"
:class="{'setting-selected': selected, 'w24': fixedWidth, 'flex-auto': !fixedWidth }"
>
{{label}}
</div>
</template>
<script>
export default {
props: {
fixedWidth: Boolean,
selected: Boolean,
label: String,
}
}
</script>

File diff suppressed because one or more lines are too long

View File

@ -1,25 +0,0 @@
.uw-ultrawidify-container-root {
.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,55 +0,0 @@
<template>
<div class="qsb flex flex-row flex-cross-center flex-center flex-nogrow"
:class="{
'id': selector.startsWith('#'),
'class': selector.startsWith('.'),
}">
<div class="flex mt2px">{{selector}}</div> <span class="flex closeButton" @click="remove">×</span>
</div>
</template>
<script>
export default {
props: {
selector: String,
},
methods: {
remove() {
this.$emit('remove');
}
}
}
</script>
<style lang="scss" scoped>
.uw-ultrawidify-container-root {
.mt2px {
margin-top: 7px;
}
.id {
border: 1px solid #d00;
background-color: rgba(216, 94, 24, 0.25)
}
.class {
border: 1px solid #33d;
background-color: rgba(69, 69, 255, 0.25)
}
.closeButton {
margin-top: 2px;
margin-left: 4px;
margin-right: 4px;
font-size: 1.5em;
color: #e00;
}
.closeButton:hover {
cursor: pointer;
color: #fa6;
}
.qsb {
cursor:default;
margin-left: 3px;
margin-right: 3px;
margin-bottom: 2px;
}
}
</style>

View File

@ -1,11 +1,13 @@
enum AspectRatioType {
Initial = -1, // page default
Reset = 0, // reset to initial
Automatic = 1, // set by Aard
FitWidth = 2, // legacy/dynamic = fit to width
FitHeight = 3, // legacy/dynamic = fit to height
Fixed = 4, // pre-determined aspect ratio
Manual = 5, // ratio achieved by zooming in/zooming out
Cycle = -2,
Initial = -1, // page default
Reset = 0, // reset to initial
Automatic = 1, // we want to request automatic aspect ratio detection
FitWidth = 2, // legacy/dynamic = fit to width
FitHeight = 3, // legacy/dynamic = fit to height
Fixed = 4, // pre-determined aspect ratio
Manual = 5, // ratio achieved by zooming in/zooming out
AutomaticUpdate = 6, // set by Aard
}
export default AspectRatioType;

View File

@ -2,6 +2,8 @@ enum VideoAlignmentType {
Left = 0,
Center = 1,
Right = 2,
Top = 3,
Bottom = 4,
Default = -1
};

View File

@ -0,0 +1,6 @@
import AspectRatioType from '../enums/AspectRatioType.enum';
export interface Ar {
type: AspectRatioType,
ratio?: number
}

View File

@ -6,155 +6,252 @@ import ExtensionMode from '../enums/ExtensionMode.enum'
import StretchType from '../enums/StretchType.enum'
import VideoAlignmentType from '../enums/VideoAlignmentType.enum'
export interface KeyboardShortcutInterface {
key?: string,
code?: string,
ctrlKey?: boolean,
metaKey?: boolean,
altKey?: boolean,
shiftKey?: boolean,
onKeyUp?: boolean,
onKeyDown?: boolean,
onMouseMove?: boolean,
}
interface ActionScopeInterface {
show: boolean,
label?: string, // example override, takes precedence over default label
shortcut?: {
key?: string,
code?: string,
ctrlKey?: boolean,
metaKey?: boolean,
altKey?: boolean,
shiftKey?: boolean,
onKeyUp?: boolean,
onKeyDown?: boolean,
onMouseMove?: boolean,
}[],
shortcut?: KeyboardShortcutInterface[],
}
interface RestrictionsSettings {
disableOnSmallPlayers?: boolean; // Whether ultrawidify should disable itself when the player is small
minAllowedWidth?: number; // if player is less than this many px wide, ultrawidify will disable itself
minAllowedHeight?: number; // if player is less than this many px tall, ultrawidify will disable itself
onlyAllowInFullscreen?: boolean; // if enabled, ultrawidify will be disabled when not in full screen regardless of what previous two options say
onlyAllowAutodetectionInFullScreen?: boolean; // if enabled, autodetection will only start once in full screen
}
interface ExtensionEnvironmentSettingsInterface {
normal: ExtensionMode,
theater: ExtensionMode,
fullscreen: ExtensionMode,
}
export interface CommandInterface {
action: string,
label: string,
comment?: string,
arguments?: any,
shortcut?: KeyboardShortcutInterface,
internalOnly?: boolean,
actionId?: string,
}
export type SettingsReloadComponent = 'PlayerData' | 'VideoData';
export type SettingsReloadFlags = true | SettingsReloadComponent;
export interface AardSettings {
aardType: 'webgl' | 'legacy' | 'auto';
disabledReason: string, // if automatic aspect ratio has been disabled, show reason
allowedMisaligned: number, // top and bottom letterbox thickness can differ by this much.
// Any more and we don't adjust ar.
allowedArVariance: number, // amount by which old ar can differ from the new (1 = 100%)
timers: { // autodetection frequency
playing: number, // while playing
paused: number, // while paused
error: number, // after error
minimumTimeout: number,
tickrate: number, // 1 tick every this many milliseconds
},
autoDisable: { // settings for automatically disabling the extension
maxExecutionTime: number, // if execution time of main autodetect loop exceeds this many milliseconds,
// we disable it.
consecutiveTimeoutCount: number, // we only do it if it happens this many consecutive times
// FOR FUTURE USE
consecutiveArResets: number // if aspect ratio reverts immediately after AR change is applied, we disable everything
},
canvasDimensions: {
blackframeCanvas: { // smaller than sample canvas, blackframe canvas is used to recon for black frames
// it's not used to detect aspect ratio by itself, so it can be tiny af
width: number,
height: number,
},
sampleCanvas: { // size of image sample for detecting aspect ratio. Bigger size means more accurate results,
// at the expense of performance
width: number,
height: number,
},
},
// NOTE: Black Frame is currently not in use.
blackframe: {
sufficientColorVariance: number, // calculate difference between average intensity and pixel, for every pixel for every color
// component. Average intensity is normalized to where 0 is black and 1 is biggest value for
// that component. If sum of differences between normalized average intensity and normalized
// component varies more than this % between color components, we can afford to use less strict
// cumulative threshold.
cumulativeThresholdLax: number,
cumulativeThresholdStrict: number,// if we add values of all pixels together and get more than this, the frame is bright enough.
// (note: blackframe is 16x9 px -> 144px total. cumulative threshold can be reached fast)
blackPixelsCondition: number, // How much pixels must be black (1 all, 0 none) before we consider frame as black. Takes
// precedence over cumulative threshold: if blackPixelsCondition is met, the frame is dark
// regardless of whether cumulative threshold has been reached.
},
// Used by old aspect ratio detection algorithm. Pls remove.
blackbar: {
blackLevel: number, // everything darker than 10/255 across all RGB components is considered black by
// default. blackLevel can decrease if we detect darker black.
threshold: number, // if pixel is darker than the sum of black level and this value, we count it as black
// on 0-255. Needs to be fairly high (8 might not cut it) due to compression
// artifacts in the video itself
frameThreshold: number, // threshold, but when doing blackframe test
imageThreshold: number, // in order to detect pixel as "not black", the pixel must be brighter than
// the sum of black level, threshold and this value.
gradientThreshold: number, // When trying to determine thickness of the black bars, we take 2 values: position of
// the last pixel that's darker than our threshold, and position of the first pixel that's
// brighter than our image threshold. If positions are more than this many pixels apart,
// we assume we aren't looking at letterbox and thus don't correct the aspect ratio.
gradientSampleSize: number, // How far do we look to find the gradient
maxGradient: number, // if two neighboring pixels in gradientSampleSize differ by more than this, then we aren't
// looking at a gradient
gradientNegativeTreshold: number,
gradientMaxSD: number, // reserved for future use
antiGradientMode: AntiGradientMode
},
// Also not in use, probs.
variableBlackbarThresholdOptions: { // In case of poor bitrate videos, jpeg artifacts may cause us issues
// FOR FUTURE USE
enabled: boolean, // allow increasing blackbar threshold
disableArDetectOnMax: boolean, // disable autodetection when threshold goes over max blackbar threshold
maxBlackbarThreshold: number, // max threshold (don't increase past this)
thresholdStep: number, // when failing to set aspect ratio, increase threshold by this much
increaseAfterConsecutiveResets: number // increase if AR resets this many times in a row
},
blackLevels: {
defaultBlack: number, // By default, pixels darker than this are considered black.
// (If detection algorithm detects darker blacks, black is considered darkest detected pixel)
blackTolerance: number, // If pixel is more than this much brighter than blackLevel, it's considered not black
// It is not considered a valid image detection if gradient detection is enabled
imageDelta: number, // When gradient detection is enabled, pixels this much brighter than black skip gradient detection
}
sampling: {
edgePosition: number; // % of width (max 0.33). Pixels up to this far away from either edge may contain logo.
staticCols: number, // we take a column at [0-n]/n-th parts along the width and sample it
randomCols: number, // we add this many randomly selected columns to the static columns
staticRows: number, // forms grid with staticSampleCols. Determined in the same way. For black frame checks,
},
guardLine: { // all pixels on the guardline need to be black, or else we trigger AR recalculation
// (if AR fails to be recalculated, we reset AR)
enabled: boolean,
ignoreEdgeMargin: number, // we ignore anything that pokes over the black line this close to the edge
// (relative to width of the sample)
imageTestThreshold: number, // when testing for image, this much pixels must be over blackbarThreshold
edgeTolerancePx: number, // black edge violation is performed this far from reported 'last black pixel'
edgeTolerancePercent: null // unused. same as above, except use % of canvas height instead of pixels
},
arSwitchLimiter: { // to be implemented
switches: number, // we can switch this many times
period: number // per this period
},
// pls deprecate and move things used
edgeDetection: {
slopeTestWidth: number,
gradientTestSamples: number, // we check this many pixels below (or above) the suspected edge to check for gradient
gradientTestBlackThreshold: number, // if pixel in test sample is brighter than that, we aren't looking at gradient
gradientTestDeltaThreshold: number, // if delta between two adjacent pixels in gradient test exceeds this, it's not gradient
gradientTestMinDelta: number, // if last pixels of the test sample is less than this brighter than the first -> not gradient
thresholds: {
edgeDetectionLimit: number, // during scanning of the edge, quit after edge gets detected at this many points
minQualitySingleEdge: number, // At least one of the detected must reach this quality
minQualitySecondEdge: number, // The other edge must reach this quality (must be smaller or equal to single edge quality)
}
maxLetterboxOffset: 0.1, // Upper and lower letterbox can be different by this many (% of height)
// Previous iteration variables VVVV
sampleWidth: number, // we take a sample this wide for edge detection
detectionThreshold: number, // sample needs to have this many non-black pixels to be a valid edge
confirmationThreshold: number, //
singleSideConfirmationThreshold: number, // we need this much edges (out of all samples, not just edges) in order
// to confirm an edge in case there's no edges on top or bottom (other
// than logo, of course)
logoThreshold: number, // if edge candidate sits with count greater than this*all_samples, it can't be logo
// or watermark.
edgeTolerancePx?: number, // we check for black edge violation this far from detection point
edgeTolerancePercent?: number, // we check for black edge detection this % of height from detection point. unused
middleIgnoredArea: number, // we ignore this % of canvas height towards edges while detecting aspect ratios
minColsForSearch: number, // if we hit the edge of blackbars for all but this many columns (%-wise), we don't
// continue with search. It's pointless, because black edge is higher/lower than we
// are now. (NOTE: keep this less than 1 in case we implement logo detection)
},
pillarTest: {
ignoreThinPillarsPx: number, // ignore pillars that are less than this many pixels thick.
allowMisaligned: number // left and right edge can vary this much (%)
},
textLineTest: {
nonTextPulse: number, // if a single continuous pulse has this many non-black pixels, we aren't dealing
// with text. This value is relative to canvas width (%)
pulsesToConfirm: number, // this is a threshold to confirm we're seeing text.
pulsesToConfirmIfHalfBlack: number, // this is the threshold to confirm we're seeing text if longest black pulse
// is over 50% of the canvas width
testRowOffset: number // we test this % of height from detected edge
}
}
interface SettingsInterface {
arDetect: {
disabledReason: string, // if automatic aspect ratio has been disabled, show reason
allowedMisaligned: number, // top and bottom letterbox thickness can differ by this much.
// Any more and we don't adjust ar.
allowedArVariance: number, // amount by which old ar can differ from the new (1 = 100%)
timers: { // autodetection frequency
playing: number, // while playing
paused: number, // while paused
error: number, // after error
minimumTimeout: number,
tickrate: number, // 1 tick every this many milliseconds
},
autoDisable: { // settings for automatically disabling the extension
maxExecutionTime: number, // if execution time of main autodetect loop exceeds this many milliseconds,
// we disable it.
consecutiveTimeoutCount: number, // we only do it if it happens this many consecutive times
_updateFlags?: {
requireReload?: SettingsReloadFlags,
forSite?: string
}
// FOR FUTURE USE
consecutiveArResets: number // if aspect ratio reverts immediately after AR change is applied, we disable everything
},
canvasDimensions: {
blackframeCanvas: { // smaller than sample canvas, blackframe canvas is used to recon for black frames
// it's not used to detect aspect ratio by itself, so it can be tiny af
width: number,
height: number,
},
sampleCanvas: { // size of image sample for detecting aspect ratio. Bigger size means more accurate results,
// at the expense of performance
width: number,
height: number,
},
},
arDetect: AardSettings,
// samplingInterval: 10, // we sample at columns at (width/this) * [ 1 .. this - 1]
blackframe: {
sufficientColorVariance: number, // calculate difference between average intensity and pixel, for every pixel for every color
// component. Average intensity is normalized to where 0 is black and 1 is biggest value for
// that component. If sum of differences between normalized average intensity and normalized
// component varies more than this % between color components, we can afford to use less strict
// cumulative threshold.
cumulativeThresholdLax: number,
cumulativeThresholdStrict: number,// if we add values of all pixels together and get more than this, the frame is bright enough.
// (note: blackframe is 16x9 px -> 144px total. cumulative threshold can be reached fast)
blackPixelsCondition: number, // How much pixels must be black (1 all, 0 none) before we consider frame as black. Takes
// precedence over cumulative threshold: if blackPixelsCondition is met, the frame is dark
// regardless of whether cumulative threshold has been reached.
},
blackbar: {
blackLevel: number, // everything darker than 10/255 across all RGB components is considered black by
// default. blackLevel can decrease if we detect darker black.
threshold: number, // if pixel is darker than the sum of black level and this value, we count it as black
// on 0-255. Needs to be fairly high (8 might not cut it) due to compression
// artifacts in the video itself
frameThreshold: number, // threshold, but when doing blackframe test
imageThreshold: number, // in order to detect pixel as "not black", the pixel must be brighter than
// the sum of black level, threshold and this value.
gradientThreshold: number, // When trying to determine thickness of the black bars, we take 2 values: position of
// the last pixel that's darker than our threshold, and position of the first pixel that's
// brighter than our image threshold. If positions are more than this many pixels apart,
// we assume we aren't looking at letterbox and thus don't correct the aspect ratio.
gradientSampleSize: number, // How far do we look to find the gradient
maxGradient: number, // if two neighboring pixels in gradientSampleSize differ by more than this, then we aren't
// looking at a gradient
gradientNegativeTreshold: number,
gradientMaxSD: number, // reserved for future use
antiGradientMode: AntiGradientMode
},
variableBlackbarThresholdOptions: { // In case of poor bitrate videos, jpeg artifacts may cause us issues
// FOR FUTURE USE
enabled: boolean, // allow increasing blackbar threshold
disableArDetectOnMax: boolean, // disable autodetection when threshold goes over max blackbar threshold
maxBlackbarThreshold: number, // max threshold (don't increase past this)
thresholdStep: number, // when failing to set aspect ratio, increase threshold by this much
increaseAfterConsecutiveResets: number // increase if AR resets this many times in a row
},
sampling: {
staticCols: number, // we take a column at [0-n]/n-th parts along the width and sample it
randomCols: number, // we add this many randomly selected columns to the static columns
staticRows: number, // forms grid with staticSampleCols. Determined in the same way. For black frame checks
},
guardLine: { // all pixels on the guardline need to be black, or else we trigger AR recalculation
// (if AR fails to be recalculated, we reset AR)
ui: {
inPlayer: {
enabled: boolean,
ignoreEdgeMargin: number, // we ignore anything that pokes over the black line this close to the edge
// (relative to width of the sample)
imageTestThreshold: number, // when testing for image, this much pixels must be over blackbarThreshold
edgeTolerancePx: number, // black edge violation is performed this far from reported 'last black pixel'
edgeTolerancePercent: null // unused. same as above, except use % of canvas height instead of pixels
},
fallbackMode: {
enabled: boolean,
safetyBorderPx: number, // determines the thickness of safety border in fallback mode
noTriggerZonePx: number // if we detect edge less than this many pixels thick, we don't correct.
},
arSwitchLimiter: { // to be implemented
switches: number, // we can switch this many times
period: number // per this period
},
edgeDetection: {
sampleWidth: number, // we take a sample this wide for edge detection
detectionThreshold: number, // sample needs to have this many non-black pixels to be a valid edge
confirmationThreshold: number, //
singleSideConfirmationThreshold: number, // we need this much edges (out of all samples, not just edges) in order
// to confirm an edge in case there's no edges on top or bottom (other
// than logo, of course)
logoThreshold: number, // if edge candidate sits with count greater than this*all_samples, it can't be logo
// or watermark.
edgeTolerancePx?: number, // we check for black edge violation this far from detection point
edgeTolerancePercent?: number, // we check for black edge detection this % of height from detection point. unused
middleIgnoredArea: number, // we ignore this % of canvas height towards edges while detecting aspect ratios
minColsForSearch: number, // if we hit the edge of blackbars for all but this many columns (%-wise), we don't
// continue with search. It's pointless, because black edge is higher/lower than we
// are now. (NOTE: keep this less than 1 in case we implement logo detection)
},
pillarTest: {
ignoreThinPillarsPx: number, // ignore pillars that are less than this many pixels thick.
allowMisaligned: number // left and right edge can vary this much (%)
},
textLineTest: {
nonTextPulse: number, // if a single continuous pulse has this many non-black pixels, we aren't dealing
// with text. This value is relative to canvas width (%)
pulsesToConfirm: number, // this is a threshold to confirm we're seeing text.
pulsesToConfirmIfHalfBlack: number, // this is the threshold to confirm we're seeing text if longest black pulse
// is over 50% of the canvas width
testRowOffset: number // we test this % of height from detected edge
enabledFullscreenOnly: boolean,
popupAlignment: 'left' | 'right',
minEnabledWidth: number, // don't show UI if player is narrower than % of screen width
minEnabledHeight: number, // don't show UI if player is narrower than % of screen height
activation: 'trigger-zone' | 'player', // what needs to be hovered in order for UI to be visible
triggerZoneDimensions: { // how large the trigger zone is (relative to player size)
width: number
height: number,
offsetX: number, // fed to translateX(offsetX + '%'). Valid range [-100, 0]
offsetY: number // fed to translateY(offsetY + '%'). Valid range [-100, 100]
},
}
}
restrictions?: RestrictionsSettings;
crop: {
default: any;
},
stretch: {
default: any;
conditionalDifferencePercent: number // black bars less than this wide will trigger stretch
// if mode is set to '1'. 1.0=100%
},
kbm: {
enabled: boolean, // if keyboard/mouse handler service will run
keyboardEnabled: boolean, // if keyboard shortcuts are processed
mouseEnabled: boolean, // if mouse movement is processed
}
zoom: {
minLogZoom: number,
maxLogZoom: number,
announceDebounce: number // we wait this long before announcing new zoom
},
miscSettings: {
mousePan: {
enabled: boolean
@ -162,10 +259,7 @@ interface SettingsInterface {
mousePanReverseMouse: boolean,
defaultAr?: any
},
stretch: {
conditionalDifferencePercent: number // black bars less than this wide will trigger stretch
// if mode is set to '1'. 1.0=100%
},
resizer: {
setStyleString: {
maxRetries: number,
@ -197,8 +291,8 @@ interface SettingsInterface {
// ::: ACTIONS :::
// -----------------------------------------
// Nastavitve za ukaze. Zamenja stare nastavitve za bližnične tipke.
//
// Polje 'shortcut' je tabela, če se slučajno lotimo kdaj delati choordov.
//
// 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)
@ -220,74 +314,105 @@ interface SettingsInterface {
},
userAdded?: boolean,
}[],
// This object fulfills the same purpose as 'actions', but is written in less retarded and overly
// complicated way. Hopefully it'll be easier to maintain it that way.
commands?: {
crop?: CommandInterface[],
stretch?: CommandInterface[],
zoom?: CommandInterface[],
pan?: CommandInterface[],
internal?: CommandInterface[],
},
whatsNewChecked: boolean,
newFeatureTracker: any,
// -----------------------------------------
// ::: SITE CONFIGURATION :::
// -----------------------------------------
// Nastavitve za posamezno stran
// Config for a given page:
//
//
// <hostname> : {
// status: <option> // should extension work on this site?
// arStatus: <option> // should we do autodetection on this site?
//
//
// defaultAr?: <ratio> // automatically apply this aspect ratio on this side. Use extension defaults if undefined.
// stretch? <stretch mode> // automatically stretch video on this site in this manner
// videoAlignment? <left|center|right>
//
// type: <official|community|user> // 'official' — blessed by Tam.
// type: <official|community|user> // 'official' — blessed by Tam.
// // 'community' — blessed by reddit.
// // 'user' — user-defined (not here)
// override: <true|false> // override user settings for this site on update
// }
//
// Veljavne vrednosti za možnosti
// }
//
// Valid values for options:
//
// status, arStatus, statusEmbedded:
//
//
// * enabled — always allow, full
// * basic — allow, but only the basic version without playerData
// * default — allow if default is to allow, block if default is to block
// * disabled — never allow
//
//
sites: {
[x: string]: {
mode?: ExtensionMode,
autoar?: ExtensionMode,
autoarFallback?: ExtensionMode,
stretch?: StretchType,
videoAlignment?: VideoAlignmentType,
keyboardShortcutsEnabled?: ExtensionMode,
type?: string,
override?: boolean,
arPersistence?: boolean,
actions?: any;
cropModePersistence?: CropModePersistence;
DOM?: {
player?: {
manual?: boolean,
querySelectors?: string,
additionalCss?: string,
useRelativeAncestor?: boolean,
videoAncestor?: any,
playerNodeCss?: string,
periodicallyRefreshPlayerElement?: boolean
},
video?: {
manual?: boolean,
querySelectors?: string,
additionalCss?: string,
useRelativeAncestor?: boolean,
playerNodeCss?: string
}
},
css?: string;
usePlayerArInFullscreen?: boolean;
}
[x: string]: SiteSettingsInterface,
}
}
export default SettingsInterface;
export interface SiteSettingsInterface {
enable: ExtensionEnvironmentSettingsInterface;
enableAard: ExtensionEnvironmentSettingsInterface;
enableKeyboard: ExtensionEnvironmentSettingsInterface;
type?: 'official' | 'community' | 'user-defined' | 'testing' | 'officially-disabled' | 'unknown' | 'modified';
defaultType: 'official' | 'community' | 'user-defined' | 'testing' | 'officially-disabled' | 'unknown' | 'modified';
// must be defined in @global and @empty
persistCSA?: CropModePersistence, // CSA - crop, stretch, alignment
defaults?: { // must be defined in @global and @empty
crop?: {type: AspectRatioType, [x: string]: any},
stretch?: StretchType,
alignment?: {x: VideoAlignmentType, y: VideoAlignmentType},
}
cropModePersistence?: CropModePersistence;
stretchModePersistence?: CropModePersistence;
alignmentPersistence?: CropModePersistence;
activeDOMConfig?: string;
DOMConfig?: { [x: string]: SiteDOMSettingsInterface };
// the following script are for extension caching and shouldn't be saved.
// if they _are_ saved, they will be overwritten
currentDOMConfig?: SiteDOMSettingsInterface;
// the following fields are for use with extension update script
override?: boolean; // whether settings for this site will be overwritten by extension upgrade script
}
export interface SiteDOMSettingsInterface {
type: 'official' | 'community' | 'user-defined' | 'modified' | undefined;
elements?: {
player?: SiteDOMElementSettingsInterface,
video?: SiteDOMElementSettingsInterface,
other?: { [x: number]: SiteDOMElementSettingsInterface }
};
customCss?: string;
periodicallyRefreshPlayerElement?: boolean;
// the following script are for extension caching and shouldn't be saved.
// if they _are_ saved, they will be overwritten
anchorElementIndex?: number;
anchorElement?: HTMLElement;
}
export interface SiteDOMElementSettingsInterface {
manual?: boolean;
querySelectors?: string;
index?: number; // previously: useRelativeAncestor + videoAncestor
mode?: 'index' | 'qs';
nodeCss?: {[x: string]: string};
}
export default SettingsInterface;

View File

@ -2,16 +2,16 @@
* For some reason, Chrome really doesn't like when chrome.runtime
* methods are wrapped inside a ES6 proxy object. If 'port' is a
* ES6 Proxy of a Port object that `chrome.runtime.connect()` creates,
* then Chrome will do bullshits like `port.sendMessage` and
* then Chrome will do bullshits like `port.sendMessage` and
* `port.onMessage.addListener` crashing your Vue3 UI with bullshits
* excuses, e.g.
*
*
* | TypeError: Illegal invocation. Function must be called on
* | an object of type Port
*
*
* which is some grade A bullshit because Firefox can handle that just
* fine.
*
*
* There's two ways how I could handle this:
* * Find out how to get the original object from the proxy Vue3
* creates, which would take time and ruin my xmass holiday, or
@ -19,8 +19,8 @@
* the very real possibility that there's prolly a reason Chrome
* does things in its own very special(tm) way, as if it had one
* extra chromosome over Firefox.
*
* Easy chhoice, really.
*
* Easy choice, really.
*/
export class ChromeShittinessMitigations {
static port = null;
@ -30,4 +30,4 @@ export class ChromeShittinessMitigations {
}
}
export default ChromeShittinessMitigations;
export default ChromeShittinessMitigations;

View File

@ -1,7 +1,7 @@
class KeyboardShortcutParser {
static parseShortcut(keypress) {
let shortcutCombo = '';
if (keypress.ctrlKey) {
shortcutCombo += 'Ctrl + ';
}
@ -21,6 +21,20 @@ class KeyboardShortcutParser {
}
return shortcutCombo;
}
static generateShortcutFromKeypress(event) {
return {
ctrlKey: event.ctrlKey,
altKey: event.altKey,
shiftKey: event.shiftKey,
metaKey: event.metaKey,
code: event.code,
key: event.key,
keyup: true,
keydown: false,
type: event.type, // only needed for purposes of EditShortcutButton
}
}
}
export default KeyboardShortcutParser;
export default KeyboardShortcutParser;

View File

@ -1,3 +1,18 @@
export async function sleep(timeout) {
return new Promise<void>( (resolve, reject) => setTimeout(() => resolve(), timeout));
}
/**
* Creates deep copy of an object
* @param obj
* @returns
*/
export function _cp(obj) {
try {
return JSON.parse(JSON.stringify(obj));
} catch (e) {
// console.error('Failed to parse json. This probably means that the data we received was not an object. Will return data as-is');
// console.error('data in:', obj, 'error:', e);
return obj;
}
}

69
src/csui/GlobalFrame.vue Normal file
View File

@ -0,0 +1,69 @@
<template>
<div class="uw-clickthrough relative w-100 h-100">
<template v-for="rectangle of drawnRectangles" :key="rectangle.id ?? rectangle">
<!-- Player element overlays -->
<div class="absolute z-index-overlay"
:style="rectangle.style"
>
<!-- used for drawing player element overlay rectangles - keep this section empty -->
</div>
<!-- Notification overlay -->
<div class="absolute z-index-notification notification-area">
<template v-for="notification of displayedNotifications" :key="notification.id">
<div class="notification d-flex flex-row">
<div class="notification-icon">
<mdicon :name="notification.icon ?? 'alert'" :size="128"></mdicon>
</div>
<div class="notification-text">
<h3 class="notification-title">{{ notification.title }}</h3>
<p class="notification-verbose">{{ notification.text }}</p>
</div>
</div>
</template>
</div>
</template>
</div>
</template>
<script>
import UIProbeMixin from './src/utils/UIProbeMixin';
export default {
components: {
},
mixins: [
UIProbeMixin
],
data() {
return {
drawnRectangles: [],
displayedNotifications: [],
}
},
async created() {
this.logger = new Logger();
// this prolly needs to be taken out
await this.logger.init({
allowLogging: true,
});
/**
* Setup the "companion" onMouseMove handler to the one in the content script.
* We can handle events with the same function we use to handle events from
* the content script.
*/
document.addEventListener('mousemove', (event) => {
this.handleProbe({
coords: {
x: event.clientX,
y: event.clientY
}
}, this.origin);
});
}
}
</script>

View File

@ -1,461 +0,0 @@
<template>
<div v-if="showLoggerUi" class="root-window flex flex-column overflow-hidden"
@keyup.stop
@keydown.stop
@keypress.stop
>
<div class="header">
<div class="header-top flex flex-row">
<div class="flex-grow">
<h1>{{header.header}}</h1>
</div>
<div class="button flex-noshrink button-header"
@click="hidePopup()"
>
<template v-if="logStringified">Finish logging</template>
<template v-else>Hide popup</template>
</div>
<!-- <div class="button flex-noshrink button-header"
@click="stopLogging()"
>
Stop logging
</div> -->
</div>
<div class="header-bottom">
<div>{{header.subheader}}</div>
</div>
</div>
<div class="content flex flex-row flex-grow overflow-hidden">
<!-- LOGGER SETTINGS PANEL -->
<div class="settings-panel flex flex-noshrink flex-column">
<div class="panel-top flex-nogrow">
<h2>Logger configuration</h2>
</div>
<div class="flex flex-row flex-end w100">
<div v-if="!showTextMode" class="button" @click="loadDefaultConfig()">
Default config
</div>
<div v-if="!showTextMode" class="button" @click="showTextMode = true">
<Icon icon="clipboard-plus" style="font-size: 2em"></Icon>&nbsp; Paste config ...
</div>
<div v-else class="button" @click="showTextMode = false">
Back
</div>
</div>
<div class="panel-middle scrollable flex-grow log-config-margin">
<template v-if="showTextMode">
<div ref="settingsEditArea"
style="white-space: pre-wrap; border: 1px solid orange; padding: 10px;"
class="monospace h100"
:class="{'jsonbg': !confHasError, 'jsonbg-error': confHasError}"
contenteditable="true"
@input="updateSettings"
>
{{parsedSettings}}
</div>
</template>
<template v-else>
<JsonObject label="logger-settings"
:value="currentSettings"
:ignoreKeys="{'allowLogging': false}"
@change="updateSettingsUi"
></JsonObject>
</template>
</div>
<div class="flex flex-row flex-end">
<div class="button" @click="restoreLoggerSettings()">
Revert
</div>
<div class="button button-primary" @click="saveLoggerSettings()">
Save
</div>
</div>
</div>
<!-- LOGGER OUTPUT/START LOGGING -->
<div class="results-panel flex flex-shrink flex-column overflow-hidden">
<div class="panel-top flex-nogrow">
<h2>Logger results</h2>
</div>
<template v-if="logStringified">
<div v-if="confHasError" class="warn">
Logger configuration contains an error. You can export current log, but you will be unable to record a new log.
</div>
<div class="panel-middle scrollable flex-grow p-t-025em">
<pre>
{{logStringified}}
</pre>
</div>
<div class="flex-noshrink flex flex-row flex-end p-t-025em">
<div class="button button-bar"
@click="startLogging()"
>
New log
</div>
<div class="button button-bar"
@click="exportLog()"
>
Export log
</div>
<div class="button button-bar button-primary"
@click="exportAndQuit()"
>
Export & finish
</div>
</div>
</template>
<template v-else>
<div class="panel-middle scrollable flex-grow">
<div>
<p>Here's express usage tutorial on how to use the logger.</p>
<p>Quick rundown of all the options you can put into logger configuration can be found <a href="https://github.com/tamius-han/ultrawidify/wiki/Development&Debugging:-Logger-options" target="_blank">here</a>.</p>
<p>If you want logging results to appear here, in this window, you need to put appropriate configuration in the fileOptions section of the settings. You can edit the settings by clicking 'paste config' button.</p>
<p>To start logging to console, it's enough to click that tiny 'save' button down there, under logger options (<code>logger-settings.allowLogging</code> must be set to true. Then depending on where you want logging to happen you also need to enable either <code>logger-settings.fileOptions.enabled</code> or <code>logger-settings.consoleOptions.enabled</code>)</p>
<p>You can quickly toggle values for various components by clicking on "true" or "false".</p>
<p>Click the small 'save' down at the bottom of this window to immediately apply the logger configuration changes.</p>
<p>Yes, I know this is not a pinnacle of user-friendliness, nor a pinnacle of web design. It was put together very quickly, mostly for my convenience. I have plans to move extension UI from the popup into the player, tho, and these plans will make this window obsolete. Because of that, I plan to do absolutely nothing about the state of this window. Wait for the extension redesign pls.</p>
</div>
<div v-if="confHasError" class="warn">
Logger configuration contains an error. Cannot start logging.
</div>
<div v-else-if="lastSettings && lastSettings.allowLogging && lastSettings.consoleOptions && lastSettings.consoleOptions.enabled"
class="flex flex-column flex-center flex-cross-center w100 h100"
>
<p class="m-025em">
Logging in progress ...
</p>
<div class="button button-big button-primary"
@click="stopLogging()"
>
Stop logging
</div>
<template v-if="lastSettings && lastSettings.timeout"
class="m-025em"
>
<p>
... or wait until logging ends.
</p>
<p>
You can <a @click="hidePopup()">hide popup</a> it will automatically re-appear when logging finishes.
</p>
</template>
<template v-else-if="lastSettings">
<p>
You can <a @click="hidePopup()">hide popup</a> the logging will continue until you re-open the popup and stop it.
</p>
</template>
</div>
<div v-else class="flex flex-column flex-center flex-cross-center w100 h100">
<div class="button button-big button-primary"
@click="startLogging()"
>
Start logging
</div>
</div>
</div>
</template>
</div>
</div>
<!-- <div>
button row is heres
</div> -->
</div>
</template>
<script>
import { mapState } from 'vuex';
import Logger, { baseLoggingOptions } from '../ext/lib/Logger';
import Comms from '../ext/lib/comms/Comms';
import IO from '../common/js/IO';
import JsonObject from '../common/components/JsonEditor/JsonObject';
import Icon from '../common/components/Icon';
export default {
components: {
JsonObject,
Icon,
},
data() {
return {
showLoggerUi: false,
header: {
header: 'whoopsie daisy',
subheader: 'you broke the header choosing script'
},
parsedSettings: '',
lastSettings: {},
currentSettings: {},
confHasError: false,
logStringified: '',
showTextMode: false,
}
},
async created() {
const headerRotation = [{
header: "DEFORESTATOR 5000",
subheader: "Iron Legion's finest logging tool"
}, {
header: "Astinus",
subheader: "Ultrawidify logging tool"
}, {
header: "Tracer",
subheader: "I'm already printing stack traces"
}, {
header: "Grûmsh",
subheader: "He who watches"
}, {
header: "Situation Room/The Council",
subheader: "We will always be watching"
}];
this.header = headerRotation[Math.floor(+Date.now() / (3600000*24)) % headerRotation.length] || this.header;
this.getLoggerSettings();
},
computed: {
...mapState([
'uwLog',
'showLogger',
'loggingEnded',
]),
},
watch: {
uwLog(newValue, oldValue) {
if (oldValue !== newValue) {
this.$store.dispatch('uw-show-logger');
this.logStringified = JSON.stringify(newValue, null, 2);
}
},
async showLogger(newValue) {
this.showLoggerUi = newValue;
// update logger settings (they could have changed while the popup was closed)
if (newValue) {
this.getLoggerSettings();
}
},
loggingEnded(newValue) {
// note the value of loggingEnded never actually matters. Even if this value is 'true'
// internally, vuexStore.dspatch() will still do its job and give us the signal we want
if (newValue) {
this.stopLogging();
}
}
},
methods: {
loadDefaultConfig() {
this.lastSettings = baseLoggingOptions;
this.parsedSettings = JSON.stringify(this.lastSettings, null, 2) || '';
this.currentSettings = JSON.parse(JSON.stringify(this.lastSettings));
},
async getLoggerSettings() {
this.lastSettings = await Logger.getConfig() || baseLoggingOptions;
this.parsedSettings = JSON.stringify(this.lastSettings, null, 2) || '';
this.currentSettings = JSON.parse(JSON.stringify(this.lastSettings));
},
updateSettings(val) {
try {
this.parsedSettings = JSON.stringify(JSON.parse(val.target.textContent.trim()), null, 2);
this.lastSettings = JSON.parse(val.target.textContent.trim());
this.currentSettings = JSON.parse(JSON.stringify(this.lastSettings));
this.confHasError = false;
} catch (e) {
this.confHasError = true;
}
},
updateSettingsUi(val) {
try {
this.parsedSettings = JSON.stringify(val, null, 2);
this.lastSettings = val;
this.currentSettings = JSON.parse(JSON.stringify(this.lastSettings));
} catch (e) {
}
},
saveLoggerSettings() {
Logger.saveConfig({...this.lastSettings});
},
restoreLoggerSettings() {
this.getLoggerSettings();
this.confHasError = false;
},
async startLogging(){
this.logStringified = undefined;
await Logger.saveConfig({...this.lastSettings, allowLogging: true});
window.location.reload();
},
hidePopup() {
// this function only works as 'close' if logging has finished
if (this.logStringified) {
Logger.saveConfig({...this.lastSettings, allowLogging: false});
this.logStringified = undefined;
}
this.$store.dispatch('uw-hide-logger');
this.showLoggerUi = false;
},
closePopupAndStopLogging() {
Logger.saveConfig({...this.lastSettings, allowLogging: false});
this.logStringified = undefined;
this.$store.dispatch('uw-hide-logger');
},
stopLogging() {
Logger.saveConfig({...this.lastSettings, allowLogging: false});
this.lastSettings.allowLogging = false;
},
exportLog() {
IO.csStringToFile(this.logStringified);
},
exportAndQuit() {
this.exportLog();
this.logStringified = undefined;
this.closePopupAndStopLogging();
}
}
}
</script>
<style lang="scss" src="../res/css/flex.scss" scoped></style>
<style lang="scss" scoped>
@import '../res/css/colors.scss';
@import '../res/css/font/overpass.css';
@import '../res/css/font/overpass-mono.css';
@import '../res/css/common.scss';
.uw-ultrawidify-container-root {
.root-window {
position: fixed !important;
top: 5vh !important;
left: 5vw !important;
width: 90vw !important;
height: 90vh !important;
z-index: 999999 !important;
background-color: rgba( $page-background, 0.9) !important;
color: #f1f1f1 !important;
font-size: 14px !important;
box-sizing: border-box !important;
pointer-events: auto !important;
}
div {
font-family: 'Overpass';
}
h1, h2 {
font-family: 'Overpass Thin';
}
h1 {
font-size: 4em;
}
h2 {
font-size: 2em;
}
.header {
h1 {
margin-bottom: -0.20em;
margin-top: 0.0em;
}
.header-top, .header-bottom {
padding-left: 16px;
padding-right: 16px;
}
.header-top {
background-color: $popup-header-background !important;
}
.header-bottom {
font-size: 1.75em;
}
}
.content {
box-sizing: border-box;
padding: 8px 32px;
width: 100%;
}
.settings-panel {
box-sizing: border-box;
padding-right: 8px;
flex-grow: 2 !important;
min-width: 30% !important;
flex-shrink: 0 !important;
height: inherit !important;
}
.results-panel {
box-sizing: border-box;
padding-left: 8px;
max-width: 70% !important;
flex-grow: 5 !important;
flex-shrink: 0 !important;
height: inherit !important;
}
.scrollable {
overflow: auto;
}
.overflow-hidden {
overflow: hidden;
}
pre {
font-family: 'Overpass Mono';
}
.m-025em {
margin: 0.25em;
}
.p-t-025em {
padding-top: 0.25em;
}
.button {
display: inline-flex;
align-items: center;
justify-items: center;
padding-left: 2em;
padding-right: 2em;
}
.button-primary {
background-color: $primary;
color: #fff;
}
.button-big {
font-size: 1.5em;
padding: 1.75em 3.25em;
}
.button-bar {
font-size: 1.25em;
padding: 0.25em 1.25em;
margin-left: 0.25em;
}
.button-header {
font-size: 2em;
padding-top: 0.1em;
padding-left: 1em;
padding-right: 1em;
}
.jsonbg {
background-color: #131313;
}
.jsonbg-error {
background-color: #884420;
}
.log-config-margin {
margin-top: 3em;
margin-bottom: 3em;
}
}
</style>

View File

@ -1,216 +0,0 @@
<template>
<div v-if="showNotification" class="uw-ultrawidify-container flex flex-column overflow-hidden">
<div class="notification-popup flex flex-row">
<div v-if="notificationIcon" class="flex-nogrow flex-noshrink notification-icon">
<Icon
class="flex-nogrow flex-noshrink"
:icon="notificationIcon"
>
</Icon>
</div>
<div class="notification-content flex-grow flex-shrink flex flex-column flex-cross-center">
<div
class="notification-text"
v-html="notificationText"
>
</div>
<div
v-if="notificationActions"
class="action-buttons flex flex-row"
>
<div
v-for="action of notificationActions"
class="action-button"
:key="action"
@click="action.command"
>
<Icon v-if="action.icon" :icon="action.icon"></Icon>{{action.label}}
</div>
</div>
<div
v-if="hideActions"
class="hide-actions"
>
Never show again:<wbr>&nbsp;
<template
v-for="action of hideActions"
:key="action"
>
<i @click="closeNotification">
<a
class="hide-action-button"
@click="action.command"
>
{{action.label}}
</a>
<wbr>&nbsp;
</i>
</template>
</div>
</div>
<div
class="notification-icon action-button"
@click="closeNotification()"
>
<Icon
class="flex-nogrow flex-noshrink"
icon="x"
>
</Icon>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex';
import Icon from '../common/components/Icon';
export default {
components: {
Icon,
},
data() {
return {
notificationTimeout: null,
notificationIcon: "exclamation-triangle",
notificationText: "<b>Sample text.</b> This will be replaced with real notification later.",
notificationActions: null,
hideActions: null,
showNotification: false,
};
},
computed: {
...mapState([
'notificationConfig'
]),
},
watch: {
/**
* Sets new notification config. Currently, we can only show one notification at a time.
*
* We expect a config object like this:
* {
* timeout: number how long we'll be displaying the notification. If empty, 10s. -1: until user dismisses it
* icon: string what icon we're gonna show. We're using bootstrap icons. Can be empty.
* text: notification text. Supports HTML.
* notificationActions: [
* {
* command: function that gets executed upon clicking the button.
* label: label of the button
* icon: icon of the button
* }
* ],
* hideOptions: [
* // more of notificationActions except it's a special case
* ]
* }
*/
notificationConfig(newConfig) {
if (newConfig) {
this.notificationText = newConfig.text;
this.notificationActions = newConfig.notificationActions;
this.notificationIcon = newConfig.icon;
this.hideActions = newConfig.hideActions;
this.showNotification = true;
if (newConfig.timeout !== -1) {
this.notificationTimeout = setTimeout(() => this.closeNotification(), newConfig.timeout ?? 5000);
}
}
}
},
methods: {
closeNotification() {
clearTimeout(this.notificationTimeout);
this.showNotification = false;
this.notificationIcon = null;
this.notificationText = null;
this.notificationActions = null;
this.hideActions = null;
}
}
}
</script>
<style lang="scss" scoped>
@import '../res/css/uwui-base.scss';
@import '../res/css/colors.scss';
@import '../res/css/font/overpass.css';
@import '../res/css/font/overpass-mono.css';
@import '../res/css/common.scss';
.uw-ultrawidify-container-root {
position: relative;
width: 100%;
height: 100%;
pointer-events: none;
display: block !important;
position: relative !important;
width: 100% !important;
height: 100% !important;
pointer-events: none !important;
font-size: 16px !important;
.notification-popup {
pointer-events: auto !important;
position: absolute;
z-index: 99999999;
top: 2em;
right: 2em;
width: 32em;
padding: 0.7em 0.5em;
font-family: 'Overpass';
background-color: rgba(108, 55, 12, 0.779);
color: #fff;
user-select: none;
}
.notifcation-content {
margin-left: 0.5em;
}
.notification-text {
text-align: justify;
padding-left: 0.5em;
padding-right: 0.25em;
}
.notification-icon {
font-size: 3em;
line-height: 0.5;
}
.action-button {
pointer-events: auto;
cursor: pointer;
}
.hide-actions {
color: #ccc;
font-size: 0.8em;
justify-self: flex-end;
align-self: flex-end;
margin-top: 1em;
margin-bottom: -1em;
}
.hide-action-button {
color: #eee;
font-size: 0.9em;
text-decoration: underline;
text-decoration-color: rgba(255,255,255,0.5);
pointer-events: auto;
cursor: pointer;
}
}
</style>

687
src/csui/PlayerOverlay.vue Normal file
View File

@ -0,0 +1,687 @@
<template>
<div
class="context-spawn uw-ui-area"
style="z-index: 1000;"
v-if="!triggerZoneEditorVisible"
>
<div
class="spawn-container uw-ui-trigger"
:style="triggerZoneStyles"
>
&nbsp;
</div>
</div>
<div
v-if="contextMenuActive || settingsInitialized && uwTriggerZoneVisible && !isGlobal"
class="context-spawn uw-clickable uw-ui-area"
style="z-index: 1001"
>
<GhettoContextMenu
alignment="right" class="uw-menu"
@mouseenter="() => {preventContextMenuHide(); newFeatureViewUpdate('uw6.ui-popup')}"
@mouseleave="allowContextMenuHide()"
>
<template v-slot:activator>
<div class="context-item">
Ultrawidify
</div>
</template>
<slot>
<!--
Didn't manage to ensure that extension status pops up above other menu items in less than 3 minutes with z-index,
so wrapping 'status' and 'real menu items' in two different divs, ordering them in the opposite way, and then
ensuring correct ordering with flex-direction: column-reverse ended up being easier and faster.
-->
<div class="menu-width flex-reverse-order">
<div style="z-index: 1000">
<GhettoContextMenu alignment="right">
<template v-slot:activator>
Crop
</template>
<slot>
<GhettoContextMenuOption
v-for="(command, index) of settings?.active.commands.crop"
:key="index"
:label="command.label"
:shortcut="getKeyboardShortcutLabel(command)"
@click="execAction(command)"
>
</GhettoContextMenuOption>
</slot>
</GhettoContextMenu>
<GhettoContextMenu alignment="right">
<template v-slot:activator>
Stretch
</template>
<slot>
<GhettoContextMenuOption
v-for="(command, index) of settings?.active.commands.stretch"
:key="index"
:label="command.label"
:shortcut="getKeyboardShortcutLabel(command)"
@click="execAction(command)"
>
</GhettoContextMenuOption>
</slot>
</GhettoContextMenu>
<GhettoContextMenu alignment="right">
<template v-slot:activator>
<div class="context-item">
Align
</div>
</template>
<slot>
<GhettoContextMenuItem :disableHover="true" :css="{'reduced-padding': true}">
<AlignmentOptionsControlComponent
:eventBus="eventBus"
>
</AlignmentOptionsControlComponent>
</GhettoContextMenuItem>
</slot>
</GhettoContextMenu>
<!-- shortcut for configuring UI -->
<GhettoContextMenuOption
v-if="settings.active.newFeatureTracker?.['uw6.ui-popup']?.show > 0"
@click="showUwWindow('playerUiSettings')"
>
<span style="color: #fa6;">Change when and if<br/>this popup appears</span>
<span style="font-size: 0.8rem">
<span style="font-size: 0.8rem; opacity: 0.5">This menu option will show {{settings.active.newFeatureTracker?.['uw6.ui-popup']?.show}} more<br/> times; or until clicked or dismissed.<br/>
Also accessible via:<br/> <span style="font-size: 0.85em">EXTENSION SETTINGS > UI AND KEYBOARD</span>.
</span>
<br/>
<a style="color: #fa6; cursor: pointer;" @click="() => acknowledgeNewFeature('uw6.ui-popup')">Dismiss this option</a>
</span>
</GhettoContextMenuOption>
<!-- -->
<GhettoContextMenuOption
@click="showUwWindow()"
label="Extension settings"
>
</GhettoContextMenuOption>
<GhettoContextMenuOption
@click="showUwWindow('playerDetection')"
label="Incorrect cropping?"
>
</GhettoContextMenuOption>
<GhettoContextMenuOption
@click="showUwWindow('about')"
label="Not working?"
>
</GhettoContextMenuOption>
</div>
<div style="z-index: 10000">
<GhettoContextMenuItem
class="extension-status-messages"
:disableHover="true"
>
Site compatibility:
<SupportLevelIndicator
:siteSupportLevel="siteSupportLevel"
>
</SupportLevelIndicator>
<div v-if="statusFlags.hasDrm" class="aard-blocked">
Autodetection potentially<br/>
unavailable due to <a href="https://en.wikipedia.org/wiki/Digital_rights_management">DRM</a>.
</div>
<div v-else-if="statusFlags.aardErrors?.cors" class="aard-blocked">
Autodetection blocked<br/>
by site/browser (CORS).
</div>
<div v-else-if="statusFlags.aardErrors?.webglError" class="aard-blocked">
Autodetection unavailable<br/>
due to webgl error.
</div>
</GhettoContextMenuItem>
</div>
</div>
</slot>
</GhettoContextMenu>
</div>
<div
v-if="settingsInitialized && uwWindowVisible"
class="uw-window flex flex-col uw-clickable uw-ui-area"
:class="{'fade-out': uwWindowFadeOut}"
>
<PlayerUIWindow
:settings="settings"
:eventBus="eventBus"
:logger="logger"
:in-player="!isGlobal"
:site="site"
:defaultTab="defaultWindowTab"
@close="uwWindowVisible = false"
@preventClose="(event) => uwWindowFadeOutDisabled = event"
></PlayerUIWindow>
</div>
<div
v-if="triggerZoneEditorVisible"
class="context-spawn uw-ui-area"
style="z-index: 1000;"
>
<TriggerZoneEditor
:settings="settings"
:eventBus="eventBus"
:playerDimensions="playerDimensions"
>
</TriggerZoneEditor>
</div>
</template>
<script>
import PlayerUIWindow from './src/PlayerUIWindow.vue';
import GhettoContextMenu from './src/components/GhettoContextMenu.vue';
import GhettoContextMenuItem from './src/components/GhettoContextMenuItem.vue';
import GhettoContextMenuOption from './src/components/GhettoContextMenuOption.vue';
import AlignmentOptionsControlComponent from './src/PlayerUiPanels/AlignmentOptionsControlComponent.vue';
import BrowserDetect from '../ext/conf/BrowserDetect';
import Logger from '../ext/lib/Logger';
import Settings from '../ext/lib/Settings';
import EventBus from '../ext/lib/EventBus';
import UIProbeMixin from './src/utils/UIProbeMixin';
import KeyboardShortcutParserMixin from './src/utils/KeyboardShortcutParserMixin';
import CommsMixin from './src/utils/CommsMixin';
import SupportLevelIndicator from './src/components/SupportLevelIndicator.vue';
import TriggerZoneEditor from './src/components/TriggerZoneEditor.vue';
export default {
components: {
PlayerUIWindow,
GhettoContextMenu,
GhettoContextMenuItem,
GhettoContextMenuOption,
AlignmentOptionsControlComponent,
SupportLevelIndicator,
TriggerZoneEditor,
},
mixins: [
UIProbeMixin,
KeyboardShortcutParserMixin,
CommsMixin
],
data() {
return {
uwTriggerZoneVisible: false,
uwTriggerZoneTimeout: undefined,
uwTriggerRegionConf: {
left: "10%",
top: "10%",
height: "30%",
width: "30%",
maxWidth: "24rem",
maxHeight: "13.37rem",
},
uwWindowFadeOutDisabled: false,
uwWindowFadeOut: false,
uwWindowCloseTimeout: undefined,
uwWindowVisible: false,
triggerZoneEditorVisible: false,
// component properties
settings: {},
BrowserDetect: BrowserDetect,
settingsInitialized: false,
eventBus: new EventBus(),
logger: null,
// NOTE: chromium doesn't allow us to access window.parent.location
// meaning we will have to correct this value from our uwui-probe
// messages ... which is a bummer.
site: null,
origin: '*', // will be set appropriately once the first uwui-probe event is received
lastProbeTs: null,
isGlobal: true,
disabled: false,
contextMenuActive: false,
triggerZoneActive: false,
uiVisible: true,
debugData: {
resizer: {},
player: {},
},
debugDataPrettified: '',
// in global overlay, this property is used to determine
// if closing the window should emit uw-set-ui-state
// event on eventBus
showPlayerUIAfterClose: false,
statusFlags: {
hasDrm: undefined,
aardErrors: undefined,
},
defaultWindowTab: 'videoSettings',
saveState: {},
siteSettings: undefined,
previewZoneVisible: false,
};
},
computed: {
// LPT: NO ARROW FUNCTIONS IN COMPUTED,
// IS SUPER HARAM
// THINGS WILL NOT WORK IF YOU USE ARROWS
windowWidth() {
return window.innerWidth;
},
windowHeight() {
return window.innerHeight;
},
// LPT: NO ARROW FUNCTIONS IN COMPUTED,
// IS SUPER HARAM
// THINGS WILL NOT WORK IF YOU USE ARROWS
siteSupportLevel() {
return (this.site && this.siteSettings) ? this.siteSettings.data.type || 'no-support' : 'waiting';
}
},
watch: {
showUi(visible) {
if (visible !== undefined) {
this.uiVisible = visible;
}
},
resizerDebugData(newData) {
this.debugData.resizer = newData;
this.debugDataPrettified = JSON.stringify(this.debugData, null, 2);
},
playerDebugData(newData) {
this.debugData.player = newData;
this.debugDataPrettified = JSON.stringify(this.debugData, null, 2);
}
},
async created() {
this.logger = new Logger();
// this prolly needs to be taken out
await this.logger.init({
allowLogging: true,
});
this.settings = new Settings({afterSettingsSaved: this.updateConfig, logger: this.logger});
this.settings.listenAfterChange(() => this.updateTriggerZones());
await this.settings.init();
this.settingsInitialized = true;
// set up communication with client script.
// NOTE: companion onmousemove is set up in UIProbeMixin
window.addEventListener('message', event => {
this.handleMessage(event);
});
this.eventBus.subscribe('uw-config-broadcast', {function: (data) => {
switch (data.type) {
case 'drm-status':
this.statusFlags.hasDrm = data.hasDrm;
break;
case 'aard-error':
this.statusFlags.aardErrors = data.aardErrors;
break;
case 'player-dimensions':
this.playerDimensionsUpdate(data.data);
break;
}
}});
this.eventBus.subscribe('uw-set-ui-state', { function: (data) => {
if (data.globalUiVisible !== undefined) {
if (this.isGlobal) {
if (data.globalUiVisible) {
this.showUwWindow();
} else {
this.hideUwWindow(true);
}
// this.showPlayerUIAfterClose = data.showPlayerUIAfterClose;
} else {
// non global UIs are hidden while global overlay
// is visible and vice versa
// this.disabled = data.globalUiVisible;
this.saveState = {
uwWindowVisible: this.uwWindowVisible,
uwWindowFadeOutDisabled: this.uwWindowFadeOutDisabled,
uwWindowFadeOut: this.uwWindowFadeOut
};
this.uwWindowFadeOutDisabled = false;
this.hideUwWindow(true);
}
}
}});
this.eventBus.subscribe(
'uw-restore-ui-state',
{
function: (data) => {
if (this.saveState) {
if (this.saveState.uwWindowVisible) {
this.showUwWindow();
}
this.uwWindowFadeOutDisabled = this.saveState.uwWindowFadeOutDisabled;
this.uwWindowFadeOut = this.saveState.uwWindowFadeOut;
}
this.saveState = {};
}
}
);
this.eventBus.subscribe('ui-trigger-zone-update', {
function: (data) => {
this.showTriggerZonePreview = data.previewZoneVisible;
// this.;
}
});
this.eventBus.subscribe(
'start-trigger-zone-edit',
{
function: () => {
this.triggerZoneEditorVisible = true;
this.uwWindowVisible = false;
}
}
);
this.eventBus.subscribe(
'finish-trigger-zone-edit',
{
function: () => {
this.triggerZoneEditorVisible = false;
this.showUwWindow('playerUiSettings');
}
}
);
this.sendToParentLowLevel('uwui-get-role', null);
this.sendToParentLowLevel('uwui-get-theme', null);
this.sendToParentLowLevel('uw-bus-tunnel', {
action: 'get-player-dimensions'
});
},
methods: {
/**
* Gets URL of the browser settings page (i think?)
*/
getUrl(url) {
return BrowserDetect.getURL(url);
},
/**
* Mostly intended to process messages received via window.addEventListener('message').
* This method should include minimal logic instead, it should only route messages
* to the correct function down the line.
*/
handleMessage(event) {
switch (event.data.action) {
case 'uwui-probe':
if (!this.site) {
this.origin = event.origin;
this.site = event.origin.split('//')[1];
this.siteSettings = this.settings.getSiteSettings(this.site);
}
return this.handleProbe(event.data, event.origin); // handleProbe is defined in UIProbeMixin
case 'uw-bus-tunnel':
return this.handleBusTunnelIn(event.data.payload);
case 'uwui-set-role':
this.isGlobal = event.data.payload.role === 'global';
this.sendToParentLowLevel('uwui-interface-ready', true);
break;
}
},
/**
* Handles trigger zone
*/
handleTriggerZone(mouseInside) {
// this.triggerZoneActive = mouseInside;
},
acknowledgeNewFeature(featureKey) {
delete this.settings.active.newFeatureTracker[featureKey];
this.settings.saveWithoutReload();
},
newFeatureViewUpdate(featureKey) {
if (!this.settings.active.newFeatureTracker[featureKey]) {
return;
}
try {
this.settings.active.newFeatureTracker[featureKey].show--;
this.settings.saveWithoutReload();
if (this.settings.active.newFeatureTracker[featureKey]?.show < 0) {
this.acknowledgeNewFeature(featureKey);
}
} catch (e) {
// do nothing
}
},
/**
* Sends message to parent _without_ using event bus.
*/
sendToParentLowLevel(action, payload, lowLevelExtras = {}) {
window.parent.postMessage(
{
action, payload, ...lowLevelExtras
},
'*'
);
},
preventContextMenuHide() {
this.contextMenuActive = true;
},
allowContextMenuHide() {
this.contextMenuActive = false;
},
setTriggerZoneActive(active, event) {
this.triggerZoneActive = active;
},
showUwWindow(tab) {
this.defaultWindowTab = tab; // can be undefined
this.uwWindowFadeOut = false;
this.uwWindowVisible = true;
this.uwTriggerZoneVisible = false;
this.allowContextMenuHide();
// refresh DRM status
this.eventBus.send('get-drm-status');
// if (this.isGlobal) {
// this.sendToParentLowLevel('uwui-clickable', undefined, {clickable: true});
// }
},
hideUwWindow(skipTimeout = false) {
if (this.uwWindowFadeOutDisabled) {
return;
}
const timeout = skipTimeout ? 0 : 1100;
this.uwWindowCloseTimeout = setTimeout(
() => {
this.uwWindowVisible = false;
// Global UI has some extra housekeeping to do when window gets hidden
if (this.isGlobal) {
this.sendToParentLowLevel('uwui-global-window-hidden', {});
}
},
timeout
);
this.uwWindowFadeOut = true;
},
cancelUwWindowHide() {
this.uwWindowFadeOut = false;
clearTimeout(this.uwWindowCloseTimeout);
},
handleBusTunnelIn(payload) {
this.eventBus.send(payload.action, payload.config, payload.routingData);
}
}
}
</script>
<style lang="scss">
.ard-blocked {
color: rgb(219, 125, 48) !important;
background-color: rgba(0,0,0,0.85) !important;
}
</style>
<style lang="scss" src="./src/res-common/panels.scss" scoped module></style>
<style lang="scss" src="./src/res-common/common.scss" scoped module></style>
<style lang="scss" scoped>
.uw-hover {
position: absolute;
z-index: 999999999999999999;
}
.reduced-padding {
padding: 1rem !important;
}
.uv-hover-trigger-region {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border: 0.5rem dashed #fff;
color: #fff;
backdrop-filter: blur(0.5rem) brightness(0.5);
}
.uw-window {
position: fixed;
top: 10%;
left: 10%;
z-index: 999999999999999999;
width: 2500px;
height: 1200px;
max-width: 80%;
max-height: 80%;
pointer-events: all !important;
opacity: 1;
backdrop-filter: blur(16px) saturate(120%);
&.fade-out {
opacity: 0;
transition: opacity 0.5s;
transition-delay: 0.5s;
}
}
.gib-bg {
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(16px) saturate(120%);
width: fit-content;
block-size: fit-content;
}
.context-spawn {
position: absolute;
top: 0;
left: 0;
width: 100dvw;
height: 100dvh;
box-sizing: border-box;
overflow: hidden;
display: flex;
flex-direction: row;
align-content: center;
align-items: center;
// width: 100%;
// height: 100%;
color: #fff;
// .context-item {
// font-size: .95rem;
// padding: 1rem 1.6rem;
// background-color: rgba(0, 0, 0, 0.5);
// backdrop-filter: blur(16px) saturate(120%);
// white-space: nowrap;
// }
// .spawn-container {
// border: 1px solid white;
// }
}
.extension-status-messages {
z-index: 1000;
text-transform: uppercase;
display: flex;
flex-direction: column;
text-align: center;
width: 112.25%;
transform: translate(-12.5%, 12.5%) scale(0.75);
> * {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
}
}
.flex-reverse-order {
display: flex;
flex-direction: column-reverse;
}
.aard-blocked {
color: #fa6;
}
.trigger-zone-preview {
border: 4px solid #fa4;
}
.debug-1 {
border: 1px solid yellow;
&:hover {
background-color: rbba(255,255,0,0.5);
}
}
.debug-2 {
border: 1px solid blue;
&:hover {
background-color: rbba(0,0,255,.5);
}
}
</style>

View File

@ -1,177 +0,0 @@
<template>
<div class="uw-hover uv-hover-trigger-region">
TEST CONTENT
</div>
<div class="popup-panel">
<div class="tab-row flex flex-row">
<div class="tab">
todo: icon<br/>
Video options
</div>
</div>
<div>sudpo
<!-- Panel section -->
<template v-if="settingsInitialized">
<VideoSettings
:settings="settings"
></VideoSettings>
<ResizerDebugPanel :debugData="debugData">
</ResizerDebugPanel>
</template>
</div>
</div>
</template>
<script>
import VideoSettings from './PlayerUiPanels/VideoSettings.vue'
import { mapState } from 'vuex';
import Icon from '../common/components/Icon';
import ResizerDebugPanel from './PlayerUiPanels/ResizerDebugPanelComponent';
import BrowserDetect from '../ext/conf/BrowserDetect';
import ExecAction from './ui-libs/ExecAction';
import Logger from '../ext/lib/Logger';
import Settings from '../ext/lib/Settings';
export default {
components: {
Icon,
ResizerDebugPanel, VideoSettings
},
data() {
return {
// component properties
settings: {},
settingsInitialized: false,
execAction: new ExecAction(),
logger: null,
uiVisible: true,
debugData: {
resizer: {},
player: {},
},
debugDataPrettified: ''
};
},
computed: {
...mapState([
'showUi',
'resizerDebugData',
'playerDebugData'
]),
windowWidth: () => {
return window.innerWidth;
},
windowHeight: () => {
return window.innerHeight;
},
},
watch: {
showUi(visible) {
if (visible !== undefined) {
this.uiVisible = visible;
}
},
resizerDebugData(newData) {
this.debugData.resizer = newData;
this.debugDataPrettified = JSON.stringify(this.debugData, null, 2);
},
playerDebugData(newData) {
this.debugData.player = newData;
this.debugDataPrettified = JSON.stringify(this.debugData, null, 2);
}
},
async created() {
try {
this.logger = new Logger();
await this.logger.init({
allowLogging: true,
});
this.settings = new Settings({afterSettingsSaved: this.updateConfig, logger: this.logger});
await this.settings.init();
this.settingsInitialized = true;
this.execAction.setSettings(this.settings);
} catch (e) {
console.error('Failed to initiate ultrawidify player ui.', e);
}
},
methods: {
getUrl(url) {
return BrowserDetect.getURL(url);
},
}
}
</script>
<style lang="scss" src="../res/css/uwui-base.scss" scoped></style>
<style lang="scss" scoped>
@import '../res/css/uwui-base.scss';
@import '../res/css/colors.scss';
@import '../res/css/font/overpass.css';
@import '../res/css/font/overpass-mono.css';
@import '../res/css/common.scss';
.uw-ultrawidify-container-root {
// .relative-wrapper {
// position: relative;
// width: 100%;
// height: 100%;
// }
.uw-hover {
position: absolute;
top: 10%;
left: 10%;
width: 100px;
height: 100px;
color: #fff;
background-color: #000;
z-index: 999999999999999999;
}
.uw-hover:hover {
background-color: #f00;
}
.popup-panel {
position: absolute;
top: 10%;
left: 10%;
z-index: 999999999999999999;
width: 2500px;
height: 1200px;
max-width: 80%;
max-height: 80%;
pointer-events: all !important;
background-color: rgba(0,0,0,0.69);
color: #fff;
overflow-y: auto;
.tab {
display: block;
height: 42px;
font-size: 2.5rem;
background: rgb(87, 54, 26);
}
.tab:hover {
background-color: #f00;
}
}
pre {
white-space: pre-wrap;
}
}
</style>

535
src/csui/Popup.vue Normal file
View File

@ -0,0 +1,535 @@
<template>
<div class="popup-panel">
<!--
NOTE the code that makes ultrawidify popup work in firefox regardless of whether the
extension is being displayed in a normal or a small/overflow popup breaks the popup
behaviour on Chrome (where the popup would never reach the full width of 800px)
Since I'm tired and the hour is getting late, we'll just add an extra CSS class for
non-firefox builds of this extension and be done with it. No need to complicate things
further than that.
-->
<div v-if="settingsInitialized"
class="popup flex flex-col no-overflow"
:class="{'popup-chrome': ! BrowserDetect?.firefox}"
>
<div class="flex-row flex-nogrow flex-noshrink relative header"
>
<div class="grow shrink">
<h1>
<span class="smallcaps">Ultrawidify</span>: <small>Quick settings</small>
</h1>
</div>
<div v-if="BrowserDetect?.processEnvChannel !== 'stable'" class="absolute channel-info version-info">
Build channel: {{BrowserDetect?.processEnvChannel}} <br/>
<label>Version:</label> <br/>
{{ settings.getExtensionVersion() }}
</div>
<div v-else class="version-info">
<label>Version:</label> <br/>
{{ settings.getExtensionVersion() }}
</div>
</div>
<!-- CONTAINER ROOT -->
<div class="flex flex-row body no-overflow flex-grow">
<!-- TABS -->
<div class="flex flex-col tab-row" style="flex: 3 3; border-right: 1px solid #222;">
<div
v-for="tab of tabs"
:key="tab.id"
class="tab flex flex-row"
:class="{'active': tab.id === selectedTab}"
@click="selectTab(tab.id)"
>
<div class="icon-container">
<mdicon
:name="tab.icon"
:size="32"
/>
</div>
<div class="label">
{{tab.label}}
</div>
</div>
</div>
<!-- CONTENT -->
<div class="scrollable" style="flex: 7 7; padding: 1rem;">
<template v-if="settings && siteSettings">
<InPlayerUIAdvertisement
v-if="selectedTab === 'playerUiCtl'"
:eventBus="eventBus"
/>
<PopupVideoSettings
v-if="selectedTab === 'videoSettings'"
:settings="settings"
:eventBus="eventBus"
:siteSettings="siteSettings"
></PopupVideoSettings>
<!-- <PlayerDetectionPanel
v-if="selectedTab === 'playerDetection'"
:settings="settings"
:eventBus="eventBus"
:siteSettings="siteSettings"
:site="site.host"
>
</PlayerDetectionPanel> -->
<BaseExtensionSettings
v-if="selectedTab === 'extensionSettings'"
:settings="settings"
:eventBus="eventBus"
:siteSettings="siteSettings"
:site="site.host"
>
</BaseExtensionSettings>
</template>
<template v-else>No settings or site settings found.</template>
</div>
</div>
</div>
</div>
</template>
<script>
import BaseExtensionSettings from './src/PlayerUiPanels/BaseExtensionSettings.vue'
import PlayerDetectionPanel from './src/PlayerUiPanels/PlayerDetectionPanel.vue'
import PopupVideoSettings from './src/popup/panels/PopupVideoSettings.vue'
import InPlayerUIAdvertisement from './src/PlayerUiPanels/InPlayerUiAdvertisement.vue';
import Debug from '../ext/conf/Debug';
import BrowserDetect from '../ext/conf/BrowserDetect';
import Comms from '../ext/lib/comms/Comms';
import CommsClient, {CommsOrigin} from '../ext/lib/comms/CommsClient';
import Settings from '../ext/lib/Settings';
import Logger from '../ext/lib/Logger';
import EventBus from '../ext/lib/EventBus';
import {ChromeShittinessMitigations as CSM} from '../common/js/ChromeShittinessMitigations';
export default {
data () {
return {
comms: undefined,
eventBus: new EventBus(),
settings: {},
settingsInitialized: false,
narrowPopup: null,
sideMenuVisible: null,
logger: undefined,
site: undefined,
siteSettings: undefined,
selectedTab: 'playerUiCtl',
tabs: [
// see this for icons: https://pictogrammers.com/library/mdi/
{id: 'playerUiCtl', label: 'In-player UI', icon: 'artboard'},
{id: 'videoSettings', label: 'Video settings', icon: 'crop'},
// {id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
{id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' },
],
}
},
async created() {
this.logger = new Logger();
await this.logger.init({
allowLogging: true,
});
this.settings = new Settings({afterSettingsSaved: () => this.updateConfig(), logger: this.logger});
await this.settings.init();
this.settingsInitialized = true;
// const port = chrome.runtime.connect({name: 'popup-port'});
// port.onMessage.addListener( (m,p) => this.processReceivedMessage(m,p));
// CSM.setProperty('port', port);
this.eventBus = new EventBus();
this.eventBus.subscribe(
'set-current-site',
{
function: (config, context) => {
if (this.site) {
if (!this.site.host) {
// dunno why this fix is needed, but sometimes it is
this.site.host = config.site.host;
}
}
this.site = config.site;
// this.selectedSite = this.selectedSite || config.site.host;
this.siteSettings = this.settings.getSiteSettings(this.site.host);
this.eventBus.setupPopupTunnelWorkaround({
origin: CommsOrigin.Popup,
comms: {
forwardTo: 'active'
}
});
this.loadFrames(this.site);
}
}
);
this.comms = new CommsClient('popup-port', this.logger, this.eventBus);
this.eventBus.setComms(this.comms);
this.eventBus.setupPopupTunnelWorkaround({
origin: CommsOrigin.Popup,
comms: {forwardTo: 'active'}
});
// ensure we'll clean player markings on popup close
window.addEventListener("unload", () => {
CSM.port.postMessage({
cmd: 'unmark-player',
forwardToAll: true,
});
// if (BrowserDetect.anyChromium) {
// chrome.extension.getBackgroundPage().sendUnmarkPlayer({
// cmd: 'unmark-player',
// forwardToAll: true,
// });
// }
});
// get info about current site from background script
while (true) {
this.requestSite();
await this.sleep(5000);
}
},
async updated() {
const body = document.getElementsByTagName('body')[0];
// ensure that narrowPopup only gets set the first time the popup renders
// if popup was rendered before, we don't do anything because otherwise
// we'll be causing an unwanted re-render
//
// another thing worth noting the popup gets first initialized with
// offsetWidth set to 0. This means proper popup will be displayed as a
// mini popup if we don't check for that.
if (this.narrowPopup === null && body.offsetWidth > 0) {
this.narrowPopup = body.offsetWidth < 600;
}
},
components: {
Debug,
BrowserDetect,
PopupVideoSettings, PlayerDetectionPanel, BaseExtensionSettings, InPlayerUIAdvertisement
},
methods: {
async sleep(t) {
return new Promise( (resolve,reject) => {
setTimeout(() => resolve(), t);
});
},
toObject(obj) {
return JSON.parse(JSON.stringify(obj));
},
requestSite() {
try {
this.logger.log('info','popup', '[popup::getSite] Requesting current site ...')
// CSM.port.postMessage({command: 'get-current-site'});
this.eventBus.send(
'get-current-site',
{
comms: {forwardTo: 'active'}
}
);
} catch (e) {
this.logger.log('error','popup','[popup::getSite] sending get-current-site failed for some reason. Reason:', e);
}
},
getRandomColor() {
return `rgb(${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)})`;
},
selectTab(tab) {
this.selectedTab = tab;
},
processReceivedMessage(message, port) {
this.logger.log('info', 'popup', '[popup::processReceivedMessage] received message:', message)
if (message.command === 'set-current-site'){
if (this.site) {
if (!this.site.host) {
// dunno why this fix is needed, but sometimes it is
this.site.host = site.tabHostname;
}
}
this.site = message.site;
// update activeSites
// this.activeSites = this.activeSites.filter(x => x.host !== message.site);
// add current site
// this.activeSites = unshift({
// host: message.site.host,
// isIFrame: false, // currently unused
// });
this.selectedSite = this.selectedSite || message.site.host;
this.loadFrames(this.site);
}
return true;
},
isDefaultFrame(frameId) {
return frameId === '__playing' || frameId === '__all';
},
loadFrames() {
this.activeSites = [{
host: this.site.host,
isIFrame: false, // not used tho. Maybe one day
}];
this.selectedSite = this.selectedSite || this.site.host;
// for (const frame in videoTab.frames) {
// this.activeFrames.push({
// id: `${this.site.id}-${frame}`,
// label: videoTab.frames[frame].host,
// ...this.frameStore[frame],
// })
// // only add each host once at most
// if (!this.activeSites.find(x => x.host === videoTab.frames[frame].host)) {
// this.activeSites.push({
// host: videoTab.frames[frame].host,
// isIFrame: undefined // maybe one day
// });
// }
// }
// update whether video tab can be shown
// this.updateCanShowVideoTab();
},
getRandomColor() {
return `rgb(${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)}, ${Math.floor(Math.random() * 128)})`;
}
}
}
</script>
<style lang="scss">
// @import 'res/css/uwui-base.scss';
@import 'res/css/colors.scss';
@import 'res/css/font/overpass.css';
@import 'res/css/font/overpass-mono.css';
@import 'res/css/common.scss';
@import './src/res-common/_variables';
.header {
background-color: rgb(90, 28, 13);
color: #fff;
padding: 8px;
display: flex;
flex-direction: row;
justify-content: space-between;
h1 {
font-size: 2rem;
}
.version-info {
text-align: right;
font-size: 0.8rem;
opacity: 0.8;
label {
opacity: 0.5;
}
}
}
.site-support-info {
display: flex;
flex-direction: row;
align-items: center;
.site-support-site {
font-size: 1.5em;
}
.site-support {
display: inline-flex;
flex-direction: row;
align-items: center;
margin-left: 1rem;
border-radius: 8px;
padding: 0rem 1.5rem 0rem 1rem;
position: relative;
.tooltip {
padding: 1rem;
display: none;
position: absolute;
bottom: 0;
transform: translateY(110%);
width: 42em;
background-color: rgba(0,0,0,0.90);
color: #ccc;
}
&:hover {
.tooltip {
display: block;
}
}
.mdi {
margin-right: 1rem;
}
&.official {
background-color: #fa6;
color: #000;
.mdi {
fill: #000 !important;
}
}
&.community {
background-color: rgb(85, 85, 179);
color: #fff;
.mdi {
fill: #fff !important;
}
}
&.no-support {
background-color: rgb(138, 65, 126);
color: #eee;
.mdi {
fill: #eee !important;
}
}
&.user-added {
border: 1px solid #ff0;
color: #ff0;
.mdi {
fill: #ff0 !important;
}
}
}
}
.content {
flex-grow: 1;
.warning-area {
flex-grow: 0;
flex-shrink: 0;
}
.panel-content {
flex-grow: 1;
flex-shrink: 1;
overflow-y: auto;
padding: 1rem;
}
}
.warning-box {
background: rgb(255, 174, 107);
color: #000;
margin: 1rem;
padding: 1rem;
display: flex;
flex-direction: row;
align-items: center;
.icon-container {
margin-right: 1rem;
flex-shrink: 0;
flex-grow: 0;
}
a {
color: rgba(0,0,0,0.7);
cursor: pointer;
}
}
.popup-panel {
background-color: rgba(0,0,0,0.50);
color: #fff;
overflow-y: auto;
.popup-window-header {
padding: 1rem;
background-color: rgba(5,5,5, 0.75);
}
.tab-row {
background-color: rgba(11,11,11, 0.75);
.tab {
display: flex;
flex-direction: row;
align-items: center;
padding: 1rem;
font-size: 1.25rem;
// height: rem;
min-height: 3rem;
border-bottom: 1px solid rgba(128, 128, 128, 0.5);
border-top: 1px solid rgba(128, 128, 128, 0.5);
opacity: 0.5;
&:hover {
opacity: 1;
}
&.active {
opacity: 1.0;
background-color: $primaryBg;
color: rgb(255, 174, 107);
border-bottom: 1px solid rgba(116, 78, 47, 0.5);
border-top: 1px solid rgba(116, 78, 47, 0.5);
}
.icon-container {
width: 64px;
flex-grow: 0;
flex-shrink: 0;
}
.label {
flex-grow: 1;
flex-shrink: 1;
padding: 0 !important;
}
}
}
.popup-title, .popup-title h1 {
font-size: 48px !important;
}
}
pre {
white-space: pre-wrap;
}
.button {
border: 1px solid #222 !important;
}
h1 {
margin: 0; padding: 0; font-weight: 400; font-size:24px;
}
</style>

16
src/csui/csui-global.html Normal file
View File

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

11
src/csui/csui-global.js Normal file
View File

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

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en" style="position: relative">
<head>
<meta charset="UTF-8">
<meta name="color-scheme" content="dark">
<title>Ultrawidify - Content Script User Interface (in-player overlay)</title>
<!-- <link rel="stylesheet" href="csui.css"> -->
</head>
<body class="uw-ultrawidify-container-root" style="background-color: transparent;">
<div id="app"></div>
<script src="csui.js"></script>
</body>
</html>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en" style="position: relative">
<head>
<meta charset="UTF-8">
<meta name="color-scheme" content="light">
<title>Ultrawidify - Content Script User Interface (in-player overlay)</title>
<!-- <link rel="stylesheet" href="csui.css"> -->
</head>
<body class="uw-ultrawidify-container-root" style="background-color: transparent;">
<div id="app"></div>
<script src="csui.js"></script>
</body>
</html>

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en" style="position: relative">
<head>
<meta charset="UTF-8">
<title>Ultrawidify - Content Script User Interface (in-player overlay)</title>
<!-- <link rel="stylesheet" href="csui.css"> -->
</head>
<body class="uw-ultrawidify-container-root" style="background-color: transparent;">
<div id="app"></div>
<script src="csui.js"></script>
</body>
</html>

View File

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

8
src/csui/csui-popup.js Normal file
View File

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

11
src/csui/csui.js Normal file
View File

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

View File

@ -0,0 +1,396 @@
@import "colors.scss";
@import "fonts.scss";
@import "flex.scss";
// @import "~/@mdi/font/css/materialdesignicons.css";
// @import "form.scss";
body {
background-color: $background-primary;
color: $text-normal;
font-family: 'Overpass', sans-serif;
font-size: 1.2em;
width: 100%;
min-height: 100%;
height: 100vh;
border: 0px;
margin: 0px;
padding: 0px;
}
/* STANDARD WIDTHS AND HEIGHTS */
.w100 {
width: 100%;
}
.h100, .h-full {
height: 100%;
}
.w24 {
width: 100px;
}
.m-t-0-33em {
margin-top: 0.33em;
}
.x-pad-1em {
padding-left: 1em;
padding-right: 1em;
}
/* overflow stuff */
.overflow-y-auto {
overflow: auto;
scrollbar-width: thin;
scrollbar-color: rgba($primary-color, 0.7) $background-primary;
}
.scrollbar-darker {
scrollbar-color: rgba($primary-color, 0.5) $background-primary;
}
/* scrollbars for chrome/webkit */
.overflow-y-auto::-webkit-scrollbar {
width: 3px;
}
.overflow-y-auto::-webkit-scrollbar-track {
background: $background-primary;
}
.overflow-y-auto::-webkit-scrollbar-thumb {
background: rgba($primary-color, 0.7);
}
.overflow-y-auto::-webkit-scrollbar-button {
display: none;
}
.scrollbar-darker::-webkit-scrollbar-thumb {
background: rgba($primary-color, 0.5);
}
.no-overflow {
overflow: hidden;
}
.no-overflow-x {
overflow-x: hidden;
}
.no-overflow-y {
overflow-y: hidden;
}
/* .SELECTED CLASSES */
.selected-tab {
background-color: initial;
border-left: $primary-color 5px solid;
}
.selected-tab-secondary {
background-color: initial;
border-left: $secondary-color 3px solid !important;
}
/* BASIC STYLING */
.description {
color: $text-dim;
font-size: 1rem;
}
.label {
padding-top: 1.5rem;
font-size: 1.5rem;
font-variant: small-caps;
font-weight: 600;
}
.label-secondary {
font-weight: 600;
}
.button-box {
padding-top: 0.69rem;
padding-bottom: 0.69rem;
}
.indent {
padding-left: 4.2rem;
}
.row-padding {
padding-top: 0.69rem;
padding-bottom: 0.69rem;
}
a, a:visited {
color: $primary-color;
}
a:hover {
color: lighten($primary-color, 10%);
}
/* INPUT FORMATTING */
input[type="number"], input[type="text"], input {
outline: none;
background-color: $input-background;
color: $text-normal;
padding: 0.1rem;
padding-top: 0.2rem;
padding-bottom: 0.1rem;
margin-left: 1rem;
border: 1px solid $input-border;
}
input:disabled {
background: #444444;
color: darken($text-normal, 50%);
}
/* ELEMENT POSITIONING */
.row {
display: block;
margin-top: 20px;
margin-bottom: 10px;
}
.button-row {
display: block;
margin-top: 5px;
margin-bottom: 10px;
}
.float-left {
float: left;
}
.float-right {
float: right;
}
.inline-block {
display: inline-block;
}
.block {
display: block;
}
.hidden {
display: none !important;
}
.hide {
display: none !important;
}
/* TEXT FORMATTING (no colors) */
small {
font-size: 0.75em;
font-weight: 200;
}
.small {
font-size: 0.75em;
font-weight: 200;
}
.medium-small {
font-size: 0.85em;
font-weight: 200;
}
.smallcaps{
font-variant: small-caps;
}
.center{
text-align: center;
width: 100%;
}
.text-center {
text-align: center;
}
.invalid-input {
border: 1px solid #720 !important;
background-color: #410 !important;
}
.button {
/*display: inline-block;*/
// padding-top: 8px;
// padding-bottom: 3px;
//padding-left: 5px;
//padding-right: 5px;
border: 1px solid rgb(39, 39, 39);
margin-top: 3px;
margin-bottom: 3px;
color: $text-dim;
text-align: center;
cursor: pointer;
user-select: none;;
}
.selected, .setting-selected {
color: $selected-color !important;
background-color: $background-selected !important;
}
.selected-tab {
color: $selected-color;
}
.setting-selected {
border: 1px solid shade($selected-color, 25%);
}
.button:hover {
color: lighten($selected-color, 10%);
background-color: lighten($background-selected, 10%);
}
.disabled {
pointer-events: none;
/* color: #666; */
filter: contrast(50%) brightness(40%) grayscale(100%);
}
.disabled-button {
color: #666 !important;
cursor: not-allowed !important;
}
.disabled-button:hover {
color: #777 !important;
background-color: #222 !important;
}
/* BROWSER-SPECIFIC DISABLE */
.disabled-edge {
pointer-events: none !important;
filter: contrast(50%) brightness(40%) grayscale(100%) !important;
content: "NOT SUPPORTED IN THIS BROWSER";
}
.disabled-edge::after {
background-color: #333272;
color: #d8d9e6;
display: inline-block;
font-size: .75em;
font-variant: small-caps;
padding-left: 5px;
padding-right: 5px;
margin-left: 10px;
}
/** misc **/
.warning-color {
color: #d6ba4a;
}
.warning, .warning-lite {
color: #d6ba4a;
padding-left: 35px;
float: right;
}
.warning::before, .warning-lite::before {
content: "";
display: inline-block;
}
.warning::before {
font-weight: bold;
font-size: 2.5em;
margin-left: -35px;
padding-right: 10px;
}
.info {
color: $info-color;
padding-left: 35px;
float: right;
}
.info::before {
content: "";
display: inline-block;
font-weight: bold;
margin-left: -35px;
padding-right: 10px;
}
.new {
color: #fff;
}
.new::after {
content: "";
color: $info-color !important;
display: inline-block;
font-weight: bold;
padding-right: 10px;
}
.experimental::after {
// content: "Experimental";
content: "";
color: #ffde12;
// background-color: #1f1f1f;
display: inline-block;
font-size: .75em;
font-variant: small-caps;
padding-left: 5px;
padding-right: 5px;
margin-left: 10px;
margin-top: -4px;
}
.experimental:hover::after {
content: "Ⓔ Experimental";
// content: "";
color: #ffde12;
// background-color: #1f1f1f;
display: inline-block;
font-size: .75em;
font-variant: small-caps;
padding-left: 5px;
padding-right: 5px;
margin-left: 10px;
margin-top: -4px;
}
.sticky-bottom {
width: 100%;
position: sticky;
// position: fixed;
bottom: 0px;
background-color: rgba($background-primary, 0.7);
}
.rtl {
direction: rtl;
}
.ltr {
direction: ltr;
}
.monospace {
font-family: 'Overpass Mono';
}
.pointer {
cursor: pointer;
user-select: none;
}

View File

@ -0,0 +1,51 @@
.flex {
display: flex;
}
.flex-row {
flex-direction: row;
}
.flex-col {
flex-direction: column;
}
.flex-auto {
flex: 1 1 auto;
}
.flex-grow {
flex-grow: 1;
}
.flex-nogrow {
flex-grow: 0;
}
.flex-shrink {
flex-shrink: 1;
}
.flex-noshrink {
flex-shrink: 0;
}
.flex-wrap {
flex-wrap: wrap;
}
.flex-center {
align-content: center;
align-items: center;
}
.flex-end {
justify-content: flex-end;
}
.flex-cross-center {
justify-content: center;
justify-items: center;
}
.flex-self-center {
align-self: center;
}

View File

@ -1,111 +1,111 @@
@font-face {
font-family: 'Overpass';
src: url('/res/fonts/overpass-webfont/overpass-thin.woff2') format('woff2'); /* Super Modern Browsers */
src: url('/csui/res/fonts/overpass-webfont/overpass-thin.woff2') format('woff2'); /* Super Modern Browsers */
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: 'Overpass';
src: url('/res/fonts/overpass-webfont/overpass-thin-italic.woff2') format('woff2');
src: url('/csui/res/fonts/overpass-webfont/overpass-thin-italic.woff2') format('woff2');
font-weight: 200;
font-style: italic;
}
@font-face {
font-family: 'Overpass';
src: url('/res/fonts/overpass-webfont/overpass-extralight.woff2') format('woff2');
src: url('/csui/res/fonts/overpass-webfont/overpass-extralight.woff2') format('woff2');
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: 'Overpass';
src: url('/res/fonts/overpass-webfont/overpass-extralight-italic.woff2') format('woff2');
src: url('/csui/res/fonts/overpass-webfont/overpass-extralight-italic.woff2') format('woff2');
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: 'Overpass';
src: url('/res/fonts/overpass-webfont/overpass-light.woff2') format('woff2');
src: url('/csui/res/fonts/overpass-webfont/overpass-light.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Overpass';
src: url('/res/fonts/overpass-webfont/overpass-light-italic.woff2') format('woff2');
src: url('/csui/res/fonts/overpass-webfont/overpass-light-italic.woff2') format('woff2');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: 'Overpass';
src: url('/res/fonts/overpass-webfont/overpass-regular.woff2') format('woff2');
src: url('/csui/res/fonts/overpass-webfont/overpass-regular.woff2') format('woff2');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Overpass';
src: url('/res/fonts/overpass-webfont/overpass-italic.woff2') format('woff2');
src: url('/csui/res/fonts/overpass-webfont/overpass-italic.woff2') format('woff2');
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: 'Overpass';
src: url('/res/fonts/overpass-webfont/overpass-semibold.woff2') format('woff2');
src: url('/csui/res/fonts/overpass-webfont/overpass-semibold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: 'Overpass';
src: url('/res/fonts/overpass-webfont/overpass-semibold-italic.woff2') format('woff2');
src: url('/csui/res/fonts/overpass-webfont/overpass-semibold-italic.woff2') format('woff2');
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: 'Overpass';
src: url('/res/fonts/overpass-webfont/overpass-bold.woff2') format('woff2');
src: url('/csui/res/fonts/overpass-webfont/overpass-bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: 'Overpass';
src: url('/res/fonts/overpass-webfont/overpass-bold-italic.woff2') format('woff2');
src: url('/csui/res/fonts/overpass-webfont/overpass-bold-italic.woff2') format('woff2');
font-weight: 700;
font-style: italic;
}
@font-face {
font-family: 'Overpass mono';
src: url('/res/fonts/overpass-mono-webfont/overpass-mono-light.woff2') format('woff2');
src: url('/csui/res/fonts/overpass-mono-webfont/overpass-mono-light.woff2') format('woff2');
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: 'Overpass mono';
src: url('/res/fonts/overpass-mono-webfont/overpass-mono-regular.woff2') format('woff2');
src: url('/csui/res/fonts/overpass-mono-webfont/overpass-mono-regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: 'Overpass mono';
src: url('/res/fonts/overpass-mono-webfont/overpass-mono-semibold.woff2') format('woff2');
src: url('/csui/res/fonts/overpass-mono-webfont/overpass-mono-semibold.woff2') format('woff2');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: 'Overpass mono';
src: url('/res/fonts/overpass-mono-webfont/overpass-mono-bold.woff2') format('woff2');
src: url('/csui/res/fonts/overpass-mono-webfont/overpass-mono-bold.woff2') format('woff2');
font-weight: 600;
font-style: normal;
}

26653
src/csui/res/css/mdi.scss Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
@import 'flex.scss';
@import 'colors.scss';
@import 'common.scss';
* {
font-family: 'Overpass';
}
.uw-ultrawidify-container-root {
// here's the defaults:
// all: initial;
// * {
// all: unset;
// }
// here's things that we don't want as defaults
// (must come after the all: declaration, otherwise
// all: declaration overrides everything.)
// we put our UI _over_ site's player:
z-index: 999999;
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-color: none transparent !important;
// Ensure we're display:block
display: block;
// we are click-through by default:
pointer-events: none;
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,415 @@
<template>
<div
class="popup-panel flex flex-col uw-clickable h-full"
>
<div class="popup-window-header">
<div class="header-title">
<div class="popup-title">Ultrawidify <small>{{settings?.active?.version}} - {{BrowserDetect.processEnvChannel}}</small></div>
<div class="site-support-info flex flex-row">
<div class="site-support-site">{{site}}</div>
<SupportLevelIndicator
v-if="inPlayer"
:siteSupportLevel="siteSupportLevel"
>
</SupportLevelIndicator>
</div>
</div>
<div class="header-buttons">
<div
class="header-button close-button"
@click="$emit('close')"
>
<mdicon name="close" :size="36"></mdicon>
</div>
<!-- <a >{{preventClose ? 'allow auto-close' : 'prevent auto-close'}}</a> -->
</div>
</div>
<div class="tab-main flex flex-row">
<div class="tab-row flex flex-col grow-0 shrink-0">
<div
v-for="tab of tabs"
:key="tab.id"
class="tab"
:class="{
'active': tab.id === selectedTab,
'highlight-tab': tab.highlight,
}"
@click="selectTab(tab.id)"
>
<div class="icon-container">
<mdicon
v-if="tab.icon"
:name="tab.icon"
:size="32"
/>
</div>
<div class="label">
{{tab.label}}
</div>
</div>
</div>
<div class="content flex flex-col">
<!-- autodetection warning -->
<div class="warning-area">
<div
v-if="statusFlags.hasDrm"
class="warning-box"
>
<div class="icon-container">
<mdicon name="alert" :size="32" />
</div>
<div>
This site is blocking automatic aspect ratio detection. You will have to adjust aspect ratio manually.<br/>
<a>Learn more ...</a>
</div>
</div>
</div>
<div class="flex flex-row panel-content">
<!-- Panel section -->
<!-- <VideoSettings
v-if="selectedTab === 'videoSettings'"
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
:site="site"
></VideoSettings> -->
<PlayerDetectionPanel
v-if="selectedTab === 'playerDetection'"
:siteSettings="siteSettings"
:eventBus="eventBus"
:site="site"
>
</PlayerDetectionPanel>
<PlayerUiSettings
v-if="selectedTab === 'playerUiSettings'"
:settings="settings"
:eventBus="eventBus"
>
</PlayerUiSettings>
<BaseExtensionSettings
v-if="selectedTab === 'extensionSettings'"
:settings="settings"
:siteSettings="siteSettings"
:site="site"
></BaseExtensionSettings>
<AutodetectionSettingsPanel
v-if="selectedTab === 'autodetectionSettings'"
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
:site="site"
>
</AutodetectionSettingsPanel>
<DebugPanel
v-if="selectedTab === 'debugging'"
:settings="settings"
:eventBus="eventBus"
:site="site"
></DebugPanel>
<ChangelogPanel
v-if="selectedTab === 'changelog'"
:settings="settings"
></ChangelogPanel>
<AboutPanel
v-if="selectedTab === 'about'"
>
</AboutPanel>
<!-- <ResetBackupPanel
v-if="selectedTab === 'resetBackup'"
:settings="settings"
>
</ResetBackupPanel> -->
</div>
</div>
</div>
</div>
</template>
<script>
import DebugPanel from './PlayerUiPanels/DebugPanel.vue'
import AutodetectionSettingsPanel from './PlayerUiPanels/AutodetectionSettingsPanel.vue'
import BaseExtensionSettings from './PlayerUiPanels/BaseExtensionSettings.vue'
import PlayerDetectionPanel from './PlayerUiPanels/PlayerDetectionPanel.vue'
import VideoSettings from './PlayerUiPanels/VideoSettings.vue'
import BrowserDetect from '../../ext/conf/BrowserDetect'
import ChangelogPanel from './PlayerUiPanels/ChangelogPanel.vue'
import AboutPanel from './PlayerUiPanels/AboutPanel.vue'
import PlayerUiSettings from './PlayerUiPanels/PlayerUiSettings.vue'
import ResetBackupPanel from './PlayerUiPanels/ResetBackupPanel.vue'
import SupportLevelIndicator from './components/SupportLevelIndicator.vue'
export default {
components: {
VideoSettings,
PlayerDetectionPanel,
BaseExtensionSettings,
AutodetectionSettingsPanel,
DebugPanel,
PlayerUiSettings,
ChangelogPanel,
AboutPanel,
SupportLevelIndicator,
ResetBackupPanel,
},
mixins: [],
data() {
return {
statusFlags: {
hasDrm: undefined,
},
tabs: [
// {id: 'videoSettings', label: 'Video settings', icon: 'crop'},
{id: 'extensionSettings', label: 'Site and Extension options', icon: 'cogs' },
{id: 'playerUiSettings', label: 'UI and keyboard', icon: 'movie-cog-outline' },
{id: 'playerDetection', label: 'Player detection', icon: 'television-play'},
{id: 'autodetectionSettings', label: 'Autodetection options', icon: 'auto-fix'},
// {id: 'advancedOptions', label: 'Advanced options', icon: 'cogs' },
// {id: 'debugging', label: 'Debugging', icon: 'bug-outline' }
{id: 'changelog', label: 'What\'s new', icon: 'alert-decagram' },
{id: 'about', label: 'About', icon: 'information-outline'},
// {id: 'resetBackup', label: 'Reset and backup', icon: 'file-restore-outline'},
],
selectedTab: 'extensionSettings',
BrowserDetect: BrowserDetect,
preventClose: false,
siteSettings: null,
}
},
props: [
'settings',
'eventBus',
'logger',
'in-player',
'site',
'defaultTab'
],
computed: {
// LPT: NO ARROW FUNCTIONS IN COMPUTED,
// IS SUPER HARAM
// THINGS WILL NOT WORK IF YOU USE ARROWS
siteSupportLevel() {
return (this.site && this.siteSettings) ? this.siteSettings.data.type || 'no-support' : 'waiting';
}
},
created() {
if (this.defaultTab) {
this.selectedTab = this.defaultTab;
}
this.siteSettings = this.settings.getSiteSettings(this.site);
this.tabs.find(x => x.id === 'changelog').highlight = !this.settings.active.whatsNewChecked;
this.eventBus.subscribe(
'uw-show-ui',
() => {
if (this.inPlayer) {
return; // show-ui is only intended for global overlay
}
}
)
},
methods: {
/**
* Gets URL of the browser settings page (i think?)
*/
getUrl(url) {
return BrowserDetect.getURL(url);
},
selectTab(tab) {
this.selectedTab = tab;
},
setPreventClose(bool) {
this.preventClose = bool;
this.$emit('preventClose', bool);
}
}
}
</script>
<style lang="scss" scoped>
@import '../res/css/uwui-base.scss';
@import '../res/css/colors.scss';
@import '../res/css/font/overpass.css';
@import '../res/css/font/overpass-mono.css';
@import '../res/css/common.scss';
@import '../src/res-common/_variables';
// .relative-wrapper {
// position: relative;
// width: 100%;
// height: 100%;
// }
.tab-row {
width: 22rem;
flex-grow: 0;
flex-shrink: 0;
}
.tab-main {
flex-grow: 1;
flex-shrink: 1;
overflow: hidden;
}
.content {
flex-grow: 1;
.warning-area {
flex-grow: 0;
flex-shrink: 0;
}
.panel-content {
flex-grow: 1;
flex-shrink: 1;
overflow-y: auto;
padding: 1rem;
}
}
.warning-box {
background: rgb(255, 174, 107);
color: #000;
margin: 1rem;
padding: 1rem;
display: flex;
flex-direction: row;
align-items: center;
.icon-container {
margin-right: 1rem;
flex-shrink: 0;
flex-grow: 0;
}
a {
color: rgba(0,0,0,0.7);
cursor: pointer;
}
}
.site-support-info {
display: flex;
flex-direction: row;
align-items: bottom;
.site-support-site {
font-size: 1.5em;
}
}
.popup-panel {
background-color: rgba(0,0,0,0.50);
color: #fff;
overflow-y: auto;
.popup-window-header {
padding: 1rem;
background-color: rgba(5,5,5, 0.75);
display: flex;
flex-direction: row;
.header-title {
flex: 1 1;
}
.header-buttons {
flex: 0 0;
display: flex;
flex-direction: row;
.header-button {
cursor: pointer;
border-radius: 50%;
width: 48px;
height: 48px;
display: flex;
justify-content: center;
align-items: center;
&.button-active {
background-color: #fa6;
color: #000;
&:hover {
color: #ccc;
}
}
&.close-button {
color: #f00;
}
&:hover {
color: #fa6;
}
}
}
}
.tab-row {
background-color: rgba(11,11,11, 0.75);
.tab {
display: flex;
flex-direction: row;
align-items: center;
padding: 2rem;
font-size: 1.5rem;
height: 4rem;
border-bottom: 1px solid rgba(128, 128, 128, 0.5);
border-top: 1px solid rgba(128, 128, 128, 0.5);
opacity: 0.5;
&:hover {
opacity: 1;
}
&.active {
opacity: 1.0;
background-color: $primaryBg;
color: rgb(255, 174, 107);
border-bottom: 1px solid rgba(116, 78, 47, 0.5);
border-top: 1px solid rgba(116, 78, 47, 0.5);
}
.icon-container {
width: 64px;
flex-grow: 0;
flex-shrink: 0;
}
.label {
flex-grow: 1;
flex-shrink: 1;
padding: 0 !important;
}
&.highlight-tab {
opacity: 0.9;
color: #eee;
// .label {
// color: rgb(239, 192, 152);
// }
}
}
}
.popup-title, .popup-title h1 {
font-size: 48px !important;
}
}
pre {
white-space: pre-wrap;
}
</style>

View File

@ -0,0 +1,122 @@
<template>
<div class="flex flex-col w-full h-full gap-2">
<h1>About Ultrawidify</h1>
<p>
Ultrawidify version: <b>{{addonVersion}}</b><br>
Install source: <b>{{addonSource}}</b>
</p>
<div class="flex flex-row gap-2 bg-black">
<div class="w-[1/2]" style="width: 50%">
<h2>Report a problem</h2>
<p>
You may report <strike>undocumented features</strike> bugs using one of the following options (in order of preference):
</p>
<ul>
<li> <a target="_blank" href="https://github.com/tamius-han/ultrawidify/issues"><b>Github (preferred)</b></a><br/></li>
<li>Email: <a target="_blank" :href="mailtoLink">tamius.han@gmail.com</a></li>
</ul>
<p>
When reporting bugs, please include the following information:
</p>
<ul>
<li>Extension version</li>
<li>Which browser you're using¹</li>
<li>Which extension store you installed extension from (install source)²</li>
<li>Which site you're having the issue on (preferably link. For issues with autodetection, please also include a timestamp)</li>
<li>What do I need to do in order to make the issue happen</li>
<li>Please include a screenshot of the problem. It usually helps.</li>
</ul>
<p>
<small>
[1] If using anything other than Firefox, Chrome, or Edge, please check if issue also happens in Chrome or Edge. Bugs affecting Opera will only get fixed if they also affect Chrome, because <a target="_blank" href="https://stuff.tamius.net/sacred-texts/2024/06/08/why-im-boycotting-opera/">I'm salty</a>.<br/>
[2] It is recommended that users of Microsoft Edge install Ultrawidify from the Microsoft Edge Addons source
</small>
</p>
</div>
<div class="w-[1/2]" style="width: 50%">
<h2>Thank you monies</h2>
<p>
If you think I deserve money for the work I did up to this point, you can bankroll my caffeine addiction.
</p>
<p class="text-center">
<a class="donate" href="https://www.paypal.com/paypalme/tamius" target="_blank">Donate on Paypal</a>
</p>
<p>
I also have <a href="https://instagram.com/shaman_urkog" target="_blank">instagram with nerdy shit</a> <small>(mini painting + various fantasy events)</small>.
</p>
</div>
</div>
</div>
</template>
<script>
import BrowserDetect from '../../../ext/conf/BrowserDetect';
export default {
components: {
BrowserDetect,
},
data() {
return {
// 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,
}
},
async created() {
const messageTemplate = encodeURIComponent(
`Describe your issue in more detail. In case of misaligned videos, please provide screenshots. When reporting\
issues with autodetection not detecting aspect ratio correctly, please provide a link with timestamp to the\
problematic video at the time where the problem happens. You may delete this paragraph, as it's only a template.
Extension info (AUTOGENERATED DO NOT CHANGE OR REMOVE):
* Build: ${BrowserDetect.processEnvBrowser}-${this.addonVersion}-stable
Browser-related stuff (please ensure this section is correct):
* User Agent string: ${window.navigator.userAgent}
* vendor: ${window.navigator.vendor}
* Operating system: ${window.navigator.platform}
`
);
this.mailtoLink = `mailto:tamius.han@gmail.com?subject=%5BUltrawidify%5D%20ENTER%20SUMMARY%20OF%20YOUR%20ISSUE%20HERE&body=${messageTemplate}`;
},
methods: {
}
}
</script>
<style lang="scss" scoped>
.flex {
display: flex;
}
.flex-row {
flex-direction: row;
}
p, li {
font-size: 1rem;
}
small {
opacity: 0.5;
font-size: 0.8em;
}
a {
color: #fa6;
}
.text-center {
text-align: center;
}
.donate {
margin: 1rem;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
background-color: #fa6;
color: #000;
}
</style>

View File

@ -0,0 +1,171 @@
<template>
<div class="alignment-box">
<div
class="col top left"
@click="align(VideoAlignment.Left, VideoAlignment.Top)"
>
<div class="alignment-ui"></div>
</div>
<div
class="col top center"
@click="align(VideoAlignment.Center, VideoAlignment.Top)"
>
<div class="alignment-ui"></div>
</div>
<div
class="col top right"
@click="align(VideoAlignment.Right, VideoAlignment.Top)"
>
<div class="alignment-ui"></div>
</div>
<div
class="col ycenter left"
@click="align(VideoAlignment.Left, VideoAlignment.Center)"
>
<div class="alignment-ui"></div>
</div>
<div
class="col ycenter center"
@click="align(VideoAlignment.Center, VideoAlignment.Center)"
>
<div class="alignment-ui"></div>
</div>
<div
class="col ycenter right"
@click="align(VideoAlignment.Right, VideoAlignment.Center)"
>
<div class="alignment-ui"></div>
</div>
<div
class="col bottom left"
@click="align(VideoAlignment.Left, VideoAlignment.Bottom)"
>
<div class="alignment-ui"></div>
</div>
<div
class="col bottom center"
@click="align(VideoAlignment.Center, VideoAlignment.Bottom)"
>
<div class="alignment-ui"></div>
</div>
<div
class="col bottom right"
@click="align(VideoAlignment.Right, VideoAlignment.Bottom)"
>
<div class="alignment-ui"></div>
</div>
</div>
</template>
<script>
import VideoAlignmentType from '../../../common/enums/VideoAlignmentType.enum';
export default {
props: [
'eventBus'
],
data() {
return {
VideoAlignment: VideoAlignmentType
}
},
methods: {
align(alignmentX, alignmentY) {
this.eventBus?.sendToTunnel('set-alignment', {x: alignmentX, y: alignmentY})
}
}
}
</script>
<style lang="scss" scoped module>
.alignment-box {
aspect-ratio: 1;
// width: 100%;
// min-width: 40px;
// max-width: 80px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
.col {
display: flex;
align-items: center;
justify-content: center;
background-color: rgba(0,0,0,0.25);
cursor: pointer;
padding: 0.5rem;
width: 1.5rem;
height: 1.5rem;
border-bottom: 1px solid transparent;
.alignment-ui {
width: 50%;
height: 50%;
border-style: solid;
border-width: 0px;
border-color: #fff;
}
&:hover {
background-color: rgba(0,0,0,0.5);
border-bottom: 1px solid #fa6;
.alignment-ui {
border-color: #fa6 !important;
}
&.center.ycenter {
.alignment-ui {
background-color: #fa6;
}
}
}
&.top {
align-items: flex-start;
.alignment-ui {
border-top-width: 3px;
}
}
&.bottom {
align-items: flex-end;
.alignment-ui {
border-bottom-width: 3px;
}
}
&.left {
justify-content: flex-start;
.alignment-ui {
border-left-width: 3px;
}
}
&.right {
justify-content: flex-end;
.alignment-ui {
border-right-width: 3px;
}
}
&.center.ycenter {
.alignment-ui {
width: 25%;
height: 25%;
background-color: #fff;
}
}
}
}
</style>

View File

@ -0,0 +1,478 @@
<template>
<div class="flex flex-col tab-root">
<div class="flex flex-row flex-wrap">
<!-- AARD performance metrics -->
<div class="sub-panel">
<div class="flex flex-row">
<h1><mdicon name="television-play" :size="32" /> Automatic Aspect Ratio Detection</h1>
</div>
<div class="sub-panel-content">
<p>
<b>Autodetection performance</b>
</p>
<p>
Automatic aspect ratio detection is a resource-hungry feature.
This page allows you to trade autodetection accuracy and/or frequency for
better performance.
</p>
<p>
Note that some browsers <a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance/now" target="_blank">limit the accuracy of time measurements</a>, though once the bars go past the blue line those limitations are largely inconsequential.
</p>
<div class="performance-graph-container">
<div class="performance-graph">
<div class="time-budget hz144"></div>
<div class="time-budget hz120"></div>
<div class="time-budget hz60"></div>
<div class="time-budget hz30"></div>
<div class="time-budget hz24"></div>
<div class="time-budget rest"></div>
<div class="bar-container">
<div class="average-case">
<div class="stats">
<b>Average: </b>
<span class="draw">draw (main) {{(performanceData?.imageDraw?.averageTime ?? 0).toFixed(1)}} ms</span> |
<span class="draw-blackframe">blackframe {{(performanceData?.blackFrame?.averageTime ?? 0).toFixed(1)}} ms</span> |
<span class="processing">
processing {{
Math.max(
(performanceData?.total?.averageTime ?? 0)
- (performanceData?.imageDraw?.averageTime ?? 0)
- (performanceData?.blackFrame?.averageTime ?? 0),
0
).toFixed(1)
}} ms
</span>
</div>
<div class="bar">
<div
class="draw"
:style="{'width': (performanceData?.imageDraw?.averageTime ?? 0) + '%'}"
>
</div>
<div
class="draw-blackframe"
:style="{'width': (performanceData?.blackFrame?.averageTime ?? 0) + '%'}"
>
</div>
<div
class="processing"
:style="{
'width': Math.max(
(performanceData?.total?.averageTime ?? 0)
- (performanceData?.imageDraw?.averageTime ?? 0)
- (performanceData?.blackFrame?.averageTime ?? 0),
0
) + '%'
}"
>
</div>
</div>
</div>
<div class="worst-case">
<div class="stats">
<b>Worst: </b>
<span class="draw">draw (main) {{(performanceData?.imageDraw?.worstTime ?? 0).toFixed(1)}} ms</span> |
<span class="draw-blackframe">blackframe {{(performanceData?.blackFrame?.worstTime ?? 0).toFixed(1)}} ms</span> |
<span class="processing">
processing {{
Math.max(
(performanceData?.total?.worstTime ?? 0)
- (performanceData?.imageDraw?.worstTime ?? 0)
- (performanceData?.blackFrame?.worstTime ?? 0),
0
).toFixed(1)
}} ms
</span>
</div>
<div class="bar">
<div
class="draw"
:style="{'width': (performanceData?.imageDraw?.worstTime ?? 0) + '%'}"
>
</div>
<div
class="draw-blackframe"
:style="{'width': (performanceData?.blackFrame?.worstTime ?? 0) + '%'}"
>
</div>
<div
class="processing"
:style="{
'width': Math.max(
(performanceData?.total?.worstTime ?? 0)
- (performanceData?.imageDraw?.worstTime ?? 0)
- (performanceData?.blackFrame?.worstTime ?? 0),
0
) + '%'
}"
>
</div>
</div>
</div>
<div class="average-case">
<div class="stats">
<b>AR change (average): </b>
<span class="draw">draw (main) {{(performanceData?.imageDraw?.averageTime ?? 0).toFixed(1)}} ms</span> |
<span class="draw-blackframe">blackframe {{(performanceData?.blackFrame?.averageTime ?? 0).toFixed(1)}} ms</span> |
<span class="processing">processing {{
(
(performanceData?.fastLetterbox?.averageTime ?? 0)
+ (performanceData?.edgeDetect?.averageTime ?? 0)
).toFixed(1)
}} ms</span>
</div>
<div class="bar">
<div
class="draw"
:style="{'width': (performanceData?.imageDraw?.averageTime ?? 0) + '%'}"
>
</div>
<div
class="draw-blackframe"
:style="{'width': (performanceData?.blackFrame?.averageTime ?? 0) + '%'}"
>
</div>
<div
class="processing"
:style="{
'width': (
(performanceData?.fastLetterbox?.averageTime ?? 0)
+ (performanceData?.edgeDetect?.averageTime ?? 0)
) + '%'
}"
>
</div>
</div>
</div>
<div class="worst-case">
<div class="stats">
<b>AR change (worst): </b>
<span class="draw">draw (main) {{(performanceData?.imageDraw?.worstTime ?? 0).toFixed(1)}} ms</span> |
<span class="draw-blackframe">blackframe {{(performanceData?.blackFrame?.worstTime ?? 0).toFixed(1)}} ms</span> |
<span class="processing">processing {{
(
(performanceData?.fastLetterbox?.worstTime ?? 0)
+ (performanceData?.edgeDetect?.worstTime ?? 0)
).toFixed(1)
}} ms</span>
</div>
<div class="bar">
<div
class="draw"
:style="{'width': (performanceData?.imageDraw?.worstTime ?? 0) + '%'}"
>
</div>
<div
class="draw-blackframe"
:style="{'width': (performanceData?.blackFrame?.worstTime ?? 0) + '%'}"
>
</div>
<div
class="processing"
:style="{
'width': (
(performanceData?.fastLetterbox?.worstTime ?? 0)
+ (performanceData?.edgeDetect?.worstTime ?? 0)
) + '%'
}"
>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="settings-segment">
<h2>Basic settings</h2>
<div class="option">
<div class="name">
Autodetection frequency
</div>
<div class="description">
Shorter intervals (left side of the slider) are more responsive to changes in aspect ratio detections,
but requires more system resources.
</div>
<div class="indent">
<div class="flex flex-row row-padding">
<div class="flex flex-input">
More often&nbsp;<small>(~60/s)</small>
<input type="range"
:value="Math.log(settings.active.arDetect.timers.playing)"
@change="setArCheckFrequency($event.target.value)"
min="2.3"
max="9.3"
step="any"
/>
&nbsp; Less often&nbsp;<small>(~1/10s)</small>
</div>
</div>
</div>
</div>
<div class="option">
<div class="name">
Autodetection sensitivity
</div>
<div class="description">
</div>
</div>
<div class="field">
<div class="label">Maximum allowed vertical video misalignment:</div>
<div class="input">
<input v-model="settings.active.arDetect.allowedMisaligned" />
</div>
<div class="description">
Ultrawidify detects letterbox only if video is vertically centered. Some people are bad at vertically
centering the content, though. This is how off-center the video can be before autodetection will
refuse to crop it.
</div>
</div>
<div class="option">
<div class="name">Video sample size</div>
<div class="input">
<input v-model="settings.active.arDetect.canvasDimensions.sampleCanvas.width" /> x <input v-model="settings.active.arDetect.canvasDimensions.sampleCanvas.height" />
</div>
</div>
<div class="field">
<div class="label">Sample columns:</div>
<div class="input"><input v-model="settings.active.arDetect.sampling.staticCols" /></div>
</div>
<div class="field">
<div class="label">Sample rows:</div>
<div class="input"><input v-model="settings.active.arDetect.sampling.staticRows" /></div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Button from '../components/Button.vue'
import KeyboardShortcutParser from '../../../common/js/KeyboardShortcutParser';
import ShortcutButton from '../components/ShortcutButton';
import EditShortcutButton from '../components/EditShortcutButton';
import BrowserDetect from '../../../ext/conf/BrowserDetect';
import AspectRatioType from '../../../common/enums/AspectRatioType.enum';
import StretchType from '../../../common/enums/StretchType.enum';
import CropModePersistence from '../../../common/enums/CropModePersistence.enum';
import AlignmentOptionsControlComponent from './AlignmentOptionsControlComponent.vue';
export default {
data() {
return {
exec: null,
performanceData: {},
graphRefreshInterval: undefined,
}
},
mixins: [
],
props: [
'settings',
'frame',
'eventBus',
'site'
],
created() {
this.eventBus.subscribe('uw-config-broadcast', {function: (config) => this.handleConfigBroadcast(config)});
},
mounted() {
this.eventBus.sendToTunnel('get-aard-timing');
this.graphRefreshInterval = setInterval(() => this.eventBus.sendToTunnel('get-aard-timing'), 500);
},
destroyed() {
clearInterval(this.graphRefreshInterval);
},
components: {
ShortcutButton,
EditShortcutButton,
Button,
AlignmentOptionsControlComponent
},
computed: {
},
methods: {
async openOptionsPage() {
BrowserDetect.runtime.openOptionsPage();
},
refreshGraph() {
this.eventBus.sendToTunnel('get-aard-timing');
},
handleConfigBroadcast(data) {
if (data.type === 'aard-performance-data') {
this.performanceData = data.performanceData;
this.$nextTick( () => this.$forceUpdate() );
}
},
}
}
</script>
<style lang="scss" src="../../res/css/flex.scss" scoped module></style>
<style lang="scss" src="../res-common/panels.scss" scoped module></style>
<style lang="scss" src="../res-common/common.scss" scoped module></style>
<style lang="scss" scoped module>
@import '../res-common/variables';
.performance-graph-container {
position: relative;
width: 100%;
height: 8rem;
padding: 1rem;
.performance-graph {
border: 1px solid #fa6;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
// graph is 100 ms wide
.time-budget {
height: 100%;
display: inline-block;
position: relative;
box-sizing: border-box;
z-index: 100;
&.hz144 {
width: 6.9%;
// background-color: rgba(143, 143, 235, 0.47);
}
&.hz120 {
width: 1.39%;
// background-color: rgba(108, 108, 211, 0.441);
}
&.hz60 {
width: 8.33%;
// background-color: rgba(78, 78, 182, 0.327);
border-right: 2px solid rgb(96, 96, 227);
&::after {
content: '60fps';
position: absolute;
top: 0;
right: -2px;
border-right: 2px solid rgb(96, 96, 227);
transform: translateY(-100%);
font-size: 0.6rem;
padding-right: 0.25rem;
text-transform: small-caps;
}
}
&.hz30 {
width: 16.67%;
// background-color: rgba(56, 56, 151, 0.308);
border-right: 2px solid #fb772a;
&::after {
content: '30fps';
position: absolute;
top: 0;
right: -2px;
border-right: 2px solid #fb772a;
transform: translateY(-100%);
font-size: 0.6rem;
padding-right: 0.25rem;
text-transform: small-caps;
}
}
&.hz24 {
width: 8.33%;
// background-color: rgba(37, 37, 118, 0.269);
}
}
.bar-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
> div {
height: 2.5rem;
}
.stats {
background-color: rgba(0, 0, 0, 0.544);
font-size: 0.75rem;
font-family: 'Overpass Mono';
z-index: 11010;
span {
font-size: 0.75rem;
z-index: 11011;
&::before {
content: '';
display: inline-block;
width: 0.75rem;
height: 0.75rem;
margin-top: 0.25rem;
margin-right: 0.5rem;
}
&.draw::before {
background-color: #fb772a;
}
&.draw-blackframe::before {
background-color: #e70c0c;
}
&.processing::before {
background-color: rgb(176, 167, 239);
}
}
}
.bar {
width: 100%;
height: 0.69rem;
background-color: #000;
overflow: hidden;
* {
display: inline-block;
height: 100%;
}
.draw {
background-color: #fb772a;
}
.draw-blackframe {
background-color: #e70c0c;
}
.processing {
background-color: rgb(176, 167, 239);
}
}
}
}
}
</style>

View File

@ -0,0 +1,124 @@
<template>
<div class="flex flex-col w-100">
<!-- TAB ROW -->
<div class="flex flex-row">
<div
class="tab"
:class="{'active': tab === 'siteSettings'}"
@click="setTab('siteSettings')"
>
Settings for current site<br/>
<small>{{ site }}</small>
</div>
<div
class="tab"
:class="{'active': tab === 'extensionSettings'}"
@click="setTab(tab = 'extensionSettings')"
>
Default settings for extension
</div>
<div
class="tab"
:class="{'active': tab === 'otherSites'}"
@click="setTab(tab = 'otherSites')"
>
Settings for other sites
</div>
</div>
<template v-if="tab === 'siteSettings' && siteSettings">
<!-- <div class="button">
Reset settings for site
</div> -->
<SiteExtensionSettings
v-if="settings"
:settings="settings"
:siteSettings="siteSettings"
:isDefaultConfiguration="false"
></SiteExtensionSettings>
</template>
<template v-if="tab === 'extensionSettings' && globalSettings">
<SiteExtensionSettings
v-if="settings"
:settings="settings"
:siteSettings="globalSettings"
:isDefaultConfiguration="true"
></SiteExtensionSettings>
</template>
<template v-if="tab === 'otherSites'">
<OtherSiteSettings
v-if="settings"
:settings="settings"
>
</OtherSiteSettings>
</template>
<!-- Reset options -->
<div class="flex flex-col" style="margin-top: 2rem">
<h2>Reset and backup</h2>
<p>
Pressing the button will reset settings to default without asking.
</p>
<button
class="danger"
@click="resetSettings"
>
Reset settings
</button>
</div>
</div>
</template>
<script>
import SiteExtensionSettings from './PanelComponents/ExtensionSettings/SiteExtensionSettings.vue';
import OtherSiteSettings from './PanelComponents/ExtensionSettings/OtherSiteSettings.vue';
export default {
data() {
return {
tab: 'siteSettings'
}
},
mixins: [
],
props: [
'settings',
'site',
],
components: {
SiteExtensionSettings,
OtherSiteSettings
},
computed: {
globalSettings() {
return this.settings?.getSiteSettings('@global') ?? null;
},
siteSettings() {
if (this.site) {
return this.settings?.getSiteSettings(this.site) ?? null;
}
return null;
},
resetSettings() {
this.settings.active = JSON.parse(JSON.stringify(this.settings.default));
this.settings.saveWithoutReload();
}
},
methods: {
setTab(tab) {
this.tab = tab;
}
}
}
</script>
<style lang="scss" src="../../res/css/flex.scss" scoped module></style>
<style lang="scss" src="../res-common/panels.scss" scoped module></style>
<style lang="scss" src="../res-common/common.scss" scoped module></style>

View File

@ -0,0 +1,127 @@
<template>
<div class="flex flex-col w-full h-full gap-2">
<div class="flex flex-row gap-2 bg-black">
<div class="w-[1/2]" style="width: 50%">
<h1>What's new</h1>
<p>Full changelog for older versions <a href="https://github.com/tamius-han/ultrawidify/blob/master/CHANGELOG.md" target="_blank">is available here</a>.</p>
<h2>6.2.0</h2>
<p>
There's been another major change that skips a version.
</p>
<ul>
<li>Parts of automatic aspect ratio detection use WebGL now. This should result in improved performance. Slightly underwhelming blog post about this issue can be found <a href="https://stuff.tamius.net/sacred-texts/2024/10/02/hot-dang-i-shouldnt-have-given-on-webgl/" target="_blank">here</a>.</li>
<li>In-player UI now indicates whether a site is having a problem with automatic aspect ratio detection.</li>
<li>UI improvements</li>
</ul>
<p>
Since automatic aspect ratio detection had to be rewritten completely and since the new version needs to be out 5 days ago, there are some regressions in the automatic aspect ratio detection.
</p>
<h2>6.0.1</h2>
<p>Fixed external links.</p>
<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>
</div>
<div class="w-[1/2]" style="width: 50%; padding-left: 1rem; padding-top: 5rem;">
<h2>Thank you monies</h2>
<p>
If you think I deserve money for the work I did up to this point, you can bankroll my caffeine addiction.
</p>
<p class="text-center">
<a class="donate" href="https://www.paypal.com/paypalme/tamius" target="_blank">Donate on Paypal</a>
</p>
</div>
</div>
</div>
</template>
<script>
export default({
props: [
'settings'
],
mounted() {
this.settings.active.whatsNewChecked = true;
this.settings.saveWithoutReload();
}
});
</script>
<style lang="scss" scoped>
.flex {
display: flex;
}
.flex-row {
flex-direction: row;
}
p, li {
font-size: 1rem;
}
small {
opacity: 0.5;
font-size: 0.8em;
}
a {
color: #fa6;
}
.text-center {
text-align: center;
}
.donate {
margin: 1rem;
padding: 0.5rem 1rem;
border-radius: 0.25rem;
background-color: #fa6;
color: #000;
}
</style>

View File

@ -0,0 +1,126 @@
<template>
<div>
<div>
<h1>{{loggerPanel.title}}<small><br/>{{loggerPanel.subtitle}}</small></h1>
<div>Logger configuration:</div>
<template v-if="loggerPanel.pasteConfMode">
</template>
<template v-else>
<JsonObject
label="logger-settings"
:value="currentSettings"
:ignoreKeys="{'allowLogging': false}"
@change="updateSettingsUi"
></JsonObject>
</template>
<div class="flex flex-row flex-end">
<div class="button" @click="restoreLoggerSettings()">
Revert
</div>
<div class="button button-primary" @click="saveLoggerSettings()">
Save
</div>
<div class="button button-primary" @click="startLogging()">
Save and start logging
</div>
</div>
</div>
</div>
</template>
<script>
import JsonObject from '../components/JsonEditor/JsonObject.vue'
import Logger, { baseLoggingOptions } from '../../../ext/lib/Logger';
export default {
components: {
JsonObject
},
data() {
return {
loggerPanelRotation: [{
title: 'DEFORESTATOR 5000',
subtitle: 'Iron Legion\'s finest logging tool'
},{
title: 'Astinus',
subtitle: 'Ultrawidify logging tool'
}, {
title: 'Tracer',
subtitle: "Maybe I'll be Tracer — I'm already printing stack traces."
}, {
title: 'Grûmsh',
subtitle: "He who watches (all the things printed to the console)."
}, {
title: 'Situation Room/The Council',
subtitle: 'We will always be watching.'
}, {
title: 'Isengard Land Management Services',
subtitle: "#log4saruman"
}, {
title: 'Saw Hero',
subtitle: 'Ultrawidify logging tool'
}],
loggerPanel: {
title: 'whoopsie daisy',
subtitle: 'you broke the header choosing script',
pasteConfMode: false,
},
lastSettings: undefined,
parsedSettings: undefined,
currentSettings: undefined
};
},
props: [
'settings',
'eventBus',
'site'
],
created() {
this.loggerPanel = {
...this.loggerPanelRotation[Math.floor(Math.random() * this.loggerPanelRotation.length)],
pasteConfMode: false,
};
this.loadDefaultConfig();
},
methods: {
loadDefaultConfig() {
this.lastSettings = baseLoggingOptions;
this.parsedSettings = JSON.stringify(this.lastSettings, null, 2) || '';
this.currentSettings = JSON.parse(JSON.stringify(this.lastSettings));
},
async getLoggerSettings() {
this.lastSettings = await Logger.getConfig() || baseLoggingOptions;
this.parsedSettings = JSON.stringify(this.lastSettings, null, 2) || '';
this.currentSettings = JSON.parse(JSON.stringify(this.lastSettings));
},
updateSettingsUi(val) {
try {
this.parsedSettings = JSON.stringify(val, null, 2);
this.lastSettings = val;
this.currentSettings = JSON.parse(JSON.stringify(this.lastSettings));
} catch (e) {
}
},
saveLoggerSettings() {
Logger.saveConfig({...this.lastSettings});
},
restoreLoggerSettings() {
this.getLoggerSettings();
this.confHasError = false;
},
async startLogging(){
this.logStringified = undefined;
await Logger.saveConfig({...this.lastSettings, allowLogging: true});
window.location.reload();
},
}
}
</script>

View File

@ -0,0 +1,34 @@
<template>
<div>
<h1>In-player UI</h1>
<div
class="button b3"
style="margin: 16px; padding: 4px;"
@click="showInPlayerUi()"
>
Show in-player UI
</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',
],
methods: {
showInPlayerUi() {
this.eventBus.send('uw-set-ui-state', {globalUiVisible: true}, {comms: {forwardTo: 'active'}});
}
}
}
</script>

View File

@ -0,0 +1,122 @@
<template>
<div class="">
<template v-if="!selectedSite">
<div style="margin-top: 1rem; margin-bottom: 1rem;">
<b>NOTE:</b> Sites not on this list use default extension settings.
</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="flex flex-row">
<div class="flex-grow pointer">
<b>{{ site.key }}</b>
<span :style="getSiteTypeColor(site.type)">
(config: {{site.type ?? 'unknown'}})
</span></div>
<div>Edit</div>
</div>
<div class="flex flex-row">
<small>
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;
kbd: <span :style="getSiteEnabledColor(site.key, 'enableKeyboard')"><small>{{ getSiteEnabledModes(site.key, 'enableKeyboard') }}</small></span>
</small>
</div>
</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 }}
</div>
</div>
<div>
<SiteExtensionSettings
v-if="selectedSiteSettings"
:settings="settings"
:siteSettings="selectedSiteSettings"
:isDefaultConfiguration="false"
></SiteExtensionSettings>
</div>
</template>
</div>
</template>
<script>
import ExtensionMode from '../../../../../common/enums/ExtensionMode.enum';
import SiteExtensionSettings from './SiteExtensionSettings.vue';
export default {
data() {
return {
selectedSite: null,
}
},
props: [
'settings',
],
components: {
SiteExtensionSettings,
},
computed: {
sites() {
if (!this.settings?.active?.sites) {
return [];
} else {
const sites = [];
for (const siteKey in this.settings.active.sites) {
if (!siteKey.startsWith('@')) {
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: {
getSiteTypeColor(siteType) {
switch (siteType) {
case 'official': return 'color: #fa6';
case 'community': return 'color: rgb(114, 114, 218)';
case 'officially-disabled': return 'color: #f00';
case 'testing': return 'color: #d81';
default: return 'color: rgb(138, 65, 126)'
};
},
getSiteEnabledColor(site, component) {
const status = this.getSiteEnabledModes(site, component);
return status === 'disabled' ? 'color: #f00' : 'color: #1f8';
},
getSiteEnabledModes(site, component) {
if (this.settings?.getSiteSettings(site).data[component]?.normal === ExtensionMode.Enabled) {
return 'always';
}
if (this.settings?.getSiteSettings(site).data[component]?.theater === ExtensionMode.Enabled) {
return 'T + FS';
}
if (this.settings?.getSiteSettings(site).data[component]?.fullscreen === ExtensionMode.Enabled) {
return 'fullscreen';
}
return 'disabled';
}
}
}
</script>
<style lang="scss" src="../../../../res/css/flex.scss" scoped></style>
<style lang="scss" src="../../../res-common/panels.scss" scoped></style>
<style lang="scss" src="../../../res-common/common.scss" scoped></style>

View File

@ -0,0 +1,462 @@
<template>
<div>
<!-- Enable extension -->
<div class="field">
<div class="label">
Enable extension under the following conditions:
</div>
<div class="select">
<select
v-model="simpleExtensionSettings.enable"
@click="setExtensionMode('enable', $event)"
>
<option
v-if="simpleExtensionSettings.enable === 'complex'"
value="complex"
>
(Site uses advanced settings)
</option>
<template v-if="isDefaultConfiguration">
<option value="disabled">
Disabled (unless enabled for specific site)
</option>
</template>
<template v-else>
<option value="default">
Use default ()
</option>
<option value="disabled">
Never
</option>
</template>
<option value="fs">
Fullscreen only
</option>
<option value="theater">
Fullscreen and theater mode
</option>
<option value="enabled">
Always
</option>
</select>
</div>
</div>
<!-- Enable AARD -->
<div class="field">
<div class="label">
Enable automatic aspect ratio detection under the following conditions:
</div>
<div class="select">
<select
v-model="simpleExtensionSettings.enableAard"
@click="setExtensionMode('enableAard', $event)"
>
<option
v-if="simpleExtensionSettings.enable === 'complex'"
value="complex"
>
(Site uses advanced settings)
</option>
<template v-if="isDefaultConfiguration">
<option value="disabled">
Disabled (unless enabled for specific site)
</option>
</template>
<template v-else>
<option value="default">
Use default ()
</option>
<option value="disabled">
Never
</option>
</template>
<option value="fs">
Fullscreen only
</option>
<option value="theater">
Fullscreen and theater mode
</option>
<option value="enabled">
Always
</option>
</select>
</div>
</div>
<!-- Enable keyboard -->
<div class="field">
<div class="label">
Enable keyboard shortcuts under the following conditions
</div>
<div class="select">
<select
v-model="simpleExtensionSettings.enableKeyboard"
@click="setExtensionMode('enableKeyboard', $event)"
>
<option
v-if="simpleExtensionSettings.enable === 'complex'"
value="complex"
>
(Site uses advanced settings)
</option>
<template v-if="isDefaultConfiguration">
<option value="disabled">
Disabled (unless enabled for specific site)
</option>
</template>
<template v-else>
<option value="default">
Use default ()
</option>
<option value="disabled">
Never
</option>
</template>
<option value="fs">
Fullscreen only
</option>
<option value="theater">
Fullscreen and theater mode
</option>
<option value="enabled">
Always
</option>
</select>
</div>
</div>
<!-- Default crop -->
<div class="field">
<div class="label">Default crop:</div>
<div class="select">
<select
v-model="siteDefaultCrop"
@change="setOption('defaults.crop', $event)"
>
<option
v-if="!isDefaultConfiguration"
:value="JSON.stringify({useDefault: true})"
>
Use default ({{getCommandValue(settings?.active.commands.crop, siteSettings.data.defaults.crop)}})
</option>
<option
v-for="(command, index) of settings?.active.commands.crop"
:key="index"
:value="JSON.stringify(command.arguments)"
>
{{command.label}}
</option>
</select>
</div>
<div class="hint">This is how extension will crop video if/when autodetection is disabled. Pick 'Reset' option to keep aspect ratio as-is by default.</div>
</div>
<!-- Default stretch -->
<div class="field">
<div class="label">Default stretch:</div>
<div class="select">
<select
v-model="siteDefaultStretch"
@change="setOption('defaults.stretch', $event)"
>
<option
v-if="!isDefaultConfiguration"
:value="JSON.stringify({useDefault: true})"
>
Use default ({{getCommandValue(settings?.active.commands.stretch, siteSettings.data.defaults.stretch)}})
</option>
<option
v-for="(command, index) of settings?.active.commands.stretch"
:key="index"
:value="JSON.stringify(command.arguments)"
>
{{command.label}}
</option>
</select>
</div>
</div>
<!-- Default alignment -->
<div class="field">
<div class="label">Default alignment:</div>
<div class="select">
<select
v-model="siteDefaultAlignment"
@change="setOption('defaults.alignment', $event)"
>
<option
v-if="!isDefaultConfiguration"
:value="JSON.stringify({useDefault: true})"
>
Use default ({{getAlignmentLabel(siteSettings.data.defaults.alignment)}})
</option>
<option
v-for="(command, index) of alignmentOptions"
:key="index"
:value="JSON.stringify(command.arguments)"
>
{{command.label}}
</option>
</select>
</div>
</div>
<!-- Crop, et. al. Persistence -->
<div class="field">
<div class="label">Persist crop, stretch, and alignment between videos</div>
<div class="select">
<select
v-model="siteDefaultCropPersistence"
@click="setOption('persistCSA', $event)"
>
<option
v-if="!isDefaultConfiguration"
:value="CropModePersistence.Default"
>
Use default ({{defaultPersistanceLabel}})
</option>
<option :value="CropModePersistence.Disabled">Disabled</option>
<option :value="CropModePersistence.UntilPageReload">Until page reload</option>
<option :value="CropModePersistence.CurrentSession">Current session</option>
<option :value="CropModePersistence.Forever">Always persist</option>
</select>
</div>
</div>
</div>
</template>
<script>
import ExtensionMode from '../../../../../common/enums/ExtensionMode.enum';
import VideoAlignmentType from '../../../../../common/enums/VideoAlignmentType.enum';
import CropModePersistence from './../../../../../common/enums/CropModePersistence.enum';
export default {
data() {
return {
CropModePersistence: CropModePersistence,
alignmentOptions: [
{label: 'Top left', arguments: {x: VideoAlignmentType.Left, y: VideoAlignmentType.Top}},
{label: 'Top center', arguments: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Top}},
{label: 'Top right', arguments: {x: VideoAlignmentType.Right, y: VideoAlignmentType.Top}},
{label: 'Left', arguments: {x: VideoAlignmentType.Left, y: VideoAlignmentType.Center}},
{label: 'Center', arguments: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Center}},
{label: 'Right', arguments: {x: VideoAlignmentType.Right, y: VideoAlignmentType.Center}},
{label: 'Bottom left', arguments: {x: VideoAlignmentType.Left, y: VideoAlignmentType.Bottom}},
{label: 'Bottom center', arguments: {x: VideoAlignmentType.Center, y: VideoAlignmentType.Bottom}},
{label: 'Bottom right', arguments: {x: VideoAlignmentType.Right, y: VideoAlignmentType.Bottom}}
]
}
},
mixins: [
],
props: [
'settings',
'siteSettings',
'isDefaultConfiguration'
],
components: {
},
computed: {
simpleExtensionSettings() {
return {
enable: this.compileSimpleSettings('enable'),
enableAard: this.compileSimpleSettings('enableAard'),
enableKeyboard: this.compileSimpleSettings('enableKeyboard'),
}
},
siteDefaultCrop() {
return this.siteSettings.raw?.defaults?.crop ? JSON.stringify(this.siteSettings.raw?.defaults?.crop) : JSON.stringify({useDefault: true});
},
siteDefaultStretch() {
return this.siteSettings.raw?.defaults?.stretch ? JSON.stringify(this.siteSettings.raw?.defaults?.stretch) : JSON.stringify({useDefault: true});
},
siteDefaultAlignment() {
return this.siteSettings.raw?.defaults?.alignment ? JSON.stringify(this.siteSettings.raw?.defaults?.alignment) : JSON.stringify({useDefault: true});
},
siteDefaultCropPersistence() {
return this.siteSettings.raw?.persistCSA ?? undefined;
},
defaultPersistanceLabel() {
switch (this.siteSettings.defaultSettings.persistCSA) {
case CropModePersistence.CurrentSession:
return 'current session';
case CropModePersistence.Disabled:
return 'disabled';
case CropModePersistence.UntilPageReload:
return 'until page reload';
case CropModePersistence.Forever:
return 'Always persist';
}
return '??';
}
},
methods: {
/**
* Compiles our extension settings into more user-friendly options
*/
compileSimpleSettings(component) {
try {
if (
this.siteSettings?.data?.[component]?.normal === ExtensionMode.Disabled
&& this.siteSettings?.data?.[component]?.theater === ExtensionMode.Disabled
&& this.siteSettings?.data?.[component]?.fullscreen === ExtensionMode.Disabled
) {
return 'disabled';
}
if (
this.siteSettings?.data?.[component]?.normal === ExtensionMode.Default
&& this.siteSettings?.data?.[component]?.theater === ExtensionMode.Default
&& this.siteSettings?.data?.[component]?.fullscreen === ExtensionMode.Default
) {
return 'default';
}
if (
this.siteSettings?.data?.[component]?.normal === ExtensionMode.Disabled
&& this.siteSettings?.data?.[component]?.theater === ExtensionMode.Disabled
&& this.siteSettings?.data?.[component]?.fullscreen === ExtensionMode.Enabled
) {
return 'fs';
}
if (
this.siteSettings?.data?.[component]?.normal === ExtensionMode.Disabled
&& this.siteSettings?.data?.[component]?.theater === ExtensionMode.Enabled
&& this.siteSettings?.data?.[component]?.fullscreen === ExtensionMode.Enabled
) {
return 'theater';
}
if (
this.siteSettings?.data?.[component]?.normal === ExtensionMode.Enabled
&& this.siteSettings?.data?.[component]?.theater === ExtensionMode.Enabled
&& this.siteSettings?.data?.[component]?.fullscreen === ExtensionMode.Enabled
) {
return 'enabled';
}
return 'complex';
} catch (e) {
return 'loading';
}
},
getCommandValue(availableCommands, command) {
for (const cmd of availableCommands) {
if (JSON.stringify(cmd.arguments) === JSON.stringify(command)) {
return cmd.label;
}
}
return 'Unknown command';
},
getAlignmentLabel(alignment) {
// in case default settings don't have this set
if (!alignment) {
return 'Center'
}
let x, y;
if (alignment.x === VideoAlignmentType.Center) {
x = 'center';
} else if (alignment.x === VideoAlignmentType.Left) {
x = 'left';
} else if (alignment.x === VideoAlignmentType.Right) {
x = 'right';
} else {
x = '??'
}
if (alignment.y === VideoAlignmentType.Center) {
y = 'center';
} else if (alignment.y === VideoAlignmentType.Bottom) {
y = 'bottom';
} else if (alignment.y === VideoAlignmentType.Top) {
y = 'top';
} else {
y = '???'
};
if (x === y) {
return x;
}
return `${y} ${x}`;
},
getOption(option) {
},
async setOption(option, $event) {
const value = $event.target.value;
let commandArguments;
// if argument is json, parse json. Otherwise, pass the value as-is
try {
commandArguments = value !== undefined ? JSON.parse(value) : undefined;
} catch(e) {
commandArguments = value;
}
if (commandArguments.useDefault) {
commandArguments = undefined;
}
await this.siteSettings.set(option, commandArguments);
this.$nextTick( () => this.$forceUpdate() );
},
setExtensionMode(component, event) {
const option = event.target.value;
if (option === 'complex') {
return;
}
if (option === 'default') {
return this.siteSettings.set(component, {
normal: ExtensionMode.Default,
theater: ExtensionMode.Default,
fullscreen: ExtensionMode.Default
});
}
if (option === 'disabled') {
return this.siteSettings.set(component, {
normal: ExtensionMode.Disabled,
theater: ExtensionMode.Disabled,
fullscreen: ExtensionMode.Disabled
});
}
if (option === 'enabled') {
return this.siteSettings.set(component, {
normal: ExtensionMode.Enabled,
theater: ExtensionMode.Enabled,
fullscreen: ExtensionMode.Enabled
});
}
if (option === 'theater') {
return this.siteSettings.set(component, {
normal: ExtensionMode.Disabled,
theater: ExtensionMode.Enabled,
fullscreen: ExtensionMode.Enabled
});
}
if (option === 'fs') {
return this.siteSettings.set(component, {
normal: ExtensionMode.Disabled,
theater: ExtensionMode.Disabled,
fullscreen: ExtensionMode.Enabled
});
}
}
}
}
</script>
<style lang="scss" src="../../../../res/css/flex.scss" scoped></style>
<style lang="scss" src="../../../res-common/panels.scss" scoped></style>
<style lang="scss" src="../../../res-common/common.scss" scoped></style>
<style scoped>
.button-hover:hover {
color: #fa6;
}
</style>

View File

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

View File

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

View File

@ -0,0 +1,156 @@
<template>
<div class="flex flex-col">
<!--
min, max and value need to be implemented in js as this slider
should use logarithmic scale
-->
<div class="flex flex-row flex-end">
<Button
v-if="zoomAspectRatioLocked"
label="Unlock aspect ratio"
icon="lock-open"
:fixedWidth="true"
@click="toggleZoomAr()"
>
</Button>
<Button
v-else
label="Lock aspect ratio"
icon="lock"
:fixedWidth="true"
@click="toggleZoomAr()"
>
</Button>
</div>
<template v-if="zoomAspectRatioLocked">
<input id="_input_zoom_slider"
class="input-slider"
type="range"
step="any"
min="-1"
max="3"
:value="zoom.x"
@input="changeZoom($event.target.value)"
/>
<div style="overflow: auto" class="flex flex-row">
<div class="flex flex-grow medium-small x-pad-1em">
Zoom: {{getZoomForDisplay('x')}}
</div>
<div class="flex flex-nogrow flex-noshrink medium-small">
<a class="_zoom_reset x-pad-1em" @click="resetZoom()">reset</a>
</div>
</div>
</template>
<template v-else>
<div>Horizontal zoom</div>
<input id="_input_zoom_slider"
class="input-slider"
type="range"
step="any"
min="-1"
max="4"
:value="zoom.x"
@input="changeZoom($event.target.value, 'x')"
/>
<div>Vertical zoom</div>
<input id="_input_zoom_slider"
class="input-slider"
type="range"
step="any"
min="-1"
max="3"
:value="zoom.y"
@input="changeZoom($event.target.value, 'y')"
/>
<div style="overflow: auto" class="flex flex-row">
<div class="flex flex-grow medium-small x-pad-1em">
Zoom: {{getZoomForDisplay('x')}} x {{getZoomForDisplay('y')}}
</div>
<div class="flex flex-nogrow flex-noshrink medium-small">
<a class="_zoom_reset x-pad-1em" @click="resetZoom()">reset</a>
</div>
</div>
</template>
</div>
</template>
<script>
export default {
data() {
return {
zoomAspectRatioLocked: true,
zoom: {
x: 0,
y: 0
},
// TODO: this should be mixin?
resizerConfig: {
crop: null,
stretch: null,
zoom: null,
pan: null
}
}
},
mixins: [
],
props: [
'settings', // required for buttons and actions, which are global
'siteSettings',
'eventBus',
'isEditing'
],
methods: {
getZoomForDisplay(axis) {
// zoom is internally handled logarithmically, because we want to have x0.5, x1, x2, x4 ... magnifications
// spaced out at regular intervals. When displaying, we need to convert that to non-logarithmic values.
return `${(Math.pow(2, this.zoom[axis]) * 100).toFixed()}%`
},
toggleZoomAr() {
this.zoomAspectRatioLocked = !this.zoomAspectRatioLocked;
},
resetZoom() {
// we store zoom logarithmically on this component
this.zoom = {x: 0, y: 0};
// we do not use logarithmic zoom elsewhere
// todo: replace eventBus with postMessage to parent
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'y'});
// this.eventBus.send('set-zoom', {zoom: 1, axis: 'x'});
this.eventBus?.sendToTunnel('set-zoom', {zoom: 1, axis: 'y'});
this.eventBus?.sendToTunnel('set-zoom', {zoom: 1, axis: 'x'});
},
changeZoom(newZoom, axis) {
// we store zoom logarithmically on this compnent
if (!axis) {
this.zoom.x = newZoom;
} else {
this.zoom[axis] = newZoom;
}
// we do not use logarithmic zoom elsewhere, therefore we need to convert
newZoom = Math.pow(2, newZoom);
if (this.zoomAspectRatioLocked) {
this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom, axis: 'y'});
this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom, axis: 'x'});
} else {
this.eventBus?.sendToTunnel('set-zoom', {zoom: newZoom, axis: axis ?? 'x'});
}
},
}
}
</script>
<style lang="scss" src="../../../../res/css/flex.scss" scoped></style>
<style lang="scss" src="../../../res-common/panels.scss" scoped></style>
<style lang="scss" src="../../../res-common/common.scss" scoped></style>

View File

@ -0,0 +1,569 @@
<template>
<div class="flex flex-col tab-root">
<!-- ADD ANY OPTION BARS HERE -->
<!-- The rest of the tab -->
<div class="flex flex-row flex-wrap">
<!-- Player element picker -->
<div class="sub-panel">
<div class="flex flex-row">
<h1><mdicon name="television-play" :size="32" /> Player element</h1>
</div>
<div class="flex flex-row">
<div class="sub-panel-content">
<p>
You're probably on this page because Ultrawidify doesn't crop the player correctly.
</p>
<p>
If you hover over the boxes below, the corresponding element will change (sepia filter + higher brightness + reduced contrast + it gets an outline). Player element
should be the closest element to the video element, for which the sepia/brightness effect covers the area you expect the video will cover.
</p>
<p>
You need to reload the page for changes to take effect.
</p>
<!-- <p>
<a @click="showAdvancedOptions = !showAdvancedOptions">
<template v-if="showAdvancedOptions">Hide advanced options</template>
<template v-else>Show advanced options</template>
</a>
</p> -->
<div v-if="showAdvancedOptions" style="display: flex; flex-direction: row">
<div style="display: flex; flex-direction: column">
<div>
<input :checked="playerManualQs"
@change="togglePlayerManualQs"
type="checkbox"
/>
<div>
Use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors" target="_blank">CSS selector</a> for player<br/>
<small>If defining multiple selectors, separate them with commas.</small>
</div>
</div>
<div>Selector</div>
<input type="text"
v-model="playerQs"
@change="updatePlayerQuerySelector"
@blur="updatePlayerQuerySelector"
:disabled="playerByNodeIndex || !playerManualQs"
/>
</div>
<div style="display: flex; flex-direction: column">
<b>Custom CSS for site</b>
<textarea></textarea>
</div>
</div>
<div style="display: flex; flex-direction: row;">
<div class="element-tree">
<table>
<thead>
<tr>
<th>
<div class="status-relative">
Status <mdicon name="help-circle" @click="showLegend = !showLegend" />
<div v-if="showLegend" class="element-symbol-legend">
<b>Symbols:</b><br />
<mdicon name="alert-remove" class="invalid" /> Element of invalid dimensions<br />
<mdicon name="refresh-auto" class="auto-match" /> Ultrawidify's player detection thinks this should be the player<br />
<mdicon name="bookmark" class="parent-offset-match" /> Site settings say this should be the player (based on counting parents)<br />
<mdicon name="crosshairs" class="qs-match" /> Site settings say this should be the player (based on query selectors)<br />
<mdicon name="check-circle" class="activePlayer" /> Element that is actually the player
</div>
</div>
</th>
<th>Element</th>
<!-- <th>Actions</th> -->
<!-- <th>Quick fixes</th> -->
</tr>
</thead>
<tbody>
<tr
v-for="(element, index) of elementStack"
:key="index"
class="element-row"
>
<td>
<div class="status">
<div
v-if="element.heuristics?.invalidSize"
class="invalid"
>
<mdicon name="alert-remove" />
</div>
<div
v-if="element.heuristics?.autoMatch"
class="auto-match"
>
<mdicon name="refresh-auto" />
</div>
<div
v-if="element.heuristics?.qsMatch"
class="qs-match"
>
<mdicon name="crosshairs" />
</div>
<div
v-if="element.heuristics?.manualElementByParentIndex"
class="parent-offset-match"
>
<mdicon name="bookmark" />
</div>
<div
v-if="element.heuristics?.activePlayer"
class="activePlayer"
>
<mdicon name="check-circle" />
</div>
</div>
</td>
<td>
<div
class="element-data"
@mouseover="markElement(index, true)"
@mouseleave="markElement(index, false)"
@click="setPlayer(index)"
>
<div class="tag">
<b>{{element.tagName}}</b> <i class="id">{{element.id ? `#`:''}}{{element.id}}</i> @ <span class="dimensions">{{element.width}}</span>x<span class="dimensions">{{element.height}}</span>
</div>
<div v-if="element.classList" class="class-list">
<div v-for="cls of element.classList" :key="cls">
{{cls}}
</div>
</div>
</div>
</td>
<td>
<div class="flex flex-row">
<!-- <div @click="designatePlayer(index)">Set as player {{ index }}</div> -->
</div>
</td>
<!-- <td>
<div
class="css-fixes"
>
<div style="width: 100%"><b>Quick fixes:</b></div>
<div
class="css-line"
:class="{'active': cssStack[index]?.includes('width: 100%;')}"
@click="toggleCssForElement(index, 'width: 100%;')"
>
Width: 100%
</div>
<div
class="css-line"
:class="{'active': cssStack[index]?.includes('height: 100%;')}"
@click="toggleCssForElement(index, 'height: 100%;')"
>
Height: 100%
</div>
<div
class="css-line"
:class="{'active': cssStack[index]?.includes('display: flex;')}"
@click="toggleCssForElement(index, 'display: flex;')"
>
Display: flex
</div>
<div class="css-line">
Flex direction:
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('flex-direction: row;')}"
@click="toggleCssForElement(index, 'flex-direction', 'row')"
>
row
</span> |
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('flex-direction: column;')}"
@click="toggleCssForElement(index, 'flex-direction', 'column')"
>
column
</span>
</div>
<div class="css-line">
Justify content:
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('justify-content: start;')}"
@click="toggleCssForElement(index, 'justify-content', 'start')"
>
start
</span> |
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('justify-content: center;')}"
@click="toggleCssForElement(index, 'justify-content', 'center')"
>
center
</span> |
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('justify-content: end;')}"
@click="toggleCssForElement(index, 'justify-content', 'end')"
>
end
</span>
</div>
<div class="css-line">
Align items:
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('align-items: start;')}"
@click="toggleCssForElement(index, 'align-items', 'start')"
>
start
</span> |
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('align-items: center;')}"
@click="toggleCssForElement(index, 'align-items', 'center')"
>
center
</span> |
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('align-items: end;')}"
@click="toggleCssForElement(index, 'align-items', 'end')"
>
end
</span>
</div>
<div class="css-line">
Justify self:
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('justify-self: start;')}"
@click="toggleCssForElement(index, 'justify-self', 'start')"
>
start
</span> |
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('justify-self: center;')}"
@click="toggleCssForElement(index, 'justify-self', 'center')"
>
center
</span> |
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('justify-self: end;')}"
@click="toggleCssForElement(index, 'justify-self', 'end')"
>
end
</span>
</div>
<div class="css-line">
Align self:
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('align-self: start;')}"
@click="toggleCssForElement(index, 'align-self', 'start')"
>
start
</span> |
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('align-self: center;')}"
@click="toggleCssForElement(index, 'align-self', 'center')"
>
center
</span> |
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('align-self: end;')}"
@click="toggleCssForElement(index, 'align-self', 'end')"
>
end
</span>
</div>
<div class="css-line">
Text-align:
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('text-align: left;')}"
@click="toggleCssForElement(index, 'text-align', 'left')"
>
left
</span> |
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('text-align: center;')}"
@click="toggleCssForElement(index, 'text-align', 'center')"
>
center
</span> |
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.find(x => x.includes('text-align: right'))}"
@click="toggleCssForElement(index, 'text-align', 'right')"
>
right
</span>
</div>
<div class="css-line">
Position:
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('position: relative;')}"
@click="toggleCssForElement(index, 'position', 'relative')"
>
relative
</span> |
<span
class="css-line-suboption"
:class="{'active': cssStack[index]?.includes('position: absolute;')}"
@click="toggleCssForElement(index, 'position', 'absolute')"
>
absolute
</span>
</div>
</div>
</td> -->
</tr>
</tbody>
</table>
<div class="element-config">
</div>
</div>
<!-- <div class="css-preview">
{{cssStack}}
</div> -->
</div>
</div>
<!-- <div class="sub-panel-content">
<h2>Advanced settings</h2>
</div> -->
</div>
</div>
</div>
</div>
</template>
<script>
export default({
components: {
},
data() {
return {
elementStack: [],
cssStack: [],
showLegend: false,
showAdvancedOptions: false,
};
},
mixins: [],
props: [
'siteSettings',
'frame',
'eventBus',
'site',
'isPopup'
],
created() {
this.eventBus.subscribe('uw-config-broadcast', {function: (config) => this.handleElementStack(config)});
},
mounted() {
this.getPlayerTree();
},
computed: {},
methods: {
getPlayerTree() {
if (this.isPopup) {
this.eventBus.send('get-player-tree');
} else {
this.eventBus.sendToTunnel('get-player-tree');
}
},
handleElementStack(configBroadcast) {
if (configBroadcast.type === 'player-tree') {
this.elementStack = configBroadcast.config;
this.$nextTick( () => this.$forceUpdate() );
}
},
markElement(parentIndex, enable) {
this.eventBus.sendToTunnel('set-mark-element', {parentIndex, enable});
},
async setPlayer(index) {
// yup.
this.siteSettings.getDOMConfig('modified', 'original');
await this.siteSettings.setUpdateFlags(['PlayerData']);
await this.siteSettings.set('DOMConfig.modified.type', 'modified', {noSave: true});
await this.siteSettings.set('activeDOMConfig', 'modified', {noSave: true});
// if user agrees with ultrawidify on what element player should be,
// we just unset our settings for this site
if (this.elementStack[index].heuristics?.autoMatch) {
await this.siteSettings.set('DOMConfig.modified.elements.player.manual', false);
this.getPlayerTree();
} else {
// ensure settings exist:
await this.siteSettings.set('DOMConfig.modified.elements.player.manual', true, {noSave: true});
await this.siteSettings.set('DOMConfig.modified.elements.player.mode', 'index', {noSave: true});
await this.siteSettings.set('DOMConfig.modified.elements.player.index', index, true);
this.getPlayerTree();
}
},
/**
* Toggles active CSS for element of certain parent index.
* cssValue is optional and can be included in cssRule argument
*/
toggleCssForElement(index, cssRule, cssValue) {
// we will handle elements that put cssValue as a separate argument elsewhere
if (cssValue) {
return this.toggleCssForElement_3arg(index,cssRule, cssValue);
}
// this rule applies to current element remove it!
if (this.cssStack[index]?.includes(cssRule)) {
this.cssStack[index] = this.cssStack[index].filter(x => ! x.includes(cssRule));
} else {
if (!this.cssStack[index]) {
this.cssStack[index] = [];
}
this.cssStack[index].push(cssRule)
}
//TODO: update settings!
},
toggleCssForElement_3arg(index, cssRule, cssValue) {
const matching = this.cssStack[index]?.find(x => x.includes(cssRule))
if (matching) {
this.cssStack[index] = this.cssStack[index].filter(x => ! x.includes(cssRule));
if (!matching.includes(cssValue)) {
this.cssStack[index].push(`${cssRule}: ${cssValue};`);
}
} else {
if (!this.cssStack[index]) {
this.cssStack[index] = [];
}
this.cssStack[index].push(`${cssRule}: ${cssValue};`);
}
//TODO: update settings!
}
}
})
</script>
<style lang="scss" scoped>
p {
font-size: 1rem;
}
.element-tree {
.element-row {
// display: flex;
// flex-direction: row;
margin: 0.5rem;
.status {
width: 6.9rem;
margin-right: 1rem;
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
.invalid {
color: #f00;
}
}
.element-data {
border: 1px solid rgba(255,255,255,0.5);
padding: 0.5rem 1rem;
max-width: 30rem;
display: flex;
flex-direction: column;
.tag {
text-transform: lowercase;
font-weight: bold;
}
.id {
font-style: italic;
}
.class-list {
font-style: italic;
opacity: 0.75;
font-size: 0.75rem;
display: flex;
flex-direction: row;
flex-wrap: wrap;
> div {
background-color: rgba(255,255,255,0.5);
border-radius: 0.25rem;
padding: 0.125rem 0.5rem;
margin: 0.125rem 0.25rem;
}
}
.dimensions {
color: #473c85;
}
}
}
}
// .css-fixes {
// // display: flex;
// // flex-direction: row;
// // flex-wrap: wrap;
// // align-items: flex-start;
// // justify-content:flex-start;
// }
.css-line {
display: inline-block;
flex-grow: 0;
flex-shrink: 0;
border: 1px solid rgba(255,255,255,0.5);
background: #000;
margin: 0.125rem 0.25rem;
padding: 0.125rem 0.25rem;
&.active, >span.active {
background: rgb(255, 174, 107);
color: #000;
}
}
.status-relative {
position: relative;
.element-symbol-legend {
position: absolute;
top: 100%;
left: 0;
z-index: 20000;
width: 32rem;
text-align: left;
background-color: #000;
padding: 1rem;
border: 1px solid rgba(255,255,255,0.5);
}
}
.tab-root {
width: 100%;
}
</style>

View File

@ -0,0 +1,303 @@
<template>
<div class="flex flex-col" style="position: relative; width: 100%;">
<!-- The rest of the tab is under 'edit ratios and shortcuts' row -->
<div class="flex flex-col" style="width: 100%">
<h2>Player UI options</h2>
<div class="flex flex-col compact-form">
<div class="field">
<div class="label">Enable in-player UI</div>
<input
type="checkbox"
v-model="settings.active.ui.inPlayer.enabled"
@change="saveSettings()"
/>
</div>
<div
class="flex flex-col field-group compact-form"
:class="{disabled: !settings.active.ui.inPlayer.enabled}"
>
<div class="field">
<div class="label">Enable only in full screen</div>
<input type="checkbox" v-model="settings.active.ui.inPlayer.enabledFullscreenOnly" />
</div>
<div class="field disabled">
<div class="label">
Popup activator position:
</div>
<div class="select">
<select
v-model="settings.active.ui.inPlayer.popupAlignment"
@change="saveSettings()"
>
<option value="left">Left</option>
<option value="right">Right</option>
</select>
</div>
</div>
<div class="field">
<div class="label">
Activate in-player UI:
</div>
<div class="select">
<select
v-model="settings.active.ui.inPlayer.activation"
@change="saveSettings()"
>
<option value="player">
When mouse hovers over player
</option>
<option value="trigger-zone">
When mouse hovers over trigger zone
</option>
</select>
</div>
</div>
<div class="field" :class="{'disabled': settings.active.ui.inPlayer.activation !== 'trigger-zone'}">
<div class="label">Edit trigger zone:</div>
<button @click="startTriggerZoneEdit()">Edit</button>
</div>
<div class="field">
<div class="label">
Do not show in-player UI when video player is narrower than (% of screen width)
</div>
<div class="input range-input">
<input
:value="settings.active.ui.inPlayer.minEnabledWidth"
class="slider"
type="range"
min="0"
max="1"
step="0.01"
@input="(event) => setPlayerRestrictions('minEnabledWidth', event.target.value)"
@change="(event) => saveSettings()"
>
<input
:value="ghettoComputed.minEnabledWidth"
@input="(event) => setPlayerRestrictions('minEnabledWidth', event.target.value, true)"
@change="(event) => saveSettings(true)"
>
</div>
</div>
<div class="field">
<div class="label">
Do not show in-player UI when video player is shorter than (% of screen width)
</div>
<div class="input range-input">
<input
:value="settings.active.ui.inPlayer.minEnabledHeight"
class="slider"
type="range"
min="0"
max="1"
step="0.01"
@input="(event) => setPlayerRestrictions('minEnabledHeight', event.target.value)"
@change="(event) => saveSettings()"
>
<input
:value="ghettoComputed.minEnabledHeight"
@input="(event) => setPlayerRestrictions('minEnabledHeight', event.target.value, true)"
@change="(event) => saveSettings(true)"
>
</div>
</div>
</div>
</div>
<h2 class="mt2r">Menu options and keyboard shortcuts</h2>
<div>
Click 'add new' to add a new option. Click a button to edit or remove the keyboard shortcut.
</div>
<div class="keyboard-settings">
<!-- CROP OPTIONS -->
<div>
<div class="flex flex-row">
<h3 class="mth3">CROP OPTIONS</h3>
</div>
<CropOptionsPanel
:settings="settings"
:eventBus="eventBus"
:isEditing="true"
>
</CropOptionsPanel>
</div>
<!-- STRETCH OPTIONS -->
<div>
<div class="flex flex-row">
<h3 class="mth3">STRETCH OPTIONS</h3>
</div>
<StretchOptionsPanel
:settings="settings"
:eventBus="eventBus"
:isEditing="true"
></StretchOptionsPanel>
</div>
</div>
</div>
</div>
</template>
<script>
import Button from '../components/Button.vue'
import BrowserDetect from '../../../ext/conf/BrowserDetect';
import CropOptionsPanel from './PanelComponents/VideoSettings/CropOptionsPanel.vue'
import StretchOptionsPanel from './PanelComponents/VideoSettings/StretchOptionsPanel.vue'
export default {
components: {
Button,
CropOptionsPanel,
StretchOptionsPanel
},
data() {
return {
ghettoComputed: { }
}
},
mixins: [
],
props: [
'settings', // required for buttons and actions, which are global
'eventBus',
],
mounted() {
this.ghettoComputed = {
minEnabledWidth: this.optionalToFixed(this.settings.active.ui.inPlayer.minEnabledWidth * 100, 0),
minEnabledHeight: this.optionalToFixed(this.settings.active.ui.inPlayer.minEnabledHeight * 100, 0),
}
},
methods: {
forcePositiveNumber(value) {
// Change EU format to US if needed
// | remove everything after second period if necessary
// | | | remove non-numeric characters
// | | | |
return value.replaceAll(',', '.').split('.', 2).join('.').replace(/[^0-9.]/g, '');
},
optionalToFixed(v, n) {
if ((`${v}`.split('.')[1]?.length ?? 0) > n) {
return v.toFixed(n);
}
return v;
},
setPlayerRestrictions(key, value, isTextInput) {
if (isTextInput) {
value = (+this.forcePositiveNumber(value) / 100);
}
if (isNaN(+value)) {
value = 0.5;
}
this.settings.active.ui.inPlayer[key] = value;
if (isTextInput) {
this.ghettoComputed[key] = this.optionalToFixed(value, 0);
} else {
this.ghettoComputed[key] = this.optionalToFixed(value * 100, 0);
}
},
saveSettings(forceRefresh) {
this.settings.saveWithoutReload();
if (forceRefresh) {
this.$nextTick( () => this.$forceRefresh() );
}
},
startTriggerZoneEdit() {
this.eventBus.send('start-trigger-zone-edit');
},
async openOptionsPage() {
BrowserDetect.runtime.openOptionsPage();
},
}
}
</script>
<style lang="scss" src="../../res/css/flex.scss" scoped module></style>
<style lang="scss" src="../res-common/panels.scss" scoped module></style>
<style lang="scss" src="../res-common/common.scss" scoped module></style>
<style lang="scss" scoped>
.justify-center {
justify-content: center;
}
.items-center {
align-items: center;
}
.mt-4{
margin-top: 1rem;
}
.input {
max-width: 24rem;
}
.range-input {
display: flex;
flex-direction: row;
* {
margin-left: 0.5rem;
margin-right: 0.5rem;
}
input {
max-width: 5rem;
}
input[type=range] {
max-width: none;
}
}
.trigger-zone-editor {
background-color: rgba(0,0,0,0.25);
padding-bottom: 2rem;
.field {
margin-bottom: -1em;
}
}
.disabled {
pointer-events: none;
/* color: #666; */
filter: contrast(50%) brightness(40%) grayscale(100%);
}
.compact-form {
> .field, > .field-group {
margin-top: 0;
margin-bottom: 0;
}
}
.keyboard-settings {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 1rem;
> * {
width: calc(50% - 0.5rem);
}
}
.mt2r {
margin-top: 2rem;
margin-bottom: 0.5rem;
}
.mth3 {
margin-top: 1.5rem;
}
</style>

View File

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

View File

@ -0,0 +1,157 @@
<template>
<div class="panel-root">
<h1>Resizer debug data</h1>
<div class="flex flex-row flex-wrap">
<div class="panel">
<h3 class="panel-title">
Player info
</h3>
<div class="data-item">
<div class="data-title">Window size:
<small>(Inner size)</small>
</div>
<div class="data">{{windowWidth}} x {{windowHeight}}</div>
<div class="button" @click="refreshWindowSize()">
<!-- <Icon icon="arrow-clockwise"></Icon> -->
Refresh</div>
</div>
<div class="data-item">
<div class="data-title">Player dimensions:</div>
<div class="data">{{debugData?.resizer?.playerData?.dimensions?.width ?? 'not detected'}} x {{debugData?.resizer?.playerData?.dimensions?.height ?? 'not detected'}}</div>
</div>
<div class="data-item">
<div class="data-title">Is fullscreen?</div>
<div class="data">{{debugData?.resizer?.playerData?.dimensions?.fullscreen ?? 'unknown'}}</div>
</div>
<div class="data-item">
<div class="data-title">Player id | classlist</div>
<div class="data">{{debugData?.resizer?.playerData?.elementId || '<no ID>'}} | {{debugData?.resizer?.playerData?.classList || '<no classes>'}}</div>
</div>
</div>
<!-- Stream info -->
<div class="panel">
<h3 class="panel-title">
Stream info
</h3>
<div class="data-item">
<div class="data-title">
Stream dimensions: <small>(Native size of the video)</small>
</div>
<div class="data">
{{debugData?.resizer?.videoRawData?.streamDimensions.x}} x {{debugData?.resizer?.videoRawData?.streamDimensions?.y}}
</div>
</div>
<div class="data-item">
<div class="data-title">
Stream displayed dimensions: <small>(Video file is being upscaled to this size)</small>
</div>
<div class="data">
{{debugData?.resizer?.videoRawData?.displayedSize?.x.toFixed()}} x {{debugData?.resizer?.videoRawData?.displayedSize?.y.toFixed()}}
</div>
</div>
<div class="data-item">
<div class="data-title">Video element native size: <small>(Size of the html element. Should be same as above most of the time!)</small></div>
<div class="data">{{debugData?.resizer?.videoRawData?.displayedSize?.x.toFixed(1)}} x {{debugData?.resizer?.videoRawData?.displayedSize?.y.toFixed(1)}}</div>
</div>
<div class="data-item">
<div class="data-title">Size difference to player (raw): <small>(positive numbers: video element is bigger than player element)</small></div>
<div class="data">x: {{debugData?.resizer?.sizeDifferenceToPlayer?.beforeZoom?.wdiff.toFixed(1)}}; &nbsp; &nbsp; y: {{debugData?.resizer?.sizeDifferenceToPlayer?.beforeZoom?.hdiff.toFixed(1)}}</div>
</div>
<div class="data-item">
<div class="data-title">Video element size (post zoom): <small>(Size of the html element after transform:scale is applied. Or at least, that's what Resizer::computeOffsets() thinks the final size is.)</small></div>
<div class="data">{{debugData?.resizer?.transformedSize?.x.toFixed(2)}} x {{debugData?.resizer?.transformedSize?.y.toFixed(2)}}</div>
</div>
<div class="data-item">
<div class="data-title"><b>Size difference to player (post-zoom):</b> <small>(same as above, except after cropping, stretching, panning and zoom are applied)</small></div>
<div class="data">x: {{debugData?.resizer?.sizeDifferenceToPlayer?.afterZoom?.wdiff.toFixed(2)}}; &nbsp; &nbsp; y: {{debugData?.resizer?.sizeDifferenceToPlayer?.afterZoom?.hdiff.toFixed(2)}}</div>
</div>
</div>
<!-- Transform info -->
<div class="panel">
<h3 class="panel-title">
Transformations
</h3>
<div class="data-item">
<div class="data-title">Alignment: <small>(I agree that 'left' and 'right' are both evil, but that's not the kind of alignments we're thinking of)</small></div>
<div class="data">{{debugData?.resizer?.videoTransform?.alignment || '<unknown>'}}</div>
</div>
<div class="data-item">
<div class="data-title">Scale factor</div>
<div class="data">x: {{debugData?.resizer?.videoTransform?.scale.x.toFixed(2)}}; &nbsp; &nbsp; y: {{debugData?.resizer?.videoTransform?.scale.y.toFixed(2)}}</div>
</div>
<div class="data-item">
<div class="data-title">Translation</div>
<div class="data">x: {{debugData?.resizer?.videoTransform?.translate.x.toFixed(2)}}; &nbsp; &nbsp; y: {{debugData?.resizer?.videoTransform?.translate.y.toFixed(2)}}</div>
</div>
</div>
</div>
<!-- <div class="uw-debug-info flex"> -->
<!-- <pre> -->
<!-- {{debugDataPrettified}} -->
<!-- </pre> -->
<!-- </div> -->
</div>
</template>
<script>
// import Icon from '../../common/components/Icon';
export default {
components: {
// Icon,
},
props: {
debugData: Object
},
data() {
return {
windowWidth: window.innerWidth,
windowHeight: window.innerHeight
};
},
methods: {
refreshWindowSize() {
this.windowWidth = window.innerWidth;
this.windowHeight = window.innerHeight;
},
}
}
</script>
<style lang="scss" scoped>
@import '../../../res/css/uwui-base.scss';
@import '../../../res/css/colors.scss';
@import '../../../res/css/font/overpass.css';
@import '../../../res/css/font/overpass-mono.css';
@import '../../../res/css/common.scss';
.panel-root {
display: block;
padding: 16px;
}
.panel {
display: inline-block;
width: 420px;
max-width: 100%;
padding: 12px 6px;
}
.data-item {
display: block;
margin-bottom: 0.69rem;
}
.data-title {
small {
display: block;
opacity: 0.69;
}
}
.data {
color: $primary-color;
}
</style>

View File

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

View File

@ -0,0 +1,192 @@
<template>
<div class="flex flex-col" style="position: relative; width: 100%;">
<!-- 'Change UI' options is a tiny bit in upper right corner. -->
<div
class="options-bar flex flex-row"
:class="{isEditing: editMode}"
>
<template v-if="editMode">
<div style="height: 100%; display: flex; flex-direction: column; justify-content: center; flex: 0 0; padding-right: 8px;">
<mdicon name="alert" size="32" />
</div>
<div class="flex-grow">
You are currently editing options and shortcuts.<br/>
<b>NOTE: changes will take effect after page reload.</b>
</div>
<div
class="flex-nogrow flex-noshrink"
@click="editMode = !editMode"
>
Exit edit mode
</div>
</template>
<template v-else>
<div class="flex-grow"></div>
<div
class=""
@click="editMode = !editMode"
>
Edit ratios and shortcuts
</div>
</template>
</div>
<!-- The rest of the tab is under 'edit ratios and shortcuts' row -->
<div class="flex flex-row flex-wrap" style="width: 100%">
<div class="flex flex-col">
<!-- CROP OPTIONS -->
<div v-if="settings" class="sub-panel">
<div class="flex flex-row">
<mdicon name="crop" :size="32" />
<h1>Crop video:</h1>
</div>
<CropOptionsPanel
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
:isEditing="editMode"
>
</CropOptionsPanel>
</div>
<!-- STRETCH OPTIONS -->
<div v-if="settings" class="sub-panel">
<div class="flex flex-row">
<mdicon name="stretch-to-page-outline" :size="32" />
<h1>Stretch video:</h1>
</div>
<StretchOptionsPanel
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
:isEditing="editMode"
></StretchOptionsPanel>
</div>
</div>
<div class="flex flex-col">
<!-- VIDEO ALIGNMENT -->
<div class="sub-panel">
<div class="flex flex-row">
<mdicon name="align-horizontal-center" :size="32" />
<h1>Video alignment:</h1>
</div>
<div class="flex flex-row justify-center mt-4">
<alignment-options-control-component
:eventBus="eventBus"
>
</alignment-options-control-component>
</div>
<!-- <div class="flex flex-row flex-wrap">
<div class="m-t-0-33em display-block">
<input id="_input_zoom_site_allow_pan"
type="checkbox"
/>
Pan with mouse
</div>
</div> -->
</div>
<!-- ZOOM OPTIONS -->
<!-- <div class="sub-panel">
<div class="flex flex-row">
<mdicon name="magnify-plus-outline" :size="32" />
<h1>Manual zoom:</h1>
</div>
<ZoomOptionsPanel
:settings="settings"
:siteSettings="siteSettings"
:eventBus="eventBus"
:isEditing="editMode"
></ZoomOptionsPanel>
</div> -->
</div>
</div>
</div>
</template>
<script>
import ZoomOptionsPanel from './PanelComponents/VideoSettings/ZoomOptionsPanel.vue'
import CropOptionsPanel from './PanelComponents/VideoSettings/CropOptionsPanel'
import StretchOptionsPanel from './PanelComponents/VideoSettings/StretchOptionsPanel'
import Button from '../components/Button.vue'
import ShortcutButton from '../components/ShortcutButton';
import EditShortcutButton from '../components/EditShortcutButton';
import ComputeActionsMixin from '../mixins/ComputeActionsMixin';
import BrowserDetect from '../../../ext/conf/BrowserDetect';
import AlignmentOptionsControlComponent from './AlignmentOptionsControlComponent.vue';
import CommsMixin from '../utils/CommsMixin';
export default {
data() {
return {
exec: null,
scope: 'page',
editMode: false,
resizerConfig: {
crop: null,
stretch: null,
zoom: null,
pan: null
}
}
},
mixins: [
ComputeActionsMixin,
CommsMixin,
],
props: [
'settings', // required for buttons and actions, which are global
'siteSettings',
'frame',
'eventBus',
'site'
],
created() {
this.eventBus.subscribe('uw-config-broadcast', {function: (config) => this.handleConfigBroadcast(config)});
},
mounted() {
this.eventBus.sendToTunnel('get-ar');
},
components: {
ShortcutButton,
EditShortcutButton,
Button,
AlignmentOptionsControlComponent,
StretchOptionsPanel,
CropOptionsPanel, ZoomOptionsPanel
},
methods: {
async openOptionsPage() {
BrowserDetect.runtime.openOptionsPage();
},
}
}
</script>
<style lang="scss" src="../../res/css/flex.scss" scoped module></style>
<style lang="scss" src="../res-common/panels.scss" scoped module></style>
<style lang="scss" src="../res-common/common.scss" scoped module></style>
<style lang="scss" scoped>
.justify-center {
justify-content: center;
}
.items-center {
align-items: center;
}
.mt-4{
margin-top: 1rem;
}
</style>

View File

@ -0,0 +1,17 @@
<template>
<div>
</div>
</template>
<script>
export default {
props: {
siteSettings: Object,
hasDrm: Boolean,
problems: Object,
}
}
</script>
<style lang="scss">
</style>

View File

@ -12,9 +12,9 @@
</div>
</div>
<div class="command-details">
<div class="flex flex-column cmd-container cd-pad">
<div class="flex flex-col cmd-container cd-pad">
<div class="flex bold">
Command:
Command:
</div>
<div class="flex cmdlist">
{{parseCommand(action.cmd)}}
@ -22,7 +22,7 @@
</div>
<!-- SCOPES -->
<div class="flex flex-column scopes-container cd-pad">
<div class="flex flex-col scopes-container cd-pad">
<div class="flex bold">Popup tabs:</div>
<!-- global scope -->
@ -151,99 +151,97 @@ export default {
<style lang="scss" scoped>
@import '../../res/css/colors.scss';
.uw-ultrawidify-container-root {
.action {
cursor: pointer;
position: relative;
box-sizing: border-box;
}
.action {
cursor: pointer;
position: relative;
box-sizing: border-box;
}
.action .command-details {
height: 0px;
max-height: 0px;
transition: max-height 0.5s ease;
overflow: hidden;
transition: height 0.5s ease;
position: absolute;
top: 0;
right: 0;
width: 50%;
}
.action .command-details {
height: 0px;
max-height: 0px;
transition: max-height 0.5s ease;
overflow: hidden;
transition: height 0.5s ease;
position: absolute;
top: 0;
right: 0;
width: 50%;
}
.action:hover .command-details {
height: auto;
max-height: 200px;
transition: max-height 0.5s ease;
}
.action:hover .command-details {
height: auto;
max-height: 200px;
transition: max-height 0.5s ease;
}
.command-details {
position: absolute;
top: 0;
right: 0;
width: 50%;
}
.command-details {
position: absolute;
top: 0;
right: 0;
width: 50%;
}
.action-name-cmd-container, .p1rem {
padding: 1rem;
}
.action-name-cmd-container, .p1rem {
padding: 1rem;
}
.cd-pad {
padding: 0.5em;
}
.cd-pad {
padding: 0.5em;
}
.action-name {
font-size: 1.5rem;
font-weight: 300;
color: $text-normal;
width: 50%;
}
.action-name {
font-size: 1.5rem;
font-weight: 300;
color: $text-normal;
width: 50%;
}
.action-name:hover, .action:hover .action-name {
color: lighten($primary-color, 20%);
}
.action-name:hover, .action:hover .action-name {
color: lighten($primary-color, 20%);
}
.red {
color: $primary-color !important;
}
.red {
color: $primary-color !important;
}
.cmd-container {
width: 13.37rem;
}
.cmd-container {
width: 13.37rem;
}
.cmdlist {
font-family: 'Overpass Mono';
font-size: 0.9rem;
color: $text-dim;
}
.cmdlist {
font-family: 'Overpass Mono';
font-size: 0.9rem;
color: $text-dim;
}
.bold {
font-weight: 600;
}
.bold {
font-weight: 600;
}
.scope-scope {
width: 5rem;
text-align: right !important;
color: $secondary-color;
}
.scope-scope {
width: 5rem;
text-align: right !important;
color: $secondary-color;
}
.scope-visible {
width: 7rem;
}
.scope-visible {
width: 7rem;
}
.scope-button-label {
width: 16rem;
}
.scope-button-label {
width: 16rem;
}
.scope-row-label {
color: $text-dark;
}
.scope-row-label {
color: $text-dark;
}
.scope-row-highlight {
color: $text-normal;
}
.scope-row-highlight {
color: $text-normal;
}
.transparent {
opacity: 0;
}
.transparent {
opacity: 0;
}
</style>

View File

@ -0,0 +1,43 @@
<template>
<div class="button center-text"
:class="{'setting-selected': selected }"
>
<div class="label">
{{label}}
</div>
</div>
</template>
<script>
export default {
props: {
fixedWidth: Boolean,
selected: Boolean,
label: String,
icon: String,
}
}
</script>
<style lang="scss" scoped>
.button {
display: flex;
flex-direction: row;
flex-wrap: none;
align-items: center;
.icon {
margin-right: 8px;
flex-grow: 0;
flex-shrink: 0;
}
.label {
text-align: center;
flex-grow: 1;
flex-shrink: 1;
}
}
</style>

View File

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

View File

@ -0,0 +1,118 @@
<template>
<div class="context-container" @mouseleave="hideContextMenu()">
<GhettoContextMenuItem
class="activator uw-clickable"
:css="{
'expand-left': alignment === 'left',
'expand-right': alignment === 'right',
}"
@click="showContextMenu()"
@mouseenter="showContextMenu()"
>
<slot name="activator"></slot>
</GhettoContextMenuItem>
<div
v-if="contextMenuVisible"
class="context-menu uw-clickable"
:class="{
'menu-left': alignment === 'left',
'menu-right': alignment === 'right'
}"
@mouseleave="hideContextMenu()"
>
<slot></slot>
</div>
</div>
</template>
<script>
import GhettoContextMenuItem from './GhettoContextMenuItem.vue';
export default {
components: {
GhettoContextMenuItem,
},
props: {
alignment: String,
},
data() {
return {
contextMenuVisible: false,
contextMenuHideTimeout: undefined,
}
},
methods: {
showContextMenu() {
this.contextMenuVisible = true;
},
hideContextMenu() {
this.contextMenuHideTimeout = setTimeout( () => {
this.contextMenuVisible = false;
}, 50);
}
}
}
</script>
<style lang="scss" scoped>
.context-container {
position: relative;
}
.context-menu-wrapper {
position: relative;
}
.context-menu {
position: absolute;
display: flex;
flex-direction: column;
min-width: 5rem;
top: 50%;
transform: translateY(-50%);
}
.menu-left {
right: 100%;
}
.menu-right {
left: 100%;
}
</style>
<style lang="scss">
.activator {
position: relative;
padding: 1rem 1.6rem;
font-size: .95rem;
padding: 1rem 1.6rem;
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(16px) saturate(120%);
white-space: nowrap;
&:hover {
background-color: rgba(255, 128, 64, 0.5);
}
&.expand-left {
padding-left: 2.2rem;
}
&.expand-right {
padding-right: 2.2rem;
}
&.expand-left::before,
&.expand-right::after {
position: absolute;
top: 50%;
transform: translateY(-50%);
font-size: 0.88rem;
}
&.expand-left::before {
content: '⮜';
left: 0.5rem;
}
&.expand-right::after {
content: '⮞';
right: 0.5rem;
}
}
</style>

View File

@ -0,0 +1,45 @@
<template>
<div
class="flex flex-col flex-center center-text item"
:class="{
'can-hover': !disableHover,
...(css ? css : {}),
}"
>
<slot></slot>
</div>
</template>
<script>
export default {
props: {
label: String,
shortcut: String,
childAlignment: String,
css: Object,
disableHover: Boolean,
}
}
</script>
<style lang="scss" scoped>
.item {
font-size: .95rem;
font-family: 'Overpass';
padding: 1rem 1.6rem;
background-color: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(16px) saturate(120%);
white-space: nowrap;
&.can-hover:hover {
background-color: rgba(0,0,0,0.8);
border-bottom: 1px solid #fa6;
}
display: flex;
flex-direction: column;
}
</style>

View File

@ -0,0 +1,26 @@
<template>
<div>
<GhettoContextMenuItem>
<template v-if="label">
{{label}} {{shortcut ? `(${shortcut})` : ''}}
</template>
<template v-else>
<slot></slot>
</template>
</GhettoContextMenuItem>
</div>
</template>
<script>
import GhettoContextMenuItem from './GhettoContextMenuItem.vue';
export default {
components: {
GhettoContextMenuItem,
},
props: {
label: String,
shortcut: String,
}
}
</script>

View File

@ -1,8 +1,8 @@
<template>
<div class="flex flex-column json-level-indent">
<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>
"{{value_internal.key}}" <b>:</b>
<span v-if="!expanded_internal"><b> [</b> ... <b>]</b>,</span>
<template v-else><b>[</b></template>
</div>

Some files were not shown because too many files have changed in this diff Show More