playlist download progress, fix library error downloading
This commit is contained in:
Generated
+103
-19
@@ -2581,6 +2581,33 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@distube/ytdl-core": {
|
||||
"version": "4.13.5",
|
||||
"resolved": "https://registry.npmjs.org/@distube/ytdl-core/-/ytdl-core-4.13.5.tgz",
|
||||
"integrity": "sha512-g+4UJIR/auAJbia7iB0aWvaJDbs22P53NySWa47b1NT4xMTDJYguxHFArPrvRkcJrb/AgKjv/XoSZGghpL0CJA==",
|
||||
"dependencies": {
|
||||
"http-cookie-agent": "^6.0.5",
|
||||
"m3u8stream": "^0.8.6",
|
||||
"miniget": "^4.2.3",
|
||||
"sax": "^1.4.1",
|
||||
"tough-cookie": "^4.1.4",
|
||||
"undici": "^6.19.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/distubejs/ytdl-core?sponsor"
|
||||
}
|
||||
},
|
||||
"node_modules/@distube/ytdl-core/node_modules/undici": {
|
||||
"version": "6.19.2",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.19.2.tgz",
|
||||
"integrity": "sha512-JfjKqIauur3Q6biAtHJ564e3bWa8VvT+7cSiOJHFbX4Erv6CLGDpg8z+Fmg/1OI/47RA+GI2QZaF48SSaLvyBA==",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.3.tgz",
|
||||
@@ -6341,7 +6368,6 @@
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
|
||||
"integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
@@ -10493,6 +10519,29 @@
|
||||
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
|
||||
"devOptional": true
|
||||
},
|
||||
"node_modules/http-cookie-agent": {
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-6.0.5.tgz",
|
||||
"integrity": "sha512-sfZ8fDgDP3B1YB+teqSnAK1aPgBu8reUUGxSsndP2XnYN6cM29EURXWXZqQQiaRdor3B4QjpkUNfv21syaO4DA==",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/3846masa"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tough-cookie": "^4.0.0",
|
||||
"undici": "^5.11.0 || ^6.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"undici": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/http-deceiver": {
|
||||
"version": "1.2.7",
|
||||
"resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
|
||||
@@ -14673,6 +14722,11 @@
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
|
||||
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
@@ -14727,6 +14781,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/querystringify": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@@ -15005,8 +15064,7 @@
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.8",
|
||||
@@ -17162,6 +17220,36 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
|
||||
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
|
||||
"dependencies": {
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
"universalify": "^0.2.0",
|
||||
"url-parse": "^1.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tough-cookie/node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tough-cookie/node_modules/universalify": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
@@ -17646,7 +17734,7 @@
|
||||
"version": "6.18.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.18.0.tgz",
|
||||
"integrity": "sha512-nT8jjv/fE9Et1ilR6QoW8ingRTY2Pp4l2RUrdzV5Yz35RJDrtPc1DXvuNqcpsJSGIRHFdt3YKKktTzJA6r0fTA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
@@ -17790,6 +17878,15 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"dependencies": {
|
||||
"querystringify": "^2.1.1",
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
@@ -19039,19 +19136,6 @@
|
||||
"yt-search-video": "bin/mpv_video.sh"
|
||||
}
|
||||
},
|
||||
"node_modules/ytdl-core": {
|
||||
"version": "4.11.5",
|
||||
"resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-4.11.5.tgz",
|
||||
"integrity": "sha512-27LwsW4n4nyNviRCO1hmr8Wr5J1wLLMawHCQvH8Fk0hiRqrxuIu028WzbJetiYH28K8XDbeinYW4/wcHQD1EXA==",
|
||||
"dependencies": {
|
||||
"m3u8stream": "^0.8.6",
|
||||
"miniget": "^4.2.2",
|
||||
"sax": "^1.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/zone.js": {
|
||||
"version": "0.14.7",
|
||||
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.7.tgz",
|
||||
@@ -19061,6 +19145,7 @@
|
||||
"version": "0.0.1",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"@distube/ytdl-core": "^4.13.5",
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/config": "^3.2.2",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
@@ -19076,8 +19161,7 @@
|
||||
"rxjs": "^7.8.1",
|
||||
"spotify-url-info": "^3.2.15",
|
||||
"sqlite3": "^5.1.7",
|
||||
"yt-search": "^2.11.0",
|
||||
"ytdl-core": "^4.11.5"
|
||||
"yt-search": "^2.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@distube/ytdl-core": "^4.13.5",
|
||||
"@nestjs/common": "^10.0.0",
|
||||
"@nestjs/config": "^3.2.2",
|
||||
"@nestjs/core": "^10.0.0",
|
||||
@@ -35,8 +36,7 @@
|
||||
"rxjs": "^7.8.1",
|
||||
"spotify-url-info": "^3.2.15",
|
||||
"sqlite3": "^5.1.7",
|
||||
"yt-search": "^2.11.0",
|
||||
"ytdl-core": "^4.11.5"
|
||||
"yt-search": "^2.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.0.0",
|
||||
|
||||
@@ -7,12 +7,15 @@ export class PlaylistEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
@Column({ nullable: true })
|
||||
name?: string;
|
||||
|
||||
@Column()
|
||||
spotifyUrl: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
error?: string;
|
||||
|
||||
@Column({default: () => Date.now()})
|
||||
createdAt?: number;
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
export interface PlaylistModel {
|
||||
id: number;
|
||||
name: string;
|
||||
name?: string;
|
||||
spotifyUrl: string;
|
||||
tracks?: any[]; //todo fix it
|
||||
error?: string;
|
||||
createdAt?: number;
|
||||
}
|
||||
@@ -33,10 +33,17 @@ export class PlaylistService {
|
||||
}
|
||||
|
||||
async create(playlist: PlaylistEntity): Promise<void> {
|
||||
const details = await getDetails(playlist.spotifyUrl);
|
||||
const savedPlaylist = await this.repository.save({...playlist, name: details.preview.title});
|
||||
let details;
|
||||
let playlist2Save: PlaylistEntity;
|
||||
try {
|
||||
details = await getDetails(playlist.spotifyUrl);
|
||||
playlist2Save = {...playlist, name: details.preview.title};
|
||||
} catch (err) {
|
||||
playlist2Save = {...playlist, error: String(err)};
|
||||
}
|
||||
const savedPlaylist = await this.repository.save(playlist2Save);
|
||||
this.io.emit('playlistNew', savedPlaylist);
|
||||
for(let track of details.tracks) {
|
||||
for(let track of details?.tracks ?? []) {
|
||||
await this.trackService.create({
|
||||
artist: track.artist,
|
||||
song: track.name,
|
||||
|
||||
@@ -6,7 +6,7 @@ import {PlaylistEntity} from "../playlist/playlist.entity";
|
||||
import {Interval} from "@nestjs/schedule";
|
||||
import {TrackStatusEnum} from "./track.model";
|
||||
import * as yts from 'yt-search';
|
||||
import * as ytdl from 'ytdl-core';
|
||||
import * as ytdl from '@distube/ytdl-core';
|
||||
import * as fs from 'fs';
|
||||
import {ConfigService} from "@nestjs/config";
|
||||
import {resolve} from "path";
|
||||
|
||||
@@ -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