From 5350e45ce6cb1d38bcc32892beb29556b04cf6db Mon Sep 17 00:00:00 2001 From: _Bastler <_Bastler@bstly.de> Date: Sun, 28 Nov 2021 17:30:19 +0100 Subject: [PATCH] add jukebox --- src/app/app-routing.module.ts | 3 + src/app/app.module.ts | 4 +- src/app/pages/jukebox/jukebox.component.html | 75 ++++++++++ src/app/pages/jukebox/jukebox.component.scss | 9 ++ src/app/pages/jukebox/jukebox.compontent.ts | 136 +++++++++++++++++++ src/app/services/jukebox.service.ts | 30 ++++ 6 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 src/app/pages/jukebox/jukebox.component.html create mode 100644 src/app/pages/jukebox/jukebox.component.scss create mode 100644 src/app/pages/jukebox/jukebox.compontent.ts create mode 100644 src/app/services/jukebox.service.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b59905c..6aee5eb 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -32,10 +32,13 @@ import { BorrowItemEditComponent, BorrowItemsComponent } from './pages/borrow/it import { BorrowRequestsComponent } from './pages/borrow/requests/requests.component'; import { BorrowComponent } from './pages/borrow/borrow.component'; import { InviteCodeComponent } from './pages/invites/code/code.component'; +import { JukeboxComponent } from './pages/jukebox/jukebox.compontent'; const routes: Routes = [ { path: 'profile/:username', component: UserComponent, canActivate: [ AuthUpdateGuard ] }, { path: 'edit-profile', component: ProfileComponent, canActivate: [ AuthenticatedGuard ] }, + { path: 'partey/jukebox', component: JukeboxComponent, canActivate: [ AuthenticatedGuard ] }, + { path: 'partey/manage', component: ParteyComponent, canActivate: [ AuthenticatedGuard ] }, { path: '', component: MainComponent, children: [ { path: '', redirectTo: "/services", pathMatch: 'full' }, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 37c234b..edba6ac 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -65,6 +65,7 @@ import { MatPaginatorIntl } from '@angular/material/paginator'; import { NgxMatDateAdapter, NGX_MAT_DATE_FORMATS } from '@angular-material-components/datetime-picker'; import { MAT_DATE_LOCALE } from '@angular/material/core'; import { NgxMatMomentAdapter } from '@angular-material-components/moment-adapter'; +import { JukeboxComponent } from './pages/jukebox/jukebox.compontent'; export function init_app(i18n: I18nService) { @@ -124,7 +125,8 @@ export class XhrInterceptor implements HttpInterceptor { UrlShortenerComponent, UrlShortenerShareDialog, UrlShortenerEditDialog, UrlShortenerPasswordComponent, BorrowComponent, BorrowItemsComponent, BorrowItemEditComponent, BorrowRequestsComponent, BorrowRequestEditComponent, BorrowProvingComponent, BorrowProvingResultDialog, DividerComponent, DividertestComponent, - DurationpickerComponent + DurationpickerComponent, + JukeboxComponent ], imports: [ BrowserModule, diff --git a/src/app/pages/jukebox/jukebox.component.html b/src/app/pages/jukebox/jukebox.component.html new file mode 100644 index 0000000..a5f5731 --- /dev/null +++ b/src/app/pages/jukebox/jukebox.component.html @@ -0,0 +1,75 @@ +
+ + + {{'jukebox' | i18n}} + + +
+ + + +
+
+ + + +
+ +

{{'jukebox.timeout' | i18n:timeout}}

+ +
+ + + + {{track.name}} + + + {{artist.name}}, + + + + +
+ + +
+ + + + {{'jukebox' | i18n}} + {{'jukebox.wait' | i18n}} + + +

{{'jukebox.timeout' | i18n:timeout}}

+
+ + +
+ + + + {{'jukebox' | i18n}} + {{'jukebox.unavailable' | i18n}} + + +

{{'jukebox.unavailable.hint' | i18n}}

+
+ + +
+ + + + {{'jukebox' | i18n}} + {{'jukebox.forbidden' | i18n}} + + +

{{'jukebox.forbidden.hint' | i18n}}

