From 42312cf409dd812114175398a1845532160a85b3 Mon Sep 17 00:00:00 2001 From: Flo Date: Sun, 4 Aug 2024 13:18:06 +0000 Subject: [PATCH] updates --- bin/script/exec | 4 +- package-lock.json | 693 ++++++++++++++---- package.json | 1 + src/app/app.component.html | 3 +- src/app/app.module.ts | 26 +- src/app/component/home/home.component.html | 5 + src/app/component/home/home.component.scss | 0 src/app/component/home/home.component.ts | 31 + .../navigation/navigation.component.html | 15 + .../navigation/navigation.component.scss | 0 .../navigation/navigation.component.ts | 28 + src/app/feature/tag/list/list.component.html | 12 +- src/app/feature/tag/list/list.component.ts | 53 +- src/app/feature/tag/tag.component.html | 18 +- src/app/feature/tag/tag.component.ts | 55 +- src/app/feature/tag/tag.module.ts | 21 +- .../tag/video-list/video-list.component.html | 43 -- .../tag/video-list/video-list.component.scss | 65 -- .../tag/video-list/video-list.component.ts | 94 --- .../feature/video/list/list.component.html | 58 +- .../feature/video/list/list.component.scss | 65 -- src/app/feature/video/list/list.component.ts | 83 +-- .../video/upload/upload.component.html | 85 ++- .../feature/video/upload/upload.component.ts | 52 +- src/app/feature/video/video.component.html | 126 +++- src/app/feature/video/video.component.ts | 94 ++- src/app/feature/video/video.module.ts | 21 +- src/app/model/TagListEntry.ts | 8 +- src/app/model/VideoDetails.ts | 3 + src/app/model/VideoListEntry.ts | 11 +- .../components/card/card.component.html | 31 + .../components/card/card.component.scss | 0 .../shared/components/card/card.component.ts | 13 + .../components/form/form.component.html | 21 + .../components/form/form.component.scss | 0 .../shared/components/form/form.component.ts | 12 + .../image-presenter.component.html | 13 + .../image-presenter.component.scss | 0 .../image-presenter.component.ts | 12 + .../paginator/paginator.component.html | 69 ++ .../paginator/paginator.component.scss | 0 .../paginator/paginator.component.ts | 80 ++ .../tab-control/tab-control.component.html | 20 + .../tab-control/tab-control.component.scss | 0 .../tab-control/tab-control.component.ts | 30 + .../components/table/table.component.html | 54 ++ .../components/table/table.component.scss | 0 .../components/table/table.component.ts | 31 + .../tag-selector/tag-selector.component.html | 17 + .../tag-selector/tag-selector.component.scss | 0 .../tag-selector/tag-selector.component.ts | 55 ++ .../video-list/video-list.component.html | 42 ++ .../video-list/video-list.component.scss | 0 .../video-list/video-list.component.ts | 67 ++ .../video-presenter.component.html | 15 + .../video-presenter.component.scss | 0 .../video-presenter.component.ts | 11 + src/app/shared/models/TabItem.ts | 4 + src/app/shared/shared.module.ts | 91 ++- src/index.html | 24 +- src/styles.scss | 68 +- tailwind.config.js | 9 + 62 files changed, 1838 insertions(+), 724 deletions(-) create mode 100644 src/app/component/home/home.component.html create mode 100644 src/app/component/home/home.component.scss create mode 100644 src/app/component/home/home.component.ts create mode 100644 src/app/component/navigation/navigation.component.html create mode 100644 src/app/component/navigation/navigation.component.scss create mode 100644 src/app/component/navigation/navigation.component.ts delete mode 100644 src/app/feature/tag/video-list/video-list.component.html delete mode 100644 src/app/feature/tag/video-list/video-list.component.scss delete mode 100644 src/app/feature/tag/video-list/video-list.component.ts create mode 100644 src/app/shared/components/card/card.component.html create mode 100644 src/app/shared/components/card/card.component.scss create mode 100644 src/app/shared/components/card/card.component.ts create mode 100644 src/app/shared/components/form/form.component.html create mode 100644 src/app/shared/components/form/form.component.scss create mode 100644 src/app/shared/components/form/form.component.ts create mode 100644 src/app/shared/components/image-presenter/image-presenter.component.html create mode 100644 src/app/shared/components/image-presenter/image-presenter.component.scss create mode 100644 src/app/shared/components/image-presenter/image-presenter.component.ts create mode 100644 src/app/shared/components/paginator/paginator.component.html create mode 100644 src/app/shared/components/paginator/paginator.component.scss create mode 100644 src/app/shared/components/paginator/paginator.component.ts create mode 100644 src/app/shared/components/tab-control/tab-control.component.html create mode 100644 src/app/shared/components/tab-control/tab-control.component.scss create mode 100644 src/app/shared/components/tab-control/tab-control.component.ts create mode 100644 src/app/shared/components/table/table.component.html create mode 100644 src/app/shared/components/table/table.component.scss create mode 100644 src/app/shared/components/table/table.component.ts create mode 100644 src/app/shared/components/tag-selector/tag-selector.component.html create mode 100644 src/app/shared/components/tag-selector/tag-selector.component.scss create mode 100644 src/app/shared/components/tag-selector/tag-selector.component.ts create mode 100644 src/app/shared/components/video-list/video-list.component.html create mode 100644 src/app/shared/components/video-list/video-list.component.scss create mode 100644 src/app/shared/components/video-list/video-list.component.ts create mode 100644 src/app/shared/components/video-presenter/video-presenter.component.html create mode 100644 src/app/shared/components/video-presenter/video-presenter.component.scss create mode 100644 src/app/shared/components/video-presenter/video-presenter.component.ts create mode 100644 src/app/shared/models/TabItem.ts create mode 100644 tailwind.config.js diff --git a/bin/script/exec b/bin/script/exec index db7c48a..87c1e00 100755 --- a/bin/script/exec +++ b/bin/script/exec @@ -8,11 +8,11 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) #MAC if [[ "$OSTYPE" == "darwin"* ]]; then - docker-compose -f "${SCRIPT_DIR}/../../docker/docker-compose-mac.yml" $COMMAND + docker compose -f "${SCRIPT_DIR}/../../docker/docker-compose-mac.yml" $COMMAND #LINUX elif [[ "$OSTYPE" == "linux-gnu" ]]; then - docker-compose -f "${SCRIPT_DIR}/../../docker/docker-compose.yml" $COMMAND + docker compose -f "${SCRIPT_DIR}/../../docker/docker-compose.yml" $COMMAND else echo "Dieses Skript wird auf deinem Gerät nicht unterstützt" diff --git a/package-lock.json b/package-lock.json index fa6624b..ebf5c15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,9 +33,22 @@ "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.0.0", + "tailwindcss": "^3.4.3", "typescript": "~4.9.4" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", @@ -50,12 +63,12 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1502.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1502.10.tgz", - "integrity": "sha512-S8lN73WYCfpEpw1Q41ZcUinw7JfDeSM8LyGs797OVshnW75QcOkOecWj/3CKR23G44IgFrHN6sqtzWxKmMxLig==", + "version": "0.1502.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1502.11.tgz", + "integrity": "sha512-+hkG5UjIaKMRdo6SFLNQs+Cv7yAVeN8ijfDwI2z/mp7/otowuSEy+H3Tii195jfJ8TQ+y1B7svnx2D6O7oOYbQ==", "dev": true, "dependencies": { - "@angular-devkit/core": "15.2.10", + "@angular-devkit/core": "15.2.11", "rxjs": "6.6.7" }, "engines": { @@ -83,15 +96,15 @@ "dev": true }, "node_modules/@angular-devkit/build-angular": { - "version": "15.2.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-15.2.10.tgz", - "integrity": "sha512-3pCPVEJilVwHIJC6Su1/PIEqvFfU1Lxew9yItxX4s6dud8HY+fuKrsDnao4NNMFNqCLqL4el5QbSBKnnpWH1sg==", + "version": "15.2.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-15.2.11.tgz", + "integrity": "sha512-MnpVCJdk5jHuK7CH/cTcRT0JQkkKkRTEV3WTyOUhTm0O3PlKwvTM6/Sner+zyuhKyw5VFBBMypHh59aTUDEZ1A==", "dev": true, "dependencies": { "@ampproject/remapping": "2.2.0", - "@angular-devkit/architect": "0.1502.10", - "@angular-devkit/build-webpack": "0.1502.10", - "@angular-devkit/core": "15.2.10", + "@angular-devkit/architect": "0.1502.11", + "@angular-devkit/build-webpack": "0.1502.11", + "@angular-devkit/core": "15.2.11", "@babel/core": "7.20.12", "@babel/generator": "7.20.14", "@babel/helper-annotate-as-pure": "7.18.6", @@ -103,7 +116,7 @@ "@babel/runtime": "7.20.13", "@babel/template": "7.20.7", "@discoveryjs/json-ext": "0.5.7", - "@ngtools/webpack": "15.2.10", + "@ngtools/webpack": "15.2.11", "ansi-colors": "4.1.3", "autoprefixer": "10.4.13", "babel-loader": "9.1.2", @@ -144,7 +157,7 @@ "tree-kill": "1.2.2", "tslib": "2.5.0", "webpack": "5.76.1", - "webpack-dev-middleware": "6.0.1", + "webpack-dev-middleware": "6.1.2", "webpack-dev-server": "4.11.1", "webpack-merge": "5.8.0", "webpack-subresource-integrity": "5.1.0" @@ -217,12 +230,12 @@ "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1502.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1502.10.tgz", - "integrity": "sha512-55b9WZIGU4DNgiIV2lkkN6iQxJrgWY5CDaNu0kJC/qazotJgBbcN/8jgBx2DD8HNE1V3iXxWk66pt1h946Po+Q==", + "version": "0.1502.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1502.11.tgz", + "integrity": "sha512-OTONIRp770Jfems4+cULmtoeSzjnpx5UjV2EazojnhRXXBSJMWRMPvwD2QvQl9UO/6eOV3d2mgmP2xOZgc/D6w==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1502.10", + "@angular-devkit/architect": "0.1502.11", "rxjs": "6.6.7" }, "engines": { @@ -254,9 +267,9 @@ "dev": true }, "node_modules/@angular-devkit/core": { - "version": "15.2.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.2.10.tgz", - "integrity": "sha512-bFPm7wjvfBds9km2rCJxUhzkqe4h3h/199yJtzC1bNvwRr2LMHvtyoQAzftda+gs7Ulqac5wzUEZX/cVV3WrsA==", + "version": "15.2.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-15.2.11.tgz", + "integrity": "sha512-zd6QelJ8pOPvz6TsehR0JqixjDjzgEOkKywBJBuwNXY+Nw3MJGayJeWS0UgC+Gk+LoTkpI21RoyaYELkAmD/tw==", "dev": true, "dependencies": { "ajv": "8.12.0", @@ -298,12 +311,12 @@ "dev": true }, "node_modules/@angular-devkit/schematics": { - "version": "15.2.10", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.2.10.tgz", - "integrity": "sha512-EeoDs4oKFpLZNa21G/8dqBdclEc4U2piI9EeXCVTaN6z5DYXIZ0G1WtCXU8nhD+GckS47rmfZ4/3lMaXAvED+g==", + "version": "15.2.11", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-15.2.11.tgz", + "integrity": "sha512-Wfj0FO8lcGqOkg7GTYOGUsAHqTS9GMfGXTAGsM/8g0SZI4kaPy/luZSPFtevpFE5PSR6dyWSIC0GgzfavjhJMg==", "dev": true, "dependencies": { - "@angular-devkit/core": "15.2.10", + "@angular-devkit/core": "15.2.11", "jsonc-parser": "3.2.0", "magic-string": "0.29.0", "ora": "5.4.1", @@ -364,15 +377,15 @@ } }, "node_modules/@angular/cli": { - "version": "15.2.10", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-15.2.10.tgz", - "integrity": "sha512-/TSnm/ZQML6A4lvunyN2tjTB5utuvk3d1Pnfyehp/FXtV6YfZm6+EZrOpKkKPCxTuAgW6c9KK4yQtt3RuNVpwQ==", + "version": "15.2.11", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-15.2.11.tgz", + "integrity": "sha512-fsIMvUWVCZM3qQSKZXR0yHTXxvoNrbs/PDUsGhRjWZrfUDHBCzMmKral5x8onMA/KPU9O3JiolKjiKVwzkudJA==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1502.10", - "@angular-devkit/core": "15.2.10", - "@angular-devkit/schematics": "15.2.10", - "@schematics/angular": "15.2.10", + "@angular-devkit/architect": "0.1502.11", + "@angular-devkit/core": "15.2.11", + "@angular-devkit/schematics": "15.2.11", + "@schematics/angular": "15.2.11", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.3", "ini": "3.0.1", @@ -3095,9 +3108,9 @@ } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, "engines": { "node": ">=6.0.0" @@ -3134,9 +3147,9 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.23", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.23.tgz", - "integrity": "sha512-9/4foRoUKp8s96tSkh8DlAAc5A0Ty8vLXld+l9gjKKY6ckwI8G15f0hskGmuLZu78ZlGa1vtsfOa+lnB4vG6Jg==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -3144,9 +3157,9 @@ } }, "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz", - "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", "dev": true }, "node_modules/@material/animation": { @@ -3901,9 +3914,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "15.2.10", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-15.2.10.tgz", - "integrity": "sha512-ZExB4rKh/Saad31O/Ofd2XvRuILuCNTYs0+qJL697Be2pzeewvzBhE4Xe1Mm7Jg13aWSPeuIdzSGOqCdwxxxFQ==", + "version": "15.2.11", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-15.2.11.tgz", + "integrity": "sha512-yqp+FziuJ+wIVij4eTqfhuiTPNaG1PU8ukeGOdqkVH4nQMlmzs9UldXy1iYC/6swzn6XO/pkqisU3m/jxemMzA==", "dev": true, "engines": { "node": "^14.20.0 || ^16.13.0 || >=18.10.0", @@ -4126,13 +4139,13 @@ } }, "node_modules/@schematics/angular": { - "version": "15.2.10", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-15.2.10.tgz", - "integrity": "sha512-eLdyP+T1TueNQ8FCP7sP+tt8z+YQ1BINsJsyAyoJT/XZjcCV7LUxgDIU94/kuvIotmJ2xTuFWHFPfAY+CN3duQ==", + "version": "15.2.11", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-15.2.11.tgz", + "integrity": "sha512-z38f9abwfzUGe9TEIggf3igpVf/ylmSlHy1jydAxXbeKv24ejg8m5dYBPH2/MvgUFP6tjHdxjKD56DnOdyKl3g==", "dev": true, "dependencies": { - "@angular-devkit/core": "15.2.10", - "@angular-devkit/schematics": "15.2.10", + "@angular-devkit/core": "15.2.11", + "@angular-devkit/schematics": "15.2.11", "jsonc-parser": "3.2.0" }, "engines": { @@ -4403,9 +4416,9 @@ } }, "node_modules/@types/express-serve-static-core": { - "version": "4.17.43", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz", - "integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==", + "version": "4.19.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", + "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", "dev": true, "dependencies": { "@types/node": "*", @@ -4472,9 +4485,9 @@ "dev": true }, "node_modules/@types/qs": { - "version": "6.9.11", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.11.tgz", - "integrity": "sha512-oGk0gmhnEJK4Yyk+oI7EfXsLayXatCWPHary1MtcmbAifkobT9cM9yutG/hZKIseOU0MqbIwQ/u2nn/Gb+ltuQ==", + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", "dev": true }, "node_modules/@types/range-parser": { @@ -4509,14 +4522,14 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz", - "integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==", + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", "dev": true, "dependencies": { "@types/http-errors": "*", - "@types/mime": "*", - "@types/node": "*" + "@types/node": "*", + "@types/send": "*" } }, "node_modules/@types/sockjs": { @@ -4914,6 +4927,12 @@ "node": ">=4" } }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -4946,6 +4965,12 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -5210,12 +5235,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -5367,6 +5392,15 @@ "node": ">=6" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001589", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001589.tgz", @@ -6167,6 +6201,12 @@ "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", "dev": true }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -6179,6 +6219,12 @@ "node": ">=8" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, "node_modules/dns-packet": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", @@ -6324,9 +6370,9 @@ } }, "node_modules/engine.io": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", - "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", "dev": true, "dependencies": { "@types/cookie": "^0.4.1", @@ -6338,7 +6384,7 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" + "ws": "~8.17.1" }, "engines": { "node": ">=10.2.0" @@ -6644,17 +6690,17 @@ "dev": true }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dev": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -6685,34 +6731,10 @@ "node": ">= 0.10.0" } }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, "node_modules/express/node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "dev": true, "engines": { "node": ">= 0.6" @@ -6751,21 +6773,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/express/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -6860,9 +6867,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -6953,9 +6960,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -7067,9 +7074,9 @@ } }, "node_modules/fs-monkey": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", - "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.6.tgz", + "integrity": "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg==", "dev": true }, "node_modules/fs.realpath": { @@ -7411,9 +7418,9 @@ } }, "node_modules/html-entities": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.4.0.tgz", - "integrity": "sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", "dev": true, "funding": [ { @@ -7847,9 +7854,9 @@ "dev": true }, "node_modules/ipaddr.js": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz", - "integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", "dev": true, "engines": { "node": ">= 10" @@ -8235,6 +8242,15 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8670,6 +8686,15 @@ } } }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -9433,6 +9458,17 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -9886,6 +9922,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -10335,16 +10380,16 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", - "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -10411,6 +10456,15 @@ "node": ">=6" } }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/piscina": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/piscina/-/piscina-3.2.0.tgz", @@ -10465,6 +10519,101 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", + "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-load-config/node_modules/yaml": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz", + "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/postcss-loader": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.0.2.tgz", @@ -10546,6 +10695,25 @@ "postcss": "^8.1.0" } }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, "node_modules/postcss-selector-parser": { "version": "6.0.15", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", @@ -10726,6 +10894,24 @@ "node": ">= 0.8" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/read-package-json": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-6.0.4.tgz", @@ -11690,13 +11876,13 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", - "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", "dev": true, "dependencies": { "debug": "~4.3.4", - "ws": "~8.11.0" + "ws": "~8.17.1" } }, "node_modules/socket.io-parser": { @@ -12005,6 +12191,115 @@ "node": ">=6" } }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/sucrase/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/sucrase/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.1.tgz", + "integrity": "sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/jackspeak": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz", + "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/sucrase/node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sucrase/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -12038,6 +12333,72 @@ "node": ">=0.10" } }, + "node_modules/tailwindcss": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", + "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tailwindcss/node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -12048,9 +12409,9 @@ } }, "node_modules/tar": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", - "integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "dev": true, "dependencies": { "chownr": "^2.0.0", @@ -12296,6 +12657,27 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -12359,6 +12741,12 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -12785,9 +13173,9 @@ } }, "node_modules/webpack-dev-middleware": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.0.1.tgz", - "integrity": "sha512-PZPZ6jFinmqVPJZbisfggDiC+2EeGZ1ZByyMP5sOFJcPPWSexalISz+cvm+j+oYPT7FIJyxT76esjnw9DhE5sw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-6.1.2.tgz", + "integrity": "sha512-Wu+EHmX326YPYUpQLKmKbTyZZJIB8/n6R09pTmB03kJmnMsVPTo9COzHZFr01txwaCAuZvfBJE4ZCHRcKs5JaQ==", "dev": true, "dependencies": { "colorette": "^2.0.10", @@ -12805,6 +13193,11 @@ }, "peerDependencies": { "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } } }, "node_modules/webpack-dev-server": { @@ -12863,9 +13256,9 @@ } }, "node_modules/webpack-dev-server/node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz", - "integrity": "sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA==", + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz", + "integrity": "sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==", "dev": true, "dependencies": { "colorette": "^2.0.10", @@ -13135,16 +13528,16 @@ "dev": true }, "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { diff --git a/package.json b/package.json index 5c8739f..86a0578 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "karma-coverage": "~2.2.0", "karma-jasmine": "~5.1.0", "karma-jasmine-html-reporter": "~2.0.0", + "tailwindcss": "^3.4.3", "typescript": "~4.9.4" } } diff --git a/src/app/app.component.html b/src/app/app.component.html index 90c6b64..04cd27d 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1 +1,2 @@ - \ No newline at end of file + + diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 53724a0..c57823d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -6,18 +6,26 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { AppComponent } from './app.component'; import { RouterModule, Routes } from '@angular/router'; import { SharedModule } from './shared/shared.module'; - +import { NavigationComponent } from './component/navigation/navigation.component'; +import { HomeComponent } from './component/home/home.component'; const routes: Routes = [ - { path: 'video', loadChildren: () => import('./feature/video/video.module').then( m => m.VideoModule ) }, - { path: 'tag', loadChildren: () => import('./feature/tag/tag.module').then( m => m.TagModule ) }, - { path: '', redirectTo: 'video/list', pathMatch: 'full' } + { path: 'home', component: HomeComponent }, + { + path: 'video', + loadChildren: () => + import('./feature/video/video.module').then((m) => m.VideoModule), + }, + { + path: 'tag', + loadChildren: () => + import('./feature/tag/tag.module').then((m) => m.TagModule), + }, + { path: '', redirectTo: 'home', pathMatch: 'full' }, ]; @NgModule({ - declarations: [ - AppComponent, - ], + declarations: [AppComponent, NavigationComponent, HomeComponent], imports: [ BrowserModule, BrowserAnimationsModule, @@ -26,6 +34,6 @@ const routes: Routes = [ SharedModule, ], providers: [], - bootstrap: [AppComponent] + bootstrap: [AppComponent], }) -export class AppModule { } +export class AppModule {} diff --git a/src/app/component/home/home.component.html b/src/app/component/home/home.component.html new file mode 100644 index 0000000..68e4222 --- /dev/null +++ b/src/app/component/home/home.component.html @@ -0,0 +1,5 @@ + diff --git a/src/app/component/home/home.component.scss b/src/app/component/home/home.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/component/home/home.component.ts b/src/app/component/home/home.component.ts new file mode 100644 index 0000000..42a197f --- /dev/null +++ b/src/app/component/home/home.component.ts @@ -0,0 +1,31 @@ +import { Component } from '@angular/core'; +import { VideoListEntry } from 'src/app/model/VideoListEntry'; +import { RequestService } from 'src/app/request.service'; +import { OnReadListModel } from 'src/app/shared/components/video-list/video-list.component'; + +@Component({ + selector: 'app-home', + templateUrl: './home.component.html', + styleUrls: ['./home.component.scss'], +}) +export class HomeComponent { + total: number = 0; + videos: VideoListEntry[] = []; + + constructor(public requestService: RequestService) {} + + readList(model: OnReadListModel): void { + this.requestService.post( + 'video-list/read-list', + { + query: model.query, + page: model.page, + perPage: model.perPage, + }, + (response: any) => { + this.videos = response.items; + this.total = response.total; + } + ); + } +} diff --git a/src/app/component/navigation/navigation.component.html b/src/app/component/navigation/navigation.component.html new file mode 100644 index 0000000..06fcf74 --- /dev/null +++ b/src/app/component/navigation/navigation.component.html @@ -0,0 +1,15 @@ + +

