first nice version

This commit is contained in:
Flo 2024-02-23 14:07:10 +01:00
parent 362a24a5c3
commit d7611f7531
18 changed files with 389 additions and 47 deletions

View File

@ -20,7 +20,6 @@
"skipTests": true "skipTests": true
}, },
"@schematics/angular:module": { "@schematics/angular:module": {
"skipTests": true
}, },
"@schematics/angular:pipe": { "@schematics/angular:pipe": {
"skipTests": true "skipTests": true

View File

@ -10,7 +10,7 @@ services:
networks: networks:
- mytube - mytube
volumes: volumes:
- ./../:/var/www/ - ./../:/var/www/html
build: build:
context: ./../ context: ./../
dockerfile: ./docker/npm/dockerfile dockerfile: ./docker/npm/dockerfile
@ -21,7 +21,7 @@ services:
networks: networks:
- mytube - mytube
volumes: volumes:
- ./../:/var/www/html:z - ./../:/var/www/html
build: build:
context: ./../ context: ./../
dockerfile: ./docker/nginx/dockerfile dockerfile: ./docker/nginx/dockerfile

View File

@ -6,6 +6,8 @@ upstream host-backend-nginx {
server{ server{
listen 80 default_server; listen 80 default_server;
client_max_body_size 1000M;
root /var/www/html/dist/mytube; root /var/www/html/dist/mytube;
index index.html; index index.html;

View File

@ -1,4 +1,4 @@
FROM node:latest FROM node:20-slim
WORKDIR /var/www/html WORKDIR /var/www/html

View File

@ -1,3 +1 @@
<video style="height: 100%; width: 100%;" controls autoplay> <router-outlet></router-outlet>
<source src="http://wsl-flo/api/video/stream" type="video/mp4" id="video_player" >
</video>

View File

@ -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({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'] 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) );
}
} }

View File

@ -1,29 +1,17 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http'; 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 { 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({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,
@ -31,23 +19,8 @@ import { AppComponent } from './app.component';
imports: [ imports: [
BrowserModule, BrowserModule,
HttpClientModule, HttpClientModule,
MatSlideToggleModule, RouterModule.forRoot(routes),
MatCardModule, SharedModule,
MatButtonModule,
MatIconModule,
MatGridListModule,
MatFormFieldModule,
FormsModule,
MatExpansionModule,
MatMenuModule,
MatListModule,
MatToolbarModule,
MatSidenavModule,
MatInputModule,
MatSelectModule,
MatDividerModule,
MatDialogModule,
MatSnackBarModule
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]

View File

@ -0,0 +1,15 @@
<div class="cardContainer">
<mat-card
class="mat-card"
*ngFor="let video of videoList"
(click)="selectVideo(video)">
<mat-card-header>
<mat-card-title class="mat-card-title">
{{video.title}}
</mat-card-title>
</mat-card-header>
<mat-card-content class="mat-card-content">
<img [src]="getThumbnailUrl(video)" alt="Thumbnail">
</mat-card-content>
</mat-card>
</div>

View File

@ -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;
}

View File

@ -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;
}
);
}
}

View File

@ -0,0 +1,15 @@
<div
*ngIf="videoDetails !== null">
<button [routerLink]="['/video/list']">Liste</button>
<h1>{{videoDetails.title}}</h1>
<video
style="height: 100%; width: 100%;"
controls
autoplay>
<source [src]="videoUrl" type="video/mp4">
</video>
</div>

View File

@ -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;
}
);
}
}

View File

@ -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 { }

View File

@ -0,0 +1,5 @@
export interface VideoDetails {
title: string;
id: string;
}

View File

@ -0,0 +1,5 @@
export interface VideoListEntry {
title: string;
id: string;
}

View File

@ -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 { }

View File

@ -3,11 +3,61 @@ body {
height: 100vh; 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 { .container {
padding: 0; padding: 0;
max-width: unset; 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;
} }