🎨 Added working entity system

That took a loooong time.
This commit is contained in:
corner 2019-09-26 17:53:08 +02:00
parent 73cc6d7784
commit a59e935a1e
21 changed files with 340 additions and 79 deletions

43
package-lock.json generated
View File

@ -566,6 +566,14 @@
"semver-intersect": "1.4.0"
}
},
"@types/axios": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz",
"integrity": "sha1-7CMA++fX3d1+udOr+HmZlkyvzkY=",
"requires": {
"axios": "*"
}
},
"@types/events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
@ -1214,6 +1222,38 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true
},
"axios": {
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
"integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==",
"requires": {
"follow-redirects": "1.5.10",
"is-buffer": "^2.0.2"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"follow-redirects": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
"integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
"requires": {
"debug": "=3.1.0"
}
},
"is-buffer": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
"integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw=="
}
}
},
"axobject-query": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz",
@ -6034,8 +6074,7 @@
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
"dev": true
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"multicast-dns": {
"version": "6.2.3",

View File

@ -22,6 +22,8 @@
"@fortawesome/angular-fontawesome": "^0.5.0",
"@fortawesome/fontawesome-svg-core": "^1.2.24",
"@fortawesome/free-solid-svg-icons": "^5.11.1",
"@types/axios": "^0.14.0",
"axios": "^0.19.0",
"rxjs": "~6.4.0",
"tslib": "^1.9.0",
"zone.js": "~0.9.1"
@ -31,9 +33,9 @@
"@angular/cli": "~8.0.6",
"@angular/compiler-cli": "~8.0.3",
"@angular/language-service": "~8.0.3",
"@types/node": "~8.9.4",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^8.9.5",
"codelyzer": "^5.0.0",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",

View File

@ -1,7 +0,0 @@
import { Album } from './album';
describe('Album', () => {
it('should create an instance', () => {
expect(new Album()).toBeTruthy();
});
});

View File

@ -1,2 +0,0 @@
export class Album {
}

View File

@ -1,7 +0,0 @@
import { Artist } from './artist';
describe('Artist', () => {
it('should create an instance', () => {
expect(new Artist()).toBeTruthy();
});
});

View File

@ -1,2 +0,0 @@
export class Artist {
}

View File

@ -0,0 +1,74 @@
import { DataService } from '../services/data.service';
/** This class represents one song. */
export class Song {
// The following list are identical to the items returned from the api.
id: number;
name: string;
artist: number;
album: number;
path: string;
data: DataService;
constructor(data: any) {
this.id = data.data.result[0].id;
this.name = data.data.result[0].name;
this.artist = data.data.result[0].artist;
this.album = data.data.result[0].album;
this.path = data.data.result[0].path;
this.data = new DataService();
}
async getArtist() {
// tslint:disable-next-line: no-use-before-declare
return new Artist(await this.data.getArtist(this.artist));
}
}
/** This class represents one artist */
export class Artist {
// The following list are identical to the items returned from the api.
id: number;
name: string;
albums: Array<number>;
songs: Array<number>;
constructor(data: any) {
this.id = data.data.result[0].id;
this.name = data.data.result[0].name;
this.albums = data.data.result[0].album;
this.songs = data.data.result[0].song;
}
// There will be more functions here...
}
/** This class represents one album */
export class Album {
// The following list are identical to the items returned from the api.
id: number;
name: string;
artist: number;
image: string;
data: DataService;
constructor(data: any) {
this.id = data.data.result[0].id;
this.name = data.data.result[0].name;
this.artist = data.data.result[0].artist;
this.image = data.data.result[0].image;
}
async getArtist() {
return new Artist(await this.data.getArtist(this.artist));
}
// There will be more functions here...
}

View File

@ -1,7 +0,0 @@
import { Song } from './song';
describe('Song', () => {
it('should create an instance', () => {
expect(new Song()).toBeTruthy();
});
});

View File

@ -1,2 +0,0 @@
export class Song {
}

View File

@ -1,7 +1,7 @@
<ng-container *ngIf="( artists | async ) as artists; else artistInfo">
<ul>
<li *ngFor="let artist of artists.result">
<li *ngFor="let artist of artists">
<a [routerLink]="'/artists/' + artist.id">{{ artist.name }}</a>
</li>
</ul>
@ -12,22 +12,24 @@
<div *ngIf="( artistData | async ) as data">
<h1>{{ data.result[0].name }}'s profile</h1>
<h1>{{ data.name }}'s profile</h1>
<h4>{{ data.result[0].name }}'s albums</h4>
<h4>{{ data.name }}'s albums</h4>
<ul>
<li *ngFor="let album of data.result[0].albums">
{{ album.name }}
<li *ngFor="let album of data.albums">
{{ album }}
</li>
</ul>
<h4>{{ data.result[0].name }}'s songs</h4>
<h4>{{ data.name }}'s songs</h4>
<ul>
<li *ngFor="let song of data.result[0].songs">
{{ song.name }}
<li *ngFor="let song of data.songs">
{{ song }}
</li>
</ul>
{{ data | json}}
</div>
</ng-template>

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core';
import { DataService } from 'src/app/services/data.service';
import { ActivatedRoute } from '@angular/router';
import { ApiService } from 'src/app/services/api.service';
@Component({
selector: 'app-artists',
@ -13,7 +13,7 @@ export class ArtistsComponent implements OnInit {
artists;
constructor(
private data: DataService,
private api: ApiService,
private route: ActivatedRoute
) { }
@ -25,12 +25,12 @@ export class ArtistsComponent implements OnInit {
if (params.has('id')) {
// ... then get artist data ...
this.artistData = this.data.getArtist(params.get('id'));
this.artistData = this.api.getArtist(Number(params.get('id')));
} else {
// Otherwise, get all artists and list them out.
this.artists = this.data.getAllArtists();
this.artists = this.api.getAllArtists();
}

View File

@ -1,7 +1,7 @@
<div class="container">
<div *ngIf="( currentSong | async ) as song" class="songInfo">
{{ song.result[0].name }}
{{ song.name }}
</div>
<div class="buttons">

View File

@ -1,8 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { faPlay, faPause } from '@fortawesome/free-solid-svg-icons';
import { AudioService } from 'src/app/services/audio.service';
import { DataService } from 'src/app/services/data.service';
import { Observable } from 'rxjs';
import { Song } from '../../classes/entities';
import { ApiService } from 'src/app/services/api.service';
@Component({
selector: 'app-controls',
@ -15,16 +15,16 @@ export class ControlsComponent implements OnInit {
faPlay = faPlay;
faPause = faPause;
currentSong: Observable<any>;
currentSong: Promise<Song>;
constructor(
public audio: AudioService,
private data: DataService
private api: ApiService
) { }
ngOnInit() {
this.audio.currentSong.subscribe(id => {
this.currentSong = this.data.getSong(id);
this.currentSong = this.api.getSong(id);
});
}

View File

@ -1,3 +1,3 @@
<p *ngIf="( song | async ) as song">
<span (click)="audio.setSong(song.result[0].id)">{{ song.result[0].name }}</span>
</p>
<div *ngIf="songs && artists">
<p *ngFor="let song of songs; let i=index" (click)="audio.setSong(song)">{{ song.name }} by <span *ngIf="artists[i]">{{ artists[i].name }}</span></p>
</div>

View File

@ -1,7 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { DataService } from 'src/app/services/data.service';
import { Observable } from 'rxjs';
import { AudioService } from 'src/app/services/audio.service';
import { Song, Artist } from 'src/app/classes/entities';
import { ApiService } from 'src/app/services/api.service';
@Component({
selector: 'app-home',
@ -10,16 +10,23 @@ import { AudioService } from 'src/app/services/audio.service';
})
export class HomeComponent implements OnInit {
song: Observable<object>;
songs: Song[];
artists: Artist[] = [];
constructor(
private data: DataService,
private api: ApiService,
public audio: AudioService
) { }
ngOnInit() {
// Load the first song from the API
this.song = this.data.getSong(0);
// Removing the async / await from the .then() callback will load the artists async from the songs.
this.api.getSong('all').then(async res => {
this.songs = res;
await res.forEach(async (song: Song) => {
this.artists.push(await song.getArtist());
});
});
}
}

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { ApiService } from './api.service';
describe('ApiService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: ApiService = TestBed.get(ApiService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,73 @@
import { Injectable } from '@angular/core';
import { DataService } from './data.service';
import { EntityService } from './entity.service';
/** This class contains helper functions to retreive data from the api. */
@Injectable({
providedIn: 'root'
})
export class ApiService {
constructor(
private data: DataService,
private entityService: EntityService
) { }
/**
* This function gets one or multiple songs from the api's database.
* @param id The id of the song. Either a number, a string with numbers by commas or the string 'all'.
* @returns a promise. When resolved it contains either one Song entity or an array of Song entities.
*/
async getSong(id: number | string): Promise<any> {
// Retreive data from the api...
const data = await this.data.getSong(id);
// ... and convert that data to entities.
return this.entityService.createEntityFromData(data);
}
/**
* This function gets one or multiple artists from the api's database.
* @param id The id of the song. Either a number, a string with numbers by commas or the string 'all'.
* @returns a promise. When resolved it contains either one Artist entity or an array of Artist entities.
*/
async getArtist(id: number | string): Promise<any> {
// Retreive data from the api...
const data = await this.data.getArtist(id);
// ... and convert that data to entities.
return this.entityService.createEntityFromData(data);
}
/**
* This function gets one or multiple albums from the api's database.
* @param id The id of the song. Either a number, a string with numbers by commas or the string 'all'.
* @returns a promise. When resolved it contains either one Album entity or an array of Album entities.
*/
async getAlbum(id: number | string): Promise<any> {
// Retreive data from the api...
const data = await this.data.getAlbum(id);
// ... and convert that data to entities.
return this.entityService.createEntityFromData(data);
}
/**
* This function gets all albums from the api's database.
* @returns a promise. When resolved it contains either one Album entity or an array of Album entities.
*/
async getAllAlbums(): Promise<any> {
// Retreive data from the api...
const data = await this.data.getAllAlbums();
// ... and convert that data to entities.
return this.entityService.createEntityFromData(data);
}
/**
* This function gets all albums from the api's database.
* @returns a promise. When resolved it contains either one Album entity or an array of Album entities.
*/
async getAllArtists(): Promise<any> {
// Retreive data from the api...
const data = await this.data.getAllArtists();
// ... and convert that data to entities.
return this.entityService.createEntityFromData(data);
}
}

View File

@ -1,6 +1,7 @@
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { DataService } from './data.service';
import { Song } from '../classes/entities';
/**
* The audio service handles all logic that has to do with the audio player.
@ -41,11 +42,8 @@ export class AudioService {
* The setSong function set the song for the player.
* @param id The id of the song that needs to be played
*/
setSong(id: number) {
this.data.getSong(id).subscribe((res: any) => {
console.log(this.data.apiUrl + '/play/' + res.result[0].path);
this.player.src = this.data.apiUrl + '/play/' + res.result[0].path;
this.currentSong.next(id);
});
setSong(song: Song) {
this.player.src = this.data.apiUrl + '/play/' + song.path;
this.currentSong.next(song.id);
}
}

View File

@ -1,6 +1,10 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import axios, { AxiosResponse } from 'axios';
import { AxiosInstance } from 'axios';
export interface ApiData {
result: any;
}
/**
* The DataService handles all data traffic to and from the API.
@ -13,56 +17,64 @@ import { Observable } from 'rxjs';
})
export class DataService {
// This variable could even be moved elsewhere.
public apiUrl = 'http://localhost:673';
constructor(private http: HttpClient) { }
/** This variable will store the axios http client. */
private axiosInstance: AxiosInstance;
constructor() {
this.axiosInstance = axios.create({
timeout: 3000
});
}
/**
* @param type The type of the thing you want to get. The type is a string containing either 'artist', 'album' or 'song'.
* @param id The id of the artist. It can be a number representing the id, multiple numbers seperated by commas or the string 'all'.
* @returns An Observable<object> containing the returned data from the API.
* @returns A Promise<AxiosResponse<object>> containing the returned data from the API.
*/
get(type: string, id: string | number): Observable<object> {
return this.http.get(`${this.apiUrl}/get/${type}/${id}`);
get(type: string, id: string | number): Promise<AxiosResponse<ApiData>> {
return this.axiosInstance.get(`${this.apiUrl}/get/${type}/${id}`);
}
/**
* @param id The id of the artist. It can be a number representing the id, multiple numbers seperated by commas or the string 'all'.
* @returns An Observable<object> containing the returned data from the API.
* @returns A Promise<AxiosResponse<object>> containing the returned data from the API.
*/
getSong(id: number | string): Observable<object> {
getSong(id: number | string): Promise<AxiosResponse<ApiData>> {
return this.get('song', id);
}
/**
* @param id The id of the artist. It can be a number representing the id, multiple numbers seperated by commas or the string 'all'.
* @returns An Observable<object> containing the returned data from the API.
* @returns A Promise<AxiosResponse<object>> containing the returned data from the API.
*/
getAlbum(id: number | string): Observable<object> {
getAlbum(id: number | string): Promise<AxiosResponse<ApiData>> {
return this.get('album', id);
}
/**
* A function to get one, multiple or all artists.
* @param id The id of the artist. It can be a number representing the id, multiple numbers seperated by commas or the string 'all'.
* @returns An Observable<object> containing the returned data from the API.
* @returns A Promise<AxiosResponse<object>> containing the returned data from the API.
*/
getArtist(id: number | string): Observable<object> {
return this.get('artist', id);
getArtist(id: number | string): Promise<AxiosResponse<ApiData>> {
return this.get('artist', id);
}
/**
* A function to get all albums from the API.
* @returns An Observable<object> containing the returned data from the API.
* @returns A Promise<AxiosResponse<object>> containing the returned data from the API.
*/
getAllAlbums(): Observable<object> {
getAllAlbums(): Promise<AxiosResponse<ApiData>> {
return this.get('album', 'all');
}
/**
* A function to get all artists from the API.
* @returns An Observable<object> containing the returned data from the API.
* @returns A Promise<AxiosResponse<object>> containing the returned data from the API.
*/
getAllArtists(): Observable<object> {
getAllArtists(): Promise<AxiosResponse<ApiData>> {
return this.get('artist', 'all');
}
}

View File

@ -0,0 +1,12 @@
import { TestBed } from '@angular/core/testing';
import { EntityService } from './entity.service';
describe('EntityService', () => {
beforeEach(() => TestBed.configureTestingModule({}));
it('should be created', () => {
const service: EntityService = TestBed.get(EntityService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,57 @@
import { Injectable } from '@angular/core';
import { Song, Album, Artist } from '../classes/entities';
@Injectable({
providedIn: 'root'
})
export class EntityService {
constructor() { }
/**
* This function takes api data and converts it into entities.
* @param data The data from the api.
* @returns either an array of entities or a single entity.
*/
createEntityFromData(data: any) {
// If data child is present...
if (data.data) {
// Prepare array for elements to be returned.
const result = [];
// For every entity in the api data.
data.data.result.forEach(element => {
// If the type is...
switch (element.type) {
// ... a song, add a song entity to the result array.
case 'song':
result.push(new Song({data: {result: [element]}}));
break;
// ... an artist, add an artist entity to the result array.
case 'artist':
result.push(new Artist({ data: { result: [element] } }));
break;
// ... an album, add an album entity to the result array.
case 'album':
result.push(new Album({ data: { result: [element] } }));
break;
// .. anything else, just ignore it.
default:
break;
}
}); // end result.forEach
// Return the result array, if it has more than one item.
return result.length > 1 ? result : result[0];
} else {
// ? Make sure that the data is in the correct format.
}
}
}