├── src ├── assets │ ├── .gitkeep │ └── images │ │ └── img-broken.png ├── app │ ├── app.component.css │ ├── app.component.html │ ├── shared │ │ ├── components │ │ │ ├── header-user │ │ │ │ ├── header-user.component.css │ │ │ │ ├── header-user.component.html │ │ │ │ ├── header-user.component.ts │ │ │ │ └── header-user.component.spec.ts │ │ │ ├── play-list-header │ │ │ │ ├── play-list-header.component.ts │ │ │ │ ├── play-list-header.component.html │ │ │ │ ├── play-list-header.component.spec.ts │ │ │ │ └── play-list-header.component.css │ │ │ ├── section-generic │ │ │ │ ├── section-generic.component.html │ │ │ │ ├── section-generic.component.css │ │ │ │ ├── section-generic.component.ts │ │ │ │ └── section-generic.component.spec.ts │ │ │ ├── card-player │ │ │ │ ├── card-player.component.spec.ts │ │ │ │ ├── card-player.component.ts │ │ │ │ ├── card-player.component.html │ │ │ │ └── card-player.component.css │ │ │ ├── media-player │ │ │ │ ├── media-player.component.spec.ts │ │ │ │ ├── media-player.component.ts │ │ │ │ ├── media-player.component.html │ │ │ │ └── media-player.component.css │ │ │ ├── play-list-body │ │ │ │ ├── play-list-body.component.ts │ │ │ │ ├── play-list-body.component.spec.ts │ │ │ │ ├── play-list-body.component.css │ │ │ │ └── play-list-body.component.html │ │ │ └── side-bar │ │ │ │ ├── side-bar.component.spec.ts │ │ │ │ ├── side-bar.component.css │ │ │ │ ├── side-bar.component.html │ │ │ │ └── side-bar.component.ts │ │ ├── services │ │ │ ├── multimedia.service.spec.ts │ │ │ └── multimedia.service.ts │ │ ├── directives │ │ │ ├── img-broken.directive.spec.ts │ │ │ └── img-broken.directive.ts │ │ ├── pipe │ │ │ ├── order-list.pipe.ts │ │ │ └── order-list.pipe.spec.ts │ │ └── shared.module.ts │ ├── modules │ │ ├── favorites │ │ │ ├── pages │ │ │ │ └── favorite-page │ │ │ │ │ ├── favorite-page.component.css │ │ │ │ │ ├── favorite-page.component.html │ │ │ │ │ ├── favorite-page.component.ts │ │ │ │ │ └── favorite-page.component.spec.ts │ │ │ ├── favorites-routing.module.ts │ │ │ └── favorites.module.ts │ │ ├── tracks │ │ │ ├── pages │ │ │ │ └── tracks-page │ │ │ │ │ ├── tracks-page.component.css │ │ │ │ │ ├── tracks-page.component.html │ │ │ │ │ ├── tracks-page.component.spec.ts │ │ │ │ │ └── tracks-page.component.ts │ │ │ ├── tracks-routing.module.ts │ │ │ ├── services │ │ │ │ ├── track.service.spec.ts │ │ │ │ └── track.service.ts │ │ │ └── tracks.module.ts │ │ ├── history │ │ │ ├── pages │ │ │ │ └── history-page │ │ │ │ │ ├── history-page.component.css │ │ │ │ │ ├── history-page.component.html │ │ │ │ │ ├── history-page.component.ts │ │ │ │ │ └── history-page.component.spec.ts │ │ │ ├── components │ │ │ │ └── search │ │ │ │ │ ├── search.component.html │ │ │ │ │ ├── search.component.css │ │ │ │ │ ├── search.component.ts │ │ │ │ │ └── search.component.spec.ts │ │ │ ├── history-routing.module.ts │ │ │ ├── services │ │ │ │ ├── search.service.spec.ts │ │ │ │ └── search.service.ts │ │ │ └── history.module.ts │ │ ├── home │ │ │ ├── pages │ │ │ │ └── home-page │ │ │ │ │ ├── home-page.component.html │ │ │ │ │ ├── home-page.component.css │ │ │ │ │ ├── home-page.component.ts │ │ │ │ │ └── home-page.component.spec.ts │ │ │ ├── home.module.ts │ │ │ └── home-routing.module.ts │ │ └── auth │ │ │ ├── auth.module.ts │ │ │ ├── auth-routing.module.ts │ │ │ ├── services │ │ │ ├── auth.service.ts │ │ │ └── auth.service.spec.ts │ │ │ └── pages │ │ │ └── login-page │ │ │ ├── login-page.component.html │ │ │ ├── login-page.component.ts │ │ │ ├── login-page.component.spec.ts │ │ │ └── login-page.component.css │ ├── core │ │ ├── models │ │ │ ├── artist.model.ts │ │ │ └── tracks.model.ts │ │ ├── interceptors │ │ │ ├── inject-session.interceptor.spec.ts │ │ │ └── inject-session.interceptor.ts │ │ └── guards │ │ │ ├── session.guard.spec.ts │ │ │ └── session.guard.ts │ ├── data │ │ ├── user.json │ │ └── tracks.json │ ├── app.component.ts │ ├── app.component.spec.ts │ ├── app-routing.module.ts │ └── app.module.ts ├── favicon.ico ├── environments │ ├── environment.prod.ts │ └── environment.ts ├── main.ts ├── test.ts ├── index.html ├── styles.css └── polyfills.ts ├── .editorconfig ├── tsconfig.app.json ├── tsconfig.spec.json ├── .gitignore ├── README.md ├── tsconfig.json ├── package.json ├── karma.conf.js └── angular.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/shared/components/header-user/header-user.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/modules/favorites/pages/favorite-page/favorite-page.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/shared/components/header-user/header-user.component.html: -------------------------------------------------------------------------------- 1 |

header-user works!

2 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leifermendez/angular-spotify/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/app/modules/tracks/pages/tracks-page/tracks-page.component.css: -------------------------------------------------------------------------------- 1 | .tracks-page { 2 | padding: 1rem; 3 | } -------------------------------------------------------------------------------- /src/app/modules/history/pages/history-page/history-page.component.css: -------------------------------------------------------------------------------- 1 | .history-page { 2 | padding: .65rem 0; 3 | } -------------------------------------------------------------------------------- /src/assets/images/img-broken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leifermendez/angular-spotify/HEAD/src/assets/images/img-broken.png -------------------------------------------------------------------------------- /src/app/core/models/artist.model.ts: -------------------------------------------------------------------------------- 1 | export interface ArtistModel { 2 | name: string; 3 | nickname: string; 4 | nationality: string 5 | } 6 | -------------------------------------------------------------------------------- /src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | api: 'https://api-spotify-leifer.herokuapp.com/api/1.0' 4 | }; 5 | -------------------------------------------------------------------------------- /src/app/modules/favorites/pages/favorite-page/favorite-page.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
-------------------------------------------------------------------------------- /src/app/modules/home/pages/home-page/home-page.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
-------------------------------------------------------------------------------- /src/app/data/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "userFail": { 3 | "email": "0x0x0x0x0x0", 4 | "password": "------------" 5 | }, 6 | "userOk": { 7 | "email": "test@test.com", 8 | "password": "12345678" 9 | } 10 | } -------------------------------------------------------------------------------- /src/app/modules/history/pages/history-page/history-page.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |
-------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | 10 | 11 | 12 | } -------------------------------------------------------------------------------- /src/app/modules/home/pages/home-page/home-page.component.css: -------------------------------------------------------------------------------- 1 | .home-wrapper { 2 | margin: 0 0 0 17.5rem; 3 | background: rgb(23, 23, 26); 4 | background: linear-gradient(90deg, rgba(23, 23, 26, 1) 44%, rgba(24, 24, 24, 1) 100%); 5 | min-height: 100vh; 6 | } -------------------------------------------------------------------------------- /src/app/modules/history/components/search/search.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
Resultado: {{src}}
5 |
-------------------------------------------------------------------------------- /src/app/core/models/tracks.model.ts: -------------------------------------------------------------------------------- 1 | import { ArtistModel } from "./artist.model"; 2 | 3 | export interface TrackModel { 4 | name: string; 5 | album: string; 6 | cover: string; 7 | url: string; 8 | _id: string | number; 9 | artist?: ArtistModel; 10 | } -------------------------------------------------------------------------------- /src/app/modules/history/components/search/search.component.css: -------------------------------------------------------------------------------- 1 | .search-component { 2 | padding: 0 1.5rem; 3 | } 4 | 5 | .search-component input { 6 | border: 0; 7 | border-radius: 3rem; 8 | height: 40px; 9 | width: 20rem; 10 | padding: 0 .75rem; 11 | font-size: 90%; 12 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts", 10 | "src/polyfills.ts" 11 | ], 12 | "include": [ 13 | "src/**/*.d.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /src/app/modules/home/pages/home-page/home-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-home-page', 5 | templateUrl: './home-page.component.html', 6 | styleUrls: ['./home-page.component.css'] 7 | }) 8 | export class HomePageComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/shared/components/header-user/header-user.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-header-user', 5 | templateUrl: './header-user.component.html', 6 | styleUrls: ['./header-user.component.css'] 7 | }) 8 | export class HeaderUserComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /src/app/modules/favorites/pages/favorite-page/favorite-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-favorite-page', 5 | templateUrl: './favorite-page.component.html', 6 | styleUrls: ['./favorite-page.component.css'] 7 | }) 8 | export class FavoritePageComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/modules/tracks/pages/tracks-page/tracks-page.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
-------------------------------------------------------------------------------- /src/app/shared/components/play-list-header/play-list-header.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-play-list-header', 5 | templateUrl: './play-list-header.component.html', 6 | styleUrls: ['./play-list-header.component.css'] 7 | }) 8 | export class PlayListHeaderComponent implements OnInit { 9 | 10 | constructor() { } 11 | 12 | ngOnInit(): void { 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/app/shared/services/multimedia.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { MultimediaService } from './multimedia.service'; 4 | 5 | describe('MultimediaService', () => { 6 | let service: MultimediaService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({}); 10 | service = TestBed.inject(MultimediaService); 11 | }); 12 | 13 | it('should be created', () => { 14 | expect(service).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/modules/tracks/tracks-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { TracksPageComponent } from './pages/tracks-page/tracks-page.component'; 2 | import { NgModule } from '@angular/core'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', 8 | component: TracksPageComponent 9 | } 10 | ]; 11 | 12 | @NgModule({ 13 | imports: [RouterModule.forChild(routes)], 14 | exports: [RouterModule] 15 | }) 16 | export class TracksRoutingModule { } 17 | -------------------------------------------------------------------------------- /src/app/modules/history/history-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { HistoryPageComponent } from './pages/history-page/history-page.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', 8 | component: HistoryPageComponent 9 | } 10 | ]; 11 | 12 | @NgModule({ 13 | imports: [RouterModule.forChild(routes)], 14 | exports: [RouterModule] 15 | }) 16 | export class HistoryRoutingModule { } 17 | -------------------------------------------------------------------------------- /src/app/modules/favorites/favorites-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { FavoritePageComponent } from './pages/favorite-page/favorite-page.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: '', 8 | component: FavoritePageComponent 9 | } 10 | ]; 11 | 12 | @NgModule({ 13 | imports: [RouterModule.forChild(routes)], 14 | exports: [RouterModule] 15 | }) 16 | export class FavoritesRoutingModule { } 17 | -------------------------------------------------------------------------------- /src/app/shared/components/section-generic/section-generic.component.html: -------------------------------------------------------------------------------- 1 |
2 |