+ MyTube +

+
+ + diff --git a/src/app/component/navigation/navigation.component.scss b/src/app/component/navigation/navigation.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/component/navigation/navigation.component.ts b/src/app/component/navigation/navigation.component.ts new file mode 100644 index 0000000..38b7793 --- /dev/null +++ b/src/app/component/navigation/navigation.component.ts @@ -0,0 +1,28 @@ +import { Component } from '@angular/core'; + +interface Link { + routerLink: string; + caption: string; +} + +@Component({ + selector: 'app-navigation', + templateUrl: './navigation.component.html', + styleUrls: ['./navigation.component.scss'], +}) +export class NavigationComponent { + links: Link[] = [ + { + routerLink: '/video/upload', + caption: 'Upload', + }, + { + routerLink: '/video/list', + caption: 'Videos', + }, + { + routerLink: '/tag/list', + caption: 'Tags', + }, + ]; +} diff --git a/src/app/feature/tag/list/list.component.html b/src/app/feature/tag/list/list.component.html index 7c1fe15..12449ad 100644 --- a/src/app/feature/tag/list/list.component.html +++ b/src/app/feature/tag/list/list.component.html @@ -1 +1,11 @@ -

list works!

+
+
+ +
+ + +
diff --git a/src/app/feature/tag/list/list.component.ts b/src/app/feature/tag/list/list.component.ts index 0ede71c..741572e 100644 --- a/src/app/feature/tag/list/list.component.ts +++ b/src/app/feature/tag/list/list.component.ts @@ -1,10 +1,61 @@ import { Component } from '@angular/core'; +import { Subject, debounceTime } from 'rxjs'; +import { TagListEntry } from 'src/app/model/TagListEntry'; +import { RequestService } from 'src/app/request.service'; +import { ColumnDefinition } from 'src/app/shared/components/table/table.component'; @Component({ selector: 'app-list', templateUrl: './list.component.html', - styleUrls: ['./list.component.scss'] + styleUrls: ['./list.component.scss'], }) export class ListComponent { + query$ = new Subject(); + query: string = ''; + tags: TagListEntry[] = []; + tagListColumns: ColumnDefinition[] = [ + { + header: 'Tag', + columnFunction: (item: TagListEntry) => { + return item.description; + }, + routerLink: (item: TagListEntry) => { + return ['/tag', item.id]; + }, + }, + { + header: 'Anzahl Videos', + columnFunction: (item: TagListEntry) => { + return item.videoCount; + }, + columnCss: 'text-center', + }, + ]; + constructor(public requestService: RequestService) { + this.query$.pipe(debounceTime(300)).subscribe((query) => { + this.query = query; + this.readList(); + }); + } + + ngOnInit(): void { + this.readList(); + } + + onInputChanged(event: Event): void { + this.query$.next((event.target as HTMLTextAreaElement).value); + } + + readList(): void { + this.requestService.post( + 'tag-list/read-list', + { + query: this.query, + }, + (response: any) => { + this.tags = response.items; + } + ); + } } diff --git a/src/app/feature/tag/tag.component.html b/src/app/feature/tag/tag.component.html index 4165ea9..3a55343 100644 --- a/src/app/feature/tag/tag.component.html +++ b/src/app/feature/tag/tag.component.html @@ -1 +1,17 @@ -

