Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material
						commit
						25bfb9e518
					
				
											
												
													File diff suppressed because one or more lines are too long
												
											
										
									
								
											
												
													File diff suppressed because one or more lines are too long
												
											
										
									
								
											
												
													File diff suppressed because one or more lines are too long
												
											
										
									
								
											
												
													File diff suppressed because one or more lines are too long
												
											
										
									
								
											
												
													File diff suppressed because one or more lines are too long
												
											
										
									
								
											
												
													File diff suppressed because one or more lines are too long
												
											
										
									
								
											
												
													File diff suppressed because one or more lines are too long
												
											
										
									
								@ -1 +1 @@
 | 
			
		||||
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],l=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"-es2015."+{1:"c401a556fe28cac6abab"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var l=0;l<i.length;l++)r(i[l]);var f=c;t()}([]);
 | 
			
		||||
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],l=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"-es2015."+{1:"95e37a140299b9d6887a"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var l=0;l<i.length;l++)r(i[l]);var f=c;t()}([]);
 | 
			
		||||
@ -1 +1 @@
 | 
			
		||||
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],l=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"-es5."+{1:"c401a556fe28cac6abab"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var l=0;l<i.length;l++)r(i[l]);var f=c;t()}([]);
 | 
			
		||||
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],l=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"-es5."+{1:"95e37a140299b9d6887a"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var l=0;l<i.length;l++)r(i[l]);var f=c;t()}([]);
 | 
			
		||||
@ -0,0 +1,13 @@
 | 
			
		||||
<div *ngIf="playlists && playlists.length > 0">
 | 
			
		||||
    <div class="container">
 | 
			
		||||
        <div class="row justify-content-center">
 | 
			
		||||
            <div *ngFor="let playlist of playlists; let i = index" [ngClass]="[ postsService.card_size === 'small' ? 'col-2 mb-2 mt-2 file-col' : '', postsService.card_size === 'medium' ? 'col-6 col-lg-4 mb-2 mt-2 file-col' : '' ]">
 | 
			
		||||
                <app-unified-file-card [index]="i" [card_size]="postsService.card_size" (goToFile)="goToPlaylist($event)" [file_obj]="playlist" [is_playlist]="true" (editPlaylist)="editPlaylistDialog($event)" (deleteFile)="deletePlaylist($event)"></app-unified-file-card>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
<div *ngIf="playlists && playlists.length === 0" style="text-align: center;">
 | 
			
		||||
    No playlists available. Create one from your downloading files by clicking the blue plus button.
 | 
			
		||||
</div>
 | 
			
		||||
<div class="add-playlist-button"><button (click)="openCreatePlaylistDialog()" mat-fab><mat-icon>add</mat-icon></button></div>
 | 
			
		||||
@ -0,0 +1,10 @@
 | 
			
		||||
