Compare commits

..

297 commits
dev ... master

Author SHA1 Message Date
Abdulmohsen
c68646e266
Merge pull request #621 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
fix: setting simple_mode cookie was crashing http layer
2026-06-19 19:59:35 +03:00
Abdulmohsen
0ba52fa38e
Merge pull request #620 from arabcoders/dev
Refactor: re-design simple mode
2026-06-19 18:50:15 +03:00
Abdulmohsen
44f5f2eee0
Merge pull request #618 from arabcoders/dev
chore: use ty as type checker.
2026-06-14 19:35:40 +03:00
Abdulmohsen
aedeaf9a0a
Merge pull request #617 from arabcoders/dev
Feat: paginate queue
2026-06-14 18:29:27 +03:00
Abdulmohsen
a614a48108
Merge pull request #615 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
fix: breaking change between yt-dlp 2026.03.17 -> 2026.06.09
2026-06-11 18:03:12 +03:00
Abdulmohsen
9ada8b5a91
Merge pull request #613 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
fix: video progress bar on iOS would fail
2026-06-10 16:53:16 +03:00
Abdulmohsen
a194b9bf28
Merge pull request #610 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Refactor: minor improvements to the WebUI
2026-06-08 10:08:19 +03:00
Abdulmohsen
6df1250a43
Merge pull request #609 from arabcoders/dev
feat: make log level runtime configurable
2026-05-31 21:07:00 +03:00
Abdulmohsen
ef8642ace7
Merge pull request #608 from arabcoders/dev
feat: add system diagnostics endpoint.
2026-05-26 19:49:47 +03:00
Abdulmohsen
8e14258c7a
Merge pull request #607 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Fix: recent change made fetch_info unpicklable
2026-05-24 22:07:41 +03:00
Abdulmohsen
f65cdddbfe
Merge pull request #604 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Refactor: use item guid as thumbnail name for easier cleanup
2026-05-15 19:40:35 +03:00
Abdulmohsen
cee3bcf48d
Merge pull request #603 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
feat: thumbnail generation
2026-05-14 23:34:00 +03:00
Abdulmohsen
e7772b361e
Merge pull request #602 from arabcoders/dev
Refactor: do not log httpx related as exceptions but errors.
2026-05-13 11:37:28 +03:00
Abdulmohsen
d3a3b5d95f
Merge pull request #601 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
feat: add generic browser extractor
2026-05-08 01:15:59 +03:00
Abdulmohsen
522e6a75ef
Merge pull request #600 from arabcoders/dev
fix: Handle TimeoutError in fetch_info to prevent double wait
2026-05-07 21:54:47 +03:00
Abdulmohsen
6cf2eb053c
Merge pull request #599 from arabcoders/dev
refactor: improve video player rendering
2026-05-05 23:42:24 +03:00
Abdulmohsen
b35ac1831a
Merge pull request #598 from arabcoders/dev
feat: extra placeholders for output template
2026-05-05 10:53:05 +03:00
Abdulmohsen
cf4386e1a5
Merge pull request #594 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
feat: add support for ignoring conditions in downloads and UI
2026-04-28 01:21:27 +03:00
Abdulmohsen
cb1c355e58
Merge pull request #593 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
refactor: re-add the automatic history update after download
2026-04-26 20:26:51 +03:00
Abdulmohsen
4ee7537733
Merge pull request #592 from arabcoders/dev
fix: Slowness in deleting big number of items
2026-04-25 01:42:01 +03:00
Abdulmohsen
fdc08989ae
Merge pull request #591 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Fix pagination UI and refactor presets layout
2026-04-22 20:01:40 +03:00
Abdulmohsen
ed77ea508c
Merge pull request #590 from arabcoders/dev
Refactor: Seperate history items from queue
2026-04-21 23:24:42 +03:00
Abdulmohsen
8729f030c7
Merge pull request #589 from arabcoders/dev
feat(console): make terminal window more durable
2026-04-14 17:32:17 +03:00
Abdulmohsen
0eabc2197b
Merge pull request #588 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
fix: better partial live download handling
2026-04-11 19:27:44 +03:00
Abdulmohsen
28ec496430
Merge pull request #587 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
fix: improve the native build
2026-04-10 20:11:51 +03:00
Abdulmohsen
2139e20332
Merge pull request #586 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
minor bug fixes and overall ui improvements
2026-04-10 18:33:29 +03:00
Abdulmohsen
1087596486
Merge pull request #584 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
fix: video player responsiveness
2026-04-09 00:42:56 +03:00
Abdulmohsen
dbf1f59efb
Merge pull request #583 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Improve MiniFilter tokenization
2026-03-31 19:15:34 +03:00
Abdulmohsen
235438af51
Merge pull request #581 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
refactor: minor fixes for better mobile view.
2026-03-27 00:20:49 +03:00
Abdulmohsen
4bea8a315a
Merge pull request #579 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Feat: Rebase the UI on top of nuxt/ui
2026-03-25 23:48:31 +03:00
Abdulmohsen
932f51c583
Merge pull request #576 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
chore: update external packages.
2026-03-18 21:46:27 +03:00
Abdulmohsen
2f435a3a64
Merge pull request #574 from arabcoders/dev
chore: update external packages
2026-03-09 21:19:22 +03:00
Abdulmohsen
b462048679
Merge pull request #573 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
chore: update dependancies
2026-03-03 22:39:05 +03:00
Abdulmohsen
ffbd82096c
Merge pull request #572 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
chore: update external packages
2026-02-22 22:38:43 +03:00
Abdulmohsen
705973219c
Merge pull request #570 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
refactor: move frontend to bun
2026-02-20 23:38:49 +03:00
Abdulmohsen
d9c9a85939
Merge pull request #569 from arabcoders/dev
feat: improve task creation to support multiple URLs.
2026-02-17 21:02:09 +03:00
Abdulmohsen
0c7ebb66ed
Merge pull request #567 from arabcoders/dev
fix: forcibly register the NFOMaker PP
2026-02-14 20:32:56 +03:00
Abdulmohsen
c36295d76f
Merge pull request #566 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
fix: improve exception handling in status tracker
2026-02-13 22:07:05 +03:00
Abdulmohsen
ee0942b37a
Merge pull request #562 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
fix: update import path for fetch_info in HandleTask class. Closes #561
2026-02-11 18:58:15 +03:00
Abdulmohsen
1cc6172862
Merge pull request #560 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
2026-02-08 11:11:05 +03:00
Abdulmohsen
0473c86fd6
Merge pull request #557 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
fix: use yt-dlp[default] for upgrading logic
2026-02-05 18:08:35 +03:00
Abdulmohsen
d17aa6c29e
Merge pull request #553 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Fix: sidecar loading for history items
2026-01-28 18:07:57 +03:00
Abdulmohsen
8659f74a3b
Merge pull request #552 from arabcoders/dev
Fix: offload info extraction to ProcessPoolExecutor
2026-01-27 11:53:15 +03:00
Abdulmohsen
412ad9565d
Merge pull request #549 from arabcoders/dev
Refactor: migrate from file-based config to db model
2026-01-25 21:42:20 +03:00
Abdulmohsen
be6b68efdd
Merge pull request #542 from arabcoders/dev 2026-01-14 23:16:23 +03:00
Abdulmohsen
3b49678272
Merge pull request #541 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Fix: preset export was exporting cookies due to inverted check.
2026-01-11 21:36:42 +03:00
Abdulmohsen
2435144cb1
Merge pull request #540 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
feat: support using output template for metadata generation.
2026-01-08 20:05:47 +03:00
Abdulmohsen
1189d31f5c
Merge pull request #538 from arabcoders/dev
Feat: add flag to ignore already archived items from showing in download history.
2026-01-07 18:03:29 +03:00
Abdulmohsen
163be08803
Merge pull request #537 from arabcoders/dev
Refactor: more UX consistency changes
2026-01-06 18:02:40 +03:00
Abdulmohsen
0a70c02b3e
Merge pull request #536 from arabcoders/dev
Refactor: add priority to presets
2026-01-05 19:02:10 +03:00
Abdulmohsen
d5ae882df0
Merge pull request #535 from arabcoders/dev
Refactor: add enabled field to notifications.
2026-01-04 23:37:50 +03:00
Abdulmohsen
9eb78ecdc0
Merge pull request #534 from arabcoders/dev
Refactor: add priority and description fields to conditions
2026-01-04 19:18:10 +03:00
Abdulmohsen
a924ddb718
Merge pull request #533 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Refactor: add enabled field to conditions
2026-01-04 17:30:15 +03:00
Abdulmohsen
67f3a24580
Merge pull request #531 from arabcoders/dev
Refactor: integrate cf solver for both yt-dlp and httpx
2026-01-02 20:34:48 +03:00
Abdulmohsen
b9421f442a
Merge pull request #530 from arabcoders/dev
Refactor: reduce cf_solver priority  to allow impersonate to work
2026-01-01 22:37:44 +03:00
Abdulmohsen
3b55c4d677
Merge pull request #529 from arabcoders/dev
Feat: CF Solver handler
2026-01-01 21:03:22 +03:00
Abdulmohsen
b8934d40eb
Merge pull request #528 from arabcoders/dev
Refactor: improve filtering capabilities in downloads page
2025-12-31 21:37:28 +03:00
Abdulmohsen
2f6dcbb4d4
Merge pull request #527 from arabcoders/dev
Refactor: playlists sometimes return different ID what we got
2025-12-30 23:28:41 +03:00
Abdulmohsen
18673f0a55
Merge pull request #526 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Refactor: stagger tasks creation for playlist processing
2025-12-30 20:50:44 +03:00
Abdulmohsen
714b6d95ba
Merge pull request #525 from arabcoders/dev 2025-12-29 23:07:07 +03:00
Abdulmohsen
c9ba2b0f4c
Merge pull request #524 from arabcoders/dev
Fix: prevent sending only space as download path
2025-12-28 23:20:34 +03:00
Abdulmohsen
3d14a50f07
Merge pull request #523 from arabcoders/dev
Feat: Introduce random delays to task handlers
2025-12-28 17:52:38 +03:00
Abdulmohsen
b5276feba0
Merge pull request #522 from arabcoders/dev
Fix: Dont Crash on download_path being set to root path
2025-12-28 00:15:30 +03:00
Abdulmohsen
54dedb6696
Merge pull request #521 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Refactor: switch native builds to onedir
2025-12-27 22:06:00 +03:00
Abdulmohsen
20e6c129ba
Merge pull request #517 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Some extractors dont work with --flat-playlist
2025-12-26 20:08:38 +03:00
Abdulmohsen
47ca5461f1
Merge pull request #515 from arabcoders/dev
Refactor: swap sqlite to aiosqlite and add queued db updates
2025-12-19 22:11:36 +03:00
Abdulmohsen
93ce7a730b
Merge pull request #514 from arabcoders/dev
Fix: performance regression while loading items.
2025-12-16 15:33:06 +03:00
Abdulmohsen
2214bc66de
Merge pull request #512 from arabcoders/dev
Feat: set_preset key for condition to switch preset based on yt-dlp data
2025-12-08 20:47:37 +03:00
Abdulmohsen
3ece619834
Merge pull request #507 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Drop download_archive for generic extractors
2025-12-04 20:21:28 +03:00
Abdulmohsen
af84a03ad1
Merge pull request #504 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
QoL: Support newlines as seperator for URLs.
2025-11-29 19:13:25 +03:00
Abdulmohsen
7e6b6f0544
Merge pull request #502 from arabcoders/dev
Feat: Add task handler for tver.jp
2025-11-25 21:10:05 +03:00
Abdulmohsen
91f527887c
Merge pull request #501 from arabcoders/dev
Refactor: Improve display of notifications to indicate severity
2025-11-22 21:07:43 +03:00
Abdulmohsen
69dc19ad4f
Merge pull request #500 from arabcoders/dev
Refactor: move static serving to our internal implementation
2025-11-19 21:24:17 +03:00
Abdulmohsen
9272e0b173
Merge pull request #498 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Fix: regrassion in tasks due change in how we handle event loop
2025-11-19 02:57:16 +03:00
Abdulmohsen
c6d89dde9c
Merge pull request #497 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
2025-11-19 01:42:59 +03:00
Abdulmohsen
89a92028e0
Merge pull request #496 from arabcoders/dev
Fix: Not being able to download subtitles.
2025-11-18 22:56:14 +03:00
Abdulmohsen
70d015a2a5
Merge pull request #495 from arabcoders/dev
Feat: Make it possible to enable/disable task
2025-11-18 19:39:33 +03:00
Abdulmohsen
d13ac83425
Merge pull request #492 from arabcoders/dev
Refactor: change how clear in/completed buttons behave
2025-11-16 20:07:50 +03:00
Abdulmohsen
83a416ec4c
Merge pull request #491 from arabcoders/dev
Refactor: Use tab design for index page
2025-11-15 22:12:25 +03:00
Abdulmohsen
477dc41bb6
Merge pull request #489 from arabcoders/dev
Feat: File browser controls improvements
2025-11-14 20:17:14 +03:00
Abdulmohsen
59fe171a9d
Merge pull request #488 from arabcoders/dev
Add app manifest to allow the app to be installed
2025-11-13 23:36:14 +03:00
Abdulmohsen
46a3abebde
Merge pull request #487 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
revert: Revert changes to video player, it was causing issue with sid…
2025-11-13 00:21:35 +03:00
Abdulmohsen
b94d62accd
Merge pull request #486 from arabcoders/dev
Fix: add yt-dlp-ejs
2025-11-12 23:55:47 +03:00
Abdulmohsen
d4c14c7991
Merge pull request #485 from arabcoders/dev
Refactor: refactor postprocessor into correct directory
2025-11-12 22:06:20 +03:00
Abdulmohsen
c1b9034ef8
Merge pull request #483 from arabcoders/dev
Feat: add drop file support for cookies field.
2025-11-12 18:59:49 +03:00
Abdulmohsen
8ff6582316
Merge pull request #482 from arabcoders/dev
Fix: handle errors when fetching remote thumbnails
2025-11-11 23:45:47 +03:00
Abdulmohsen
d1a20b3972
Merge pull request #481 from arabcoders/dev
Strip out comment lines when creating yt-dlp command
2025-11-10 18:29:25 +03:00
Abdulmohsen
f0b5e20af8
Merge pull request #480 from arabcoders/dev
[FIX] implement --max-downloads arg. Ref #479
2025-11-10 00:35:48 +03:00
Abdulmohsen
8273847d46
Merge pull request #478 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
[FIX] Chromium browser does not render subtitles after HLS attach
2025-11-08 23:19:41 +03:00
Abdulmohsen
f4e5dfb2f6
Merge pull request #477 from arabcoders/dev
Minor fixes
2025-11-08 21:40:21 +03:00
Abdulmohsen
03e8166afe
Merge pull request #476 from arabcoders/dev
hide keyboard shortcuts on mobile
2025-11-07 00:26:30 +03:00
Abdulmohsen
1db5e26843
Merge pull request #473 from arabcoders/dev
[FEAT] Add keyboard shortcuts to built-in video player
2025-11-03 01:46:07 +03:00
Abdulmohsen
ee9e2a1d0b
Merge pull request #470 from arabcoders/dev
[FEAT] Add yt-dlp cli wrapper
2025-11-01 18:12:31 +03:00
Abdulmohsen
71153d6fb4
Merge pull request #468 from arabcoders/dev
[FEAT] Support running commands from newDownload into console.
2025-10-27 20:04:13 +03:00
Abdulmohsen
a81c73a469
Merge pull request #467 from arabcoders/dev
minor fixes to file listing, and unify how we present tips
2025-10-26 16:15:44 +03:00
Abdulmohsen
70c42252a6
Merge pull request #466 from arabcoders/dev
[FEAT] add grid display style for files
2025-10-24 20:06:28 +03:00
Abdulmohsen
477c7e7c98
Merge pull request #464 from arabcoders/dev
[FIX] Cancelling live streams. Closes #463
2025-10-24 00:47:11 +03:00
Abdulmohsen
b95b6eb6be
Merge pull request #460 from arabcoders/dev
Some checks failed
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
v1.0.2
2025-10-22 19:02:44 +03:00
Abdulmohsen
238deddb9d
Merge pull request #455 from arabcoders/dev
UI/UX improvements
2025-10-19 22:22:52 +03:00
Abdulmohsen
618ce4d387
Merge pull request #451 from arabcoders/dev
[FEAT] add custom download fields to simple mode.
2025-10-16 17:23:02 +03:00
Abdulmohsen
5bf97047af
Merge pull request #450 from arabcoders/dev
create new branch for testing ground
2025-10-16 15:58:57 +03:00
Abdulmohsen
2a13c47bb6
Merge pull request #444 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
v1.0.1
2025-10-15 19:22:47 +03:00
Abdulmohsen
f91811ad50
Merge pull request #440 from arabcoders/dev
Update bookmarklet URL and add note on mixed content
2025-10-13 19:24:28 +03:00
Abdulmohsen
f51b11c421
Merge pull request #438 from arabcoders/dev
Clarify standalone builds limitations
2025-10-12 21:17:55 +03:00
Abdulmohsen
fa4da50403
Merge pull request #436 from arabcoders/dev
Some checks failed
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
Fixes issue with simple view and background toggle
2025-10-11 21:21:48 +03:00
Abdulmohsen
851124a047
Merge pull request #434 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Prepare for v1.0 release.
2025-10-02 19:25:25 +03:00
Abdulmohsen
1103b93f00
Merge pull request #431 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Add experimental simple view mode
2025-09-27 21:05:58 +03:00
Abdulmohsen
8a852bcd24
Merge pull request #427 from arabcoders/dev
Bundles deno runtime
2025-09-24 22:39:50 +03:00
Abdulmohsen
b105749121
Merge pull request #424 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Added Web UI for task definitions & inspect handler
2025-09-23 19:49:09 +03:00
Abdulmohsen
dd3059d01e
Merge pull request #423 from arabcoders/dev
Use local downloaded thumbnail if available
2025-09-22 00:38:51 +03:00
Abdulmohsen
5c862eb613
Merge pull request #422 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
[FEAT] Add per extractor limits
2025-09-14 22:23:42 +03:00
Abdulmohsen
871f9e803e
Merge pull request #420 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
Support using GPU encoding for playback
2025-09-14 01:46:49 +03:00
Abdulmohsen
0de61566fb
Merge pull request #418 from arabcoders/dev
Some checks failed
native-build / build (amd64, ubuntu-latest) (push) Has been cancelled
native-build / build (amd64, windows-latest) (push) Has been cancelled
native-build / build (arm64, macos-latest) (push) Has been cancelled
native-build / build (arm64, ubuntu-latest) (push) Has been cancelled
native-build / build (arm64, windows-latest) (push) Has been cancelled
fix version tagging
2025-09-12 23:41:14 +03:00
Abdulmohsen
2323739c49
Merge pull request #417 from arabcoders/dev
Remove deprecated features and flags
2025-09-12 22:59:26 +03:00
Abdulmohsen
049b9a4e38
Merge pull request #416 from arabcoders/dev
Mostly CI improvements to include tests and linting
2025-09-12 03:02:03 +03:00
Abdulmohsen
c968ce3ed2
Merge pull request #415 from arabcoders/dev
updated external packages
2025-09-09 00:26:27 +03:00
Abdulmohsen
125a1a81a0
Merge pull request #414 from arabcoders/dev
Make it possible to ignore item download via conditions
2025-09-08 19:20:05 +03:00
Abdulmohsen
71ba8ea7f7
Merge pull request #411 from arabcoders/dev
renamed screenshot
2025-09-04 17:30:20 +03:00
Abdulmohsen
bb37f7b6ba
Merge pull request #410 from arabcoders/dev
Minor FAQ/README update
2025-09-04 17:28:57 +03:00
Abdulmohsen
2c7ba8e184
Merge pull request #409 from arabcoders/dev
Some checks failed
Build Native wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, windows-latest) (push) Has been cancelled
FEAT: new TwitchHandler
2025-09-04 00:02:29 +03:00
Abdulmohsen
ab8545392e
Merge pull request #408 from arabcoders/dev
Expose archiver as an API endpoint.
2025-09-03 19:37:59 +03:00
Abdulmohsen
c528e2b557
Merge pull request #405 from arabcoders/dev
Some checks failed
Build Native wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, windows-latest) (push) Has been cancelled
FEAT: Make it possible to limit notify target to specific presets.
2025-09-02 22:59:22 +03:00
Abdulmohsen
a5d91e58fa
Merge pull request #404 from arabcoders/dev
Cleanup user python packages dir after each version change.
2025-09-02 19:54:34 +03:00
Abdulmohsen
984d47e946
Merge pull request #402 from arabcoders/dev
Some checks failed
Build Native wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, windows-latest) (push) Has been cancelled
Enhance file browser API
2025-09-01 19:56:16 +03:00
Abdulmohsen
2a1cd0a632
Merge pull request #400 from arabcoders/dev
allow updating github release notes
2025-08-31 02:14:55 +03:00
Abdulmohsen
7185bae58d
Merge pull request #399 from arabcoders/dev
Some checks failed
Build Native wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, windows-latest) (push) Has been cancelled
test new build system
2025-08-31 01:52:57 +03:00
Abdulmohsen
c5e3899c26
Merge pull request #398 from arabcoders/dev
Some checks failed
Build Native wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, windows-latest) (push) Has been cancelled
Re-implemented Archive feature
2025-08-30 20:57:55 +03:00
Abdulmohsen
aaab5a72e8
Merge pull request #397 from arabcoders/dev
Major refactor
2025-08-30 02:29:02 +03:00
Abdulmohsen
3de3471bd5
Merge pull request #395 from arabcoders/dev
Some checks failed
Build Native wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, windows-latest) (push) Has been cancelled
Implement a workaround for slowness in status update
2025-08-27 01:02:17 +03:00
Abdulmohsen
8b9077d476
Merge pull request #393 from arabcoders/dev
Fix windows multiprocessing threads
2025-08-26 01:07:37 +03:00
Abdulmohsen
963fa5870c
Merge pull request #392 from arabcoders/dev
Some checks failed
Build Native wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, windows-latest) (push) Has been cancelled
restore pycryptodome
2025-08-26 00:31:42 +03:00
Abdulmohsen
b48daf11c8
Merge pull request #391 from arabcoders/dev
more fixes.
2025-08-26 00:22:20 +03:00
Abdulmohsen
c3d8aa8348
Merge pull request #390 from arabcoders/dev
followup fix
2025-08-25 23:41:50 +03:00
Abdulmohsen
1dd464d6cc
Merge pull request #389 from arabcoders/dev
Fix native builds.
2025-08-25 23:39:08 +03:00
Abdulmohsen
c91ca38035
Merge pull request #388 from arabcoders/dev
Some checks failed
Build Native wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build Native wrappers / build (arm64, windows-latest) (push) Has been cancelled
Remove webview in favor of using built-in browser.
2025-08-25 22:43:05 +03:00
Abdulmohsen
46725e6f8e
Merge pull request #387 from arabcoders/dev
Added support for pausing adding items from playlist.
2025-08-25 19:20:36 +03:00
Abdulmohsen
43e0128c3b
Merge pull request #383 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
upgrade curl_cffi to 0.13.x
2025-08-24 20:06:20 +03:00
Abdulmohsen
8473618726
Merge pull request #382 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Added better fallback to hls.
2025-08-23 21:44:50 +03:00
Abdulmohsen
d2ce9940e8
Merge pull request #380 from arabcoders/dev
Refactor: update basic mode watch logic to check config loading state
2025-08-22 18:22:59 +03:00
Abdulmohsen
4c9c9eab84
Merge pull request #379 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Added autocomplete for cli options
2025-08-20 19:37:34 +03:00
Abdulmohsen
b4505d2fcd
Merge pull request #378 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Added built-in reference for yt-dlp options.
2025-08-19 22:33:42 +03:00
Abdulmohsen
f6d1618c91
Merge pull request #377 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
bump playlist_items_concurrency to 4 by default
2025-08-16 20:29:11 +03:00
Abdulmohsen
650ba810a8
Merge pull request #376 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
fix breaking change to playlist handling.
2025-08-15 04:47:20 +03:00
Abdulmohsen
286d8ce818
Merge pull request #374 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Make playlist_index works
2025-08-14 12:30:41 +03:00
Abdulmohsen
2cdfa2503b
Merge pull request #373 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Update yt-dlp to 2025.8.11
2025-08-12 03:45:45 +03:00
Abdulmohsen
6aab6fd062
Merge pull request #370 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Make it possible to pin specific yt-dlp version
2025-08-09 18:39:35 +03:00
Abdulmohsen
3447ac8032
Merge pull request #368 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Disable opacity whenever an overlay is showing
2025-08-08 22:37:33 +03:00
Abdulmohsen
3f11262afb
Merge pull request #366 from arabcoders/dev
Custom Fields Feature
2025-08-08 00:02:45 +03:00
Abdulmohsen
ffd99e7c09
Merge pull request #365 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Fixed a bug in DownloadQueue
2025-08-05 18:15:45 +03:00
Abdulmohsen
4c54f80b87
Merge pull request #364 from arabcoders/dev
Implement a way to mark items from task as already downloaded.
2025-08-05 17:59:17 +03:00
Abdulmohsen
c310ae9abc
Merge pull request #361 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Add force download switch
2025-08-02 16:45:06 +03:00
Abdulmohsen
4c5ebf2f39
Merge pull request #359 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
fix windows build
2025-08-01 18:32:02 +03:00
Abdulmohsen
cc3057f432
Merge pull request #358 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Update the pyinstaller build to include version number.
2025-08-01 18:11:58 +03:00
Abdulmohsen
517d4e7528
Merge pull request #357 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Fix threading issue on windows exe
2025-08-01 17:13:12 +03:00
Abdulmohsen
a939654715
Merge pull request #356 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Respect auto_start when re-queuing item
2025-07-31 23:56:36 +03:00
Abdulmohsen
9b5c1d9ae7
Merge pull request #355 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Fix automatic premiere re-queue.
2025-07-27 17:08:28 +03:00
Abdulmohsen
33fbf4bda8
Merge pull request #354 from arabcoders/dev
Added toggle for task handler & convert_url
2025-07-27 16:57:27 +03:00
Abdulmohsen
b4e5990b83
Merge pull request #352 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Fix playlist_autonumber. Closes #351
2025-07-24 23:50:17 +03:00
Abdulmohsen
fd75d3cb8f
Merge pull request #350 from arabcoders/dev
Fixed in browser match_str to closely match yt-dlp match_str
2025-07-24 19:39:01 +03:00
Abdulmohsen
6ba83ac00b
Merge pull request #349 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Fix upgrading and installing pip packages
2025-07-24 17:26:08 +03:00
Abdulmohsen
35aa158f40
Merge pull request #347 from arabcoders/dev 2025-07-23 03:32:31 +03:00
Abdulmohsen
b7cb89f656
Merge pull request #346 from arabcoders/dev
Remove duplicate notifications, and improve yt-dlp opts handling
2025-07-20 17:09:31 +03:00
Abdulmohsen
10182e1373
Merge pull request #343 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Completed the events system upgrade and expanded the notifications events with support for apprise.
2025-07-19 19:54:15 +03:00
Abdulmohsen
e398321901
Merge pull request #342 from arabcoders/dev
Cleaning up the event dispatcher system.
2025-07-18 22:06:53 +03:00
Abdulmohsen
f9b70fec5f
Merge pull request #341 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Allow the UI to bypass some checks for apprise URLs
2025-07-18 00:40:37 +03:00
Abdulmohsen
6e84b78f22
Merge pull request #340 from arabcoders/dev
Major notification update to add initial support for apprise URLs.
2025-07-18 00:17:07 +03:00
Abdulmohsen
78002ef99a
Merge pull request #339 from arabcoders/dev
add preset name to history/queue
2025-07-16 17:37:28 +03:00
Abdulmohsen
043b019bde
Merge pull request #338 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Fix color contrast on pause button in dark mode. Closes #337
2025-07-15 19:01:30 +03:00
Abdulmohsen
f66980c3bf
Merge pull request #336 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Add the ability to disable temp file handling
2025-07-14 23:06:38 +03:00
Abdulmohsen
f4ec06cef9
Merge pull request #334 from arabcoders/dev
Make it possible to cancel download and keep the downloaded tmp data
2025-07-11 19:58:33 +03:00
Abdulmohsen
4c11e178b2
Merge pull request #333 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
Added the ability to add items in paused state
2025-07-08 22:25:42 +03:00
Abdulmohsen
49cce85cfe
Merge pull request #331 from arabcoders/dev
Some checks failed
Build WebView wrappers / build (amd64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (amd64, windows-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, macos-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, ubuntu-latest) (push) Has been cancelled
Build WebView wrappers / build (arm64, windows-latest) (push) Has been cancelled
support running in pywebview in windows.
2025-07-07 18:48:28 +03:00
Abdulmohsen
034ff25e76
Merge pull request #330 from arabcoders/dev
split windows and nix oses packaging.
2025-07-07 16:39:36 +03:00
Abdulmohsen
a324ed8ffd
Merge pull request #329 from arabcoders/dev
remove sha256sum generation
2025-07-07 16:28:43 +03:00
Abdulmohsen
1169eea47b
Merge pull request #328 from arabcoders/dev
fix
2025-07-07 16:23:39 +03:00
Abdulmohsen
cc4b9032b0
Merge pull request #327 from arabcoders/dev
followup fix for artifacts release
2025-07-07 16:17:53 +03:00
Abdulmohsen
0edac6c93e
Merge pull request #326 from arabcoders/dev
fix artifact version tagging
2025-07-07 15:55:45 +03:00
Abdulmohsen
27e70b6df8
Merge pull request #325 from arabcoders/dev
correctly upload artifacts
2025-07-06 20:07:31 +03:00
Abdulmohsen
eb3ac026ba
Merge pull request #324 from arabcoders/dev
fix release updater
2025-07-06 19:40:18 +03:00
Abdulmohsen
6607a233c0
Merge pull request #323 from arabcoders/dev
PR to support webview
2025-07-06 19:26:47 +03:00
Abdulmohsen
b3bd02afc8
Merge pull request #322 from arabcoders/dev
Download as zipfile when downloading multiple files
2025-07-04 19:23:33 +03:00
Abdulmohsen
26455b62bf
Merge pull request #319 from arabcoders/dev
Added file browser actions
2025-07-01 18:14:33 +03:00
Abdulmohsen
6c44c587c7
Merge pull request #317 from arabcoders/dev
sometimes yt-dlp info extract doesn't return formats.
2025-06-28 22:25:15 +03:00
Abdulmohsen
8e3e7dda80
Merge pull request #316 from arabcoders/dev
Renamed presets and added optimized preset for mobile devices
2025-06-28 20:27:52 +03:00
Abdulmohsen
3fe9188b47
Merge pull request #314 from arabcoders/dev
Implements ytdlp filter logic to our own playlist processing.
2025-06-27 20:19:26 +03:00
Abdulmohsen
b5bb2a202c
Merge pull request #313 from arabcoders/dev
Fix running in subpath again.
2025-06-27 16:36:27 +03:00
Abdulmohsen
1ce9acd316
Merge pull request #311 from arabcoders/dev
Use Asyncio.Semaphore for managing concurrency.
2025-06-24 22:26:10 +03:00
Abdulmohsen
de4b88eef3
Merge pull request #309 from arabcoders/dev
minor design update for index page.
2025-06-21 20:58:01 +03:00
Abdulmohsen
3f3f63f0e2
Merge pull request #308 from arabcoders/dev
Make it possible to view local item info
2025-06-20 21:03:13 +03:00
Abdulmohsen
8fe68f36ee
Merge pull request #306 from arabcoders/dev
Improve tasks page for mass operations.
2025-06-20 00:29:26 +03:00
Abdulmohsen
4bf65f08c4
Merge pull request #305 from arabcoders/dev
When item download fails, trigger item_error event
2025-06-19 19:11:24 +03:00
Abdulmohsen
6e05efb146
Merge pull request #304 from arabcoders/dev
Use simver starting from this pull request.
2025-06-18 22:19:43 +03:00
Abdulmohsen
c07465a676
Merge pull request #303 from arabcoders/dev 2025-06-16 22:19:11 +03:00
Abdulmohsen
4c76aa7123
Merge pull request #301 from arabcoders/dev
Fix premiere video re-queue.
2025-06-15 20:00:15 +03:00
Abdulmohsen
b65008b628
Merge pull request #300 from arabcoders/dev
Fixed bug in auth handler.
2025-06-15 16:41:24 +03:00
Abdulmohsen
2eab819e87
Merge pull request #299 from arabcoders/dev
Tag premiere video correctly.
2025-06-14 22:14:04 +03:00
Abdulmohsen
670c99510e
Merge pull request #297 from arabcoders/dev
Support running on windows
2025-06-10 13:44:17 +03:00
Abdulmohsen
f1f79afea1
Merge pull request #296 from arabcoders/dev
Add support for running YTPTube from sub-directory
2025-06-05 22:52:13 +03:00
Abdulmohsen
38d99c0e1e
Merge pull request #294 from arabcoders/dev
Make sure to build linux/arm64 for default branch
2025-06-04 22:49:13 +03:00
Abdulmohsen
4a26dc1a7f
Merge pull request #293 from arabcoders/dev
fixed bug in default URL separator selector.
2025-06-04 22:42:10 +03:00
Abdulmohsen
3bbb1509e2
Merge pull request #292 from arabcoders/dev
Add notifications center
2025-06-04 22:23:56 +03:00
Abdulmohsen
cf2fb5fac8
Merge pull request #289 from arabcoders/dev
Major WebUI improvements + minor bug fixes
2025-06-04 00:45:25 +03:00
Abdulmohsen
2a60a848d6
Merge pull request #285 from arabcoders/dev
minor UI and text update.
2025-06-03 03:15:42 +03:00
Abdulmohsen
a434e3a6aa
Merge pull request #284 from arabcoders/dev
Refactor streaming logic to be more efficient
2025-06-01 19:21:01 +03:00
Abdulmohsen
8dfdf7f615
Merge pull request #283 from arabcoders/dev
Update API documentation
2025-05-28 18:24:50 +03:00
Abdulmohsen
fd27ae924e
Merge pull request #281 from arabcoders/dev
Removed a custom monkey patch for yt-dlp that addressed live-from-start MPD issues.
2025-05-27 22:26:20 +03:00
Abdulmohsen
adb191bb6d
Merge pull request #278 from arabcoders/dev
Fix problem with processing playlist
2025-05-23 18:31:36 +03:00
Abdulmohsen
4398b30b38
Merge pull request #277 from arabcoders/dev
Minor improvements.
2025-05-23 02:08:13 +03:00
Abdulmohsen
f7f5ef0e59
Merge pull request #275 from arabcoders/dev
Fix healthcheck when user:pass are set.
2025-05-13 16:33:46 +03:00
Abdulmohsen
cbc6277dc6
Merge pull request #274 from arabcoders/dev
Fix #273
2025-05-12 18:21:51 +03:00
Abdulmohsen
853a443b88
Merge pull request #270 from arabcoders/dev
Design Update & Fold preset --format into the cli args
2025-05-10 19:54:07 +03:00
Abdulmohsen
12b4b12344
Merge pull request #269 from arabcoders/dev
Communicate more error logs to user when yt-dlp throws them.
2025-05-02 21:20:42 +03:00
Abdulmohsen
1deba05902
Merge pull request #268 from arabcoders/dev 2025-05-01 03:40:42 +03:00
Abdulmohsen
9f0d88375f
Merge pull request #265 from arabcoders/dev
Switched newDownload form to use HttpAPI for easier state management
2025-04-28 19:24:37 +03:00
Abdulmohsen
d385e41f6a
Merge pull request #264 from arabcoders/dev
upgrade external packages.
2025-04-26 19:58:34 +03:00
Abdulmohsen
1bbaf461b2
Merge pull request #263 from arabcoders/dev
More protection against stale downloads
2025-04-26 19:34:31 +03:00
Abdulmohsen
b29dc00bd8
Merge pull request #262 from arabcoders/dev
Automatically re-queue upcoming live streams in history.
2025-04-22 19:10:33 +03:00
Abdulmohsen
53d3b3c81d
Merge pull request #261 from arabcoders/dev
restore message about live stream not being live yet.
2025-04-21 20:53:59 +03:00
Abdulmohsen
a2f8cb5523
Merge pull request #260 from arabcoders/dev
Display better description  when there are no formats available.
2025-04-20 17:00:39 +03:00
Abdulmohsen
3425657dbf
Merge pull request #259 from arabcoders/dev
added some protection against staled downloads
2025-04-19 17:56:31 +03:00
Abdulmohsen
fb2aa7396b
Merge pull request #258 from arabcoders/dev
Minor refactor for is_downloaded function and updated dependencies
2025-04-12 18:49:53 +03:00
Abdulmohsen
2beb6a6b08
Merge pull request #257 from arabcoders/dev
Minor logs view update
2025-04-10 02:52:40 +03:00
Abdulmohsen
53da40b5f5
Merge pull request #256 from arabcoders/dev
When using external downloader show indicator in the WebUI
2025-04-10 01:38:01 +03:00
Abdulmohsen
31f64e67da
Merge pull request #253 from arabcoders/dev
go with blacklist approach for blocking yt-dlp options.
2025-04-07 02:31:21 +03:00
Abdulmohsen
ec1f2f448a
Merge pull request #251 from arabcoders/dev
migrate the entire app to use cli options for ytdlp. Closes #250
2025-04-06 02:31:30 +03:00
Abdulmohsen
c118522fa2
Merge pull request #249 from arabcoders/dev
Add Logs Viewer
2025-04-04 23:40:01 +03:00
Abdulmohsen
62b41bfdfa
Merge pull request #248 from arabcoders/dev
added new env variable to auto upgrade yt-dlp on container startup
2025-04-03 23:31:18 +03:00
Abdulmohsen
e79d182daa
Merge pull request #247 from arabcoders/dev
Fix re-queuing existing item.
2025-04-02 01:51:46 +03:00
Abdulmohsen
18fe5409ce
Merge pull request #246 from arabcoders/dev
Simplify the codebase and removed all backwards compatibility flags
2025-04-02 00:49:10 +03:00
Abdulmohsen
2ed0bde90a
Merge pull request #245 from arabcoders/dev
Update yt-dlp to 2025.3.31
2025-04-01 19:06:23 +03:00
Abdulmohsen
6cf0b9120e
Merge pull request #243 from arabcoders/dev
deprecate JSON yt-dlp config, and encourage cli options.
2025-03-31 23:26:01 +03:00
Abdulmohsen
8305518a01
Merge pull request #242 from arabcoders/dev
Add support for --match-filter
2025-03-30 01:47:34 +03:00
Abdulmohsen
80737f7d90
Merge pull request #240 from arabcoders/dev
Small improvements to file browser.
2025-03-28 23:35:50 +03:00
Abdulmohsen
dc3c2b41e3
Merge pull request #239 from arabcoders/dev 2025-03-28 03:29:39 +03:00
Abdulmohsen
5bac7b8d20
Merge pull request #237 from arabcoders/dev
Nested browser frontend page was not loading correctly.
2025-03-28 02:27:31 +03:00
Abdulmohsen
daad7752b3
Merge pull request #236 from arabcoders/dev
Added Simple file browser.
2025-03-28 02:12:41 +03:00
Abdulmohsen
15e4232fc8
Merge pull request #234 from arabcoders/dev
minor update to list view, and added download selected items.
2025-03-27 00:55:40 +03:00
Abdulmohsen
6d77de76cd
Merge pull request #233 from arabcoders/dev
Design update
2025-03-26 22:12:28 +03:00
Abdulmohsen
5f7bbb5eba
Merge pull request #231 from arabcoders/dev
Update yt-dlp to 2025.3.26
2025-03-26 04:00:50 +03:00
Abdulmohsen
df699dd0c5
Merge pull request #229 from arabcoders/dev
Maintain playlist keys when we are extracting a playlist/channel
2025-03-26 01:04:58 +03:00
Abdulmohsen
fd93472bd0
Merge pull request #226 from arabcoders/dev
Update yt-dlp to 2025.3.25
2025-03-25 04:03:22 +03:00
Abdulmohsen
810a63d485
Merge pull request #224 from arabcoders/dev
restore opacity if the video is playing.
2025-03-25 02:08:38 +03:00
Abdulmohsen
68473a1b0d
Merge pull request #223 from arabcoders/dev
Add random beautiful backgrounds
2025-03-25 01:56:18 +03:00
Abdulmohsen
28d902b2a9
Merge pull request #222 from arabcoders/dev
Minor README & EventBus update
2025-03-23 00:55:16 +03:00
Abdulmohsen
00f3f27b8b
Merge pull request #221 from arabcoders/dev
Switiching to EventBus & updated yt-dlp to 2025.3.21
2025-03-22 20:43:51 +03:00
Abdulmohsen
25da932d21
Merge pull request #219 from arabcoders/dev
bump curl_cffi to 0.7.1
2025-03-20 01:04:48 +03:00
Abdulmohsen
87e93a5df6
Merge pull request #218 from arabcoders/dev
potential fix for #217
2025-03-18 00:24:48 +03:00
Abdulmohsen
9445e3ef86
Merge pull request #216 from arabcoders/dev 2025-03-17 19:52:45 +03:00
Abdulmohsen
0ed8aa5a30
Merge pull request #215 from arabcoders/dev
Add changeslog page to WebUI
2025-03-17 00:12:13 +03:00
Abdulmohsen
3161fc0bb8
Merge pull request #214 from arabcoders/dev
fix params Impersonate target
2025-03-16 21:32:25 +03:00
Abdulmohsen
98970da663
Merge pull request #213 from arabcoders/dev
Fix date range conversion
2025-03-16 21:13:29 +03:00
Abdulmohsen
19bdd84dd5
Merge pull request #211 from arabcoders/dev
Added indicator in the WebUI for when post-processing are running
2025-03-15 01:33:55 +03:00
Abdulmohsen
9adb6efbc5
Merge pull request #210 from arabcoders/dev
Standardize the form design & add import/export to all functions
2025-03-14 23:18:44 +03:00
Abdulmohsen
674c53893f
Merge pull request #209 from arabcoders/dev
Preset exports are base64 encoded now
2025-03-13 23:16:12 +03:00
Abdulmohsen
d5efb994ec
Merge pull request #208 from arabcoders/dev
Made it possible to not use format in preset
2025-03-11 21:10:27 +03:00
Abdulmohsen
10aeff4bfa
Merge pull request #207 from arabcoders/dev
Added advanced iOS shortcut
2025-03-11 02:23:11 +03:00
Abdulmohsen
9427f7b2a0
Merge pull request #206 from arabcoders/dev
Improve history status display
2025-03-10 01:48:30 +03:00
Abdulmohsen
7a2dc8a452
Merge pull request #205 from arabcoders/dev
Updated worker pool to be a bit more dynamic
2025-03-09 01:20:58 +03:00
Abdulmohsen
243d4554d7
Merge pull request #204 from arabcoders/dev
Disable yt-dlp console access by default
2025-03-07 20:45:37 +03:00
Abdulmohsen
7b395d717d
Merge pull request #203 from arabcoders/dev
Make presets more powerful
2025-03-07 19:01:07 +03:00
Abdulmohsen
c944c36028
Merge pull request #202 from arabcoders/dev
remove debug statement
2025-03-07 01:27:25 +03:00
Abdulmohsen
e52bef3781
Merge pull request #201 from arabcoders/dev
Dev
2025-03-07 01:05:05 +03:00
Abdulmohsen
73f97b80f3
Merge pull request #200 from arabcoders/dev
Rely on native video player
2025-03-05 22:18:35 +03:00
Abdulmohsen
327136bf09
Merge pull request #199 from arabcoders/dev 2025-03-04 21:25:12 +03:00
Abdulmohsen
48c849da5a
Merge pull request #197 from arabcoders/dev
Manage presets via WebUI
2025-03-04 02:18:46 +03:00
Abdulmohsen
a608d02d67
Merge pull request #193 from arabcoders/dev
better fallback if file was renamed and retains the same ID
2025-03-01 20:15:40 +03:00
Abdulmohsen
65dbfc775e
Merge pull request #192 from arabcoders/dev
Try to direct play video first and fallback to HLS if src load fails
2025-02-28 01:53:32 +03:00
Abdulmohsen
ad561f7e73
Merge pull request #191 from arabcoders/dev
remove json cookies and improve extraction error messages.
2025-02-27 00:45:51 +03:00
Abdulmohsen
806b795b38
Merge pull request #190 from arabcoders/dev 2025-02-20 17:32:03 +03:00
Abdulmohsen
a1f8245086
Merge pull request #188 from arabcoders/dev
Minor frontend improvements
2025-01-30 18:23:46 +03:00
Abdulmohsen
d135de5088
Merge pull request #187 from arabcoders/dev
Changed UMASK from 022 to 0002
2025-01-29 13:29:17 +03:00
Abdulmohsen
f554871b9f
Merge pull request #186 from arabcoders/dev
Major code changes.
2025-01-28 00:18:41 +03:00
Abdulmohsen
f790257b4f
Merge pull request #184 from arabcoders/dev
Revamped the notification system
2025-01-25 22:56:30 +03:00
Abdulmohsen
f0ac16cea7
Merge pull request #183 from arabcoders/dev
Overhauled the tasks page
2025-01-24 23:29:46 +03:00
Abdulmohsen
ff540addff
Merge pull request #181 from arabcoders/dev
Handle yt-dlp CLI options
2025-01-23 18:30:03 +03:00
Abdulmohsen
82d1401c27
Merge pull request #179 from arabcoders/dev
separate PR and push workflow
2025-01-19 23:55:47 +03:00
Abdulmohsen
12e18ee745
Merge pull request #178 from arabcoders/dev
updated version tagger
2025-01-19 22:30:08 +03:00
Abdulmohsen
c54e9107b3
Merge pull request #177 from arabcoders/dev
Refactoring the code to use shared manager
2025-01-19 21:40:30 +03:00
Abdulmohsen
bcf5593523
Merge pull request #176 from arabcoders/dev 2025-01-19 18:27:31 +03:00
Abdulmohsen
9ab357983b
Merge pull request #175 from arabcoders/dev
Finalize Fix for #173
2025-01-18 21:14:06 +03:00
Abdulmohsen
501e97aabd
Merge pull request #174 from arabcoders/dev
Dev
2025-01-18 17:56:15 +03:00
Abdulmohsen
084def9346
Merge pull request #172 from arabcoders/dev
Add Basic mode for WebUI
2025-01-17 15:55:51 +03:00
Abdulmohsen
5be33c6a9a
Merge pull request #170 from arabcoders/dev 2025-01-16 19:11:13 +03:00
Abdulmohsen
5b72e45432
Merge pull request #168 from arabcoders/dev
Added view url information in history and queue
2025-01-13 18:38:38 +03:00
Abdulmohsen
0614a09231
Merge pull request #166 from arabcoders/dev
Added way to check for youtube cookies status
2025-01-11 21:10:05 +03:00
Abdulmohsen
1cc4d73051
Merge pull request #165 from arabcoders/dev
Added new option to view url info
2025-01-11 17:13:11 +03:00
Abdulmohsen
ec8cd6b88a
Merge pull request #164 from arabcoders/dev
fixed typo in README and updated dependencies
2025-01-10 19:20:41 +03:00
Abdulmohsen
2e99ff1f5e
Merge pull request #163 from arabcoders/dev
Fix and document presets.json
2025-01-10 19:05:47 +03:00
Abdulmohsen
9adf48e404
Merge pull request #162 from arabcoders/dev
updates API endpoints, fix webhooks not firing due to an error.
2025-01-03 20:41:03 +03:00
Abdulmohsen
fa2fd2970a
Merge pull request #161 from arabcoders/dev
Fix download audio only preset
2025-01-03 00:48:41 +03:00
Abdulmohsen
4094e98feb
Merge pull request #159 from arabcoders/dev
fix cancelling selected items.
2025-01-02 21:37:47 +03:00
Abdulmohsen
5301e134ce
Merge pull request #157 from arabcoders/dev
fix yt-dlp Impersonate target and webhook not firing
2025-01-02 19:16:26 +03:00
Abdulmohsen
02ba97b8ab
Merge pull request #156 from arabcoders/dev
added confirm to pause download pool
2025-01-01 16:53:33 +03:00
Abdulmohsen
e415edd761
Merge pull request #155 from arabcoders/dev
Added the ability to pause download pool
2024-12-31 18:26:28 +03:00
Abdulmohsen
0682f9d443
Merge pull request #154 from arabcoders/dev
Minor UI changes
2024-12-29 22:07:31 +03:00
Abdulmohsen
0230f00a8c
Merge pull request #152 from arabcoders/dev
Document env variables and added remove_files env option.
2024-12-25 19:56:03 +03:00
Abdulmohsen
14d598eb10
Merge pull request #151 from arabcoders/dev 2024-12-25 10:26:52 +03:00
43 changed files with 2985 additions and 3138 deletions

View file

@ -30,8 +30,5 @@
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file"
},
"files.associations": {
"*.css": "tailwindcss"
}
}