tag works!

+
+

+ {{ tagDetails.description }} +

+ +
    +
  • + {{ alias.description }} +
  • +
+
+ + diff --git a/src/app/feature/tag/tag.component.ts b/src/app/feature/tag/tag.component.ts index 1324f94..d684199 100644 --- a/src/app/feature/tag/tag.component.ts +++ b/src/app/feature/tag/tag.component.ts @@ -1,10 +1,63 @@ import { Component } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { TagDetails } from 'src/app/model/TagDetails'; +import { VideoListEntry } from 'src/app/model/VideoListEntry'; +import { RequestService } from 'src/app/request.service'; +import { OnReadListModel } from 'src/app/shared/components/video-list/video-list.component'; @Component({ selector: 'app-tag', templateUrl: './tag.component.html', - styleUrls: ['./tag.component.scss'] + styleUrls: ['./tag.component.scss'], }) export class TagComponent { + tagId: string | undefined; + tagDetails: TagDetails | null = null; + total: number = 0; + videos: VideoListEntry[] = []; + constructor( + private route: ActivatedRoute, + public requestService: RequestService + ) { + this.route.params.subscribe((params) => this.updateTagId(params['id'])); + } + + updateTagId(tagId: string) { + this.tagId = tagId; + this.readDetails(); + this.readList({ page: 1, query: '', perPage: 36 }); + } + + readDetails(): void { + if (this.tagId === undefined) return; + + this.requestService.post( + 'tag/read-details', + { + tagId: this.tagId, + }, + (response: any) => { + this.tagDetails = response; + } + ); + } + + readList(model: OnReadListModel): void { + if (this.tagId === undefined) return; + + this.requestService.post( + 'tag/read-video-list', + { + tagId: this.tagId, + query: model.query, + page: model.page, + perPage: model.perPage, + }, + (response: any) => { + this.videos = response.items; + this.total = response.total; + } + ); + } } diff --git a/src/app/feature/tag/tag.module.ts b/src/app/feature/tag/tag.module.ts index 7b92a03..62f29d7 100644 --- a/src/app/feature/tag/tag.module.ts +++ b/src/app/feature/tag/tag.module.ts @@ -2,27 +2,16 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { TagComponent } from './tag.component'; import { ListComponent } from './list/list.component'; -import { VideoListComponent } from './video-list/video-list.component'; import { SharedModule } from 'src/app/shared/shared.module'; import { RouterModule, Routes } from '@angular/router'; const routes: Routes = [ - { path: ':id/list', component: ListComponent }, - { path: ':id/video-list', component: VideoListComponent }, + { path: 'list', component: ListComponent }, { path: ':id', component: TagComponent }, - { path: '', redirectTo: 'tag/list', pathMatch: 'full'}, -] +]; @NgModule({ - declarations: [ - TagComponent, - ListComponent, - VideoListComponent - ], - imports: [ - CommonModule, - SharedModule, - RouterModule.forChild(routes) - ] + declarations: [TagComponent, ListComponent], + imports: [CommonModule, SharedModule, RouterModule.forChild(routes)], }) -export class TagModule { } +export class TagModule {} diff --git a/src/app/feature/tag/video-list/video-list.component.html b/src/app/feature/tag/video-list/video-list.component.html deleted file mode 100644 index 9c0ee23..0000000 --- a/src/app/feature/tag/video-list/video-list.component.html +++ /dev/null @@ -1,43 +0,0 @@ -
-
- -
- -
- - - -
-
- -
-