{{title}}

3 | 4 |
5 | 10 |
11 |
-------------------------------------------------------------------------------- /src/app/shared/components/play-list-header/play-list-header.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |
7 |
8 |
Playlist
9 |

Canciones que te gustan

10 |
Leifer Jesus Mendez Zambrano 206 canciones
11 |
12 |
-------------------------------------------------------------------------------- /src/app/shared/directives/img-broken.directive.spec.ts: -------------------------------------------------------------------------------- 1 | import { ElementRef } from '@angular/core'; 2 | import { ImgBrokenDirective } from './img-broken.directive'; 3 | 4 | 5 | //TODO La prueba de ImgBroken es la siguiente 6 | 7 | describe('ImgBrokenDirective', () => { 8 | 9 | //TODO Deberia instanciar correctamente 10 | it('should create an instance', () => { 11 | const mockElement = new ElementRef('') 12 | const directive = new ImgBrokenDirective(mockElement); 13 | expect(directive).toBeTruthy(); 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/shared/components/section-generic/section-generic.component.css: -------------------------------------------------------------------------------- 1 | .section--generic { 2 | margin-bottom: 2rem; 3 | } 4 | 5 | .section--generic__player--zone .player--list { 6 | display: grid; 7 | list-style: none; 8 | margin: 0; 9 | padding: 0; 10 | gap: 1.15rem; 11 | } 12 | 13 | .section--generic__player--zone .player--list.card-small { 14 | grid-template-columns: repeat(5, 1fr); 15 | } 16 | 17 | .section--generic__player--zone .player--list.card-big { 18 | grid-template-columns: repeat(6, minmax(100px, 1fr)); 19 | } -------------------------------------------------------------------------------- /src/app/modules/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { HomeRoutingModule } from './home-routing.module'; 5 | import { HomePageComponent } from './pages/home-page/home-page.component'; 6 | import { SharedModule } from '@shared/shared.module'; 7 | 8 | 9 | @NgModule({ 10 | declarations: [ 11 | HomePageComponent 12 | ], 13 | imports: [ 14 | CommonModule, 15 | HomeRoutingModule, 16 | SharedModule 17 | ] 18 | }) 19 | export class HomeModule { } 20 | -------------------------------------------------------------------------------- /src/app/modules/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { AuthRoutingModule } from './auth-routing.module'; 5 | import { LoginPageComponent } from './pages/login-page/login-page.component'; 6 | import { ReactiveFormsModule } from '@angular/forms'; 7 | 8 | 9 | @NgModule({ 10 | declarations: [ 11 | LoginPageComponent 12 | ], 13 | imports: [ 14 | CommonModule, 15 | AuthRoutingModule, 16 | ReactiveFormsModule 17 | ] 18 | }) 19 | export class AuthModule { } 20 | -------------------------------------------------------------------------------- /src/app/shared/directives/img-broken.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, HostListener, Input } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: 'img[appImgBroken]' 5 | }) 6 | export class ImgBrokenDirective { 7 | @Input() customImg: string = '' 8 | @HostListener('error') handleError(): void { 9 | const elNative = this.elHost.nativeElement 10 | console.log('🔴 Esta imagen revento -->', this.elHost); 11 | elNative.src = this.customImg 12 | 13 | } 14 | 15 | constructor(private elHost: ElementRef) { 16 | 17 | 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | // First, initialize the Angular testing environment. 11 | getTestBed().initTestEnvironment( 12 | BrowserDynamicTestingModule, 13 | platformBrowserDynamicTesting(), 14 | { teardown: { destroyAfterEach: true }}, 15 | ); 16 | -------------------------------------------------------------------------------- /src/app/core/interceptors/inject-session.interceptor.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | 3 | import { InjectSessionInterceptor } from './inject-session.interceptor'; 4 | 5 | describe('InjectSessionInterceptor', () => { 6 | beforeEach(() => TestBed.configureTestingModule({ 7 | providers: [ 8 | InjectSessionInterceptor 9 | ] 10 | })); 11 | 12 | it('should be created', () => { 13 | const interceptor: InjectSessionInterceptor = TestBed.inject(InjectSessionInterceptor); 14 | expect(interceptor).toBeTruthy(); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /src/app/modules/tracks/services/track.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { HttpClientTestingModule } from '@angular/common/http/testing' 3 | import { TrackService } from './track.service'; 4 | 5 | describe('TrackService', () => { 6 | let service: TrackService; 7 | 8 | beforeEach(() => { 9 | TestBed.configureTestingModule({ 10 | imports: [HttpClientTestingModule] 11 | }); 12 | service = TestBed.inject(TrackService); 13 | }); 14 | 15 | it('should be created', () => { 16 | expect(service).toBeTruthy(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /src/app/modules/favorites/favorites.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { FavoritesRoutingModule } from './favorites-routing.module'; 5 | import { FavoritePageComponent } from './pages/favorite-page/favorite-page.component'; 6 | import { SharedModule } from '@shared/shared.module'; 7 | 8 | 9 | @NgModule({ 10 | declarations: [ 11 | FavoritePageComponent 12 | ], 13 | imports: [ 14 | CommonModule, 15 | FavoritesRoutingModule, 16 | SharedModule 17 | ] 18 | }) 19 | export class FavoritesModule { } 20 | -------------------------------------------------------------------------------- /src/app/modules/auth/auth-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | import { LoginPageComponent } from './pages/login-page/login-page.component'; 4 | 5 | const routes: Routes = [ 6 | { 7 | path: 'login',//TODO http://localhost:4200/auth/login 8 | component: LoginPageComponent 9 | }, 10 | { 11 | path: '**', 12 | redirectTo: '/auth/login' 13 | } 14 | ]; 15 | 16 | @NgModule({ 17 | imports: [RouterModule.forChild(routes)], 18 | exports: [RouterModule] 19 | }) 20 | export class AuthRoutingModule { } 21 | -------------------------------------------------------------------------------- /src/app/modules/history/services/search.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { TestBed } from '@angular/core/testing'; 3 | 4 | import { SearchService } from './search.service'; 5 | 6 | describe('SearchService', () => { 7 | let service: SearchService; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | imports: [HttpClientTestingModule] 12 | }); 13 | service = TestBed.inject(SearchService); 14 | }); 15 | 16 | it('should be created', () => { 17 | expect(service).toBeTruthy(); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/app/modules/tracks/tracks.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { HttpClientModule } from '@angular/common/http'; 4 | import { TracksRoutingModule } from './tracks-routing.module'; 5 | import { TracksPageComponent } from './pages/tracks-page/tracks-page.component'; 6 | import { SharedModule } from '@shared/shared.module'; 7 | 8 | 9 | @NgModule({ 10 | declarations: [ 11 | TracksPageComponent 12 | ], 13 | imports: [ 14 | CommonModule, 15 | TracksRoutingModule, 16 | SharedModule 17 | ] 18 | }) 19 | export class TracksModule { } 20 | -------------------------------------------------------------------------------- /src/app/shared/components/section-generic/section-generic.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { TrackModel } from '@core/models/tracks.model'; 3 | 4 | @Component({ 5 | selector: 'app-section-generic', 6 | templateUrl: './section-generic.component.html', 7 | styleUrls: ['./section-generic.component.css'] 8 | }) 9 | export class SectionGenericComponent implements OnInit { 10 | @Input() title: string = '' 11 | @Input() mode: 'small' | 'big' = 'big' 12 | @Input() dataTracks: Array = [] 13 | 14 | constructor() { } 15 | 16 | ngOnInit(): void { 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/app/modules/history/services/search.service.ts: -------------------------------------------------------------------------------- 1 | import { environment } from './../../../../environments/environment'; 2 | import { Injectable } from '@angular/core'; 3 | import { HttpClient } from '@angular/common/http'; 4 | import { Observable } from 'rxjs'; 5 | import { map } from 'rxjs/operators'; 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class SearchService { 11 | private readonly URL = environment.api 12 | 13 | constructor(private http: HttpClient) { } 14 | 15 | searchTracks$(term: string): Observable { 16 | return this.http.get(`${this.URL}/tracks?src=${term}`) 17 | .pipe( 18 | map((dataRaw: any) => dataRaw.data) 19 | ) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | 7 | beforeEach(async () => { 8 | await TestBed.configureTestingModule({ 9 | imports: [ 10 | RouterTestingModule 11 | ], 12 | declarations: [ 13 | AppComponent 14 | ], 15 | }).compileComponents(); 16 | }); 17 | 18 | 19 | it('should create the app', () => { 20 | const fixture = TestBed.createComponent(AppComponent); 21 | const app = fixture.componentInstance; 22 | expect(app).toBeTruthy(); 23 | }); 24 | 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /src/app/modules/history/components/search/search.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, OnInit, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-search', 5 | templateUrl: './search.component.html', 6 | styleUrls: ['./search.component.css'] 7 | }) 8 | export class SearchComponent implements OnInit { 9 | 10 | @Output() callbackData: EventEmitter = new EventEmitter() 11 | 12 | src: string = '' 13 | 14 | constructor() { } 15 | 16 | ngOnInit(): void { 17 | } 18 | 19 | callSearch(term: string): void { 20 | if (term.length >= 3) { 21 | this.callbackData.emit(term) 22 | console.log('🔴 Llamamos a nuestra API HTTP GET---> ', term); 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Spotify (APP) 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/app/core/guards/session.guard.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | 4 | import { SessionGuard } from './session.guard'; 5 | 6 | //TODO:🔴🔴 Es el nombre de la prueba "Examen del Session Guard" 7 | describe('Testing of Session Guard 👍', () => { 8 | let guard: SessionGuard; 9 | 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({ 12 | imports: [ 13 | RouterTestingModule 14 | ] 15 | }); 16 | guard = TestBed.inject(SessionGuard); 17 | }); 18 | 19 | //TODO La primera pregunta de ese gran examen! 20 | it('should be created', () => { 21 | expect(guard).toBeTruthy(); //TODO 🤬🤬🤬 22 | }); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /src/app/modules/auth/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { environment } from './../../../../environments/environment'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Injectable } from '@angular/core'; 4 | import { Observable } from 'rxjs'; 5 | 6 | 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class AuthService { 11 | private readonly URL = environment.api 12 | constructor(private http: HttpClient) { } 13 | 14 | sendCredentials(email: string, password: string): Observable { 15 | const body = { 16 | email, 17 | password 18 | } 19 | return this.http.post(`${this.URL}/auth/login`, body) 20 | } 21 | 22 | suma(a: number, b: number): number { 23 | return a + b 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/app/modules/history/history.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | 4 | import { HistoryRoutingModule } from './history-routing.module'; 5 | import { HistoryPageComponent } from './pages/history-page/history-page.component'; 6 | import { SharedModule } from '@shared/shared.module'; 7 | import { SearchComponent } from './components/search/search.component'; 8 | import { FormsModule } from '@angular/forms'; 9 | 10 | 11 | @NgModule({ 12 | declarations: [ 13 | HistoryPageComponent, 14 | SearchComponent, 15 | 16 | ], 17 | imports: [ 18 | CommonModule, 19 | SharedModule, 20 | HistoryRoutingModule, 21 | FormsModule 22 | ] 23 | }) 24 | export class HistoryModule { } 25 | -------------------------------------------------------------------------------- /src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // This file can be replaced during build by using the `fileReplacements` array. 2 | // `ng build` replaces `environment.ts` with `environment.prod.ts`. 3 | // The list of file replacements can be found in `angular.json`. 4 | 5 | export const environment = { 6 | production: false, 7 | api: 'http://localhost:3000/api/1.0' 8 | }; 9 | 10 | /* 11 | * For easier debugging in development mode, you can import the following file 12 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. 13 | * 14 | * This import should be commented out in production mode because it will have a negative impact 15 | * on performance if an error is thrown. 16 | */ 17 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI. 18 | -------------------------------------------------------------------------------- /src/app/modules/home/pages/home-page/home-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomePageComponent } from './home-page.component'; 4 | 5 | describe('HomePageComponent', () => { 6 | let component: HomePageComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ HomePageComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HomePageComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/shared/components/card-player/card-player.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { CardPlayerComponent } from './card-player.component'; 4 | 5 | describe('CardPlayerComponent', () => { 6 | let component: CardPlayerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ CardPlayerComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(CardPlayerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/shared/components/header-user/header-user.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HeaderUserComponent } from './header-user.component'; 4 | 5 | describe('HeaderUserComponent', () => { 6 | let component: HeaderUserComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ HeaderUserComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(HeaderUserComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/shared/components/media-player/media-player.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MediaPlayerComponent } from './media-player.component'; 4 | 5 | describe('MediaPlayerComponent', () => { 6 | let component: MediaPlayerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ MediaPlayerComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(MediaPlayerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | # Only exists if Bazel was run 8 | /bazel-out 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # profiling files 14 | chrome-profiler-events*.json 15 | 16 | # IDEs and editors 17 | /.idea 18 | .project 19 | .classpath 20 | .c9/ 21 | *.launch 22 | .settings/ 23 | *.sublime-workspace 24 | 25 | # IDE - VSCode 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | .history/* 32 | 33 | # misc 34 | /.angular/cache 35 | /.sass-cache 36 | /connect.lock 37 | /coverage 38 | /libpeerconnection.log 39 | npm-debug.log 40 | yarn-error.log 41 | testem.log 42 | /typings 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | -------------------------------------------------------------------------------- /src/app/modules/favorites/pages/favorite-page/favorite-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FavoritePageComponent } from './favorite-page.component'; 4 | 5 | describe('FavoritePageComponent', () => { 6 | let component: FavoritePageComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ FavoritePageComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(FavoritePageComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/shared/components/card-player/card-player.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { TrackModel } from '@core/models/tracks.model'; 3 | import { MultimediaService } from '@shared/services/multimedia.service'; 4 | 5 | @Component({ 6 | selector: 'app-card-player', 7 | templateUrl: './card-player.component.html', 8 | styleUrls: ['./card-player.component.css'] 9 | }) 10 | export class CardPlayerComponent implements OnInit { 11 | @Input() mode: 'small' | 'big' = 'small' 12 | @Input() track: TrackModel = { _id: 0, name: '', album: '', url: '', cover: '' }; 13 | 14 | constructor(private multimediaService: MultimediaService) { } 15 | 16 | ngOnInit(): void { 17 | } 18 | 19 | sendPlay(track: TrackModel): void { 20 | this.multimediaService.trackInfo$.next(track) 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/app/shared/components/play-list-header/play-list-header.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PlayListHeaderComponent } from './play-list-header.component'; 4 | 5 | describe('PlayListHeaderComponent', () => { 6 | let component: PlayListHeaderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ PlayListHeaderComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PlayListHeaderComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/shared/components/section-generic/section-generic.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { SectionGenericComponent } from './section-generic.component'; 4 | 5 | describe('SectionGenericComponent', () => { 6 | let component: SectionGenericComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | declarations: [ SectionGenericComponent ] 12 | }) 13 | .compileComponents(); 14 | }); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(SectionGenericComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/shared/pipe/order-list.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { TrackModel } from '@core/models/tracks.model'; 3 | 4 | @Pipe({ 5 | name: 'orderList' 6 | }) 7 | export class OrderListPipe implements PipeTransform { 8 | 9 | transform(value: Array, args: string | null = null, sort: string = 'asc'): TrackModel[] { 10 | if (args === null) { 11 | return value 12 | } else { 13 | const tmpList = value.sort((a, b) => { 14 | if (a[args] < b[args]) { 15 | return -1 16 | } 17 | else if (a[args] === b[args]) { 18 | return 0; 19 | } 20 | else if (a[args] > b[args]) { 21 | return 1; 22 | } 23 | return 1 24 | }); 25 | 26 | return (sort === 'asc') ? tmpList : tmpList.reverse() 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/app/modules/home/home-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | 5 | const routes: Routes = [ 6 | { 7 | path: 'tracks', 8 | loadChildren: () => import('@modules/tracks/tracks.module').then(m => m.TracksModule) 9 | }, 10 | { 11 | path: 'favorites', 12 | loadChildren: () => import('@modules/favorites/favorites.module').then(m => m.FavoritesModule) 13 | }, 14 | { 15 | path: 'history', 16 | loadChildren: () => import('@modules/history/history.module').then(m => m.HistoryModule) 17 | }, 18 | { 19 | path: '**',//TODO 404 cuando no existe la ruta 20 | redirectTo: '/tracks' 21 | } 22 | ]; 23 | 24 | @NgModule({ 25 | imports: [RouterModule.forChild(routes)], 26 | exports: [RouterModule] 27 | }) 28 | export class HomeRoutingModule { } 29 | -------------------------------------------------------------------------------- /src/app/modules/history/components/search/search.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | 4 | import { SearchComponent } from './search.component'; 5 | 6 | describe('SearchComponent', () => { 7 | let component: SearchComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async () => { 11 | await TestBed.configureTestingModule({ 12 | declarations: [SearchComponent] 13 | }) 14 | .compileComponents(); 15 | }); 16 | 17 | beforeEach(() => { 18 | fixture = TestBed.createComponent(SearchComponent); 19 | component = fixture.componentInstance; 20 | fixture.detectChanges(); 21 | }); 22 | 23 | it('should create', () => { 24 | expect(component).toBeTruthy(); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /src/app/shared/components/play-list-body/play-list-body.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, OnInit } from '@angular/core'; 2 | import { TrackModel } from '@core/models/tracks.model'; 3 | 4 | 5 | @Component({ 6 | selector: 'app-play-list-body', 7 | templateUrl: './play-list-body.component.html', 8 | styleUrls: ['./play-list-body.component.css'] 9 | }) 10 | export class PlayListBodyComponent implements OnInit { 11 | @Input() tracks: TrackModel[] = [] 12 | optionSort: { property: string | null, order: string } = { property: null, order: 'asc' } 13 | constructor() { } 14 | 15 | ngOnInit(): void { 16 | 17 | } 18 | 19 | changeSort(property: string): void { 20 | const { order } = this.optionSort 21 | this.optionSort = { 22 | property, 23 | order: order === 'asc' ? 'desc' : 'asc' 24 | } 25 | console.log(this.optionSort); 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { HomePageComponent } from './modules/home/pages/home-page/home-page.component'; 2 | import { NgModule } from '@angular/core'; 3 | import { RouterModule, Routes } from '@angular/router'; 4 | import { SessionGuard } from '@core/guards/session.guard'; 5 | 6 | 7 | const routes: Routes = [ //TODO: router-outlet (Padre) 8 | { 9 | path: 'auth', //TODO (Public) Login, Register, Forgot... 10 | loadChildren: () => import(`./modules/auth/auth.module`).then(m => m.AuthModule) 11 | }, 12 | { 13 | path: '',//TODO (Private) 🔴🔴 14 | component: HomePageComponent, 15 | loadChildren: () => import(`./modules/home/home.module`).then(m => m.HomeModule), 16 | canActivate: [SessionGuard] 17 | } 18 | ]; 19 | 20 | @NgModule({ 21 | imports: [RouterModule.forRoot(routes)], 22 | exports: [RouterModule] 23 | }) 24 | export class AppRoutingModule { } 25 | -------------------------------------------------------------------------------- /src/app/shared/components/side-bar/side-bar.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | 4 | import { SideBarComponent } from './side-bar.component'; 5 | 6 | describe('SideBarComponent', () => { 7 | let component: SideBarComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async () => { 11 | await TestBed.configureTestingModule({ 12 | imports: [RouterTestingModule], 13 | declarations: [SideBarComponent] 14 | }) 15 | .compileComponents(); 16 | }); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(SideBarComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/app/modules/tracks/pages/tracks-page/tracks-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | 4 | import { TracksPageComponent } from './tracks-page.component'; 5 | 6 | describe('TracksPageComponent', () => { 7 | let component: TracksPageComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async () => { 11 | await TestBed.configureTestingModule({ 12 | imports: [HttpClientTestingModule], 13 | declarations: [TracksPageComponent] 14 | }) 15 | .compileComponents(); 16 | }); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(TracksPageComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/app/modules/history/pages/history-page/history-page.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { TrackModel } from '@core/models/tracks.model'; 3 | import { SearchService } from '@modules/history/services/search.service'; 4 | import { Observable, of } from 'rxjs'; 5 | 6 | @Component({ 7 | selector: 'app-history-page', 8 | templateUrl: './history-page.component.html', 9 | styleUrls: ['./history-page.component.css'] 10 | }) 11 | export class HistoryPageComponent implements OnInit { 12 | listResults$: Observable = of([]) 13 | constructor(private searchService: SearchService) { } 14 | 15 | ngOnInit(): void { 16 | } 17 | 18 | receiveData(event: string): void { 19 | //TODO: agarras el termino y sabes que solo se ejecuta cunado tiene 3 caracters 20 | console.log('🎁 Estoy en el padre jua jua...', event); 21 | this.listResults$ = this.searchService.searchTracks$(event) 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/modules/history/pages/history-page/history-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | 4 | import { HistoryPageComponent } from './history-page.component'; 5 | 6 | describe('HistoryPageComponent', () => { 7 | let component: HistoryPageComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async () => { 11 | await TestBed.configureTestingModule({ 12 | imports: [HttpClientTestingModule], 13 | declarations: [HistoryPageComponent] 14 | }) 15 | .compileComponents(); 16 | }); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(HistoryPageComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/app/shared/components/play-list-body/play-list-body.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | import { OrderListPipe } from '@shared/pipe/order-list.pipe'; 3 | 4 | import { PlayListBodyComponent } from './play-list-body.component'; 5 | 6 | describe('PlayListBodyComponent', () => { 7 | let component: PlayListBodyComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(async () => { 11 | await TestBed.configureTestingModule({ 12 | declarations: [ 13 | PlayListBodyComponent, 14 | OrderListPipe 15 | ] 16 | }) 17 | .compileComponents(); 18 | }); 19 | 20 | beforeEach(() => { 21 | fixture = TestBed.createComponent(PlayListBodyComponent); 22 | component = fixture.componentInstance; 23 | fixture.detectChanges(); 24 | }); 25 | 26 | it('should create', () => { 27 | expect(component).toBeTruthy(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http' 4 | import { AppRoutingModule } from './app-routing.module'; 5 | import { AppComponent } from './app.component'; 6 | import { CookieService } from 'ngx-cookie-service'; 7 | import { InjectSessionInterceptor } from '@core/interceptors/inject-session.interceptor'; 8 | 9 | @NgModule({ 10 | declarations: [ //TODO: Declaraciones, componentes, directivas, pipes 11 | AppComponent, 12 | ], 13 | imports: [ //TODO: Solo se importan otros modules 14 | BrowserModule, 15 | AppRoutingModule, 16 | HttpClientModule 17 | ], 18 | providers: [ 19 | CookieService, 20 | { 21 | provide: HTTP_INTERCEPTORS, 22 | useClass: InjectSessionInterceptor, 23 | multi: true 24 | } 25 | ], 26 | bootstrap: [AppComponent] 27 | }) 28 | export class AppModule { } 29 | -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | :root { 4 | --color-1: #1DB954; 5 | --color-2: #323232; 6 | --color-3: #000000; 7 | --color-4: #FFFFFF; 8 | --color-5: #181818; 9 | --font-size-1: 1rem; 10 | --font-size-2: 1.2rem; 11 | --font-size-3: 1.8rem; 12 | --shadow-1: rgb(0 0 0 / 35%) 0px 8px 24px; 13 | --shadow-2: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px; 14 | --border-radius-1: .25rem; 15 | --border-radius-2: .5rem; 16 | --font-family-1: 'Poppins', sans-serif; 17 | --animation-1: 200ms; 18 | } 19 | 20 | html, 21 | body { 22 | margin: 0; 23 | padding: 0; 24 | background-color: var(--color-3); 25 | font-family: var(--font-family-1); 26 | font-size: var(--font-size-1); 27 | color: var(--color-4); 28 | } 29 | 30 | input, 31 | button { 32 | cursor: pointer; 33 | font-family: var(--font-family-1); 34 | } 35 | 36 | button:disabled { 37 | opacity: .5; 38 | } -------------------------------------------------------------------------------- /src/app/core/guards/session.guard.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { ActivatedRouteSnapshot, Router, RouterStateSnapshot, UrlTree } from '@angular/router'; 3 | import { CookieService } from 'ngx-cookie-service'; 4 | import { Observable } from 'rxjs'; 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class SessionGuard { 10 | 11 | constructor( 12 | private cookieService: CookieService, 13 | private router: Router) { 14 | 15 | } 16 | 17 | canActivate( 18 | route: ActivatedRouteSnapshot, 19 | state: RouterStateSnapshot): Observable | Promise | boolean | UrlTree { 20 | return this.checkCookieSession(); 21 | } 22 | 23 | checkCookieSession(): boolean { 24 | try { 25 | 26 | const token: boolean = this.cookieService.check('token') 27 | if (!token) { 28 | this.router.navigate(['/', 'auth']) 29 | } 30 | return token 31 | 32 | } catch (e) { 33 | console.log('Algo sucedio ?? 🔴', e); 34 | return false 35 | } 36 | 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/app/core/interceptors/inject-session.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { 3 | HttpRequest, 4 | HttpHandler, 5 | HttpEvent, 6 | HttpInterceptor 7 | } from '@angular/common/http'; 8 | import { Observable } from 'rxjs'; 9 | import { CookieService } from 'ngx-cookie-service'; 10 | 11 | @Injectable() 12 | export class InjectSessionInterceptor implements HttpInterceptor { 13 | 14 | constructor(private cookieService: CookieService) { } 15 | 16 | intercept(request: HttpRequest, next: HttpHandler): Observable> { 17 | try { 18 | const token = this.cookieService.get('token') 19 | let newRequest = request 20 | newRequest = request.clone( 21 | { 22 | setHeaders: { 23 | authorization: `Bearer ${token}`, 24 | CUSTOM_HEADER: 'HOLA' 25 | } 26 | } 27 | ) 28 | 29 | return next.handle(newRequest); 30 | 31 | } catch (e) { 32 | console.log('🔴🔴🔴 Ojito error', e) 33 | return next.handle(request); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/app/shared/components/side-bar/side-bar.component.css: -------------------------------------------------------------------------------- 1 | .side-bar { 2 | width: 15.5rem; 3 | height: 100vh; 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | padding: .5rem 1rem; 8 | /* background-color: red; */ 9 | } 10 | 11 | .side-bar__list { 12 | list-style: none; 13 | padding: 0; 14 | margin: 0; 15 | opacity: .8; 16 | } 17 | 18 | .side-bar__list .list-label { 19 | font-size: 90%; 20 | font-weight: 500; 21 | } 22 | 23 | .side-bar__list .list-wrapper-item { 24 | font-size: var(--font-size-1); 25 | font-weight: 500; 26 | display: flex; 27 | align-items: center; 28 | align-content: center; 29 | } 30 | 31 | .side-bar__list .list-wrapper-item .uil { 32 | font-size: var(--font-size-3); 33 | padding-right: .5rem; 34 | } 35 | 36 | .side-bar__separator { 37 | margin: 1rem 0; 38 | } 39 | 40 | .side-bar__separator.line-separator { 41 | border-bottom: solid 1px var(--color-2); 42 | } 43 | 44 | .side-bar__list.list-three .list-wrapper-item { 45 | padding: .25rem 0; 46 | } 47 | 48 | .side-bar__list li { 49 | cursor: pointer; 50 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Spotify 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 12.2.6. 4 | 5 | ## Development server 6 | 7 | Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files. 8 | 9 | ## Code scaffolding 10 | 11 | Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. 12 | 13 | ## Build 14 | 15 | Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Running unit tests 18 | 19 | Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). 20 | 21 | ## Running end-to-end tests 22 | 23 | Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities. 24 | 25 | ## Further help 26 | 27 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page. 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "resolveJsonModule": true, 6 | "baseUrl": "./", 7 | "outDir": "./dist/out-tsc", 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "sourceMap": true, 13 | "declaration": false, 14 | "downlevelIteration": true, 15 | "experimentalDecorators": true, 16 | "moduleResolution": "node", 17 | "importHelpers": true, 18 | "target": "ES2022", 19 | "module": "es2020", 20 | "paths": { 21 | "@core/*": [ 22 | "src/app/core/*" 23 | ], 24 | "@modules/*": [ 25 | "src/app/modules/*" 26 | ], 27 | "@shared/*": [ 28 | "src/app/shared/*" 29 | ] 30 | }, 31 | "lib": [ 32 | "es2018", 33 | "dom" 34 | ], 35 | "useDefineForClassFields": false 36 | }, 37 | "angularCompilerOptions": { 38 | "enableI18nLegacyMessageIdFormat": false, 39 | "strictInjectionParameters": true, 40 | "strictInputAccessModifiers": true, 41 | "strictTemplates": true 42 | } 43 | } -------------------------------------------------------------------------------- /src/app/modules/tracks/pages/tracks-page/tracks-page.component.ts: -------------------------------------------------------------------------------- 1 | import { TrackService } from '@modules/tracks/services/track.service'; 2 | import { Component, OnDestroy, OnInit } from '@angular/core'; 3 | import { TrackModel } from '@core/models/tracks.model'; 4 | import { Subscription } from 'rxjs'; 5 | 6 | @Component({ 7 | selector: 'app-tracks-page', 8 | templateUrl: './tracks-page.component.html', 9 | styleUrls: ['./tracks-page.component.css'] 10 | }) 11 | export class TracksPageComponent implements OnInit, OnDestroy { 12 | 13 | tracksTrending: Array = [] 14 | tracksRandom: Array = [] 15 | listObservers$: Array = [] 16 | 17 | constructor(private trackService: TrackService) { } 18 | 19 | ngOnInit(): void { 20 | this.loadDataAll() //TODO 📌📌 21 | this.loadDataRandom() //TODO 📌📌 22 | } 23 | 24 | async loadDataAll(): Promise { 25 | this.tracksTrending = await this.trackService.getAllTracks$().toPromise() 26 | 27 | } 28 | 29 | loadDataRandom(): void { 30 | this.trackService.getAllRandom$() 31 | .subscribe((response: TrackModel[]) => { 32 | this.tracksRandom = response 33 | }) 34 | } 35 | 36 | ngOnDestroy(): void { 37 | 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/app/shared/components/play-list-body/play-list-body.component.css: -------------------------------------------------------------------------------- 1 | .play-list-body { 2 | padding: 1rem 2rem; 3 | } 4 | 5 | .play-list-table-header { 6 | width: 100%; 7 | list-style: none; 8 | display: grid; 9 | grid-template-columns: 3fr 2fr 2fr 1fr; 10 | margin: 0; 11 | padding: 0; 12 | font-size: 90%; 13 | letter-spacing: 1px; 14 | border-bottom: solid 1px var(--color-2); 15 | padding-bottom: .5rem; 16 | } 17 | 18 | .play-list-table-header li { 19 | text-transform: uppercase; 20 | color: var(--color-4); 21 | opacity: .7; 22 | } 23 | 24 | .play-list-rows { 25 | width: 100%; 26 | list-style: none; 27 | display: grid; 28 | grid-template-columns: 3fr 2fr 2fr 1fr; 29 | margin: 0; 30 | padding: 1rem 0 0 0; 31 | } 32 | 33 | .play-list-rows .track-name-opacity { 34 | opacity: .7; 35 | font-weight: 300; 36 | } 37 | 38 | .cover-section { 39 | display: flex; 40 | padding: 0 41 | } 42 | 43 | .cover-section .cover-track { 44 | width: 40px; 45 | height: 40px; 46 | object-fit: cover; 47 | } 48 | 49 | .cover-section .cover-info { 50 | padding: 0 1rem; 51 | } 52 | 53 | .cover-section .cover-info .name-track-details { 54 | font-size: 80%; 55 | opacity: .7; 56 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spotify", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test" 10 | }, 11 | "private": true, 12 | "dependencies": { 13 | "@angular/animations": "^16.0.0", 14 | "@angular/common": "^16.0.0", 15 | "@angular/compiler": "^16.0.0", 16 | "@angular/core": "^16.0.0", 17 | "@angular/forms": "^16.0.0", 18 | "@angular/platform-browser": "^16.0.0", 19 | "@angular/platform-browser-dynamic": "^16.0.0", 20 | "@angular/router": "^16.0.0", 21 | "ngx-cookie-service": "^16.0.0", 22 | "rxjs": "~6.6.0", 23 | "tslib": "^2.3.0", 24 | "zone.js": "^0.13.0" 25 | }, 26 | "devDependencies": { 27 | "@angular-devkit/build-angular": "^16.0.0", 28 | "@angular/cli": "^16.0.0", 29 | "@angular/compiler-cli": "^16.0.0", 30 | "@types/jasmine": "~3.8.0", 31 | "@types/node": "^12.11.1", 32 | "jasmine-core": "~3.8.0", 33 | "karma": "~6.3.0", 34 | "karma-chrome-launcher": "~3.1.0", 35 | "karma-coverage": "~2.0.3", 36 | "karma-jasmine": "~4.0.0", 37 | "karma-jasmine-html-reporter": "~1.7.0", 38 | "typescript": "4.9.3" 39 | } 40 | } -------------------------------------------------------------------------------- /src/app/shared/components/side-bar/side-bar.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/shared/components/play-list-header/play-list-header.component.css: -------------------------------------------------------------------------------- 1 | .play-list-header { 2 | display: flex; 3 | height: 18rem; 4 | width: 100%; 5 | padding-top: 3rem; 6 | padding-bottom: 1rem; 7 | background-color: #ffffff14; 8 | } 9 | 10 | .content-text { 11 | padding: 1.5rem 0; 12 | display: flex; 13 | flex-direction: column; 14 | justify-content: flex-end; 15 | } 16 | 17 | .cover { 18 | position: relative; 19 | width: 17rem; 20 | padding: 1rem 2rem; 21 | display: flex; 22 | justify-content: center; 23 | } 24 | 25 | .cover .cover-mock { 26 | height: 100%; 27 | width: 18rem; 28 | display: flex; 29 | align-self: center; 30 | justify-content: center; 31 | align-items: center; 32 | font-size: 5.5rem; 33 | background-color: var(--color-2); 34 | box-shadow: var(--shadow-1); 35 | } 36 | 37 | .content-text .title, 38 | .content-text .sub-title { 39 | margin: 0; 40 | font-weight: 600; 41 | } 42 | 43 | .content-text .title { 44 | font-size: 6rem; 45 | letter-spacing: -7px; 46 | } 47 | 48 | .content-text .small-text { 49 | text-transform: uppercase; 50 | font-weight: 500; 51 | } 52 | 53 | .content-text .count-tracks { 54 | font-weight: 300; 55 | opacity: .7; 56 | padding-left: .5rem; 57 | } -------------------------------------------------------------------------------- /src/app/shared/components/play-list-body/play-list-body.component.html: -------------------------------------------------------------------------------- 1 |
2 |
    3 |
  • Nombre ❤
  • 4 |
  • álbum 🆗
  • 5 |
  • fecha incorporación 🍌😁
  • 6 |
  • 7 |
