diff --git a/angular.json b/angular.json
index 213f72b..fa037f3 100644
--- a/angular.json
+++ b/angular.json
@@ -20,7 +20,6 @@
"skipTests": true
},
"@schematics/angular:module": {
- "skipTests": true
},
"@schematics/angular:pipe": {
"skipTests": true
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index dce82e9..030fe98 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -10,7 +10,7 @@ services:
networks:
- mytube
volumes:
- - ./../:/var/www/
+ - ./../:/var/www/html
build:
context: ./../
dockerfile: ./docker/npm/dockerfile
@@ -21,7 +21,7 @@ services:
networks:
- mytube
volumes:
- - ./../:/var/www/html:z
+ - ./../:/var/www/html
build:
context: ./../
dockerfile: ./docker/nginx/dockerfile
diff --git a/docker/nginx/config/nginx.conf b/docker/nginx/config/nginx.conf
index 1400616..45b28ff 100644
--- a/docker/nginx/config/nginx.conf
+++ b/docker/nginx/config/nginx.conf
@@ -6,6 +6,8 @@ upstream host-backend-nginx {
server{
listen 80 default_server;
+ client_max_body_size 1000M;
+
root /var/www/html/dist/mytube;
index index.html;
diff --git a/docker/npm/dockerfile b/docker/npm/dockerfile
index 0535922..d6f8389 100644
--- a/docker/npm/dockerfile
+++ b/docker/npm/dockerfile
@@ -1,4 +1,4 @@
-FROM node:latest
+FROM node:20-slim
WORKDIR /var/www/html
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 27cb800..90c6b64 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,3 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 9a8db0f..8366f2c 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,9 +1,44 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
+import { RequestService } from './request.service';
+import { VideoListEntry } from './model/VideoListEntry';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
-export class AppComponent {
+export class AppComponent implements OnInit {
+ videoList: VideoListEntry[] = [];
+ selectedVideoUrl: string|null = null;
+
+ constructor(
+ public requestService : RequestService,
+ ) {
+ }
+
+ ngOnInit(): void {
+ this.readList();
+ }
+
+ selectVideo(entry: VideoListEntry) {
+ if (this.selectedVideoUrl === null) {
+ this.selectedVideoUrl = "http://wsl-flo/api/video/stream/" + entry.id;
+ } else {
+ this.selectedVideoUrl = null;
+ }
+ }
+
+ readList(): void {
+ this.requestService.post(
+ 'video-list/read-list',
+ {},
+ (response:any) => {
+ this.videoList = response;
+ }
+ );
+ }
+
+ delay(ms: number) {
+ return new Promise( resolve => setTimeout(resolve, ms) );
+ }
}
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 7bdef99..e4dc064 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -1,29 +1,17 @@
import { NgModule } from '@angular/core';
-import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
-import { MatInputModule } from '@angular/material/input';
-import { MatSelectModule } from '@angular/material/select';
-
-import { MatSidenavModule } from '@angular/material/sidenav';
-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 { MatButtonModule } from '@angular/material/button';
-import { MatCardModule } from '@angular/material/card';
-import { MatExpansionModule } from '@angular/material/expansion';
-import { MatSlideToggleModule } from '@angular/material/slide-toggle';
-import { MatFormFieldModule } from '@angular/material/form-field';
-import { MatDividerModule } from '@angular/material/divider';
-import {MatDialogModule} from '@angular/material/dialog';
-import {MatSnackBarModule} from '@angular/material/snack-bar';
-
import { AppComponent } from './app.component';
+import { RouterModule, Routes } from '@angular/router';
+import { SharedModule } from './shared/shared.module';
+const routes: Routes = [
+ { path: 'video', loadChildren: () => import('./feature/video/video.module').then( m => m.VideoModule ) },
+ { path: '', redirectTo: 'video/list', pathMatch: 'full' }
+];
+
@NgModule({
declarations: [
AppComponent,
@@ -31,23 +19,8 @@ import { AppComponent } from './app.component';
imports: [
BrowserModule,
HttpClientModule,
- MatSlideToggleModule,
- MatCardModule,
- MatButtonModule,
- MatIconModule,
- MatGridListModule,
- MatFormFieldModule,
- FormsModule,
- MatExpansionModule,
- MatMenuModule,
- MatListModule,
- MatToolbarModule,
- MatSidenavModule,
- MatInputModule,
- MatSelectModule,
- MatDividerModule,
- MatDialogModule,
- MatSnackBarModule
+ RouterModule.forRoot(routes),
+ SharedModule,
],
providers: [],
bootstrap: [AppComponent]
diff --git a/src/app/feature/video/list/list.component.html b/src/app/feature/video/list/list.component.html
new file mode 100644
index 0000000..c2db882
--- /dev/null
+++ b/src/app/feature/video/list/list.component.html
@@ -0,0 +1,15 @@
+
+
+
+
+ {{video.title}}
+
+
+
+
+
+
+
diff --git a/src/app/feature/video/list/list.component.scss b/src/app/feature/video/list/list.component.scss
new file mode 100644
index 0000000..221b568
--- /dev/null
+++ b/src/app/feature/video/list/list.component.scss
@@ -0,0 +1,56 @@
+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;
+ margin: 0 -10px;
+}
+
+.mat-card {
+ flex-basis: calc(100% - 20px);
+ max-width: calc(100% - 20px);
+ flex: 0 0 calc(33.33% - 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
new file mode 100644
index 0000000..7eebc0d
--- /dev/null
+++ b/src/app/feature/video/list/list.component.ts
@@ -0,0 +1,43 @@
+import { Component } from '@angular/core';
+import { Router } from '@angular/router';
+import { VideoListEntry } from 'src/app/model/VideoListEntry';
+import { RequestService } from 'src/app/request.service';
+
+@Component({
+ selector: 'app-list',
+ templateUrl: './list.component.html',
+ styleUrls: ['./list.component.scss']
+})
+export class ListComponent {
+ videoList: VideoListEntry[] = [];
+ selectedVideoUrl: string|null = null;
+
+ constructor(
+ private router: Router,
+ public requestService : RequestService,
+ ) {
+ }
+
+ ngOnInit(): void {
+ this.readList();
+ }
+
+ selectVideo(entry: VideoListEntry) {
+ this.router.navigate(['/video', entry.id]);
+ }
+
+ getThumbnailUrl(entry: VideoListEntry): string {
+ return 'http://wsl-flo/api/video/thumbnail/' + entry.id;
+}
+
+ readList(): void {
+ this.requestService.post(
+ 'video-list/read-list',
+ {},
+ (response:any) => {
+ this.videoList = response;
+ }
+ );
+ }
+
+}
diff --git a/src/app/feature/video/video.component.html b/src/app/feature/video/video.component.html
new file mode 100644
index 0000000..9f15798
--- /dev/null
+++ b/src/app/feature/video/video.component.html
@@ -0,0 +1,15 @@
+
+
+
+
+
{{videoDetails.title}}
+
+
+
+
diff --git a/src/app/feature/video/video.component.scss b/src/app/feature/video/video.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/feature/video/video.component.ts b/src/app/feature/video/video.component.ts
new file mode 100644
index 0000000..4182769
--- /dev/null
+++ b/src/app/feature/video/video.component.ts
@@ -0,0 +1,39 @@
+import { Component } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { VideoDetails } from 'src/app/model/VideoDetails';
+import { RequestService } from 'src/app/request.service';
+
+@Component({
+ selector: 'app-video',
+ templateUrl: './video.component.html',
+ styleUrls: ['./video.component.scss']
+})
+export class VideoComponent {
+ videoId: string;
+ videoUrl: string|null = null;
+ videoDetails: VideoDetails|null = null;
+
+ constructor(
+ private route: ActivatedRoute,
+ private requestService : RequestService,
+ ) {
+ this.videoId = this.route.snapshot.paramMap.get('id') ?? '';
+ this.videoUrl = "http://wsl-flo/api/video/stream/" + this.videoId;
+ }
+
+ ngOnInit(): void {
+ this.readDetails();
+ }
+
+ readDetails(): void {
+ this.requestService.post(
+ 'video/read-details',
+ {
+ videoId: this.videoId
+ },
+ (response:any) => {
+ this.videoDetails = response;
+ }
+ );
+ }
+}
diff --git a/src/app/feature/video/video.module.ts b/src/app/feature/video/video.module.ts
new file mode 100644
index 0000000..d89b981
--- /dev/null
+++ b/src/app/feature/video/video.module.ts
@@ -0,0 +1,25 @@
+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';
+
+const routes: Routes = [
+ { path: 'list', component: ListComponent },
+ { path: ':id', component: VideoComponent },
+ { path: '', redirectTo: 'video/list', pathMatch: 'full'},
+]
+
+@NgModule({
+ declarations: [
+ ListComponent,
+ VideoComponent
+ ],
+ imports: [
+ CommonModule,
+ SharedModule,
+ RouterModule.forChild(routes)
+ ]
+})
+export class VideoModule { }
diff --git a/src/app/model/VideoDetails.ts b/src/app/model/VideoDetails.ts
new file mode 100644
index 0000000..c0569ef
--- /dev/null
+++ b/src/app/model/VideoDetails.ts
@@ -0,0 +1,5 @@
+
+export interface VideoDetails {
+ title: string;
+ id: string;
+}
\ No newline at end of file
diff --git a/src/app/model/VideoListEntry.ts b/src/app/model/VideoListEntry.ts
new file mode 100644
index 0000000..b314066
--- /dev/null
+++ b/src/app/model/VideoListEntry.ts
@@ -0,0 +1,5 @@
+
+export interface VideoListEntry {
+ title: string;
+ id: string;
+}
\ No newline at end of file
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
new file mode 100644
index 0000000..76667c7
--- /dev/null
+++ b/src/app/shared/shared.module.ts
@@ -0,0 +1,82 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+import { FormsModule } from '@angular/forms';
+import { MatInputModule } from '@angular/material/input';
+import { MatSelectModule } from '@angular/material/select';
+import { MatSidenavModule } from '@angular/material/sidenav';
+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 { MatButtonModule } from '@angular/material/button';
+import { MatCardModule } from '@angular/material/card';
+import { MatExpansionModule } from '@angular/material/expansion';
+import { MatSlideToggleModule } from '@angular/material/slide-toggle';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatDividerModule } from '@angular/material/divider';
+import { MatDialogModule } from '@angular/material/dialog';
+import { MatSnackBarModule } from '@angular/material/snack-bar';
+import { MatCheckboxModule } from '@angular/material/checkbox';
+import { MatPaginatorModule } from '@angular/material/paginator';
+import { MatTabsModule } from '@angular/material/tabs';
+import { MatTableModule } from '@angular/material/table';
+
+
+
+
+@NgModule({
+ declarations: [
+ ],
+ imports: [
+ CommonModule,
+ MatSlideToggleModule,
+ MatCardModule,
+ MatButtonModule,
+ MatIconModule,
+ MatGridListModule,
+ MatFormFieldModule,
+ FormsModule,
+ MatExpansionModule,
+ MatMenuModule,
+ MatListModule,
+ MatToolbarModule,
+ MatSidenavModule,
+ MatInputModule,
+ MatSelectModule,
+ MatDividerModule,
+ MatDialogModule,
+ MatSnackBarModule,
+ MatPaginatorModule,
+ MatCheckboxModule,
+ MatTabsModule,
+ MatTableModule
+ ],
+ 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
diff --git a/src/styles.scss b/src/styles.scss
index 79422be..eec26cc 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -3,11 +3,61 @@ body {
height: 100vh;
}
+app-root {
+ height: 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;
}
-app-root {
- height: 100%;
+
+
+.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