{{tagDetails.description}}

-
- -
-
- - - {{video.title}} - - - {{tag.description}} - - - - - Thumbnail - - -
-
- - \ No newline at end of file diff --git a/src/app/feature/tag/video-list/video-list.component.scss b/src/app/feature/tag/video-list/video-list.component.scss deleted file mode 100644 index 6698372..0000000 --- a/src/app/feature/tag/video-list/video-list.component.scss +++ /dev/null @@ -1,65 +0,0 @@ -button { - margin: 1em; - width: 100%; -} - -mat-form-field { - margin: 1em; - width: 100%; -} - -mat-card { - margin: 1em; -} - -mat-card:hover { - background-color: lightgray; -} - -.mat-card-title { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.cardContainer { - display: flex; - flex-wrap: wrap; - justify-content: space-between; -} - -.mat-card { - flex-basis: calc(100% - 20px); - max-width: calc(100% - 20px); - flex: 0 0 calc(100% - 20px); - margin: 10px; - box-sizing: border-box; -} - -/* For tablet: */ -@media only screen and (min-width: 700px) { - .mat-card { - flex-basis: calc(50% - 20px); - max-width: calc(50% - 20px); - } -} - -/* For desktop: */ -@media only screen and (min-width: 1200px) { - .mat-card { - max-width: calc(33.33% - 20px); - flex-basis: calc(33.33% - 20px); - } -} - - - -.mat-card .mat-card-content { - height: 100%; -} - -.mat-card .mat-card-content img { - width: 100%; - height: 100%; - object-fit: cover; -} \ No newline at end of file diff --git a/src/app/feature/tag/video-list/video-list.component.ts b/src/app/feature/tag/video-list/video-list.component.ts deleted file mode 100644 index 5f86579..0000000 --- a/src/app/feature/tag/video-list/video-list.component.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { Component } from '@angular/core'; -import { PageEvent } from '@angular/material/paginator'; -import { ActivatedRoute, Router } from '@angular/router'; -import { Subject, debounceTime } from 'rxjs'; -import { TagDetails } from 'src/app/model/TagDetails'; -import { VideoListEntry } from 'src/app/model/VideoListEntry'; -import { RequestService } from 'src/app/request.service'; - -@Component({ - selector: 'app-video-list', - templateUrl: './video-list.component.html', - styleUrls: ['./video-list.component.scss'] -}) -export class VideoListComponent { - tagId: string; - tagDetails: TagDetails|null = null; - - query$ = new Subject(); - query: string = ""; - total: number = 0; - page: number = 1; - perPage: number = 25; - - videos: VideoListEntry[] = []; - selectedVideoUrl: string|null = null; - - constructor( - private route: ActivatedRoute, - private router: Router, - public requestService : RequestService, - ) { - this.tagId = this.route.snapshot.paramMap.get('id') ?? ''; - - this.query$.pipe( - debounceTime(300) - ).subscribe( query => { - this.query = query; - this.page = 1; - this.readList(); - } ) - } - - ngOnInit(): void { - this.readDetails(); - this.readList(); - } - - onPageEvent(event: PageEvent) { - this.page = event.pageIndex + 1; - this.perPage = event.pageSize; - - this.readList(); - } - - onInputChanged(event: Event): void { - this.query$.next((event.target as HTMLTextAreaElement).value); - } - - selectVideo(entry: VideoListEntry) { - this.router.navigate(['/video', entry.id]); - } - - readDetails(): void { - this.requestService.post( - 'tag/read-details', - { - tagId: this.tagId - }, - (response:any) => { - this.tagDetails = response; - } - ); - } - - readList(): void { - this.requestService.post( - 'tag/read-video-list', - { - tagId: this.tagId, - query: this.query, - page: this.page, - perPage: this.perPage, - }, - (response:any) => { - this.videos = response.items; - this.total = response.total; - } - ); - } - - getThumbnailUrl(entry: VideoListEntry): string { - return 'http://wsl-flo/api/video/thumbnail/' + entry.id; - } -} diff --git a/src/app/feature/video/list/list.component.html b/src/app/feature/video/list/list.component.html index 5bce00e..c4d603a 100644 --- a/src/app/feature/video/list/list.component.html +++ b/src/app/feature/video/list/list.component.html @@ -1,38 +1,26 @@ -
+
+
+ + +
-
- - - -
-
- -
-
+ -
-
- - - {{video.title}} - - - {{tag.description}} - - - - - Thumbnail - - -
+
+ +
- - \ No newline at end of file diff --git a/src/app/feature/video/list/list.component.scss b/src/app/feature/video/list/list.component.scss index 6698372..e69de29 100644 --- a/src/app/feature/video/list/list.component.scss +++ b/src/app/feature/video/list/list.component.scss @@ -1,65 +0,0 @@ -button { - margin: 1em; - width: 100%; -} - -mat-form-field { - margin: 1em; - width: 100%; -} - -mat-card { - margin: 1em; -} - -mat-card:hover { - background-color: lightgray; -} - -.mat-card-title { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.cardContainer { - display: flex; - flex-wrap: wrap; - justify-content: space-between; -} - -.mat-card { - flex-basis: calc(100% - 20px); - max-width: calc(100% - 20px); - flex: 0 0 calc(100% - 20px); - margin: 10px; - box-sizing: border-box; -} - -/* For tablet: */ -@media only screen and (min-width: 700px) { - .mat-card { - flex-basis: calc(50% - 20px); - max-width: calc(50% - 20px); - } -} - -/* For desktop: */ -@media only screen and (min-width: 1200px) { - .mat-card { - max-width: calc(33.33% - 20px); - flex-basis: calc(33.33% - 20px); - } -} - - - -.mat-card .mat-card-content { - height: 100%; -} - -.mat-card .mat-card-content img { - width: 100%; - height: 100%; - object-fit: cover; -} \ No newline at end of file diff --git a/src/app/feature/video/list/list.component.ts b/src/app/feature/video/list/list.component.ts index 34d8d02..8041a12 100644 --- a/src/app/feature/video/list/list.component.ts +++ b/src/app/feature/video/list/list.component.ts @@ -1,56 +1,60 @@ -import { Component, Input } from '@angular/core'; -import { Router } from '@angular/router'; +import { Component } from '@angular/core'; +import { Subject, debounceTime } from 'rxjs'; import { VideoListEntry } from 'src/app/model/VideoListEntry'; import { RequestService } from 'src/app/request.service'; -import { Subject } from 'rxjs'; -import { debounceTime } from 'rxjs/operators'; -import { PageEvent } from '@angular/material/paginator'; +import { ColumnDefinition } from 'src/app/shared/components/table/table.component'; @Component({ selector: 'app-list', templateUrl: './list.component.html', - styleUrls: ['./list.component.scss'] + styleUrls: ['./list.component.scss'], }) export class ListComponent { query$ = new Subject(); - query: string = ""; + query: string = ''; + onlyTagless: boolean = false; + videos: VideoListEntry[] = []; total: number = 0; page: number = 1; - perPage: number = 25; + perPage: number = 180; + videoListColumns: ColumnDefinition[] = [ + { + header: 'Titel', + columnFunction: (item: VideoListEntry) => { + return item.title; + }, + routerLink: (item: VideoListEntry) => { + return ['/video', item.id]; + }, + }, + { + header: 'Tags', + columnFunction: (item: VideoListEntry) => { + return item.tags.map((tag) => tag.description); + }, + columnCss: 'text-center', + }, + ]; - videos: VideoListEntry[] = []; - selectedVideoUrl: string|null = null; - - constructor( - private router: Router, - public requestService : RequestService, - ) { - this.query$.pipe( - debounceTime(300) - ).subscribe( query => { - this.query = query; - this.page = 1; - this.readList(); - }) - } - - ngOnInit(): void { - this.readList(); - } - - onPageEvent(event: PageEvent) { - this.page = event.pageIndex + 1; - this.perPage = event.pageSize; - - this.readList(); + constructor(public requestService: RequestService) { + this.query$.pipe(debounceTime(300)).subscribe((query) => { + this.query = query; + this.page = 1; + this.readList(); + }); } onInputChanged(event: Event): void { this.query$.next((event.target as HTMLTextAreaElement).value); } - selectVideo(entry: VideoListEntry) { - this.router.navigate(['/video', entry.id]); + onPageChanged(newPage: number) { + this.page = newPage; + this.readList(); + } + + ngOnInit(): void { + this.readList(); } readList(): void { @@ -60,15 +64,12 @@ export class ListComponent { query: this.query, page: this.page, perPage: this.perPage, + onlyTagless: this.onlyTagless, }, - (response:any) => { - this.videos = response.items; + (response: any) => { this.total = response.total; + this.videos = response.items; } ); } - - getThumbnailUrl(entry: VideoListEntry): string { - return 'http://wsl-flo/api/video/thumbnail/' + entry.id; - } } diff --git a/src/app/feature/video/upload/upload.component.html b/src/app/feature/video/upload/upload.component.html index 805e035..25f314f 100644 --- a/src/app/feature/video/upload/upload.component.html +++ b/src/app/feature/video/upload/upload.component.html @@ -1,29 +1,62 @@ -
- - +
+
-
-

{{result.total}} Dateien uploaded

-

{{result.succeeded}} erfolgreich

-

{{result.failed}} fehlgeschlagen

-

Details:

- - - - - - +
+
DateiErfolgFehler
+ + + + + - - - - - - - - -
DateiGrößeStatus
{{details.file}}ErfolgFehlschlag -
{{details.error}}
-
-
\ No newline at end of file + + {{ result.file.name }} + +
+ {{ result.file.size | number : "1.0-0" }} Bytes +
+
+ {{ result.file.size / 1024 | number : "1.0-0" }} KB +
+
+ {{ result.file.size / (1024 * 1024) | number : "1.0-0" }} MB +
+
+ {{ result.file.size / (1024 * 1024 * 1024) | number : "1.0-0" }} GB +
+ + +
+ Video +
success without id
+
+
+
+ {{ result.result.details[0].error }} +
+
+ {{ result.status }} +
+
+
+ {{ result.status }} +
+ + + +
diff --git a/src/app/feature/video/upload/upload.component.ts b/src/app/feature/video/upload/upload.component.ts index 2453813..7faf2e9 100644 --- a/src/app/feature/video/upload/upload.component.ts +++ b/src/app/feature/video/upload/upload.component.ts @@ -2,29 +2,51 @@ import { Component } from '@angular/core'; import { UploadResult } from 'src/app/model/UploadResult'; import { RequestService } from 'src/app/request.service'; +interface UploadFileModel { + file: File; + result: UploadResult | undefined; + status: string; +} + @Component({ selector: 'app-upload', templateUrl: './upload.component.html', - styleUrls: ['./upload.component.scss'] + styleUrls: ['./upload.component.scss'], }) export class UploadComponent { + uploadResults: UploadFileModel[] = []; - result: UploadResult|null = null; - - constructor( - private requestService: RequestService, - ) { - } + constructor(private requestService: RequestService) {} onFileSelected(event: any) { - var files: File[] = event.target.files; - - this.requestService.postFiles( - 'video-list/upload', - files, - (response:any) => { - this.result = response; + var files: File[] = Array.from(event.target.files); + + files.forEach((file) => { + var existingItem = this.uploadResults.find( + (item) => item.file.name === file.name && item.file.size === file.size + ); + if (existingItem === undefined) + this.uploadResults.push({ + file, + result: undefined, + status: 'initialized', + }); + else if (existingItem.status === 'failed') + existingItem.status = 'initialized'; + }); + + this.uploadResults.forEach(async (item) => { + if (item.status === 'initialized') { + item.status = 'processing'; + this.requestService.postFiles( + 'video-list/upload', + [item.file], + (response: UploadResult) => { + item.result = response; + item.status = response.succeeded > 0 ? 'success' : 'failed'; + } + ); } - ); + }); } } diff --git a/src/app/feature/video/video.component.html b/src/app/feature/video/video.component.html index 159c2c8..806d4bb 100644 --- a/src/app/feature/video/video.component.html +++ b/src/app/feature/video/video.component.html @@ -1,15 +1,113 @@ -
- - - -

{{videoDetails.title}}

- - - +
+
{{ videoDetails.title }}
+
+ +
+
+ +
+ +
+ +
+
+
Titel:
+
+ +
+
+
+
Länge:
+
+ +
+
+
+
Tags:
+
+
+ +
+ +
+
+
+
+
+ Thumbnail +
+
+
+
+ + + +
+
+
+
+
diff --git a/src/app/feature/video/video.component.ts b/src/app/feature/video/video.component.ts index 4182769..bf8010e 100644 --- a/src/app/feature/video/video.component.ts +++ b/src/app/feature/video/video.component.ts @@ -1,39 +1,113 @@ -import { Component } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; +import { Component, ElementRef, ViewChild } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { ActivatedRoute, Router } from '@angular/router'; +import { TagListEntry } from 'src/app/model/TagListEntry'; import { VideoDetails } from 'src/app/model/VideoDetails'; import { RequestService } from 'src/app/request.service'; +import { TagSelectorComponent } from 'src/app/shared/components/tag-selector/tag-selector.component'; @Component({ selector: 'app-video', templateUrl: './video.component.html', - styleUrls: ['./video.component.scss'] + styleUrls: ['./video.component.scss'], }) export class VideoComponent { + @ViewChild('myVideo') videoElement: ElementRef | undefined; + + host: string; videoId: string; - videoUrl: string|null = null; - videoDetails: VideoDetails|null = null; + thumbnailVersion: number; + videoDetails: VideoDetails | undefined; + currentTimestamp: number | undefined; constructor( - private route: ActivatedRoute, - private requestService : RequestService, + public router: Router, + public route: ActivatedRoute, + public requestService: RequestService, + public dialog: MatDialog ) { this.videoId = this.route.snapshot.paramMap.get('id') ?? ''; - this.videoUrl = "http://wsl-flo/api/video/stream/" + this.videoId; + let hostString = window.location.host; + let protocol = window.location.protocol; + this.host = protocol + '//' + hostString + '/api/'; + this.thumbnailVersion = 0; } ngOnInit(): void { this.readDetails(); } + updateTimestamp(): void { + if (this.videoElement === undefined) { + return; + } + const video: HTMLVideoElement = this.videoElement.nativeElement; + this.currentTimestamp = video.currentTime; + } + readDetails(): void { this.requestService.post( 'video/read-details', { - videoId: this.videoId + videoId: this.videoId, }, - (response:any) => { + (response: any) => { this.videoDetails = response; } ); } + + setThumbnail(): void { + this.requestService.post( + 'video/set-thumbnail', + { + videoId: this.videoId, + timestamp: this.currentTimestamp, + }, + (response: any) => { + this.thumbnailVersion++; + } + ); + } + + addTag(): void { + let dialogRef = this.dialog.open(TagSelectorComponent, { + panelClass: 'bg-zinc-900', + maxHeight: '400px', + width: '600px', + }); + + dialogRef.afterClosed().subscribe((result) => { + console.log(result); + + if (result === undefined) return; + this.videoDetails?.tags.push(result); + }); + } + + update(): void { + this.requestService.post( + 'video/update-details', + { + videoId: this.videoId, + title: this.videoDetails?.title, + tagIds: this.videoDetails?.tags.map((item) => item.id), + }, + (response: any) => { + this.readDetails(); + } + ); + } + + delete(): void { + this.requestService.post( + 'video/delete', + { + videoId: this.videoId, + }, + (response: any) => { + this.router.navigate(['/video/list']); + } + ); + } } diff --git a/src/app/feature/video/video.module.ts b/src/app/feature/video/video.module.ts index 8b5647a..22323b9 100644 --- a/src/app/feature/video/video.module.ts +++ b/src/app/feature/video/video.module.ts @@ -1,28 +1,19 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { ListComponent } from './list/list.component'; import { VideoComponent } from './video.component'; import { SharedModule } from 'src/app/shared/shared.module'; import { RouterModule, Routes } from '@angular/router'; import { UploadComponent } from './upload/upload.component'; +import { ListComponent } from './list/list.component'; const routes: Routes = [ - { path: 'list', component: ListComponent }, { path: 'upload', component: UploadComponent }, + { path: 'list', component: ListComponent }, { path: ':id', component: VideoComponent }, - { path: '', redirectTo: 'video/list', pathMatch: 'full'}, -] +]; @NgModule({ - declarations: [ - ListComponent, - VideoComponent, - UploadComponent - ], - imports: [ - CommonModule, - SharedModule, - RouterModule.forChild(routes) - ] + declarations: [VideoComponent, UploadComponent, ListComponent], + imports: [CommonModule, SharedModule, RouterModule.forChild(routes)], }) -export class VideoModule { } +export class VideoModule {} diff --git a/src/app/model/TagListEntry.ts b/src/app/model/TagListEntry.ts index 9e640fc..580a4ac 100644 --- a/src/app/model/TagListEntry.ts +++ b/src/app/model/TagListEntry.ts @@ -1,5 +1,5 @@ - export interface TagListEntry { - id: string; - description: string; -} \ No newline at end of file + id: string; + description: string; + videoCount?: number; +} diff --git a/src/app/model/VideoDetails.ts b/src/app/model/VideoDetails.ts index c0569ef..f8941a7 100644 --- a/src/app/model/VideoDetails.ts +++ b/src/app/model/VideoDetails.ts @@ -1,5 +1,8 @@ +import { TagListEntry } from "./TagListEntry"; export interface VideoDetails { title: string; id: string; + duration: string; + tags: TagListEntry[]; } \ No newline at end of file diff --git a/src/app/model/VideoListEntry.ts b/src/app/model/VideoListEntry.ts index 8dcc34d..e0667dc 100644 --- a/src/app/model/VideoListEntry.ts +++ b/src/app/model/VideoListEntry.ts @@ -1,7 +1,8 @@ -import { TagListEntry } from "./TagListEntry"; +import { TagListEntry } from './TagListEntry'; export interface VideoListEntry { - title: string; - id: string; - tags: TagListEntry[]; -} \ No newline at end of file + title: string; + id: string; + duration: string; + tags: TagListEntry[]; +} diff --git a/src/app/shared/components/card/card.component.html b/src/app/shared/components/card/card.component.html new file mode 100644 index 0000000..02ae685 --- /dev/null +++ b/src/app/shared/components/card/card.component.html @@ -0,0 +1,31 @@ +
+ + +
+
"{{ content }}"
+
+ +
+
+
diff --git a/src/app/shared/components/card/card.component.scss b/src/app/shared/components/card/card.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/shared/components/card/card.component.ts b/src/app/shared/components/card/card.component.ts new file mode 100644 index 0000000..cde549d --- /dev/null +++ b/src/app/shared/components/card/card.component.ts @@ -0,0 +1,13 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'shared-card', + templateUrl: './card.component.html', + styleUrls: ['./card.component.scss'], +}) +export class CardComponent { + @Input() header: string | null = null; + @Input() icon: string | null = null; + @Input() subHeader: string | null = null; + @Input() content: string | null = null; +} diff --git a/src/app/shared/components/form/form.component.html b/src/app/shared/components/form/form.component.html new file mode 100644 index 0000000..f500cd5 --- /dev/null +++ b/src/app/shared/components/form/form.component.html @@ -0,0 +1,21 @@ +
+ {{ caption }} +
+ +
+
+ +
+
+
+ +
+
diff --git a/src/app/shared/components/form/form.component.scss b/src/app/shared/components/form/form.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/shared/components/form/form.component.ts b/src/app/shared/components/form/form.component.ts new file mode 100644 index 0000000..7a0e704 --- /dev/null +++ b/src/app/shared/components/form/form.component.ts @@ -0,0 +1,12 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; + +@Component({ + selector: 'shared-form', + templateUrl: './form.component.html', + styleUrls: ['./form.component.scss'] +}) +export class FormComponent { + @Output() submit = new EventEmitter(); + @Input() caption: string|null = null; + +} diff --git a/src/app/shared/components/image-presenter/image-presenter.component.html b/src/app/shared/components/image-presenter/image-presenter.component.html new file mode 100644 index 0000000..7ed5002 --- /dev/null +++ b/src/app/shared/components/image-presenter/image-presenter.component.html @@ -0,0 +1,13 @@ +
+
+ {{header}} +
+ + + +
\ No newline at end of file diff --git a/src/app/shared/components/image-presenter/image-presenter.component.scss b/src/app/shared/components/image-presenter/image-presenter.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/shared/components/image-presenter/image-presenter.component.ts b/src/app/shared/components/image-presenter/image-presenter.component.ts new file mode 100644 index 0000000..6ce8ea5 --- /dev/null +++ b/src/app/shared/components/image-presenter/image-presenter.component.ts @@ -0,0 +1,12 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'shared-image-presenter', + templateUrl: './image-presenter.component.html', + styleUrls: ['./image-presenter.component.scss'] +}) +export class ImagePresenterComponent { + @Input() header: string|null = null; + @Input() alt: string|null = null; + @Input() source = ''; +} diff --git a/src/app/shared/components/paginator/paginator.component.html b/src/app/shared/components/paginator/paginator.component.html new file mode 100644 index 0000000..8961ffe --- /dev/null +++ b/src/app/shared/components/paginator/paginator.component.html @@ -0,0 +1,69 @@ + diff --git a/src/app/shared/components/paginator/paginator.component.scss b/src/app/shared/components/paginator/paginator.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/shared/components/paginator/paginator.component.ts b/src/app/shared/components/paginator/paginator.component.ts new file mode 100644 index 0000000..93b4529 --- /dev/null +++ b/src/app/shared/components/paginator/paginator.component.ts @@ -0,0 +1,80 @@ +import { + Component, + EventEmitter, + Input, + OnChanges, + Output, + SimpleChanges, +} from '@angular/core'; + +export interface PaginatorItem { + page: number | null; +} + +@Component({ + selector: 'shared-paginator', + templateUrl: './paginator.component.html', + styleUrls: ['./paginator.component.scss'], +}) +export class PaginatorComponent implements OnChanges { + @Output() pageChange = new EventEmitter(); + @Input() page: number = 1; + @Input() perPage: number = 10; + @Input() total: number | null = null; + items: PaginatorItem[] = []; + + ngOnChanges(changes: SimpleChanges): void { + this.calculateItems(); + } + + protected setActivePage(newPage: number | null) { + if (newPage === null) { + return; + } + if (newPage === -1) { + newPage = this.page - 1; + } + if (newPage === -2) { + newPage = this.page + 1; + } + + if (newPage <= 0 || newPage > this.getMaxPages()) return; + + this.page = newPage; + this.pageChange.emit(newPage); + this.calculateItems(); + } + + private calculateItems(): void { + this.items = []; + + let first = 1; + let last = this.getMaxPages(); + let rangeFirst = this.page - 1; + let rangeLast = this.page + 1; + + if (this.page === 1) { + rangeFirst = 1; + rangeLast = last < 3 ? last : 3; + } + + if (this.page === last) { + rangeFirst = Math.max(1, last - 2); + rangeLast = last; + } + + if (rangeFirst > 1) this.items.push({ page: first }); + if (rangeFirst > 2) this.items.push({ page: null }); + + for (let index = rangeFirst; index <= rangeLast; index++) { + this.items.push({ page: index }); + } + + if (rangeLast < last - 1) this.items.push({ page: null }); + if (rangeLast < last) this.items.push({ page: last }); + } + + private getMaxPages(): number { + return Math.ceil((this.total ?? 0) / this.perPage); + } +} diff --git a/src/app/shared/components/tab-control/tab-control.component.html b/src/app/shared/components/tab-control/tab-control.component.html new file mode 100644 index 0000000..f6b074f --- /dev/null +++ b/src/app/shared/components/tab-control/tab-control.component.html @@ -0,0 +1,20 @@ +
+
    +
  • + +
  • +