.add-playlist-button {
 | 
			
		||||
    float: right;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    bottom: 15px;
 | 
			
		||||
    right: 15px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.file-col {
 | 
			
		||||
    max-width: 240px;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,25 @@
 | 
			
		||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { CustomPlaylistsComponent } from './custom-playlists.component';
 | 
			
		||||
 | 
			
		||||
describe('CustomPlaylistsComponent', () => {
 | 
			
		||||
  let component: CustomPlaylistsComponent;
 | 
			
		||||
  let fixture: ComponentFixture<CustomPlaylistsComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async(() => {
 | 
			
		||||
    TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ CustomPlaylistsComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(CustomPlaylistsComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,112 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { PostsService } from 'app/posts.services';
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
import { MatDialog } from '@angular/material/dialog';
 | 
			
		||||
import { CreatePlaylistComponent } from 'app/create-playlist/create-playlist.component';
 | 
			
		||||
import { ModifyPlaylistComponent } from 'app/dialogs/modify-playlist/modify-playlist.component';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-custom-playlists',
 | 
			
		||||
  templateUrl: './custom-playlists.component.html',
 | 
			
		||||
  styleUrls: ['./custom-playlists.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class CustomPlaylistsComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  playlists = null;
 | 
			
		||||
  playlists_received = false;
 | 
			
		||||
  downloading_content = {'video': {}, 'audio': {}};
 | 
			
		||||
 | 
			
		||||
  constructor(public postsService: PostsService, private router: Router, private dialog: MatDialog) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.postsService.service_initialized.subscribe(init => {
 | 
			
		||||
      if (init) {
 | 
			
		||||
        this.getAllPlaylists();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getAllPlaylists() {
 | 
			
		||||
    this.playlists_received = false;
 | 
			
		||||
    this.postsService.getAllFiles().subscribe(res => {
 | 
			
		||||
      this.playlists = res['playlists'];
 | 
			
		||||
      this.playlists_received = true;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // creating a playlist
 | 
			
		||||
  openCreatePlaylistDialog() {
 | 
			
		||||
    const dialogRef = this.dialog.open(CreatePlaylistComponent, {
 | 
			
		||||
      data: {
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
    dialogRef.afterClosed().subscribe(result => {
 | 
			
		||||
      if (result) {
 | 
			
		||||
        this.getAllPlaylists();
 | 
			
		||||
        this.postsService.openSnackBar('Successfully created playlist!', '');
 | 
			
		||||
      } else if (result === false) {
 | 
			
		||||
        this.postsService.openSnackBar('ERROR: failed to create playlist!', '');
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  goToPlaylist(playlist) {
 | 
			
		||||
    const playlistID = playlist.id;
 | 
			
		||||
    const type = playlist.type;
 | 
			
		||||
 | 
			
		||||
    if (playlist) {
 | 
			
		||||
      if (this.postsService.config['Extra']['download_only_mode']) {
 | 
			
		||||
        this.downloading_content[type][playlistID] = true;
 | 
			
		||||
        this.downloadPlaylist(playlist.fileNames, type, playlist.name, playlistID);
 | 
			
		||||
      } else {
 | 
			
		||||
        localStorage.setItem('player_navigator', this.router.url);
 | 
			
		||||
        const fileNames = playlist.fileNames;
 | 
			
		||||
        this.router.navigate(['/player', {fileNames: fileNames.join('|nvr|'), type: type, id: playlistID, uid: playlistID}]);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      // playlist not found
 | 
			
		||||
      console.error(`Playlist with ID ${playlistID} not found!`);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  downloadPlaylist(fileNames, type, zipName = null, playlistID = null) {
 | 
			
		||||
    this.postsService.downloadFileFromServer(fileNames, type, zipName).subscribe(res => {
 | 
			
		||||
      if (playlistID) { this.downloading_content[type][playlistID] = false };
 | 
			
		||||
      const blob: Blob = res;
 | 
			
		||||
      saveAs(blob, zipName + '.zip');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  deletePlaylist(args) {
 | 
			
		||||
    const playlist = args.file;
 | 
			
		||||
    const index = args.index;
 | 
			
		||||
    const playlistID = playlist.id;
 | 
			
		||||
    this.postsService.removePlaylist(playlistID, 'audio').subscribe(res => {
 | 
			
		||||
      if (res['success']) {
 | 
			
		||||
        this.playlists.splice(index, 1);
 | 
			
		||||
        this.postsService.openSnackBar('Playlist successfully removed.', '');
 | 
			
		||||
      }
 | 
			
		||||
      this.getAllPlaylists();
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  editPlaylistDialog(args) {
 | 
			
		||||
    const playlist = args.playlist;
 | 
			
		||||
    const index = args.index;
 | 
			
		||||
    const dialogRef = this.dialog.open(ModifyPlaylistComponent, {
 | 
			
		||||
      data: {
 | 
			
		||||
        playlist: playlist,
 | 
			
		||||
        width: '65vw'
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    dialogRef.afterClosed().subscribe(res => {
 | 
			
		||||
      // updates playlist in file manager if it changed
 | 
			
		||||
      if (dialogRef.componentInstance.playlist_updated) {
 | 
			
		||||
        this.playlists[index] = dialogRef.componentInstance.original_playlist;
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,34 @@
 | 
			
		||||
<div class="flex-grid">
 | 
			
		||||
    <div class="col">
 | 
			
		||||
        <div style="display: inline-block;">
 | 
			
		||||
            <mat-form-field style="width: 132px;">
 | 
			
		||||
                <mat-select [(ngModel)]="this.filterProperty" (selectionChange)="filterOptionChanged($event.value)">
 | 
			
		||||
                    <mat-option *ngFor="let filterOption of filterProperties | keyvalue" [value]="filterOption.value">
 | 
			
		||||
                        {{filterOption['value']['label']}}
 | 
			
		||||
                    </mat-option>
 | 
			
		||||
                </mat-select>
 | 
			
		||||
            </mat-form-field>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div style="display: inline-block;">
 | 
			
		||||
            <button (click)="toggleModeChange()" mat-icon-button><mat-icon>{{descendingMode ? 'arrow_downward' : 'arrow_upward'}}</mat-icon></button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="col">
 | 
			
		||||
        <h4 style="text-align: center">My videos</h4>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="col" style="top: 25px;">
 | 
			
		||||
        <mat-form-field [ngClass]="searchIsFocused ? 'search-bar-focused' : 'search-bar-unfocused'" class="search-bar" color="accent">
 | 
			
		||||
            <input (focus)="searchIsFocused = true" (blur)="searchIsFocused = false" class="search-input" type="text" placeholder="Search" i18n-placeholder="Files search placeholder" [(ngModel)]="search_text" (ngModelChange)="onSearchInputChanged($event)" matInput>
 | 
			
		||||
            <mat-icon matSuffix>search</mat-icon>
 | 
			
		||||
        </mat-form-field>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
<div>
 | 
			
		||||
    <div class="container">
 | 
			
		||||
        <div class="row justify-content-center">
 | 
			
		||||
            <div *ngFor="let file of filtered_files; let i = index" [ngClass]="[ postsService.card_size === 'small' ? 'col-2 mb-2 mt-2 file-col' : '', postsService.card_size === 'medium' ? 'col-6 col-lg-4 mb-2 mt-2 file-col' : '' ]">
 | 
			
		||||
                <app-unified-file-card [index]="i" [card_size]="postsService.card_size" (goToFile)="goToFile($event)" (goToSubscription)="goToSubscription($event)" [file_obj]="file" [use_youtubedl_archive]="postsService.config['Downloader']['use_youtubedl_archive']" (deleteFile)="deleteFile($event)"></app-unified-file-card>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,34 @@
 | 
			
		||||
.file-col {
 | 
			
		||||
    max-width: 240px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.search-bar {
 | 
			
		||||
    transition: all .5s ease;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    float: right;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
.search-bar-unfocused {
 | 
			
		||||
    width: 100px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.search-input {
 | 
			
		||||
    transition: all .5s ease;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.search-bar-focused {
 | 
			
		||||
    width: 200px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.flex-grid {
 | 
			
		||||
    width: 100%;
 | 
			
		||||
    display: block;
 | 
			
		||||
    position: relative;
 | 
			
		||||
    padding-left: 12px;
 | 
			
		||||
    padding-right: 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.col {
 | 
			
		||||
    width: 33%;
 | 
			
		||||
    display: inline-block;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,25 @@
 | 
			
		||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { RecentVideosComponent } from './recent-videos.component';
 | 
			
		||||
 | 
			
		||||
describe('RecentVideosComponent', () => {
 | 
			
		||||
  let component: RecentVideosComponent;
 | 
			
		||||
  let fixture: ComponentFixture<RecentVideosComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async(() => {
 | 
			
		||||
    TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ RecentVideosComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(RecentVideosComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,262 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { PostsService } from 'app/posts.services';
 | 
			
		||||
import { Router } from '@angular/router';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-recent-videos',
 | 
			
		||||
  templateUrl: './recent-videos.component.html',
 | 
			
		||||
  styleUrls: ['./recent-videos.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class RecentVideosComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  normal_files_received = false;
 | 
			
		||||
  subscription_files_received = false;
 | 
			
		||||
  files: any[] = null;
 | 
			
		||||
  filtered_files: any[] = null;
 | 
			
		||||
  downloading_content = {'video': {}, 'audio': {}};
 | 
			
		||||
  search_mode = false;
 | 
			
		||||
  search_text = '';
 | 
			
		||||
  searchIsFocused = false;
 | 
			
		||||
  descendingMode = true;
 | 
			
		||||
  filterProperties = {
 | 
			
		||||
    'registered': {
 | 
			
		||||
      'key': 'registered',
 | 
			
		||||
      'label': 'Download Date',
 | 
			
		||||
      'property': 'registered'
 | 
			
		||||
    },
 | 
			
		||||
    'upload_date': {
 | 
			
		||||
      'key': 'upload_date',
 | 
			
		||||
      'label': 'Upload Date',
 | 
			
		||||
      'property': 'upload_date'
 | 
			
		||||
    },
 | 
			
		||||
    'name': {
 | 
			
		||||
      'key': 'name',
 | 
			
		||||
      'label': 'Name',
 | 
			
		||||
      'property': 'title'
 | 
			
		||||
    },
 | 
			
		||||
    'file_size': {
 | 
			
		||||
      'key': 'file_size',
 | 
			
		||||
      'label': 'File Size',
 | 
			
		||||
      'property': 'size'
 | 
			
		||||
    },
 | 
			
		||||
    'duration': {
 | 
			
		||||
      'key': 'duration',
 | 
			
		||||
      'label': 'Duration',
 | 
			
		||||
      'property': 'duration'
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
  filterProperty = this.filterProperties['upload_date'];
 | 
			
		||||
 | 
			
		||||
  constructor(public postsService: PostsService, private router: Router) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.postsService.service_initialized.subscribe(init => {
 | 
			
		||||
      if (init) {
 | 
			
		||||
        this.getAllFiles();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // set filter property to cached
 | 
			
		||||
    const cached_filter_property = localStorage.getItem('filter_property');
 | 
			
		||||
    if (cached_filter_property && this.filterProperties[cached_filter_property]) {
 | 
			
		||||
      this.filterProperty = this.filterProperties[cached_filter_property];
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // search
 | 
			
		||||
 | 
			
		||||
  onSearchInputChanged(newvalue) {
 | 
			
		||||
    if (newvalue.length > 0) {
 | 
			
		||||
      this.search_mode = true;
 | 
			
		||||
      this.filterFiles(newvalue);
 | 
			
		||||
    } else {
 | 
			
		||||
      this.search_mode = false;
 | 
			
		||||
      this.filtered_files = this.files;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private filterFiles(value: string) {
 | 
			
		||||
    const filterValue = value.toLowerCase();
 | 
			
		||||
    this.filtered_files = this.files.filter(option => option.id.toLowerCase().includes(filterValue));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  filterByProperty(prop) {
 | 
			
		||||
    if (this.descendingMode) {
 | 
			
		||||
      this.filtered_files = this.filtered_files.sort((a, b) => (a[prop] > b[prop] ? -1 : 1));
 | 
			
		||||
    } else {
 | 
			
		||||
      this.filtered_files = this.filtered_files.sort((a, b) => (a[prop] > b[prop] ? 1 : -1));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  filterOptionChanged(value) {
 | 
			
		||||
    this.filterByProperty(value['property']);
 | 
			
		||||
    localStorage.setItem('filter_property', value['key']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  toggleModeChange() {
 | 
			
		||||
    this.descendingMode = !this.descendingMode;
 | 
			
		||||
    this.filterByProperty(this.filterProperty['property']);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // get files
 | 
			
		||||
 | 
			
		||||
  getAllFiles() {
 | 
			
		||||
    this.normal_files_received = false;
 | 
			
		||||
    this.postsService.getAllFiles().subscribe(res => {
 | 
			
		||||
      this.files = res['files'];
 | 
			
		||||
      this.files.forEach(file => {
 | 
			
		||||
        file.duration = typeof file.duration !== 'string' ? file.duration : this.durationStringToNumber(file.duration);
 | 
			
		||||
      });
 | 
			
		||||
      this.files.sort(this.sortFiles);
 | 
			
		||||
      if (this.search_mode) {
 | 
			
		||||
        this.filterFiles(this.search_text);
 | 
			
		||||
      } else {
 | 
			
		||||
        this.filtered_files = this.files;
 | 
			
		||||
      }
 | 
			
		||||
      this.filterByProperty(this.filterProperty['property']);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // navigation
 | 
			
		||||
 | 
			
		||||
  goToFile(file) {
 | 
			
		||||
    if (this.postsService.config['Extra']['download_only_mode']) {
 | 
			
		||||
      this.downloadFile(file);
 | 
			
		||||
    } else {
 | 
			
		||||
      this.navigateToFile(file);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  navigateToFile(file) {
 | 
			
		||||
    localStorage.setItem('player_navigator', this.router.url);
 | 
			
		||||
    if (file.sub_id) {
 | 
			
		||||
      const sub = this.postsService.getSubscriptionByID(file.sub_id)
 | 
			
		||||
      if (sub.streamingOnly) {
 | 
			
		||||
        this.router.navigate(['/player', {name: file.id,
 | 
			
		||||
                                          url: file.requested_formats ? file.requested_formats[0].url : file.url}]);
 | 
			
		||||
      } else {
 | 
			
		||||
        this.router.navigate(['/player', {fileNames: file.id,
 | 
			
		||||
          type: file.isAudio ? 'audio' : 'video', subscriptionName: sub.name,
 | 
			
		||||
          subPlaylist: sub.isPlaylist, uuid: this.postsService.user ? this.postsService.user.uid : null}]);
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      this.router.navigate(['/player', {type: file.isAudio ? 'audio' : 'video', uid: file.uid}]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  goToSubscription(file) {
 | 
			
		||||
    this.router.navigate(['/subscription', {id: file.sub_id}]);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // downloading
 | 
			
		||||
 | 
			
		||||
  downloadFile(file) {
 | 
			
		||||
    if (file.sub_id) {
 | 
			
		||||
      this.downloadSubscriptionFile(file);
 | 
			
		||||
    } else {
 | 
			
		||||
      this.downloadNormalFile(file);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  downloadSubscriptionFile(file) {
 | 
			
		||||
    const type = file.isAudio ? 'audio' : 'video';
 | 
			
		||||
    const ext = type === 'audio' ? '.mp3' : '.mp4'
 | 
			
		||||
    const sub = this.postsService.getSubscriptionByID(file.sub_id);
 | 
			
		||||
    console.log(sub.isPlaylist)
 | 
			
		||||
    this.postsService.downloadFileFromServer(file.id, type, null, null, sub.name, sub.isPlaylist,
 | 
			
		||||
      this.postsService.user ? this.postsService.user.uid : null, null).subscribe(res => {
 | 
			
		||||
          const blob: Blob = res;
 | 
			
		||||
          saveAs(blob, file.id + ext);
 | 
			
		||||
        }, err => {
 | 
			
		||||
          console.log(err);
 | 
			
		||||
      });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  downloadNormalFile(file) {
 | 
			
		||||
    const type = file.isAudio ? 'audio' : 'video';
 | 
			
		||||
    const ext = type === 'audio' ? '.mp3' : '.mp4'
 | 
			
		||||
    const name = file.id;
 | 
			
		||||
    this.downloading_content[type][name] = true;
 | 
			
		||||
    this.postsService.downloadFileFromServer(name, type).subscribe(res => {
 | 
			
		||||
      this.downloading_content[type][name] = false;
 | 
			
		||||
      const blob: Blob = res;
 | 
			
		||||
      saveAs(blob, decodeURIComponent(name) + ext);
 | 
			
		||||
 | 
			
		||||
      if (!this.postsService.config.Extra.file_manager_enabled) {
 | 
			
		||||
        // tell server to delete the file once downloaded
 | 
			
		||||
        this.postsService.deleteFile(name, false).subscribe(delRes => {
 | 
			
		||||
          // reload mp4s
 | 
			
		||||
          this.getAllFiles();
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // deleting
 | 
			
		||||
 | 
			
		||||
  deleteFile(args) {
 | 
			
		||||
    const file = args.file;
 | 
			
		||||
    const index = args.index;
 | 
			
		||||
    const blacklistMode = args.blacklistMode;
 | 
			
		||||
 | 
			
		||||
    if (file.sub_id) {
 | 
			
		||||
      this.deleteSubscriptionFile(file, index, blacklistMode);
 | 
			
		||||
    } else {
 | 
			
		||||
      this.deleteNormalFile(file, index, blacklistMode);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  deleteNormalFile(file, index, blacklistMode = false) {
 | 
			
		||||
    this.postsService.deleteFile(file.uid, file.isAudio, blacklistMode).subscribe(result => {
 | 
			
		||||
      if (result) {
 | 
			
		||||
        this.postsService.openSnackBar('Delete success!', 'OK.');
 | 
			
		||||
        this.files.splice(index, 1);
 | 
			
		||||
      } else {
 | 
			
		||||
        this.postsService.openSnackBar('Delete failed!', 'OK.');
 | 
			
		||||
      }
 | 
			
		||||
    }, err => {
 | 
			
		||||
      this.postsService.openSnackBar('Delete failed!', 'OK.');
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  deleteSubscriptionFile(file, index, blacklistMode = false) {
 | 
			
		||||
    if (blacklistMode) {
 | 
			
		||||
      this.deleteForever(file, index);
 | 
			
		||||
    } else {
 | 
			
		||||
      this.deleteAndRedownload(file, index);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  deleteAndRedownload(file, index) {
 | 
			
		||||
    const sub = this.postsService.getSubscriptionByID(file.sub_id);
 | 
			
		||||
    this.postsService.deleteSubscriptionFile(sub, file.id, false, file.uid).subscribe(res => {
 | 
			
		||||
      this.postsService.openSnackBar(`Successfully deleted file: '${file.id}'`);
 | 
			
		||||
      this.files.splice(index, 1);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  deleteForever(file, index) {
 | 
			
		||||
    const sub = this.postsService.getSubscriptionByID(file.sub_id);
 | 
			
		||||
    this.postsService.deleteSubscriptionFile(sub, file.id, true, file.uid).subscribe(res => {
 | 
			
		||||
      this.postsService.openSnackBar(`Successfully deleted file: '${file.id}'`);
 | 
			
		||||
      this.files.splice(index, 1);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // sorting and filtering
 | 
			
		||||
 | 
			
		||||
  sortFiles(a, b) {
 | 
			
		||||
    // uses the 'registered' flag as the timestamp
 | 
			
		||||
    const result = b.registered - a.registered;
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  durationStringToNumber(dur_str) {
 | 
			
		||||
    let num_sum = 0;
 | 
			
		||||
    const dur_str_parts = dur_str.split(':');
 | 
			
		||||
    for (let i = dur_str_parts.length-1; i >= 0; i--) {
 | 
			
		||||
      num_sum += parseInt(dur_str_parts[i])*(60**(dur_str_parts.length-1-i));
 | 
			
		||||
    }
 | 
			
		||||
    return num_sum;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,39 @@
 | 
			
		||||
<div (mouseover)="elevated=true" (mouseout)="elevated=false" style="position: relative; width: fit-content;">
 | 
			
		||||
    <div class="download-time"><mat-icon class="audio-video-icon">{{(file_obj.type === 'audio' || file_obj.isAudio) ? 'audiotrack' : 'movie'}}</mat-icon>  {{file_obj.registered | date:'shortDate'}}</div>
 | 
			
		||||
    <button [matMenuTriggerFor]="action_menu" class="menuButton" mat-icon-button><mat-icon>more_vert</mat-icon></button>
 | 
			
		||||
    <mat-menu #action_menu="matMenu">
 | 
			
		||||
        <ng-container *ngIf="!is_playlist">
 | 
			
		||||
            <button (click)="openFileInfoDialog()" mat-menu-item><mat-icon>info</mat-icon><ng-container i18n="Video info button">Info</ng-container></button>
 | 
			
		||||
            <button (click)="navigateToSubscription()" mat-menu-item *ngIf="file_obj.sub_id"><mat-icon>{{file_obj.isAudio ? 'library_music' : 'video_library'}}</mat-icon> <ng-container i18n="Go to subscription menu item">Go to subscription</ng-container></button>
 | 
			
		||||
            <mat-divider></mat-divider>
 | 
			
		||||
            <button *ngIf="file_obj.sub_id" (click)="emitDeleteFile()" mat-menu-item>
 | 
			
		||||
                <mat-icon>restore</mat-icon><ng-container i18n="Delete and redownload subscription video button">Delete and redownload</ng-container>
 | 
			
		||||
            </button>
 | 
			
		||||
            <button *ngIf="file_obj.sub_id && use_youtubedl_archive" (click)="emitDeleteFile(true)" mat-menu-item>
 | 
			
		||||
                <mat-icon>delete_forever</mat-icon><ng-container i18n="Delete forever subscription video button">Delete forever</ng-container>
 | 
			
		||||
            </button>
 | 
			
		||||
            <button *ngIf="!file_obj.sub_id" (click)="emitDeleteFile()" mat-menu-item><mat-icon>delete</mat-icon><ng-container i18n="Delete video button">Delete</ng-container></button>
 | 
			
		||||
            <button *ngIf="!file_obj.sub_id && use_youtubedl_archive" (click)="emitDeleteFile(true)" mat-menu-item><mat-icon>delete_forever</mat-icon><ng-container i18n="Delete and blacklist video button">Delete and blacklist</ng-container></button>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
        <ng-container *ngIf="is_playlist">
 | 
			
		||||
            <button (click)="emitEditPlaylist()" mat-menu-item><mat-icon>edit</mat-icon><ng-container i18n="Playlist edit button">Edit</ng-container></button>
 | 
			
		||||
            <mat-divider></mat-divider>
 | 
			
		||||
            <button (click)="emitDeleteFile()" mat-menu-item><mat-icon>delete_forever</mat-icon><ng-container i18n="Delete playlist">Delete</ng-container></button>
 | 
			
		||||
        </ng-container>
 | 
			
		||||
    </mat-menu>
 | 
			
		||||
    <mat-card [matTooltip]="null" (click)="navigateToFile()" matRipple class="file-mat-card" [ngClass]="{'small-mat-card': card_size === 'small', 'file-mat-card': card_size === 'medium', 'mat-elevation-z4': !elevated, 'mat-elevation-z8': elevated}">
 | 
			
		||||
        <div style="padding:5px">
 | 
			
		||||
            <div *ngIf="file_obj.thumbnailURL" class="img-div">
 | 
			
		||||
                <div style="position: relative">
 | 
			
		||||
                    <img [ngClass]="{'image-small': card_size === 'small', 'image': card_size === 'medium'}" [src]="file_obj.thumbnailURL" alt="Thumbnail">
 | 
			
		||||
                    <div class="duration-time">
 | 
			
		||||
                        {{file_length}}
 | 
			
		||||
                    </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                
 | 
			
		||||
            </div>
 | 
			
		||||
 | 
			
		||||
            <span [ngClass]="{'max-two-lines': card_size !== 'small', 'max-one-line': card_size === 'small' }"><strong>{{!is_playlist ? file_obj.title : file_obj.name}}</strong></span>      
 | 
			
		||||
        </div>
 | 
			
		||||
    </mat-card>
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,117 @@
 | 
			
		||||
.file-mat-card {
 | 
			
		||||
    width: 200px;
 | 
			
		||||
    height: 200px;
 | 
			
		||||
    padding: 0px;
 | 
			
		||||
    cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.small-mat-card {
 | 
			
		||||
  width: 150px;
 | 
			
		||||
  height: 150px;
 | 
			
		||||
  padding: 0px;
 | 
			
		||||
  cursor: pointer;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.menuButton {
 | 
			
		||||
  right: 0px;
 | 
			
		||||
  top: -1px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  z-index: 999;
 | 
			
		||||
  
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Coerce the <span> icon container away from display:inline */
 | 
			
		||||
.mat-icon-button .mat-button-wrapper {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.image {
 | 
			
		||||
  width: 200px;
 | 
			
		||||
  height: 112.5px;
 | 
			
		||||
  object-fit: cover;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.image-small {
 | 
			
		||||
  width: 150px;
 | 
			
		||||
  height: 84.5px;
 | 
			
		||||
  object-fit: cover;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.example-full-width-height {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  height: 100%
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.centered {
 | 
			
		||||
  margin: 0 auto;
 | 
			
		||||
  top: 50%;
 | 
			
		||||
  left: 50%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.img-div {
 | 
			
		||||
  max-height: 80px;
 | 
			
		||||
  padding: 0px;
 | 
			
		||||
  margin: 32px 0px 0px -5px;
 | 
			
		||||
  width: calc(100% + 5px + 5px);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.max-two-lines {
 | 
			
		||||
  display: -webkit-box;
 | 
			
		||||
  display: -moz-box;
 | 
			
		||||
  max-height: 2.4em;
 | 
			
		||||
  line-height: 1.2em;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  -webkit-box-orient: vertical;
 | 
			
		||||
  -webkit-line-clamp: 2;
 | 
			
		||||
  bottom: 5px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.max-one-line {
 | 
			
		||||
  display: -webkit-box;
 | 
			
		||||
  display: -moz-box;
 | 
			
		||||
  max-height: 1.2em;
 | 
			
		||||
  line-height: 1.2em;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
  -webkit-box-orient: vertical;
 | 
			
		||||
  -webkit-line-clamp: 2;
 | 
			
		||||
  bottom: 5px;
 | 
			
		||||
  position: absolute;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.duration-time {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  bottom: 5px;
 | 
			
		||||
  right: 5px;
 | 
			
		||||
  z-index: 99999;
 | 
			
		||||
  background: rgba(255,255,255,0.6);
 | 
			
		||||
  padding-left: 5px;
 | 
			
		||||
  padding-right: 5px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.download-time {
 | 
			
		||||
  position: absolute;
 | 
			
		||||
  top: 1px;
 | 
			
		||||
  left: 5px;
 | 
			
		||||
  z-index: 99999;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.audio-video-icon {
 | 
			
		||||
  position: relative;
 | 
			
		||||
  top: 6px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@media (max-width: 576px){
 | 
			
		||||
  
 | 
			
		||||
  // .example-card {
 | 
			
		||||
  //   width: 175px !important;
 | 
			
		||||
  // }
 | 
			
		||||
 | 
			
		||||
  // .image {
 | 
			
		||||
  //     width: 175px;
 | 
			
		||||
  // }
 | 
			
		||||
    
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,25 @@
 | 
			
		||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { UnifiedFileCardComponent } from './unified-file-card.component';
 | 
			
		||||
 | 
			
		||||
describe('UnifiedFileCardComponent', () => {
 | 
			
		||||
  let component: UnifiedFileCardComponent;
 | 
			
		||||
  let fixture: ComponentFixture<UnifiedFileCardComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async(() => {
 | 
			
		||||
    TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ UnifiedFileCardComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(UnifiedFileCardComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,95 @@
 | 
			
		||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
 | 
			
		||||
import { MatDialog } from '@angular/material/dialog';
 | 
			
		||||
import { VideoInfoDialogComponent } from 'app/dialogs/video-info-dialog/video-info-dialog.component';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-unified-file-card',
 | 
			
		||||
  templateUrl: './unified-file-card.component.html',
 | 
			
		||||
  styleUrls: ['./unified-file-card.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class UnifiedFileCardComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
  // required info
 | 
			
		||||
  file_title = '';
 | 
			
		||||
  file_length = '';
 | 
			
		||||
  file_thumbnail = '';
 | 
			
		||||
  type = null;
 | 
			
		||||
  elevated = false;
 | 
			
		||||
 | 
			
		||||
  @Input() file_obj = null;
 | 
			
		||||
  @Input() card_size = 'medium';
 | 
			
		||||
  @Input() use_youtubedl_archive = false;
 | 
			
		||||
  @Input() is_playlist = false;
 | 
			
		||||
  @Input() index: number;
 | 
			
		||||
  @Output() goToFile = new EventEmitter<any>();
 | 
			
		||||
  @Output() goToSubscription = new EventEmitter<any>();
 | 
			
		||||
  @Output() deleteFile = new EventEmitter<any>();
 | 
			
		||||
  @Output() editPlaylist = new EventEmitter<any>();
 | 
			
		||||
 | 
			
		||||
  /*
 | 
			
		||||
    Planned sizes:
 | 
			
		||||
    small: 150x175
 | 
			
		||||
    medium: 200x200
 | 
			
		||||
    big: 250x200
 | 
			
		||||
  */
 | 
			
		||||
 | 
			
		||||
  constructor(private dialog: MatDialog) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.file_length = fancyTimeFormat(this.file_obj.duration);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  emitDeleteFile(blacklistMode = false) {
 | 
			
		||||
    this.deleteFile.emit({
 | 
			
		||||
      file: this.file_obj,
 | 
			
		||||
      index: this.index,
 | 
			
		||||
      blacklistMode: blacklistMode
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  navigateToFile() {
 | 
			
		||||
    this.goToFile.emit(this.file_obj);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  navigateToSubscription() {
 | 
			
		||||
    this.goToSubscription.emit(this.file_obj);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  openFileInfoDialog() {
 | 
			
		||||
    this.dialog.open(VideoInfoDialogComponent, {
 | 
			
		||||
      data: {
 | 
			
		||||
        file: this.file_obj,
 | 
			
		||||
      },
 | 
			
		||||
      minWidth: '50vw'
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  emitEditPlaylist() {
 | 
			
		||||
    this.editPlaylist.emit({
 | 
			
		||||
      playlist: this.file_obj,
 | 
			
		||||
      index: this.index
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function fancyTimeFormat(time) {
 | 
			
		||||
  if (typeof time === 'string') {
 | 
			
		||||
    return time;
 | 
			
		||||
  }
 | 
			
		||||
  // Hours, minutes and seconds
 | 
			
		||||
  const hrs = ~~(time / 3600);
 | 
			
		||||
  const mins = ~~((time % 3600) / 60);
 | 
			
		||||
  const secs = ~~time % 60;
 | 
			
		||||
 | 
			
		||||
  // Output like "1:01" or "4:03:59" or "123:03:59"
 | 
			
		||||
  let ret = '';
 | 
			
		||||
 | 
			
		||||
  if (hrs > 0) {
 | 
			
		||||
      ret += '' + hrs + ':' + (mins < 10 ? '0' : '');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ret += '' + mins + ':' + (secs < 10 ? '0' : '');
 | 
			
		||||
  ret += '' + secs;
 | 
			
		||||
  return ret;
 | 
			
		||||
}
 | 
			
		||||
					Loading…
					
					
				
		Reference in New Issue