+
+ + +
\ No newline at end of file diff --git a/src/app/pages/jukebox/jukebox.component.scss b/src/app/pages/jukebox/jukebox.component.scss new file mode 100644 index 0000000..781e60f --- /dev/null +++ b/src/app/pages/jukebox/jukebox.component.scss @@ -0,0 +1,9 @@ +mat-form-field { + display: block; +} + +mat-card.track { + margin-top: 10px; + margin-bottom: 10px; + cursor: pointer; +} \ No newline at end of file diff --git a/src/app/pages/jukebox/jukebox.compontent.ts b/src/app/pages/jukebox/jukebox.compontent.ts new file mode 100644 index 0000000..6994745 --- /dev/null +++ b/src/app/pages/jukebox/jukebox.compontent.ts @@ -0,0 +1,136 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { MatDialog } from '@angular/material/dialog'; +import { MatSnackBar } from '@angular/material/snack-bar'; + +import { FormControl } from '@angular/forms'; + +import { ConfirmDialog } from '../../ui/confirm/confirm.component'; +import { JukeboxService } from 'src/app/services/jukebox.service'; +import { I18nService } from '../../services/i18n.service'; +import { Data } from '@angular/router'; + +@Component({ + selector: 'app-jukebox', + templateUrl: './jukebox.component.html', + styleUrls: [ './jukebox.component.scss' ] +}) +export class JukeboxComponent implements OnInit { + + timeout: number = 0; + timer; + searchResult: any; + active: boolean = false; + wait: boolean = false; + forbidden: boolean = false; + unavailable: boolean = false; + searchDisabled = true; + searchFormControl = new FormControl(); + + constructor( + public dialog: MatDialog, + private snackBar: MatSnackBar, + private i18n: I18nService, + private jukeboxService: JukeboxService) { } + + ngOnInit(): void { + this.check(); + + this.searchFormControl.valueChanges.subscribe(value => { + this.searchDisabled = false; + }) + } + + check() { + this.timeout = 0; + this.wait = false; + this.forbidden = false; + this.unavailable = false; + this.jukeboxService.check().subscribe((response) => { + this.active = true; + }, (error) => { + this.active = false; + if (error.status == 403) { + this.forbidden = true; + } else if (error.status == 410) { + this.unavailable = true; + } else if (error.status == 402) { + this.wait = true; + this.timeout = 60 - error.error; + this.timer = setInterval(() => { + this.timeout--; + if (this.timeout == 0) { + clearInterval(this.timer); + this.check(); + } + }, 1000) + } + }) + } + + search() { + this.searchDisabled = true; + this.jukeboxService.search(this.searchFormControl.value).subscribe((data: any) => { + this.searchResult = data.tracks; + }) + } + + searchMore() { + this.searchDisabled = true; + this.jukeboxService.searchOffset(this.searchFormControl.value, this.searchResult.offset + this.searchResult.limit).subscribe((data: any) => { + this.searchResult.offset = data.tracks.offset; + this.searchResult.total = data.tracks.total; + for (let track of data.tracks.items) { + this.searchResult.items.push(track); + } + }) + } + + getImageUrl(track: any) { + let url = ""; + let width; + if (track.album && track.album.images) { + for (let image of track.album.images) { + if (!width || width > image.width) { + url = image.url; + } + } + } + return url; + } + + queue(track: any) { + const dialogRef = this.dialog.open(ConfirmDialog, { + data: { + 'label': 'jukebox.addToQueue.confirm', + 'args': [ track.artists[ 0 ].name, track.name ] + } + }) + + dialogRef.afterClosed().subscribe(result => { + if (result) { + this.jukeboxService.queue(track.uri).subscribe(() => { + this.active = false; + this.wait = true; + this.timeout = 60; + this.timer = setInterval(() => { + this.timeout--; + if (this.timeout == 0) { + clearInterval(this.timer); + this.check(); + } + }, 1000) + + this.snackBar.open(this.i18n.get("jukebox.addToQueue.success", []), this.i18n.get("close", []), { + duration: 3000 + }); + }, (error) => { + this.snackBar.open(this.i18n.get("jukebox.addToQueue.error", []), this.i18n.get("close", []), { + duration: 3000 + }); + }) + } + }); + } + +} \ No newline at end of file diff --git a/src/app/services/jukebox.service.ts b/src/app/services/jukebox.service.ts new file mode 100644 index 0000000..dc77b7c --- /dev/null +++ b/src/app/services/jukebox.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { environment } from '../../environments/environment'; + +@Injectable({ + providedIn: 'root', +}) +export class JukeboxService { + + constructor(private http: HttpClient) { + } + + check() { + return this.http.get(environment.apiUrl + "/jukebox"); + } + + search(query: string) { + return this.http.get(environment.apiUrl + "/jukebox/search?q=" + query); + } + + searchOffset(query: string, offset: number) { + return this.http.get(environment.apiUrl + "/jukebox/search?q=" + query + "&offset=" + offset); + } + + queue(uri: string) { + return this.http.post(environment.apiUrl + "/jukebox/queue?uri=" + uri, ""); + } + +} \ No newline at end of file