+
+ +
+ +
diff --git a/src/app/shared/components/tab-control/tab-control.component.scss b/src/app/shared/components/tab-control/tab-control.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/shared/components/tab-control/tab-control.component.ts b/src/app/shared/components/tab-control/tab-control.component.ts new file mode 100644 index 0000000..a4a0552 --- /dev/null +++ b/src/app/shared/components/tab-control/tab-control.component.ts @@ -0,0 +1,30 @@ +import { Component, ContentChildren, EventEmitter, Input, Output, QueryList } from '@angular/core'; +import { TabItem } from '../../models/TabItem'; + +@Component({ + selector: 'shared-tab-control', + templateUrl: './tab-control.component.html', + styleUrls: ['./tab-control.component.scss'] +}) +export class TabControlComponent { + @Input() public tabs: TabItem[] = []; + @Input() public activeTabId: string = ''; + @Output() public activeTabIdChange = new EventEmitter(); + + @ContentChildren('tabContent') tabContents!: QueryList; + + public activeIndex = 0; + + public ngOnChanges() { + this.setActiveTab(null); + } + + public setActiveTab(tab: TabItem|null) { + if (tab !== null) { + this.activeTabId = tab.id; + this.activeTabIdChange.emit(this.activeTabId); + } + + this.activeIndex = this.tabs.findIndex((i) => i.id === this.activeTabId); + } +} diff --git a/src/app/shared/components/table/table.component.html b/src/app/shared/components/table/table.component.html new file mode 100644 index 0000000..f94ab58 --- /dev/null +++ b/src/app/shared/components/table/table.component.html @@ -0,0 +1,54 @@ + + + + + + + + + + + +
+
+
+ +
+
+
+ +
+
+ {{ column.columnContent }} +
+
+
+
+
diff --git a/src/app/shared/components/table/table.component.scss b/src/app/shared/components/table/table.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/shared/components/table/table.component.ts b/src/app/shared/components/table/table.component.ts new file mode 100644 index 0000000..9e426a2 --- /dev/null +++ b/src/app/shared/components/table/table.component.ts @@ -0,0 +1,31 @@ +import { Component, Input } from '@angular/core'; + +export interface ColumnDefinition { + header: string; + columnContent?: string | undefined; + columnFunction?: Function | undefined; + routerLink?: Function | undefined; + headerCss?: string | undefined; + columnCss?: string | undefined; +} + +@Component({ + selector: 'shared-table', + templateUrl: './table.component.html', + styleUrls: ['./table.component.scss'], +}) +export class TableComponent { + @Input() foreground: string = 'text-white'; + @Input() background: string = 'bg-zinc-800'; + @Input() backgroundColumn: string = 'bg-zinc-800'; + @Input() backgroundColumnAlternate: string = 'bg-zinc-700'; + @Input() border: string = 'border-zinc-600'; + + @Input() items: any[] = []; + @Input() columns: ColumnDefinition[] = []; + + isBoolean(obje: any): boolean { + console.log(obje); + return typeof obje === 'boolean'; + } +} diff --git a/src/app/shared/components/tag-selector/tag-selector.component.html b/src/app/shared/components/tag-selector/tag-selector.component.html new file mode 100644 index 0000000..eede1d8 --- /dev/null +++ b/src/app/shared/components/tag-selector/tag-selector.component.html @@ -0,0 +1,17 @@ +
+
+ +
+ +