5
FAQ.md
View file

@ -3,9 +3,8 @@
Certain configuration values can be set via environment variables, using the `-e` parameter on the docker command line,
or the `environment:` section in `compose.yaml` file.
<details>
<summary>Click to expand all environment Variables</summary>
<summary>Click to expand</summary>
| Environment Variable | Description | Default |
| ------------------------------- | ------------------------------------------------------------------- | --------------------- |
@ -65,6 +64,7 @@ or the `environment:` section in `compose.yaml` file.
| YTP_THUMB_GENERATE | Enable ffmpeg thumbnail generation when no local thumbnail exists. | `true` |
| YTP_THUMB_SIDECAR | Save generated thumbnails next to media instead of temp cache. | `false` |
| YTP_DISABLE_EXEC | Strip some dangerous yt-dlp options. | `false` |
</details>
> [!NOTE]
> To raise the worker limit for a specific extractor, set an env variable using this format: `YTP_MAX_WORKERS_FOR_<EXTRACTOR_NAME>`
@ -77,7 +77,6 @@ or the `environment:` section in `compose.yaml` file.
> `YTP_AUTO_CLEAR_HISTORY_DAYS` `0` days means no automatic clearing of the download history. lowest value that will
> trigger the clearing is `1` day. This setting will **NOT** delete the downloaded files, it will only clear the
> history from the database.
</details>
# Browser extensions & bookmarklets

View file

@ -7,69 +7,5 @@ export default defineAppConfig({
success: 'emerald',
neutral: 'stone',
},
formField: {
slots: {
label: 'block font-bold text-default',
},
},
tooltip: {
slots: {
content: 'pointer-events-auto',
},
},
dropdownMenu: {
slots: {
content: 'ytp-floating-surface',
},
},
popover: {
slots: {
content: 'ytp-floating-surface',
},
},
select: {
slots: {
content: 'ytp-floating-surface',
},
variants: {
variant: {
outline: 'bg-elevated/40 hover:bg-elevated/55 disabled:bg-elevated/20',
},
},
},
selectMenu: {
slots: {
content: 'ytp-floating-surface',
},
variants: {
variant: {
outline: 'bg-elevated/40 hover:bg-elevated/55 disabled:bg-elevated/20',
},
},
},
button: {
compoundVariants: [
{
color: 'neutral',
variant: 'outline',
class:
'bg-transparent hover:bg-elevated/30 active:bg-elevated/40 disabled:bg-transparent aria-disabled:bg-transparent',
},
{
color: 'neutral',
variant: 'soft',
class:
'bg-elevated/30 hover:bg-elevated/45 active:bg-elevated/55 disabled:bg-elevated/20 aria-disabled:bg-elevated/20',
},
{
color: 'neutral',
variant: 'ghost',
class: 'hover:bg-elevated/30 active:bg-elevated/40 focus-visible:bg-elevated/30',
},
],
defaultVariants: {
size: 'sm',
},
},
},
});

View file

