elf integration

This commit is contained in:
raiper34
2024-06-23 18:16:54 +02:00
parent 46793174ab
commit 903697646d
10 changed files with 222 additions and 29 deletions
+67 -1
View File
@@ -8,7 +8,17 @@
"workspaces": [
"spooty-be",
"spooty-fe"
]
],
"dependencies": {
"@ngneat/elf": "^2.5.1",
"@ngneat/elf-cli-ng": "^1.0.0",
"@ngneat/elf-devtools": "^1.3.0",
"@ngneat/elf-entities": "^5.0.2",
"@ngneat/elf-pagination": "^1.1.0",
"@ngneat/elf-persist-state": "^1.2.1",
"@ngneat/elf-requests": "^1.9.2",
"@ngneat/elf-state-history": "^1.4.0"
}
},
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
@@ -4603,6 +4613,62 @@
"typeorm": "^0.3.0"
}
},
"node_modules/@ngneat/elf": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@ngneat/elf/-/elf-2.5.1.tgz",
"integrity": "sha512-13BItNZFgHglTiXuP9XhisNczwQ5QSzH+imAv9nAPsdbCq/3ortqkIYRnlxB8DGPVcuIjLujQ4OcZa+9QWgZtw==",
"peerDependencies": {
"rxjs": ">=7.0.0"
}
},
"node_modules/@ngneat/elf-cli-ng": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@ngneat/elf-cli-ng/-/elf-cli-ng-1.0.0.tgz",
"integrity": "sha512-twAvWL5tEtBy/Q65kqoa5aaSE4kG1sFGLZH7QPJwLEBuFayDIzyhT/swJQVLZc+Dv6M4cdHpAyLAg3OfeuWd+Q=="
},
"node_modules/@ngneat/elf-devtools": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ngneat/elf-devtools/-/elf-devtools-1.3.0.tgz",
"integrity": "sha512-J9+4Vk/S/nKFGnXGZUDYrIx/K8Jfv4TLpzR3voBSOtSeq+c8Q0hdmo8iW7oaK6y5FWFDk0VJktlEh9cjylYxFg=="
},
"node_modules/@ngneat/elf-entities": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/@ngneat/elf-entities/-/elf-entities-5.0.2.tgz",
"integrity": "sha512-G4ag51lvM3tOSgpxVVFYAgsh/bOL5BkNb4Z0VtosaM/CTWTHoNrf8UdvcaeJ3+sP1RS3bmEdZ9xUE8ifnVxssA==",
"peerDependencies": {
"@ngneat/elf": ">=2.5.0",
"rxjs": ">=7.0.0"
}
},
"node_modules/@ngneat/elf-pagination": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@ngneat/elf-pagination/-/elf-pagination-1.1.0.tgz",
"integrity": "sha512-MhaeHxRXlLMDg4SIidWS/USr82l8tJ5/QKl8JbOTWDu6YAygVb2pwlXITPFG0Nk2dusRTt5/P2JcGdcxQK6//Q==",
"peerDependencies": {
"@ngneat/elf-entities": "*"
}
},
"node_modules/@ngneat/elf-persist-state": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@ngneat/elf-persist-state/-/elf-persist-state-1.2.1.tgz",
"integrity": "sha512-R+5IRLC35cDT403sSs37UeqqOpHNnCwHl3eicPv/Rc+JJ7Av1bcQClSF2mHC0jE4pkYmEuVghpzUntvmrKCCwA==",
"peerDependencies": {
"rxjs": ">=7.0.0"
}
},
"node_modules/@ngneat/elf-requests": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@ngneat/elf-requests/-/elf-requests-1.9.2.tgz",
"integrity": "sha512-DSlUVGlltkxcCjH0lWIjSJLz7SAR6ZdGtVwQumip7pXdxeSYmYFnqIwDObR4Cbd3Jbmx0jQs+C9VSbgTDhtdLA=="
},
"node_modules/@ngneat/elf-state-history": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@ngneat/elf-state-history/-/elf-state-history-1.4.0.tgz",
"integrity": "sha512-tjKGVfd96npOyxQUgMSzfeybtRxSlvCcIWCeMTeWOfwYyPO2Z3FjGIzDupDUZdV0MY/6jWOiCxP4aNhj0z6YRQ==",
"peerDependencies": {
"@ngneat/elf-entities": "*"
}
},
"node_modules/@ngtools/webpack": {
"version": "18.0.5",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.0.5.tgz",
+6 -2
View File
@@ -1,12 +1,16 @@
{
"name": "spooty",
"workspaces": ["spooty-be", "spooty-fe"],
"workspaces": [
"spooty-be",
"spooty-fe"
],
"scripts": {
"start:be": "npm run start:dev -w spooty-be",
"start:fe": "npm run start -w spooty-fe",
"build:be": "npm run build -w spooty-be",
"build:fe": "npm run build -w spooty-fe",
"build": "npm run build:be npm run build:fe",
"gen:fe": "npm run gen -w spooty-fe",
"start": "npm run start:prod -w spooty-be"
}
}
}
@@ -20,10 +20,9 @@ export class PlaylistController {
}
@Post()
create(@Body() playlist: PlaylistModel): any {
getDetails(playlist.spotifyUrl).then(async details => {
async create(@Body() playlist: PlaylistModel): Promise<void> {
await getDetails(playlist.spotifyUrl).then(async details => {
const savedPlaylist = await this.service.create({...playlist, name: details.preview.title});
console.log(savedPlaylist);
details.tracks.forEach(track => {
this.trackService.create({
artist: track.artist,
@@ -32,6 +31,5 @@ export class PlaylistController {
}, savedPlaylist);
});
});
//return {url: url.url};
}
}
+10 -1
View File
@@ -6,7 +6,8 @@
"start": "ng serve --proxy-config proxy.conf.json",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
"test": "ng test",
"gen": "ng generate"
},
"private": true,
"dependencies": {
@@ -19,6 +20,14 @@
"@angular/platform-browser-dynamic": "^18.0.0",
"@angular/router": "^18.0.0",
"@fortawesome/fontawesome-free": "^6.5.2",
"@ngneat/elf": "^2.5.1",
"@ngneat/elf-cli-ng": "^1.0.0",
"@ngneat/elf-devtools": "^1.3.0",
"@ngneat/elf-entities": "^5.0.2",
"@ngneat/elf-pagination": "^1.1.0",
"@ngneat/elf-persist-state": "^1.2.1",
"@ngneat/elf-requests": "^1.9.2",
"@ngneat/elf-state-history": "^1.4.0",
"bulma": "^1.0.1",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
+17 -8
View File
@@ -1,9 +1,10 @@
<section class="hero is-primary">
<div class="hero-body">
<p class="title">
<span><i class="fa-solid fa-music"></i> Spooty</span>
<i class="fa-brands fa-spotify"></i>
<span>Spooty</span>
</p>
<p class="subtitle"><i class="fa-solid fa-house"></i> Self-hosted <i class="fa-brands fa-spotify"></i> spotify <i class="fa-solid fa-download"></i>downloader</p>
<p class="subtitle">Self-hosted spotify downloader</p>
</div>
</section>
@@ -13,7 +14,7 @@
<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" (click)="download()">
<button class="button is-primary" [class.is-loading]="(createLoading$ | async)?.isLoading" (click)="download()">
<i class="fa-solid fa-download"></i> Download
</button>
</div>
@@ -21,18 +22,25 @@
<hr>
<div class="box">
<div class="is-flex is-align-items-center">
<p class="subtitle">List</p>
<button class="button" (click)="get()"><i class="fa-solid fa-arrows-rotate"></i> Refresh</button>
<p class="subtitle">List</p>&nbsp;
<i class="fa-solid fa-arrows-rotate is-clickable" [class.loading-animation]="(loading$ | async)?.isLoading" (click)="fetchPlaylists()"></i>
</div>
<article class="panel is-primary" *ngFor="let playlist of playlists">
<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="fa-solid fa-caret-down"></i> {{playlist.name}}</span>
<span>
<i class="is-clickable fa-solid"
[ngClass]="playlist.collapsed ? 'fa-caret-up' : 'fa-caret-down'"
(click)="toggleCollapse(playlist.id)"
></i>&nbsp;
<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">
<a class="panel-block is-flex is-justify-content-space-between" *ngFor="let track of playlist.tracks">
<div>
<a href="http://localhost:3000/track/download/{{track.id}}" class="panel-icon is-color-info" download>
<a href="api/track/download/{{track.id}}" class="panel-icon is-color-info" download>
<i class="fa-solid fa-download"></i>
</a>
<span>{{track.artist}} - {{track.song}}</span>&nbsp;
@@ -51,6 +59,7 @@
</ng-container>
</div>
</a>
</ng-container>
</article>
</div>
</div>
+15
View File
@@ -1,7 +1,22 @@
.panel-heading {
padding: 5px 10px;
border-radius: 10px;
}
.panel-block {
padding: 5px 10px;
}
.panel {
margin: 5px 0;
}
.loading-animation {
animation: spin 0.3s linear infinite;
}
@keyframes spin {
100% {
transform:rotate(360deg);
}
}
+15 -13
View File
@@ -1,34 +1,36 @@
import { Component } from '@angular/core';
import { RouterOutlet } from '@angular/router';
import {HttpClient} from "@angular/common/http";
import {FormsModule} from "@angular/forms";
import {NgFor, NgSwitch, NgSwitchCase} from "@angular/common";
import {CommonModule, NgFor, NgSwitch, NgSwitchCase} from "@angular/common";
import {PlaylistService} from "./services/playlist.service";
@Component({
selector: 'app-root',
standalone: true,
imports: [RouterOutlet, FormsModule, NgFor, NgSwitch, NgSwitchCase],
imports: [CommonModule, RouterOutlet, FormsModule, NgFor, NgSwitch, NgSwitchCase],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
url = ''
playlists: any[] = [];
tracks = [
{code: 'CC', name: 'Aaaa'},
{code: 'CrC', name: 'Aaaadfdfd'},
]
playlists$ = this.playlistService.all$;
loading$ = this.playlistService.loading$;
createLoading$ = this.playlistService.createLoading$;
constructor(private readonly http: HttpClient) {
this.get();
constructor(private readonly playlistService: PlaylistService) {
this.fetchPlaylists();
}
fetchPlaylists(): void {
this.playlistService.fetch();
}
download(): void {
this.http.post('/api/playlist', {spotifyUrl: this.url}).subscribe(() => this.get());
this.playlistService.create(this.url);
}
get(): void {
this.http.get('/api/playlist').subscribe(data => this.playlists = data as any);
toggleCollapse(playlistId: number): void {
this.playlistService.toggleCollapsed(playlistId);
}
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { PlaylistService } from './playlist.service';
describe('PlaylistService', () => {
let service: PlaylistService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(PlaylistService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,72 @@
import { Injectable } from '@angular/core';
import {createStore} from "@ngneat/elf";
import {HttpClient} from "@angular/common/http";
import {
addEntities,
selectAllEntities, selectEntities,
setEntities,
UIEntitiesRef,
unionEntities, updateEntities,
withEntities,
withUIEntities
} from "@ngneat/elf-entities";
import {joinRequestResult, trackRequestResult} from "@ngneat/elf-requests";
import {tap} from "rxjs";
const STORE_NAME = 'playlist';
const ENDPOINT = '/api/playlist';
const CREATE_LOADING = 'CREATE_LOADING';
export interface Playlist {
id: number;
name: string;
spotifyUrl: string;
tracks?: any[]; //todo fix it
}
export interface PlaylistUi {
id: number,
collapsed: boolean;
}
@Injectable({
providedIn: 'root'
})
export class PlaylistService {
private store = createStore(
{ name: STORE_NAME },
withEntities<Playlist>(),
withUIEntities<PlaylistUi>()
);
all$ = this.store.combine({
entities: this.store.pipe(selectAllEntities()),
UIEntities: this.store.pipe(selectEntities({ ref: UIEntitiesRef })),
}).pipe(unionEntities());
loading$ = this.store.pipe(joinRequestResult([STORE_NAME]));
createLoading$ = this.store.pipe(joinRequestResult([CREATE_LOADING], { initialStatus: 'idle' }));
constructor(private readonly http: HttpClient) {
}
fetch(): void {
this.http.get<Playlist[]>(ENDPOINT).pipe(
tap((data: Playlist[]) => this.store.update(
setEntities(data),
setEntities(data.map(item => ({id: item.id, collapsed: false})), {ref: UIEntitiesRef})
)),
trackRequestResult([STORE_NAME], { skipCache: true }),
).subscribe();
}
create(spotifyUrl: string): void {
this.http.post(ENDPOINT, {spotifyUrl}).pipe(
trackRequestResult([CREATE_LOADING], { skipCache: true })
).subscribe();
}
toggleCollapsed(id: number): void {
this.store.update(updateEntities(id, old => ({...old, collapsed: !old.collapsed}), { ref: UIEntitiesRef }))
}
}
+2
View File
@@ -1,6 +1,8 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
import { devTools } from '@ngneat/elf-devtools';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));
devTools();