diff --git a/src/frontend/src/app/components/playlist-box/playlist-box.component.ts b/src/frontend/src/app/components/playlist-box/playlist-box.component.ts index b91c601..4bd18cf 100644 --- a/src/frontend/src/app/components/playlist-box/playlist-box.component.ts +++ b/src/frontend/src/app/components/playlist-box/playlist-box.component.ts @@ -1,8 +1,9 @@ import {Component, Input} from '@angular/core'; import {AsyncPipe, CommonModule, NgForOf, NgIf} from "@angular/common"; import {TrackListComponent} from "../track-list/track-list.component"; -import {Playlist, PlaylistService, PlaylistStatusEnum, PlaylistUi} from "../../services/playlist.service"; +import {PlaylistService, PlaylistStatusEnum, PlaylistUi} from "../../services/playlist.service"; import {Observable, map} from "rxjs"; +import {Playlist} from "../../models/playlist"; const STATUS2CLASS = { [PlaylistStatusEnum.Completed]: 'is-success', diff --git a/src/frontend/src/app/components/track-list/track-list.component.ts b/src/frontend/src/app/components/track-list/track-list.component.ts index fa7ded2..24e4d2e 100644 --- a/src/frontend/src/app/components/track-list/track-list.component.ts +++ b/src/frontend/src/app/components/track-list/track-list.component.ts @@ -1,7 +1,8 @@ import {Component, Input} from '@angular/core'; import {CommonModule, NgFor, NgSwitch, NgSwitchCase} from "@angular/common"; -import {Track, TrackService, TrackStatusEnum} from "../../services/track.service"; +import {TrackService} from "../../services/track.service"; import {Observable} from "rxjs"; +import {Track, TrackStatusEnum} from "../../models/track"; @Component({ selector: 'app-track-list', diff --git a/src/frontend/src/app/models/playlist.ts b/src/frontend/src/app/models/playlist.ts new file mode 100644 index 0000000..380f3da --- /dev/null +++ b/src/frontend/src/app/models/playlist.ts @@ -0,0 +1,8 @@ +export interface Playlist { + id: number; + name?: string; + spotifyUrl: string; + error?: string; + active: boolean; + createdAt: number; +} diff --git a/src/frontend/src/app/models/track.ts b/src/frontend/src/app/models/track.ts new file mode 100644 index 0000000..a46f8fd --- /dev/null +++ b/src/frontend/src/app/models/track.ts @@ -0,0 +1,19 @@ +export interface Track { + id: number; + artist: string; + name: string; + spotifyUrl: string; + youtubeUrl: string; + status: TrackStatusEnum; + playlistId?: number; + error?: string; +} + +export enum TrackStatusEnum { + New, + Searching, + Queued, + Downloading, + Completed, + Error, +} diff --git a/src/frontend/src/app/services/playlist.service.ts b/src/frontend/src/app/services/playlist.service.ts index 02418b5..d132e91 100644 --- a/src/frontend/src/app/services/playlist.service.ts +++ b/src/frontend/src/app/services/playlist.service.ts @@ -3,9 +3,7 @@ import {createStore} from "@ngneat/elf"; import {HttpClient} from "@angular/common/http"; import { deleteEntities, - getEntityByPredicate, selectAllEntities, selectEntities, selectEntity, - selectEntityByPredicate, setEntities, UIEntitiesRef, unionEntities, updateEntities, upsertEntities, @@ -14,20 +12,17 @@ import { } from "@ngneat/elf-entities"; import {joinRequestResult, trackRequestResult} from "@ngneat/elf-requests"; import {combineLatest, filter, first, map, Observable, of, switchMap, tap} from "rxjs"; -import {Track, TrackService} from "./track.service"; +import {TrackService} from "./track.service"; import {Socket} from "ngx-socket-io"; +import {Playlist} from "../models/playlist"; const STORE_NAME = 'playlist'; const ENDPOINT = '/api/playlist'; const CREATE_LOADING = 'CREATE_LOADING'; - -export interface Playlist { - id: number; - name?: string; - spotifyUrl: string; - error?: string; - active: boolean; - createdAt: number; +enum WsPlaylistOperation { + New = 'playlistNew', + Update = 'playlistUpdate', + Delete = 'playlistDelete', } export interface PlaylistUi { @@ -53,26 +48,18 @@ export class PlaylistService { withEntities(), withUIEntities() ); + all$ = this.store.combine({ entities: this.store.pipe(selectAllEntities()), UIEntities: this.store.pipe(selectEntities({ ref: UIEntitiesRef })), - }).pipe(unionEntities(), map(data => data.sort((a, b) => this.sort(a, b)))); - - loading$ = this.store.pipe(joinRequestResult([STORE_NAME])); + }).pipe(unionEntities(), map(data => data.sort((a, b) => this.groupActiveAndSortByCreation(a, b)))); createLoading$ = this.store.pipe(joinRequestResult([CREATE_LOADING], { initialStatus: 'idle' })); constructor(private readonly http: HttpClient, private readonly socket: Socket, private readonly trackService: TrackService, ) { - this.socket.on('playlistUpdate', (playlist: Playlist) => this.store.update(upsertEntities(playlist))); - this.socket.on('playlistDelete', ({id}: {id: number}) => this.store.update(deleteEntities(Number(id)))); - this.socket.on('playlistNew', (playlist: Playlist) => - this.store.update( - upsertEntities(playlist), - upsertEntities({id: playlist.id, collapsed: false}, {ref: UIEntitiesRef}) - ) - ); + this.initWsConnection(); } getById(id: number): Observable { @@ -133,7 +120,6 @@ export class PlaylistService { setEntities(data.map(item => ({id: item.id, collapsed: false})), {ref: UIEntitiesRef}) )), tap((data: Playlist[]) => data.forEach(playlist => this.trackService.fetch(playlist.id))), - trackRequestResult([STORE_NAME], { skipCache: true }), ).subscribe(); } @@ -144,17 +130,13 @@ export class PlaylistService { } toggleCollapsed(id: number): void { - this.store.update(updateEntities(id, old => ({...old, collapsed: !old.collapsed}), { ref: UIEntitiesRef })) + this.store.update(updateEntities(id, old => ({...old, collapsed: !old.collapsed}), { ref: UIEntitiesRef })); } delete(id: number): void { this.delete$(id).subscribe(); } - private delete$(id: number): Observable { - return this.http.delete(`${ENDPOINT}/${id}`); - } - retryFailed(id: number): void { this.http.get(`${ENDPOINT}/retry/${id}`).subscribe(); } @@ -163,7 +145,22 @@ export class PlaylistService { this.http.put(`${ENDPOINT}/${id}`, {active}).subscribe(); } - private sort(a: Playlist & PlaylistUi, b: Playlist & PlaylistUi): number { + private delete$(id: number): Observable { + return this.http.delete(`${ENDPOINT}/${id}`); + } + + private groupActiveAndSortByCreation(a: Playlist & PlaylistUi, b: Playlist & PlaylistUi): number { return a.active === b.active ? (b.createdAt - a.createdAt) : (a.active < b.active ? 1 : -1); } + + private initWsConnection(): void { + this.socket.on(WsPlaylistOperation.Update, (playlist: Playlist) => this.store.update(upsertEntities(playlist))); + this.socket.on(WsPlaylistOperation.Delete, ({id}: {id: number}) => this.store.update(deleteEntities(Number(id)))); + this.socket.on(WsPlaylistOperation.New, (playlist: Playlist) => + this.store.update( + upsertEntities(playlist), + upsertEntities({id: playlist.id, collapsed: false}, {ref: UIEntitiesRef}) + ) + ); + } } diff --git a/src/frontend/src/app/services/track.service.ts b/src/frontend/src/app/services/track.service.ts index 82d74f4..ded0ae8 100644 --- a/src/frontend/src/app/services/track.service.ts +++ b/src/frontend/src/app/services/track.service.ts @@ -3,30 +3,15 @@ import {createStore} from "@ngneat/elf"; import {deleteEntities, selectManyByPredicate, upsertEntities, withEntities} from "@ngneat/elf-entities"; import {Socket} from "ngx-socket-io"; import {map, Observable, tap} from "rxjs"; -import {trackRequestResult} from "@ngneat/elf-requests"; import {HttpClient} from "@angular/common/http"; +import {Track, TrackStatusEnum} from "../models/track"; const STORE_NAME = 'track'; const ENDPOINT = '/api/track'; - -export interface Track { - id: number; - artist: string; - name: string; - spotifyUrl: string; - youtubeUrl: string; - status: TrackStatusEnum; - playlistId?: number; - error?: string; -} - -export enum TrackStatusEnum { - New, - Searching, - Queued, - Downloading, - Completed, - Error, +enum WsTrackOperation { + New = 'trackNew', + Update = 'trackUpdate', + Delete = 'trackDelete', } @Injectable({ @@ -58,17 +43,12 @@ export class TrackService { private readonly http: HttpClient, private readonly socket: Socket, ) { - this.socket.on('trackUpdate', (track: Track) => this.store.update(upsertEntities(track))); - this.socket.on('trackDelete', ({id}: {id: number}) => this.store.update(deleteEntities(id))); - this.socket.on('trackNew', ({track, playlistId}: {track: Track, playlistId: number}) => - this.store.update(upsertEntities([{...track, playlistId}])) - ); + this.initWsConnection(); } fetch(playlistId: number): void { this.http.get(`${ENDPOINT}/playlist/${playlistId}`).pipe( tap((data: Track[]) => this.store.update(upsertEntities(data.map(track => ({...track, playlistId}))))), - trackRequestResult([STORE_NAME], { skipCache: true }), ).subscribe(); } @@ -79,4 +59,12 @@ export class TrackService { retry(id: number): void { this.http.get(`${ENDPOINT}/retry/${id}`).subscribe(); } + + private initWsConnection(): void { + this.socket.on(WsTrackOperation.Update, (track: Track) => this.store.update(upsertEntities(track))); + this.socket.on(WsTrackOperation.Delete, ({id}: {id: number}) => this.store.update(deleteEntities(id))); + this.socket.on(WsTrackOperation.New, ({track, playlistId}: {track: Track, playlistId: number}) => + this.store.update(upsertEntities([{...track, playlistId}])) + ); + } }