playlist download progress, fix library error downloading
This commit is contained in:
@@ -14,7 +14,11 @@
|
||||
<p class="subtitle">Download</p>
|
||||
<div class="is-flex">
|
||||
<input class="input" type="text" [(ngModel)]="url" placeholder="Paste playlist/song/artist url"/>
|
||||
<button class="button is-primary" [class.is-loading]="(createLoading$ | async)?.isLoading" (click)="download()">
|
||||
<button class="button is-primary"
|
||||
[class.is-loading]="(createLoading$ | async)?.isLoading"
|
||||
(click)="download()"
|
||||
[disabled]="!url"
|
||||
>
|
||||
<i class="fa-solid fa-download"></i> Download
|
||||
</button>
|
||||
</div>
|
||||
@@ -24,22 +28,7 @@
|
||||
<div class="is-flex is-align-items-center">
|
||||
<p class="subtitle">Playlists</p>
|
||||
</div>
|
||||
|
||||
<article class="panel is-primary" *ngFor="let playlist of playlists$ | async">
|
||||
<p class="panel-heading is-flex is-justify-content-space-between">
|
||||
<span>
|
||||
<i class="is-clickable fa-solid"
|
||||
[ngClass]="playlist.collapsed ? 'fa-caret-up' : 'fa-caret-down'"
|
||||
(click)="toggleCollapse(playlist.id)"
|
||||
></i>
|
||||
<span>{{playlist.name}}</span>
|
||||
</span>
|
||||
<a [href]="playlist.spotifyUrl" target="_blank" class="is-color-black"><i class="fa-brands fa-spotify"></i></a>
|
||||
</p>
|
||||
<ng-container *ngIf="playlist.collapsed">
|
||||
<app-track-list [playlistId]="playlist.id"></app-track-list>
|
||||
</ng-container>
|
||||
</article>
|
||||
<app-playlist-box *ngFor="let playlist of playlists$ | async" [playlist]="playlist"></app-playlist-box>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
.panel-heading {
|
||||
padding: 5px 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.loading-animation {
|
||||
animation: spin 0.3s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
100% {
|
||||
transform:rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { RouterOutlet } from '@angular/router';
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {CommonModule, NgFor, NgSwitch, NgSwitchCase} from "@angular/common";
|
||||
import {CommonModule, NgFor} from "@angular/common";
|
||||
import {PlaylistService} from "./services/playlist.service";
|
||||
import {TrackListComponent} from "./components/track-list/track-list.component";
|
||||
import {PlaylistBoxComponent} from "./components/playlist-box/playlist-box.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [CommonModule, RouterOutlet, FormsModule, NgFor, NgSwitch, NgSwitchCase, TrackListComponent],
|
||||
imports: [CommonModule, RouterOutlet, FormsModule, NgFor, PlaylistBoxComponent],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.scss'
|
||||
})
|
||||
export class AppComponent {
|
||||
|
||||
url = ''
|
||||
playlists$ = this.playlistService.all$;
|
||||
loading$ = this.playlistService.loading$;
|
||||
createLoading$ = this.playlistService.createLoading$;
|
||||
playlists$ = this.playlistService.all$;
|
||||
|
||||
constructor(private readonly playlistService: PlaylistService) {
|
||||
this.fetchPlaylists();
|
||||
@@ -28,10 +27,7 @@ export class AppComponent {
|
||||
}
|
||||
|
||||
download(): void {
|
||||
this.playlistService.create(this.url);
|
||||
}
|
||||
|
||||
toggleCollapse(playlistId: number): void {
|
||||
this.playlistService.toggleCollapsed(playlistId);
|
||||
this.url && this.playlistService.create(this.url);
|
||||
this.url = '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<article class="panel" [ngClass]="playlist.error ? 'is-danger' : 'is-primary'">
|
||||
<p class="panel-heading is-flex is-justify-content-space-between">
|
||||
<span>
|
||||
<i class="is-clickable fa-solid"
|
||||
[ngClass]="playlist.collapsed ? 'fa-caret-up' : 'fa-caret-down'"
|
||||
(click)="toggleCollapse(playlist.id)"
|
||||
></i>
|
||||
<span *ngIf="playlist.error; else noErrorTemplate" class="is-size-6 has-text-weight-medium">{{playlist.error}}</span>
|
||||
<ng-template #noErrorTemplate>
|
||||
<span class="is-size-6 has-text-weight-semibold">{{playlist.name}}</span>
|
||||
</ng-template>
|
||||
<a [href]="playlist.spotifyUrl" target="_blank" class="is-color-black"><i class="fa-brands fa-spotify"></i></a>
|
||||
</span>
|
||||
<span *ngIf="!playlist.error">
|
||||
<span class="is-size-6 has-text-weight-medium">{{trackCompletedCount$ | async}}/{{trackCount$ | async}}</span>
|
||||
</span>
|
||||
</p>
|
||||
<ng-container *ngIf="playlist.collapsed">
|
||||
<app-track-list [playlistId]="playlist.id"></app-track-list>
|
||||
</ng-container>
|
||||
</article>
|
||||
@@ -0,0 +1,8 @@
|
||||
.panel-heading {
|
||||
padding: 5px 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
margin: 5px 0;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PlaylistBoxComponent } from './playlist-box.component';
|
||||
|
||||
describe('PlaylistListComponent', () => {
|
||||
let component: PlaylistBoxComponent;
|
||||
let fixture: ComponentFixture<PlaylistBoxComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PlaylistBoxComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PlaylistBoxComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
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, PlaylistUi} from "../../services/playlist.service";
|
||||
import {Observable} from "rxjs";
|
||||
|
||||
@Component({
|
||||
selector: 'app-playlist-box',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
AsyncPipe,
|
||||
NgForOf,
|
||||
NgIf,
|
||||
TrackListComponent
|
||||
],
|
||||
templateUrl: './playlist-box.component.html',
|
||||
styleUrl: './playlist-box.component.scss'
|
||||
})
|
||||
export class PlaylistBoxComponent {
|
||||
|
||||
@Input() set playlist(val: Playlist & PlaylistUi) {
|
||||
this._playlist = val;
|
||||
this.trackCount$ = this.playlistService.getTrackCount(val.id);
|
||||
this.trackCompletedCount$ = this.playlistService.getCompletedTrackCount(val.id);
|
||||
}
|
||||
get playlist(): Playlist & PlaylistUi {
|
||||
return this._playlist;
|
||||
}
|
||||
_playlist!: Playlist & PlaylistUi;
|
||||
trackCount$!: Observable<number>;
|
||||
trackCompletedCount$!: Observable<number>;
|
||||
|
||||
constructor(private readonly playlistService: PlaylistService) { }
|
||||
|
||||
|
||||
toggleCollapse(playlistId: number): void {
|
||||
this.playlistService.toggleCollapsed(playlistId);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
withUIEntities
|
||||
} from "@ngneat/elf-entities";
|
||||
import {joinRequestResult, trackRequestResult} from "@ngneat/elf-requests";
|
||||
import {map, tap} from "rxjs";
|
||||
import {map, Observable, tap} from "rxjs";
|
||||
import {Track, TrackService} from "./track.service";
|
||||
import {Socket} from "ngx-socket-io";
|
||||
|
||||
@@ -20,8 +20,9 @@ const CREATE_LOADING = 'CREATE_LOADING';
|
||||
|
||||
export interface Playlist {
|
||||
id: number;
|
||||
name: string;
|
||||
name?: string;
|
||||
spotifyUrl: string;
|
||||
error?: string;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
@@ -61,6 +62,14 @@ export class PlaylistService {
|
||||
);
|
||||
}
|
||||
|
||||
getTrackCount(id: number): Observable<number> {
|
||||
return this.trackService.getAllByPlaylist(id).pipe(map(data => data.length));
|
||||
}
|
||||
|
||||
getCompletedTrackCount(id: number): Observable<number> {
|
||||
return this.trackService.getCompletedByPlaylist(id).pipe(map(data => data.length));
|
||||
}
|
||||
|
||||
fetch(): void {
|
||||
this.http.get<Playlist[]>(ENDPOINT).pipe(
|
||||
tap((data: Playlist[]) => this.store.update(
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import {createStore} from "@ngneat/elf";
|
||||
import {selectManyByPredicate, upsertEntities, withEntities} from "@ngneat/elf-entities";
|
||||
import {Socket} from "ngx-socket-io";
|
||||
import {tap} from "rxjs";
|
||||
import {map, Observable, tap} from "rxjs";
|
||||
import {trackRequestResult} from "@ngneat/elf-requests";
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
|
||||
@@ -39,10 +39,14 @@ export class TrackService {
|
||||
withEntities<Track>(),
|
||||
);
|
||||
|
||||
getAllByPlaylist(id: number) {
|
||||
getAllByPlaylist(id: number): Observable<Track[]> {
|
||||
return this.store.pipe(selectManyByPredicate(({playlistId}) => playlistId === id))
|
||||
}
|
||||
|
||||
getCompletedByPlaylist(id: number): Observable<Track[]> {
|
||||
return this.getAllByPlaylist(id).pipe(map(data => data.filter(item => item.status === TrackStatusEnum.Completed)));
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly http: HttpClient,
|
||||
private readonly socket: Socket,
|
||||
|
||||
Reference in New Issue
Block a user