@ -1,5 +1,3 @@
@layer theme, base, legacy, components, utilities;
@import 'tailwindcss';
@import '@nuxt/ui';
@ -32,11 +30,6 @@
html.simple-mode.bg-fanart #__nuxt {
background-image: none !important;
}
html.dark.simple-mode.bg-fanart body,
html.dark.simple-mode.bg-fanart #__nuxt {
background-color: var(--ui-bg) !important;
}
}
@layer components {
@ -58,9 +51,9 @@
.shell-surface {
background:
radial-gradient(circle at top left, rgb(99 102 241 / 0.14), transparent 28%),
radial-gradient(circle at top right, rgb(245 158 11 / 0.12), transparent 22%),
linear-gradient(180deg, rgb(255 255 255 / 0.6), rgb(248 250 252 / 0.72));
radial-gradient(circle at top left, rgb(99 102 241 / 0.1), transparent 28%),
radial-gradient(circle at top right, rgb(245 158 11 / 0.08), transparent 22%),
linear-gradient(180deg, rgb(255 255 255 / 0.84), rgb(248 250 252 / 0.9));
backdrop-filter: blur(16px);
}
@ -72,115 +65,6 @@
backdrop-filter: blur(16px);
}
.ytp-page-header {
@apply flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between;
}
.ytp-page-heading {
@apply flex min-w-0 items-center gap-3;
}
.ytp-page-kicker {
@apply flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned;
}
.ytp-page-icon {
@apply inline-flex size-11 shrink-0 items-center justify-center rounded-md border bg-elevated/70 text-primary;
border-color: var(--ytp-surface-border-solid);
}
.ytp-section-icon {
@apply inline-flex size-10 shrink-0 items-center justify-center rounded-md border bg-elevated/70 text-primary;
border-color: var(--ytp-surface-border-solid);
}
.ytp-detail-icon {
@apply inline-flex size-9 shrink-0 items-center justify-center rounded-md border bg-elevated/70 text-primary;
border-color: var(--ytp-surface-border-solid);
}
.ytp-card {
@apply min-w-0 max-w-full rounded-lg border bg-elevated/20;
border-color: var(--ytp-surface-border-soft);
}
.ytp-card-padded {
@apply min-w-0 max-w-full rounded-lg border bg-elevated/20 p-4;
border-color: var(--ytp-surface-border-soft);
}
.ytp-card-padded-lg {
@apply min-w-0 max-w-full rounded-lg border bg-elevated/20 p-4 sm:p-5;
border-color: var(--ytp-surface-border-soft);
}
.ytp-panel {
@apply min-w-0 max-w-full rounded-lg border;
border-color: var(--ytp-surface-border-soft);
}
.ytp-panel-padded {
@apply min-w-0 max-w-full rounded-lg border p-4;
border-color: var(--ytp-surface-border-soft);
}
.ytp-panel-padded-lg {
@apply min-w-0 max-w-full rounded-lg border p-4 sm:p-5;
border-color: var(--ytp-surface-border-soft);
}
.ytp-frame {
@apply min-w-0 max-w-full rounded-lg border;
border-color: var(--ytp-surface-border-soft);
}
.ytp-table-surface {
@apply min-w-0 max-w-full rounded-sm border bg-elevated/20;
border-color: var(--ytp-surface-border-solid);
}
.ytp-floating-surface {
@apply border bg-default/95 shadow-xl backdrop-blur-md;
border-color: var(--ytp-surface-border-solid);
box-shadow: 0 1rem 2rem rgb(15 23 42 / 0.1);
}
.ytp-floating-surface [data-slot='item'][data-highlighted]::before,
.ytp-floating-surface [data-slot='item'][data-state='open']::before,
.ytp-floating-surface [data-slot='item']:hover::before {
background-color: color-mix(in oklab, var(--ui-primary) 11%, transparent) !important;
}
.ytp-floating-surface [data-slot='item'][data-disabled]::before {
background-color: transparent !important;
}
.dark .ytp-floating-surface {
background: color-mix(in oklab, var(--ui-bg) 92%, var(--ui-bg-elevated) 8%);
box-shadow: 0 1rem 2rem rgb(0 0 0 / 0.3);
}
.dark .ytp-floating-surface [data-slot='item'][data-highlighted]::before,
.dark .ytp-floating-surface [data-slot='item'][data-state='open']::before,
.dark .ytp-floating-surface [data-slot='item']:hover::before {
background-color: color-mix(in oklab, var(--ui-primary) 16%, transparent) !important;
}
.ytp-border-top-soft {
@apply border-t;
border-color: var(--ytp-surface-border-soft);
}
.ytp-border-bottom-soft {
@apply border-b;
border-color: var(--ytp-surface-border-soft);
}
.ytp-border-left-soft {
@apply border-l;
border-color: var(--ytp-surface-border-soft);
}
.ytp-terminal {
display: block;
width: 100%;
@ -283,15 +167,6 @@
--ui-radius: 0.5rem;
--ui-container: 96rem;
--ui-header-height: 4.25rem;
--ytp-border-strong: var(--ui-border-accented);
--ytp-surface-border-soft: var(--ui-border-accented);
--ytp-surface-border-solid: var(--ui-border-accented);
}
.dark {
--ytp-border-strong: var(--ui-border);
--ytp-surface-border-soft: color-mix(in oklab, var(--ui-border) 70%, transparent);
--ytp-surface-border-solid: var(--ui-border);
}
html:not(.no-page-anim) .page-enter-active {

View file

@ -261,7 +261,9 @@
</div>
</div>
<div class="grid gap-3 ytp-card-padded md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto]">
<div
class="grid gap-3 rounded-lg border border-default bg-default p-4 md:grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto]"
>
<UFormField :ui="fieldUi">
<template #label>
<div class="flex flex-wrap items-center gap-2">

View file

@ -1,7 +1,7 @@
<template>
<div v-if="'bool' === type && compact" class="w-full space-y-1.5">
<div
class="flex min-h-11 items-start justify-between gap-3 rounded-md border border-default bg-elevated/20 px-3 py-2"
class="flex min-h-11 items-start justify-between gap-3 rounded-md border border-default bg-default/80 px-3 py-2"
>
<div class="min-w-0 flex-1">
<div class="inline-flex min-w-0 items-center gap-2 text-sm font-semibold text-default">
@ -27,7 +27,7 @@
v-model="boolModel"
:disabled="disabled"
color="success"
label=""
:label="boolModel ? 'Yes' : 'No'"
size="lg"
class="shrink-0"
:ui="{ root: 'items-center gap-2', wrapper: 'ms-0 text-sm' }"
@ -70,7 +70,7 @@
:disabled="disabled"
size="lg"
class="w-full"
:ui="{ root: 'w-full', base: 'w-full bg-elevated/60' }"
:ui="{ root: 'w-full', base: 'w-full bg-default/90' }"
/>
<UTextarea

View file

@ -20,7 +20,7 @@
<div
v-if="open && suggestions.length"
ref="dropdownRef"
class="absolute inset-x-0 top-full z-20 mt-1 max-h-40 overflow-y-auto rounded-md ytp-floating-surface"
class="absolute inset-x-0 top-full z-20 mt-1 max-h-40 overflow-y-auto rounded-md border border-default bg-default shadow-lg"
role="listbox"
>
<button

View file

@ -12,7 +12,7 @@
variant="outline"
color="neutral"
class="w-full"
:ui="{ root: 'w-full', base: 'w-full bg-elevated/60', leadingIcon: iconClass }"
:ui="{ root: 'w-full', base: 'w-full bg-default/90', leadingIcon: iconClass }"
@focus="onFocus"
@blur="hideList"
@input="onInput"
@ -25,7 +25,7 @@
<div
v-if="showList && filteredOptions.length"
ref="dropdownRef"
class="absolute inset-x-0 top-full z-20 mt-1 max-h-40 overflow-y-auto rounded-md ytp-floating-surface"
class="absolute inset-x-0 top-full z-20 mt-1 max-h-40 overflow-y-auto rounded-md border border-default bg-default shadow-lg"
role="menu"
>
<button

View file

@ -25,7 +25,10 @@
</UButton>
</div>
<div v-if="!limits && limitsLoading" class="ytp-card flex min-h-72 items-center justify-center">
<div
v-if="!limits && limitsLoading"
class="flex min-h-72 items-center justify-center rounded-md border border-default bg-default/90"
>
<div class="flex flex-col items-center gap-3 text-center text-toned">
<UIcon name="i-lucide-loader-circle" class="size-10 animate-spin text-info" />
@ -63,7 +66,7 @@
title="Download queue is paused"
/>
<div class="ytp-card shadow-sm">
<div class="rounded-md border border-default bg-default shadow-sm">
<div class="grid gap-0 lg:grid-cols-[minmax(0,1.05fr)_minmax(0,0.95fr)]">
<section
class="space-y-4 border-b border-default px-4 py-4 sm:px-5 lg:border-r lg:border-b-0"
@ -194,10 +197,13 @@
description="Overrides and extractor usage appear here once activity is detected."
/>
<div v-else class="w-full min-w-0 max-w-full overflow-hidden ytp-table-surface">
<div
v-else
class="w-full min-w-0 max-w-full overflow-hidden rounded-md border border-default bg-default"
>
<div class="w-full max-w-full overflow-x-auto overscroll-x-contain">
<table class="min-w-180 w-full text-sm">
<thead class="bg-elevated/60 text-xs uppercase tracking-wide text-toned">
<thead class="bg-muted/40 text-xs uppercase tracking-wide text-toned">
<tr
class="text-left [&>th]:border-r [&>th]:border-default/60 [&>th]:px-3 [&>th]:py-3 [&>th]:font-semibold [&>th:last-child]:border-r-0"
>

View file

@ -119,7 +119,9 @@
@click="sourceOpen = !sourceOpen"
>
<div class="flex items-center gap-3">
<span class="ytp-detail-icon">
<span
class="inline-flex size-9 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon name="i-lucide-file-code" class="size-4" />
</span>
<p class="text-base font-semibold text-highlighted">Source</p>
@ -165,7 +167,9 @@
@click="fieldsOpen = !fieldsOpen"
>
<div class="flex items-center gap-3">
<span class="ytp-detail-icon">
<span
class="inline-flex size-9 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon name="i-lucide-tags" class="size-4" />
</span>
<p class="text-base font-semibold text-highlighted">Fields</p>
@ -221,16 +225,14 @@
>
<button
type="button"
class="grid w-full grid-cols-[minmax(7rem,0.45fr)_minmax(0,1fr)] items-center gap-3 px-3 py-2 text-left sm:grid-cols-[minmax(0,12rem)_minmax(0,1fr)]"
class="flex w-full items-center justify-between gap-3 px-3 py-2 text-left"
@click="toggleField(field.key)"
>
<span
class="min-w-0 truncate text-[11px] font-semibold uppercase tracking-wide text-toned"
>
<span class="text-[11px] font-semibold uppercase tracking-wide text-toned">
{{ field.label }}
</span>
<div class="flex min-w-0 items-center justify-end gap-2">
<span v-if="field.preview" class="min-w-0 truncate text-xs text-toned">
<div class="flex items-center gap-2">
<span v-if="field.preview" class="max-w-md truncate text-xs text-toned">
{{ field.preview }}
</span>
<UIcon
@ -304,7 +306,9 @@
@click="rawJsonOpen = !rawJsonOpen"
>
<div class="flex items-center gap-3">
<span class="ytp-detail-icon">
<span
class="inline-flex size-9 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon name="i-lucide-braces" class="size-4" />
</span>
<p class="text-base font-semibold text-highlighted">Raw Data</p>

View file

@ -1,481 +1,492 @@
<template>
<main class="space-y-4">
<form autocomplete="off" class="space-y-4" @submit.prevent="addDownload">
<div class="ytp-card p-4 sm:p-6 space-y-4">
<div class="space-y-4">
<UFormField class="w-full" :ui="downloadFieldUi">
<template #label>
<span class="inline-flex items-center gap-2 font-semibold">
<UTooltip text="Use Shift+Enter to switch to multiline input mode.">
<UIcon name="i-lucide-link" class="size-4 text-toned" />
</UTooltip>
<span>
URLs separated by newlines or
<span class="font-semibold lowercase">{{ getSeparatorsName(separator) }}</span>
</span>
</span>
</template>
<div class="flex flex-row gap-2 items-start">
<div class="min-w-0 flex-1">
<UTextarea
v-if="isMultiLineInput"
ref="urlTextarea"
id="url"
v-model="form.url"
:disabled="addInProgress"
size="lg"
:variant="show_description ? 'soft' : 'outline'"
color="neutral"
class="w-full"
:rows="3"
:maxrows="12"
autoresize
:ui="{
root: 'w-full',
base: 'min-h-[7.25rem] bg-elevated/60 ring-default focus-visible:ring-primary',
}"
@keydown="handleKeyDown"
autofocus
/>
<UInput
v-else
id="url"
v-model="form.url"
type="text"
placeholder="URLs to download"
:disabled="addInProgress"
size="lg"
:variant="show_description ? 'soft' : 'outline'"
color="neutral"
class="w-full"
:ui="{
root: 'w-full',
base: 'bg-elevated/60 ring-default focus-visible:ring-primary',
}"
@keydown="handleKeyDown"
@paste="handlePaste"
autofocus
/>
</div>
<UButton
type="submit"
color="primary"
icon="i-lucide-plus"
:loading="addInProgress"
:disabled="addInProgress || !hasValidUrl"
size="lg"
class="justify-center sm:min-w-28"
>
Add
</UButton>
</div>
</UFormField>
<div
class="grid gap-4 xl:grid-cols-[minmax(0,0.95fr)_minmax(0,1.95fr)_11rem] xl:items-end"
>
<UFormField
class="min-w-0 w-full"
label="Preset"
:ui="downloadFieldUi"
:description="
hasFormatInConfig
? 'Presets are disabled. Format key is present in the command options for yt-dlp.'
: 'Prefill saved yt-dlp command options.'
"
>
<UPageCard variant="outline" :ui="downloadCardUi">
<template #body>
<div class="space-y-4">
<UFormField class="w-full" :ui="downloadFieldUi">
<template #label>
<span class="inline-flex items-center gap-2 font-semibold">
<UIcon name="i-lucide-sliders-horizontal" class="size-4 text-toned" />
<span>Preset</span>
<UTooltip text="Use Shift+Enter to switch to multiline input mode.">
<UIcon name="i-lucide-link" class="size-4 text-toned" />
</UTooltip>
<span>
URLs separated by newlines or
<span class="font-semibold lowercase">{{ getSeparatorsName(separator) }}</span>
</span>
</span>
</template>
<div class="flex w-full gap-2">
<UButton
color="neutral"
:variant="show_description ? 'soft' : 'outline'"
icon="i-lucide-info"
square
class="shrink-0"
@click="show_description = !show_description"
/>
<div class="flex flex-row gap-2 items-start">
<div class="min-w-0 flex-1">
<USelectMenu
id="preset"
v-model="form.preset"
:items="presetItems"
value-key="value"
label-key="label"
<UTextarea
v-if="isMultiLineInput"
ref="urlTextarea"
id="url"
v-model="form.url"
:disabled="addInProgress"
size="lg"
variant="outline"
color="neutral"
class="w-full"
:rows="3"
:maxrows="12"
autoresize
:ui="{
root: 'w-full',
base: 'min-h-[7.25rem] bg-elevated/60 ring-default focus-visible:ring-primary',
}"
@keydown="handleKeyDown"
autofocus
/>
<UInput
v-else
id="url"
v-model="form.url"
type="text"
placeholder="URLs to download"
:disabled="addInProgress"
size="lg"
:ui="{ content: 'min-w-[13rem]', item: 'pl-6' }"
:search-input="{ placeholder: 'Search presets' }"
:disabled="addInProgress || hasFormatInConfig"
placeholder="Select preset"
variant="outline"
color="neutral"
class="w-full"
:ui="{
root: 'w-full',
base: 'bg-elevated/60 ring-default focus-visible:ring-primary',
}"
@keydown="handleKeyDown"
@paste="handlePaste"
autofocus
/>
</div>
</div>
</UFormField>
<UFormField
class="min-w-0 w-full"
label="Download path"
:ui="downloadFieldUi"
:description="`Relative to ${config.app.download_path}`"
>
<template #label>
<span class="inline-flex items-center gap-2 font-semibold">
<UIcon name="i-lucide-folder-output" class="size-4 text-toned" />
<span>Download path</span>
</span>
</template>
<div class="flex items-center gap-2">
<span
class="max-w-44 shrink-0 truncate rounded-md border border-default bg-muted/40 px-3 py-2 text-xs text-toned"
:title="config.app.download_path"
<UButton
type="submit"
color="primary"
icon="i-lucide-plus"
:loading="addInProgress"
:disabled="addInProgress || !hasValidUrl"
size="lg"
class="justify-center sm:min-w-28"
>
{{ shortPath(config.app.download_path) }}
</span>
<FolderInput
id="folder"
v-model="form.folder"
:placeholder="getDefault('folder', '/')"
:disabled="addInProgress"
:ui="{ root: 'w-full', base: 'bg-default/90' }"
/>
Add
</UButton>
</div>
</UFormField>
<div class="flex w-full items-end xl:col-span-1">
<UButton
type="button"
color="neutral"
:variant="showAdvanced ? 'soft' : 'outline'"
icon="i-lucide-settings-2"
size="lg"
class="w-full justify-center"
@click="showAdvanced = !showAdvanced"
>
{{ showAdvanced ? 'Hide Options' : 'Show Options' }}
</UButton>
</div>
</div>
</div>
<div
v-if="show_description && !hasFormatInConfig && findPreset(form.preset)?.description"
class="max-h-36 overflow-auto rounded-md border border-default bg-muted/30 px-3 py-2 text-sm text-toned"
>
<button
type="button"
class="block w-full cursor-pointer text-left"
@click="expand_description"
>
<span class="inline-flex items-start gap-2">
<UIcon name="i-lucide-info" class="mt-0.5 size-4 shrink-0 text-info" />
<span class="is-ellipsis">{{ findPreset(form.preset)?.description }}</span>
</span>
</button>
</div>
</div>
<div v-if="showAdvanced" class="ytp-card p-4 sm:p-6 space-y-4">
<div class="space-y-4">
<div class="grid gap-3 md:grid-cols-2 xl:grid-cols-12">
<div class="xl:col-span-2">
<DLInput
id="force_download"
v-model="dlFields['--no-download-archive']"
type="bool"
label="Force download"
icon="i-lucide-download"
:disabled="addInProgress"
description="Ignore archive."
compact
/>
</div>
<div class="xl:col-span-2">
<DLInput
id="auto_start"
v-model="auto_start"
type="bool"
label="Auto start"
icon="i-lucide-play"
:disabled="addInProgress"
description="Download automatically."
compact
/>
</div>
<div class="xl:col-span-2">
<DLInput
id="no_cache"
v-model="dlFields['--no-continue']"
type="bool"
label="Bypass cache"
icon="i-lucide-eraser"
:disabled="addInProgress"
description="Remove temporary files."
compact
/>
</div>
<div
:class="
hasIgnoreConditionOptions
? 'md:col-span-2 xl:col-span-3'
: 'md:col-span-2 xl:col-span-6'
"
class="grid gap-4 xl:grid-cols-[minmax(0,0.95fr)_minmax(0,1.95fr)_11rem] xl:items-end"
>
<DLInput
id="output_template"
v-model="form.template"
type="string"
label="Output template"
icon="i-lucide-file-code-2"
:disabled="addInProgress"
:placeholder="
getDefault('template', config.app.output_template || '%(title)s.%(ext)s')
<UFormField
class="min-w-0 w-full"
label="Preset"
:ui="downloadFieldUi"
:description="
hasFormatInConfig
? 'Presets are disabled. Format key is present in the command options for yt-dlp.'
: 'Prefill saved yt-dlp command options.'
"
>
<template #description>
<span>
See
<NuxtLink
target="_blank"
class="font-medium text-primary hover:underline"
to="https://github.com/yt-dlp/yt-dlp#output-template"
>
the yt-dlp output template page
</NuxtLink>
for info.
<template #label>
<span class="inline-flex items-center gap-2 font-semibold">
<UIcon name="i-lucide-sliders-horizontal" class="size-4 text-toned" />
<span>Preset</span>
</span>
</template>
</DLInput>
</div>
<UFormField
v-if="hasIgnoreConditionOptions"
class="md:col-span-2 xl:col-span-3 w-full"
:ui="advancedEditorFieldUi"
>
<template #label>
<span class="inline-flex items-center gap-2 font-semibold">
<UIcon name="i-lucide-list-minus" class="size-4 text-toned" />
<span>Ignore conditions</span>
</span>
</template>
<div class="flex w-full gap-2">
<UButton
color="neutral"
variant="outline"
icon="i-lucide-info"
square
class="shrink-0"
@click="show_description = !show_description"
/>
<template #description>
<span> Skip selected condition rules. </span>
</template>
<div class="min-w-0 flex-1">
<USelectMenu
id="preset"
v-model="form.preset"
:items="presetItems"
value-key="value"
label-key="label"
color="neutral"
class="w-full"
size="lg"
:ui="{ content: 'min-w-[13rem]', item: 'pl-6' }"
:search-input="{ placeholder: 'Search presets' }"
:disabled="addInProgress || hasFormatInConfig"
placeholder="Select preset"
/>
</div>
</div>
</UFormField>
<template #hint>
<button
v-if="selectedIgnoreConditions.length > 0"
<UFormField
class="min-w-0 w-full"
label="Download path"
:ui="downloadFieldUi"
:description="`Relative to ${config.app.download_path}`"
>
<template #label>
<span class="inline-flex items-center gap-2 font-semibold">
<UIcon name="i-lucide-folder-output" class="size-4 text-toned" />
<span>Download path</span>
</span>
</template>
<div class="flex items-center gap-2">
<span
class="max-w-44 shrink-0 truncate rounded-md border border-default bg-muted/40 px-3 py-2 text-xs text-toned"
:title="config.app.download_path"
>
{{ shortPath(config.app.download_path) }}
</span>
<FolderInput
id="folder"
v-model="form.folder"
:placeholder="getDefault('folder', '/')"
:disabled="addInProgress"
:ui="{ root: 'w-full', base: 'bg-default/90' }"
/>
</div>
</UFormField>
<div class="flex w-full items-end xl:col-span-1">
<UButton
type="button"
class="font-medium text-primary hover:underline"
@click="selectedIgnoreConditions = []"
color="neutral"
variant="outline"
icon="i-lucide-settings-2"
size="lg"
class="w-full justify-center"
@click="showAdvanced = !showAdvanced"
>
Clear selection
</button>
</template>
<USelectMenu
v-model="selectedIgnoreConditions"
:items="ignoreConditionOptions"
value-key="value"
label-key="label"
multiple
placeholder="Select conditions to ignore"
class="w-full"
size="lg"
:disabled="addInProgress || conditions.isLoading.value"
:search-input="{ placeholder: 'Search conditions' }"
:ui="{ base: 'w-full', content: 'min-w-[18rem]' }"
/>
</UFormField>
{{ showAdvanced ? 'Hide Options' : 'Show Options' }}
</UButton>
</div>
</div>
</div>
<div class="grid gap-4 xl:grid-cols-2">
<UFormField
label="Command options for yt-dlp"
class="w-full"
:ui="advancedEditorFieldUi"
<div
v-if="show_description && !hasFormatInConfig && findPreset(form.preset)?.description"
class="max-h-36 overflow-auto rounded-md border border-default bg-muted/30 px-3 py-2 text-sm text-toned"
>
<button
type="button"
class="block w-full cursor-pointer text-left"
@click="expand_description"
>
<template #label>
<span class="inline-flex items-center gap-2 font-semibold">
<UIcon name="i-lucide-terminal" class="size-4 text-toned" />
<span>Command options for yt-dlp</span>
</span>
</template>
<span class="inline-flex items-start gap-2">
<UIcon name="i-lucide-info" class="mt-0.5 size-4 shrink-0 text-info" />
<span class="is-ellipsis">{{ findPreset(form.preset)?.description }}</span>
</span>
</button>
</div>
</template>
</UPageCard>
<template #description>
<span>
<UPageCard v-if="showAdvanced" variant="outline" :ui="downloadCardUi">
<template #body>
<div class="space-y-4">
<div class="grid gap-3 md:grid-cols-2 xl:grid-cols-12">
<div class="xl:col-span-2">
<DLInput
id="force_download"
v-model="dlFields['--no-download-archive']"
type="bool"
label="Force download"
icon="i-lucide-download"
:disabled="addInProgress"
description="Ignore archive."
compact
/>
</div>
<div class="xl:col-span-2">
<DLInput
id="auto_start"
v-model="auto_start"
type="bool"
label="Auto start"
icon="i-lucide-play"
:disabled="addInProgress"
description="Download automatically."
compact
/>
</div>
<div class="xl:col-span-2">
<DLInput
id="no_cache"
v-model="dlFields['--no-continue']"
type="bool"
label="Bypass cache"
icon="i-lucide-eraser"
:disabled="addInProgress"
description="Remove temporary files."
compact
/>
</div>
<div
:class="
hasIgnoreConditionOptions
? 'md:col-span-2 xl:col-span-3'
: 'md:col-span-2 xl:col-span-6'
"
>
<DLInput
id="output_template"
v-model="form.template"
type="string"
label="Output template"
icon="i-lucide-file-code-2"
:disabled="addInProgress"
:placeholder="
getDefault('template', config.app.output_template || '%(title)s.%(ext)s')
"
>
<template #description>
<span>
See
<NuxtLink
target="_blank"
class="font-medium text-primary hover:underline"
to="https://github.com/yt-dlp/yt-dlp#output-template"
>
the yt-dlp output template page
</NuxtLink>
for info.
</span>
</template>
</DLInput>
</div>
<UFormField
v-if="hasIgnoreConditionOptions"
class="md:col-span-2 xl:col-span-3 w-full"
:ui="advancedEditorFieldUi"
>
<template #label>
<span class="inline-flex items-center gap-2 font-semibold">
<UIcon name="i-lucide-list-minus" class="size-4 text-toned" />
<span>Ignore conditions</span>
</span>
</template>
<template #description>
<span> Skip selected condition rules. </span>
</template>
<template #hint>
<button
v-if="selectedIgnoreConditions.length > 0"
type="button"
class="font-medium text-primary hover:underline"
@click="selectedIgnoreConditions = []"
>
Clear selection
</button>
</template>
<USelectMenu
v-model="selectedIgnoreConditions"
:items="ignoreConditionOptions"
value-key="value"
label-key="label"
multiple
placeholder="Select conditions to ignore"
class="w-full"
size="lg"
:disabled="addInProgress || conditions.isLoading.value"
:search-input="{ placeholder: 'Search conditions' }"
:ui="{ base: 'w-full', content: 'min-w-[18rem]' }"
/>
</UFormField>
</div>
<div class="grid gap-4 xl:grid-cols-2">
<UFormField
label="Command options for yt-dlp"
class="w-full"
:ui="advancedEditorFieldUi"
>
<template #label>
<span class="inline-flex items-center gap-2 font-semibold">
<UIcon name="i-lucide-terminal" class="size-4 text-toned" />
<span>Command options for yt-dlp</span>
</span>
</template>
<template #description>
<span>
<button
type="button"
class="font-medium text-primary hover:underline"
@click="showOptions = true"
>
View all options
</button>
. Not all options are supported;
<a
target="_blank"
href="https://github.com/arabcoders/ytptube/blob/master/app/features/ytdlp/utils.py#L29"
>
some are ignored
</a>
</span>
</template>
<TextareaAutocomplete
id="cli_options"
v-model="form.cli"
:options="ytDlpOpt"
:placeholder="getDefault('cli', '')"
:disabled="addInProgress"
:rows="5"
class="w-full"
/>
</UFormField>
<UFormField label="Cookies" class="w-full" :ui="advancedEditorFieldUi">
<template #label>
<span class="inline-flex items-center gap-2 font-semibold">
<UIcon name="i-lucide-cookie" class="size-4 text-toned" />
<span>Cookies</span>
</span>
</template>
<template #hint>
<button
type="button"
class="font-medium text-primary hover:underline"
@click="showOptions = true"
@click="cookiesDropzoneRef?.triggerFileSelect()"
>
View all options
Upload file
</button>
. Not all options are supported;
<a
target="_blank"
href="https://github.com/arabcoders/ytptube/blob/master/app/features/ytdlp/utils.py#L29"
>
some are ignored
</a>
</span>
</template>
</template>
<template #description>
<span>
Use the
<NuxtLink
target="_blank"
to="https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp"
>
recommended addon
</NuxtLink>
to export cookies.
<span class="text-error"
>The cookies MUST be in Netscape HTTP Cookie format.</span
>
</span>
</template>
<TextareaAutocomplete
id="cli_options"
v-model="form.cli"
:options="ytDlpOpt"
:placeholder="getDefault('cli', '')"
<TextDropzone
ref="cookiesDropzoneRef"
id="ytdlpCookies"
v-model="form.cookies"
:disabled="addInProgress"
:rows="5"
:placeholder="
getDefault(
'cookies',
'Leave empty to use default cookies. Or drag & drop a cookie file here.',
)
"
class="w-full"
@error="(msg: string) => toast.error(msg)"
/>
</UFormField>
</div>
<div
v-if="config.dl_fields.length > 0"
class="grid gap-4 md:grid-cols-2 xl:grid-cols-2"
>
<DLInput
v-for="(fi, index) in sortedDLFields"
:id="fi?.id || `dlf-${index}`"
:key="fi.id || `dlf-${index}`"
v-model="dlFields[fi.field]"
:type="fi.kind"
:description="fi.description"
:label="fi.name"
:icon="fi.icon"
:field="fi.field"
:disabled="addInProgress"
:rows="5"
class="w-full"
/>
</UFormField>
<UFormField label="Cookies" class="w-full" :ui="advancedEditorFieldUi">
<template #label>
<span class="inline-flex items-center gap-2 font-semibold">
<UIcon name="i-lucide-cookie" class="size-4 text-toned" />
<span>Cookies</span>
</span>
</template>
<template #hint>
<button
type="button"
class="font-medium text-primary hover:underline"
@click="cookiesDropzoneRef?.triggerFileSelect()"
>
Upload file
</button>
</template>
<template #description>
<span>
Use the
<NuxtLink
target="_blank"
to="https://github.com/yt-dlp/yt-dlp/wiki/FAQ#how-do-i-pass-cookies-to-yt-dlp"
>
recommended addon
</NuxtLink>
to export cookies.
<span class="text-error"
>The cookies MUST be in Netscape HTTP Cookie format.</span
>
</span>
</template>
<TextDropzone
ref="cookiesDropzoneRef"
id="ytdlpCookies"
v-model="form.cookies"
:disabled="addInProgress"
:rows="5"
:placeholder="
getDefault(
'cookies',
'Leave empty to use default cookies. Or drag & drop a cookie file here.',
)
"
class="w-full"
@error="(msg: string) => toast.error(msg)"
/>
</UFormField>
</div>
</div>
<div v-if="config.dl_fields.length > 0" class="grid gap-4 md:grid-cols-2 xl:grid-cols-2">
<DLInput
v-for="(fi, index) in sortedDLFields"
:id="fi?.id || `dlf-${index}`"
:key="fi.id || `dlf-${index}`"
v-model="dlFields[fi.field]"
:type="fi.kind"
:description="fi.description"
:label="fi.name"
:icon="fi.icon"
:field="fi.field"
:disabled="addInProgress"
/>
</div>
</div>
<div class="flex flex-wrap items-center justify-between gap-2 border-t border-default pt-4">
<UDropdownMenu class="sm:hidden" :items="mobileActionGroups" :modal="false">
<UButton
color="neutral"
variant="outline"
icon="i-lucide-ellipsis"
trailing-icon="i-lucide-chevron-down"
>
Actions
</UButton>
</UDropdownMenu>
<div class="hidden flex-wrap items-center gap-2 sm:flex">
<UButton
type="button"
color="neutral"
variant="outline"
icon="i-lucide-info"
:disabled="addInProgress || !hasValidUrl"
@click="emitter('getInfo', splitUrls(form.url || '')[0] || '', form.preset, form.cli)"
>
yt-dlp Information
</UButton>
<UButton
v-if="config.app.console_enabled"
type="button"
color="neutral"
variant="outline"
icon="i-lucide-terminal"
:disabled="!hasValidUrl"
@click="runCliCommand"
>
Run CLI
</UButton>
<UButton
v-if="config.app.console_enabled"
type="button"
color="neutral"
variant="outline"
icon="i-lucide-flask-conical"
:disabled="!hasValidUrl"
@click="testDownloadOptions"
>
Show compiled yt-dlp options
</UButton>
</div>
<UButton
type="button"
color="neutral"
variant="outline"
icon="i-lucide-rotate-ccw"
:disabled="!!form?.id"
@click="resetConfig"
<div
class="flex flex-wrap items-center justify-between gap-2 border-t border-default pt-4"
>
Reset local settings
</UButton>
</div>
</div>
<UDropdownMenu class="sm:hidden" :items="mobileActionGroups" :modal="false">
<UButton
color="neutral"
variant="outline"
icon="i-lucide-ellipsis"
trailing-icon="i-lucide-chevron-down"
>
Actions
</UButton>
</UDropdownMenu>
<div class="hidden flex-wrap items-center gap-2 sm:flex">
<UButton
type="button"
color="info"
variant="outline"
icon="i-lucide-info"
:disabled="addInProgress || !hasValidUrl"
@click="
emitter('getInfo', splitUrls(form.url || '')[0] || '', form.preset, form.cli)
"
>
yt-dlp Information
</UButton>
<UButton
v-if="config.app.console_enabled"
type="button"
color="warning"
variant="outline"
icon="i-lucide-terminal"
:disabled="!hasValidUrl"
@click="runCliCommand"
>
Run CLI
</UButton>
<UButton
v-if="config.app.console_enabled"
type="button"
color="success"
variant="outline"
icon="i-lucide-flask-conical"
:disabled="!hasValidUrl"
@click="testDownloadOptions"
>
Show compiled yt-dlp options
</UButton>
</div>
<UButton
type="button"
color="error"
variant="outline"
icon="i-lucide-rotate-ccw"
:disabled="!!form?.id"
@click="resetConfig"
>
Reset local settings
</UButton>
</div>
</template>
</UPageCard>
</form>
<UModal
@ -696,6 +707,13 @@ const testResultsPreClasses = computed(() => [
testResultsClasses.value,
]);
const downloadCardUi = {
root: 'w-full',
container: 'w-full p-4 sm:p-6',
wrapper: 'w-full items-stretch',
body: 'w-full space-y-4',
};
const downloadFieldUi = {
label: 'font-semibold text-default',
container: 'space-y-2',
@ -775,6 +793,7 @@ const mobileActionGroups = computed(() => {
{
label: 'Reset local settings',
icon: 'i-lucide-rotate-ccw',
color: 'error',
disabled: Boolean(form.value?.id),
onSelect: () => void resetConfig(),
},

View file

@ -86,7 +86,7 @@
<UIcon name="i-lucide-check" class="size-3.5" />
</UButton>
<UButton
color="neutral"
color="error"
variant="ghost"
size="xs"
square
@ -121,7 +121,7 @@
</UButton>
<UButton
color="neutral"
color="error"
variant="ghost"
size="sm"
icon="i-lucide-trash"

View file

@ -1,36 +0,0 @@
<template>
<div class="ytp-page-header">
<div class="ytp-page-heading">
<span class="ytp-page-icon">
<slot name="icon">
<UIcon :name="icon" class="size-5" />
</slot>
</span>
<div class="min-w-0" :class="contentClass">
<div class="ytp-page-kicker">
<slot name="kicker">
<span>{{ sectionLabel }}</span>
<span>/</span>
<span>{{ pageLabel }}</span>
</slot>
</div>
</div>
</div>
<div
v-if="$slots.actions"
class="flex w-full flex-wrap items-center gap-2 xl:w-auto xl:justify-end"
>
<slot name="actions" />
</div>
</div>
</template>
<script setup lang="ts">
import type { YtpPageHeaderProps } from '~/types';
withDefaults(defineProps<YtpPageHeaderProps>(), {
contentClass: 'space-y-1',
});
</script>

View file

@ -30,15 +30,15 @@
<template #body>
<div class="w-full space-y-6">
<div class="ytp-card w-full">
<div class="p-4 sm:p-5 ytp-border-bottom-soft">
<UPageCard variant="subtle" class="w-full" :ui="settingsCardUi">
<template #header>
<div class="flex items-center gap-2">
<UIcon name="i-lucide-layout-dashboard" class="size-4 text-toned" />
<span class="text-sm font-semibold text-highlighted">Page View</span>
</div>
</div>
</template>
<div class="p-4 sm:p-5 space-y-4">
<template #body>
<UFormField label="" class="w-full" :ui="settingsFieldUi">
<USelect
v-model="draftMode"
@ -73,18 +73,18 @@
:label="page_anims ? 'Animations On' : 'Animations Off'"
description="Enable page transition animations."
/>
</div>
</div>
</template>
</UPageCard>
<div class="ytp-card w-full">
<div class="p-4 sm:p-5 ytp-border-bottom-soft">
<UPageCard variant="subtle" class="w-full" :ui="settingsCardUi">
<template #header>
<div class="flex items-center gap-2">
<UIcon name="i-lucide-image" class="size-4 text-toned" />
<span class="text-sm font-semibold text-highlighted">Background</span>
</div>
</div>
</template>
<div class="p-4 sm:p-5 space-y-4">
<template #body>
<USwitch
v-model="bg_enable"
class="w-full"
@ -95,7 +95,7 @@
<UButton
v-if="bg_enable"
color="neutral"
color="info"
variant="outline"
icon="i-lucide-image-up"
class="w-full justify-center"
@ -121,18 +121,18 @@
class="w-full"
/>
</UFormField>
</div>
</div>
</template>
</UPageCard>
<div class="ytp-card w-full">
<div class="p-4 sm:p-5 ytp-border-bottom-soft">
<UPageCard variant="subtle" class="w-full" :ui="settingsCardUi">
<template #header>
<div class="flex items-center gap-2">
<UIcon name="i-lucide-monitor" class="size-4 text-toned" />
<span class="text-sm font-semibold text-highlighted">Downloads</span>
</div>
</div>
</template>
<div class="p-4 sm:p-5 space-y-4">
<template #body>
<UFormField v-if="!modeOn" label="URL Separator" class="w-full" :ui="settingsFieldUi">
<USelect
v-model="separator"
@ -179,18 +179,18 @@
:label="show_popover ? 'Popover On' : 'Popover Off'"
description="Show additional information over certain elements."
/>
</div>
</div>
</template>
</UPageCard>
<div class="ytp-card w-full">
<div class="p-4 sm:p-5 ytp-border-bottom-soft">
<UPageCard variant="subtle" class="w-full" :ui="settingsCardUi">
<template #header>
<div class="flex items-center gap-2">
<UIcon name="i-lucide-download" class="size-4 text-toned" />
<span class="text-sm font-semibold text-highlighted">Queue</span>
</div>
</div>
</template>
<div class="p-4 sm:p-5 space-y-4">
<template #body>
<USwitch
v-model="queue_auto_refresh"
class="w-full"
@ -219,18 +219,18 @@
How often to refresh the queue (5-60 seconds). Lower values increase server load.
</p>
</UFormField>
</div>
</div>
</template>
</UPageCard>
<div class="ytp-card w-full">
<div class="p-4 sm:p-5 ytp-border-bottom-soft">
<UPageCard variant="subtle" class="w-full" :ui="settingsCardUi">
<template #header>
<div class="flex items-center gap-2">
<UIcon name="i-lucide-bell" class="size-4 text-toned" />
<span class="text-sm font-semibold text-highlighted">Notifications</span>
</div>
</div>
</template>
<div class="p-4 sm:p-5 space-y-4">
<template #body>
<USwitch
v-model="allow_toasts"
class="w-full"
@ -288,8 +288,8 @@
:ui="settingsSwitchUi"
:label="toast_dismiss_on_click ? 'Dismiss on click' : 'Keep on click'"
/>
</div>
</div>
</template>
</UPageCard>
</div>
</template>
</USlideover>
@ -337,6 +337,13 @@ const queue_auto_refresh = useStorage<boolean>('queue_auto_refresh', true);
const queue_auto_refresh_delay = useStorage<number>('queue_auto_refresh_delay', 10000);
const isSecureContext = ref<boolean>(false);
const settingsCardUi = {
root: 'w-full',
container: 'w-full p-4 sm:p-5',
wrapper: 'w-full items-stretch',
body: 'w-full space-y-4',
};
const settingsFieldUi = {
root: 'w-full',
container: 'mt-2 w-full',
@ -408,10 +415,6 @@ const toastPositionItems: Array<{ label: string; value: toastPosition }> = [
{ label: 'bottom-right', value: 'bottom-right' },
];
const closeScrollLock = (): void => {
document.body.classList.remove('settings-panel-open');
};
const handleKeydown = (e: KeyboardEvent) => {
if ('Escape' === e.key && props.isOpen) {
e.preventDefault();
@ -431,10 +434,7 @@ onMounted(async () => {
document.addEventListener('keydown', handleKeydown);
});
onBeforeUnmount(() => {
document.removeEventListener('keydown', handleKeydown);
closeScrollLock();
});
onBeforeUnmount(() => document.removeEventListener('keydown', handleKeydown));
const onNotificationTargetChange = async (): Promise<void> => {
if ('browser' === toast_target.value) {
@ -455,7 +455,7 @@ watch(
draftMode.value = mode.value;
document.body.classList.add('settings-panel-open');
} else {
closeScrollLock();
document.body.classList.remove('settings-panel-open');
}
},
);

View file

@ -1,22 +1,16 @@
<template>
<div class="ytp-card bg-elevated/40 p-3">
<div class="rounded-lg border border-default/70 bg-elevated/40 p-3">
<div class="flex items-center justify-between gap-3">
<div class="min-w-0 space-y-1">
<p class="text-[11px] font-semibold uppercase tracking-[0.18em] text-toned">{{ label }}</p>
<UTooltip v-if="tooltip" :text="tooltip" :content="{ side: 'left' }">
<div :class="valueWrap ? 'space-y-1' : 'flex items-baseline gap-1.5'">
<div class="flex items-baseline gap-1.5">
<span class="text-sm font-semibold text-highlighted">{{ value }}</span>
<span v-if="hint" class="truncate text-xs text-toned">{{ hint }}</span>
</div>
</UTooltip>
<div v-else :class="valueWrap ? 'space-y-1' : 'flex items-baseline gap-1.5'">
<span
:class="[
'text-sm font-semibold text-highlighted',
valueWrap ? 'block wrap-break-word' : '',
]"
>{{ value }}</span
>
<div v-else class="flex items-baseline gap-1.5">
<span class="text-sm font-semibold text-highlighted">{{ value }}</span>
<span v-if="hint" class="truncate text-xs text-toned">{{ hint }}</span>
</div>
</div>
@ -43,9 +37,8 @@ const props = withDefaults(
hint?: string;
tooltip?: string;
color?: Color;
valueWrap?: boolean;
}>(),
{ color: 'primary', hint: '', tooltip: '', valueWrap: false },
{ color: 'primary', hint: '', tooltip: '' },
);
const TILE_BG: Record<Color, string> = {

View file

@ -363,10 +363,12 @@
</UButton>
</div>
<div class="w-full min-w-0 max-w-full overflow-hidden ytp-table-surface">
<div
class="w-full min-w-0 max-w-full overflow-hidden rounded-lg border border-default bg-default"
>
<div class="w-full max-w-full overflow-x-auto overscroll-x-contain">
<table class="min-w-215 table-fixed w-full text-sm">
<thead class="bg-elevated/60 text-xs uppercase tracking-wide text-toned">
<thead class="bg-muted/40 text-xs uppercase tracking-wide text-toned">
<tr
class="text-left [&>th]:border-r [&>th]:border-default/60 [&>th]:px-3 [&>th]:py-3 [&>th]:font-semibold [&>th:last-child]:border-r-0"
>

View file

@ -116,7 +116,7 @@
<span>Result:</span>
</div>
<div class="overflow-hidden ytp-frame">
<div class="overflow-hidden rounded-lg border border-default bg-default">
<div class="w-full max-w-full overflow-x-auto overscroll-x-contain">
<pre
class="min-w-0 max-w-full overflow-x-auto p-4 text-xs leading-6 text-default"

View file

@ -34,7 +34,7 @@
<div
v-if="showList && filteredOptions.length"
class="absolute inset-x-0 top-full z-20 mt-1 overflow-hidden rounded-md ytp-floating-surface"
class="absolute inset-x-0 top-full z-20 mt-1 overflow-hidden rounded-md border border-default bg-default shadow-lg"
role="menu"
>
<button

View file

@ -94,7 +94,6 @@
:label="nativeSubtitleTrack.name || 'Subtitles'"
default
:src="uri(nativeSubtitleTrack.url)"
@load="handleNativeTrackLoad"
/>
Your browser does not support the video tag.
</video>
@ -171,30 +170,16 @@
@click="forceSwitchToHls"
/>
</UTooltip>
<USelect
<UButton
v-if="hasSubtitles"
v-model="subtitleSelectValue"
:items="subtitleSelectItems"
value-key="value"
label-key="label"
color="neutral"
variant="soft"
size="sm"
trailing-icon=""
:content="subtitleSelectContent"
:portal="helpPortal"
:ui="subtitleSelectUi"
class="opacity-65 transition-opacity hover:opacity-100 focus-visible:opacity-100"
:aria-label="subtitleButtonLabel"
>
<template #default>
<span class="sr-only">{{ subtitleButtonLabel }}</span>
<UIcon
:name="subtitleEnabled ? 'i-lucide-captions' : 'i-lucide-captions-off'"
class="size-4 shrink-0"
/>
</template>
</USelect>
:icon="subtitleEnabled ? 'i-lucide-captions' : 'i-lucide-captions-off'"
:aria-label="subtitleEnabled ? 'Disable subtitles' : 'Enable subtitles'"
@click="subtitleEnabled = !subtitleEnabled"
/>
<UButton
color="neutral"
variant="soft"
@ -444,12 +429,9 @@ const timeLabel = computed(() => {
return `${currentLabel} / ${durationLabel}`;
});
const {
subtitleTracks,
subtitleLoading,
subtitleLoadError,
subtitleEnabled,
selectedSubtitleTrack,
selectedSubtitleTrackId,
nativeSubtitleTrack,
usesAssSubtitleTrack,
hasSubtitles,
@ -463,40 +445,6 @@ const {
overlay: assOverlayElement,
});
const SUBTITLE_OFF_VALUE = '__off__';
const subtitleSelectContent = { align: 'end' as const } as const;
const subtitleSelectUi = {
base: 'size-8 min-h-8 min-w-8 justify-center rounded-md px-0',
value: 'flex items-center justify-center px-0',
trailing: 'hidden',
leading: 'hidden',
content: 'z-40 min-w-56 w-auto max-w-[min(24rem,calc(100vw-2rem))]',
} as const;
const subtitleSelectItems = computed(() => [
{ label: 'Off', value: SUBTITLE_OFF_VALUE },
...subtitleTracks.value.map((track) => ({ label: track.name, value: track.url })),
]);
const subtitleButtonLabel = computed(() => {
if (!subtitleEnabled.value || !selectedSubtitleTrack.value) {
return 'Subtitles off';
}
return selectedSubtitleTrack.value.name || 'Subtitles';
});
const subtitleSelectValue = computed<string>({
get: () =>
subtitleEnabled.value
? (selectedSubtitleTrackId.value ?? SUBTITLE_OFF_VALUE)
: SUBTITLE_OFF_VALUE,
set: (value: string) => {
if (SUBTITLE_OFF_VALUE === value) {
subtitleEnabled.value = false;
return;
}
selectedSubtitleTrackId.value = value;
subtitleEnabled.value = true;
},
});
useHead(() => (title.value ? { title: formatPageTitle(`Playing: ${title.value}`) } : {}));
watch(
@ -1012,10 +960,6 @@ function updateMediaSessionPosition(target: EventTarget | null) {
}
async function restoreDefaultTextTrack() {
if (true === destroyed.value) {
return;
}
const el = videoElement.value;
if (!el) {
return;
@ -1027,6 +971,36 @@ async function restoreDefaultTextTrack() {
return;
}
const firstTrack = tracksList[0] as TextTrack | undefined;
const needsReload = firstTrack && (!firstTrack.cues || firstTrack.cues.length === 0);
if (needsReload) {
const trackElements = el.querySelectorAll('track');
trackElements.forEach((trackEl, idx) => {
const parent = trackEl.parentNode;
const clone = trackEl.cloneNode(true) as HTMLTrackElement;
trackEl.remove();
setTimeout(() => {
if (parent) {
parent.appendChild(clone);
clone.addEventListener(
'load',
() => {
const trackObj = clone.track;
if (trackObj) {
trackObj.mode = idx === 0 && subtitleEnabled.value ? 'showing' : 'disabled';
}
},
{ once: true },
);
}
}, idx * 10);
});
return;
}
for (let i = 0; i < tracksList.length; i += 1) {
const track = tracksList[i] as TextTrack | undefined;
if (track) {
@ -1036,34 +1010,18 @@ async function restoreDefaultTextTrack() {
await new Promise((resolve) => setTimeout(resolve, 50));
if (el !== videoElement.value) {
return;
}
if (false === subtitleEnabled.value || null === nativeSubtitleTrack.value) {
return;
}
const activeTracksList = el.textTracks;
for (let i = 0; i < activeTracksList.length; i += 1) {
const track = activeTracksList[i] as TextTrack | undefined;
for (let i = 0; i < tracksList.length; i += 1) {
const track = tracksList[i] as TextTrack | undefined;
if (!track) {
continue;
}
track.mode = i === 0 ? 'showing' : 'disabled';
track.mode = i === 0 && subtitleEnabled.value ? 'showing' : 'disabled';
}
} catch (error) {
console.warn('Failed to restore subtitle track state', error);
}
}
function handleNativeTrackLoad() {
if (true === destroyed.value) {
return;
}
void restoreDefaultTextTrack();
}
function applyMediaSessionMetadata() {
if (false === 'mediaSession' in navigator) {
return;
@ -1284,10 +1242,6 @@ watch(subtitleEnabled, () => {
void nextTick(() => restoreDefaultTextTrack());
});
watch(selectedSubtitleTrack, () => {
void nextTick(() => restoreDefaultTextTrack());
});
onMounted(async () => {
disableOpacity();
isTouchDevice.value = window.matchMedia('(pointer: coarse)').matches;

View file

@ -123,10 +123,12 @@
<UBadge color="neutral" variant="soft" size="sm">{{ group.items.length }}</UBadge>
</div>
<div class="w-full min-w-0 max-w-full overflow-hidden ytp-table-surface">
<div
class="w-full min-w-0 max-w-full overflow-hidden rounded-lg border border-default bg-default"
>
<div class="w-full max-w-full overflow-x-auto overscroll-x-contain">
<table class="min-w-180 w-full table-auto text-sm">
<thead class="bg-elevated/60 text-xs uppercase tracking-wide text-toned">
<thead class="bg-muted/40 text-xs uppercase tracking-wide text-toned">
<tr
class="text-left [&>th]:border-r [&>th]:border-default/60 [&>th]:px-3 [&>th]:py-3 [&>th]:font-semibold [&>th:last-child]:border-r-0"
>
@ -186,7 +188,10 @@
</section>
</template>
<div v-else class="w-full min-w-0 max-w-full overflow-hidden ytp-table-surface">
<div
v-else
class="w-full min-w-0 max-w-full overflow-hidden rounded-lg border border-default bg-default"
>
<div class="w-full max-w-full overflow-x-auto overscroll-x-contain">
<table class="min-w-215 w-full table-auto text-sm">
<thead class="bg-muted/40 text-xs uppercase tracking-wide text-toned">

View file

@ -11,41 +11,54 @@
/>
</div>
<div class="ytp-panel p-0 relative w-full max-w-xl overflow-hidden bg-default/95">
<div class="space-y-6 px-5 py-6 sm:px-7 sm:py-8" role="status" aria-live="polite">
<div
class="inline-flex items-center gap-2 rounded-full border border-default bg-elevated/60 px-3 py-1.5 text-xs font-semibold tracking-[0.22em] text-toned uppercase"
>
<UIcon
name="i-lucide-loader-circle"
class="size-4 animate-spin text-info"
aria-hidden="true"
<UPageCard
variant="outline"
:ui="pageCardUi"
class="relative w-full max-w-xl overflow-hidden bg-default/95"
>
<template #body>
<div class="space-y-6 px-5 py-6 sm:px-7 sm:py-8" role="status" aria-live="polite">
<div
class="inline-flex items-center gap-2 rounded-full border border-default bg-elevated/60 px-3 py-1.5 text-xs font-semibold tracking-[0.22em] text-toned uppercase"
>
<UIcon
name="i-lucide-loader-circle"
class="size-4 animate-spin text-info"
aria-hidden="true"
/>
<span>Shutdown in progress</span>
</div>
<div class="space-y-3">
<h1 class="text-3xl font-semibold tracking-tight text-highlighted sm:text-4xl">
Goodbye!
</h1>
<p class="text-base leading-7 text-default sm:text-lg">YTPTube is shutting down.</p>
<p class="max-w-lg text-sm leading-6 text-toned sm:text-base">
You may now close this window. Thanks for using YTPTube.
</p>
</div>
<UAlert
color="info"
variant="soft"
icon="i-lucide-power"
title="Wrapping things up"
description="The native app is closing background services and should exit shortly."
/>
<span>Shutdown in progress</span>
</div>
<div class="space-y-3">
<h1 class="text-3xl font-semibold tracking-tight text-highlighted sm:text-4xl">
Goodbye!
</h1>
<p class="text-base leading-7 text-default sm:text-lg">YTPTube is shutting down.</p>
<p class="max-w-lg text-sm leading-6 text-toned sm:text-base">
You may now close this window. Thanks for using YTPTube.
</p>
</div>
<UAlert
color="info"
variant="soft"
icon="i-lucide-power"
title="Wrapping things up"
description="The native app is closing background services and should exit shortly."
/>
</div>
</div>
</template>
</UPageCard>
</div>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
const pageCardUi = {
root: 'w-full bg-transparent shadow-2xl shadow-primary/5',
container: 'w-full p-0',
wrapper: 'w-full items-stretch',
body: 'w-full p-0',
};
</script>

View file

@ -54,10 +54,7 @@ export function usePlayerSubtitles(options: UsePlayerSubtitlesOptions) {
const subtitleLoading = ref(false);
const subtitleLoadError = ref('');
const subtitleEnabled = ref(true);
const selectedTrackId = ref<string | null>(null);
const selectedTrack = computed(
() => tracks.value.find((track) => track.url === selectedTrackId.value) || null,
);
const selectedTrack = computed(() => tracks.value[0] || null);
const nativeSubtitleTrack = computed(() => {
const track = selectedTrack.value;
return subtitleEnabled.value && track?.renderer === 'native' ? track : null;
@ -85,7 +82,6 @@ export function usePlayerSubtitles(options: UsePlayerSubtitlesOptions) {
assRequestId += 1;
destroyAssRenderer();
tracks.value = [];
selectedTrackId.value = null;
subtitleLoadError.value = '';
if (!manifestUrl || !isVideo || !canPlay) {
@ -108,7 +104,6 @@ export function usePlayerSubtitles(options: UsePlayerSubtitlesOptions) {
tracks.value = (payload as SubtitleManifestResponse).subtitles || [];
if (tracks.value.length > 0) {
selectedTrackId.value = tracks.value[0]?.url || null;
subtitleEnabled.value = true;
}
} catch {
@ -217,7 +212,6 @@ export function usePlayerSubtitles(options: UsePlayerSubtitlesOptions) {
subtitleLoadError,
subtitleEnabled,
selectedSubtitleTrack: selectedTrack,
selectedSubtitleTrackId: selectedTrackId,
nativeSubtitleTrack,
usesAssSubtitleTrack: usesAssTrack,
hasSubtitles,

View file

@ -1,70 +1,72 @@
<template>
<NuxtLayout name="error-layout">
<div class="flex min-h-full flex-1 items-start justify-center py-4 sm:py-8">
<div class="ytp-card p-0 w-full">
<div class="space-y-6 px-4 py-5 sm:px-6 sm:py-6 lg:px-7">
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div class="min-w-0 space-y-2">
<div class="flex flex-wrap items-center gap-2 text-sm text-toned">
<UIcon name="i-lucide-triangle-alert" class="size-4 text-warning" />
<span>Application error</span>
<UPageCard variant="outline" :ui="pageCardUi" class="w-full">
<template #body>
<div class="space-y-6 px-4 py-5 sm:px-6 sm:py-6 lg:px-7">
<div class="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
<div class="min-w-0 space-y-2">
<div class="flex flex-wrap items-center gap-2 text-sm text-toned">
<UIcon name="i-lucide-triangle-alert" class="size-4 text-warning" />
<span>Application error</span>
</div>
<h1 class="text-2xl font-semibold text-highlighted sm:text-3xl">
{{ error.status || 'Unknown Error' }}
<span v-if="error.statusText"> - {{ error.statusText }}</span>
</h1>
<p class="max-w-3xl text-sm leading-6 text-toned">
An unexpected error interrupted this view. You can go back home or retry the
current route.
</p>
</div>
<h1 class="text-2xl font-semibold text-highlighted sm:text-3xl">
{{ error.status || 'Unknown Error' }}
<span v-if="error.statusText"> - {{ error.statusText }}</span>
</h1>
<div class="flex flex-wrap items-center gap-2 lg:justify-end">
<UButton color="neutral" variant="outline" size="sm" icon="i-lucide-house" to="/">
Back to Home
</UButton>
<p class="max-w-3xl text-sm leading-6 text-toned">
An unexpected error interrupted this view. You can go back home or retry the current
route.
</p>
<UButton color="primary" size="sm" icon="i-lucide-rotate-cw" @click="handleRetry">
Retry
</UButton>
</div>
</div>
<div class="flex flex-wrap items-center gap-2 lg:justify-end">
<UButton color="neutral" variant="outline" size="sm" icon="i-lucide-house" to="/">
Back to Home
</UButton>
<UAlert
v-if="error.message"
color="warning"
variant="soft"
icon="i-lucide-circle-alert"
title="Details"
:description="error.message"
/>
<UButton color="primary" size="sm" icon="i-lucide-rotate-cw" @click="handleRetry">
Retry
</UButton>
</div>
<section v-if="error.stack" class="space-y-3 border-t border-default pt-5">
<button
type="button"
class="inline-flex items-center gap-2 text-sm font-semibold text-highlighted transition hover:text-primary"
@click="showStacks = !showStacks"
>
<UIcon
:name="showStacks ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
class="size-4"
/>
<span>Stack trace</span>
</button>
<div
v-if="showStacks"
class="overflow-x-auto rounded-lg border border-default bg-elevated/60"
>
<pre
class="min-w-0 p-4 text-xs leading-6 whitespace-pre-wrap text-default"
><code>{{ error.stack }}</code></pre>
</div>
</section>
</div>
<UAlert
v-if="error.message"
color="warning"
variant="soft"
icon="i-lucide-circle-alert"
title="Details"
:description="error.message"
/>
<section v-if="error.stack" class="space-y-3 border-t border-default pt-5">
<button
type="button"
class="inline-flex items-center gap-2 text-sm font-semibold text-highlighted transition hover:text-primary"
@click="showStacks = !showStacks"
>
<UIcon
:name="showStacks ? 'i-lucide-chevron-up' : 'i-lucide-chevron-down'"
class="size-4"
/>
<span>Stack trace</span>
</button>
<div
v-if="showStacks"
class="overflow-x-auto rounded-lg border border-default bg-elevated/60"
>
<pre
class="min-w-0 p-4 text-xs leading-6 whitespace-pre-wrap text-default"
><code>{{ error.stack }}</code></pre>
</div>
</section>
</div>
</div>
</template>
</UPageCard>
</div>
</NuxtLayout>
</template>
@ -78,6 +80,13 @@ const props = defineProps<{
const showStacks = ref(false);
const pageCardUi = {
root: 'w-full bg-default',
container: 'w-full p-0',
wrapper: 'w-full items-stretch',
body: 'w-full p-0',
};
const handleRetry = async (): Promise<void> => {
await clearError({ redirect: useRoute().fullPath });
};

View file

@ -183,7 +183,7 @@
<UButton
v-if="true === config.app.is_native"
color="neutral"
color="error"
variant="ghost"
size="sm"
icon="i-lucide-power"

View file

@ -6,14 +6,19 @@
aria-hidden="true"
/>
<div class="ytp-page-header">
<div class="ytp-page-heading">
<span class="ytp-page-icon">
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div class="flex min-w-0 items-start gap-3">
<span
class="inline-flex size-11 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon :name="pageShell.icon" class="size-5" />
</span>
<div class="min-w-0 flex-1 space-y-3">
<nav aria-label="Breadcrumb" class="min-w-0 ytp-page-kicker">
<nav
aria-label="Breadcrumb"
class="flex min-w-0 flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned"
>
<span>{{ pageShell.sectionLabel }}</span>
<span>/</span>
<span>{{ pageShell.pageLabel }}</span>
@ -45,7 +50,7 @@
</div>
</div>
<div class="flex w-full flex-wrap items-center gap-2 xl:w-auto xl:justify-end">
<div class="flex min-w-0 flex-wrap items-center justify-end gap-2 xl:justify-end">
<UButton
color="neutral"
:variant="show_filter ? 'soft' : 'outline'"
@ -118,7 +123,7 @@
<div
v-if="controlEnabled && hasItems"
class="flex flex-wrap items-center justify-between gap-3 ytp-card px-3 py-3"
class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-default bg-default px-3 py-3"
>
<div class="flex flex-wrap items-center gap-2">
<UButton
@ -164,11 +169,11 @@
<div
v-if="contentStyle === 'list' && hasItems"
class="w-full min-w-0 max-w-full overflow-hidden ytp-table-surface"
class="w-full min-w-0 max-w-full overflow-hidden rounded-lg border border-default bg-default"
>
<div class="w-full max-w-full overflow-x-auto overscroll-x-contain">
<table class="min-w-360 w-full text-sm">
<thead class="bg-elevated/60 text-xs uppercase tracking-wide text-toned">
<thead class="bg-muted/40 text-xs uppercase tracking-wide text-toned">
<tr
class="text-center [&>th]:border-r [&>th]:border-default/60 [&>th]:px-3 [&>th]:py-3 [&>th]:font-semibold [&>th:last-child]:border-r-0"
>
@ -315,12 +320,18 @@
</div>
<div v-else-if="hasItems" class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
<div
<UCard
v-for="item in filteredItems"
:key="item.path"
class="ytp-card flex h-full flex-col overflow-hidden"
class="flex h-full flex-col"
:ui="{
root: 'bg-default border border-default',
header: 'p-4 pb-3',
body: 'flex flex-1 flex-col gap-4 p-4 pt-0',
footer: 'border-t border-default px-4 py-4',
}"
>
<div class="p-4 pb-3 ytp-border-bottom-soft">
<template #header>
<div class="flex items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<div class="flex items-start gap-2">
@ -353,33 +364,31 @@
</label>
</div>
</div>
</div>
</template>
<div class="flex flex-1 flex-col gap-4 p-4 pt-0">
<div class="flex flex-wrap gap-2 text-sm *:min-w-32 *:flex-1">
<div
class="min-w-0 rounded-md border border-default bg-muted/20 px-3 py-2 text-center text-default"
>
<span class="block truncate">{{ itemTypeLabel(item) }}</span>
</div>
<div class="flex flex-wrap gap-2 text-sm *:min-w-32 *:flex-1">
<div
class="min-w-0 rounded-md border border-default bg-muted/20 px-3 py-2 text-center text-default"
>
<span class="block truncate">{{ itemTypeLabel(item) }}</span>
</div>
<div
class="min-w-0 rounded-md border border-default bg-muted/20 px-3 py-2 text-center text-toned"
>
<span class="block truncate">{{ itemSizeLabel(item) }}</span>
</div>
<div
class="min-w-0 rounded-md border border-default bg-muted/20 px-3 py-2 text-center text-toned"
>
<span class="block truncate">{{ itemSizeLabel(item) }}</span>
</div>
<div
class="min-w-0 rounded-md border border-default bg-muted/20 px-3 py-2 text-center text-toned"
>
<UTooltip :text="moment(item.mtime).format('YYYY-MM-DD H:mm:ss Z')">
<span class="block truncate">{{ moment(item.mtime).fromNow() }}</span>
</UTooltip>
</div>
<div
class="min-w-0 rounded-md border border-default bg-muted/20 px-3 py-2 text-center text-toned"
>
<UTooltip :text="moment(item.mtime).format('YYYY-MM-DD H:mm:ss Z')">
<span class="block truncate">{{ moment(item.mtime).fromNow() }}</span>
</UTooltip>
</div>
</div>
<div v-if="controlEnabled" class="ytp-border-top-soft px-4 py-4">
<template v-if="controlEnabled" #footer>
<div class="flex flex-wrap gap-2 *:min-w-32 *:flex-1">
<UButton
v-if="item.type === 'file'"
@ -428,8 +437,8 @@
Delete
</UButton>
</div>
</div>
</div>
</template>
</UCard>
</div>
<div v-if="localSearch && !hasItems && !isLoading" class="space-y-3">
@ -601,6 +610,7 @@ const bulkActionGroups = computed<DropdownMenuItem[][]>(() => [
{
label: 'Delete Selected',
icon: 'i-lucide-trash',
color: 'error',
disabled: !hasSelected.value || isLoading.value,
onSelect: () => void handleDeleteSelected(),
},

View file

@ -1,13 +1,17 @@
<template>
<main class="w-full min-w-0 max-w-full space-y-6">
<div class="ytp-page-header">
<div class="ytp-page-heading">
<span class="ytp-page-icon">
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div class="flex min-w-0 items-center gap-3">
<span
class="inline-flex size-11 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon :name="pageShell.icon" class="size-5" />
</span>
<div class="min-w-0 space-y-2">
<div class="ytp-page-kicker">
<div
class="flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned"
>
<span>{{ pageShell.sectionLabel }}</span>
<span>/</span>
<span>{{ pageShell.pageLabel }}</span>
@ -17,7 +21,7 @@
</div>
</div>
<div class="flex w-full flex-wrap items-center gap-2 xl:w-auto xl:justify-end">
<div class="flex min-w-0 flex-wrap items-center justify-end gap-2 xl:justify-end">
<UButton
v-if="logs.length > 0"
color="neutral"
@ -60,117 +64,99 @@
/>
<template v-else>
<div v-if="filteredLogs.length > 0" class="space-y-5">
<section v-for="log in filteredLogs" :key="log.tag" class="space-y-3">
<section class="space-y-3">
<button
type="button"
class="flex w-full flex-wrap items-center justify-between gap-3 text-left"
@click="toggleRelease(log.tag)"
>
<div class="flex min-w-0 items-center gap-3">
<span class="ytp-section-icon">
<UIcon
:name="isInstalled(log) ? 'i-lucide-badge-check' : 'i-lucide-git-branch'"
class="size-5"
/>
</span>
<div v-if="filteredLogs.length > 0" class="space-y-4">
<UPageCard v-for="log in filteredLogs" :key="log.tag" variant="outline" :ui="pageCardUi">
<template #body>
<div class="space-y-4">
<div class="flex flex-wrap items-start justify-between gap-3">
<div class="min-w-0 space-y-2">
<div class="flex flex-wrap items-center gap-2">
<div
class="inline-flex items-center gap-2 text-base font-semibold text-highlighted"
>
<UIcon name="i-lucide-git-branch" class="size-4 text-toned" />
<span>{{ log.tag }}</span>
</div>
<div class="min-w-0 space-y-1">
<h2 class="truncate text-base font-semibold text-highlighted">
{{ log.tag }}
</h2>
<div class="flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-toned">
<UTooltip v-if="log.date" :text="`Release Date: ${log.date}`">
<span class="inline-flex cursor-help items-center gap-1.5">
<UIcon name="i-lucide-calendar-days" class="size-3.5 text-muted" />
<span>{{ moment(log.date).fromNow() }}</span>
</span>
</UTooltip>
<UBadge v-if="isInstalled(log)" color="success" variant="soft" size="sm">
Installed
</UBadge>
</div>
</div>
<div class="flex flex-wrap items-center justify-end gap-2 text-xs text-toned">
<span
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-list" class="size-3.5" />
<span>{{ log.commits?.length || 0 }} commits</span>
</span>
<UTooltip v-if="log.date" :text="`Release Date: ${log.date}`">
<span
class="inline-flex cursor-help items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-calendar-days" class="size-3.5" />
<span>{{ moment(log.date).fromNow() }}</span>
</span>
</UTooltip>
</div>
</div>
<div class="flex shrink-0 items-center gap-2">
<UBadge
v-if="isInstalled(log)"
color="success"
variant="soft"
size="sm"
icon="i-lucide-check"
<div class="space-y-3 border-t border-default pt-4">
<article
v-for="commit in log.commits"
:key="commit.sha"
class="flex flex-col gap-2 rounded-md border border-default bg-muted/20 px-3 py-3"
>
Installed
</UBadge>
<UBadge color="neutral" variant="outline" size="sm" icon="i-lucide-list">
{{ log.commits?.length || 0 }} commits
</UBadge>
<UIcon
name="i-lucide-chevron-right"
:class="[
'size-4 text-toned transition-transform',
isReleaseOpen(log.tag) ? 'rotate-90' : '',
]"
/>
</div>
</button>
<div v-if="isReleaseOpen(log.tag)" class="space-y-2">
<article
v-for="commit in log.commits"
:key="commit.sha"
class="rounded-md border border-default bg-elevated/20 px-3 py-3 transition-colors hover:bg-elevated/35"
>
<NuxtLink
:to="`${REPO}/commit/${commit.full_sha}`"
target="_blank"
class="block min-w-0 text-sm font-medium leading-5 text-highlighted transition hover:text-primary"
>
{{ formatCommitMessage(commit.message) }}
</NuxtLink>
<div class="mt-2 flex flex-wrap items-center gap-2 text-xs text-toned">
<span
class="inline-flex items-center gap-1 rounded-sm border border-default px-2 py-1"
>
<UIcon name="i-lucide-user" class="size-3.5 text-muted" />
<span>{{ commit.author }}</span>
</span>
<UTooltip :text="`Date: ${commit.date}`">
<time
class="inline-flex cursor-help items-center gap-1 rounded-sm border border-default px-2 py-1"
>
<UIcon name="i-lucide-clock-3" class="size-3.5 text-muted" />
<span>{{ moment(commit.date).fromNow() }}</span>
</time>
</UTooltip>
<UTooltip :text="`SHA: ${commit.full_sha}`">
<div class="min-w-0">
<NuxtLink
:to="`${REPO}/commit/${commit.full_sha}`"
target="_blank"
class="inline-flex items-center gap-1 rounded-sm border border-default px-2 py-1 font-mono transition hover:border-primary hover:text-primary"
class="block min-w-0 text-sm text-default hover:underline"
>
<UIcon name="i-lucide-git-commit-horizontal" class="size-3.5 text-muted" />
<span>{{ commit.sha }}</span>
<span class="font-semibold text-highlighted">
{{ ucFirst(commit.message).replace(/\.$/, '') }}.
</span>
</NuxtLink>
</UTooltip>
</div>
<span
v-if="commit.full_sha === app_sha"
class="inline-flex items-center gap-1 rounded-sm border border-default px-2 py-1 font-medium"
>
<UIcon name="i-lucide-check" class="size-3.5 text-success" />
<span>Installed</span>
</span>
</div>
</article>
<div class="flex flex-wrap items-center gap-2 text-xs text-toned">
<span
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-user" class="size-3.5" />
{{ commit.author }}
</span>
<UTooltip :text="`Date: ${commit.date}`">
<span
class="inline-flex cursor-help items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-clock-3" class="size-3.5" />
{{ moment(commit.date).fromNow() }}
</span>
</UTooltip>
<UTooltip :text="`SHA: ${commit.full_sha}`">
<span
class="inline-flex cursor-help items-center gap-1 rounded-md border border-default px-2 py-1 font-medium"
>
<UIcon name="i-lucide-git-commit-horizontal" class="size-3.5" />
{{ commit.sha }}
</span>
</UTooltip>
<span
v-if="commit.full_sha === app_sha"
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1 font-medium"
>
<UIcon name="i-lucide-check" class="size-3.5 text-success" />
<span>Installed</span>
</span>
</div>
</article>
</div>
</div>
</section>
</section>
</template>
</UPageCard>
</div>
<div v-else class="space-y-3">
@ -220,7 +206,13 @@ const isLoading = ref(true);
const query = ref('');
const toggleFilter = ref(false);
const latestOnly = useStorage<boolean>('changelog_latest_only', true);
const openReleases = useStorage<string[]>('changelog_open_releases', []);
const pageCardUi = {
root: 'w-full bg-default',
container: 'w-full p-4 sm:p-5',
wrapper: 'w-full items-stretch',
body: 'w-full',
};
watch(toggleFilter, () => {
if (!toggleFilter.value) {
@ -258,27 +250,6 @@ const filteredLogs = computed<changelogs>(() => {
.filter((log): log is changeset => log !== null);
});
watch(filteredLogs, (items) => {
if (openReleases.value.length > 0 || items.length < 1) {
return;
}
openReleases.value = items.slice(0, 3).map((log) => log.tag);
});
const isReleaseOpen = (tag: string): boolean => openReleases.value.includes(tag);
const toggleRelease = (tag: string): void => {
if (isReleaseOpen(tag)) {
openReleases.value = openReleases.value.filter((entry) => entry !== tag);
return;
}
openReleases.value = [...openReleases.value, tag];
};
const formatCommitMessage = (message: string): string => `${ucFirst(message).replace(/\.$/, '')}.`;
const loadContent = async (): Promise<void> => {
if (app_version.value === '' || logs.value.length > 0) {
return;

View file

@ -1,13 +1,17 @@
<template>
<main class="w-full min-w-0 max-w-full space-y-6">
<div class="ytp-page-header">
<div class="ytp-page-heading">
<span class="ytp-page-icon">
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div class="flex min-w-0 items-center gap-3">
<span
class="inline-flex size-11 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon :name="pageShell.icon" class="size-5" />
</span>
<div class="min-w-0 space-y-2">
<div class="ytp-page-kicker">
<div
class="flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned"
>
<span>{{ pageShell.sectionLabel }}</span>
<span>/</span>
<span>{{ pageShell.pageLabel }}</span>
@ -17,7 +21,7 @@
</div>
</div>
<div class="flex w-full flex-wrap items-center gap-2 xl:w-auto xl:justify-end">
<div class="flex min-w-0 flex-wrap items-center justify-end gap-2 xl:justify-end">
<UButton
v-if="items.length > 0"
color="neutral"
@ -80,7 +84,7 @@
<div
v-if="!isLoading && filteredItems.length > 0"
class="flex flex-wrap items-center justify-between gap-3 ytp-card px-3 py-3"
class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-default bg-default px-3 py-3"
>
<div class="flex flex-wrap items-center gap-2">
<UButton
@ -125,11 +129,11 @@
<div
v-if="contentStyle === 'list' && filteredItems.length > 0"
class="w-full min-w-0 max-w-full overflow-hidden ytp-table-surface"
class="w-full min-w-0 max-w-full overflow-hidden rounded-lg border border-default bg-default"
>
<div class="w-full max-w-full overflow-x-auto overscroll-x-contain">
<table class="min-w-235 w-full text-sm">
<thead class="bg-elevated/60 text-xs uppercase tracking-wide text-toned">
<thead class="bg-muted/40 text-xs uppercase tracking-wide text-toned">
<tr
class="text-center [&>th]:border-r [&>th]:border-default/60 [&>th]:px-3 [&>th]:py-3 [&>th]:font-semibold [&>th:last-child]:border-r-0"
>
@ -234,8 +238,16 @@
<div v-else-if="filteredItems.length > 0" class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
<div v-for="cond in filteredItems" :key="cond.id" class="min-w-0 w-full max-w-full">
<div class="ytp-card flex h-full min-w-0 w-full max-w-full flex-col overflow-hidden">
<div class="p-4 pb-3 ytp-border-bottom-soft">
<UCard
class="flex h-full min-w-0 w-full max-w-full flex-col"
:ui="{
root: 'bg-default border border-default',
header: 'p-4 pb-3',
body: 'flex flex-1 flex-col gap-4 p-4 pt-0',
footer: 'border-t border-default px-4 py-4',
}"
>
<template #header>
<div class="flex min-w-0 items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<div class="flex items-start gap-2">
@ -271,104 +283,100 @@
</label>
</div>
</div>
</div>
<div class="flex flex-1 flex-col gap-4 p-4 pt-0">
<div class="space-y-2 text-sm text-default">
<div class="flex flex-wrap gap-2 text-xs text-toned *:min-w-32 *:flex-1">
<button
type="button"
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1 transition hover:border-primary hover:text-default"
:disabled="conditions.addInProgress.value"
@click="() => void toggleEnabled(cond)"
>
<UIcon
name="i-lucide-power"
class="size-3.5"
:class="cond.enabled !== false ? 'text-success' : 'text-error'"
/>
<span>{{ cond.enabled !== false ? 'Enabled' : 'Disabled' }}</span>
</button>
<span
v-if="cond.priority > 0"
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-list-ordered" class="size-3.5" />
<span>Priority: {{ cond.priority }}</span>
</span>
</div>
<div class="feature-meta-grid">
<button
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(cond.id, 'filter')"
>
<UIcon name="i-lucide-filter" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Filter</div>
<span :class="['block', expandClass(cond.id, 'filter')]">{{
cond.filter
}}</span>
</div>
</button>
<button
v-if="cond.cli"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(cond.id, 'cli')"
>
<UIcon name="i-lucide-terminal" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">CLI options</div>
<span :class="['block', expandClass(cond.id, 'cli')]">{{ cond.cli }}</span>
</div>
</button>
</div>
</template>
<div class="space-y-2 text-sm text-default">
<div class="flex flex-wrap gap-2 text-xs text-toned *:min-w-32 *:flex-1">
<button
type="button"
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1 transition hover:border-primary hover:text-default"
:disabled="conditions.addInProgress.value"
@click="() => void toggleEnabled(cond)"
>
<UIcon
name="i-lucide-power"
class="size-3.5"
:class="cond.enabled !== false ? 'text-success' : 'text-error'"
/>
<span>{{ cond.enabled !== false ? 'Enabled' : 'Disabled' }}</span>
</button>
<span
v-if="cond.priority > 0"
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-list-ordered" class="size-3.5" />
<span>Priority: {{ cond.priority }}</span>
</span>
</div>
<div class="feature-meta-grid">
<button
v-if="cond.description"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(cond.id, 'description')"
@click="toggleExpand(cond.id, 'filter')"
>
<UIcon name="i-lucide-align-left" class="mt-0.5 size-4 shrink-0 text-toned" />
<UIcon name="i-lucide-filter" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Description</div>
<span :class="['block', expandClass(cond.id, 'description')]">{{
cond.description
}}</span>
<div class="text-xs font-medium text-toned">Filter</div>
<span :class="['block', expandClass(cond.id, 'filter')]">{{ cond.filter }}</span>
</div>
</button>
<div
v-if="extrasEntries(cond.extras).length > 0"
class="rounded-md border border-default bg-muted/20 px-3 py-2"
<button
v-if="cond.cli"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(cond.id, 'cli')"
>
<div class="mb-2 flex items-center gap-2 text-toned">
<UIcon name="i-lucide-list" class="size-4" />
<span class="text-sm font-medium">Extras</span>
<UIcon name="i-lucide-terminal" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">CLI options</div>
<span :class="['block', expandClass(cond.id, 'cli')]">{{ cond.cli }}</span>
</div>
</button>
</div>
<div class="flex flex-wrap gap-2">
<UBadge
v-for="([key, value], index) in extrasEntries(cond.extras)"
:key="`${cond.id}-${key}-${index}`"
color="info"
variant="soft"
size="sm"
>
<span class="font-semibold">{{ key }}</span
>: {{ value }}
</UBadge>
</div>
<button
v-if="cond.description"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(cond.id, 'description')"
>
<UIcon name="i-lucide-align-left" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Description</div>
<span :class="['block', expandClass(cond.id, 'description')]">{{
cond.description
}}</span>
</div>
</button>
<div
v-if="extrasEntries(cond.extras).length > 0"
class="rounded-md border border-default bg-muted/20 px-3 py-2"
>
<div class="mb-2 flex items-center gap-2 text-toned">
<UIcon name="i-lucide-list" class="size-4" />
<span class="text-sm font-medium">Extras</span>
</div>
<div class="flex flex-wrap gap-2">
<UBadge
v-for="([key, value], index) in extrasEntries(cond.extras)"
:key="`${cond.id}-${key}-${index}`"
color="info"
variant="soft"
size="sm"
>
<span class="font-semibold">{{ key }}</span
>: {{ value }}
</UBadge>
</div>
</div>
</div>
<div class="ytp-border-top-soft px-4 py-4">
<template #footer>
<div class="flex flex-wrap gap-2 *:min-w-32 *:flex-1">
<UButton
color="neutral"
@ -390,8 +398,8 @@
Delete
</UButton>
</div>
</div>
</div>
</template>
</UCard>
</div>
</div>
@ -558,6 +566,7 @@ const bulkActionGroups = computed<DropdownMenuItem[][]>(() => [
{
label: 'Remove Selected',
icon: 'i-lucide-trash',
color: 'error',
disabled: !hasSelected.value || massDelete.value,
onSelect: () => void deleteSelected(),
},

View file

@ -1,13 +1,17 @@
<template>
<main class="flex min-h-0 w-full min-w-0 max-w-full flex-1 flex-col gap-6">
<div class="ytp-page-header">
<div class="ytp-page-heading items-start">
<span class="ytp-page-icon">
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div class="flex min-w-0 items-start gap-3">
<span
class="inline-flex size-11 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon :name="pageShell.icon" class="size-5" />
</span>
<div class="min-w-0 space-y-2">
<div class="ytp-page-kicker">
<div
class="flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned"
>
<span>{{ pageShell.sectionLabel }}</span>
<span>/</span>
<span>{{ pageShell.pageLabel }}</span>
@ -41,324 +45,333 @@
</div>
</div>
<div class="ytp-card flex min-h-0 flex-1 p-0 sm:p-0 flex-col">
<div class="flex min-h-0 flex-1 flex-col gap-4">
<div
class="flex min-h-72 min-w-0 flex-1 overflow-hidden rounded-sm border border-default bg-neutral-950/95 shadow-sm"
>
<div ref="terminal_window" class="terminal-host h-full min-h-0 w-full overflow-hidden" />
</div>
<div class="ytp-card shadow-sm">
<UPageCard variant="naked" :ui="pageCardUi" class="flex min-h-0 flex-1">
<template #body>
<div class="flex min-h-0 flex-1 flex-col gap-4">
<div
class="flex flex-col gap-3 border-b border-default bg-muted/10 px-4 py-3 lg:flex-row lg:items-start lg:justify-between"
class="flex min-h-72 min-w-0 flex-1 overflow-hidden rounded-sm border border-default bg-neutral-950/95 shadow-sm"
>
<div class="min-w-0 flex-1 space-y-1">
<div class="flex flex-wrap items-center justify-between gap-3">
<div
class="flex min-w-0 flex-wrap items-center gap-2 text-sm font-semibold text-highlighted"
>
<UIcon name="i-lucide-send" class="size-4 shrink-0 text-toned" />
<span>Command</span>
<div
ref="terminal_window"
class="terminal-host h-full min-h-0 w-full overflow-hidden"
/>
</div>
<UBadge :color="sessionStatusColor" variant="soft" size="sm">
<span class="inline-flex items-center gap-1.5">
<UIcon
:name="sessionStatusIcon"
class="size-3.5"
:class="sessionStatusSpinning ? 'animate-spin' : ''"
/>
<span>{{ sessionStatusLabel }}</span>
</span>
<div class="rounded-xl border border-default bg-default shadow-sm">
<div
class="flex flex-col gap-3 border-b border-default bg-muted/10 px-4 py-3 lg:flex-row lg:items-start lg:justify-between"
>
<div class="min-w-0 flex-1 space-y-1">
<div class="flex flex-wrap items-center justify-between gap-3">
<div
class="flex min-w-0 flex-wrap items-center gap-2 text-sm font-semibold text-highlighted"
>
<UIcon name="i-lucide-send" class="size-4 shrink-0 text-toned" />
<span>Command</span>
<UBadge :color="sessionStatusColor" variant="soft" size="sm">
<span class="inline-flex items-center gap-1.5">
<UIcon
:name="sessionStatusIcon"
class="size-3.5"
:class="sessionStatusSpinning ? 'animate-spin' : ''"
/>
<span>{{ sessionStatusLabel }}</span>
</span>
</UBadge>
</div>
<UBadge :color="isMultiLineInput ? 'info' : 'neutral'" variant="soft" size="sm">
{{ isMultiLineInput ? 'Multi-line' : 'Single-line' }}
</UBadge>
</div>
<UBadge :color="isMultiLineInput ? 'info' : 'neutral'" variant="soft" size="sm">
{{ isMultiLineInput ? 'Multi-line' : 'Single-line' }}
</UBadge>
<p class="text-xs text-toned">
<kbd><kbd>Shift</kbd>+<kbd>Enter</kbd></kbd> to switch to multi-line input.
</p>
</div>
<p class="text-xs text-toned">
<kbd><kbd>Shift</kbd>+<kbd>Enter</kbd></kbd> to switch to multi-line input.
</p>
</div>
</div>
<div class="space-y-3 px-4 py-4">
<UAlert
v-if="sessionError"
color="error"
variant="soft"
icon="i-lucide-triangle-alert"
:title="sessionErrorTitle"
:description="sessionError"
/>
<div class="space-y-3 px-4 py-4">
<UAlert
v-if="sessionError"
color="error"
variant="soft"
icon="i-lucide-triangle-alert"
:title="sessionErrorTitle"
:description="sessionError"
/>
<UAlert
v-if="sessionStatus === 'reconnecting'"
color="warning"
variant="soft"
icon="i-lucide-rotate-cw"
title="Reconnecting to the command stream"
description="The connection was lost. Attempting to reconnect and restore the stream."
/>
<UAlert
v-if="sessionStatus === 'reconnecting'"
color="warning"
variant="soft"
icon="i-lucide-rotate-cw"
title="Reconnecting to the command stream"
description="The connection was lost. Attempting to reconnect and restore the stream."
/>
<UAlert
v-if="sessionStatus === 'interrupted'"
color="warning"
variant="soft"
icon="i-lucide-circle-off"
title="Session interrupted"
description="The command execution was interrupted."
/>
<UAlert
v-if="sessionStatus === 'interrupted'"
color="warning"
variant="soft"
icon="i-lucide-circle-off"
title="Session interrupted"
description="The command execution was interrupted."
/>
<div class="grid gap-3 xl:grid-cols-[minmax(0,1fr)_auto] xl:items-end">
<div class="space-y-3">
<TextareaAutocomplete
v-if="isMultiLineInput"
ref="commandTextarea"
v-model="command"
class="console-input"
:options="ytDlpOptions"
:disabled="isStartBlocked"
:icon="isLoading ? 'i-lucide-loader-circle' : 'i-lucide-terminal'"
:icon-class="isLoading ? 'animate-spin' : ''"
placeholder="--help"
:rows="5"
@keydown="handleKeyDown"
/>
<div class="grid gap-3 xl:grid-cols-[minmax(0,1fr)_auto] xl:items-end">
<div class="space-y-3">
<TextareaAutocomplete
v-if="isMultiLineInput"
ref="commandTextarea"
v-model="command"
class="console-input"
:options="ytDlpOptions"
:disabled="isStartBlocked"
:icon="isLoading ? 'i-lucide-loader-circle' : 'i-lucide-terminal'"
:icon-class="isLoading ? 'animate-spin' : ''"
placeholder="--help"
:rows="5"
@keydown="handleKeyDown"
/>
<InputAutocomplete
v-else
ref="commandInput"
v-model="command"
class="console-input"
:options="ytDlpOptions"
:disabled="isStartBlocked"
:icon="isLoading ? 'i-lucide-loader-circle' : 'i-lucide-terminal'"
:icon-class="isLoading ? 'animate-spin' : ''"
placeholder="--help"
:multiple="true"
:allowShortFlags="true"
@keydown="handleKeyDown"
@paste="handlePaste"
/>
</div>
<InputAutocomplete
v-else
ref="commandInput"
v-model="command"
class="console-input"
:options="ytDlpOptions"
:disabled="isStartBlocked"
:icon="isLoading ? 'i-lucide-loader-circle' : 'i-lucide-terminal'"
:icon-class="isLoading ? 'animate-spin' : ''"
placeholder="--help"
:multiple="true"
:allowShortFlags="true"
@keydown="handleKeyDown"
@paste="handlePaste"
/>
</div>
<div class="flex flex-wrap items-center justify-end gap-2 xl:self-end">
<UPopover :content="{ side: 'top', align: 'end', sideOffset: 8 }">
<UButton
color="neutral"
variant="outline"
size="lg"
icon="i-lucide-history"
trailing-icon="i-lucide-chevron-up"
class="flex-1 justify-center sm:flex-none sm:min-w-36"
>
History
</UButton>
<template #content>
<UCard
class="w-[min(92vw,42rem)] border border-default/70 shadow-sm"
:ui="historyCardUi"
>
<div class="flex flex-wrap items-center justify-between gap-3">
<div
class="flex items-center gap-2 text-sm font-semibold text-highlighted"
>
<UIcon name="i-lucide-history" class="size-4 text-toned" />
<span>Recent runs</span>
</div>
<UButton
color="neutral"
variant="outline"
size="sm"
icon="i-lucide-eye-off"
:disabled="recentSessionEntries.length < 1"
@click="() => void hideRecentSessions()"
>
Hide recent runs
</UButton>
</div>
<UAlert
v-if="recentSessionEntries.length < 1"
color="info"
variant="soft"
icon="i-lucide-clock-3"
title="No recent console sessions"
/>
<div
v-else
class="max-h-96 w-full min-w-0 max-w-full overflow-hidden rounded-lg border border-default bg-default"
>
<div class="w-full max-w-full overflow-auto overscroll-contain">
<table class="w-full text-sm">
<tbody class="divide-y divide-default">
<tr
v-for="item in recentSessionEntries"
:key="item.sessionId"
class="transition-colors hover:bg-elevated/70 [&>td]:border-r [&>td]:border-default/60 [&>td:last-child]:border-r-0"
>
<td class="px-3 py-3 align-middle">
<div class="space-y-2">
<button
type="button"
class="block w-full text-left font-mono text-xs text-default hover:text-highlighted"
@click="() => void replayRecentSession(item)"
>
{{ item.command }}
</button>
<div
class="flex flex-wrap items-center gap-2 text-[11px] text-toned"
>
<UBadge
:color="recentSessionStatusColor(item)"
variant="soft"
size="sm"
>
<span
v-if="recentSessionStatusSpinning(item)"
class="inline-flex items-center gap-1.5"
>
<UIcon
:name="recentSessionStatusIcon(item)"
class="size-3.5 animate-spin"
/>
<span>{{ recentSessionStatusLabel(item) }}</span>
</span>
<span v-else class="inline-flex items-center gap-1.5">
<UIcon
:name="recentSessionStatusIcon(item)"
class="size-3.5"
/>
<span>{{ recentSessionStatusLabel(item) }}</span>
</span>
</UBadge>
<UTooltip
v-if="item.finishedAt || item.startedAt || item.createdAt"
:text="
moment
.unix(
item.finishedAt ||
item.startedAt ||
item.createdAt ||
0,
)
.format('YYYY-M-DD H:mm Z')
"
>
<span
class="inline-flex items-center gap-1.5 cursor-help"
>
<UIcon name="i-lucide-clock-3" class="size-3.5" />
<time
:datetime="
moment
.unix(
item.finishedAt ||
item.startedAt ||
item.createdAt ||
0,
)
.toISOString()
"
>
{{
moment
.unix(
item.finishedAt ||
item.startedAt ||
item.createdAt ||
0,
)
.fromNow()
}}
</time>
</span>
</UTooltip>
</div>
</div>
</td>
<td
class="w-28 px-3 py-3 text-center align-middle whitespace-nowrap"
>
<div class="flex items-center justify-end gap-1">
<UButton
color="neutral"
variant="ghost"
size="xs"
icon="i-lucide-terminal"
@click="() => void loadCommand(item.command)"
>
Fill
</UButton>
<UButton
color="neutral"
variant="ghost"
size="xs"
icon="i-lucide-x"
square
@click="hideRecentSession(item.sessionId)"
/>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</UCard>
</template>
</UPopover>
<div class="flex flex-wrap items-center justify-end gap-2 xl:self-end">
<UPopover :content="{ side: 'top', align: 'end', sideOffset: 8 }">
<UButton
v-if="showCancelButton"
color="neutral"
variant="outline"
size="lg"
icon="i-lucide-history"
trailing-icon="i-lucide-chevron-up"
icon="i-lucide-power"
:loading="cancelPending"
class="flex-1 justify-center sm:flex-none sm:min-w-36"
@click="() => void cancelCommand()"
>
History
Close output
</UButton>
<template #content>
<UCard
class="w-[min(92vw,42rem)] border border-default/70 shadow-sm"
:ui="historyCardUi"
>
<div class="flex flex-wrap items-center justify-between gap-3">
<div class="flex items-center gap-2 text-sm font-semibold text-highlighted">
<UIcon name="i-lucide-history" class="size-4 text-toned" />
<span>Recent runs</span>
</div>
<UButton
v-else-if="canManualReconnect"
color="neutral"
variant="outline"
size="lg"
icon="i-lucide-rotate-cw"
:loading="manualReconnectPending"
class="flex-1 justify-center sm:flex-none sm:min-w-36"
@click="() => void reconnectSession()"
>
Reconnect
</UButton>
<UButton
color="neutral"
variant="outline"
size="sm"
icon="i-lucide-eye-off"
:disabled="recentSessionEntries.length < 1"
@click="() => void hideRecentSessions()"
>
Hide recent runs
</UButton>
</div>
<UAlert
v-if="recentSessionEntries.length < 1"
color="info"
variant="soft"
icon="i-lucide-clock-3"
title="No recent console sessions"
/>
<div
v-else
class="max-h-96 w-full min-w-0 max-w-full overflow-hidden ytp-table-surface"
>
<div class="w-full max-w-full overflow-auto overscroll-contain">
<table class="w-full text-sm">
<tbody class="divide-y divide-default">
<tr
v-for="item in recentSessionEntries"
:key="item.sessionId"
class="transition-colors hover:bg-elevated/70 [&>td]:border-r [&>td]:border-default/60 [&>td:last-child]:border-r-0"
>
<td class="px-3 py-3 align-middle">
<div class="space-y-2">
<button
type="button"
class="block w-full text-left font-mono text-xs text-default hover:text-highlighted"
@click="() => void replayRecentSession(item)"
>
{{ item.command }}
</button>
<div
class="flex flex-wrap items-center gap-2 text-[11px] text-toned"
>
<UBadge
:color="recentSessionStatusColor(item)"
variant="soft"
size="sm"
>
<span
v-if="recentSessionStatusSpinning(item)"
class="inline-flex items-center gap-1.5"
>
<UIcon
:name="recentSessionStatusIcon(item)"
class="size-3.5 animate-spin"
/>
<span>{{ recentSessionStatusLabel(item) }}</span>
</span>
<span v-else class="inline-flex items-center gap-1.5">
<UIcon
:name="recentSessionStatusIcon(item)"
class="size-3.5"
/>
<span>{{ recentSessionStatusLabel(item) }}</span>
</span>
</UBadge>
<UTooltip
v-if="item.finishedAt || item.startedAt || item.createdAt"
:text="
moment
.unix(
item.finishedAt ||
item.startedAt ||
item.createdAt ||
0,
)
.format('YYYY-M-DD H:mm Z')
"
>
<span class="inline-flex items-center gap-1.5 cursor-help">
<UIcon name="i-lucide-clock-3" class="size-3.5" />
<time
:datetime="
moment
.unix(
item.finishedAt ||
item.startedAt ||
item.createdAt ||
0,
)
.toISOString()
"
>
{{
moment
.unix(
item.finishedAt ||
item.startedAt ||
item.createdAt ||
0,
)
.fromNow()
}}
</time>
</span>
</UTooltip>
</div>
</div>
</td>
<td
class="w-28 px-3 py-3 text-center align-middle whitespace-nowrap"
>
<div class="flex items-center justify-end gap-1">
<UButton
color="neutral"
variant="ghost"
size="xs"
icon="i-lucide-terminal"
@click="() => void loadCommand(item.command)"
>
Fill
</UButton>
<UButton
color="neutral"
variant="ghost"
size="xs"
icon="i-lucide-x"
square
@click="hideRecentSession(item.sessionId)"
/>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</UCard>
</template>
</UPopover>
<UButton
v-if="showCancelButton"
color="neutral"
variant="outline"
size="lg"
icon="i-lucide-power"
:loading="cancelPending"
class="flex-1 justify-center sm:flex-none sm:min-w-36"
@click="() => void cancelCommand()"
>
Close output
</UButton>
<UButton
v-else-if="canManualReconnect"
color="neutral"
variant="outline"
size="lg"
icon="i-lucide-rotate-cw"
:loading="manualReconnectPending"
class="flex-1 justify-center sm:flex-none sm:min-w-36"
@click="() => void reconnectSession()"
>
Reconnect
</UButton>
<UButton
v-else
color="primary"
size="lg"
:icon="isLoading ? 'i-lucide-loader-circle' : 'i-lucide-send'"
:loading="isLoading"
:disabled="!canStartCommand"
class="flex-1 justify-center sm:flex-none sm:min-w-36"
@click="() => void runCommand()"
>
{{ runButtonLabel }}
</UButton>
<UButton
v-else
color="primary"
size="lg"
:icon="isLoading ? 'i-lucide-loader-circle' : 'i-lucide-send'"
:loading="isLoading"
:disabled="!canStartCommand"
class="flex-1 justify-center sm:flex-none sm:min-w-36"
@click="() => void runCommand()"
>
{{ runButtonLabel }}
</UButton>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
</UPageCard>
</main>
</template>
@ -423,6 +436,13 @@ const commandInput = ref<InstanceType<typeof InputAutocomplete> | null>(null);
const commandTextarea = ref<InstanceType<typeof TextareaAutocomplete> | null>(null);
const storedCommand = useStorage<string>('console_command', '');
const pageCardUi = {
root: 'flex min-h-0 flex-1 w-full bg-transparent',
container: 'flex min-h-0 flex-1 w-full p-0 sm:p-0',
wrapper: 'flex min-h-0 flex-1 w-full items-stretch',
body: 'flex min-h-0 flex-1 w-full flex-col',
};
const historyCardUi = {
body: 'space-y-3 p-4',
};

View file

@ -1,13 +1,17 @@
<template>
<main class="w-full min-w-0 max-w-full space-y-6">
<div class="ytp-page-header">
<div class="ytp-page-heading items-start">
<span class="ytp-page-icon">
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div class="flex min-w-0 items-start gap-3">
<span
class="inline-flex size-11 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon :name="pageShell.icon" class="size-5" />
</span>
<div class="min-w-0 space-y-2">
<div class="ytp-page-kicker">
<div
class="flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned"
>
<span>{{ pageShell.sectionLabel }}</span>
<span>/</span>
<span>{{ pageShell.pageLabel }}</span>
@ -17,7 +21,7 @@
</div>
</div>
<div class="flex w-full flex-wrap items-center gap-2 xl:w-auto xl:justify-end">
<div class="flex min-w-0 flex-wrap items-center justify-end gap-2 xl:justify-end">
<UButton
color="neutral"
variant="outline"
@ -43,7 +47,10 @@
</div>
</div>
<div v-if="!report && isLoading" class="ytp-card flex min-h-72 items-center justify-center">
<div
v-if="!report && isLoading"
class="flex min-h-72 items-center justify-center rounded-md border border-default bg-default/90"
>
<div class="flex flex-col items-center gap-3 text-center text-toned">
<UIcon name="i-lucide-loader-circle" class="size-10 animate-spin text-info" />
<div class="space-y-1">
@ -79,15 +86,28 @@
</div>
<div class="grid gap-3 sm:grid-cols-2 xl:grid-cols-4">
<StatCard
<article
v-for="item in summaryCards"
:key="item.label"
:label="item.label"
:value="item.value"
:hint="item.description"
:icon="item.icon"
:color="item.color"
/>
class="rounded-md border border-default bg-default px-3 py-3 shadow-sm"
>
<div class="flex items-start justify-between gap-3">
<div class="min-w-0">
<p class="text-xs font-medium uppercase tracking-wide text-toned">
{{ item.label }}
</p>
<p class="mt-1 text-xs text-toned">{{ item.description }}</p>
</div>
<span
class="inline-flex size-8 shrink-0 items-center justify-center rounded-md border border-default bg-default"
>
<UIcon :name="item.icon" class="size-4 text-toned" />
</span>
</div>
<p :class="['mt-4 text-2xl font-semibold', item.valueClass]">{{ item.value }}</p>
</article>
</div>
</section>
@ -98,16 +118,28 @@
</div>
<div class="grid gap-3 sm:grid-cols-2 xl:grid-cols-3">
<StatCard
<article
v-for="row in runtimeRows"
:key="row.label"
:label="row.label"
:value="row.value"
:hint="row.description"
:icon="row.icon"
color="neutral"
value-wrap
/>
class="rounded-md border border-default bg-default px-3 py-3 shadow-sm"
>
<div class="flex items-start justify-between gap-3">
<div class="min-w-0">
<p class="text-xs font-medium uppercase tracking-wide text-toned">
{{ row.label }}
</p>
<p class="mt-1 text-xs text-toned">{{ row.description }}</p>
</div>
<span
class="inline-flex size-8 shrink-0 items-center justify-center rounded-md border border-default bg-default"
>
<UIcon :name="row.icon" class="size-4 text-toned" />
</span>
</div>
<p class="mt-4 wrap-break-word text-sm font-semibold text-default">{{ row.value }}</p>
</article>
</div>
</section>
@ -123,7 +155,11 @@
</div>
<div class="grid gap-4 lg:grid-cols-2 2xl:grid-cols-3">
<article v-for="item in section.items" :key="item.id" class="ytp-card-padded shadow-sm">
<article
v-for="item in section.items"
:key="item.id"
class="rounded-md border border-default bg-default px-4 py-4 shadow-sm"
>
<div class="min-w-0 space-y-3">
<div class="space-y-2">
<div class="flex flex-wrap items-center gap-2">
@ -162,7 +198,6 @@
<script setup lang="ts">
import moment from 'moment';
import StatCard from '~/components/StatCard.vue';
import type { DiagnosticCheck, DiagnosticStatus } from '~/types/diagnostics';
import { requirePageShell } from '~/utils/topLevelNavigation';
import { copyText } from '~/utils';
@ -172,7 +207,7 @@ type SummaryCard = {
description: string;
value: number;
icon: string;
color: 'success' | 'error' | 'warning' | 'neutral';
valueClass: string;
};
type DetailRow = {
@ -260,28 +295,28 @@ const summaryCards = computed<Array<SummaryCard>>(() => {
description: 'Checks that passed.',
value: current.summary.pass,
icon: 'i-lucide-badge-check',
color: current.summary.pass > 0 ? 'success' : 'neutral',
valueClass: current.summary.pass > 0 ? 'text-success' : 'text-default',
},
{
label: 'Required fails',
description: 'Items that block core use.',
value: current.summary.required_failed,
icon: 'i-lucide-octagon-alert',
color: current.summary.required_failed > 0 ? 'error' : 'neutral',
valueClass: current.summary.required_failed > 0 ? 'text-error' : 'text-default',
},
{
label: 'Warnings',
description: 'Optional or incomplete items.',
value: current.summary.warn,
icon: 'i-lucide-triangle-alert',
color: current.summary.warn > 0 ? 'warning' : 'neutral',
valueClass: current.summary.warn > 0 ? 'text-warning' : 'text-default',
},
{
label: 'Skipped',
description: 'Not configured or not needed.',
value: current.summary.skip,
icon: 'i-lucide-minus',
color: 'neutral',
valueClass: current.summary.skip > 0 ? 'text-toned' : 'text-default',
},
];
});

View file

@ -1,13 +1,17 @@
<template>
<main class="w-full min-w-0 max-w-full space-y-6">
<div class="ytp-page-header">
<div class="ytp-page-heading">
<span class="ytp-page-icon">
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div class="flex min-w-0 items-center gap-3">
<span
class="inline-flex size-11 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon :name="pageShell.icon" class="size-5" />
</span>
<div class="min-w-0 space-y-2">
<div class="ytp-page-kicker">
<div
class="flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned"
>
<span>{{ pageShell.sectionLabel }}</span>
<span>/</span>
<span>{{ pageShell.pageLabel }}</span>
@ -17,7 +21,7 @@
</div>
</div>
<div class="flex w-full flex-wrap items-center gap-2 xl:w-auto xl:justify-end">
<div class="flex min-w-0 flex-wrap items-center justify-end gap-2 xl:justify-end">
<UButton
v-if="items.length > 0"
color="neutral"
@ -80,7 +84,7 @@
<div
v-if="!isLoading && filteredItems.length > 0"
class="flex flex-wrap items-center justify-between gap-3 ytp-card px-3 py-3"
class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-default bg-default px-3 py-3"
>
<div class="flex flex-wrap items-center gap-2">
<UButton
@ -125,11 +129,11 @@
<div
v-if="contentStyle === 'list' && filteredItems.length > 0"
class="w-full min-w-0 max-w-full overflow-hidden ytp-table-surface"
class="w-full min-w-0 max-w-full overflow-hidden rounded-lg border border-default bg-default"
>
<div class="w-full max-w-full overflow-x-auto overscroll-x-contain">
<table class="min-w-235 w-full text-sm">
<thead class="bg-elevated/60 text-xs uppercase tracking-wide text-toned">
<thead class="bg-muted/40 text-xs uppercase tracking-wide text-toned">
<tr
class="text-center [&>th]:border-r [&>th]:border-default/60 [&>th]:px-3 [&>th]:py-3 [&>th]:font-semibold [&>th:last-child]:border-r-0"
>
@ -233,8 +237,16 @@
<div v-else-if="filteredItems.length > 0" class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
<div v-for="field in filteredItems" :key="field.id" class="min-w-0 w-full max-w-full">
<div class="ytp-card flex h-full min-w-0 w-full max-w-full flex-col overflow-hidden">
<div class="p-4 pb-3 ytp-border-bottom-soft">
<UCard
class="flex h-full min-w-0 w-full max-w-full flex-col"
:ui="{
root: 'bg-default border border-default',
header: 'p-4 pb-3',
body: 'flex flex-1 flex-col gap-4 p-4 pt-0',
footer: 'border-t border-default px-4 py-4',
}"
>
<template #header>
<div class="flex min-w-0 items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<div class="flex items-start gap-2">
@ -270,63 +282,59 @@
</label>
</div>
</div>
</div>
</template>
<div class="flex flex-1 flex-col gap-4 p-4 pt-0">
<div class="space-y-2 text-sm text-default">
<div class="flex flex-wrap gap-2 text-xs text-toned *:min-w-32 *:flex-1">
<span
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-list-ordered" class="size-3.5" />
<span>Order: {{ field.order }}</span>
</span>
<div class="space-y-2 text-sm text-default">
<div class="flex flex-wrap gap-2 text-xs text-toned *:min-w-32 *:flex-1">
<span
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-list-ordered" class="size-3.5" />
<span>Order: {{ field.order }}</span>
</span>
<span
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-shapes" class="size-3.5" />
<span>Type: {{ field.kind }}</span>
</span>
</div>
<span
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-shapes" class="size-3.5" />
<span>Type: {{ field.kind }}</span>
</span>
</div>
<div class="feature-meta-grid">
<button
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(field.id, 'field')"
>
<UIcon name="i-lucide-terminal" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Associated option</div>
<span :class="['block', expandClass(field.id, 'field')]">{{
field.field
}}</span>
</div>
</button>
<div class="feature-meta-grid">
<button
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(field.id, 'field')"
>
<UIcon name="i-lucide-terminal" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Associated option</div>
<span :class="['block', expandClass(field.id, 'field')]">{{ field.field }}</span>
</div>
</button>
<button
v-if="field.description"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(field.id, 'description')"
>
<UIcon
name="i-lucide-message-square-text"
class="mt-0.5 size-4 shrink-0 text-toned"
/>
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Description</div>
<span :class="['block', expandClass(field.id, 'description')]">
{{ field.description }}
</span>
</div>
</button>
</div>
<button
v-if="field.description"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(field.id, 'description')"
>
<UIcon
name="i-lucide-message-square-text"
class="mt-0.5 size-4 shrink-0 text-toned"
/>
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Description</div>
<span :class="['block', expandClass(field.id, 'description')]">
{{ field.description }}
</span>
</div>
</button>
</div>
</div>
<div class="ytp-border-top-soft px-4 py-4">
<template #footer>
<div class="flex flex-wrap gap-2 *:min-w-32 *:flex-1">
<UButton
color="neutral"
@ -348,8 +356,8 @@
Delete
</UButton>
</div>
</div>
</div>
</template>
</UCard>
</div>
</div>
@ -483,6 +491,7 @@ const bulkActionGroups = computed<DropdownMenuItem[][]>(() => [
{
label: 'Remove Selected',
icon: 'i-lucide-trash',
color: 'error',
disabled: !hasSelected.value || massDelete.value,
onSelect: () => void deleteSelected(),
},

View file

@ -1,13 +1,17 @@
<template>
<main class="w-full min-w-0 max-w-full space-y-6">
<div class="ytp-page-header">
<div class="ytp-page-heading">
<span class="ytp-page-icon">
<main class="space-y-6">
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div class="flex min-w-0 items-center gap-3">
<span
class="inline-flex size-11 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon :name="pageShell.icon" class="size-5" />
</span>
<div class="min-w-0 space-y-2">
<div class="ytp-page-kicker">
<div
class="flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned"
>
<span>{{ pageShell.sectionLabel }}</span>
<span>/</span>
<span>{{ pageShell.pageLabel }}</span>
@ -16,24 +20,42 @@
<p class="max-w-3xl text-sm text-toned">{{ pageShell.description }}</p>
</div>
</div>
</div>
<div class="ytp-card p-0 overflow-hidden">
<div class="min-w-0 max-w-full px-4 py-5 sm:px-6 sm:py-6 lg:px-7">
<Markdown :file="`/api/docs/${docEntry.file}`" />
<div class="flex flex-wrap justify-end gap-2 xl:justify-end">
<UButton
v-for="entry in docsEntries"
:key="entry.id"
:to="entry.route"
:color="entry.file === docEntry.file ? 'primary' : 'neutral'"
:variant="entry.file === docEntry.file ? 'solid' : 'outline'"
size="sm"
:icon="entry.icon"
>
{{ entry.navLabel }}
</UButton>
</div>
</div>
<UPageCard variant="outline" :ui="pageCardUi">
<template #body>
<div class="px-4 py-5 sm:px-6 sm:py-6 lg:px-7">
<Markdown :file="`/api/docs/${docEntry.file}`" />
</div>
</template>
</UPageCard>
</main>
</template>
<script setup lang="ts">
import Markdown from '~/components/Markdown.vue';
import { getDocsEntryBySlug } from '~/composables/useDocs';
import { DOCS_ENTRIES, getDocsEntryBySlug } from '~/composables/useDocs';
import { formatPageTitle } from '~/utils';
import { requirePageShell } from '~/utils/topLevelNavigation';
const route = useRoute();
const docsEntries = DOCS_ENTRIES;
const docEntry = computed(() => {
const entry = getDocsEntryBySlug(route.params.slug as string | string[] | undefined);
@ -52,4 +74,11 @@ const pageShell = computed(() => requirePageShell(docEntry.value.id));
useHead(() => ({
title: formatPageTitle(docEntry.value.title),
}));
const pageCardUi = {
root: 'w-full bg-default',
container: 'w-full p-0',
wrapper: 'w-full items-stretch',
body: 'w-full p-0',
};
</script>

View file

@ -6,14 +6,18 @@
aria-hidden="true"
/>
<div class="ytp-page-header">
<div class="ytp-page-heading">
<span class="ytp-page-icon">
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div class="flex min-w-0 items-center gap-3">
<span
class="inline-flex size-11 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon :name="pageShell.icon" class="size-5" />
</span>
<div class="min-w-0 space-y-2">
<div class="ytp-page-kicker">
<div
class="flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned"
>
<span>{{ pageShell.sectionLabel }}</span>
<span>/</span>
<span>{{ pageShell.pageLabel }}</span>
@ -23,7 +27,7 @@
</div>
</div>
<div class="flex w-full flex-wrap items-center gap-2 xl:w-auto xl:justify-end">
<div class="flex min-w-0 flex-wrap items-center justify-end gap-2 xl:justify-end">
<UButton
color="neutral"
variant="outline"
@ -98,7 +102,7 @@
<div class="w-full min-w-0 max-w-full space-y-4">
<div
v-if="hasItems"
class="flex flex-wrap items-center justify-between gap-3 ytp-card px-3 py-3"
class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-default bg-default px-3 py-3"
>
<div class="flex flex-wrap items-center gap-2">
<UButton
@ -139,11 +143,11 @@
<div
v-if="'list' === contentStyle && hasItems"
class="w-full min-w-0 max-w-full overflow-hidden ytp-table-surface"
class="w-full min-w-0 max-w-full overflow-hidden rounded-lg border border-default bg-default"
>
<div class="w-full max-w-full overflow-x-auto overscroll-x-contain">
<table class="min-w-210 table-fixed w-full text-sm">
<thead class="bg-elevated/60 text-xs uppercase tracking-wide text-toned">
<thead class="bg-muted/40 text-xs uppercase tracking-wide text-toned">
<tr
class="text-center [&>th]:border-r [&>th]:border-default/60 [&>th]:px-3 [&>th]:py-3 [&>th]:font-semibold [&>th:last-child]:border-r-0"
>
@ -391,8 +395,16 @@
:min-height="showThumbnails ? 410 : 210"
class="min-h-0 min-w-0 w-full max-w-full"
>
<div class="ytp-card flex h-full min-w-0 w-full max-w-full flex-col overflow-hidden">
<div class="ytp-border-bottom-soft p-4 pb-3">
<UCard
class="flex h-full min-w-0 w-full max-w-full flex-col overflow-hidden"
:ui="{
body: 'flex flex-1 flex-col gap-4 p-4',
footer: 'border-t border-default px-4 py-4',
header: 'p-4 pb-3',
root: 'bg-default border border-default',
}"
>
<template #header>
<div class="flex min-w-0 flex-wrap items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<UTooltip :text="item.title">
@ -462,221 +474,219 @@
</label>
</div>
</div>
</template>
<div
v-if="showThumbnails"
class="-mx-4 -mt-4 overflow-hidden border-b border-default bg-muted/20"
>
<figure :class="['relative w-full overflow-hidden', thumbnailRatioClass]">
<span v-if="item.filename" class="play-overlay" @click="video_item = item">
<span class="play-icon" aria-hidden="true">
<UIcon name="i-lucide-play" class="size-6 translate-x-px text-white" />
</span>
<img
v-if="getGridImage(item)"
:src="getGridImage(item)"
@load="pImg"
@error="onImgError($event, item)"
/>
<img v-else src="/images/placeholder.png" />
</span>
<span
v-else-if="isEmbedable(item.url)"
class="play-overlay"
@click="embed_url = getEmbedable(item.url) as string"
>
<span class="play-icon embed-icon" aria-hidden="true">
<UIcon name="i-lucide-play" class="size-6 translate-x-px text-white" />
</span>
<img
v-if="getGridImage(item)"
:src="getGridImage(item)"
@load="pImg"
@error="onImgError($event, item)"
/>
<img v-else src="/images/placeholder.png" />
</span>
<template v-else>
<img
v-if="getGridImage(item)"
:src="getGridImage(item)"
@load="pImg"
@error="onImgError($event, item)"
/>
<img v-else src="/images/placeholder.png" />
</template>
</figure>
</div>
<div class="flex flex-1 flex-col gap-4 p-4">
<div
v-if="showThumbnails"
class="-mx-4 -mt-4 overflow-hidden border-b border-default bg-muted/20"
<div class="flex flex-wrap gap-2 text-sm *:min-w-32 *:flex-1">
<button
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-default transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'status')"
>
<figure :class="['relative w-full overflow-hidden', thumbnailRatioClass]">
<span v-if="item.filename" class="play-overlay" @click="video_item = item">
<span class="play-icon" aria-hidden="true">
<UIcon name="i-lucide-play" class="size-6 translate-x-px text-white" />
</span>
<img
v-if="getGridImage(item)"
:src="getGridImage(item)"
@load="pImg"
@error="onImgError($event, item)"
/>
<img v-else src="/images/placeholder.png" />
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon
:name="setIcon(item)"
:class="[setIconColor(item), isQueuedAnimation(item), 'size-4 shrink-0']"
/>
<span :class="['min-w-0 text-center', expandClass(item._id, 'status')]">
{{ setStatus(item) }}
</span>
</span>
</button>
<span
v-else-if="isEmbedable(item.url)"
class="play-overlay"
@click="embed_url = getEmbedable(item.url) as string"
>
<span class="play-icon embed-icon" aria-hidden="true">
<UIcon name="i-lucide-play" class="size-6 translate-x-px text-white" />
</span>
<img
v-if="getGridImage(item)"
:src="getGridImage(item)"
@load="pImg"
@error="onImgError($event, item)"
/>
<img v-else src="/images/placeholder.png" />
</span>
<template v-else>
<img
v-if="getGridImage(item)"
:src="getGridImage(item)"
@load="pImg"
@error="onImgError($event, item)"
/>
<img v-else src="/images/placeholder.png" />
</template>
</figure>
</div>
<div class="flex flex-wrap gap-2 text-sm *:min-w-32 *:flex-1">
<button
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-default transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'status')"
>
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon
:name="setIcon(item)"
:class="[setIconColor(item), isQueuedAnimation(item), 'size-4 shrink-0']"
/>
<span :class="['min-w-0 text-center', expandClass(item._id, 'status')]">
{{ setStatus(item) }}
</span>
</span>
</button>
<button
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-default transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'preset')"
>
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon name="i-lucide-sliders-horizontal" class="size-4 shrink-0 text-toned" />
<span :class="['min-w-0 text-center', expandClass(item._id, 'preset')]">
{{ item.preset }}
</span>
</span>
</button>
<button
v-if="'not_live' === item.status && (item.live_in || item.extras?.release_in)"
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-toned transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'retry_at')"
>
<UTooltip
:text="`Retry at: ${moment(item.live_in || item.extras?.release_in).format('YYYY-M-DD H:mm Z')}`"
>
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon name="i-lucide-calendar" class="size-4 shrink-0 text-toned" />
<span
:class="['min-w-0 text-center', expandClass(item._id, 'retry_at')]"
:date-datetime="item.live_in || item.extras?.release_in"
v-rtime="item.live_in || item.extras?.release_in"
/>
</span>
</UTooltip>
</button>
<button
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-toned transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'datetime')"
>
<UTooltip :text="moment(item.datetime).format('YYYY-M-DD H:mm Z')">
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon name="i-lucide-clock-3" class="size-4 shrink-0 text-toned" />
<span
:class="['min-w-0 text-center', expandClass(item._id, 'datetime')]"
:date-datetime="item.datetime"
v-rtime="item.datetime"
/>
</span>
</UTooltip>
</button>
<button
v-if="item.file_size"
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-toned transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'size')"
>
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon name="i-lucide-hard-drive" class="size-4 shrink-0 text-toned" />
<span :class="['min-w-0 text-center', expandClass(item._id, 'size')]">
{{ formatBytes(item.file_size) }}
</span>
</span>
</button>
</div>
<div
v-if="item.error || showMessage(item)"
class="space-y-2 border-t border-default pt-3"
<button
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-default transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'preset')"
>
<p
v-if="item.error"
:class="messageClass(item._id, 'error', 'card')"
@click="toggleMessage(item._id, 'error', 'card')"
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon name="i-lucide-sliders-horizontal" class="size-4 shrink-0 text-toned" />
<span :class="['min-w-0 text-center', expandClass(item._id, 'preset')]">
{{ item.preset }}
</span>
</span>
</button>
<button
v-if="'not_live' === item.status && (item.live_in || item.extras?.release_in)"
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-toned transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'retry_at')"
>
<UTooltip
:text="`Retry at: ${moment(item.live_in || item.extras?.release_in).format('YYYY-M-DD H:mm Z')}`"
>
{{ item.error }}
</p>
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon name="i-lucide-calendar" class="size-4 shrink-0 text-toned" />
<span
:class="['min-w-0 text-center', expandClass(item._id, 'retry_at')]"
:date-datetime="item.live_in || item.extras?.release_in"
v-rtime="item.live_in || item.extras?.release_in"
/>
</span>
</UTooltip>
</button>
<p
v-if="showMessage(item)"
:class="messageClass(item._id, 'msg', 'card')"
@click="toggleMessage(item._id, 'msg', 'card')"
>
{{ item.msg }}
</p>
</div>
<button
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-toned transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'datetime')"
>
<UTooltip :text="moment(item.datetime).format('YYYY-M-DD H:mm Z')">
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon name="i-lucide-clock-3" class="size-4 shrink-0 text-toned" />
<span
:class="['min-w-0 text-center', expandClass(item._id, 'datetime')]"
:date-datetime="item.datetime"
v-rtime="item.datetime"
/>
</span>
</UTooltip>
</button>
<div class="ytp-border-top-soft px-4 py-4">
<div class="flex flex-wrap gap-2 *:min-w-32 *:flex-1">
<UButton
v-if="showRetryAction(item)"
color="neutral"
variant="outline"
icon="i-lucide-rotate-cw"
class="w-full justify-center"
@click="() => retryItem(item, false)"
>
Retry
</UButton>
<UButton
v-if="item.filename && canShareUrl"
color="neutral"
variant="outline"
icon="i-lucide-share"
class="w-full justify-center"
@click="() => shareUrl(item)"
>
Share
</UButton>
<UButton
v-if="item.filename"
color="neutral"
variant="outline"
icon="i-lucide-download"
class="w-full justify-center"
external
:href="makeDownload(config, item)"
:download="item.filename?.split('/').reverse()[0]"
>
Download
</UButton>
<UButton
color="neutral"
variant="outline"
icon="i-lucide-trash"
class="w-full justify-center"
@click="() => removeItem(item)"
>
{{ config.app.remove_files ? 'Remove' : 'Clear' }}
</UButton>
<UDropdownMenu :items="itemActionGroups(item)" :modal="false" class="w-full">
<UButton
color="neutral"
variant="outline"
icon="i-lucide-settings-2"
trailing-icon="i-lucide-chevron-down"
class="w-full justify-center"
>
Actions
</UButton>
</UDropdownMenu>
</div>
</div>
<button
v-if="item.file_size"
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-toned transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'size')"
>
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon name="i-lucide-hard-drive" class="size-4 shrink-0 text-toned" />
<span :class="['min-w-0 text-center', expandClass(item._id, 'size')]">
{{ formatBytes(item.file_size) }}
</span>
</span>
</button>
</div>
</div>
<div
v-if="item.error || showMessage(item)"
class="space-y-2 border-t border-default pt-3"
>
<p
v-if="item.error"
:class="messageClass(item._id, 'error', 'card')"
@click="toggleMessage(item._id, 'error', 'card')"
>
{{ item.error }}
</p>
<p
v-if="showMessage(item)"
:class="messageClass(item._id, 'msg', 'card')"
@click="toggleMessage(item._id, 'msg', 'card')"
>
{{ item.msg }}
</p>
</div>
<template #footer>
<div class="flex flex-wrap gap-2 *:min-w-32 *:flex-1">
<UButton
v-if="showRetryAction(item)"
color="neutral"
variant="outline"
icon="i-lucide-rotate-cw"
class="w-full justify-center"
@click="() => retryItem(item, false)"
>
Retry
</UButton>
<UButton
v-if="item.filename && canShareUrl"
color="neutral"
variant="outline"
icon="i-lucide-share"
class="w-full justify-center"
@click="() => shareUrl(item)"
>
Share
</UButton>
<UButton
v-if="item.filename"
color="neutral"
variant="outline"
icon="i-lucide-download"
class="w-full justify-center"
external
:href="makeDownload(config, item)"
:download="item.filename?.split('/').reverse()[0]"
>
Download
</UButton>
<UButton
color="neutral"
variant="outline"
icon="i-lucide-trash"
class="w-full justify-center"
@click="() => removeItem(item)"
>
{{ config.app.remove_files ? 'Remove' : 'Clear' }}
</UButton>
<UDropdownMenu :items="itemActionGroups(item)" :modal="false" class="w-full">
<UButton
color="neutral"
variant="outline"
icon="i-lucide-settings-2"
trailing-icon="i-lucide-chevron-down"
class="w-full justify-center"
>
Actions
</UButton>
</UDropdownMenu>
</div>
</template>
</UCard>
</LateLoader>
</div>

View file

@ -1,13 +1,17 @@
<template>
<div class="space-y-6">
<div class="ytp-page-header">
<div class="ytp-page-heading">
<span class="ytp-page-icon">
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div class="flex min-w-0 items-center gap-3">
<span
class="inline-flex size-11 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon :name="pageShell.icon" class="size-5" />
</span>
<div class="min-w-0 space-y-2">
<div class="ytp-page-kicker">
<div
class="flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned"
>
<span>{{ pageShell.sectionLabel }}</span>
<span>/</span>
<span>{{ pageShell.pageLabel }}</span>
@ -17,7 +21,7 @@
</div>
</div>
<div class="flex w-full flex-wrap items-center gap-2 xl:w-auto xl:justify-end">
<div class="flex min-w-0 flex-wrap items-center justify-end gap-2 xl:justify-end">
<UButton
color="neutral"
:variant="toggleFilter ? 'soft' : 'outline'"
@ -52,7 +56,7 @@
<UButton
color="neutral"
:variant="config.showForm ? 'soft' : 'outline'"
variant="outline"
size="sm"
icon="i-lucide-plus"
@click="config.showForm = !config.showForm"
@ -105,7 +109,7 @@
<div class="w-full min-w-0 max-w-full space-y-4">
<div v-if="!socket.isConnected" class="flex justify-end">
<UButton
color="neutral"
color="info"
variant="outline"
size="sm"
icon="i-lucide-refresh-cw"
@ -118,7 +122,7 @@
<div
v-if="hasItems"
class="flex flex-wrap items-center justify-between gap-3 ytp-card px-3 py-3"
class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-default bg-default px-3 py-3"
>
<div class="flex flex-wrap items-center gap-2">
<UButton
@ -171,11 +175,11 @@
<div
v-if="'list' === contentStyle && hasItems"
class="w-full min-w-0 max-w-full overflow-hidden ytp-table-surface"
class="w-full min-w-0 max-w-full overflow-hidden rounded-lg border border-default bg-default"
>
<div class="w-full max-w-full overflow-x-auto overscroll-x-contain">
<table class="min-w-210 table-fixed w-full text-sm">
<thead class="bg-elevated/60 text-xs uppercase tracking-wide text-toned">
<thead class="bg-muted/40 text-xs uppercase tracking-wide text-toned">
<tr
class="text-center [&>th]:border-r [&>th]:border-default/60 [&>th]:px-3 [&>th]:py-3 [&>th]:font-semibold [&>th:last-child]:border-r-0"
>
@ -405,8 +409,16 @@
:min-height="showThumbnails ? 475 : 265"
class="min-h-0 min-w-0 w-full max-w-full"
>
<div class="ytp-card flex h-full min-w-0 w-full max-w-full flex-col overflow-hidden">
<div class="p-4 pb-3 ytp-border-bottom-soft">
<UCard
class="flex h-full min-w-0 w-full max-w-full flex-col overflow-hidden"
:ui="{
body: 'flex flex-1 flex-col gap-4 p-4',
footer: 'border-t border-default px-4 py-4',
header: 'p-4 pb-3',
root: 'bg-default border border-default',
}"
>
<template #header>
<div class="flex min-w-0 flex-wrap items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<UTooltip :text="item.title">
@ -482,129 +494,124 @@
</label>
</div>
</div>
</template>
<div
v-if="showThumbnails"
class="-mx-4 -mt-4 overflow-hidden border-b border-default bg-muted/20"
>
<figure :class="['relative w-full overflow-hidden', thumbnailRatioClass]">
<span
v-if="isEmbedable(item.url)"
class="play-overlay"
@click="embed_url = getEmbedable(item.url) as string"
>
<span class="play-icon embed-icon" aria-hidden="true">
<UIcon name="i-lucide-play" class="size-6 translate-x-px text-white" />
</span>
<img
v-if="getGridImage(item)"
:src="getGridImage(item)"
@load="pImg"
@error="onImgError"
/>
<img v-else src="/images/placeholder.png" />
</span>
<template v-else>
<img
v-if="getGridImage(item)"
:src="getGridImage(item)"
@load="pImg"
@error="onImgError"
/>
<img v-else src="/images/placeholder.png" />
</template>
</figure>
</div>
<div class="flex flex-1 flex-col gap-4 p-4">
<div class="queue-progress rounded-md border border-default bg-muted/20">
<div
v-if="showThumbnails"
class="-mx-4 -mt-4 overflow-hidden border-b border-default bg-muted/20"
>
<figure :class="['relative w-full overflow-hidden', thumbnailRatioClass]">
<span
v-if="isEmbedable(item.url)"
class="play-overlay"
@click="embed_url = getEmbedable(item.url) as string"
>
<span class="play-icon embed-icon" aria-hidden="true">
<UIcon name="i-lucide-play" class="size-6 translate-x-px text-white" />
</span>
<img
v-if="getGridImage(item)"
:src="getGridImage(item)"
@load="pImg"
@error="onImgError"
/>
<img v-else src="/images/placeholder.png" />
</span>
<template v-else>
<img
v-if="getGridImage(item)"
:src="getGridImage(item)"
@load="pImg"
@error="onImgError"
/>
<img v-else src="/images/placeholder.png" />
</template>
</figure>
</div>
<div class="queue-progress rounded-md border border-default bg-muted/20">
<div
class="queue-progress__bar bg-success/35"
:style="{ width: progressWidth(item) }"
></div>
<div class="queue-progress__label">
<template v-if="progressIcon(item)">
<UIcon
:name="progressIcon(item)"
:class="[
'mr-1 size-4',
progressIcon(item) === 'i-lucide-settings-2' ? 'animate-spin' : '',
]"
/>
</template>
<span>{{ progressText(item) }}</span>
</div>
</div>
<div class="flex flex-wrap gap-2 text-sm *:min-w-32 *:flex-1">
<button
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-default transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'status')"
>
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon
:name="setIcon(item)"
:class="[setIconColor(item), setIconAnimation(item), 'size-4 shrink-0']"
/>
<span :class="['min-w-0 text-center', expandClass(item._id, 'status')]">
{{ setStatus(item) }}
</span>
</span>
</button>
<button
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-default transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'preset')"
>
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon
name="i-lucide-sliders-horizontal"
class="size-4 shrink-0 text-toned"
/>
<span :class="['min-w-0 text-center', expandClass(item._id, 'preset')]">
{{ item.preset }}
</span>
</span>
</button>
<button
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-toned transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'datetime')"
>
<UTooltip :text="moment(item.datetime).format('MMMM Do YYYY, h:mm:ss a')">
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon name="i-lucide-clock-3" class="size-4 shrink-0 text-toned" />
<span
:class="['min-w-0 text-center', expandClass(item._id, 'datetime')]"
:data-datetime="item.datetime"
v-rtime="item.datetime"
/>
</span>
</UTooltip>
</button>
<button
v-if="item.downloaded_bytes"
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-toned transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'size')"
>
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon name="i-lucide-hard-drive" class="size-4 shrink-0 text-toned" />
<span :class="['min-w-0 text-center', expandClass(item._id, 'size')]">
{{ formatBytes(item.downloaded_bytes) }}
</span>
</span>
</button>
class="queue-progress__bar bg-success/35"
:style="{ width: progressWidth(item) }"
></div>
<div class="queue-progress__label">
<template v-if="progressIcon(item)">
<UIcon
:name="progressIcon(item)"
:class="[
'mr-1 size-4',
progressIcon(item) === 'i-lucide-settings-2' ? 'animate-spin' : '',
]"
/>
</template>
<span>{{ progressText(item) }}</span>
</div>
</div>
<div class="ytp-border-top-soft px-4 py-4">
<div class="flex flex-wrap gap-2 text-sm *:min-w-32 *:flex-1">
<button
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-default transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'status')"
>
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon
:name="setIcon(item)"
:class="[setIconColor(item), setIconAnimation(item), 'size-4 shrink-0']"
/>
<span :class="['min-w-0 text-center', expandClass(item._id, 'status')]">
{{ setStatus(item) }}
</span>
</span>
</button>
<button
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-default transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'preset')"
>
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon name="i-lucide-sliders-horizontal" class="size-4 shrink-0 text-toned" />
<span :class="['min-w-0 text-center', expandClass(item._id, 'preset')]">
{{ item.preset }}
</span>
</span>
</button>
<button
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-toned transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'datetime')"
>
<UTooltip :text="moment(item.datetime).format('MMMM Do YYYY, h:mm:ss a')">
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon name="i-lucide-clock-3" class="size-4 shrink-0 text-toned" />
<span
:class="['min-w-0 text-center', expandClass(item._id, 'datetime')]"
:data-datetime="item.datetime"
v-rtime="item.datetime"
/>
</span>
</UTooltip>
</button>
<button
v-if="item.downloaded_bytes"
type="button"
class="rounded-md border border-default bg-muted/20 px-3 py-2 text-toned transition hover:border-primary hover:text-default"
@click="toggleExpand(item._id, 'size')"
>
<span class="inline-flex w-full items-center justify-center gap-2">
<UIcon name="i-lucide-hard-drive" class="size-4 shrink-0 text-toned" />
<span :class="['min-w-0 text-center', expandClass(item._id, 'size')]">
{{ formatBytes(item.downloaded_bytes) }}
</span>
</span>
</button>
</div>
<template #footer>
<div class="flex flex-wrap gap-2 *:min-w-32 *:flex-1">
<UButton
color="neutral"
@ -650,8 +657,8 @@
</UButton>
</UDropdownMenu>
</div>
</div>
</div>
</template>
</UCard>
</LateLoader>
</div>

View file

@ -17,9 +17,11 @@
<template>
<main class="w-full min-w-0 max-w-full space-y-6">
<div class="ytp-page-header">
<div class="ytp-page-heading items-start">
<span class="ytp-page-icon">
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div class="flex min-w-0 items-start gap-3">
<span
class="inline-flex size-11 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon
name="i-lucide-file-text"
:class="[
@ -32,7 +34,9 @@
</span>
<div class="min-w-0 space-y-2">
<div class="ytp-page-kicker">
<div
class="flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned"
>
<span>{{ pageShell.sectionLabel }}</span>
<span>/</span>
<span>{{ pageShell.pageLabel }}</span>
@ -42,7 +46,7 @@
</div>
</div>
<div class="flex w-full flex-wrap items-center gap-2 xl:w-auto xl:justify-end">
<div class="flex min-w-0 flex-wrap items-center justify-end gap-2 xl:justify-end">
<UButton
v-if="!autoScroll"
color="neutral"
@ -119,132 +123,134 @@
</div>
</div>
<div class="ytp-card min-w-0 max-w-full p-4 sm:p-5 overflow-hidden">
<div class="ytp-frame overflow-hidden shadow-sm">
<div
ref="logContainer"
class="w-full min-w-0 max-w-full min-h-[55vh] max-h-[60vh] overflow-y-auto overflow-x-hidden bg-transparent font-mono text-sm text-default overscroll-x-contain"
@scroll.passive="handleScroll"
>
<UPageCard variant="naked" :ui="pageCardUi">
<template #body>
<div class="overflow-hidden rounded-sm border border-default bg-default shadow-sm">
<div
v-if="reachedEnd && !hasActiveFilter"
class="flex justify-center border-b border-default/40 px-4 py-3"
ref="logContainer"
class="w-full min-w-0 max-w-full min-h-[55vh] max-h-[60vh] overflow-y-auto overflow-x-hidden bg-transparent font-mono text-sm text-default overscroll-x-contain"
@scroll.passive="handleScroll"
>
<div
class="inline-flex items-center gap-1.5 rounded-full border border-warning/30 bg-warning/10 px-3 py-1 text-[11px] font-medium text-warning"
>
<UIcon name="i-lucide-triangle-alert" class="size-3.5 shrink-0" />
No older lines remain in this file.
</div>
</div>
<div
v-if="canLoadFilteredHistory"
class="flex justify-center border-b border-default/40 px-4 py-3"
>
<button
type="button"
class="inline-flex items-center gap-1.5 rounded-full border border-default/60 bg-elevated/40 px-3 py-1 text-[11px] font-medium text-toned transition-colors hover:border-default hover:text-default disabled:opacity-60"
:disabled="loading"
@click="fetchLogs(true)"
>
<UIcon
:name="loading ? 'i-lucide-loader-circle' : 'i-lucide-history'"
:class="['size-3.5 shrink-0', loading ? 'animate-spin' : '']"
/>
Load older lines into filter
</button>
</div>
<template v-if="filteredLogs.length > 0">
<article
v-for="(entry, index) in filteredLogs"
:key="entry.log.id"
:class="logRowClass(entry, index)"
v-if="reachedEnd && !hasActiveFilter"
class="flex justify-center border-b border-default/40 px-4 py-3"
>
<div
class="flex w-full min-w-0 flex-col gap-1 px-3 py-[0.65rem] leading-[1.6] md:flex-row md:items-start md:gap-2"
class="inline-flex items-center gap-1.5 rounded-full border border-warning/30 bg-warning/10 px-3 py-1 text-[11px] font-medium text-warning"
>
<span
class="inline-flex max-w-full flex-wrap items-center gap-x-2 gap-y-1 align-middle md:shrink-0 md:flex-nowrap"
<UIcon name="i-lucide-triangle-alert" class="size-3.5 shrink-0" />
No older lines remain in this file.
</div>
</div>
<div
v-if="canLoadFilteredHistory"
class="flex justify-center border-b border-default/40 px-4 py-3"
>
<button
type="button"
class="inline-flex items-center gap-1.5 rounded-full border border-default/60 bg-elevated/40 px-3 py-1 text-[11px] font-medium text-toned transition-colors hover:border-default hover:text-default disabled:opacity-60"
:disabled="loading"
@click="fetchLogs(true)"
>
<UIcon
:name="loading ? 'i-lucide-loader-circle' : 'i-lucide-history'"
:class="['size-3.5 shrink-0', loading ? 'animate-spin' : '']"
/>
Load older lines into filter
</button>
</div>
<template v-if="filteredLogs.length > 0">
<article
v-for="(entry, index) in filteredLogs"
:key="entry.log.id"
:class="logRowClass(entry, index)"
>
<div
class="flex w-full min-w-0 flex-col gap-1 px-3 py-[0.65rem] leading-[1.6] md:flex-row md:items-start md:gap-2"
>
<UTooltip :text="logTimeTitle(entry.log.datetime)">
<span class="inline text-[11px] font-semibold text-toned cursor-pointer">
{{ logTimeLabel(entry.log.datetime) }}
<span
class="inline-flex max-w-full flex-wrap items-center gap-x-2 gap-y-1 align-middle md:shrink-0 md:flex-nowrap"
>
<UTooltip :text="logTimeTitle(entry.log.datetime)">
<span class="inline text-[11px] font-semibold text-toned cursor-pointer">
{{ logTimeLabel(entry.log.datetime) }}
</span>
</UTooltip>
<UButton
color="neutral"
variant="ghost"
size="xs"
icon="i-lucide-panel-right-open"
aria-label="Open log details"
class="inline-flex align-[-0.2em] opacity-70 hover:opacity-100"
@click="openLogDetails(entry.log)"
/>
<span
:class="logLevelBadgeClass(getLogLevel(entry.log.level))"
@click="openLogDetails(entry.log)"
>
<UIcon :name="LOG_LEVEL_ICON[getLogLevel(entry.log.level)]" class="size-3" />
{{ getLogLevel(entry.log.level) }}
</span>
</UTooltip>
<UButton
color="neutral"
variant="ghost"
size="xs"
icon="i-lucide-panel-right-open"
aria-label="Open log details"
class="inline-flex align-[-0.2em] opacity-70 hover:opacity-100"
@click="openLogDetails(entry.log)"
/>
<span
:class="logLevelBadgeClass(getLogLevel(entry.log.level))"
@click="openLogDetails(entry.log)"
>
<UIcon :name="LOG_LEVEL_ICON[getLogLevel(entry.log.level)]" class="size-3" />
{{ getLogLevel(entry.log.level) }}
<span
v-if="entry.log.logger && !['ytptube', 'http_api'].includes(entry.log.logger)"
:title="entry.log.logger"
class="inline-block max-w-[46vw] truncate align-middle text-[11px] font-semibold text-toned sm:max-w-104"
>[{{ entry.log.logger }}]</span
>
</span>
<span
v-if="entry.log.logger && !['ytptube', 'http_api'].includes(entry.log.logger)"
:title="entry.log.logger"
class="inline-block max-w-[46vw] truncate align-middle text-[11px] font-semibold text-toned sm:max-w-104"
>[{{ entry.log.logger }}]</span
<button
type="button"
class="block min-w-0 flex-1 cursor-pointer text-left"
:aria-expanded="expandedRows.has(entry.log.id) || textWrap"
@click="toggleRow(entry.log.id)"
>
</span>
<button
type="button"
class="block min-w-0 flex-1 cursor-pointer text-left"
:aria-expanded="expandedRows.has(entry.log.id) || textWrap"
@click="toggleRow(entry.log.id)"
>
<span :class="messageClass(entry.log.id)"
>{{ entry.log.message
}}<span v-if="exceptionSummary(entry.log)" class="text-error/90">
: {{ exceptionSummary(entry.log) }}</span
></span
>
</button>
</div>
</article>
</template>
<div
v-else
class="flex min-h-[55vh] flex-col items-center justify-center gap-3 px-6 py-8 text-center font-sans"
>
<template v-if="loading">
<UIcon name="i-lucide-loader-circle" class="size-6 animate-spin text-toned" />
<div class="space-y-1">
<p class="text-sm font-medium text-default">Loading logs...</p>
<p class="text-sm text-toned">Connecting to log stream.</p>
</div>
<span :class="messageClass(entry.log.id)"
>{{ entry.log.message
}}<span v-if="exceptionSummary(entry.log)" class="text-error/90">
: {{ exceptionSummary(entry.log) }}</span
></span
>
</button>
</div>
</article>
</template>
<template v-else-if="hasActiveFilter">
<UIcon name="i-lucide-filter-x" class="size-6 text-toned" />
<div class="space-y-1">
<p class="text-sm font-medium text-default">No logs match these filters</p>
<p class="text-sm text-toned">Try adjusting or clearing the filters.</p>
</div>
</template>
<div
v-else
class="flex min-h-[55vh] flex-col items-center justify-center gap-3 px-6 py-8 text-center font-sans"
>
<template v-if="loading">
<UIcon name="i-lucide-loader-circle" class="size-6 animate-spin text-toned" />
<div class="space-y-1">
<p class="text-sm font-medium text-default">Loading logs...</p>
<p class="text-sm text-toned">Connecting to log stream.</p>
</div>
</template>
<template v-else>
<UIcon name="i-lucide-circle-off" class="size-6 text-toned" />
<div class="space-y-1">
<p class="text-sm font-medium text-default">No log lines available</p>
<p class="text-sm text-toned">There are no log entries to display.</p>
</div>
</template>
<template v-else-if="hasActiveFilter">
<UIcon name="i-lucide-filter-x" class="size-6 text-toned" />
<div class="space-y-1">
<p class="text-sm font-medium text-default">No logs match these filters</p>
<p class="text-sm text-toned">Try adjusting or clearing the filters.</p>
</div>
</template>
<template v-else>
<UIcon name="i-lucide-circle-off" class="size-6 text-toned" />
<div class="space-y-1">
<p class="text-sm font-medium text-default">No log lines available</p>
<p class="text-sm text-toned">There are no log entries to display.</p>
</div>
</template>
</div>
</div>
</div>
</div>
</div>
</template>
</UPageCard>
<LogDetailModal v-model="detailsOpen" :log="selectedLog" />
</main>
@ -310,6 +316,13 @@ const reachedEnd = ref(false);
const detailsOpen = ref(false);
const expandedRows = ref<Set<string>>(new Set());
const pageCardUi = {
root: 'w-full min-w-0 max-w-full bg-transparent',
container: 'w-full min-w-0 max-w-full p-4 sm:p-5',
wrapper: 'w-full min-w-0 items-stretch',
body: 'w-full min-w-0 max-w-full overflow-hidden',
};
const query = ref<string>(
(() => {
const filter = route.query.filter ?? '';

View file

@ -1,13 +1,17 @@
<template>
<main class="w-full min-w-0 max-w-full space-y-6">
<div class="ytp-page-header">
<div class="ytp-page-heading">
<span class="ytp-page-icon">
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div class="flex min-w-0 items-center gap-3">
<span
class="inline-flex size-11 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon :name="pageShell.icon" class="size-5" />
</span>
<div class="min-w-0 space-y-2">
<div class="ytp-page-kicker">
<div
class="flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned"
>
<span>{{ pageShell.sectionLabel }}</span>
<span>/</span>
<span>{{ pageShell.pageLabel }}</span>
@ -17,7 +21,7 @@
</div>
</div>
<div class="flex w-full flex-wrap items-center gap-2 xl:w-auto xl:justify-end">
<div class="flex min-w-0 flex-wrap items-center justify-end gap-2 xl:justify-end">
<UButton
v-if="notifications.length > 0"
color="neutral"
@ -93,7 +97,7 @@
<div
v-if="!isLoading && filteredTargets.length > 0"
class="flex flex-wrap items-center justify-between gap-3 ytp-card px-3 py-3"
class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-default bg-default px-3 py-3"
>
<div class="flex flex-wrap items-center gap-2">
<UButton
@ -138,11 +142,11 @@
<div
v-if="contentStyle === 'list' && filteredTargets.length > 0"
class="w-full min-w-0 max-w-full overflow-hidden ytp-table-surface"
class="w-full min-w-0 max-w-full overflow-hidden rounded-lg border border-default bg-default"
>
<div class="w-full max-w-full overflow-x-auto overscroll-x-contain">
<table class="min-w-235 w-full text-sm">
<thead class="bg-elevated/60 text-xs uppercase tracking-wide text-toned">
<thead class="bg-muted/40 text-xs uppercase tracking-wide text-toned">
<tr
class="text-center [&>th]:border-r [&>th]:border-default/60 [&>th]:px-3 [&>th]:py-3 [&>th]:font-semibold [&>th:last-child]:border-r-0"
>
@ -271,8 +275,16 @@
<div v-else-if="filteredTargets.length > 0" class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
<div v-for="item in filteredTargets" :key="item.id" class="min-w-0 w-full max-w-full">
<div class="ytp-card flex h-full min-w-0 w-full max-w-full flex-col overflow-hidden">
<div class="p-4 pb-3 ytp-border-bottom-soft">
<UCard
class="flex h-full min-w-0 w-full max-w-full flex-col"
:ui="{
root: 'bg-default border border-default',
header: 'p-4 pb-3',
body: 'flex flex-1 flex-col gap-4 p-4 pt-0',
footer: 'border-t border-default px-4 py-4',
}"
>
<template #header>
<div class="flex min-w-0 items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<div class="flex items-start gap-2">
@ -311,121 +323,119 @@
</label>
</div>
</div>
</div>
</template>
<div class="flex flex-1 flex-col gap-4 p-4 pt-0">
<div class="space-y-2 text-sm text-default">
<div class="flex flex-wrap gap-2 text-xs text-toned *:min-w-32 *:flex-1">
<button
type="button"
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1 transition hover:border-primary hover:text-default"
:disabled="addInProgress"
@click="() => void toggleEnabled(item)"
>
<UIcon
name="i-lucide-power"
class="size-3.5"
:class="item.enabled !== false ? 'text-success' : 'text-error'"
/>
<span>{{ item.enabled !== false ? 'Enabled' : 'Disabled' }}</span>
</button>
<div class="space-y-2 text-sm text-default">
<div class="flex flex-wrap gap-2 text-xs text-toned *:min-w-32 *:flex-1">
<button
type="button"
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1 transition hover:border-primary hover:text-default"
:disabled="addInProgress"
@click="() => void toggleEnabled(item)"
>
<UIcon
name="i-lucide-power"
class="size-3.5"
:class="item.enabled !== false ? 'text-success' : 'text-error'"
/>
<span>{{ item.enabled !== false ? 'Enabled' : 'Disabled' }}</span>
</button>
<span
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-bell-ring" class="size-3.5" />
<span>Events: {{ item.on.length || 'All' }}</span>
</span>
<span
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-bell-ring" class="size-3.5" />
<span>Events: {{ item.on.length || 'All' }}</span>
</span>
<span
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-sliders-horizontal" class="size-3.5" />
<span>Presets: {{ item.presets.length || 'All' }}</span>
</span>
<span
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-sliders-horizontal" class="size-3.5" />
<span>Presets: {{ item.presets.length || 'All' }}</span>
</span>
<span
v-if="headerKeys(item).length > 0"
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-key" class="size-3.5" />
<span>Headers: {{ headerKeys(item).length }}</span>
</span>
</div>
<span
v-if="headerKeys(item).length > 0"
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-key" class="size-3.5" />
<span>Headers: {{ headerKeys(item).length }}</span>
</span>
</div>
<div class="feature-meta-grid">
<button
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'url')"
>
<UIcon name="i-lucide-link" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Target URL</div>
<a
:href="item.request.url"
target="_blank"
rel="noreferrer"
class="block text-highlighted hover:underline"
@click.stop
>
<span :class="['block', expandClass(item.id, 'url')]">
{{ item.request.url }}
</span>
</a>
</div>
</button>
<button
v-if="headerKeys(item).length > 0"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'headers')"
>
<UIcon name="i-lucide-key" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Headers</div>
<span :class="['block', expandClass(item.id, 'headers')]">
{{ headerKeys(item).join(', ') }}
<div class="feature-meta-grid">
<button
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'url')"
>
<UIcon name="i-lucide-link" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Target URL</div>
<a
:href="item.request.url"
target="_blank"
rel="noreferrer"
class="block text-highlighted hover:underline"
@click.stop
>
<span :class="['block', expandClass(item.id, 'url')]">
{{ item.request.url }}
</span>
</div>
</button>
</a>
</div>
</button>
<button
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'events')"
>
<UIcon name="i-lucide-bell-ring" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Events</div>
<span :class="['block', expandClass(item.id, 'events')]">{{
joinEvents(item.on)
}}</span>
</div>
</button>
<button
v-if="headerKeys(item).length > 0"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'headers')"
>
<UIcon name="i-lucide-key" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Headers</div>
<span :class="['block', expandClass(item.id, 'headers')]">
{{ headerKeys(item).join(', ') }}
</span>
</div>
</button>
<button
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'presets')"
>
<UIcon
name="i-lucide-sliders-horizontal"
class="mt-0.5 size-4 shrink-0 text-toned"
/>
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Presets</div>
<span :class="['block', expandClass(item.id, 'presets')]">
{{ joinPresets(item.presets) }}
</span>
</div>
</button>
</div>
<button
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'events')"
>
<UIcon name="i-lucide-bell-ring" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Events</div>
<span :class="['block', expandClass(item.id, 'events')]">{{
joinEvents(item.on)
}}</span>
</div>
</button>
<button
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'presets')"
>
<UIcon
name="i-lucide-sliders-horizontal"
class="mt-0.5 size-4 shrink-0 text-toned"
/>
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Presets</div>
<span :class="['block', expandClass(item.id, 'presets')]">
{{ joinPresets(item.presets) }}
</span>
</div>
</button>
</div>
</div>
<div class="ytp-border-top-soft px-4 py-4">
<template #footer>
<div class="flex flex-wrap gap-2 *:min-w-32 *:flex-1">
<UButton
color="neutral"
@ -447,8 +457,8 @@
Delete
</UButton>
</div>
</div>
</div>
</template>
</UCard>
</div>
</div>
@ -647,6 +657,7 @@ const bulkActionGroups = computed<DropdownMenuItem[][]>(() => [
{
label: 'Remove Selected',
icon: 'i-lucide-trash',
color: 'error',
disabled: !hasSelected.value || massDelete.value,
onSelect: () => void deleteSelected(),
},

View file

@ -1,13 +1,17 @@
<template>
<main class="w-full min-w-0 max-w-full space-y-6">
<div class="ytp-page-header">
<div class="ytp-page-heading">
<span class="ytp-page-icon">
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div class="flex min-w-0 items-center gap-3">
<span
class="inline-flex size-11 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon :name="pageShell.icon" class="size-5" />
</span>
<div class="min-w-0 space-y-2">
<div class="ytp-page-kicker">
<div
class="flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned"
>
<span>{{ pageShell.sectionLabel }}</span>
<span>/</span>
<span>{{ pageShell.pageLabel }}</span>
@ -17,7 +21,7 @@
</div>
</div>
<div class="flex w-full flex-wrap items-center gap-2 xl:w-auto xl:justify-end">
<div class="flex min-w-0 flex-wrap items-center justify-end gap-2 xl:justify-end">
<UButton
v-if="presets.length > 0"
color="neutral"
@ -79,7 +83,7 @@
<div
v-if="!isLoading && filteredPresets.length > 0"
class="flex flex-wrap items-center justify-between gap-3 ytp-card px-3 py-3"
class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-default bg-default px-3 py-3"
>
<div class="flex flex-wrap items-center gap-2">
<UButton
@ -124,11 +128,11 @@
<div
v-if="contentStyle === 'list' && filteredPresets.length > 0"
class="w-full min-w-0 max-w-full overflow-hidden ytp-table-surface"
class="w-full min-w-0 max-w-full overflow-hidden rounded-lg border border-default bg-default"
>
<div class="w-full max-w-full overflow-x-auto overscroll-x-contain">
<table class="min-w-200 w-full text-sm">
<thead class="bg-elevated/60 text-xs uppercase tracking-wide text-toned">
<thead class="bg-muted/40 text-xs uppercase tracking-wide text-toned">
<tr
class="text-center [&>th]:border-r [&>th]:border-default/60 [&>th]:px-3 [&>th]:py-3 [&>th]:font-semibold [&>th:last-child]:border-r-0"
>
@ -232,8 +236,16 @@
<div v-else-if="filteredPresets.length > 0" class="grid gap-4 md:grid-cols-2 xl:grid-cols-3">
<div v-for="item in filteredPresets" :key="item.id" class="min-w-0 w-full max-w-full">
<div class="ytp-card flex h-full min-w-0 w-full max-w-full flex-col overflow-hidden">
<div class="p-4 pb-3 ytp-border-bottom-soft">
<UCard
class="flex h-full min-w-0 w-full max-w-full flex-col"
:ui="{
root: 'bg-default border border-default',
header: 'p-4 pb-3',
body: 'flex flex-1 flex-col gap-4 p-4 pt-0',
footer: 'border-t border-default px-4 py-4',
}"
>
<template #header>
<div class="flex min-w-0 items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<div class="flex items-start gap-2">
@ -271,94 +283,92 @@
</label>
</div>
</div>
</div>
</template>
<div class="flex flex-1 flex-col gap-4 p-4 pt-0">
<div class="space-y-2 text-sm text-default">
<div class="flex flex-wrap gap-2 text-xs text-toned *:min-w-32 *:flex-1">
<span
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon
name="i-lucide-cookie"
class="size-3.5"
:class="item.cookies ? 'text-success' : ''"
/>
<span>Cookies: {{ item.cookies ? 'Configured' : 'Not set' }}</span>
</span>
<div class="space-y-2 text-sm text-default">
<div class="flex flex-wrap gap-2 text-xs text-toned *:min-w-32 *:flex-1">
<span
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon
name="i-lucide-cookie"
class="size-3.5"
:class="item.cookies ? 'text-success' : ''"
/>
<span>Cookies: {{ item.cookies ? 'Configured' : 'Not set' }}</span>
</span>
<span
v-if="item.priority > 0"
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-list-ordered" class="size-3.5" />
<span>Priority: {{ item.priority }}</span>
</span>
</div>
<div v-if="item.folder || item.template || item.cli" class="feature-meta-grid">
<button
v-if="item.folder"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'folder')"
>
<UIcon name="i-lucide-folder-output" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Download path</div>
<span :class="['block', expandClass(item.id, 'folder')]">{{
calcPath(item.folder)
}}</span>
</div>
</button>
<button
v-if="item.template"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'template')"
>
<UIcon name="i-lucide-file-code-2" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Output template</div>
<span :class="['block', expandClass(item.id, 'template')]">{{
item.template
}}</span>
</div>
</button>
<button
v-if="item.cli"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'cli')"
>
<UIcon name="i-lucide-terminal" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">CLI options</div>
<span :class="['block', expandClass(item.id, 'cli')]">{{ item.cli }}</span>
</div>
</button>
</div>
<span
v-if="item.priority > 0"
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-list-ordered" class="size-3.5" />
<span>Priority: {{ item.priority }}</span>
</span>
</div>
<div v-if="item.folder || item.template || item.cli" class="feature-meta-grid">
<button
v-if="item.description"
v-if="item.folder"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'description')"
@click="toggleExpand(item.id, 'folder')"
>
<UIcon name="i-lucide-align-left" class="mt-0.5 size-4 shrink-0 text-toned" />
<UIcon name="i-lucide-folder-output" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Description</div>
<span :class="['block', expandClass(item.id, 'description')]">{{
item.description
<div class="text-xs font-medium text-toned">Download path</div>
<span :class="['block', expandClass(item.id, 'folder')]">{{
calcPath(item.folder)
}}</span>
</div>
</button>
<button
v-if="item.template"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'template')"
>
<UIcon name="i-lucide-file-code-2" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Output template</div>
<span :class="['block', expandClass(item.id, 'template')]">{{
item.template
}}</span>
</div>
</button>
<button
v-if="item.cli"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'cli')"
>
<UIcon name="i-lucide-terminal" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">CLI options</div>
<span :class="['block', expandClass(item.id, 'cli')]">{{ item.cli }}</span>
</div>
</button>
</div>
<button
v-if="item.description"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'description')"
>
<UIcon name="i-lucide-align-left" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Description</div>
<span :class="['block', expandClass(item.id, 'description')]">{{
item.description
}}</span>
</div>
</button>
</div>
<div class="ytp-border-top-soft px-4 py-4">
<template #footer>
<div class="flex flex-wrap gap-2 *:min-w-32 *:flex-1">
<UButton
color="neutral"
@ -380,8 +390,8 @@
Delete
</UButton>
</div>
</div>
</div>
</template>
</UCard>
</div>
</div>
@ -530,6 +540,7 @@ const bulkActionGroups = computed<DropdownMenuItem[][]>(() => [
{
label: 'Remove Selected',
icon: 'i-lucide-trash',
color: 'error',
disabled: !hasSelected.value || massDelete.value,
onSelect: () => void deleteSelected(),
},

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,17 @@
<template>
<main class="w-full min-w-0 max-w-full space-y-6">
<div class="ytp-page-header">
<div class="ytp-page-heading">
<span class="ytp-page-icon">
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div class="flex min-w-0 items-center gap-3">
<span
class="inline-flex size-11 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon :name="pageShell.icon" class="size-5" />
</span>
<div class="min-w-0 space-y-2">
<div class="ytp-page-kicker">
<div
class="flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned"
>
<span>{{ pageShell.sectionLabel }}</span>
<span>/</span>
<span>{{ pageShell.pageLabel }}</span>
@ -17,7 +21,7 @@
</div>
</div>
<div class="flex w-full flex-wrap items-center gap-2 xl:w-auto xl:justify-end">
<div class="flex min-w-0 flex-wrap items-center justify-end gap-2 xl:justify-end">
<UButton
v-if="definitions.length > 0"
color="neutral"
@ -97,7 +101,7 @@
<div
v-if="!isLoading && filteredDefinitions.length > 0"
class="flex flex-wrap items-center justify-between gap-3 ytp-card px-3 py-3"
class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-default bg-default px-3 py-3"
>
<div class="flex flex-wrap items-center gap-2">
<UButton
@ -130,11 +134,11 @@
<div
v-if="contentStyle === 'list' && filteredDefinitions.length > 0"
class="w-full min-w-0 max-w-full overflow-hidden ytp-table-surface"
class="w-full min-w-0 max-w-full overflow-hidden rounded-lg border border-default bg-default"
>
<div class="w-full max-w-full overflow-x-auto overscroll-x-contain">
<table class="min-w-255 w-full text-sm">
<thead class="bg-elevated/60 text-xs uppercase tracking-wide text-toned">
<thead class="bg-muted/40 text-xs uppercase tracking-wide text-toned">
<tr
class="text-center [&>th]:border-r [&>th]:border-default/60 [&>th]:px-3 [&>th]:py-3 [&>th]:font-semibold [&>th:last-child]:border-r-0"
>
@ -262,8 +266,16 @@
:key="definition.id"
class="min-w-0 w-full max-w-full"
>
<div class="ytp-card flex h-full min-w-0 w-full max-w-full flex-col overflow-hidden">
<div class="p-4 pb-3 ytp-border-bottom-soft">
<UCard
class="flex h-full min-w-0 w-full max-w-full flex-col"
:ui="{
root: 'bg-default border border-default',
header: 'p-4 pb-3',
body: 'flex flex-1 flex-col gap-4 p-4 pt-0',
footer: 'border-t border-default px-4 py-4',
}"
>
<template #header>
<div class="flex min-w-0 items-start justify-between gap-3">
<div class="min-w-0 flex-1">
<div class="flex items-start gap-2">
@ -301,49 +313,47 @@
</label>
</div>
</div>
</div>
<div class="flex flex-1 flex-col gap-4 p-4 pt-0">
<div class="space-y-2 text-sm text-default">
<div class="flex flex-wrap gap-2 text-xs text-toned *:min-w-32 *:flex-1">
<button
type="button"
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1 transition hover:border-primary hover:text-default"
@click="() => void toggle(definition)"
>
<UIcon
name="i-lucide-power"
class="size-3.5"
:class="definition.enabled ? 'text-success' : 'text-error'"
/>
<span>{{ definition.enabled ? 'Enabled' : 'Disabled' }}</span>
</button>
<span
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-list-ordered" class="size-3.5" />
<span>Priority: {{ definition.priority }}</span>
</span>
</div>
</template>
<div class="space-y-2 text-sm text-default">
<div class="flex flex-wrap gap-2 text-xs text-toned *:min-w-32 *:flex-1">
<button
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(definition.id, 'patterns')"
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1 transition hover:border-primary hover:text-default"
@click="() => void toggle(definition)"
>
<UIcon name="i-lucide-link" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">URL patterns</div>
<span :class="['block', expandClass(definition.id, 'patterns')]">
{{ definition.match_url.join('\n') }}
</span>
</div>
<UIcon
name="i-lucide-power"
class="size-3.5"
:class="definition.enabled ? 'text-success' : 'text-error'"
/>
<span>{{ definition.enabled ? 'Enabled' : 'Disabled' }}</span>
</button>
<span
class="inline-flex items-center gap-1 rounded-md border border-default px-2 py-1"
>
<UIcon name="i-lucide-list-ordered" class="size-3.5" />
<span>Priority: {{ definition.priority }}</span>
</span>
</div>
<button
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(definition.id, 'patterns')"
>
<UIcon name="i-lucide-link" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">URL patterns</div>
<span :class="['block', expandClass(definition.id, 'patterns')]">
{{ definition.match_url.join('\n') }}
</span>
</div>
</button>
</div>
<div class="ytp-border-top-soft px-4 py-4">
<template #footer>
<div class="flex flex-wrap gap-2 *:min-w-32 *:flex-1">
<UButton
color="neutral"
@ -365,8 +375,8 @@
Delete
</UButton>
</div>
</div>
</div>
</template>
</UCard>
</div>
</div>
@ -562,6 +572,7 @@ const bulkActionGroups = computed<DropdownMenuItem[][]>(() => [
{
label: 'Remove Selected',
icon: 'i-lucide-trash',
color: 'error',
disabled: !hasSelected.value || massDelete.value,
onSelect: () => void deleteSelected(),
},

View file

@ -1,13 +1,17 @@
<template>
<main class="w-full min-w-0 max-w-full space-y-6">
<div class="ytp-page-header">
<div class="ytp-page-heading">
<span class="ytp-page-icon">
<div class="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div class="flex min-w-0 items-center gap-3">
<span
class="inline-flex size-11 shrink-0 items-center justify-center rounded-md border border-default bg-elevated/70 text-primary"
>
<UIcon :name="pageShell.icon" class="size-5" />
</span>
<div class="min-w-0 space-y-2">
<div class="ytp-page-kicker">
<div
class="flex flex-wrap items-center gap-2 text-xs font-medium uppercase tracking-[0.2em] text-toned"
>
<span>{{ pageShell.sectionLabel }}</span>
<span>/</span>
<span>{{ pageShell.pageLabel }}</span>
@ -17,7 +21,7 @@
</div>
</div>
<div class="flex w-full flex-wrap items-center gap-2 xl:w-auto xl:justify-end">
<div class="flex min-w-0 flex-wrap items-center justify-end gap-2 xl:justify-end">
<UButton
v-if="tasks.length > 0"
color="neutral"
@ -79,7 +83,7 @@
<div
v-if="!isLoading && filteredTasks.length > 0"
class="flex flex-wrap items-center justify-between gap-3 ytp-card px-3 py-3"
class="flex flex-wrap items-center justify-between gap-3 rounded-lg border border-default bg-default px-3 py-3"
>
<div class="flex flex-wrap items-center gap-2">
<UButton
@ -124,11 +128,11 @@
<div
v-if="contentStyle === 'list' && filteredTasks.length > 0"
class="w-full min-w-0 max-w-full overflow-hidden ytp-table-surface"
class="w-full min-w-0 max-w-full overflow-hidden rounded-lg border border-default bg-default"
>
<div class="w-full max-w-full overflow-x-auto overscroll-x-contain">
<table class="min-w-210 table-fixed w-full text-sm">
<thead class="bg-elevated/60 text-xs uppercase tracking-wide text-toned">
<thead class="bg-muted/40 text-xs uppercase tracking-wide text-toned">
<tr
class="text-center [&>th]:border-r [&>th]:border-default/60 [&>th]:px-3 [&>th]:py-3 [&>th]:font-semibold [&>th:last-child]:border-r-0"
>
@ -335,8 +339,16 @@
class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-3"
>
<div v-for="item in filteredTasks" :key="item.id" class="min-w-0 w-full max-w-full">
<div class="ytp-card flex h-full min-w-0 w-full max-w-full flex-col overflow-hidden">
<div class="p-4 pb-3 ytp-border-bottom-soft">
<UCard
class="flex h-full min-w-0 w-full max-w-full flex-col"
:ui="{
root: 'bg-default border border-default',
header: 'p-4 pb-3',
body: 'flex flex-1 flex-col gap-4 p-4 pt-0',
footer: 'border-t border-default px-4 py-4',
}"
>
<template #header>
<div class="flex min-w-0 items-start justify-between gap-3">
<div class="min-w-0 flex-1 space-y-2">
<div class="flex items-start gap-2">
@ -402,165 +414,159 @@
</label>
</div>
</div>
</div>
</template>
<div class="flex flex-1 flex-col gap-4 p-4 pt-0">
<div class="space-y-2 text-sm text-default">
<div class="grid grid-cols-2 gap-2 text-xs text-toned sm:flex sm:flex-wrap">
<button
type="button"
class="flex min-w-0 w-full items-center gap-1 rounded-md border border-default px-2 py-1 transition hover:border-primary hover:text-default sm:w-auto sm:flex-none sm:shrink-0 sm:whitespace-nowrap"
@click="() => void toggleFlag(item, 'enabled')"
>
<UIcon
name="i-lucide-power"
class="size-3.5"
:class="item.enabled !== false ? 'text-success' : 'text-error'"
/>
<span>{{ item.enabled !== false ? 'Enabled' : 'Disabled' }}</span>
</button>
<button
type="button"
class="flex min-w-0 w-full items-center gap-1 rounded-md border border-default px-2 py-1 transition hover:border-primary hover:text-default sm:w-auto sm:flex-none sm:shrink-0 sm:whitespace-nowrap"
@click="() => void toggleFlag(item, 'auto_start')"
>
<UIcon
name="i-lucide-circle-play"
class="size-3.5"
:class="item.auto_start ? 'text-success' : 'text-error'"
/>
<span>Auto start: {{ item.auto_start ? 'Yes' : 'No' }}</span>
</button>
<button
type="button"
class="flex min-w-0 w-full items-center gap-1 rounded-md border border-default px-2 py-1 transition hover:border-primary hover:text-default sm:w-auto sm:flex-none sm:shrink-0 sm:whitespace-nowrap"
@click="() => void toggleFlag(item, 'handler_enabled')"
>
<UIcon
name="i-lucide-rss"
class="size-3.5"
:class="item.handler_enabled !== false ? 'text-success' : 'text-error'"
/>
<span>Handler: {{ item.handler_enabled !== false ? 'On' : 'Off' }}</span>
</button>
<button
type="button"
class="flex min-w-0 w-full items-start gap-1 rounded-md border border-default px-2 py-1 text-left transition hover:border-primary hover:text-default sm:flex-1"
@click="toggleExpand(item.id, 'preset')"
>
<UIcon name="i-lucide-sliders-horizontal" class="size-3.5" />
<span class="min-w-0 flex-1">
<span :class="['min-w-0 capitalize', expandClass(item.id, 'preset')]">
Preset: {{ item.preset ?? config.app.default_preset }}
</span>
</span>
</button>
</div>
<div
v-if="
item.timer ||
item.folder ||
item.template ||
item.cli ||
willTaskBeProcessed(item)
"
class="feature-meta-grid"
<div class="space-y-2 text-sm text-default">
<div class="grid grid-cols-2 gap-2 text-xs text-toned sm:flex sm:flex-wrap">
<button
type="button"
class="flex min-w-0 w-full items-center gap-1 rounded-md border border-default px-2 py-1 transition hover:border-primary hover:text-default sm:w-auto sm:flex-none sm:shrink-0 sm:whitespace-nowrap"
@click="() => void toggleFlag(item, 'enabled')"
>
<button
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'schedule')"
>
<UIcon
:name="
item.timer
? 'i-lucide-clock-3'
: willTaskBeProcessed(item)
? 'i-lucide-rss'
: 'i-lucide-triangle-alert'
"
class="mt-0.5 size-4 shrink-0"
:class="!item.timer && !willTaskBeProcessed(item) ? 'text-error' : 'text-toned'"
/>
<UIcon
name="i-lucide-power"
class="size-3.5"
:class="item.enabled !== false ? 'text-success' : 'text-error'"
/>
<span>{{ item.enabled !== false ? 'Enabled' : 'Disabled' }}</span>
</button>
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Schedule</div>
<template v-if="item.timer">
<a
target="_blank"
:href="`https://crontab.guru/#${item.timer.replace(/ /g, '_')}`"
class="block text-highlighted hover:underline"
@click.stop
>
<span :class="['block', expandClass(item.id, 'schedule')]">
{{ item.timer }} ( {{ tryParse(item.timer) }} )
</span>
</a>
</template>
<button
type="button"
class="flex min-w-0 w-full items-center gap-1 rounded-md border border-default px-2 py-1 transition hover:border-primary hover:text-default sm:w-auto sm:flex-none sm:shrink-0 sm:whitespace-nowrap"
@click="() => void toggleFlag(item, 'auto_start')"
>
<UIcon
name="i-lucide-circle-play"
class="size-3.5"
:class="item.auto_start ? 'text-success' : 'text-error'"
/>
<span>Auto start: {{ item.auto_start ? 'Yes' : 'No' }}</span>
</button>
<p
v-else-if="willTaskBeProcessed(item)"
:class="['text-sm text-default', expandClass(item.id, 'schedule')]"
<button
type="button"
class="flex min-w-0 w-full items-center gap-1 rounded-md border border-default px-2 py-1 transition hover:border-primary hover:text-default sm:w-auto sm:flex-none sm:shrink-0 sm:whitespace-nowrap"
@click="() => void toggleFlag(item, 'handler_enabled')"
>
<UIcon
name="i-lucide-rss"
class="size-3.5"
:class="item.handler_enabled !== false ? 'text-success' : 'text-error'"
/>
<span>Handler: {{ item.handler_enabled !== false ? 'On' : 'Off' }}</span>
</button>
<button
type="button"
class="flex min-w-0 w-full items-start gap-1 rounded-md border border-default px-2 py-1 text-left transition hover:border-primary hover:text-default sm:flex-1"
@click="toggleExpand(item.id, 'preset')"
>
<UIcon name="i-lucide-sliders-horizontal" class="size-3.5" />
<span class="min-w-0 flex-1">
<span :class="['min-w-0 capitalize', expandClass(item.id, 'preset')]">
Preset: {{ item.preset ?? config.app.default_preset }}
</span>
</span>
</button>
</div>
<div
v-if="
item.timer || item.folder || item.template || item.cli || willTaskBeProcessed(item)
"
class="feature-meta-grid"
>
<button
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'schedule')"
>
<UIcon
:name="
item.timer
? 'i-lucide-clock-3'
: willTaskBeProcessed(item)
? 'i-lucide-rss'
: 'i-lucide-triangle-alert'
"
class="mt-0.5 size-4 shrink-0"
:class="!item.timer && !willTaskBeProcessed(item) ? 'text-error' : 'text-toned'"
/>
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Schedule</div>
<template v-if="item.timer">
<a
target="_blank"
:href="`https://crontab.guru/#${item.timer.replace(/ /g, '_')}`"
class="block text-highlighted hover:underline"
@click.stop
>
Handler only
</p>
<p v-else :class="['text-sm text-error', expandClass(item.id, 'schedule')]">
Not configured
</p>
</div>
</button>
<span :class="['block', expandClass(item.id, 'schedule')]">
{{ item.timer }} ( {{ tryParse(item.timer) }} )
</span>
</a>
</template>
<button
v-if="item.folder"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'folder')"
>
<UIcon name="i-lucide-folder-output" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Download path</div>
<span :class="['block', expandClass(item.id, 'folder')]">
{{ calcPath(item.folder) }}
</span>
</div>
</button>
<p
v-else-if="willTaskBeProcessed(item)"
:class="['text-sm text-default', expandClass(item.id, 'schedule')]"
>
Handler only
</p>
<p v-else :class="['text-sm text-error', expandClass(item.id, 'schedule')]">
Not configured
</p>
</div>
</button>
<button
v-if="item.template"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'template')"
>
<UIcon name="i-lucide-file-code-2" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Output template</div>
<span :class="['block', expandClass(item.id, 'template')]">{{
item.template
}}</span>
</div>
</button>
<button
v-if="item.folder"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'folder')"
>
<UIcon name="i-lucide-folder-output" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Download path</div>
<span :class="['block', expandClass(item.id, 'folder')]">
{{ calcPath(item.folder) }}
</span>
</div>
</button>
<button
v-if="item.cli"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'cli')"
>
<UIcon name="i-lucide-terminal" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">CLI options</div>
<span :class="['block', expandClass(item.id, 'cli')]">{{ item.cli }}</span>
</div>
</button>
</div>
<button
v-if="item.template"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'template')"
>
<UIcon name="i-lucide-file-code-2" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">Output template</div>
<span :class="['block', expandClass(item.id, 'template')]">{{
item.template
}}</span>
</div>
</button>
<button
v-if="item.cli"
type="button"
class="flex min-w-0 w-full items-start gap-2 rounded-md border border-default bg-muted/20 px-3 py-2 text-left"
@click="toggleExpand(item.id, 'cli')"
>
<UIcon name="i-lucide-terminal" class="mt-0.5 size-4 shrink-0 text-toned" />
<div class="min-w-0 flex-1">
<div class="text-xs font-medium text-toned">CLI options</div>
<span :class="['block', expandClass(item.id, 'cli')]">{{ item.cli }}</span>
</div>
</button>
</div>
</div>
<div class="ytp-border-top-soft px-4 py-4">
<template #footer>
<div class="flex flex-wrap gap-2 *:min-w-32 *:flex-1">
<UButton
color="neutral"
@ -594,8 +600,8 @@
</UButton>
</UDropdownMenu>
</div>
</div>
</div>
</template>
</UCard>
</div>
</div>
@ -847,6 +853,7 @@ const bulkActionGroups = computed<DropdownMenuItem[][]>(() => [
{
label: 'Remove Selected',
icon: 'i-lucide-trash',
color: 'error',
disabled: !hasSelected.value || massDelete.value,
onSelect: () => void deleteSelected(),
},

View file

@ -35,18 +35,10 @@ type TerminalSessionsResponse = {
items: Array<TerminalSessionItem>;
};
type YtpPageHeaderProps = {
icon?: string;
sectionLabel?: string;
pageLabel?: string;
contentClass?: string;
};
export {
ImportedItem,
TerminalSessionItem,
TerminalSessionStatus,
TerminalSessionsResponse,
YtpPageHeaderProps,
version_check,
};

View file

@ -214,7 +214,8 @@ const NavItems: Array<NavDefinition> = [
group: 'docs',
label: 'Changelog',
pageLabel: 'Changelog',
description: 'Latest project changes',
description:
'Latest project changes, loaded remotely when available and falling back to the bundled changelog file.',
icon: 'i-lucide-git-commit-horizontal',
to: '/changelog',
matchPath: '/changelog',

View file

@ -11,9 +11,9 @@
"typecheck": "nuxt typecheck",
"format": "oxfmt --write app/",
"format:check": "oxfmt --check app/",
"lint": "eslint . --no-warn-ignored",
"lint:ci": "eslint . --quiet --no-warn-ignored",
"lint:fix": "eslint . --fix --no-warn-ignored",
"lint": "eslint .",
"lint:ci": "eslint . --quiet",
"lint:fix": "eslint . --fix",
"test": "bun test",
"test:watch": "bun test --watch",
"test:ci": "bun test --ci --dots",

224
uv.lock
View file

@ -147,14 +147,14 @@ wheels = [
[[package]]
name = "anyio"
version = "4.14.0"
version = "4.13.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/1c/b5/001890774a9552aff22502b8da382593109ce0c95314abaebbb116567545/anyio-4.14.0.tar.gz", hash = "sha256:b47c1f9ccf73e67021df785332508f99379c68fa7d0684e8e3492cb1d4b23f89", size = 253586, upload-time = "2026-06-15T22:00:49.021Z" }
sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ba/16/9826f089383c593cdfc4a6e5aca94d9e91ae1692c57af82c3b2aa5e810f7/anyio-4.14.0-py3-none-any.whl", hash = "sha256:dd9b7a2a9799ed6552fde617b2c5df02b7fdd7d88392fc48101e51bae46164d9", size = 123506, upload-time = "2026-06-15T22:00:47.595Z" },
{ url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" },
]
[[package]]
@ -253,11 +253,11 @@ wheels = [
[[package]]
name = "certifi"
version = "2026.6.17"
version = "2026.5.20"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/c9/c7/424b75da314c1045981bd9777432fad05a9e0c69daa4ed7e308bbaffe405/certifi-2026.6.17.tar.gz", hash = "sha256:024c88eeec92ca068db80f02b8b07c9cef7b9fe261d1d535abfd5abd6f6af432", size = 134594, upload-time = "2026-06-17T10:31:07.894Z" }
sdist = { url = "https://files.pythonhosted.org/packages/f3/ce/ee2ecad540810a79593028e88299baeae54d346cc7a0d94b6199988b89b1/certifi-2026.5.20.tar.gz", hash = "sha256:69dea482ab64caa7b9f6aba1c6bf48bb6a5448d1c0f1b17ab42ad8c763a5344d", size = 135422, upload-time = "2026-05-20T11:46:50.073Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/2f/c5464532e965badff2f4c4c1a3a83f5697f0d7c407ed0cda44aaa99bb451/certifi-2026.6.17-py3-none-any.whl", hash = "sha256:2227dcbaafe0d2f59279d1762ddddc37783ed4354594f194ffc31d20f41fc3db", size = 133289, upload-time = "2026-06-17T10:31:06.348Z" },
{ url = "https://files.pythonhosted.org/packages/59/8c/57e832b7af6d7c5abe66eb3fbe3a3a32f4d11ea23a1aa7131371035be991/certifi-2026.5.20-py3-none-any.whl", hash = "sha256:3c52e209ba0a4ad7aebe60436a4ab349c39e1e602e8c134221e546902ad25897", size = 134134, upload-time = "2026-05-20T11:46:48.578Z" },
]
[[package]]
@ -437,7 +437,7 @@ wheels = [
[[package]]
name = "dateparser"
version = "1.4.1"
version = "1.4.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "python-dateutil" },
@ -445,9 +445,9 @@ dependencies = [
{ name = "regex" },
{ name = "tzlocal" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d3/f4/561c49bca97af561d34eed27e3e831135eb5cb88e754c1150be41820f5c6/dateparser-1.4.1.tar.gz", hash = "sha256:f265df13c0380e2e07543ba74b67c0681aaa1096981ffcd35227e1aa0cb81c7c", size = 314734, upload-time = "2026-06-15T08:45:47.659Z" }
sdist = { url = "https://files.pythonhosted.org/packages/46/2d/a0ccdb78788064fa0dc901b8524e50615c42be1d78b78d646d0b28d09180/dateparser-1.4.0.tar.gz", hash = "sha256:97a21840d5ecdf7630c584f673338a5afac5dfe84f647baf4d7e8df98f9354a4", size = 321512, upload-time = "2026-03-26T09:56:10.292Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8b/7c/2e5dcf53909deddd0bf38cbe277ad9806be038276b1c6c436561b4d9b2e2/dateparser-1.4.1-py3-none-any.whl", hash = "sha256:f25d4e051a84be27a35bd297e3e1dc59ff78373701b89be352ba80372d22d0d0", size = 300503, upload-time = "2026-06-15T08:45:45.951Z" },
{ url = "https://files.pythonhosted.org/packages/b4/0b/3c3bb7cbe757279e693a0be6049048012f794d01f81099609ecd53b899f0/dateparser-1.4.0-py3-none-any.whl", hash = "sha256:7902b8e85d603494bf70a5a0b1decdddb2270b9c6e6b2bc8a57b93476c0df378", size = 300379, upload-time = "2026-03-26T09:56:08.409Z" },
]
[[package]]
@ -551,59 +551,59 @@ wheels = [
[[package]]
name = "greenlet"
version = "3.5.2"
version = "3.5.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/dd/8b/befc3cb36965f397d87e86fb3b00e3ec0dc67c1ecb0986d7f54ee528f018/greenlet-3.5.2.tar.gz", hash = "sha256:c1b906220d83c140361cdd12eef970fb5881a168b98ee58a43786426173da14c", size = 199243, upload-time = "2026-06-17T20:19:01.317Z" }
sdist = { url = "https://files.pythonhosted.org/packages/6d/6e/802acd792aebb2256fbbee8cacf2727faaeb6f240ac11008f09eae4414bc/greenlet-3.5.1.tar.gz", hash = "sha256:5a56aeb7d5d9cc4b3a735efb5095bd4b4f6f0e4f93e5ca876d0e2315137b7829", size = 197356, upload-time = "2026-05-20T15:05:03.917Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d0/3c/bb37b9d40d65b0741a8b040ca5c307034d0a9822994dff5f825c88dd7a6b/greenlet-3.5.2-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:0629377725977252159de1ebd3c6e49c170a63856e585446797bb3d66d4d9c34", size = 287178, upload-time = "2026-06-17T17:35:25.132Z" },
{ url = "https://files.pythonhosted.org/packages/f0/a6/0c5902393f492f8ceb19d0b5cf139284e3a11b333a049739643b1036b6f8/greenlet-3.5.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a2ddf9eddc617681108dd071b3feabf3f4a4cd64846254aec4d4ceda098b639a", size = 606900, upload-time = "2026-06-17T18:07:21.692Z" },
{ url = "https://files.pythonhosted.org/packages/d8/7c/42899c31d4b87148ae4e3f87f63e13398824be6241f4dde42ded95768a34/greenlet-3.5.2-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f41feb9f2b59e2e61ac9bea4e344ddd9396bf3cacb2583f73a3595ed7df6f8e7", size = 619265, upload-time = "2026-06-17T18:29:44.837Z" },
{ url = "https://files.pythonhosted.org/packages/6a/7e/28f991affb413b232b1e7d768db24c37b3f4d5daecc3f19b455d40bd2dea/greenlet-3.5.2-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9dc23f0e5ad76415457212a4b947d22ebe4dc80baf02adf7dd5647a90f38bb4e", size = 625044, upload-time = "2026-06-17T18:39:29.046Z" },
{ url = "https://files.pythonhosted.org/packages/d3/52/4ff8c98d3cfe62b4515f8584ae14510a58f35c549cc5292b78d9b7a40b70/greenlet-3.5.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09201fa698768db245920b00fdc86ee3e73540f01ca6db162be9632642e1a473", size = 616187, upload-time = "2026-06-17T17:39:29.473Z" },
{ url = "https://files.pythonhosted.org/packages/29/05/0cc9ec660e7acff85f93b0a048b6654371c822c884add44c02a465cf70e0/greenlet-3.5.2-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:423167363c510a75b649f5cd58d873c29498ea03598b9e4b1c3b73e0f899f3d5", size = 427322, upload-time = "2026-06-17T18:41:20.892Z" },
{ url = "https://files.pythonhosted.org/packages/c9/a6/269c8bf9aefc13361ce1088f0e392b154cb21005de7862e42b5d782b81fd/greenlet-3.5.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a1759fa4f14c398508cf20dc8037de55cc23ae8bd14c185c2718257837195ca5", size = 1573778, upload-time = "2026-06-17T18:22:13.497Z" },
{ url = "https://files.pythonhosted.org/packages/1f/9b/391d015cbc6323e81b14c02cf825fdca7e0049c9bb489bf4ac72883118ba/greenlet-3.5.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9318cdeb9abdbfdd8bc8464ee4a06dffde2c7846e1def138365a6240ab2c9a5", size = 1638092, upload-time = "2026-06-17T17:40:08.163Z" },
{ url = "https://files.pythonhosted.org/packages/49/53/5b4df711f4356c62e85d9f819d87966d526d1cfb32bae49a8f7d6fc36ea4/greenlet-3.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:2c3b3311af72b3d3b03cc0f1ffd11f072e834be5d0444105cf715fc44434e39c", size = 239352, upload-time = "2026-06-17T17:38:51.593Z" },
{ url = "https://files.pythonhosted.org/packages/bb/b6/18efc3a329ec035c3f344b8f2b60356451950ddf9b7b64ff00023778a1dd/greenlet-3.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:f9bbd6216c45a563c2a61e478e038b439d9f248bde44f775ea37d339da643af4", size = 237635, upload-time = "2026-06-17T17:35:36.632Z" },
{ url = "https://files.pythonhosted.org/packages/c7/89/aaafc8e14de4ac882e02ccb963225329b0e8578aba4365e71eb678e45722/greenlet-3.5.2-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:1c31219badba285858ba8ed117f403dea7fafee6bade9a1991875aae530c3ceb", size = 287676, upload-time = "2026-06-17T17:33:31.514Z" },
{ url = "https://files.pythonhosted.org/packages/b8/fc/2308249206c12ac70de7b9a00970f84f07d10b3cd60e05d2fbcaa84124e8/greenlet-3.5.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6f96ed6f4adc1066954ae95f45717657cb67468ef3b89e9a3632e14a625a8f39", size = 653552, upload-time = "2026-06-17T18:07:23.493Z" },
{ url = "https://files.pythonhosted.org/packages/7c/24/47730d1f8f1336b9b089237521ed7a26eee997065dcb4cab81cdca333abc/greenlet-3.5.2-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5795e883e915333c0d5648faaa691857fbc7180136883edc377f50f0d509c2a8", size = 665756, upload-time = "2026-06-17T18:29:46.616Z" },
{ url = "https://files.pythonhosted.org/packages/23/5c/2664d290cbd1fef9eb3f69b5d3bc5aa91b6fa907519298ca6af93a90c6cb/greenlet-3.5.2-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6e9e49d732ee92a189bb7035e293029244aeba648297a9b856dc733d17ca7f0d", size = 669989, upload-time = "2026-06-17T18:39:30.79Z" },
{ url = "https://files.pythonhosted.org/packages/99/69/d6c99db15dc0b5e892ac3cc7b942c8b21f4a9cc3bd9ea0bc3b0f339ffbd4/greenlet-3.5.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26aed8d9503ca78889141a9739d71b383efea5f472a7c522b5410f7eb2a1b163", size = 663228, upload-time = "2026-06-17T17:39:31.073Z" },
{ url = "https://files.pythonhosted.org/packages/42/d4/fcb53fa9847d7fbd4723fbed9469c3869b9e3544c4e001d9d5aa2f66162d/greenlet-3.5.2-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:537c5c4f30395020bb9f48f53146070e3b997c3c75da14011ab732aaa19ce3ef", size = 472888, upload-time = "2026-06-17T18:41:22.511Z" },
{ url = "https://files.pythonhosted.org/packages/4f/88/9e603f448e2bc107c883e95817b980fb9b45ba6aea0299b2e9978124bea2/greenlet-3.5.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:dbebc038fcdda8f8f21cce985fd04e34e0f42007e7fc7ab7ad285caf77974b95", size = 1620723, upload-time = "2026-06-17T18:22:14.817Z" },
{ url = "https://files.pythonhosted.org/packages/11/91/26da17e3777858c16fdb8d020a4c68f3a03cb92f238de8f5351d5d5186e9/greenlet-3.5.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:a207023f1cf8695fd82580b8099c09c5809be18bc2282362cdfb965dd884a317", size = 1684227, upload-time = "2026-06-17T17:40:09.536Z" },
{ url = "https://files.pythonhosted.org/packages/2d/44/b3a11f7aa34cb38f1b7f3df8bcd9fcd09bac9d342c2a2c9b8686c804bcd2/greenlet-3.5.2-cp314-cp314-win_amd64.whl", hash = "sha256:c674a1dd4fe41f6a93febe7ab366ceabf15080ea31a9307811c56dac5f435f73", size = 240257, upload-time = "2026-06-17T17:35:23.359Z" },
{ url = "https://files.pythonhosted.org/packages/de/e3/3b62145fe917311732041a258adb218248add00542e3131c48bd047fbed5/greenlet-3.5.2-cp314-cp314-win_arm64.whl", hash = "sha256:3c417cd6c593bbbef6f7aa31a79f37d3db7d18832fc56b694a2150130bde784e", size = 239038, upload-time = "2026-06-17T17:37:56.792Z" },
{ url = "https://files.pythonhosted.org/packages/47/ac/d3bad483e9f6cd1848604fdffa32cac25846dd6dfcec0e6f81c790185518/greenlet-3.5.2-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:a96457a30384de52d9c5d2fd33abf6c1daae3db392cd556738f408b1a79a1cf0", size = 295668, upload-time = "2026-06-17T17:36:02.293Z" },
{ url = "https://files.pythonhosted.org/packages/00/e9/3a7e557b895fd0469b00cd0b2bd498ba950e8bfdf6d7adeecf2c5e4130a6/greenlet-3.5.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4af5d4961818ab651d09c1448a03b1ba2a1726a076266ebb62330bab9f3238c", size = 652820, upload-time = "2026-06-17T18:07:24.95Z" },
{ url = "https://files.pythonhosted.org/packages/78/67/6225d5c5e4afc04be0fd161eec82e4b72017e8a100d222f25d7b42b0140d/greenlet-3.5.2-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a1789a6244ea1ba61fd4386c9a6a31873e9b0234762103364be98ef87dcb19f3", size = 658697, upload-time = "2026-06-17T18:29:48.365Z" },
{ url = "https://files.pythonhosted.org/packages/35/ad/9b3058f999b81750a9c6d9ec424f509462d232b58002086fe2ba63b66407/greenlet-3.5.2-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2ee6288f1933d698b4f098127ed17bda2910a75d2807915bd16294a972055d6c", size = 658945, upload-time = "2026-06-17T18:39:32.509Z" },
{ url = "https://files.pythonhosted.org/packages/fa/99/6324b8ef916dcaddccb340b304c992ca3f947614ce0f2685d438187300b8/greenlet-3.5.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3be00501fb4a8c37f6b4b3c4773808ceb26ea65c7ea64fd5735d0f330b3786de", size = 656436, upload-time = "2026-06-17T17:39:32.509Z" },
{ url = "https://files.pythonhosted.org/packages/92/75/1b6ecd8c027b69ab1b6798a84094df79aab5e69ac7e249c78b9d361dd1fa/greenlet-3.5.2-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:b4cad42662c796334c2d24607c411e3ed82481c1fb4e1e8ec3a5a8416060092e", size = 490529, upload-time = "2026-06-17T18:41:23.954Z" },
{ url = "https://files.pythonhosted.org/packages/a9/ee/f5bf9daac27c5e1b011965f64b5630a32b415daf7381b312943629e12c2a/greenlet-3.5.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1d554cd96841a68d464d75a3736f8e87408a7b02b1930a75fa32feb408ad62f8", size = 1617193, upload-time = "2026-06-17T18:22:16.252Z" },
{ url = "https://files.pythonhosted.org/packages/8a/21/b05d5b12715bda92ce27c118d64971d21e9b8f3563ed959a7d271e2d4223/greenlet-3.5.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3dff6cd3aac35f6cd3fc23460105acf576f5faf6c378de0bc088bf37c913864a", size = 1677512, upload-time = "2026-06-17T17:40:10.771Z" },
{ url = "https://files.pythonhosted.org/packages/b8/97/1b8f1314b868041b327dc1051603e8142b826480cb0ecb8a7b7632aee9c4/greenlet-3.5.2-cp314-cp314t-win_amd64.whl", hash = "sha256:36cfea2aa075d544617176b2e84450480f0797070ad8799a8c41ada2fe449d32", size = 243145, upload-time = "2026-06-17T17:34:37.502Z" },
{ url = "https://files.pythonhosted.org/packages/36/07/1b5311775e04c718a118c504d7a3a312430e2a1bd1347226aff4774e4549/greenlet-3.5.2-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:a0314aa832c94633355dc6f3ee54f195159533355a323f26926fc63b98b2ccbb", size = 288315, upload-time = "2026-06-17T17:34:34.04Z" },
{ url = "https://files.pythonhosted.org/packages/ed/cc/6abcd2a486b58b9f77b7a93b690d59cb2c11a5906ed2ad4c63c7b9c1113d/greenlet-3.5.2-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24c59cb7db9d5c694cb8fd0c76eef8e456b2123afdfa7e4b8f2a67a0860d7682", size = 659130, upload-time = "2026-06-17T18:07:26.354Z" },
{ url = "https://files.pythonhosted.org/packages/f2/12/f4aaad6d3d383233f700ab322568a4f29f2c701a4861d85f4811d99689b2/greenlet-3.5.2-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7bb811753703739ad318112f16eccfaabdac050037b6d092debaa8b23566b4ce", size = 669724, upload-time = "2026-06-17T18:29:50.13Z" },
{ url = "https://files.pythonhosted.org/packages/53/e0/4ce3a046b51e53934eae93d7f9c13975a97285741e9e1fcadf8751314c37/greenlet-3.5.2-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2debcd0ef9455b7d4879589903efc8e497d4b8fb8c0ae772309e44d1ca5e957f", size = 673494, upload-time = "2026-06-17T18:39:34.196Z" },
{ url = "https://files.pythonhosted.org/packages/91/2a/a089811fc31c6bf8742f40a4e73470d6d401cef18e4314eb20dc399b377c/greenlet-3.5.2-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6d78b5c1c178dad90447f1b8452262709d3eef4c98f825569e74c9d0b2260ac9", size = 668089, upload-time = "2026-06-17T17:39:33.808Z" },
{ url = "https://files.pythonhosted.org/packages/52/e0/9c18721e63445dce02ee67e4c81c0f281626604ff55ae6f7b7f4354d7129/greenlet-3.5.2-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:9558cae989faeab6fbb425cd98a0cfa4190a47fba6443973fbee0a1eb0b0b6c3", size = 479721, upload-time = "2026-06-17T18:41:25.726Z" },
{ url = "https://files.pythonhosted.org/packages/0f/1c/2f47c7d5fcfa98a62b705bf9a0505d86f4563c0d81cab1f7159ff1e743b7/greenlet-3.5.2-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:0977af2df83136f81c1f76e76d4e2fe7d0dc56ea9c101a86af26a95190b9ca32", size = 1625684, upload-time = "2026-06-17T18:22:17.664Z" },
{ url = "https://files.pythonhosted.org/packages/b9/bf/661dd24624f70b7b32972d7693d0344ecde10278f647d7b828baf739899c/greenlet-3.5.2-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:f9ed777c6891d8253e54468576f55e27f8fc1a662a664f946a191003574c0a74", size = 1688043, upload-time = "2026-06-17T17:40:12.403Z" },
{ url = "https://files.pythonhosted.org/packages/60/49/d9bde1d15a21296b3b521fe083eb8aabd54ac05d15de9832918f3d639543/greenlet-3.5.2-cp315-cp315-win_amd64.whl", hash = "sha256:c0ea4eb3de23f0bac1d75205e10ccfa9b418b17b01a2d7bf19e3b69dda08900a", size = 240531, upload-time = "2026-06-17T17:35:47.448Z" },
{ url = "https://files.pythonhosted.org/packages/7f/4d/86d7768bd53e9907de0333df215c2018cd01a593b3715cbd79aa82dd94b7/greenlet-3.5.2-cp315-cp315-win_arm64.whl", hash = "sha256:7a7bfc200be40d04961d7e80e8337d726c0c1a50777e588123c3ed8ba731dcb9", size = 239579, upload-time = "2026-06-17T17:39:39.954Z" },
{ url = "https://files.pythonhosted.org/packages/92/15/907be5e8900901039bae752fa9a31c03a3c1e064833f35a4e49449184581/greenlet-3.5.2-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:98a52d6a50d4deaba304331d83ee3e10ebbdc1517fcca40b2715d1de4534065c", size = 296697, upload-time = "2026-06-17T17:37:15.887Z" },
{ url = "https://files.pythonhosted.org/packages/95/5c/08c57be575c3d6a3c023bbf22144a1c7dc6ed4d134527bb36ded4dbf04a8/greenlet-3.5.2-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1587ff8b58fdf806993ed1490a06ac19c22d47b219c68b30954380029045d8d4", size = 656710, upload-time = "2026-06-17T18:07:28.046Z" },
{ url = "https://files.pythonhosted.org/packages/8c/d0/749f917bdc9fc90fceea4aa65fbf6556e617a50714d1496bdc8ad190bb36/greenlet-3.5.2-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:feb721811d2754bfd16b48de151dd6b1f222c048e625151f2ca44cfdfd69f59c", size = 662629, upload-time = "2026-06-17T18:29:51.728Z" },
{ url = "https://files.pythonhosted.org/packages/55/87/10776cd88df54d0f563e9e21e98363f2d6af94bedc553b1da0972fa87f80/greenlet-3.5.2-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a9476cbead736dc48ce89e3cd97acff95ecc48cbf21273603a438f9870c4a014", size = 663191, upload-time = "2026-06-17T18:39:35.639Z" },
{ url = "https://files.pythonhosted.org/packages/5a/a5/68cefae3a07f6d0093a490cf28ab604f14578f3e60205a2a2b2d5cd70af2/greenlet-3.5.2-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7fe6062b1f35534e1e8fb28dfed406cf4eeff3e0bca3a0d9f8ff69f20a4abb00", size = 660147, upload-time = "2026-06-17T17:39:35.068Z" },
{ url = "https://files.pythonhosted.org/packages/02/aa/26ddf92826a99d87bfb8fdb8f3a262a6f16495a5d8e579737baa92fb4543/greenlet-3.5.2-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:5930d3946ecae99fa7fc0e3f3ae515426ad85058ebd9bfc6c00cca8016e6206b", size = 498199, upload-time = "2026-06-17T18:41:27.464Z" },
{ url = "https://files.pythonhosted.org/packages/d2/6b/b9156d8397e4750220f54c7c5c34650f1e740a8d2f66eab9cfd1b7b53b69/greenlet-3.5.2-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:b4ac902af825cbac8e9b2fccab8122236fd2ba6c8b71a080116d2c2ec72671b1", size = 1621675, upload-time = "2026-06-17T18:22:18.873Z" },
{ url = "https://files.pythonhosted.org/packages/b0/e3/d3250f4fa01c211a93d04e34fded63187e648dbec17b9b1a14d388040593/greenlet-3.5.2-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:6f1e473c06ae8be00c9034c2bb10fa277b08a93287e3111c395b839f01d27e1f", size = 1680577, upload-time = "2026-06-17T17:40:14.055Z" },
{ url = "https://files.pythonhosted.org/packages/55/ba/eaee8bda4419770d7096b5a009ebff0ab20a2a28cdd83c4b591bfdf36fa9/greenlet-3.5.2-cp315-cp315t-win_amd64.whl", hash = "sha256:3c2315045f9983e2e50d7e89d95405c21bddb8745f2da4487bc080ab3525f904", size = 243482, upload-time = "2026-06-17T17:37:34.741Z" },
{ url = "https://files.pythonhosted.org/packages/37/45/f794a81c91e9942c61f9110bd1f9a38a0ea565eab57f8b08cd53d3131e48/greenlet-3.5.2-cp315-cp315t-win_arm64.whl", hash = "sha256:db548d5ab6c2a8ead82c013f875090d79b5d7d2b67fc513934ce6cf66492ad7f", size = 242062, upload-time = "2026-06-17T17:35:39.814Z" },
{ url = "https://files.pythonhosted.org/packages/27/69/7f7e5372d998b81001899b1c0823c957aa413ba0f2662e65821611cc31e4/greenlet-3.5.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:51518ff74664078fc51bffcc6fc529b0df5ae58da192691cee765d45ce944a2b", size = 285060, upload-time = "2026-05-20T13:08:51.899Z" },
{ url = "https://files.pythonhosted.org/packages/b1/bf/387f9b6b865fd2ae0d0be09e0004827295a01b71be76ed350dd1e28a91a4/greenlet-3.5.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ffdb3c0bb002c99cd8f298957e046c3dbf6006b5b7cdf11a4e19194624a0a0a", size = 604370, upload-time = "2026-05-20T14:00:07.492Z" },
{ url = "https://files.pythonhosted.org/packages/32/f5/169ce3d4e4c67291bd18f8cbe0299c9f3e45102c7f1fb3c14780c93e4532/greenlet-3.5.1-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7715a5a2c3378ba602c3a440558261e13a820bb53a82693aacd7b7f6d964e283", size = 616987, upload-time = "2026-05-20T14:05:44.237Z" },
{ url = "https://files.pythonhosted.org/packages/19/ba/c24110c55dffa55aa6e1d98b45310da33801aeba7686ff0190fe5d46fd32/greenlet-3.5.1-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d40a890035c0058cadbdc4af7569800fd28a0e527a0fdbb7b5f9418f176846ce", size = 622911, upload-time = "2026-05-20T14:09:10.598Z" },
{ url = "https://files.pythonhosted.org/packages/ee/e5/7f2e41d5273be07e77560d61ea4e56485b4d6c316d2a84518c62d1364061/greenlet-3.5.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc71ff466927a201b08305acac451ebe1aedfcea002f62f1f2f2ac2ac1e6a135", size = 613911, upload-time = "2026-05-20T13:14:27.539Z" },
{ url = "https://files.pythonhosted.org/packages/ec/7b/d20db2e8a5ad6c038702f3179b136f93f0a3d1a21a0c0777f3e470cdf4b2/greenlet-3.5.1-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:67821bb03e4e98664490edb787ff6af501194c29bbee0f5c1dfdcf1dc3d9d436", size = 425228, upload-time = "2026-05-20T14:01:40.837Z" },
{ url = "https://files.pythonhosted.org/packages/c5/a4/fbdc67579b73615a1f91615e814303cc71e06128f7baaba87be79b8fb90c/greenlet-3.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cd443683db272ebaaca03af98c0b063ab30db70ea8a31a1559f35e3f7b744ccd", size = 1570689, upload-time = "2026-05-20T14:02:27.225Z" },
{ url = "https://files.pythonhosted.org/packages/e6/b4/77abbe35078be39718a46cd49caf16bceb35662f97a34101dca28aa98e47/greenlet-3.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:089fff7a6ce8d9316d1f65ebc00273a56be258c1725b32b94de90a3a979557e1", size = 1635602, upload-time = "2026-05-20T13:14:36.344Z" },
{ url = "https://files.pythonhosted.org/packages/37/f7/129f27ca700845b8ee8ca88ce7f43435a1239c2eddb7677fc938822762cf/greenlet-3.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:110a1ca7b49b014b097f6078272c3f4ed31af45b254de5228b79adba879f6af9", size = 238683, upload-time = "2026-05-20T13:11:50.57Z" },
{ url = "https://files.pythonhosted.org/packages/6d/5c/a485a36e87df8d8fd0632ee01511244f5156a20ed3746cc6599340326395/greenlet-3.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f16ba1efc0715b680a18b8123d90dad887c6112ae3555b4b5c32c149540c6b4e", size = 235499, upload-time = "2026-05-20T13:12:42.028Z" },
{ url = "https://files.pythonhosted.org/packages/8a/cb/c62454606daf5640369c94d8a9dd540599b1bfc090e2d2180cb77f4038d2/greenlet-3.5.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8ab31c9de8651a2facdd5c5bb0011f2380dd1a7af78ce2adf4b56095294fc07", size = 285579, upload-time = "2026-05-20T13:08:56.396Z" },
{ url = "https://files.pythonhosted.org/packages/ec/71/c4270398c2eba968a6071af1dfbdcaeee6ec1c24bc8b435b8cc452700da6/greenlet-3.5.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e300185139abc337ade480c327183adf42a875ac7181bfe66d7d4efea31fbea", size = 651106, upload-time = "2026-05-20T14:00:09.448Z" },
{ url = "https://files.pythonhosted.org/packages/1a/ab/71e34b78a44ec271fb5f550c17bc46d301ddc5953890d935f270b0dcdb5a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7ffdb990dcaa0234cf9845aead5df2e3c3a8b6507d409274dd87e0d5ab05ffc2", size = 663478, upload-time = "2026-05-20T14:05:45.88Z" },
{ url = "https://files.pythonhosted.org/packages/c6/2d/2d80842910da44f78c286532d084b8a5c3717c844ae80ceb3858738ae89a/greenlet-3.5.1-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6c09df69dc1712d131332054a858a3e5cca400967fa3a672e2324fbb0971448c", size = 667767, upload-time = "2026-05-20T14:09:12.15Z" },
{ url = "https://files.pythonhosted.org/packages/77/96/4efd6fa5c62c85426a0c19077a586258ebc3a2a146ff2493e4312a697a22/greenlet-3.5.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2f82b3597e9d83b63408affed0b48fd0f54935edac4302237b9a837be0dae33c", size = 660800, upload-time = "2026-05-20T13:14:29.129Z" },
{ url = "https://files.pythonhosted.org/packages/e9/d3/dad2eecedfbb1ed7050a20dcfae40c1442b74bc7423608be2c7e03ee7133/greenlet-3.5.1-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:a4764e0bfc6a4d114c865b32520805c16a990ef5f286a514413b05d5ecd6a23d", size = 470786, upload-time = "2026-05-20T14:01:42.064Z" },
{ url = "https://files.pythonhosted.org/packages/7a/e0/6c71401a25cac7000261304e866a2f2cc04dc74810d40e2f118aa4799495/greenlet-3.5.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c0141e37414c10164e702b8fb1473304221ad98f71600850c6ef7ff4880feba0", size = 1617518, upload-time = "2026-05-20T14:02:28.662Z" },
{ url = "https://files.pythonhosted.org/packages/41/26/c5c06643e8c0af9e7bf18e16cb51d0ab7625155f0392e1c9015d66d556cd/greenlet-3.5.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:50ae25a67bea74ea41fb14b960bc532df73eb713417b2d61892dced82fe8d3bc", size = 1681593, upload-time = "2026-05-20T13:14:39.417Z" },
{ url = "https://files.pythonhosted.org/packages/8a/bd/e11a108317485075e68af9d23039619b86b28130c3b50d227d42edece64b/greenlet-3.5.1-cp314-cp314-win_amd64.whl", hash = "sha256:8a17c42330e261299766b75ac1ea32caa437a9453c8f65d16a13140db378ecd3", size = 239800, upload-time = "2026-05-20T13:09:30.128Z" },
{ url = "https://files.pythonhosted.org/packages/47/f8/8e8e8417b7bf28639a5a56356ef934d0375e1d0c70a57e04d7701e870ffe/greenlet-3.5.1-cp314-cp314-win_arm64.whl", hash = "sha256:7b5f5fae05b8ac6d176a61b60c394a8cbdc2b5b91b81793066e68745cf165e54", size = 236862, upload-time = "2026-05-20T13:09:10.498Z" },
{ url = "https://files.pythonhosted.org/packages/90/12/41bf27fde4d3605d3773ae57751eda182b8be2f5398011c041173b1d9534/greenlet-3.5.1-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:ea8da1e900d758d078810d4255d8c6aa572181896a31ec79d779eb79c3adc9ad", size = 293637, upload-time = "2026-05-20T13:12:35.529Z" },
{ url = "https://files.pythonhosted.org/packages/44/44/ba14b23e9757707050c2f397d305bbcae62e5d7cad122f8b6baec5ae4a1f/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a19570c52a21420dcbc94e661994bc325c0b5b11304540fed514586da5dc8f2e", size = 650840, upload-time = "2026-05-20T14:00:11.079Z" },
{ url = "https://files.pythonhosted.org/packages/a8/37/5ddc2b686a6844f91abecef43411842426da2e1573f60b49ecf2547f4ae1/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3d955c89b75eeca4723d7cc14135f393cd47c32e2a6cb4a8e4c6e760a26b0986", size = 656416, upload-time = "2026-05-20T14:05:47.118Z" },
{ url = "https://files.pythonhosted.org/packages/8c/46/5987dcd1a2570ba84f3b187536b2ca3ae97613387e57f5cfa99df068fe5e/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea37d5a157eb9493820d3792ac4ece28619a394391d2b9f2f78057d396ff0f0f", size = 656607, upload-time = "2026-05-20T14:09:13.949Z" },
{ url = "https://files.pythonhosted.org/packages/e1/f0/d17510297c35a2992712f0bf84de3779749999f7d3d63aa1f09db7c62dbe/greenlet-3.5.1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2daaaebd1a5aa88c49045b6baf9310b3263796bd88db713edf37cf53e7bb4e", size = 654397, upload-time = "2026-05-20T13:14:30.696Z" },
{ url = "https://files.pythonhosted.org/packages/2c/c1/6da0a9ddcc29d7e51ef14883fa3dc1e53b3f4ffba00582106c7bf55da1d8/greenlet-3.5.1-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:8d8a23250ea3ec7b36de8fa4b541e9e2db3ee82915cc060ab0631609ad8b28de", size = 488287, upload-time = "2026-05-20T14:01:43.143Z" },
{ url = "https://files.pythonhosted.org/packages/37/eb/147387705bb89092645b012586e7273cb5ed3c90ef7eaf3a69173eaf0209/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:3bfbd69cc349e43bf3a8ae1c85548ff0718efc887615c2db16c3833d7b0b072d", size = 1614469, upload-time = "2026-05-20T14:02:30.192Z" },
{ url = "https://files.pythonhosted.org/packages/a6/4e/37ee0da7732b7aa9896f17e15579a9df34b9fcb9dd494f0adfa749af6623/greenlet-3.5.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4378720dd888136c27215a0214d32a4d37c3852765d45bc37aad0623423cfd78", size = 1675115, upload-time = "2026-05-20T13:14:40.972Z" },
{ url = "https://files.pythonhosted.org/packages/57/f3/97dfcf4a6eb5077f8a672234216fb5923eb89f2cab7081cb10b2cf75b605/greenlet-3.5.1-cp314-cp314t-win_amd64.whl", hash = "sha256:45718441607f9325d948db98cbc691276059316d0358c188c246da4e1d4d23d2", size = 245246, upload-time = "2026-05-20T13:12:22.646Z" },
{ url = "https://files.pythonhosted.org/packages/5d/73/d7f72e34b582f694f4a9b248162db7b09cc458a259ba8f0c0bfa1a34ea7d/greenlet-3.5.1-cp315-cp315-macosx_11_0_universal2.whl", hash = "sha256:2baee5ca02031757ffe8cc3d69f0cc0aec7065ce362622da74f32d3bcab1c541", size = 285575, upload-time = "2026-05-20T13:12:07.043Z" },
{ url = "https://files.pythonhosted.org/packages/df/59/fa9c6e87dc8ad27a95dabe2f29f372b733d05a8a67470f6c901ed9975655/greenlet-3.5.1-cp315-cp315-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b1ec3274918a81d3ea778b9e75b56b72b33f300edb6cf7f3a7fe1dae56683de", size = 656428, upload-time = "2026-05-20T14:00:12.556Z" },
{ url = "https://files.pythonhosted.org/packages/f6/f9/e753408871eaa61dfe35e619cfc67512b036fde99893685d50eea9e07146/greenlet-3.5.1-cp315-cp315-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:111e2390ffffc47d5840b01711dd7fac07d4c09283d0283e7f3264b14e284c64", size = 667064, upload-time = "2026-05-20T14:05:48.662Z" },
{ url = "https://files.pythonhosted.org/packages/dc/74/807a047255bf1e09303627c46dc043dca596b6958a354d904f32ab382005/greenlet-3.5.1-cp315-cp315-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:10a9a1c0bfbc93d41156ffcb90c75fbc05544054faf15dcc1fdf9765f8b607f0", size = 672962, upload-time = "2026-05-20T14:09:15.532Z" },
{ url = "https://files.pythonhosted.org/packages/96/27/5565b5b40389f1c7753003a07e21892fda8660926787036d5bc0308b8113/greenlet-3.5.1-cp315-cp315-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e630136e905fe5ff43e86945ae41220b6d1470956a39220e708110ac48d01ea5", size = 665697, upload-time = "2026-05-20T13:14:32.943Z" },
{ url = "https://files.pythonhosted.org/packages/76/32/19d4e13225193c29b13e308015223f7d75fd3d8623d49dd19040d2ce8ec1/greenlet-3.5.1-cp315-cp315-manylinux_2_39_riscv64.whl", hash = "sha256:ef08c1567c78074b22d1a200183d52d04a14df447bf70bcbb6a3507a48e776fc", size = 476047, upload-time = "2026-05-20T14:01:44.39Z" },
{ url = "https://files.pythonhosted.org/packages/cf/82/e7de4178c0c2d1c9a5a3be3cc0b33e46a85b3ee4a77c071bf7ad8600e079/greenlet-3.5.1-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:975eac34b44a7077ca4d421348455b94f0f518246a7f14bc6d2fdcfe5b584368", size = 1621256, upload-time = "2026-05-20T14:02:31.91Z" },
{ url = "https://files.pythonhosted.org/packages/00/10/f2dddcf7dacac17dfc68691809589adad06135eb28930429cf58a6467a2f/greenlet-3.5.1-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:9ab3c3a0b2ae6198e67c898dad5215a49f9ae0d0081b3c3ec59f333e39eeca26", size = 1685956, upload-time = "2026-05-20T13:14:42.55Z" },
{ url = "https://files.pythonhosted.org/packages/22/17/4a232b32133230ada52f70e9d7f5b65b0caef8772f01849bd8d149e7e4ca/greenlet-3.5.1-cp315-cp315-win_amd64.whl", hash = "sha256:cbfc69be86e10dcfef5b1e6269d1d6926552aa89ee39e1de3353360c1b6989ab", size = 239802, upload-time = "2026-05-20T13:13:15.481Z" },
{ url = "https://files.pythonhosted.org/packages/c2/ae/4e623a7e6d4d2a5f4cb8e4c82de4169fc637942caae68d6e676b8a128ac5/greenlet-3.5.1-cp315-cp315-win_arm64.whl", hash = "sha256:92fd6d44ac5e5a887c8a5dc4a8ba0ba908527c31c12f78c6bc7dcfe8aab279f6", size = 236853, upload-time = "2026-05-20T13:15:37.301Z" },
{ url = "https://files.pythonhosted.org/packages/7a/57/816d9cff29119da3505b3d6a5e14a8af89006ac36f47f891ff293ee05af1/greenlet-3.5.1-cp315-cp315t-macosx_11_0_universal2.whl", hash = "sha256:a6fdf2433a5441ef9a95464f7c3e674775da1c8c1177fff311cee1acad4626ed", size = 293877, upload-time = "2026-05-20T13:10:19.078Z" },
{ url = "https://files.pythonhosted.org/packages/23/a1/59b0a7c7d140ff1a75626680b9a9899b79a9176cab298b394968fb023295/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7546556f0d649f99f6a361098a55f761181bb2ea12ff150bb16d26092ad88244", size = 655333, upload-time = "2026-05-20T14:00:14.758Z" },
{ url = "https://files.pythonhosted.org/packages/72/1b/5efe127597625042218939d01855109f352779050768b670b52edcc16a6c/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d5ee3ea898009fa898f85f9982255d35278c477bebe185beca249cab42d4526c", size = 659443, upload-time = "2026-05-20T14:05:50.159Z" },
{ url = "https://files.pythonhosted.org/packages/c9/9d/1dcdf7b95ab3cf8c7b6d7277c18a5e167312f2b362ddfcc5d5e6d8d84b43/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a57b0d05a0448eed231d59c0ceb287dde984551e54cbc51ac2d4865712838e9c", size = 659998, upload-time = "2026-05-20T14:09:16.912Z" },
{ url = "https://files.pythonhosted.org/packages/6c/6d/c404246ea4d22d097a7426d0efb5b781bd7eb67715f09e79001bd552ab18/greenlet-3.5.1-cp315-cp315t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5c81f74d204d3edd136ebfd50dce53acbb776995d721a0fe801626cfc93b8cd", size = 658356, upload-time = "2026-05-20T13:14:35.091Z" },
{ url = "https://files.pythonhosted.org/packages/05/7e/c4959664fc231d587d66d8e81f2095e98056ba1954beafdcbe635e251052/greenlet-3.5.1-cp315-cp315t-manylinux_2_39_riscv64.whl", hash = "sha256:b0703c2cef53e01baec47f7a3868009913ad71ec678bbecb42a6f40895e4ce62", size = 494470, upload-time = "2026-05-20T14:01:45.611Z" },
{ url = "https://files.pythonhosted.org/packages/51/02/f8ee37fb6d2219329f350af241c27fcf12df57e723d11f6fc6d3bacdadaa/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:2c18ef16bf6d4dd410e4dd52996888ea1497be26892fe5bbc73580aba4287b8e", size = 1619216, upload-time = "2026-05-20T14:02:33.403Z" },
{ url = "https://files.pythonhosted.org/packages/93/c5/3dc9475ace2c7a3680da12372cddd7f1ac874eb410a1ac48d3e9dab83782/greenlet-3.5.1-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:17d86354f0ae6b61bf9be5148d0dd34e06c3cb7c602c671f79f29ac3b150e659", size = 1678427, upload-time = "2026-05-20T13:14:43.71Z" },
{ url = "https://files.pythonhosted.org/packages/df/4e/750c15c317a41ffb36f0bf40b933e3d744a7dede61889f74443ea69690cf/greenlet-3.5.1-cp315-cp315t-win_amd64.whl", hash = "sha256:e7516cf6ae6b8a582c2770a0caed47b8a48373ed732c33d69a72913ae6ac923e", size = 245225, upload-time = "2026-05-20T13:13:59.366Z" },
{ url = "https://files.pythonhosted.org/packages/4f/fd/d3baea2eeb7b617efd47e87ca06e2ec2c6118d303aa9e918e0ce16eadc10/greenlet-3.5.1-cp315-cp315t-win_arm64.whl", hash = "sha256:5028648bf2253ec4745add746129d3904121fa7fe871a76bed23c5720573ce0a", size = 239590, upload-time = "2026-05-20T13:13:37.382Z" },
]
[[package]]
@ -1398,7 +1398,7 @@ wheels = [
[[package]]
name = "pytest"
version = "9.1.1"
version = "9.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
@ -1407,9 +1407,9 @@ dependencies = [
{ name = "pluggy" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e4/47/b9efed96c114afcfa3c9d3fe98a76a1d14c74a9e266d397cf6eb64be5e01/pytest-9.1.1.tar.gz", hash = "sha256:1088fbde8f2b49d95a549a195707afa7a76a3ce9bcadc26b6d71f0ffda5fe313", size = 1636369, upload-time = "2026-06-19T10:58:32.857Z" }
sdist = { url = "https://files.pythonhosted.org/packages/84/0e/b5858858d74958632c49b72cb25a3976ff9f632397626715be71c89d3971/pytest-9.1.0.tar.gz", hash = "sha256:41dd9148c08072446394cefd3d79701701335a9f4cae69ba92e39f6c7f5c061c", size = 1634181, upload-time = "2026-06-13T18:52:45.983Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/24/25/1de2678b631f5a49215c6c96fff41ba892b0a34df68d6d80292b1b48aa7f/pytest-9.1.1-py3-none-any.whl", hash = "sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c", size = 386536, upload-time = "2026-06-19T10:58:31.347Z" },
{ url = "https://files.pythonhosted.org/packages/8b/5a/ba30a81239b909821b3153e303e7def45178bf353da4f72380e6c5e8793b/pytest-9.1.0-py3-none-any.whl", hash = "sha256:8ebb0e7888bdf2bdfc602ec51f8f62d50200af37356c74e503c79a94f5c81f32", size = 386453, upload-time = "2026-06-13T18:52:44.045Z" },
]
[[package]]
@ -1727,32 +1727,32 @@ wheels = [
[[package]]
name = "ruff"
version = "0.15.18"
version = "0.15.17"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/74/98/1295ad5a5aa9bc85bdcdfa5d82fe7b49c61af5657df4f227637ff9de0da6/ruff-0.15.18.tar.gz", hash = "sha256:2698a964c70e8bf402dcb99c8810472d270d141e7aa8c4e13599fd52033a2f33", size = 4761437, upload-time = "2026-06-18T18:25:39.224Z" }
sdist = { url = "https://files.pythonhosted.org/packages/8c/a9/3abdf488f1bf3d24c699415e454ed554a6350d5d89ce183be1ee0a3361ac/ruff-0.15.17.tar.gz", hash = "sha256:2ec446937fd16c8c4de2674a209cc5af64d9c6f17d21fbf1151054fa0bcf5219", size = 4743346, upload-time = "2026-06-11T17:54:47.663Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b9/d0/686e984941269621e2be72612d5c1e461f8f7b38415a2a7d7a81c8ae6715/ruff-0.15.18-py3-none-linux_armv6l.whl", hash = "sha256:8b6850172348c8381b8b3084c5915a4393c2373b9b54cd5b5e1ea15812bc10df", size = 10887308, upload-time = "2026-06-18T18:25:03.062Z" },
{ url = "https://files.pythonhosted.org/packages/ed/21/bc4123e3f5515ee99f8ce1eb93a14a0628fe4d1678663cd08f933ac16931/ruff-0.15.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3fccc153a85417dcd976883160cacce486997b0a0058dd18f54b8aaaac7d1ce2", size = 11281305, upload-time = "2026-06-18T18:25:30.026Z" },
{ url = "https://files.pythonhosted.org/packages/51/93/4769464c25cf7ab2acb3c7dda9cad3d867eb41c59565b3e2a9d17249c90c/ruff-0.15.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:08d4c86a68f2c3ec2c9d56380a71fb4a4f65373055cbb8caabd645e9102f38d4", size = 10641215, upload-time = "2026-06-18T18:25:15.802Z" },
{ url = "https://files.pythonhosted.org/packages/6c/42/56926d17120db2c208d76bf60a1a019644dd9e91dc27f0f95c9caddb1366/ruff-0.15.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:37e5108745c2c0705da916d7d4de533ddf547051ef45f62888c31bae73f66318", size = 10957224, upload-time = "2026-06-18T18:25:36.955Z" },
{ url = "https://files.pythonhosted.org/packages/22/4f/d43fab8d8189afde803103022d000a8ef9f230616d436d52a8b2b8d63b50/ruff-0.15.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:56949a6ce8b3abde54c0bcb22cebfe57e8771cadc84b407ae8b8eaf67ebdcd43", size = 10699024, upload-time = "2026-06-18T18:25:05.707Z" },
{ url = "https://files.pythonhosted.org/packages/63/42/1e3e4c68bd408b9768cf3e439acbe2c78245225faef253f7028a0cdb63e0/ruff-0.15.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01a754cd6a1b630d3f97e33eb452cf7a98040482318e870f8bc52a5a30e62657", size = 11491458, upload-time = "2026-06-18T18:25:20.275Z" },
{ url = "https://files.pythonhosted.org/packages/20/77/47a3484bea8521e14a203d98c389c5c97846675e4f02734672da4a69b52a/ruff-0.15.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ba7a07e03a44dbf10bb086ee06705b173625014ec99f73a7e6836a5e5590a0c", size = 12383752, upload-time = "2026-06-18T18:25:22.535Z" },
{ url = "https://files.pythonhosted.org/packages/0a/ca/054159590787023d83b658a1a1819c4c8910114e7015069340b71c0961cb/ruff-0.15.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a2c40a41a4cadbcf5897b548ab29dfe248b20c540961c0247d98a3973c70403", size = 11577923, upload-time = "2026-06-18T18:25:10.702Z" },
{ url = "https://files.pythonhosted.org/packages/6d/ff/d353d6b7bbd73cc0ec37f4463d7540e45e894338abdd9964eee0de332708/ruff-0.15.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f0480ce690cbb6c4db6e5d08f19fce98e10ba131a8b60c1bcdac42771e3ae2d", size = 11583925, upload-time = "2026-06-18T18:25:32.391Z" },
{ url = "https://files.pythonhosted.org/packages/c1/4a/891f89b9c296ed3e5f3ece1a5629badc989d9a8fdaa30431aaf4774bc1c2/ruff-0.15.18-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2330215f1f393fa8733f55edce04fcf94c36a2c460fcde31f78cc84e4951e9b1", size = 11582834, upload-time = "2026-06-18T18:25:27.309Z" },
{ url = "https://files.pythonhosted.org/packages/32/a3/ed9e370154bf85de360b93c03026157f02d4943b2d01ff4945f4429f8e8a/ruff-0.15.18-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6aa6a3d979e48ae617578183674bf264fbe7d0114a796a26bd678d67963c7ff", size = 10927328, upload-time = "2026-06-18T18:25:34.676Z" },
{ url = "https://files.pythonhosted.org/packages/f5/d1/5cf5909329fedb5d39d555ee818ba5cf4638e1a301b89785d34f2905bfcb/ruff-0.15.18-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:a81beadbbff2c9c245561ae3f77b16709d87f35eec650d0501679239d3449b22", size = 10693187, upload-time = "2026-06-18T18:25:08.245Z" },
{ url = "https://files.pythonhosted.org/packages/fd/44/ff6c635cf2c4f4e7b618b6640da057376baa36014695487d88aed4794268/ruff-0.15.18-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2186d9e940ae332ab293623a75b5f4fe49565f449954d50a72a046683aa6b809", size = 11208721, upload-time = "2026-06-18T18:25:41.327Z" },
{ url = "https://files.pythonhosted.org/packages/88/d9/5baa2a30861adfb7022cf33c1e35b2fc18085b08c16f83eff4c7b99a5f48/ruff-0.15.18-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5c2abf140438032bc77b2284a6c9944ecd8a19e5f1c7b52b1b8e4a0a80d19a7a", size = 11678599, upload-time = "2026-06-18T18:25:13.607Z" },
{ url = "https://files.pythonhosted.org/packages/c3/1a/0725a7cfdc32ff769efb96ee782bec882e16448c5d9e3be947ec4c04ce27/ruff-0.15.18-py3-none-win32.whl", hash = "sha256:02299e6e9fa5b297a3f6d5d10d7bcd655c925b028bb8b9d4588214549c6b9ec4", size = 10901903, upload-time = "2026-06-18T18:25:24.755Z" },
{ url = "https://files.pythonhosted.org/packages/f3/51/805d9f6fb7970505c3504794a5ec350f605361b807fef4dcf214ebd35e72/ruff-0.15.18-py3-none-win_amd64.whl", hash = "sha256:dac80dc8d26b2257dbefabed62f5d255c3937b4ccb122da1fc634794fa3578b3", size = 12041189, upload-time = "2026-06-18T18:25:17.915Z" },
{ url = "https://files.pythonhosted.org/packages/29/4c/67bb45e41609eb4726f1bfeb59e083cf91d14c696d4bd14c234a980be93d/ruff-0.15.18-py3-none-win_arm64.whl", hash = "sha256:b2c9257fcbd4a3e5b977a1904e6facca016bafe2edc17df24db67cfaee03b4e4", size = 11329958, upload-time = "2026-06-18T18:25:43.686Z" },
{ url = "https://files.pythonhosted.org/packages/db/4d/e11259f5da07cb6afb2d074c31bf09da9671993f7329d4f15d2fdc458301/ruff-0.15.17-py3-none-linux_armv6l.whl", hash = "sha256:d9feddb927fc68bd295f5eebc587a7e42cfaf9b65f60ca4a2386febff575da8f", size = 10856677, upload-time = "2026-06-11T17:54:49.533Z" },
{ url = "https://files.pythonhosted.org/packages/29/3e/772d679e1a0dc058e58875bd2c0cb713a0530877b4a76fee3c7966df0d49/ruff-0.15.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:25805a226d741c47d274a35ad5c10a7dde175fcddfa511d7cf3da0a21eb3eab7", size = 11223443, upload-time = "2026-06-11T17:55:00.573Z" },
{ url = "https://files.pythonhosted.org/packages/68/58/bd41f7688b2fd5623012605130ed70e60aa7f2244baa3d5066bdd61530c8/ruff-0.15.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f6ad73b14c2d18a3bf8ad7cb6974294d7f613a7898604826058e6ac64918ef4d", size = 10566458, upload-time = "2026-06-11T17:55:07.52Z" },
{ url = "https://files.pythonhosted.org/packages/d8/5b/733371013fcf1ec339e477ece6ab42bfe10bdd9bba8ee88a9516aa56bfc0/ruff-0.15.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ba0c1e4f95bcb3869d0d30cbd5917071ef2e28665abfec970cdab0492c713ed", size = 10914483, upload-time = "2026-06-11T17:55:05.501Z" },
{ url = "https://files.pythonhosted.org/packages/bd/cc/6f24251cc0252f7239391ccb85833f320efad14ebe5b443943f37ced6332/ruff-0.15.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:81647960f10bff57d2e51cadd0c3950fe598400c852863a038720ef5b8cca91e", size = 10647497, upload-time = "2026-06-11T17:54:57.733Z" },
{ url = "https://files.pythonhosted.org/packages/68/dd/0d10c17ce1a1624d6fc3156309c3f834fdb5dfaad026ec90c85684f3990e/ruff-0.15.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e01a84ddbc8c16c23055ba3924476850f1bbc1917cebbb9376665a63e74260d", size = 11416967, upload-time = "2026-06-11T17:54:51.461Z" },
{ url = "https://files.pythonhosted.org/packages/2f/91/556bfb156f6144f355e831c23db00b2fc4120f86b3ce81cc5f7fd2df51f3/ruff-0.15.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84fe9f653152f8f294f9f7e03bf3a453d8b4a27f7a59c78c8666167f2b17b96c", size = 12335770, upload-time = "2026-06-11T17:54:45.793Z" },
{ url = "https://files.pythonhosted.org/packages/88/82/8b5999aa13355e926f06d9f42a32dcca862f623bf0363785ff89d607dffd/ruff-0.15.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c0fe88a7676e7a05b73174d4d4a59cb2ac21ff8263583f87a81a6018475a978", size = 11575441, upload-time = "2026-06-11T17:54:32.661Z" },
{ url = "https://files.pythonhosted.org/packages/11/93/f10377bb04109ca0e8cbc483ff1982c54b6d418210041776f93e8cdc7fa9/ruff-0.15.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecfc3c7878fff94633ab0348524e093f9ce3243080416dd7d14f8ba400174719", size = 11557614, upload-time = "2026-06-11T17:54:34.698Z" },
{ url = "https://files.pythonhosted.org/packages/c7/a6/eeeae7f7d5493df41649ab3db92f086b2d0a30199e4efdf8e3dd7a033f24/ruff-0.15.17-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:b8461180b22420b1bdc289909410930761629fddf2a5aaf60fae1ab26cedc4c4", size = 11544450, upload-time = "2026-06-11T17:54:39.042Z" },
{ url = "https://files.pythonhosted.org/packages/32/88/5991ce565129a24dd4a00db1254b3b5db2e53018cbe4018ea5a89738e727/ruff-0.15.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6eccbe50a038b503e7140b441aa9c7fc8c1f36edf23ebef9f4165c2f28f568b7", size = 10892524, upload-time = "2026-06-11T17:55:09.432Z" },
{ url = "https://files.pythonhosted.org/packages/f5/1d/0fdd248313425f55223968af04b0a42125466a8d88d21c1d99c6af0a51e8/ruff-0.15.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:382fc0521025f5a8ad447d8bdd523545d0d7646adb718eb1c2dac5065ec27c0f", size = 10659573, upload-time = "2026-06-11T17:54:36.824Z" },
{ url = "https://files.pythonhosted.org/packages/9e/0e/072e8260deb9461062ce9311ced27a8e541229a6ffd483013dd37661e43e/ruff-0.15.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:456d41fcd1b2777ad63f09a6e7121d43f7b688bbc76a800c10f7f8fb1f912c3f", size = 11127818, upload-time = "2026-06-11T17:55:03.124Z" },
{ url = "https://files.pythonhosted.org/packages/ab/b4/55060a34163121498014696b5f656db5b8c6963768f227dbf0d76b311073/ruff-0.15.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b1a04bcc94ae6194e9db05d16ad31f298a7194bfbcb08258bbe589cee1d587b8", size = 11655901, upload-time = "2026-06-11T17:54:53.562Z" },
{ url = "https://files.pythonhosted.org/packages/49/71/9b29d6b87cef468d697f43c6a91e3fae4a80185779d7d5a4ef27d173439f/ruff-0.15.17-py3-none-win32.whl", hash = "sha256:596065960ab1ff593f744220c9fe6580eda00a95003cffa9f4048bb5b1bf0392", size = 10925574, upload-time = "2026-06-11T17:54:55.723Z" },
{ url = "https://files.pythonhosted.org/packages/3d/b2/8fc77f3723228836fa5d12497eb71c808f83782e10d058d2b15cfa14640b/ruff-0.15.17-py3-none-win_amd64.whl", hash = "sha256:6769e5fa1710b179b92e0bfa5a51735b35baea9013dadb06d5f44cbcf9547084", size = 12058788, upload-time = "2026-06-11T17:54:41.042Z" },
{ url = "https://files.pythonhosted.org/packages/2d/c7/c53e8dbff9c9dc4b7928773421ae294a5d28fcb8dcda1a089579d3a7e510/ruff-0.15.17-py3-none-win_arm64.whl", hash = "sha256:f3be1fbb34bcdfd146240d8fb92a709d4c2c8191348580a3c044ec60fa0b4456", size = 11355275, upload-time = "2026-06-11T17:54:43.635Z" },
]
[[package]]
name = "selenium"
version = "4.45.0"
version = "4.44.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
@ -1762,9 +1762,9 @@ dependencies = [
{ name = "urllib3", extra = ["socks"] },
{ name = "websocket-client" },
]
sdist = { url = "https://files.pythonhosted.org/packages/86/48/486aa67320f27452e9f551b8608f1a59ce7091c8fe7ebc9f4eba274775d4/selenium-4.45.0.tar.gz", hash = "sha256:563f0c4102f112df1cda30d46ce6d177b2e4a7a3d4b0756902d5dc84d3a8a365", size = 1005503, upload-time = "2026-06-16T04:43:57.915Z" }
sdist = { url = "https://files.pythonhosted.org/packages/2d/4a/6d0a4f4a07e2a91511a51398203ee82bf6ce644a448aaa35c59b44aa9531/selenium-4.44.0.tar.gz", hash = "sha256:b03a831fcfcab9d912b4682f60718c48a04560d6c62f7496c16b7498c9a4427e", size = 993133, upload-time = "2026-05-12T22:48:19.246Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e4/8a/6ff6beb9c7c6cc642f628df9328a8b6637f86602eff8d28e70b5d4e8bca7/selenium-4.45.0-py3-none-any.whl", hash = "sha256:1fd9d0dc08192b2f8100e264ed720f83b05d2dd3a7feff673df04e0c7580df4b", size = 9536616, upload-time = "2026-06-16T04:43:55.968Z" },
{ url = "https://files.pythonhosted.org/packages/1f/bc/885047e975e996cb317db31c4551caa915aafc6befea990f082c7233adc2/selenium-4.44.0-py3-none-any.whl", hash = "sha256:d01ea3e5ecad8149460a765f7cf5177194c21dcc0173093fc05427c289b1bf24", size = 9654291, upload-time = "2026-05-12T22:48:16.836Z" },
]
[[package]]
@ -1814,36 +1814,36 @@ wheels = [
[[package]]
name = "sqlalchemy"
version = "2.0.51"
version = "2.0.50"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "greenlet", marker = "platform_machine == 'AMD64' or platform_machine == 'WIN32' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'ppc64le' or platform_machine == 'win32' or platform_machine == 'x86_64'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/02/f1/a7a892f18d4d224e6b26f706531eafccc41e37594d37d304786969ee13cb/sqlalchemy-2.0.51.tar.gz", hash = "sha256:804dccd8a4a6242c4e30ad961e540e18a588f6527202f2d6791b01845d59fdc9", size = 9912201, upload-time = "2026-06-15T15:41:20.012Z" }
sdist = { url = "https://files.pythonhosted.org/packages/57/da/6fbf010c8ebb347679d0d100b22fe9ba5e13fd04046c5df7280d2f0bf706/sqlalchemy-2.0.50.tar.gz", hash = "sha256:af5607d11ef90fd6a5c0549fe0045dce1663d427426bcfb506dcb5346a85a3b9", size = 9907424, upload-time = "2026-05-24T19:20:04.018Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/fe/a210d52fd1a90ecfae8a78e9d8b27e18d733d60818a8bf250ff690b75120/sqlalchemy-2.0.51-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c2056838b6685b72fdb36c99996cf862753461a62f2e84f4196371d3b2d6a07", size = 2157184, upload-time = "2026-06-15T16:08:50.374Z" },
{ url = "https://files.pythonhosted.org/packages/17/6b/2dce8369b199cb855110e056032f94a9f66dacc2237d3d39c115a86eac56/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:483b11bd46bf35fc14c52faf338b04300c9e6ce554bce9b11be85bfec3bc3195", size = 3284735, upload-time = "2026-06-15T16:19:46.934Z" },
{ url = "https://files.pythonhosted.org/packages/53/ff/dbc495b8a14da840faffb353857a72d4190113cac33727906fb997047f0f/sqlalchemy-2.0.51-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1bed1ee8b01da6088210aa9412023326fb98a599ba502e6118308601dcbef77f", size = 3302756, upload-time = "2026-06-15T16:26:41.336Z" },
{ url = "https://files.pythonhosted.org/packages/cf/d5/fde8f4dddcf518ee15ab35a7c6a28acc32c8ba548d1d2aa451f96e6dbb0b/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72ca54c952107ba5cd58854b67a5a6268631289d21651a1235396f3b98b47400", size = 3232055, upload-time = "2026-06-15T16:19:49.286Z" },
{ url = "https://files.pythonhosted.org/packages/67/d1/43d3a0ac955a58601c24fa23038b1c55ee3a1ec02c0f96ebb1eae2bcf614/sqlalchemy-2.0.51-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b3e693d15533a45cd5906f0589f9c35090bef6ef45bf1e8195c424aa0ae06a8d", size = 3269850, upload-time = "2026-06-15T16:26:43.017Z" },
{ url = "https://files.pythonhosted.org/packages/94/df/de669c7054cd47c4439ac34b1b2ee8b804a794791fbb10720e997a2c87c7/sqlalchemy-2.0.51-cp313-cp313-win32.whl", hash = "sha256:b93ab07b5292dbe7e6b8da89475275e7042744283921344b56105f3eeb0f828b", size = 2117721, upload-time = "2026-06-15T16:23:12.36Z" },
{ url = "https://files.pythonhosted.org/packages/d0/8a/403c51d064196bae20a0bc2476577f83a3f8dd299719a97417086b7f2ec5/sqlalchemy-2.0.51-cp313-cp313-win_amd64.whl", hash = "sha256:0f053118c30e53161857a953e4de667d90e274980dccbe5dd3829bbbeece72a5", size = 2143615, upload-time = "2026-06-15T16:23:13.906Z" },
{ url = "https://files.pythonhosted.org/packages/b1/49/a739be2e1d02a96a658eb71ab45d921c874249252358ad24a5bffdd02525/sqlalchemy-2.0.51-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:6ea306caaae6bd5afd0a46050003c88f6bf33227377a49298c498c3cb88ff491", size = 2158999, upload-time = "2026-06-15T16:08:51.759Z" },
{ url = "https://files.pythonhosted.org/packages/23/6b/2e0e38cf75c8780eca78d9b2e78164f8bcfd70125e5caa588ff5cbb9c9f4/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c45a496d6bc05dec41dcd4c3a2b183723f47473255c159cd80b503c8f246424d", size = 3282539, upload-time = "2026-06-15T16:19:51.065Z" },
{ url = "https://files.pythonhosted.org/packages/dd/a1/e77854cb5336fd37dc3c6ae3b71de242c98caac5725120be0b526b31cbd0/sqlalchemy-2.0.51-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4004ada0aafe8ae1991b2cd1d99c6d9146126e123bd6f883c260d974aa012e54", size = 3287545, upload-time = "2026-06-15T16:26:44.735Z" },
{ url = "https://files.pythonhosted.org/packages/f6/ab/9e17272fd4dac8df3b83c4fbe52b998a1c9d89a843c8c35ff29b74ff7364/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f6bcad487aee1c638d707235682fc96f741de00663619881ab235400d03289e", size = 3230929, upload-time = "2026-06-15T16:19:52.625Z" },
{ url = "https://files.pythonhosted.org/packages/02/3c/52f408ea701781caee975606beccc48845f2aee8711ac29843d612c0306c/sqlalchemy-2.0.51-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:39a76529db6305693d8d4affa58ad5b5e2e18edd62daea628b29b97930b3513d", size = 3252888, upload-time = "2026-06-15T16:26:46.454Z" },
{ url = "https://files.pythonhosted.org/packages/24/16/3efd2ee6bc4ca4693a30a1dd17a91b606cae15d517d2a4746611d9b73ce8/sqlalchemy-2.0.51-cp314-cp314-win32.whl", hash = "sha256:08a204d8b5638717c26a24df18fcf40af45a6b22e35b70b1d62f0113c2e278e8", size = 2120551, upload-time = "2026-06-15T16:23:15.629Z" },
{ url = "https://files.pythonhosted.org/packages/7b/78/55b12e70f45bccc40d9e483925c065027b3b98ea4cbbdf6f8c2546feaf6c/sqlalchemy-2.0.51-cp314-cp314-win_amd64.whl", hash = "sha256:96747bfbadb055466e5b46d572618170046b45ce5a4879167f50d70a5319a499", size = 2146318, upload-time = "2026-06-15T16:23:17.108Z" },
{ url = "https://files.pythonhosted.org/packages/21/db/a9574ed40fed418924b1b1a3e54f47ee3963053b3d3d325a0d36b41f2c08/sqlalchemy-2.0.51-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e5ea1a213be1fcd5e49d9904c3b9939211ded90bc2a64e93f4c01963474285de", size = 2178920, upload-time = "2026-06-15T15:59:56.285Z" },
{ url = "https://files.pythonhosted.org/packages/bf/90/a1bb5c7cbba76b7bc1fbd586d0a5479a7bc9c27b4a8298f22ec9423b2bb3/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c6b36ed71f41942bdcd2ad2522be46bfce09d5705be5640ecf19bbc7660e4b7", size = 3566534, upload-time = "2026-06-15T15:58:35.024Z" },
{ url = "https://files.pythonhosted.org/packages/15/4b/481f1fed30e0e9e8dd24aecbb49f29eb57fe7657ece5cf06ee9b84bb97d8/sqlalchemy-2.0.51-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0c2c62877097e1a0db401fba5cb4debee33265e5b2a55c4ccb489c02c53b4f72", size = 3535844, upload-time = "2026-06-15T16:02:43.973Z" },
{ url = "https://files.pythonhosted.org/packages/02/71/0aa64aeda645510af0a43f7d9ee70932f0d1dc4263aed34c50ee891d9df3/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0378d055e9e8cd6ce4d8dff683bdd3d7d413533c4ee51d67a2b1e0f9eacc0f23", size = 3475355, upload-time = "2026-06-15T15:58:36.592Z" },
{ url = "https://files.pythonhosted.org/packages/05/db/6061db32316446135a3abae5f308d144ab988a34234726042da3e58b1c63/sqlalchemy-2.0.51-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6e46fc36029eff666391e0531e5387b62ce6c4f1d8e50b3fb3099eaca1b42522", size = 3486591, upload-time = "2026-06-15T16:02:45.346Z" },
{ url = "https://files.pythonhosted.org/packages/0d/c9/f14fdf71bb8957e0c7e39db69bbdf12b5c80f4ef775fdfa127bf4e0d6760/sqlalchemy-2.0.51-cp314-cp314t-win32.whl", hash = "sha256:9161cfc9efce70d1715f47d6ff40f79c6778c00d53be4fbc09d70301e4b83ba7", size = 2151313, upload-time = "2026-06-15T16:03:39.127Z" },
{ url = "https://files.pythonhosted.org/packages/6a/c6/673e618e6f4f297e126d9b56ea2f6478708f6c1af4e3223835c22e2c3697/sqlalchemy-2.0.51-cp314-cp314t-win_amd64.whl", hash = "sha256:159bb6ba32059f57ad7375a8f50d844dd2f19d14954ecf820cd33e20debd46b2", size = 2186280, upload-time = "2026-06-15T16:03:40.569Z" },
{ url = "https://files.pythonhosted.org/packages/e2/22/dbf013a12ec759e54a34a119e9e217435b3f71b2dd5c61a7ade0a25dae87/sqlalchemy-2.0.51-py3-none-any.whl", hash = "sha256:bb024d8b621d0be75f4f44ecc7c950450026e76d66dc8f791bb5331d7fed59d5", size = 1944334, upload-time = "2026-06-15T16:09:22.418Z" },
{ url = "https://files.pythonhosted.org/packages/0b/c4/c42356b527296e9862f67990efce31ef78b4cf69cd3f80873a528a060320/sqlalchemy-2.0.50-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:06a9210bdc5f4298cff0781087e2ff45683922252dacc452846373a58761f093", size = 2156697, upload-time = "2026-05-24T19:27:54.764Z" },
{ url = "https://files.pythonhosted.org/packages/60/a1/b1a70e3c4365ac7fe9e347f3710f19b562c866fb96d45e3c891588789a7b/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b53784972ade4f8174b9aa661f31a06f8a936d2cfdd602913ff3c6dd40ae873", size = 3284260, upload-time = "2026-05-24T20:09:34.195Z" },
{ url = "https://files.pythonhosted.org/packages/3f/4a/f3ac3caa19f263d57b0a47f8c91bbf56583dc2d3fc63acfbf644abb24fe0/sqlalchemy-2.0.50-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:31648fa14460537e768a7303b078e4344d208e0d23e06867c1f376a227ed82db", size = 3302280, upload-time = "2026-05-24T20:17:17.825Z" },
{ url = "https://files.pythonhosted.org/packages/66/55/ccada3e3d62254587819749a0bc69f41173eb48a6e385d10e66d32a9c88e/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:03f4323c980ad0e918cc9e5369b015f759f4e534db5bbaf4dc36832c10d05064", size = 3231580, upload-time = "2026-05-24T20:09:36.406Z" },
{ url = "https://files.pythonhosted.org/packages/05/f6/6809349130a2de0e109e7f00fd7d431da9565b9b2868b32ee684754f672b/sqlalchemy-2.0.50-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b9dcc43afef8ac157cd92fce96985d6b8b0cfbd3df4d666f66b4d55a75d202f", size = 3269375, upload-time = "2026-05-24T20:17:20.34Z" },
{ url = "https://files.pythonhosted.org/packages/48/84/278a811ef4e07be9c89dc5cdd7be833268509a66a68c4897cf585e67428f/sqlalchemy-2.0.50-cp313-cp313-win32.whl", hash = "sha256:60922d6599065ddca2c6f376b9aa2f41a6b85a271725e0909490bbc50b1998a5", size = 2117229, upload-time = "2026-05-24T19:50:08.215Z" },
{ url = "https://files.pythonhosted.org/packages/f6/1c/067cc6187ed32d2ec222fe6d2643acc1659a6d0659f8a7cbc5ad3ae83280/sqlalchemy-2.0.50-cp313-cp313-win_amd64.whl", hash = "sha256:287086e67275a212c4582d166a6fb03a65ccc5551d80866270ce0dd9f34eccd3", size = 2143126, upload-time = "2026-05-24T19:50:09.691Z" },
{ url = "https://files.pythonhosted.org/packages/df/32/10ac51b4be7cdecd7e93d069251c86dfbf70b7adbd7c67b48ccea6c49e1c/sqlalchemy-2.0.50-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c966932507a4d7d0a37314927dbfcd89720e3f37d2a1e3352e7ae7939fa8e8a0", size = 2158519, upload-time = "2026-05-24T19:27:56.472Z" },
{ url = "https://files.pythonhosted.org/packages/5a/76/e703d2f7681d7d66c4c891af3f07c7ccf4c76ad7f18351de035b5eda007a/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:faffef4bcc20a1892e65e155293d99d60855bbbc79250ab712819cfd56a8e6bb", size = 3282063, upload-time = "2026-05-24T20:09:38.57Z" },
{ url = "https://files.pythonhosted.org/packages/31/26/ef168b184a25701f9995e8fb7e503fafd7a99c1c77cda1bc1a26ea2ed486/sqlalchemy-2.0.50-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c206aec519a2e7bd08abbfb33436e325fd22c632d9c21a9047e376ce241646e", size = 3287069, upload-time = "2026-05-24T20:17:21.942Z" },
{ url = "https://files.pythonhosted.org/packages/c2/15/765acc2bc693bccc43ca4a95d5b69750da8aaf6db1b5c616536e087f8920/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bef4ac756363227ef6402a75fee025a4bc690f92328e825868939b3b3a446a6d", size = 3230453, upload-time = "2026-05-24T20:09:40.398Z" },
{ url = "https://files.pythonhosted.org/packages/63/61/08e03c3adbf5db0087a0b6816746fec8f3032fb2f7fc899a9bb9b2a48ce4/sqlalchemy-2.0.50-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:96fbee6b19c19cd1556c8bf9419447cf2ec149ffcab7ab64348c23e54ef8547f", size = 3252413, upload-time = "2026-05-24T20:17:24.067Z" },
{ url = "https://files.pythonhosted.org/packages/03/0c/370a1f2db38436c615e10134c8a37de3688e74084792380695f3f5083860/sqlalchemy-2.0.50-cp314-cp314-win32.whl", hash = "sha256:8f00e3eb43ba30eb1b238ee03a8a62309486d1321eda3328bb611e0340033ad8", size = 2120063, upload-time = "2026-05-24T19:50:11.08Z" },
{ url = "https://files.pythonhosted.org/packages/7f/a0/fe92bb9817863bc13ba093bda931979a26cc2ca69f8e8f26d07add3d7c6f/sqlalchemy-2.0.50-cp314-cp314-win_amd64.whl", hash = "sha256:15708c613cd5005b7dffe1f66ee6a63ee8f5e46799f71c70ebad74178c676a39", size = 2145830, upload-time = "2026-05-24T19:50:12.452Z" },
{ url = "https://files.pythonhosted.org/packages/cc/ff/e5640a98a0b2f491eb8fde10fb6c773621a2e44340de231fafcc9370f4a9/sqlalchemy-2.0.50-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3699dac4be410e97049a1658e9480da9cde956594aa0f3aebc60b88f21c5ba70", size = 2178435, upload-time = "2026-05-24T19:42:58.889Z" },
{ url = "https://files.pythonhosted.org/packages/b7/85/337116e186f1236375b5fb70c21cfac98e8e8ab0d3a47be838dc47a59e08/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f96233858e3df43932ac11589e22520da6e8aeb624b03fedfeebb0e8ea213086", size = 3566059, upload-time = "2026-05-24T20:01:20.848Z" },
{ url = "https://files.pythonhosted.org/packages/96/34/bb0e190e161c3c2c24314a65add57218be14a4a9486886b7f5047c1ff7c8/sqlalchemy-2.0.50-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c4e70c46fad30c3bcc6a4708bc0130a3173e11a5b25f0ea4a9d8911b450f1f52", size = 3535366, upload-time = "2026-05-24T20:03:56.768Z" },
{ url = "https://files.pythonhosted.org/packages/df/5a/a7f759f97e4fd499c5d4e4488c760d5a7fbecf3028b465a04274fcd52384/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1918a3cf564d16d95bca7301005f41ab2ad50b07cd3b9da50d3ed986db148d6a", size = 3474879, upload-time = "2026-05-24T20:01:23.058Z" },
{ url = "https://files.pythonhosted.org/packages/9d/d9/2907ea38eb60687d297bf9c39e5ee58053c87b57fe8a9cae97090cecbf10/sqlalchemy-2.0.50-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b00098cdbdbd38c7be3d568b0c9c3122b8c0ec62b911b57cd5e6e0254d60a76d", size = 3486117, upload-time = "2026-05-24T20:03:59.052Z" },
{ url = "https://files.pythonhosted.org/packages/f2/e3/5aa06f167559f8c0bdae487e297d23ba548150ab016a3418265d617a4985/sqlalchemy-2.0.50-cp314-cp314t-win32.whl", hash = "sha256:1fbd55a969d7ac44a98e3dec75016074f809fa08f871585ace58dde110d1bf3e", size = 2150823, upload-time = "2026-05-24T20:08:58.644Z" },
{ url = "https://files.pythonhosted.org/packages/65/9b/112fb8f977582d7489d036e409e3723948bcf5320b3ac465f3c481bbe8f9/sqlalchemy-2.0.50-cp314-cp314t-win_amd64.whl", hash = "sha256:c5c3cdb753a9004183e1ccb634b41611654c989e61bc68617ce878e46d6f1e51", size = 2185794, upload-time = "2026-05-24T20:09:00.319Z" },
{ url = "https://files.pythonhosted.org/packages/d0/10/f7220e9b784d295d241c86ed99aeb537f92afcd469a64861f2717e9bb077/sqlalchemy-2.0.50-py3-none-any.whl", hash = "sha256:92064363517a3ff8212b5a93b8c62876579d8dfd1ca5b561335f30152d884fa9", size = 1943861, upload-time = "2026-05-24T19:59:01.119Z" },
]
[[package]]
@ -1909,14 +1909,14 @@ wheels = [
[[package]]
name = "tzlocal"
version = "5.4.3"
version = "5.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/48/55/15e2340963d2bfedcc6042da3911438fd336f8ae96b65bdbe3a29766da0c/tzlocal-5.4.3.tar.gz", hash = "sha256:3a8c9bc18cf47e1dcde252ea0e6a72a6cde320a400b6ac6db1f1f8cccd553c00", size = 30873, upload-time = "2026-06-17T04:17:41.764Z" }
sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/28/fc144409c71569e928585f8f3c629d80d1ca3ef40175e9222f01588f98c9/tzlocal-5.4.3-py3-none-any.whl", hash = "sha256:24ce97bb58e2a973f7640ec2553ab4e6f6d5a0d0d1aa9dc43bca21d89e1feb82", size = 18039, upload-time = "2026-06-17T04:17:40.027Z" },
{ url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" },
]
[[package]]