8 |
    9 |
  • 10 | 11 |
  • 12 |
  • 13 | {{track.album}} 14 |
  • 15 |
  • 16 | {{track.album}} 17 |
  • 18 |
  • 19 | {{track.album}} 20 |
  • 21 | 22 |
23 |
24 | 25 | 26 | 27 | 28 |
29 | 30 |
31 |
{{track.name}}
32 |
{{track.artist.name}}
33 |
34 |
35 | 36 |
-------------------------------------------------------------------------------- /src/app/shared/components/card-player/card-player.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | {{track.name}} 6 |
7 |
8 | 11 | 14 |
15 |
16 | 17 | 18 |
19 | 20 | 21 | 22 |
23 |
{{track.name}}
24 |
{{track.name}}
25 |
26 |
27 | 30 | 33 |
34 |
-------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, './coverage/spotify'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /src/app/shared/components/media-player/media-player.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnDestroy, OnInit, ViewChild, ElementRef } from '@angular/core'; 2 | import { TrackModel } from '@core/models/tracks.model'; 3 | import { MultimediaService } from '@shared/services/multimedia.service'; 4 | import { Subscription } from 'rxjs'; //TODO: Programacion reactiva! 5 | 6 | @Component({ 7 | selector: 'app-media-player', 8 | templateUrl: './media-player.component.html', 9 | styleUrls: ['./media-player.component.css'] 10 | }) 11 | export class MediaPlayerComponent implements OnInit, OnDestroy { 12 | @ViewChild('progressBar') progressBar: ElementRef = new ElementRef('') 13 | listObservers$: Array = [] 14 | state: string = 'paused' 15 | constructor(public multimediaService: MultimediaService) { } 16 | 17 | ngOnInit(): void { 18 | 19 | const observer1$ = this.multimediaService.playerStatus$ 20 | .subscribe(status => this.state = status) 21 | this.listObservers$ = [observer1$] 22 | } 23 | 24 | ngOnDestroy(): void { 25 | this.listObservers$.forEach(u => u.unsubscribe()) 26 | console.log('🔴🔴🔴🔴🔴🔴🔴 BOOM!'); 27 | } 28 | 29 | 30 | handlePosition(event: MouseEvent): void { 31 | const elNative: HTMLElement = this.progressBar.nativeElement 32 | const { clientX } = event 33 | const { x, width } = elNative.getBoundingClientRect() 34 | const clickX = clientX - x //TODO: 1050 - x 35 | const percentageFromX = (clickX * 100) / width 36 | console.log(`Click(x): ${percentageFromX}`); 37 | this.multimediaService.seekAudio(percentageFromX) 38 | 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { RouterModule } from '@angular/router'; 2 | import { NgModule } from '@angular/core'; 3 | import { CommonModule } from '@angular/common'; 4 | import { SideBarComponent } from './components/side-bar/side-bar.component'; 5 | import { MediaPlayerComponent } from './components/media-player/media-player.component'; 6 | import { HeaderUserComponent } from './components/header-user/header-user.component'; 7 | import { CardPlayerComponent } from './components/card-player/card-player.component'; 8 | import { SectionGenericComponent } from './components/section-generic/section-generic.component'; 9 | import { PlayListHeaderComponent } from './components/play-list-header/play-list-header.component'; 10 | import { PlayListBodyComponent } from './components/play-list-body/play-list-body.component'; 11 | import { OrderListPipe } from './pipe/order-list.pipe'; 12 | import { ImgBrokenDirective } from './directives/img-broken.directive'; 13 | 14 | 15 | 16 | @NgModule({ 17 | declarations: [ 18 | SideBarComponent, 19 | MediaPlayerComponent, 20 | HeaderUserComponent, 21 | CardPlayerComponent, 22 | SectionGenericComponent, 23 | PlayListHeaderComponent, 24 | PlayListBodyComponent, 25 | OrderListPipe, 26 | ImgBrokenDirective 27 | 28 | ], 29 | imports: [ 30 | CommonModule, 31 | RouterModule 32 | ], 33 | exports: [ 34 | SideBarComponent, 35 | MediaPlayerComponent, 36 | HeaderUserComponent, 37 | CardPlayerComponent, 38 | SectionGenericComponent, 39 | PlayListHeaderComponent, 40 | PlayListBodyComponent, 41 | OrderListPipe, 42 | ImgBrokenDirective 43 | ] 44 | }) 45 | export class SharedModule { } 46 | -------------------------------------------------------------------------------- /src/app/modules/auth/pages/login-page/login-page.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/modules/tracks/services/track.service.ts: -------------------------------------------------------------------------------- 1 | import { TrackModel } from '@core/models/tracks.model'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Injectable } from '@angular/core'; 4 | import { Observable, of } from 'rxjs'; 5 | import { map, mergeMap, tap, catchError } from 'rxjs/operators'; 6 | import { environment } from 'src/environments/environment'; 7 | @Injectable({ 8 | providedIn: 'root' 9 | }) 10 | export class TrackService { 11 | private readonly URL = environment.api 12 | 13 | constructor(private http: HttpClient) { 14 | 15 | } 16 | 17 | /** 18 | * 19 | * @returns Devolver todas las canciones! molonas! 🤘🤘 20 | */ 21 | 22 | private skipById(listTracks: TrackModel[], id: number): Promise { 23 | return new Promise((resolve, reject) => { 24 | const listTmp = listTracks.filter(a => a._id !== id) 25 | resolve(listTmp) 26 | }) 27 | } 28 | 29 | /** 30 | * //TODO {data:[..1,...2,..2]} 31 | * 32 | * @returns 33 | */ 34 | getAllTracks$(): Observable { 35 | return this.http.get(`${this.URL}/tracks`) 36 | .pipe( 37 | map(({ data }: any) => { 38 | return data 39 | }) 40 | ) 41 | } 42 | 43 | 44 | /** 45 | * 46 | * @returns Devolver canciones random 47 | */ 48 | getAllRandom$(): Observable { 49 | return this.http.get(`${this.URL}/tracks`) 50 | .pipe( 51 | mergeMap(({ data }: any) => this.skipById(data, 2)), 52 | // map((dataRevertida) => { //TODO aplicar un filter comun de array 53 | // return dataRevertida.filter((track: TrackModel) => track._id !== 1) 54 | // }) 55 | catchError((err) => { 56 | const { status, statusText } = err; 57 | return of([]) 58 | }) 59 | ) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/app/modules/auth/services/auth.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { TestBed } from '@angular/core/testing'; 3 | import { AuthService } from './auth.service'; 4 | import * as mockRaw from '../../../data/user.json' 5 | import { of } from 'rxjs'; 6 | 7 | describe('AuthService', () => { 8 | let service: AuthService; 9 | let mockUser: any = (mockRaw as any).default; 10 | let httpClientSpy: { post: jasmine.Spy } 11 | 12 | beforeEach(() => { 13 | TestBed.configureTestingModule({ 14 | imports: [HttpClientTestingModule] 15 | }); 16 | httpClientSpy = jasmine.createSpyObj('HttpClient', ['post']) 17 | service = new AuthService(httpClientSpy as any) 18 | }); 19 | 20 | it('should be created', () => { 21 | expect(service).toBeTruthy(); 22 | }); 23 | 24 | //TODO: Prueba del sendCredentials 25 | 26 | it('Debe de retornar un objecto con "data" y "tokenSession"', 27 | (done: DoneFn) => { 28 | 29 | //TODO: Arrange 30 | 31 | const user: any = mockUser.userOk 32 | const mockResponse = { 33 | data: {}, 34 | tokenSession: '0x0x0x' 35 | } 36 | 37 | httpClientSpy.post.and.returnValue( 38 | of(mockResponse) //TODO: ✔✔✔ ya es observable 39 | ) 40 | 41 | //TODO: Act 42 | service.sendCredentials(user.email, user.password) 43 | .subscribe(responseApi => {//TODO ['data','tokenSession'] 44 | const getProperties = Object.keys(responseApi) 45 | expect(getProperties).toContain('data') 46 | expect(getProperties).toContain('tokenSession') 47 | done() 48 | }) 49 | 50 | }) 51 | 52 | 53 | it('TEST de suma de 2 + 3', () => { 54 | 55 | const a = 2 56 | const b = 3 57 | 58 | 59 | const c = service.suma(a, b) 60 | 61 | 62 | expect(c).toEqual(5) 63 | 64 | }) 65 | 66 | }); 67 | -------------------------------------------------------------------------------- /src/app/modules/auth/pages/login-page/login-page.component.ts: -------------------------------------------------------------------------------- 1 | import { UntypedFormControl, UntypedFormGroup, Validators } from '@angular/forms'; 2 | import { Component, OnInit } from '@angular/core'; 3 | import { AuthService } from '@modules/auth/services/auth.service'; 4 | import { CookieService } from 'ngx-cookie-service'; 5 | import { Router } from '@angular/router'; 6 | 7 | @Component({ 8 | selector: 'app-login-page', 9 | templateUrl: './login-page.component.html', 10 | styleUrls: ['./login-page.component.css'] 11 | }) 12 | export class LoginPageComponent implements OnInit { 13 | errorSession: boolean = false 14 | formLogin: UntypedFormGroup = new UntypedFormGroup({}); 15 | 16 | constructor(private authService: AuthService, private cookie: CookieService, 17 | private router: Router) { } 18 | 19 | ngOnInit(): void { 20 | this.formLogin = new UntypedFormGroup( 21 | { 22 | email: new UntypedFormControl('', [ 23 | Validators.required, 24 | Validators.email 25 | ]), 26 | password: new UntypedFormControl('', 27 | [ 28 | Validators.required, 29 | Validators.minLength(6), 30 | Validators.maxLength(12) 31 | ]) 32 | } 33 | ) 34 | } 35 | 36 | sendLogin(): void { 37 | const { email, password } = this.formLogin.value 38 | this.authService.sendCredentials(email, password) 39 | //TODO: 200 <400 40 | .subscribe(responseOk => { //TODO: Cuando el usuario credenciales Correctas ✔✔ 41 | console.log('Session iniciada correcta', responseOk); 42 | const { tokenSession, data } = responseOk 43 | this.cookie.set('token', tokenSession, 4, '/') //TODO:📌📌📌📌 44 | this.router.navigate(['/', 'tracks']) 45 | }, 46 | err => {//TODO error 400>= 47 | this.errorSession = true 48 | console.log('⚠⚠⚠⚠Ocurrio error con tu email o password'); 49 | }) 50 | 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/app/shared/components/side-bar/side-bar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ElementRef, OnInit } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | @Component({ 5 | selector: 'app-side-bar', 6 | templateUrl: './side-bar.component.html', 7 | styleUrls: ['./side-bar.component.css'] 8 | }) 9 | export class SideBarComponent implements OnInit { 10 | 11 | mainMenu: { 12 | defaultOptions: Array, accessLink: Array 13 | } = { defaultOptions: [], accessLink: [] } 14 | 15 | customOptions: Array = [] 16 | 17 | constructor(private router: Router) { } 18 | 19 | ngOnInit(): void { 20 | this.mainMenu.defaultOptions = [ 21 | { 22 | name: 'Home', 23 | icon: 'uil uil-estate', 24 | router: ['/', 'auth'] 25 | }, 26 | { 27 | name: 'Buscar', 28 | icon: 'uil uil-search', 29 | router: ['/', 'history'] 30 | }, 31 | { 32 | name: 'Tu biblioteca', 33 | icon: 'uil uil-chart', 34 | router: ['/', 'favorites'], 35 | query: { hola: 'mundo' } 36 | } 37 | ] 38 | 39 | this.mainMenu.accessLink = [ 40 | { 41 | name: 'Crear lista', 42 | icon: 'uil-plus-square' 43 | }, 44 | { 45 | name: 'Canciones que te gustan', 46 | icon: 'uil-heart-medical' 47 | } 48 | ] 49 | 50 | this.customOptions = [ 51 | { 52 | name: 'Mi lista º1', 53 | router: ['/'] 54 | }, 55 | { 56 | name: 'Mi lista º2', 57 | router: ['/'] 58 | }, 59 | { 60 | name: 'Mi lista º3', 61 | router: ['/'] 62 | }, 63 | { 64 | name: 'Mi lista º4', 65 | router: ['/'] 66 | } 67 | ] 68 | 69 | } 70 | 71 | goTo($event: any): void { 72 | this.router.navigate(['/', 'favorites'], { 73 | queryParams: { 74 | key1: 'value1', 75 | key2: 'value2', 76 | key3: 'value3' 77 | } 78 | }) 79 | console.log($event) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/app/shared/pipe/order-list.pipe.spec.ts: -------------------------------------------------------------------------------- 1 | import { TrackModel } from './../../core/models/tracks.model'; 2 | import { OrderListPipe } from './order-list.pipe'; 3 | import * as mockRaw from '../../data/tracks.json' 4 | 5 | describe('OrderListPipe', () => { 6 | it('create an instance', () => { 7 | const pipe = new OrderListPipe(); 8 | expect(pipe).toBeTruthy(); 9 | }); 10 | 11 | it('Probando entrada y salida de valores', () => { 12 | //TODO: Arrange 13 | const pipe = new OrderListPipe(); 14 | const { data }: any = (mockRaw as any).default 15 | 16 | //TODO: Act 17 | const result: TrackModel[] = pipe.transform(data) 18 | 19 | //TODO: Assert 20 | expect(result).toEqual(data) 21 | 22 | }) 23 | 24 | it('Probar si se ordena de manera correct ASC', () => { 25 | //TODO: Arrange 26 | const pipe = new OrderListPipe(); 27 | const { data }: any = (mockRaw as any).default 28 | const firstValue = data.find((i: any) => i._id === 7) //TODO 50-cent 29 | const lastValue = data.find((i: any) => i._id === 6) //TODO TNT 30 | 31 | //TODO:Act 32 | const result: TrackModel[] = pipe.transform(data, 'name', 'asc') 33 | const firstResult = result[0] 34 | const lastResult = result[result.length - 1] 35 | 36 | //TODO: Assert 37 | expect(firstResult).toEqual(firstValue) 38 | expect(lastResult).toEqual(lastValue) 39 | 40 | }) 41 | 42 | it('Probar si se ordena de manera correct DESC', () => { 43 | //TODO: Arrange 44 | const pipe = new OrderListPipe(); 45 | const { data }: any = (mockRaw as any).default 46 | const firstValue = data.find((i: any) => i._id === 7) //TODO 50-cent 47 | const lastValue = data.find((i: any) => i._id === 6) //TODO TNT 48 | 49 | //TODO:Act 50 | const result: TrackModel[] = pipe.transform(data, 'name', 'desc') 51 | const firstResult = result[0] 52 | const lastResult = result[result.length - 1] 53 | 54 | //TODO: Assert 55 | expect(firstResult).toEqual(lastValue) 56 | expect(lastResult).toEqual(firstValue) 57 | 58 | }) 59 | 60 | 61 | }); 62 | -------------------------------------------------------------------------------- /src/app/shared/components/card-player/card-player.component.css: -------------------------------------------------------------------------------- 1 | .card-player { 2 | background-color: var(--color-2); 3 | height: 5rem; 4 | border-radius: var(--border-radius-1); 5 | display: flex; 6 | cursor: pointer; 7 | transition: all ease var(--animation-1); 8 | justify-content: space-between; 9 | } 10 | 11 | .card-player:hover { 12 | background-color: #4d4d4d; 13 | } 14 | 15 | .card-player .cover { 16 | width: 5rem; 17 | height: 100%; 18 | object-fit: cover; 19 | border-top-left-radius: var(--border-radius-1); 20 | border-bottom-left-radius: var(--border-radius-1); 21 | } 22 | 23 | .card-player .content-player { 24 | font-weight: 500; 25 | display: flex; 26 | align-items: center; 27 | align-content: center; 28 | padding: 0 0 0 .75rem; 29 | } 30 | 31 | .card-player .buttons-player { 32 | opacity: 0; 33 | width: 5rem; 34 | display: flex; 35 | justify-content: center; 36 | align-content: center; 37 | align-items: center; 38 | transition: all ease var(--animation-1); 39 | } 40 | 41 | .card-player:hover .buttons-player { 42 | opacity: 1; 43 | } 44 | 45 | .card-player .buttons-player .play-btn { 46 | color: var(--color-4); 47 | border: 0; 48 | font-size: 1.15rem; 49 | border-radius: 2rem; 50 | background-color: var(--color-1); 51 | width: 45px; 52 | height: 45px; 53 | } 54 | 55 | .card-player.player-big { 56 | position: relative; 57 | flex-direction: column; 58 | height: 18rem; 59 | padding: 1rem; 60 | } 61 | 62 | .card-player.player-big .cover { 63 | width: 100%; 64 | height: 200px; 65 | border-radius: var(--border-radius-1); 66 | } 67 | 68 | .card-player.player-big .content-player { 69 | padding: .5rem 0; 70 | flex-direction: column; 71 | align-self: flex-start; 72 | align-items: flex-start; 73 | } 74 | 75 | .card-player.player-big .content-player .sub-title-track { 76 | font-size: 80%; 77 | opacity: .7; 78 | } 79 | 80 | .card-player.player-big .buttons-player { 81 | position: absolute; 82 | right: 0; 83 | top: 13rem 84 | } -------------------------------------------------------------------------------- /src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** 22 | * By default, zone.js will patch all possible macroTask and DomEvents 23 | * user can disable parts of macroTask/DomEvents patch by setting following flags 24 | * because those flags need to be set before `zone.js` being loaded, and webpack 25 | * will put import in the top of bundle, so user need to create a separate file 26 | * in this directory (for example: zone-flags.ts), and put the following flags 27 | * into that file, and then add the following code before importing zone.js. 28 | * import './zone-flags'; 29 | * 30 | * The flags allowed in zone-flags.ts are listed here. 31 | * 32 | * The following flags will work for all browsers. 33 | * 34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 37 | * 38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 40 | * 41 | * (window as any).__Zone_enable_cross_context_check = true; 42 | * 43 | */ 44 | 45 | /*************************************************************************************************** 46 | * Zone JS is required by default for Angular itself. 47 | */ 48 | import 'zone.js'; // Included with Angular CLI. 49 | 50 | 51 | /*************************************************************************************************** 52 | * APPLICATION IMPORTS 53 | */ 54 | -------------------------------------------------------------------------------- /src/app/modules/auth/pages/login-page/login-page.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientTestingModule } from '@angular/common/http/testing'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { By } from '@angular/platform-browser'; 4 | import { RouterTestingModule } from '@angular/router/testing'; 5 | 6 | import { LoginPageComponent } from './login-page.component'; 7 | 8 | describe('LoginPageComponent', () => { 9 | let component: LoginPageComponent; 10 | let fixture: ComponentFixture; 11 | 12 | beforeEach(async () => { 13 | await TestBed.configureTestingModule({ 14 | imports: [ 15 | HttpClientTestingModule, 16 | RouterTestingModule 17 | ], 18 | declarations: [LoginPageComponent] 19 | }) 20 | .compileComponents(); 21 | }); 22 | 23 | beforeEach(() => { 24 | fixture = TestBed.createComponent(LoginPageComponent); 25 | component = fixture.componentInstance; 26 | fixture.detectChanges(); 27 | }); 28 | 29 | //TODO: Tu primer enunciado el cual debe de asegurar lo siguiente 30 | //TODO: Debe de asegurarse que el formulario sea invalido cuando ingrese dato erroneos 31 | 32 | //TODO: Patron AAA 33 | 34 | it('🔴 Deberia de retornar "invalido" el formulario', () => { 35 | 36 | //TODO: Arrange 37 | const mockCredentials = { 38 | email: '0x0x0x0x0x0', 39 | password: '1111111111111111111111111' 40 | } 41 | 42 | const emailForm: any = component.formLogin.get('email') 43 | const passwordForm: any = component.formLogin.get('password') 44 | 45 | //TODO: Act 46 | 47 | emailForm.setValue(mockCredentials.email) 48 | passwordForm.setValue(mockCredentials.password) 49 | 50 | //TODO: Assert 51 | 52 | expect(component.formLogin.invalid).toEqual(true); 53 | }); 54 | 55 | it('✔✔ Deberia de retornar "valido" el formulario', () => { 56 | 57 | //TODO: Arrange 58 | const mockCredentials = { 59 | email: 'test@test.com', 60 | password: '12345678' 61 | } 62 | 63 | const emailForm: any = component.formLogin.get('email') 64 | const passwordForm: any = component.formLogin.get('password') 65 | 66 | //TODO: Act 67 | 68 | emailForm.setValue(mockCredentials.email) 69 | passwordForm.setValue(mockCredentials.password) 70 | 71 | //TODO: Assert 72 | 73 | expect(component.formLogin.invalid).toEqual(false); 74 | }); 75 | 76 | it('👍 El boton deberia de tener la palabra "Iniciar sesión"', () => { 77 | 78 | const elementRef = fixture.debugElement.query(By.css('.form-action button')) 79 | const getInnerText = elementRef.nativeElement.innerText 80 | 81 | expect(getInnerText).toEqual('Iniciar sesión') 82 | 83 | }) 84 | 85 | }); 86 | -------------------------------------------------------------------------------- /src/app/modules/auth/pages/login-page/login-page.component.css: -------------------------------------------------------------------------------- 1 | .login-auth-page { 2 | position: absolute; 3 | width: 100vw; 4 | height: 100vh; 5 | background-color: var(--color-4); 6 | color: var(--color-3); 7 | display: flex; 8 | justify-content: center; 9 | flex-direction: column; 10 | align-items: center; 11 | align-content: center; 12 | } 13 | 14 | .login-auth-page .login-auth-wrapper { 15 | width: 25%; 16 | display: flex; 17 | justify-content: center; 18 | flex-direction: column; 19 | align-items: center; 20 | } 21 | 22 | .login-auth-page .login-social-zone { 23 | display: flex; 24 | flex-direction: column; 25 | gap: .75rem; 26 | width: 100%; 27 | } 28 | 29 | .login-auth-page .social-btn { 30 | border: 0; 31 | padding: .5rem 2rem; 32 | width: 100%; 33 | font-size: var(--font-size-2); 34 | border-radius: var(--border-radius-2); 35 | box-shadow: var(--shadow-1); 36 | border: solid 1px var(--color-4); 37 | background-color: var(--color-4); 38 | font-weight: 600; 39 | } 40 | 41 | .login-auth-page .login-social-zone .social-btn.btn-fb { 42 | background-color: #3A61B3; 43 | color: var(--color-4); 44 | } 45 | 46 | .login-auth-page .login-social-zone .social-btn.btn-apple { 47 | background-color: #1a1a1a; 48 | color: var(--color-4); 49 | } 50 | 51 | .login-auth-page .separator { 52 | width: 100%; 53 | margin: 2rem 0; 54 | border-bottom: solid 1px var(--color-2); 55 | opacity: .2; 56 | } 57 | 58 | .login-auth-page .login-auth-form { 59 | display: flex; 60 | width: 100%; 61 | justify-content: flex-start; 62 | flex-direction: column; 63 | } 64 | 65 | .login-auth-page .login-auth-form .form-group { 66 | display: flex; 67 | justify-content: flex-start; 68 | flex-direction: column; 69 | } 70 | 71 | .login-auth-page .login-auth-form .form-group input { 72 | border: solid 1px #e7e7e7; 73 | border-radius: var(--border-radius-1); 74 | height: 30px; 75 | padding: .25rem .5rem; 76 | font-size: 90%; 77 | } 78 | 79 | .login-auth-page .login-auth-form label { 80 | padding-bottom: .35rem; 81 | font-weight: 600; 82 | display: inline-block; 83 | margin: 0; 84 | } 85 | 86 | .login-auth-page .login-auth-form .form-steps { 87 | padding: .5rem 0; 88 | } 89 | 90 | .login-auth-page .login-auth-form .form-steps .link { 91 | color: var(--color-2); 92 | font-size: 85%; 93 | font-weight: 500; 94 | text-decoration: underline; 95 | } 96 | 97 | .login-auth-page .login-auth-form .form-steps .link:hover { 98 | color: var(--color-1); 99 | } 100 | 101 | .login-auth-page .login-auth-form .form-action .login { 102 | color: var(--color-4); 103 | margin: 1rem 0; 104 | background-color: var(--color-1); 105 | } 106 | 107 | input.ng-invalid.ng-touched { 108 | border: solid 2px red !important 109 | } -------------------------------------------------------------------------------- /src/app/shared/components/media-player/media-player.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 |
7 | 8 |
9 | 10 |
11 | 12 |
13 | 14 |
15 |
16 |
17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 |
25 |

