First commit

This commit is contained in:
2025-10-15 00:07:35 -05:00
commit 5e354c05be
69 changed files with 4598 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
{
"icon": "icon.png",
"info": {
"description": "Download YouTube Videos (using CrossLang, via web interface, great for homelabs)",
"maintainer": "Mike Nolan",
"repo":"https://onedev.site.tesses.net/tytd2025",
"homepage": "https://tesses.net/apps/tytd/2025/",
"license": "GPLv3",
"short_name": "tytd2025",
"short_name_pretty": "Tesses YouTube Downloader 2025",
"type": "webapp"
},
"name": "Tesses.YouTubeDownloader.Server",
"project_dependencies": [
"..\/Tesses.YouTubeDownloader"
],
"version": "1.0.0.0-prod"
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,19 @@
<svg width="100%" height="100%" viewBox="4 4 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill="#D0BCFF">
<animate attributeName="d" dur="5s" repeatCount="indefinite" calcMode="spline" keySplines="0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8" keyTimes="0; 0.14; 0.14; 0.29; 0.29; 0.43; 0.43; 0.57; 0.57; 0.71; 0.71; 0.86; 0.86; 1" values="M20.9 10.4 21.4 9.5 21.9 8.7 22.5 7.8 23.2 7.2 24.2 7 25.1 7.4 25.7 8.1 26.2 9 26.8 9.8 27.3 10.6 28.1 11.2 29 11.3 30 11 30.9 10.6 31.8 10.3 32.8 9.9 33.7 10 34.5 10.6 34.9 11.5 34.8 12.5 34.8 13.5 34.7 14.5 34.7 15.5 35.2 16.3 36 16.8 37 17.1 37.9 17.3 38.9 17.5 39.8 17.9 40.4 18.7 40.5 19.7 40 20.5 39.4 21.3 38.7 22 38.1 22.8 37.6 23.7 37.7 24.6 38.3 25.5 38.9 26.2 39.6 27 40.2 27.7 40.5 28.7 40.3 29.6 39.5 30.3 38.6 30.6 37.6 30.8 36.7 31 35.7 31.3 35 31.9 34.6 32.8 34.7 33.8 34.8 34.8 34.9 35.8 34.8 36.8 34.3 37.6 33.4 38.1 32.4 38 31.5 37.6 30.6 37.2 29.7 36.9 28.7 36.6 27.8 36.9 27.1 37.6 26.6 38.5 26.1 39.3 25.5 40.2 24.8 40.8 23.8 41 22.9 40.6 22.3 39.9 21.8 39 21.2 38.2 20.7 37.4 19.9 36.8 19 36.7 18 37 17.1 37.4 16.2 37.7 15.2 38.1 14.3 38 13.5 37.4 13.1 36.5 13.2 35.5 13.2 34.5 13.3 33.5 13.3 32.5 12.8 31.7 12 31.2 11 31 10.1 30.7 9.1 30.5 8.2 30.1 7.6 29.3 7.5 28.3 8 27.5 8.7 26.7 9.3 26 9.9 25.2 10.4 24.3 10.3 23.4 9.7 22.5 9.1 21.8 8.4 21 7.8 20.3 7.5 19.3 7.7 18.4 8.5 17.7 9.4 17.4 10.4 17.2 11.3 17 12.3 16.7 13 16.1 13.4 15.2 13.3 14.2 13.2 13.2 13.1 12.2 13.2 11.2 13.7 10.4 14.6 9.9 15.6 10 16.5 10.4 17.4 10.8 18.3 11.1 19.3 11.4 20.2 11.1Z;
M20.3 8.6 21.1 8 22 7.6 23 7.3 23 7.3 24 7.2 25 7.3 25.9 7.5 26.8 8 27.6 8.6 28.4 9.1 28.4 9.1 29.3 9.6 30.3 9.8 31.3 9.9 32.3 10 33.3 10.2 34.2 10.6 34.2 10.6 35 11.2 35.7 11.9 36.3 12.7 36.7 13.6 36.9 14.6 37.2 15.5 37.2 15.5 37.6 16.5 38.2 17.3 38.9 18 39.6 18.7 40.2 19.5 40.6 20.4 40.6 20.4 40.9 21.3 41 22.3 40.9 23.3 40.6 24.3 40.2 25.2 39.8 26.1 39.8 26.1 39.5 27 39.4 28 39.5 29 39.6 30 39.5 31 39.3 32 39.3 32 38.9 32.9 38.3 33.7 37.6 34.4 36.8 35 35.9 35.4 35 35.8 35 35.8 34.1 36.3 33.4 37 32.9 37.9 32.3 38.7 31.6 39.4 30.8 40 30.8 40 29.9 40.4 28.9 40.7 27.9 40.8 27 40.7 26 40.4 25 40.1 25 40.1 24 40 23 40.1 22.1 40.4 21.1 40.7 20.1 40.8 19.1 40.7 19.1 40.7 18.2 40.5 17.3 40 16.4 39.5 15.7 38.8 15.2 37.9 14.6 37.1 14.6 37.1 13.9 36.4 13.1 35.8 12.2 35.4 11.3 35 10.5 34.4 9.7 33.8 9.7 33.8 9.1 32.9 8.7 32 8.5 31.1 8.4 30.1 8.5 29.1 8.6 28.1 8.6 28.1 8.5 27.1 8.3 26.1 7.8 25.2 7.4 24.3 7.1 23.4 7 22.4 7 22.4 7.1 21.4 7.3 20.4 7.8 19.5 8.3 18.7 9.1 18 9.8 17.3 9.8 17.3 10.4 16.5 10.8 15.6 11 14.6 11.3 13.7 11.7 12.8 12.2 11.9 12.2 11.9 12.9 11.2 13.8 10.7 14.7 10.2 15.6 10 16.6 9.9 17.6 9.8 17.6 9.8 18.6 9.6 19.5 9.2Z;
M18.6 9.6 19.5 9.2 20.3 8.6 21.1 8 22 7.6 23 7.3 24 7.2 25 7.3 25.9 7.5 26.8 8 27.6 8.6 28.4 9.1 29.3 9.6 30.3 9.8 31.3 9.9 32.3 10 33.3 10.2 34.2 10.6 35 11.2 35.7 11.9 36.3 12.7 36.7 13.6 36.9 14.6 37.2 15.5 37.6 16.4 38.2 17.3 38.9 18 39.6 18.7 40.2 19.5 40.6 20.4 40.9 21.3 41 22.3 40.9 23.3 40.6 24.3 40.2 25.2 39.8 26.1 39.5 27 39.4 28 39.5 29 39.6 30 39.5 31 39.3 32 38.9 32.9 38.3 33.7 37.6 34.4 36.8 35 35.9 35.4 35 35.8 34.1 36.3 33.4 37 32.9 37.9 32.3 38.7 31.6 39.4 30.8 40 29.9 40.4 28.9 40.7 27.9 40.8 27 40.7 26 40.4 25 40.1 24 40 23 40.1 22.1 40.4 21.1 40.7 20.1 40.8 19.1 40.7 18.2 40.5 17.3 40 16.4 39.5 15.7 38.8 15.2 37.9 14.6 37.1 13.9 36.4 13.1 35.8 12.2 35.4 11.3 35 10.5 34.4 9.7 33.8 9.1 32.9 8.7 32 8.5 31.1 8.4 30.1 8.5 29.1 8.6 28.1 8.5 27.1 8.3 26.1 7.8 25.2 7.4 24.3 7.1 23.4 7 22.4 7.1 21.4 7.3 20.4 7.8 19.5 8.3 18.7 9.1 18 9.8 17.3 10.4 16.5 10.8 15.6 11 14.6 11.3 13.7 11.7 12.8 12.2 11.9 12.9 11.2 13.8 10.7 14.7 10.2 15.6 10 16.6 9.9 17.6 9.8Z;
M18.6 9.9 19.5 9.4 20.3 8.8 21.1 8.2 22 7.8 23 7.6 23.9 7.5 24.9 7.6 25.9 7.8 26.8 8.2 27.6 8.7 28.5 9.3 29.3 9.9 30.1 10.5 30.9 11 31.7 11.6 32.5 12.2 33.3 12.8 33.7 13.1 34.1 13.3 34.9 13.9 35.7 14.5 36.6 15 37.4 15.6 38.2 16.2 39 16.8 39.7 17.5 40.2 18.3 40.7 19.2 40.9 20.1 41 21.1 40.9 22.1 40.7 23.1 40.3 24 40 24.9 39.7 25.9 39.4 26.8 39 27.8 38.7 28.7 38.4 29.6 38.1 30.6 37.8 31.5 37.5 32.5 37.2 33.4 36.9 34.4 36.6 35.3 36.2 36.2 35.7 37.1 35 37.8 34.3 38.4 33.4 38.9 32.5 39.3 31.5 39.5 30.5 39.5 30 39.5 29.5 39.5 28.5 39.5 27.5 39.5 26.5 39.5 25.5 39.4 24.5 39.4 23.6 39.4 22.6 39.4 21.6 39.5 20.6 39.5 19.6 39.5 18.6 39.5 17.6 39.5 16.6 39.5 15.6 39.3 14.7 39 13.8 38.5 13.1 37.9 12.4 37.2 11.9 36.3 11.5 35.4 11.2 34.5 10.9 33.5 10.6 32.6 10.3 31.6 10 30.7 9.7 29.7 9.3 28.8 9 27.9 8.7 26.9 8.4 26 8 25 7.7 24.1 7.4 23.2 7.1 22.2 7.1 21.7 7 21.2 7.1 20.2 7.3 19.3 7.7 18.4 8.3 17.5 8.9 16.8 9.7 16.2 10.5 15.6 11.4 15.1 12.2 14.5 13 14 13.8 13.4 14.6 12.8 15.4 12.3 16.2 11.7 17 11.1 17.8 10.5Z;
M15.4 12.3 16.2 11.7 17 11.1 17.8 10.5 18.6 9.9 19.5 9.4 20.3 8.8 21.1 8.3 22 7.8 23 7.6 23.9 7.5 24.9 7.6 25.9 7.8 26.8 8.2 27.6 8.7 28.5 9.3 29.3 9.9 30.1 10.5 30.9 11 31.7 11.6 32.5 12.2 33.3 12.8 34.1 13.3 34.9 13.9 35.7 14.5 36.6 15 37.4 15.6 38.2 16.2 39 16.8 39.7 17.5 40.2 18.3 40.7 19.2 40.9 20.1 41 21.1 40.9 22.1 40.7 23.1 40.3 24 40 24.9 39.7 25.9 39.4 26.8 39 27.8 38.7 28.7 38.4 29.6 38.1 30.6 37.8 31.5 37.5 32.5 37.2 33.4 36.9 34.4 36.6 35.3 36.2 36.2 35.7 37.1 35 37.8 34.3 38.4 33.4 38.9 32.5 39.3 31.5 39.4 30.5 39.5 29.5 39.5 28.5 39.5 27.5 39.5 26.5 39.5 25.5 39.4 24.5 39.4 23.6 39.4 22.6 39.4 21.6 39.5 20.6 39.5 19.6 39.5 18.6 39.5 17.6 39.5 16.6 39.5 15.6 39.3 14.7 39 13.8 38.5 13.1 37.9 12.4 37.2 11.9 36.3 11.5 35.4 11.2 34.5 10.9 33.5 10.6 32.6 10.3 31.6 10 30.7 9.7 29.7 9.3 28.8 9 27.9 8.7 26.9 8.4 26 8 25 7.7 24.1 7.4 23.2 7.1 22.2 7 21.2 7.1 20.2 7.3 19.3 7.7 18.4 8.3 17.5 8.9 16.8 9.7 16.2 10.5 15.6 11.4 15.1 12.2 14.5 13 14 13.8 13.4 14.6 12.8Z;
M17 12.8 17.7 12.1 18.5 11.5 19.3 10.9 20.1 10.5 20.2 10.4 21.1 10 22 9.7 23 9.4 24 9.2 25 9 26 9 27 9 27.6 9.1 28 9.1 28.9 9.3 29.9 9.6 30.9 9.9 31.8 10.3 32.6 10.8 33.5 11.3 34.3 11.9 34.6 12.2 35 12.6 35.7 13.3 36.4 14.1 36.9 14.9 37.4 15.8 37.9 16.6 38.3 17.6 38.6 18.5 38.6 18.7 38.8 19.5 38.9 20.5 39 21.5 39 22.5 38.9 23.5 38.7 24.5 38.5 25.4 38.2 26.3 38.2 26.4 37.8 27.3 37.4 28.2 36.8 29.1 36.2 29.9 35.6 30.6 34.9 31.3 34.2 32 33.8 32.5 33.5 32.8 32.8 33.5 32.1 34.2 31.4 34.9 30.7 35.6 29.9 36.2 29.1 36.8 28.2 37.3 27.9 37.5 27.4 37.8 26.4 38.2 25.5 38.5 24.5 38.7 23.5 38.9 22.5 39 21.5 39 20.5 38.9 20.4 38.9 19.5 38.8 18.6 38.6 17.6 38.3 16.7 37.9 15.8 37.5 14.9 37 14.1 36.4 13.4 35.8 13.3 35.8 12.6 35.1 12 34.3 11.3 33.5 10.8 32.7 10.3 31.8 9.9 30.9 9.6 30 9.4 29.3 9.3 29 9.1 28 9 27 9 26 9 25 9.2 24 9.4 23 9.6 22.1 9.8 21.7 10 21.1 10.4 20.2 10.9 19.4 11.4 18.5 12.1 17.8 12.7 17 13.4 16.3 14.2 15.6 14.2 15.5 14.9 14.9 15.6 14.2 16.3 13.5Z;
M33.5 11.3 34.3 11.9 35 12.6 35.3 12.8 35.7 13.3 36.4 14.1 36.9 14.9 37.4 15.8 37.9 16.6 38.3 17.6 38.3 17.7 38.6 18.5 38.8 19.5 38.9 20.5 39 21.5 39 22.5 38.9 23.5 38.9 23.5 38.7 24.5 38.5 25.4 38.2 26.4 37.8 27.3 37.4 28.2 36.9 28.9 36.8 29.1 36.2 29.9 35.6 30.6 34.9 31.3 34.2 32 33.5 32.8 33.1 33.2 32.8 33.5 32.1 34.2 31.4 34.9 30.7 35.6 29.9 36.2 29.1 36.8 28.7 37 28.2 37.3 27.4 37.8 26.4 38.2 25.5 38.5 24.5 38.7 23.5 38.9 23.3 38.9 22.5 39 21.5 39 20.5 38.9 19.5 38.8 18.6 38.6 17.6 38.3 17.6 38.3 16.7 37.9 15.8 37.5 14.9 37 14.1 36.4 13.3 35.8 12.7 35.2 12.6 35.1 12 34.3 11.3 33.5 10.8 32.7 10.3 31.8 9.9 30.9 9.7 30.3 9.6 29.9 9.3 29 9.1 28 9 27 9 26 9 25 9.1 24.5 9.2 24 9.4 23 9.6 22.1 10 21.1 10.4 20.2 10.9 19.4 11.1 19.1 11.5 18.5 12.1 17.7 12.7 17 13.5 16.3 14.2 15.6 14.9 14.9 14.9 14.8 15.6 14.2 16.3 13.5 17 12.8 17.7 12.1 18.5 11.5 19.3 11 19.3 10.9 20.2 10.4 21.1 10 22 9.7 23 9.4 24 9.2 24.7 9.1 25 9 26 9 27 9 28 9.1 28.9 9.3 29.9 9.6 30.4 9.7 30.9 9.9 31.8 10.3 32.6 10.8Z;
M33.2 11.1 34.2 11.2 35.1 11.4 36 11.9 36.6 12.7 36.8 13.7 36.9 14.7 36.9 15.7 37 16.7 37.1 17.7 37.3 18.6 37.9 19.4 38.5 20.2 39.2 20.9 39.8 21.7 40.5 22.4 40.9 23.3 41 24.3 40.7 25.2 40.1 26 39.4 26.8 38.8 27.5 38.1 28.3 37.5 29.1 37.1 30 37 31 37 31.9 36.9 32.9 36.8 33.9 36.7 34.9 36.2 35.8 35.5 36.4 34.5 36.8 33.6 36.8 32.6 36.9 31.6 37 30.6 37.1 29.6 37.2 28.8 37.7 28 38.4 27.3 39 26.5 39.7 25.8 40.3 24.9 40.8 23.9 41 23 40.8 22.1 40.2 21.4 39.6 20.6 38.9 19.9 38.3 19.1 37.6 18.3 37.2 17.3 37 16.3 37 15.3 36.9 14.3 36.8 13.3 36.7 12.4 36.4 11.7 35.7 11.3 34.8 11.2 33.8 11.1 32.8 11 31.8 11 30.8 10.9 29.9 10.4 29 9.8 28.2 9.1 27.5 8.5 26.7 7.8 26 7.3 25.1 7 24.2 7.1 23.2 7.6 22.3 8.2 21.6 8.9 20.8 9.5 20.1 10.2 19.3 10.7 18.5 10.9 17.5 11 16.6 11.1 15.6 11.1 14.6 11.2 13.6 11.5 12.6 12.1 11.9 13 11.4 13.9 11.2 14.9 11.1 15.9 11 16.9 11 17.9 10.9 18.8 10.6 19.6 10 20.4 9.3 21.1 8.6 21.9 8 22.6 7.4 23.6 7 24.6 7.1 25.5 7.5 26.2 8.1 27 8.7 27.7 9.4 28.5 10 29.3 10.6 30.2 10.9 31.2 11 32.2 11.1Z;
M27.7 9.4 28.5 10 29.3 10.6 30.2 10.9 31.2 11 32.2 11.1 33.2 11.1 34.2 11.2 35.1 11.4 36 11.9 36.6 12.7 36.8 13.7 36.9 14.7 36.9 15.7 37 16.7 37.1 17.7 37.3 18.6 37.9 19.4 38.5 20.2 39.2 20.9 39.8 21.7 40.5 22.4 40.9 23.3 41 24.3 40.7 25.2 40.1 26 39.4 26.8 38.8 27.5 38.1 28.3 37.5 29.1 37.1 30 37 31 37 31.9 36.9 32.9 36.8 33.9 36.7 34.9 36.2 35.8 35.5 36.4 34.5 36.8 33.6 36.9 32.6 36.9 31.6 37 30.6 37.1 29.6 37.2 28.8 37.7 28 38.4 27.3 39 26.5 39.7 25.8 40.3 24.9 40.8 23.9 41 23 40.8 22.1 40.2 21.4 39.6 20.6 38.9 19.9 38.3 19.1 37.6 18.3 37.2 17.3 37 16.3 37 15.3 36.9 14.3 36.8 13.3 36.7 12.4 36.4 11.7 35.7 11.3 34.8 11.2 33.8 11.1 32.8 11 31.8 11 30.8 10.9 29.9 10.4 29 9.8 28.2 9.1 27.5 8.5 26.7 7.8 26 7.3 25.1 7 24.2 7.1 23.2 7.6 22.3 8.2 21.6 8.9 20.8 9.5 20.1 10.2 19.3 10.7 18.5 10.9 17.5 11 16.5 11.1 15.6 11.1 14.6 11.2 13.6 11.5 12.6 12.1 11.9 13 11.4 13.9 11.2 14.9 11.1 15.9 11 16.9 11 17.9 10.9 18.8 10.6 19.6 10 20.4 9.3 21.1 8.6 21.9 8 22.6 7.4 23.6 7 24.6 7.1 25.5 7.5 26.2 8.1 27 8.7Z;
M27.9 10.6 28.8 10.3 29.8 10.1 30.8 10 31.8 10.1 32.7 10.3 33.7 10.6 34.6 11.1 34.8 11.3 35.4 11.7 36.1 12.4 36.7 13.1 37.2 14 37.6 14.9 37.9 15.9 38 16.9 38 17.9 37.8 18.8 37.5 19.8 37.1 20.7 36.8 21.6 36.5 22.6 36.4 23.6 36.4 24.4 36.4 24.6 36.5 25.5 36.8 26.5 37.2 27.4 37.6 28.3 37.8 29.3 38 30.3 38 31.3 37.8 32.3 37.6 33.2 37.2 34.1 36.6 35 36 35.7 35.3 36.4 34.4 37 34.1 37.2 33.6 37.4 32.6 37.8 31.6 37.9 30.6 38 29.7 37.9 28.7 37.7 27.8 37.3 26.8 36.9 25.9 36.6 24.9 36.4 23.9 36.4 22.9 36.4 22 36.6 21 37 20.1 37.4 20.1 37.4 19.2 37.7 18.2 37.9 17.2 38 16.2 37.9 15.3 37.7 14.3 37.4 13.4 36.9 12.6 36.3 11.9 35.6 11.3 34.9 10.8 34 10.4 33.1 10.1 32.1 10 31.1 10 30.6 10 30.1 10.2 29.2 10.5 28.2 10.9 27.3 11.2 26.4 11.5 25.4 11.6 24.4 11.6 23.4 11.5 22.5 11.2 21.5 10.8 20.6 10.4 19.7 10.2 18.7 10 17.7 10 16.7 10 16.6 10.2 15.7 10.4 14.8 10.8 13.9 11.4 13 12 12.3 12.7 11.6 13.6 11 14.4 10.6 15.4 10.2 16.4 10.1 17.4 10 18.3 10.1 19.3 10.3 20.2 10.7 20.9 11 21.2 11.1 22.1 11.4 23.1 11.6 24.1 11.6 25.1 11.6 26 11.4 27 11Z;
M36 35.7 35.3 36.4 34.4 37 33.6 37.4 32.6 37.8 31.6 37.9 30.6 38 29.7 37.9 28.7 37.7 27.8 37.3 26.8 36.9 25.9 36.6 24.9 36.4 23.9 36.4 22.9 36.4 22 36.6 21 37 20.1 37.4 19.2 37.7 18.2 37.9 17.2 38 16.2 37.9 15.3 37.7 14.3 37.4 13.4 36.9 12.6 36.3 11.9 35.6 11.3 34.9 10.8 34 10.4 33.1 10.1 32.1 10 31.1 10 30.2 10.2 29.2 10.5 28.2 10.9 27.3 11.2 26.4 11.5 25.4 11.6 24.4 11.6 23.4 11.5 22.5 11.2 21.5 10.8 20.6 10.4 19.7 10.2 18.7 10 17.7 10 16.7 10.2 15.7 10.4 14.8 10.8 13.9 11.4 13 12 12.3 12.7 11.6 13.6 11 14.4 10.6 15.4 10.2 16.4 10.1 17.4 10 18.3 10.1 19.3 10.3 20.2 10.7 21.2 11.1 22.1 11.4 23.1 11.6 24.1 11.6 25.1 11.6 26 11.4 27 11 27.9 10.6 28.8 10.3 29.8 10.1 30.8 10 31.8 10.1 32.7 10.3 33.7 10.6 34.6 11.1 35.4 11.7 36.1 12.4 36.7 13.1 37.2 14 37.6 14.9 37.9 15.9 38 16.9 38 17.8 37.8 18.8 37.5 19.8 37.1 20.7 36.8 21.6 36.5 22.6 36.4 23.6 36.4 24.6 36.5 25.5 36.8 26.5 37.2 27.4 37.6 28.3 37.8 29.3 38 30.3 38 31.3 37.8 32.3 37.6 33.2 37.2 34.1 36.6 35Z;
M32.1 32.1 31.4 32.8 30.7 33.5 29.9 34.1 29.1 34.7 28.3 35.3 27.6 35.8 27.5 35.8 26.6 36.4 25.8 36.8 24.9 37.3 24 37.7 23.1 38 22.1 38.3 21.2 38.6 20.2 38.8 19.2 38.9 18.2 39 17.2 39 16.6 38.9 16.3 38.9 15.3 38.7 14.3 38.4 13.4 38 12.5 37.5 11.7 36.9 11.1 36.3 10.5 35.5 10 34.6 9.6 33.7 9.3 32.7 9.1 31.7 9.1 31.4 9 30.8 9 29.8 9.1 28.8 9.2 27.8 9.4 26.8 9.7 25.9 10 24.9 10.3 24 10.7 23.1 11.2 22.2 11.6 21.4 12.2 20.5 12.2 20.4 12.7 19.7 13.3 18.9 13.9 18.1 14.5 17.3 15.2 16.6 15.9 15.9 16.6 15.2 17.3 14.5 18.1 13.9 18.9 13.3 19.7 12.7 20.4 12.2 20.5 12.2 21.4 11.6 22.2 11.2 23.1 10.7 24 10.3 24.9 10 25.9 9.7 26.8 9.4 27.8 9.2 28.8 9.1 29.8 9 30.8 9 31.4 9.1 31.7 9.1 32.7 9.3 33.7 9.6 34.6 10 35.5 10.5 36.3 11.1 36.9 11.7 37.5 12.5 38 13.4 38.4 14.3 38.7 15.3 38.9 16.3 38.9 16.6 39 17.2 39 18.2 38.9 19.2 38.8 20.2 38.6 21.2 38.3 22.1 38 23.1 37.7 24 37.3 24.9 36.8 25.8 36.4 26.6 35.8 27.5 35.8 27.6 35.3 28.3 34.7 29.1 34.1 29.9 33.5 30.7 32.8 31.4Z;
M24.3 10.2 24.9 10 25.9 9.7 26.8 9.4 27.1 9.4 27.8 9.2 28.8 9.1 29.8 9 29.9 9 30.8 9 31.7 9.1 32.7 9.3 32.8 9.3 33.7 9.6 34.6 10 35.5 10.5 35.5 10.5 36.3 11.1 36.9 11.7 37.5 12.5 37.5 12.5 38 13.4 38.4 14.3 38.7 15.2 38.7 15.3 38.9 16.3 39 17.2 39 18.1 39 18.2 38.9 19.2 38.8 20.2 38.6 20.9 38.6 21.2 38.3 22.1 38 23.1 37.8 23.7 37.7 24 37.3 24.9 36.8 25.8 36.5 26.4 36.4 26.6 35.8 27.5 35.3 28.3 35 28.8 34.7 29.1 34.1 29.9 33.5 30.7 33.1 31.1 32.8 31.4 32.1 32.1 31.4 32.8 31.1 33.1 30.7 33.5 29.9 34.1 29.1 34.7 28.8 35 28.3 35.3 27.5 35.8 26.6 36.4 26.4 36.5 25.8 36.8 24.9 37.3 24 37.7 23.7 37.8 23.1 38 22.1 38.3 21.2 38.6 20.9 38.6 20.2 38.8 19.2 38.9 18.2 39 18.1 39 17.2 39 16.3 38.9 15.3 38.7 15.2 38.7 14.3 38.4 13.4 38 12.5 37.5 12.5 37.5 11.7 36.9 11.1 36.3 10.5 35.5 10.5 35.5 10 34.6 9.6 33.7 9.3 32.8 9.3 32.7 9.1 31.7 9 30.8 9 29.9 9 29.8 9.1 28.8 9.2 27.8 9.4 27.1 9.4 26.8 9.7 25.9 10 24.9 10.2 24.3 10.3 24 10.7 23.1 11.2 22.2 11.5 21.6 11.6 21.4 12.2 20.5 12.7 19.7 13 19.2 13.3 18.9 13.9 18.1 14.5 17.3 14.9 16.9 15.2 16.6 15.9 15.9 16.6 15.2 16.9 14.9 17.3 14.5 18.1 13.9 18.9 13.3 19.2 13 19.7 12.7 20.5 12.2 21.4 11.6 21.6 11.5 22.2 11.2 23.1 10.7 24 10.3Z;
M22.5 7.8 23.2 7.2 24.2 7 25.1 7.4 25.7 8.1 26.2 9 26.8 9.8 27.3 10.6 28.1 11.2 29 11.3 30 11 30.9 10.6 31.8 10.3 32.8 9.9 33.7 10 34.5 10.6 34.9 11.5 34.8 12.5 34.8 13.5 34.7 14.5 34.7 15.5 35.2 16.3 36 16.8 37 17 37.9 17.3 38.9 17.5 39.8 17.9 40.4 18.7 40.5 19.7 40 20.5 39.3 21.3 38.7 22 38.1 22.8 37.6 23.7 37.7 24.6 38.3 25.4 38.9 26.2 39.6 27 40.2 27.7 40.5 28.6 40.3 29.6 39.5 30.3 38.6 30.6 37.6 30.8 36.7 31 35.7 31.3 35 31.9 34.6 32.8 34.7 33.8 34.8 34.8 34.8 35.8 34.8 36.8 34.3 37.6 33.4 38.1 32.4 38 31.5 37.6 30.6 37.2 29.7 36.9 28.7 36.6 27.8 36.9 27.1 37.6 26.6 38.5 26.1 39.3 25.5 40.2 24.8 40.8 23.8 41 22.9 40.6 22.3 39.9 21.8 39 21.2 38.2 20.7 37.4 19.9 36.8 19 36.7 18 37 17.1 37.4 16.2 37.7 15.2 38.1 14.3 38 13.5 37.4 13.1 36.5 13.2 35.5 13.2 34.5 13.3 33.5 13.3 32.5 12.8 31.7 12 31.2 11 31 10.1 30.7 9.1 30.5 8.2 30.1 7.6 29.3 7.5 28.3 8 27.5 8.7 26.7 9.3 26 9.9 25.2 10.4 24.3 10.3 23.4 9.7 22.6 9.1 21.8 8.4 21 7.8 20.3 7.5 19.4 7.7 18.4 8.5 17.7 9.4 17.4 10.4 17.2 11.3 17 12.3 16.7 13 16.1 13.4 15.2 13.3 14.2 13.2 13.2 13.2 12.2 13.2 11.2 13.7 10.4 14.6 9.9 15.6 10 16.5 10.4 17.4 10.8 18.3 11.1 19.3 11.4 20.2 11.1 20.9 10.4 21.4 9.5 21.9 8.7Z"></animate>
<animateTransform attributeName="transform" attributeType="XML" type="rotate" dur="5s" repeatCount="indefinite" calcMode="spline" keySplines="0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8; 0.5 0.2 0 0.8" keyTimes="0; 0.14; 0.29; 0.43; 0.57; 0.71; 0.86; 1" values="0 24 24; 154 24 24; 309 24 24; 463 24 24; 617 24 24; 771 24 24; 926 24 24; 1080 24 24"></animateTransform>
</path>
</svg>

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,78 @@
:root,
body.light {
--primary:#c00100;
--on-primary:#ffffff;
--primary-container:#ffdad4;
--on-primary-container:#410000;
--secondary:#775651;
--on-secondary:#ffffff;
--secondary-container:#ffdad4;
--on-secondary-container:#2c1512;
--tertiary:#705c2e;
--on-tertiary:#ffffff;
--tertiary-container:#fbdfa6;
--on-tertiary-container:#251a00;
--error:#ba1a1a;
--on-error:#ffffff;
--error-container:#ffdad6;
--on-error-container:#410002;
--background:#fffbff;
--on-background:#201a19;
--surface:#fff8f6;
--on-surface:#201a19;
--surface-variant:#f5ddda;
--on-surface-variant:#534341;
--outline:#857370;
--outline-variant:#d8c2be;
--shadow:#000000;
--scrim:#000000;
--inverse-surface:#362f2e;
--inverse-on-surface:#fbeeec;
--inverse-primary:#ffb4a8;
--surface-dim:#e4d7d5;
--surface-bright:#fff8f6;
--surface-container-lowest:#ffffff;
--surface-container-low:#fef1ee;
--surface-container:#f8ebe9;
--surface-container-high:#f3e5e3;
--surface-container-highest:#ede0dd;
}
body.dark {
--primary:#ffb4a8;
--on-primary:#690100;
--primary-container:#930100;
--on-primary-container:#ffdad4;
--secondary:#e7bdb6;
--on-secondary:#442925;
--secondary-container:#5d3f3b;
--on-secondary-container:#ffdad4;
--tertiary:#dec48c;
--on-tertiary:#3e2e04;
--tertiary-container:#564419;
--on-tertiary-container:#fbdfa6;
--error:#ffb4ab;
--on-error:#690005;
--error-container:#93000a;
--on-error-container:#ffb4ab;
--background:#201a19;
--on-background:#ede0dd;
--surface:#181211;
--on-surface:#ede0dd;
--surface-variant:#534341;
--on-surface-variant:#d8c2be;
--outline:#a08c89;
--outline-variant:#534341;
--shadow:#000000;
--scrim:#000000;
--inverse-surface:#ede0dd;
--inverse-on-surface:#362f2e;
--inverse-primary:#c00100;
--surface-dim:#181211;
--surface-bright:#3f3736;
--surface-container-lowest:#120d0c;
--surface-container-low:#201a19;
--surface-container:#251e1d;
--surface-container-high:#2f2827;
--surface-container-highest:#3b3332;
}

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="200mm"
height="200mm"
viewBox="0 0 200 200"
version="1.1"
id="svg5"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="tytd.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview7"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:document-units="mm"
showgrid="false"
inkscape:zoom="1.1438552"
inkscape:cx="396.90338"
inkscape:cy="561.25985"
inkscape:window-width="2560"
inkscape:window-height="1528"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs2" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#ff0000;stroke-width:0.224175"
id="rect113"
width="120"
height="84"
x="40"
y="58"
ry="18.792341" />
<path
style="display:inline;fill:#00AA00;fill-rule:evenodd;stroke:#000000;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M
100 140
130 106
105 106
105 60
95 60
95 106
70 106
100 140
"
id="path510"
inkscape:connector-type="polyline"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,38 @@
func Components.AddToPersonalList(tytd,id)
{
return <form hx-post="./add-to-list" hx-swap="outerHTML">
<input type="hidden" name="id" value={id}>
<div class="row">
<div class="max">
<div class="field label suffix border fill">
<select name="name">
<option value="" selected>Create personal list</option>
<each(var item : tytd.GetPersonalLists())>
<option value={item.name}>Add to {item.name}</option>
</each>
</select>
<label>Personal List</label>
<i>arrow_drop_down</i>
</div>
</div>
<button name="action" value="add"><i>add</i></button>
</div>
</form>;
}
func Components.CreateNewList(tytd,id)
{
return <form hx-post="./add-to-list" hx-swap="outerHTML">
<input type="hidden" name="id" value={id}>
<div class="row">
<div class="field label border max">
<input type="text" name="name">
<label>List Name</label>
</div>
<button name="action" value="add"><i>add</i></button>
</div>
</form>;
}

View File

@@ -0,0 +1,30 @@
func Components.AddVideo(id)
{
return <form hx-post="./add-video" hx-swap="outerHTML">
<input type="hidden" name="url" value={id}>
<div class="row">
<div class="max">
<div class="field label suffix border fill">
<select name="res">
<each(var item : Resolution.Resolutions)>
<if(item.default)>
<true>
<option value={item.value} selected>{item.name}</option>
</true>
<false>
<option value={item.value}>{item.name}</option>
</false>
</if>
</each>
</select>
<label>Resolution</label>
<i>arrow_drop_down</i>
</div>
</div>
<button name="action" value="add"><i>add</i></button>
<button name="action" value="download"><i>download</i></button>
<button name="action" value="play"><i>play_arrow</i></button>
</div>
</form>;
}

View File

@@ -0,0 +1,33 @@
func Components.Add()
{
return <form hx-post="./add" hx-swap="outerHTML">
<div class="field border fill">
<input type="text" name="url">
</div>
<div class="row">
<div class="max">
<div class="field label suffix border fill">
<select name="res">
<each(var item : Resolution.Resolutions)>
<if(item.default)>
<true>
<option value={item.value} selected>{item.name}</option>
</true>
<false>
<option value={item.value}>{item.name}</option>
</false>
</if>
</each>
</select>
<label>Resolution</label>
<i>arrow_drop_down</i>
</div>
</div>
<button name="action" value="add"><i>add</i></button>
<button name="action" value="info"><i>info</i></button>
<button name="action" value="download"><i>download</i></button>
<button name="action" value="play"><i>play_arrow</i></button>
</div>
</form>;
}

View File

@@ -0,0 +1,62 @@
func Components.DiscoverEntry(item)
{
switch(item.type)
{
case "video":
return
<div class="row">
<div class="min">
<img src={$"./video-thumbnail?v={Net.Http.UrlEncode(item.id)}"} width="144" width="120">
</div>
<div class="max">
<div class="col">
<div class="min">
<a target="_blank" href={$"./video?v={Net.Http.UrlEncode(item.id)}"}>{item.title}</a>
</div>
<div class="min">
<span>{item.views} • {item.uploaded}</span>
</div>
<div class="min">
<each(var part : item.author)>
<if(TypeOf(part.navigationEndpoint) == "Dictionary")>
<true>
<a target="_blank" href={$"./channel?id={Net.Http.UrlEncode(part.navigationEndpoint.browseEndpoint.browseId)}"}>{part.text}</a>
</true>
<false>
<span>{part.text}</span>
</false>
</if>
</each>
</div>
</div>
</div>
</div>;
break;
case "channel":
return
<div class="row">
<div class="min">
<img src={$"./channel-thumbnail?id={Net.Http.UrlEncode(item.id)}"} width="144" width="144">
</div>
<div class="max">
<div class="col">
<div class="min">
<a target="_blank" href={$"./channel?id={Net.Http.UrlEncode(item.id)}"}>{item.title}</a>
</div>
<div class="min">
<span>{item.subs}</span>
</div>
<div class ="min">
<p class="min">{item.description}</p>
</div>
</div>
</div>
</div>;
break;
}
return "";
}

View File

@@ -0,0 +1,60 @@
func Components.DownloadedVideo(item)
{
return
<div class="row">
<div class="min">
<img src={$"./api/v1/video-thumbnail?v={Net.Http.UrlEncode(item.videoId)}"} width="144" width="120">
</div>
<div class="max">
<div class="col">
<div class="min">
<a hx-get={$"./video?v={Net.Http.UrlEncode(item.videoId)}"} hx-target="body" hx-push-url="true" href={$"./video?v={Net.Http.UrlEncode(item.videoId)}"}>{item.title}</a>
</div>
<div class="min">
<span>{item.viewCountStr} (when downloaded)</span>
</div>
<div class="min">
<a hx-get={$"./channel?id={Net.Http.UrlEncode(item.channelId)}"} href={$"./channel?id={Net.Http.UrlEncode(item.channelId)}"} hx-target="body" hx-push-url="true">{item.author}</a>
</div>
</div>
</div>
</div>;
}
func Components.DownloadedPlaylist(item)
{
var res = <div class="row">
<div class="min">
<img src={$"./api/v1/playlist-thumbnail?id={Net.Http.UrlEncode(item.playlistId)}"} width="144" width="120">
</div>
<div class="max">
<div class="col">
<div class="min">
<a hx-get={$"./playlist?id={Net.Http.UrlEncode(item.playlistId)}"} href={$"./playlist?id={Net.Http.UrlEncode(item.playlistId)}"} hx-target="body" hx-push-url="true">{item.title}</a>
</div>
<div class="min">
<a hx-get={$"./channel?id={Net.Http.UrlEncode(item.channelId)}"} href={$"./channel?id={Net.Http.UrlEncode(item.channelId)}"} hx-target="body" hx-push-url="true">{item.channelTitle}</a>
</div>
</div>
</div>
</div>;
return res;
}
func Components.DownloadedChannel(item)
{
var res = <div class="row">
<div class="min">
<img src={$"./api/v1/channel-thumbnail?id={Net.Http.UrlEncode(item.channelId)}"} width="144" width="120">
</div>
<div class="max">
<a hx-get={$"./channel?id={Net.Http.UrlEncode(item.channelId)}"} href={$"./channel?id={Net.Http.UrlEncode(item.channelId)}"} hx-target="body" hx-push-url="true">{item.title}</a>
</div>
</div>;
return res;
}

View File

@@ -0,0 +1,11 @@
func Components.MusicAlbum(item)
{
return <div class="row">
<div class="min">
<img src={$"./album-art?id={Net.Http.UrlEncode(item.id)}"} width="200" height="200">
</div>
<div class="min">
<a href={$"./music-album?id={Net.Http.UrlEncode(item.id)}"}>{item.title}</a>
</div>
</div>;
}

View File

@@ -0,0 +1,4 @@
func Components.MusicArtist(item)
{
return <a href={$"./music-artist?id={Net.Http.UrlEncode(item.id)}"}>{item.name}</a>;
}

View File

@@ -0,0 +1,77 @@
func Components.PackageItem(tytd,item)
{
var html = <div class="row">
<div class="min">
<img width="64" height="64" src={item.thumb} alt="Package thumbnail">
</div>
<div class="max">
<div class="col">
<div class="min">
<a target="_blank" href={item.url}>{item.name}</a>
</div>
<div class="min">
<span>{item.version}</span>
</div>
</div>
</div>
<div class="min">
<raw(Components.InstallButton(tytd,item.name,item.version))>
</div>
</div>;
return html;
}
func Components.InstallButton(tytd, name, version, $confirm)
{
var id = Crypto.Base64Encode(new ByteArray($"{name}-{version}")).Replace("=","");
var version = Version.Parse(version);
if(version != null)
{
var state = tytd.PackageState(name,version);
return <div id={id}>
<if(state == 0)>
<true>
<button hx-post="./package-manage" hx-vals={Json.Encode({action="install",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML">Install</button>
</true>
<false>
<if(confirm)>
<true>
<div class="row">
<div class="min">
<button hx-post="./package-manage" hx-vals={Json.Encode({action="confirm",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML">Yes Uninstall</button>
</div>
<div class="min">
<button hx-post="./package-manage" hx-vals={Json.Encode({action="cancel",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML">Cancel</button>
</div>
</div>
</true>
<false>
<if(state == 1)>
<true>
<button hx-post="./package-manage" hx-vals={Json.Encode({action="uninstall",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML">Uninstall</button>
</true>
<false>
<nav class="group split">
<button hx-post="./package-manage" hx-vals={Json.Encode({action="install",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML" class="left-round">
<i>update</i>
<span>Update</span>
</button>
<button hx-post="./package-manage" hx-vals={Json.Encode({action="uninstall",name=name,version=version.ToString()})} hx-target={$"#{id}"} hx-swap="outerHTML" class="right-round square">
<i>delete</i>
</button>
</nav>
</false>
</if>
</false>
</if>
</false>
</if>
</div>;
}
return "";
}

View File

@@ -0,0 +1,30 @@
func Components.InstalledPlugin(item)
{
var html = <div class="row">
<div class="min">
<img width="64" height="64" src={$"./api/v1/plugin-thumbnail.png?name={Net.Http.UrlEncode(item.pluginName)}"} alt="Package thumbnail">
</div>
<div class="max">
<div class="col">
<div class="min">
<if(item.pluginObject.Server != undefined && item.pluginObject.Server != null)>
<true>
<a href={$"./plugin/{Net.Http.UrlPathEncode(item.pluginName)}/"}>{TypeOf(item.info.short_name_pretty) == "String" ? item.info.short_name_pretty : (TypeOf(item.info.short_name) == "String" ? item.info.short_name : item.name)}</a>
</true>
<false>
<span>{TypeOf(item.info.short_name_pretty) == "String" ? item.info.short_name_pretty : (TypeOf(item.info.short_name) == "String" ? item.info.short_name : item.name)}</span>
</false>
</if>
</div>
<div class="min">
<span>{item.version}</span>
</div>
</div>
</div>
</div>;
return html;
}

View File

@@ -0,0 +1,13 @@
var progress=0;
func Components.Progress(tytd)
{
var vid = tytd.CurrentVideo;
var html = <div hx-trigger="every 1500ms" hx-indicator="none" hx-get="./progress" hx-swap="outerHTML">
<h2>{vid.Title}</h2>
<h4>{vid.Channel}</h4>
<progress value={tytd.CurrentVideoProgress * 100.0} max="100" min="0"></progress>
</div>;
progress++;
return html;
}

View File

@@ -0,0 +1,68 @@
func Components.Shell(title, html, page, $mypage)
{
if(TypeOf(mypage) != "Path")
mypage = /;
var index = (/).MakeRelative(mypage).ToString();
if(index == "") index = "./";
var pages = [
{
text="Home",
icon="home",
href=index,
classStr=""
},
{
text = "Downloads",
icon = "download",
href=(/"downloads").MakeRelative(mypage).ToString(),
classStr=""
},
{
text = "Plugins",
icon = "extension",
href= (/"plugins").MakeRelative(mypage).ToString(),
classStr=""
},
{
text = "Settings",
icon = "settings",
href= (/"settings").MakeRelative(mypage).ToString(),
classStr=""
}
];
pages[page].classStr = "active";
return <!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TYTD - {title}</title>
<link rel="stylesheet" href={(/"beer.min.css").MakeRelative(mypage).ToString()}>
<link rel="stylesheet" href={(/"theme.css").MakeRelative(mypage).ToString()}>
<script src={(/"htmx.min.js").MakeRelative(mypage).ToString()} defer></script>
<script type="module" src={(/"beer.min.js").MakeRelative(mypage).ToString()} defer></script>
</head>
<body hx-indicator="#loading-indicator">
<nav class="bottom">
<each(var page : pages)>
<a hx-get={page.href} hx-target="body" hx-push-url="true" class={page.classStr}>
<i>{page.icon}</i>
<div>{page.text}</div>
</a>
</each>
</nav>
<main class="responsive">
<div class="htmx-indicator shape loading-indicator extra" id="loading-indicator">
<img class="responsive" src="./tytd.svg">
</div>
<raw(html)>
</main>
</body>
</html>;
}

View File

@@ -0,0 +1,46 @@
func Components.Subscribe(tytd,id)
{
var bell = tytd.GetSubscriptionBell(id);
var subscribed= bell != null;
var bellLogo = "notifications_off";
switch(bell)
{
case SubscriptionBell.DownloadLow:
case SubscriptionBell.DownloadHigh:
bellLogo = "download";
break;
case SubscriptionBell.Bell:
case SubscriptionBell.BellLow:
case SubscriptionBell.BellHigh:
bellLogo="notifications_active";
break;
}
<return>
<div id="subscribe-btn">
<if(subscribed)>
<true>
<div class="row">
<div class="min">
<button hx-post="./subscribe" hx-swap="outerHTML" hx-target="#subscribe-btn" hx-vals={Json.Encode({id,type="unsubscribe"})}>Unsubscribe</button>
</div>
<div class="min">
<button class="circle">
<i>{bellLogo}</i>
<menu class="left no-wrap">
<each(var b : SubscriptionBell.Bells)>
<li hx-post="./subscribe" hx-swap="outerHTML" hx-target="#subscribe-btn" hx-vals={Json.Encode({id,type="bell",bell=b.value})}>{b.name}</li>
</each>
</menu>
</button>
</div>
</div>
</true>
<false>
<button hx-post="./subscribe" hx-swap="outerHTML" hx-target="#subscribe-btn" hx-vals={Json.Encode({id,type="subscribe"})}>Subscribe</button>
</false>
</if>
</div>
</return>
}

View File

@@ -0,0 +1,374 @@
var TYTDResources = [
{path="/beer.min.css",value=embed("beer.min.css")},
{path="/beer.min.js",value=embed("beer.min.js")},
{path="/material-symbols-outlined.woff2",value=embed("material-symbols-outlined.woff2")},
{path="/material-symbols-rounded.woff2",value=embed("material-symbols-rounded.woff2")},
{path="/material-symbols-sharp.woff2",value=embed("material-symbols-sharp.woff2")},
{path="/material-symbols-subset.woff2",value=embed("material-symbols-subset.woff2")},
{path="/htmx.min.js",value=embed("htmx.min.js")},
{path="/favicon.ico",value=embed("favicon.ico")},
{path="/tytd.svg",value=embed("tytd.svg")},
{path="/loading-indicator.svg",value=embed("loading-indicator.svg")},
{path="/theme.css",value=embed("theme.css")}
];
var times=1;
class TYTDApp {
private TYTD;
public TYTDApp()
{
var tytdfs = new SubdirFilesystem(FS.Local, "TYTD");
this.TYTD = new TYTD.Downloader(tytdfs,FS.MakeFull("TYTD"));
this.TYTD.Start();
}
public Handle(ctx)
{
if(ctx.Path == "/")
{
ctx.WithMimeType("text/html").SendText(Pages.Index(this.TYTD));
return true;
}
else if(ctx.Path == "/api/v1/download")
{
var v = ctx.QueryParams.TryGetFirst("v");
var inline = ctx.QueryParams.GetFirstBoolean("inline");
var res = ctx.QueryParams.TryGetFirst("res");
if(TypeOf(res) != "String") res = Resolution.LowVideo;
var path = this.TYTD.GetVideoPath(v,res);
if(path != null)
{
var info = this.TYTD.GetVideo(v);
var filename = $"{info.title}-{info.videoId}.{path.GetExtension()}";
var strm = this.TYTD.Storage.OpenFile(path,"rb");
ctx.WithMimeType(Net.Http.MimeType(path.ToString())).WithContentDisposition(filename,inline).SendStream(strm);
strm.Close();
}
}
else if(ctx.Path == "/api/v1/playlist.json")
{
var id = ctx.QueryParams.TryGetFirst("id");
var page = ctx.QueryParams.TryGetFirstInt("page");
var count = ctx.QueryParams.TryGetFirstInt("count");
if(TypeOf(page)!="Long") page = 1;
if(TypeOf(count)!="Long") count = 20;
if(TypeOf(id)!="String") id = "";
ctx.WithMimeType("application/json").SendJson(this.TYTD.GetPlaylistContents(id,page-1,count));
return true;
}
else if(ctx.Path == "/api/v1/channel.json")
{
var id = ctx.QueryParams.TryGetFirst("id");
var page = ctx.QueryParams.TryGetFirstInt("page");
var count = ctx.QueryParams.TryGetFirstInt("count");
if(TypeOf(page)!="Long") page = 1;
if(TypeOf(count)!="Long") count = 20;
if(TypeOf(id)!="String") id = "";
ctx.WithMimeType("application/json").SendJson(this.TYTD.GetChannelContents(id,page-1,count));
return true;
}
else if(ctx.Path == "/api/v1/downloads.json")
{
var q = ctx.QueryParams.TryGetFirst("q");
var type = ctx.QueryParams.TryGetFirst("type");
var page = ctx.QueryParams.TryGetFirstInt("page");
var count = ctx.QueryParams.TryGetFirstInt("count");
if(TypeOf(page)!="Long") page = 1;
if(TypeOf(count)!="Long") count = 20;
if(TypeOf(type)!="String") type = "videos";
if(TypeOf(q)!="String") q = "";
var result = { entries = [] };
switch(type)
{
case "videos":
result.entries = this.TYTD.GetVideos(q,page-1,count);
break;
case "playlists":
result.entries = this.TYTD.GetPlaylists(q,page-1,count);
break;
case "channels":
result.entries = this.TYTD.GetChannels(q,page-1,count);
break;
}
ctx.WithMimeType("application/json").SendJson(result);
return true;
}
else if(ctx.Path == "/subscribe")
{
var id = ctx.QueryParams.TryGetFirst("id");
var bell = ctx.QueryParams.TryGetFirst("bell");
var type = ctx.QueryParams.TryGetFirst("type");
switch(type)
{
case "subscribe":
this.TYTD.SetSubscriptionBell(id, SubscriptionBell.BellLow);
break;
case "unsubscribe":
this.TYTD.SetSubscriptionBell(id, null);
break;
case "bell":
this.TYTD.SetSubscriptionBell(id, bell);
break;
}
ctx.WithMimeType("text/html").SendText(Components.Subscribe(this.TYTD,id));
return true;
}
else if(ctx.Path == "/downloads")
{
ctx.WithMimeType("text/html").SendText(Pages.Downloads(this.TYTD,ctx));
return true;
}
else if(ctx.Path == "/list")
{
ctx.WithMimeType("text/html").SendText(Pages.List(this.TYTD,ctx));
return true;
}
else if(ctx.Path == "/settings")
{
if(ctx.Method == "POST")
{
/*
hours
minutes
seconds
tag
enablePlugins
*/
var enablePlugins = ctx.QueryParams.GetFirstBoolean("enablePlugins");
var tag = ctx.QueryParams.TryGetFirst("tag");
var hours = ctx.QueryParams.TryGetFirstInt("hours");
var minutes = ctx.QueryParams.TryGetFirstInt("minutes");
var seconds = ctx.QueryParams.TryGetFirstInt("seconds");
if(TypeOf(tag) == "String" && TypeOf(hours) == "Long" && TypeOf(minutes) == "Long" && TypeOf(seconds) == "Long")
{
seconds += minutes * 60;
seconds += hours * 3600;
this.TYTD.Config.TYTDTag = tag;
this.TYTD.Config.BellTimer = seconds;
this.TYTD.Config.EnablePlugins = enablePlugins;
this.TYTD.SaveConfig();
}
}
ctx.WithMimeType("text/html").SendText(Pages.Settings(this.TYTD,ctx));
return true;
}
else if(ctx.Path == "/video")
{
ctx.WithMimeType("text/html").SendText(Pages.VideoInfo(this.TYTD,ctx));
return true;
}
else if(ctx.Path == "/playlist")
{
ctx.WithMimeType("text/html").SendText(Pages.PlaylistInfo(this.TYTD,ctx));
return true;
}
else if(ctx.Path == "/channel")
{
ctx.WithMimeType("text/html").SendText(Pages.ChannelInfo(this.TYTD,ctx));
return true;
}
else if(ctx.Path == "/add")
{
var url = ctx.QueryParams.TryGetFirst("url");
var action =ctx.QueryParams.TryGetFirst("action");
var res = ctx.QueryParams.TryGetFirst("res");
if(action == "add")
{
this.TYTD.DownloadItem(url,res);
}
else if(action == "info")
{
ctx.ResponseHeaders.SetValue("HX-Redirect", TYTD.PageRedirect(url));
}
else if(action == "download")
{
ctx.ResponseHeaders.SetValue("HX-Redirect", $"./api/v1/download?v={Net.Http.UrlEncode(url)}&res={Net.Http.UrlEncode(res)}");
}
else if(action == "play")
{
ctx.ResponseHeaders.SetValue("HX-Redirect", $"./api/v1/download?v={Net.Http.UrlEncode(url)}&res={Net.Http.UrlEncode(res)}&inline=true");
}
ctx.WithMimeType("text/html").SendText(Components.Add());
return true;
}
else if(ctx.Path == "/add-video")
{
var url = ctx.QueryParams.TryGetFirst("url");
var action =ctx.QueryParams.TryGetFirst("action");
var res = ctx.QueryParams.TryGetFirst("res");
if(action == "add")
{
this.TYTD.DownloadItem(url,res);
}
else if(action == "download")
{
ctx.ResponseHeaders.SetValue("HX-Redirect", $"./api/v1/download?v={Net.Http.UrlEncode(url)}&res={Net.Http.UrlEncode(res)}");
}
else if(action == "play")
{
ctx.ResponseHeaders.SetValue("HX-Redirect", $"./api/v1/download?v={Net.Http.UrlEncode(url)}&res={Net.Http.UrlEncode(res)}&inline=true");
}
ctx.WithMimeType("text/html").SendText(Components.AddVideo(url));
return true;
}
else if(ctx.Path == "/add-to-list")
{
var id = ctx.QueryParams.TryGetFirst("id");
if(TypeOf(id) != "String") id = "";
var name = ctx.QueryParams.TryGetFirst("name");
if(TypeOf(name) != "String") name = "";
if(name == "")
{
ctx.WithMimeType("text/html").SendText(Components.CreateNewList(this.TYTD,id));
}
else {
this.TYTD.AddToPersonalList(name,id);
ctx.WithMimeType("text/html").SendText(Components.AddToPersonalList(this.TYTD,id));
}
return true;
}
else if(ctx.Path == "/progress")
{
ctx.WithMimeType("text/html").SendText(Components.Progress(this.TYTD));
return true;
}
else if(ctx.Path == "/plugins")
{
ctx.WithMimeType("text/html").SendText(Pages.Plugins(this.TYTD,ctx));
return true;
}
else if(ctx.Path == "/plugins-download")
{
ctx.WithMimeType("text/html").SendText(Pages.DownloadPlugins(this.TYTD,ctx));
return true;
}
else if(ctx.Path == "/package-manage" && ctx.Method == "POST")
{
var data = {
name = ctx.QueryParams.TryGetFirst("name"),
version = ctx.QueryParams.TryGetFirst("version"),
action = ctx.QueryParams.TryGetFirst("action")
};
var mustConfirm = false;
switch(data.action)
{
case "install":
{
var v = Version.Parse(data.version);
if(v != null)
this.TYTD.PackageInstall(data.name,v);
}
break;
case "uninstall":
{
mustConfirm = true;
}
break;
case "confirm":
{
this.TYTD.PackageUninstall(data.name);
}
break;
}
ctx.WithMimeType("text/html").SendText(Components.InstallButton(this.TYTD, data.name, data.version, mustConfirm));
return true;
}
else if(ctx.Path == "/api/v1/video-thumbnail")
{
var v = ctx.QueryParams.TryGetFirst("v");
var res = ctx.QueryParams.TryGetFirst("res");
if(TypeOf(res) != "String") res = "0";
var thu = this.TYTD.GetVideoThumbnail(v,res);
if(thu == null) return false;
ctx.WithMimeType("image/jpg").SendBytes(thu);
return true;
}
else if(ctx.Path == "/api/v1/playlist-thumbnail")
{
var id = ctx.QueryParams.TryGetFirst("id");
var res = ctx.QueryParams.TryGetFirst("res");
if(TypeOf(res) != "String") res = "0";
var thu = this.TYTD.GetPlaylistThumbnail(id,res);
if(thu == null) return false;
ctx.WithMimeType("image/jpg").SendBytes(thu);
return true;
}
else if(ctx.Path == "/api/v1/channel-thumbnail")
{
var id = ctx.QueryParams.TryGetFirst("id");
var res = ctx.QueryParams.TryGetFirst("res");
if(TypeOf(res) != "String") res = "0";
var thu = this.TYTD.GetChannelThumbnail(id,res);
if(thu == null) return false;
ctx.WithMimeType("image/jpg").SendBytes(thu);
return true;
}
else if(ctx.Path == "/api/v1/plugin-thumbnail.png")
{
var name = ctx.QueryParams.TryGetFirst("name");
ctx.WithMimeType().SendBytes(this.TYTD.GetPluginThumbnail(name));
return true;
}
else if(ctx.Path.StartsWith("/plugin/"))
{
return this.TYTD.Servers.Handle(ctx);
}
else {
each(var file : TYTDResources)
{
if(ctx.Path == file.path)
{
ctx.WithMimeType(Net.Http.MimeType(file.path)).SendBytes(file.value);
return true;
}
}
}
return false;
}
public Close()
{
this.TYTD.Stop();
}
}
func WebAppMain(args)
{
return new TYTDApp();
}
func main(args)
{
var res = WebAppMain(args);
Net.Http.ListenSimpleWithLoop(res,3255);
res.Close();
return 0;
}

View File

@@ -0,0 +1,39 @@
func Pages.ChannelInfo(tytd,ctx)
{
var id = ctx.QueryParams.TryGetFirst("id");
var page = ctx.QueryParams.TryGetFirstInt("page");
if(TypeOf(page) != "Long") page = 1;
page--;
var html = <h1>Could not find channel</h1>;
var title = "N/A";
if(TypeOf(id) == "String")
{
id = TYTD.GetChannelId(id);
var res = tytd.GetChannelContents(id,page,20);
html = <null>
<div class="row">
<div class="max">
<h1>{res.authorName}</h1>
</div>
<div class="min">
<raw(Components.Subscribe(tytd,id))>
</div>
</div>
<each(var item : res.items)>
<raw(Components.DownloadedVideo(item))>
</each>
<footer class="row center-align">
<button hx-get={$"./channel?id={Net.Http.UrlEncode(id)}&page={page}"} hx-target="body" hx-push-url="true" >Prev</button>{page+1}<button hx-get={$"./channel?id={Net.Http.UrlEncode(id)}&type=videos&page={page+2}"} hx-target="body" hx-push-url="true" >Next</button>
</footer>
</null>;
title = res.authorName;
}
return Components.Shell($"Channel {title}",html,1);
}

View File

@@ -0,0 +1,34 @@
func Pages.Discover(tytd,ctx)
{
var q = ctx.QueryParams.TryGetFirst("q");
var res = null;
var q2 = "";
if(TypeOf(q) == "String")
{
q2 = q;
res = tytd.Discover(q);
}
var html = <null>
<form hx-get="./discover" hx-target="body" hx-push-url="true">
<div class="field large prefix round fill active">
<i class="front">search</i>
<input name="q" value={q2}>
</div>
</form>
<if(res != null)>
<true>
<ul>
<each(var item : res.items)>
<li><raw(Components.DiscoverEntry(item))></li>
</each>
</ul>
<button class="responsive">More</button>
</true></null>;
return Components.Shell($"Discover {q2}",html,1);
}

View File

@@ -0,0 +1,293 @@
func Pages.Downloads(tytd,ctx)
{
var type = ctx.QueryParams.TryGetFirst("type");
var q = ctx.QueryParams.TryGetFirst("q");
var page = ctx.QueryParams.TryGetFirstInt("page");
if(TypeOf(page) != "Long") page = 1;
page--;
if(TypeOf(q) != "String")
{
q = "";
}
if(TypeOf(type) != "String")
{
type = "videos";
}
var html ="";
switch(type) {
case "videos":
{
var res = tytd.GetVideos(q,page,20);
html = <null>
<form hx-get="./downloads" hx-target="body" hx-push-url="true" class="s m">
<div class="row">
<div class="max">
<div class="field label suffix border round fill">
<select name="type">
<option value="videos" selected>Videos</option>
<option value="playlists">Playlists</option>
<option value="channels">Channels</option>
<option value="personal">Personal List</option>
</select>
<label>Type</label>
<i>arrow_drop_down</i>
</div>
</div>
<div class="min">
<button>Change</button>
</div>
</div>
</form>
<nav class="tabbed l">
<a class="active" hx-get="./downloads?type=videos" hx-target="body" hx-push-url="true">
<i>movie</i>
<span>Videos</span>
</a>
<a hx-get="./downloads?type=playlists" hx-target="body" hx-push-url="true">
<i>list</i>
<span>Playlists</span>
</a>
<a hx-get="./downloads?type=channels" hx-target="body" hx-push-url="true">
<i>person</i>
<span>Channels</span>
</a>
<a hx-get="./downloads?type=personal" hx-target="body" hx-push-url="true">
<i>edit_note</i>
<span>Personal Lists</span>
</a>
</nav>
<form hx-get="./downloads" hx-target="body" hx-push-url="true">
<div class="field large prefix round fill active">
<i class="front">search</i>
<input name="q" value={q}>
<input name="type" type="hidden" value="videos">
</div>
</form>
<each(var item : res)>
<raw(Components.DownloadedVideo(item))>
</each>
<footer class="row center-align">
<button hx-get={$"./downloads?q={Net.Http.UrlEncode(q)}&type=videos&page={page}"} hx-target="body" hx-push-url="true" >Prev</button>{page+1}<button hx-get={$"./downloads?q={Net.Http.UrlEncode(q)}&type=videos&page={page+2}"} hx-target="body" hx-push-url="true" >Next</button>
</footer>
</null>;
}
break;
case "playlists":
{
var res = tytd.GetPlaylists(q,page,20);
html = <null>
<form hx-get="./downloads" hx-target="body" hx-push-url="true" class="s m">
<div class="row">
<div class="max">
<div class="field label suffix border round fill">
<select name="type">
<option value="videos">Videos</option>
<option value="playlists" selected>Playlists</option>
<option value="channels">Channels</option>
<option value="personal">Personal List</option>
</select>
<label>Type</label>
<i>arrow_drop_down</i>
</div>
</div>
<div class="min">
<button>Change</button>
</div>
</div>
</form>
<nav class="tabbed l">
<a hx-get="./downloads?type=videos" hx-target="body" hx-push-url="true">
<i>movie</i>
<span>Videos</span>
</a>
<a class="active" hx-get="./downloads?type=playlists" hx-target="body" hx-push-url="true">
<i>list</i>
<span>Playlists</span>
</a>
<a hx-get="./downloads?type=channels" hx-target="body" hx-push-url="true">
<i>person</i>
<span>Channels</span>
</a>
<a hx-get="./downloads?type=personal" hx-target="body" hx-push-url="true">
<i>edit_note</i>
<span>Personal Lists</span>
</a>
</nav>
<form hx-get="./downloads" hx-target="body" hx-push-url="true">
<div class="field large prefix round fill active">
<i class="front">search</i>
<input name="q" value={q}>
<input name="type" type="hidden" value="playlists">
</div>
</form>
<each(var item : res)>
<raw(Components.DownloadedPlaylist(item))>
</each>
<footer class="row center-align">
<button hx-get={$"./downloads?q={Net.Http.UrlEncode(q)}&type=playlists&page={page}"} hx-target="body" hx-push-url="true" >Prev</button>{page+1}<button hx-get={$"./downloads?q={Net.Http.UrlEncode(q)}&type=playlists&page={page+2}"} hx-target="body" hx-push-url="true" >Next</button>
</footer>
</null>;
}
break;
case "channels":
{
var res = tytd.GetChannels(q,page,20);
html = <null>
<form hx-get="./downloads" hx-target="body" hx-push-url="true" class="s m">
<div class="row">
<div class="max">
<div class="field label suffix border round fill">
<select name="type">
<option value="videos">Videos</option>
<option value="playlists">Playlists</option>
<option value="channels" selected>Channels</option>
<option value="personal">Personal List</option>
</select>
<label>Type</label>
<i>arrow_drop_down</i>
</div>
</div>
<div class="min">
<button>Change</button>
</div>
</div>
</form>
<nav class="tabbed l">
<a hx-get="./downloads?type=videos" hx-target="body" hx-push-url="true">
<i>movie</i>
<span>Videos</span>
</a>
<a hx-get="./downloads?type=playlists" hx-target="body" hx-push-url="true">
<i>list</i>
<span>Playlists</span>
</a>
<a class="active" hx-get="./downloads?type=channels" hx-target="body" hx-push-url="true">
<i>person</i>
<span>Channels</span>
</a>
<a hx-get="./downloads?type=personal" hx-target="body" hx-push-url="true">
<i>edit_note</i>
<span>Personal Lists</span>
</a>
</nav>
<form hx-get="./downloads" hx-target="body" hx-push-url="true">
<div class="field large prefix round fill active">
<i class="front">search</i>
<input name="q" value={q}>
<input name="type" type="hidden" value="channels">
</div>
</form>
<each(var item : res)>
<raw(Components.DownloadedChannel(item))>
</each>
<footer class="row center-align">
<button hx-get={$"./downloads?q={Net.Http.UrlEncode(q)}&type=channels&page={page}"} hx-target="body" hx-push-url="true" >Prev</button>{page+1}<button hx-get={$"./downloads?q={Net.Http.UrlEncode(q)}&type=channels&page={page+2}"} hx-target="body" hx-push-url="true" >Next</button>
</footer>
</null>;
}
break;
case "personal":
{
var res = tytd.GetPersonalLists();
html = <null>
<form hx-get="./downloads" hx-target="body" hx-push-url="true" class="s m">
<div class="row">
<div class="max">
<div class="field label suffix border round fill">
<select name="type">
<option value="videos">Videos</option>
<option value="playlists">Playlists</option>
<option value="channels">Channels</option>
<option value="personal" selected>Personal List</option>
</select>
<label>Type</label>
<i>arrow_drop_down</i>
</div>
</div>
<div class="min">
<button>Change</button>
</div>
</div>
</form>
<nav class="tabbed l">
<a hx-get="./downloads?type=videos" hx-target="body" hx-push-url="true">
<i>movie</i>
<span>Videos</span>
</a>
<a hx-get="./downloads?type=playlists" hx-target="body" hx-push-url="true">
<i>list</i>
<span>Playlists</span>
</a>
<a hx-get="./downloads?type=channels" hx-target="body" hx-push-url="true">
<i>person</i>
<span>Channels</span>
</a>
<a class="active" hx-get="./downloads?type=personal" hx-target="body" hx-push-url="true">
<i>edit_note</i>
<span>Personal Lists</span>
</a>
</nav>
<each(var item : res)>
<if(TypeOf(item.firstVideo) == "String")>
<true>
<div class="row">
<div class="min">
<img src={$"./api/v1/video-thumbnail?v={Net.Http.UrlEncode(item.firstVideo)}"} width="144" width="120">
</div>
<div class="max">
<a href={$"./list?name={Net.Http.UrlEncode(item.name)}"}>{item.name}</a>
</div>
</div>
</true>
<false>
<div><a href={$"./list?name={Net.Http.UrlEncode(item.name)}"}>{item.name}</a></div>
</false>
</if>
</each>
</null>;
}
break;
}
return Components.Shell($"Downloads {q}",html,1);
}

View File

@@ -0,0 +1,11 @@
func Pages.Index(tytd)
{
var html = <null>
<raw(Components.Add())>
<raw(Components.Progress(tytd))>
</null>;
return Components.Shell("Home",html,0);
}

View File

@@ -0,0 +1,36 @@
func Pages.List(tytd,ctx)
{
var name = ctx.QueryParams.TryGetFirst("name");
var page = ctx.QueryParams.TryGetFirstInt("page");
if(TypeOf(page) != "Long") page = 1;
page--;
if(TypeOf(name) != "String")
{
name = "";
}
var res = tytd.GetPersonalListContents(name, page, 20);
var html = <null>
<div class="row">
<div class="min">
<button hx-get="./downloads?type=personal" hx-target="body" hx-push-url="true"><i>arrow_back</i></button>
</div>
<div class="max">
<h1>{name}</h1>
</div>
</div>
<each(var item : res)>
<raw(Components.DownloadedVideo(item))>
</each>
<footer class="row center-align">
<button hx-get={$"./downloads?name={Net.Http.UrlEncode(name)}&page={page}"} hx-target="body" hx-push-url="true" >Prev</button>{page+1}<button hx-get={$"./downloads?name={Net.Http.UrlEncode(name)}&page={page+2}"} hx-target="body" hx-push-url="true" >Next</button>
</footer>
</null>;
return Components.Shell($"Personal List {name}",html,1);
}

View File

@@ -0,0 +1,25 @@
func Pages.Artist(music,ctx)
{
var id = ctx.QueryParams.TryGetFirst("id");
var res = music.GetArtistsAlbums(id);
if(TypeOf(id) != "String")
{
return Components.Shell("Error",<h1>Artist id not specified</h1>,pages);
}
var html = <null>
<h1>{res.name}</h1>
<ul>
<each(var item : res.items)>
<li><raw(Components.MusicAlbum(item))></li>
</each>
</ul>
</null>;
return Components.Shell($"Artist {res.name}",html,2);
}

View File

@@ -0,0 +1,33 @@
func Pages.Artists(music,ctx)
{
var q = ctx.QueryParams.TryGetFirst("q");
var res = null;
var q2 = "";
if(TypeOf(q) == "String")
{
q2 = q;
res = music.GetArtists(q);
}
var html = <null>
<form hx-get="./music-artists" hx-target="body" hx-push-url="true">
<div class="field large prefix round fill active">
<i class="front">search</i>
<input name="q" value={q2}>
</div>
</form>
<if(res != null)>
<true>
<ul>
<each(var item : res)>
<li><raw(Components.MusicArtist(item))></li>
</each>
</ul>
</true></null>;
return Components.Shell($"Search Artists {q2}",html,2);
}

View File

@@ -0,0 +1,8 @@
func Pages.Music(ctx)
{
var html = <ul>
<div><a href="./music-artists">Artists</a></div>
<div><a href="./music-downloaded">My Music</a></div>
</ul>;
return Components.Shell("Music",html,2);
}

View File

@@ -0,0 +1,39 @@
func Pages.PlaylistInfo(tytd,ctx)
{
var id = ctx.QueryParams.TryGetFirst("id");
var page = ctx.QueryParams.TryGetFirstInt("page");
if(TypeOf(page) != "Long") page = 1;
page--;
var html = <h1>Could not find playlist</h1>;
var title = "N/A";
if(TypeOf(id) == "String")
{
id = TYTD.GetPlaylistId(id);
var res = tytd.GetPlaylistContents(id,page,20);
html = <null>
<div class="col">
<div class="max">
<h1>{res.title}</h1>
</div>
<div class="max">
<a href={$"./channel?id={Net.Http.UrlEncode(res.channelId)}"}>{res.channelTitle}</a>
</div>
</div>
<each(var item : res.items)>
<raw(Components.DownloadedVideo(item))>
</each>
<footer class="row center-align">
<button hx-get={$"./playlist?id={Net.Http.UrlEncode(id)}&page={page}"} hx-target="body" hx-push-url="true" >Prev</button>{page+1}<button hx-get={$"./playlist?id={Net.Http.UrlEncode(id)}&type=videos&page={page+2}"} hx-target="body" hx-push-url="true" >Next</button>
</footer>
</null>;
title = res.authorName;
}
return Components.Shell($"Playlist {title}",html,1);
}

View File

@@ -0,0 +1,79 @@
func Pages.DownloadPlugins(tytd,ctx)
{
var q = ctx.QueryParams.TryGetFirst("q");
if(TypeOf(q) != "String")
{
q = "";
}
var server = ctx.QueryParams.TryGetFirst("server");
if(TypeOf(server) != "String") server = "https://cpkg.tesseslanguage.com/";
var items=[];
var items2 = [];
each(var item : tytd.PackageManager.Search(q,{server,type="lib",pluginHost="tytd2025"}))
{
items2.Add({
name = item.packageName,
version = item.version,
url = $"{server}/package?name={Net.Http.UrlEncode(item.packageName)}",
thumb = $"{server}/api/v1/package_icon.png?name={Net.Http.UrlEncode(item.packageName)}&version={Net.Http.UrlEncode(item.version)}"
});
}
each(var item : tytd.PackageManager.GetPackageServers())
{
items.Add({
active = items.Count == 0,
url = item
});
}
var html= <null><nav class="tabbed">
<a hx-get="./plugins" hx-target="body" hx-push-url="true">
<i>download_done</i>
<span>Installed</span>
</a>
<a hx-get="./plugins-download" hx-target="body" class="active" hx-push-url="true">
<i>download</i>
<span>Download</span>
</a>
</nav>
<form hx-get="./plugins-download" hx-target="body" hx-push-url="true">
<div class="field suffix border round">
<select name="server">
<each(var item : items)>
<if(item.active)>
<true>
<option value={item.url} selected>{item.url}</option>
</true>
<false>
<option value={item.url}>{item.url}</option>
</false>
</if>
</each>
</select>
<i>arrow_drop_down</i>
</div>
<div class="row no-space">
<div class="field border left-round max">
<input type="text" name="q" value={q}>
</div>
<button type="submit" class="large right-round min">Search</button>
</div>
</form>
<each(var item : items2)>
<raw(Components.PackageItem(tytd,item))>
</each>
</null>;
return Components.Shell("Download plugins",html ,2);
}

View File

@@ -0,0 +1,21 @@
func Pages.Plugins(tytd,ctx)
{
var html= <null><nav class="tabbed">
<a class="active" hx-get="./plugins" hx-target="body" hx-push-url="true">
<i>download_done</i>
<span>Installed</span>
</a>
<a hx-get="./plugins-download" hx-target="body" hx-push-url="true">
<i>download</i>
<span>Download</span>
</a>
</nav>
<each(var item : tytd.Plugins)>
<raw(Components.InstalledPlugin(item))>
</each>
</null>;
return Components.Shell("Installed plugins",html ,2);
}

View File

@@ -0,0 +1,66 @@
func Pages.Settings(tytd,ctx)
{
var totalSecs = tytd.Config.BellTimer ?? 10800;
var enablePlugins = tytd.Config.EnablePlugins ?? true;
var hours = totalSecs / 3600;
totalSecs -= hours * 3600;
var minutes = totalSecs / 60;
var seconds = totalSecs % 60;
var html = <form hx-post="./settings" hx-target="body" >
<div class="field middle-align">
<nav>
<div class="max">
<h6>Enable plugins</h6>
<div>Plugins allow you to extend tytd, but they have full access</div>
</div>
<label class="switch">
<if(enablePlugins)>
<true>
<input name="enablePlugins" type="checkbox" checked>
</true>
<false>
<input name="enablePlugins" type="checkbox">
</false>
</if>
<span></span>
</label>
</nav>
</div>
<div class="field label border">
<input type="text" name="tag" value={tytd.TYTDTag}>
<label>TYTD Tag</label>
</div>
<fieldset>
<legend>Subscriber Poll Rate</legend>
<div class="row">
<div class="max">
<div class="field label border">
<input type="number" name="hours" value={hours}>
<label>Hours</label>
</div>
</div>
<div class="max">
<div class="field label border">
<input type="number" name="minutes" value={minutes}>
<label>Minutes</label>
</div>
</div>
<div class="max">
<div class="field label border">
<input type="number" name="seconds" value={seconds}>
<label>Seconds</label>
</div>
</div>
</div>
</fieldset>
<footer>
<button>Save</button>
</footer>
</form>;
return Components.Shell("Settings",html ,3);
}

View File

@@ -0,0 +1,30 @@
func Pages.VideoInfo(tytd,ctx)
{
var vid = ctx.QueryParams.TryGetFirst("v");
tytd.PutVideoInfoIfNotExists(vid);
var vi = tytd.GetVideo(vid);
var html = <h1>Could not find video</h1>;
if(vi != null)
{
html = <div class="col">
<div class="min">
<img src={$"./api/v1/video-thumbnail?v={Net.Http.UrlEncode(vi.videoId)}"}>
</div>
<div class="min">
<raw(Components.AddVideo($"https://www.youtube.com/watch?v={vi.videoId}"))>
</div>
<div class="min">
<raw(Components.AddToPersonalList(tytd,vi.videoId))>
</div>
<div class="min">
<h4>{vi.title}</h4>
<a href={$"./channel?id={Net.Http.UrlEncode(vi.channelId)}"}>{vi.author}</a>
<p>{vi.shortDescription}</p>
</div>
</div>;
}
return Components.Shell(vi != null ? vi.title : "Could not find video", html, 1);
}