+ {{ tag.description }} +

+
diff --git a/src/app/shared/components/tag-selector/tag-selector.component.scss b/src/app/shared/components/tag-selector/tag-selector.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/shared/components/tag-selector/tag-selector.component.ts b/src/app/shared/components/tag-selector/tag-selector.component.ts new file mode 100644 index 0000000..925caad --- /dev/null +++ b/src/app/shared/components/tag-selector/tag-selector.component.ts @@ -0,0 +1,55 @@ +import { Component, Inject } from '@angular/core'; +import { ColumnDefinition } from '../table/table.component'; +import { Subject, debounceTime } from 'rxjs'; +import { TagListEntry } from 'src/app/model/TagListEntry'; +import { RequestService } from 'src/app/request.service'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'shared-tag-selector', + templateUrl: './tag-selector.component.html', + styleUrls: ['./tag-selector.component.scss'], +}) +export class TagSelectorComponent { + query$ = new Subject(); + query: string = ''; + tags: TagListEntry[] = []; + + constructor( + public requestService: RequestService, + public dialogRef: MatDialogRef + ) { + this.query$.pipe(debounceTime(300)).subscribe((query) => { + this.query = query; + this.readList(); + }); + } + + ngOnInit(): void { + this.readList(); + } + + onInputChanged(event: Event): void { + this.query$.next((event.target as HTMLTextAreaElement).value); + } + + onClickTag(tag: TagListEntry) { + this.dialogRef.close(tag); + } + + onClose() { + this.dialogRef.close(undefined); + } + + readList(): void { + this.requestService.post( + 'tag-list/read-list', + { + query: this.query, + }, + (response: any) => { + this.tags = response.items; + } + ); + } +} diff --git a/src/app/shared/components/video-list/video-list.component.html b/src/app/shared/components/video-list/video-list.component.html new file mode 100644 index 0000000..48ec408 --- /dev/null +++ b/src/app/shared/components/video-list/video-list.component.html @@ -0,0 +1,42 @@ +
+ +
+ +
+ + + + Thumbnail + +
+ + diff --git a/src/app/shared/components/video-list/video-list.component.scss b/src/app/shared/components/video-list/video-list.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/shared/components/video-list/video-list.component.ts b/src/app/shared/components/video-list/video-list.component.ts new file mode 100644 index 0000000..b4617e9 --- /dev/null +++ b/src/app/shared/components/video-list/video-list.component.ts @@ -0,0 +1,67 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { Router } from '@angular/router'; +import { Subject, debounceTime } from 'rxjs'; +import { VideoListEntry } from 'src/app/model/VideoListEntry'; +import { RequestService } from 'src/app/request.service'; + +export interface OnReadListModel { + page: number; + perPage: number; + query: string; +} + +@Component({ + selector: 'shared-video-list', + templateUrl: './video-list.component.html', + styleUrls: ['./video-list.component.scss'], +}) +export class VideoListComponent { + query$ = new Subject(); + query: string = ''; + @Input() total: number = 0; + @Input() page: number = 1; + @Input() perPage: number = 36; + @Input() videos: VideoListEntry[] = []; + @Output() onReadList: EventEmitter = new EventEmitter(); + + constructor(private router: Router, public requestService: RequestService) { + this.query$.pipe(debounceTime(300)).subscribe((query) => { + this.query = query; + this.page = 1; + this.readList(); + }); + } + + ngOnInit(): void { + this.readList(); + } + + onPageChanged(newPage: number) { + this.page = newPage; + + this.readList(); + } + + onInputChanged(event: Event): void { + this.query$.next((event.target as HTMLTextAreaElement).value); + } + + selectVideo(entry: VideoListEntry) { + this.router.navigate(['/video', entry.id]); + } + + readList(): void { + this.onReadList.emit({ + query: this.query, + page: this.page, + perPage: this.perPage, + }); + } + + getThumbnailUrl(entry: VideoListEntry): string { + let hostString = window.location.host; + let protocol = window.location.protocol; + let host = protocol + '//' + hostString + '/api/'; + return host + 'video/thumbnail/' + entry.id; + } +} diff --git a/src/app/shared/components/video-presenter/video-presenter.component.html b/src/app/shared/components/video-presenter/video-presenter.component.html new file mode 100644 index 0000000..f7c2416 --- /dev/null +++ b/src/app/shared/components/video-presenter/video-presenter.component.html @@ -0,0 +1,15 @@ +
+
+ {{header}} +
+ + +
\ No newline at end of file diff --git a/src/app/shared/components/video-presenter/video-presenter.component.scss b/src/app/shared/components/video-presenter/video-presenter.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/shared/components/video-presenter/video-presenter.component.ts b/src/app/shared/components/video-presenter/video-presenter.component.ts new file mode 100644 index 0000000..814896e --- /dev/null +++ b/src/app/shared/components/video-presenter/video-presenter.component.ts @@ -0,0 +1,11 @@ +import { Component, Input } from '@angular/core'; + +@Component({ + selector: 'shared-video-presenter', + templateUrl: './video-presenter.component.html', + styleUrls: ['./video-presenter.component.scss'] +}) +export class VideoPresenterComponent { + @Input() header: string|null = null; + @Input() source = ''; +} diff --git a/src/app/shared/models/TabItem.ts b/src/app/shared/models/TabItem.ts new file mode 100644 index 0000000..6200d6d --- /dev/null +++ b/src/app/shared/models/TabItem.ts @@ -0,0 +1,4 @@ +export interface TabItem { + id: string; + title: string; +} \ No newline at end of file diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts index 76667c7..08879f5 100644 --- a/src/app/shared/shared.module.ts +++ b/src/app/shared/shared.module.ts @@ -1,6 +1,13 @@ import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; - +import { FormComponent } from './components/form/form.component'; +import { CardComponent } from './components/card/card.component'; +import { PaginatorComponent } from './components/paginator/paginator.component'; +import { TabControlComponent } from './components/tab-control/tab-control.component'; +import { VideoPresenterComponent } from './components/video-presenter/video-presenter.component'; +import { ImagePresenterComponent } from './components/image-presenter/image-presenter.component'; +import { TableComponent } from './components/table/table.component'; import { FormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; @@ -9,7 +16,7 @@ import { MatToolbarModule } from '@angular/material/toolbar'; import { MatMenuModule } from '@angular/material/menu'; import { MatListModule } from '@angular/material/list'; import { MatIconModule } from '@angular/material/icon'; -import { MatGridListModule } from '@angular/material/grid-list' +import { MatGridListModule } from '@angular/material/grid-list'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatExpansionModule } from '@angular/material/expansion'; @@ -22,14 +29,58 @@ import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatPaginatorModule } from '@angular/material/paginator'; import { MatTabsModule } from '@angular/material/tabs'; import { MatTableModule } from '@angular/material/table'; - - - +import { MatChipsModule } from '@angular/material/chips'; +import { VideoListComponent } from './components/video-list/video-list.component'; +import { TagSelectorComponent } from './components/tag-selector/tag-selector.component'; @NgModule({ declarations: [ + ImagePresenterComponent, + VideoPresenterComponent, + CardComponent, + TabControlComponent, + PaginatorComponent, + FormComponent, + TableComponent, + VideoListComponent, + TagSelectorComponent, + ], + exports: [ + ImagePresenterComponent, + VideoPresenterComponent, + CardComponent, + TabControlComponent, + PaginatorComponent, + FormComponent, + TableComponent, + VideoListComponent, + TagSelectorComponent, + + MatSlideToggleModule, + MatCardModule, + MatButtonModule, + MatIconModule, + MatGridListModule, + MatFormFieldModule, + FormsModule, + MatExpansionModule, + MatMenuModule, + MatListModule, + MatToolbarModule, + MatSidenavModule, + MatInputModule, + MatSelectModule, + MatDividerModule, + MatDialogModule, + MatSnackBarModule, + MatPaginatorModule, + MatCheckboxModule, + MatTabsModule, + MatTableModule, + MatChipsModule, ], imports: [ + RouterModule, CommonModule, MatSlideToggleModule, MatCardModule, @@ -51,32 +102,8 @@ import { MatTableModule } from '@angular/material/table'; MatPaginatorModule, MatCheckboxModule, MatTabsModule, - MatTableModule + MatTableModule, + MatChipsModule, ], - exports: [ - // to be replaced - MatSlideToggleModule, - MatCardModule, - MatButtonModule, - MatIconModule, - MatGridListModule, - MatFormFieldModule, - FormsModule, - MatExpansionModule, - MatMenuModule, - MatListModule, - MatToolbarModule, - MatSidenavModule, - MatInputModule, - MatSelectModule, - MatDividerModule, - MatDialogModule, - MatSnackBarModule, - MatPaginatorModule, - MatCheckboxModule, - MatTabsModule, - MatTableModule - - ] }) -export class SharedModule { } \ No newline at end of file +export class SharedModule {} diff --git a/src/index.html b/src/index.html index d9531f3..016a8be 100644 --- a/src/index.html +++ b/src/index.html @@ -1,17 +1,15 @@ - + - - - mytube - - - - - - - - - + + + MyTube + + + + + + + diff --git a/src/styles.scss b/src/styles.scss index 3e158d6..bd6213e 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,65 +1,3 @@ -body { - margin: 0; - height: 100vh; -} - -app-root { - display:inline-block; - height: 100%; - width: 100%; -} - -.flex-container { - display: flex; -} - -.flex-1 { - flex: 1; -} -.flex-2 { - flex: 2; -} -.flex-3 { - flex: 3; -} -.flex-4 { - flex: 4; -} -.flex-5 { - flex: 5; -} - -.container { - padding: 0; - max-width: unset; -} - - - -.svg { - filter: invert(1); -} - -.svg-1 { - filter: invert(1); - height: 1em; - width: 1em; -} - -.svg-2 { - filter: invert(1); - height: 2em; - width: 2em; -} - -.svg-3 { - filter: invert(1); - height: 3em; - width: 3em; -} - -.svg-4 { - filter: invert(1); - height: 4em; - width: 4em; -} \ No newline at end of file +@tailwind base; +@tailwind components; +@tailwind utilities; \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..5397866 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,9 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ["./src/**/*.{html,ts}"], + theme: { + extend: {}, + }, + plugins: [], +} +