{{mockCover.name}}

26 |
{{mockCover?.album}}
27 |
28 |
29 | 32 |
33 |
34 |
35 | 36 | 37 |
38 |
39 | 42 | 49 | 52 |
53 |
54 | 55 |
{{multimediaService.timeElapsed$ | async}}
56 | 57 | 58 | 59 | 60 |
{{multimediaService.timeRemaining$ | async}}
61 |
62 |
63 |
64 | 65 | 66 |
67 | 70 | 73 | 76 |
77 |
-------------------------------------------------------------------------------- /src/app/shared/components/media-player/media-player.component.css: -------------------------------------------------------------------------------- 1 | .media-player { 2 | background-color: var(--color-5); 3 | position: fixed; 4 | bottom: 0; 5 | width: 100%; 6 | z-index: 2; 7 | height: 85px; 8 | border-top: solid 1px var(--color-2); 9 | box-shadow: 0px -15px 15px 7px #0000002e; 10 | } 11 | 12 | .media-player .media-player--wrapper { 13 | position: relative; 14 | display: flex; 15 | justify-content: space-between; 16 | height: 100%; 17 | padding: 0; 18 | } 19 | 20 | .media-player .media-player--wrapper .player-center { 21 | display: flex; 22 | align-content: center; 23 | align-items: center; 24 | } 25 | 26 | .media-player--wrapper .artist { 27 | width: 25%; 28 | padding: 0 .5rem; 29 | } 30 | 31 | .media-player--wrapper .player-audio { 32 | width: 25%; 33 | padding: 0 .5rem; 34 | } 35 | 36 | .media-player--wrapper .player-controls { 37 | width: 50%; 38 | padding: 0 .5rem; 39 | position: relative; 40 | } 41 | 42 | .media-player--wrapper .artist .artist-inside { 43 | display: flex; 44 | gap: .5rem 45 | } 46 | 47 | .media-player--wrapper .artist .track-like { 48 | display: flex; 49 | flex-direction: column; 50 | align-self: center; 51 | padding: 0 .25rem; 52 | } 53 | 54 | .media-player--wrapper .artist .track-like .btn-like { 55 | border: 0; 56 | background-color: transparent; 57 | color: var(--color-4); 58 | font-size: var(--font-size-2); 59 | opacity: .7; 60 | } 61 | 62 | .media-player--wrapper .artist .artist-inside .track-info { 63 | display: flex; 64 | flex-direction: column; 65 | align-self: center; 66 | } 67 | 68 | .media-player--wrapper .artist .artist-inside .track-title { 69 | margin: 0; 70 | font-weight: 400; 71 | font-size: .8rem; 72 | } 73 | 74 | .media-player--wrapper .artist .artist-inside .sub-title { 75 | margin: 0; 76 | font-weight: 300; 77 | font-size: 80%; 78 | opacity: .6; 79 | } 80 | 81 | .media-player--wrapper .artist .artist-inside .cover { 82 | width: 55px; 83 | height: 55px; 84 | object-fit: cover; 85 | } 86 | 87 | .media-player--wrapper .player-controls-inside { 88 | display: flex; 89 | justify-content: center; 90 | align-self: center; 91 | flex-direction: column; 92 | width: 100%; 93 | position: relative; 94 | } 95 | 96 | .media-player--wrapper .player-controls-inside .buttons-media { 97 | display: flex; 98 | justify-content: center; 99 | align-self: center; 100 | gap: .5rem 101 | } 102 | 103 | .media-player--wrapper .player-controls-inside .buttons-media .btn { 104 | background-color: transparent; 105 | border: 0; 106 | color: var(--color-4); 107 | opacity: .7; 108 | font-size: 1.65rem 109 | } 110 | 111 | .media-player--wrapper .player-controls-inside .buttons-media .play { 112 | font-size: 2.35rem; 113 | } 114 | 115 | .media-player--wrapper .player-controls-inside .media-linetime { 116 | display: flex; 117 | justify-content: space-between; 118 | font-size: 70%; 119 | padding: 0; 120 | } 121 | 122 | .media-player--wrapper .player-controls-inside .media-linetime .time { 123 | padding: .5rem 0; 124 | } 125 | 126 | .media-player--wrapper .player-controls-inside .media-linetime .time-progress { 127 | background-color: var(--color-2); 128 | height: 2px; 129 | width: 100%; 130 | left: 0; 131 | position: absolute; 132 | cursor: pointer; 133 | } 134 | 135 | .media-player--wrapper .player-controls-inside .media-linetime .time-progress-live { 136 | background-color: var(--color-4); 137 | width: 0%; 138 | height: 2px; 139 | transition: all ease var(--animation-1); 140 | position: absolute; 141 | } 142 | 143 | .media-player--wrapper .player-audio-inside { 144 | display: flex; 145 | justify-content: flex-end; 146 | width: 100%; 147 | } 148 | 149 | .media-player--wrapper .player-audio-inside .btn-media { 150 | background-color: transparent; 151 | border: 0; 152 | color: var(--color-4); 153 | font-size: var(--font-size-2); 154 | } -------------------------------------------------------------------------------- /src/app/shared/services/multimedia.service.ts: -------------------------------------------------------------------------------- 1 | import { TrackModel } from './../../core/models/tracks.model'; 2 | import { EventEmitter, Injectable } from '@angular/core'; 3 | import { BehaviorSubject, Observable, Observer, Subject } from 'rxjs'; 4 | 5 | 6 | @Injectable({ 7 | providedIn: 'root' 8 | }) 9 | export class MultimediaService { 10 | callback: EventEmitter = new EventEmitter() 11 | 12 | public trackInfo$: BehaviorSubject = new BehaviorSubject(undefined) 13 | public audio!: HTMLAudioElement //TODO