mirror of
https://github.com/Tzahi12345/YoutubeDL-Material.git
synced 2026-03-07 20:10:03 +03:00
Unified create and modify playlist components
This commit is contained in:
@@ -45,6 +45,9 @@ export class CustomPlaylistsComponent implements OnInit {
|
||||
// creating a playlist
|
||||
openCreatePlaylistDialog(): void {
|
||||
const dialogRef = this.dialog.open(CreatePlaylistComponent, {
|
||||
data: {
|
||||
create_mode: true
|
||||
},
|
||||
minWidth: '90vw',
|
||||
minHeight: '95vh'
|
||||
});
|
||||
@@ -103,9 +106,10 @@ export class CustomPlaylistsComponent implements OnInit {
|
||||
editPlaylistDialog(args: { playlist: Playlist; index: number; }): void {
|
||||
const playlist = args.playlist;
|
||||
const index = args.index;
|
||||
const dialogRef = this.dialog.open(ModifyPlaylistComponent, {
|
||||
const dialogRef = this.dialog.open(CreatePlaylistComponent, {
|
||||
data: {
|
||||
playlist_id: playlist.id
|
||||
playlist_id: playlist.id,
|
||||
create_mode: false
|
||||
},
|
||||
minWidth: '85vw'
|
||||
});
|
||||
@@ -113,7 +117,7 @@ export class CustomPlaylistsComponent implements OnInit {
|
||||
dialogRef.afterClosed().subscribe(() => {
|
||||
// updates playlist in file manager if it changed
|
||||
if (dialogRef.componentInstance.playlist_updated) {
|
||||
this.playlists[index] = dialogRef.componentInstance.original_playlist;
|
||||
this.playlists[index] = dialogRef.componentInstance.playlist;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,31 +48,53 @@
|
||||
</div>
|
||||
|
||||
<div *ngIf="selectMode">
|
||||
<mat-selection-list *ngIf="normal_files_received" (selectionChange)="fileSelectionChanged($event)">
|
||||
<mat-list-option [selected]="selected_data.includes(file.uid)" *ngFor="let file of paged_data" [value]="file.uid">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-10">
|
||||
<mat-icon class="audio-video-icon">{{(file.type === 'audio' || file.isAudio) ? 'audiotrack' : 'movie'}}</mat-icon>
|
||||
{{file.title}}
|
||||
</div>
|
||||
<div class="col-2">{{file.registered | date:'shortDate'}}</div>
|
||||
</div>
|
||||
<mat-tab-group [(selectedIndex)]="selectedIndex">
|
||||
<mat-tab label="Order" i18n-label="Order">
|
||||
<div *ngIf="selected_data.length">
|
||||
<span *ngIf="reverse_order === false" i18n="Normal order">Normal order </span>
|
||||
<span *ngIf="reverse_order === true" i18n="Reverse order">Reverse order </span>
|
||||
<button (click)="toggleSelectionOrder()" mat-icon-button><mat-icon>{{!reverse_order ? 'arrow_downward' : 'arrow_upward'}}</mat-icon></button>
|
||||
</div>
|
||||
|
||||
</mat-list-option>
|
||||
</mat-selection-list>
|
||||
|
||||
<ng-container *ngIf="!normal_files_received && loading_files && loading_files.length > 0">
|
||||
<mat-selection-list *ngIf="!normal_files_received">
|
||||
<mat-list-option *ngFor="let file of paged_data">
|
||||
<content-loader class="list-ghosts" [primaryColor]="postsService.theme.ghost_primary" [secondaryColor]="postsService.theme.ghost_secondary" width="250" height="8"><svg:rect x="0" y="0" rx="3" ry="3" width="250" height="8" /></content-loader>
|
||||
</mat-list-option>
|
||||
</mat-selection-list>
|
||||
</ng-container>
|
||||
<!-- Selection order -->
|
||||
<mat-button-toggle-group *ngIf="selected_data.length" class="media-list" cdkDropList (cdkDropListDropped)="drop($event)" style="width: 80%; left: 9%" vertical #group="matButtonToggleGroup">
|
||||
<!-- The following for loop can be optimized but it requires a pipe https://stackoverflow.com/a/35703364/8088021 -->
|
||||
<mat-button-toggle class="media-box" cdkDrag *ngFor="let file of (reverse_order ? selected_data_objs.slice().reverse() : selected_data_objs); let i = index" [checked]="false"><div><div class="playlist-item-text">{{file.title}}</div> <button (click)="removeSelectedFile(i)" class="remove-item-button" mat-icon-button><mat-icon>cancel</mat-icon></button></div></mat-button-toggle>
|
||||
</mat-button-toggle-group>
|
||||
|
||||
<div style="margin-top: 20px;" *ngIf="!selected_data.length">
|
||||
<h4 style="text-align: center;">No files selected!</h4>
|
||||
</div>
|
||||
|
||||
</mat-tab>
|
||||
<mat-tab label="Select files" i18n-label="Select files">
|
||||
<mat-selection-list *ngIf="normal_files_received" (selectionChange)="fileSelectionChanged($event)">
|
||||
<mat-list-option [selected]="selected_data.includes(file.uid)" *ngFor="let file of paged_data" [value]="file">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-10">
|
||||
<mat-icon class="audio-video-icon">{{(file.type === 'audio' || file.isAudio) ? 'audiotrack' : 'movie'}}</mat-icon>
|
||||
{{file.title}}
|
||||
</div>
|
||||
<div class="col-2">{{file.registered | date:'shortDate'}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</mat-list-option>
|
||||
</mat-selection-list>
|
||||
|
||||
<ng-container *ngIf="!normal_files_received && loading_files && loading_files.length > 0">
|
||||
<mat-selection-list *ngIf="!normal_files_received">
|
||||
<mat-list-option *ngFor="let file of paged_data">
|
||||
<content-loader class="list-ghosts" [primaryColor]="postsService.theme.ghost_primary" [secondaryColor]="postsService.theme.ghost_secondary" width="250" height="8"><svg:rect x="0" y="0" rx="3" ry="3" width="250" height="8" /></content-loader>
|
||||
</mat-list-option>
|
||||
</mat-selection-list>
|
||||
</ng-container>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
|
||||
<div *ngIf="usePaginator">
|
||||
<div style="position: relative;" *ngIf="usePaginator && selectedIndex > 0">
|
||||
<div style="position: absolute; margin-left: 8px; margin-top: 5px; scale: 0.8">
|
||||
<mat-form-field>
|
||||
<mat-label><ng-container i18n="File type">File type</ng-container></mat-label>
|
||||
|
||||
@@ -71,4 +71,42 @@
|
||||
.audio-video-icon {
|
||||
position: relative;
|
||||
top: 6px;
|
||||
}
|
||||
|
||||
.cdk-drag-preview {
|
||||
box-sizing: border-box;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
|
||||
0 8px 10px 1px rgba(0, 0, 0, 0.14),
|
||||
0 3px 14px 2px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.cdk-drag-placeholder {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.cdk-drag-animating {
|
||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.media-box:last-child {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.media-list.cdk-drop-list-dragging .media-box:not(.cdk-drag-placeholder) {
|
||||
transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.remove-item-button {
|
||||
right: 10px;
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
.playlist-item-text {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 70%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { DatabaseFile, FileType, FileTypeFilter } from '../../../api-types';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { Subject } from 'rxjs';
|
||||
import { distinctUntilChanged } from 'rxjs/operators';
|
||||
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
|
||||
|
||||
@Component({
|
||||
selector: 'app-recent-videos',
|
||||
@@ -14,11 +15,25 @@ import { distinctUntilChanged } from 'rxjs/operators';
|
||||
export class RecentVideosComponent implements OnInit {
|
||||
|
||||
@Input() usePaginator = true;
|
||||
|
||||
// File selection
|
||||
|
||||
@Input() selectMode = false;
|
||||
@Input() defaultSelected: DatabaseFile[] = [];
|
||||
@Input() sub_id = null;
|
||||
@Input() customHeader = null;
|
||||
@Input() selectedIndex = 1;
|
||||
@Output() fileSelectionEmitter = new EventEmitter<{new_selection: string[], thumbnailURL: string}>();
|
||||
|
||||
pageSize = 10;
|
||||
paged_data: DatabaseFile[] = null;
|
||||
|
||||
selected_data: string[] = [];
|
||||
selected_data_objs: DatabaseFile[] = [];
|
||||
reverse_order = false;
|
||||
|
||||
// File listing (with cards)
|
||||
|
||||
cached_file_count = 0;
|
||||
loading_files = null;
|
||||
|
||||
@@ -63,20 +78,32 @@ export class RecentVideosComponent implements OnInit {
|
||||
|
||||
playlists = null;
|
||||
|
||||
pageSize = 10;
|
||||
paged_data: DatabaseFile[] = null;
|
||||
|
||||
selected_data: string[] = [];
|
||||
|
||||
@ViewChild('paginator') paginator: MatPaginator
|
||||
|
||||
constructor(public postsService: PostsService, private router: Router) {
|
||||
// get cached file count
|
||||
if (localStorage.getItem('cached_file_count')) {
|
||||
this.cached_file_count = +localStorage.getItem('cached_file_count') <= 10 ? +localStorage.getItem('cached_file_count') : 10;
|
||||
|
||||
this.loading_files = Array(this.cached_file_count).fill(0);
|
||||
}
|
||||
|
||||
// set filter property to cached value
|
||||
const cached_filter_property = localStorage.getItem('filter_property');
|
||||
if (cached_filter_property && this.filterProperties[cached_filter_property]) {
|
||||
this.filterProperty = this.filterProperties[cached_filter_property];
|
||||
}
|
||||
|
||||
// set file type filter to cached value
|
||||
const cached_file_type_filter = localStorage.getItem('file_type_filter');
|
||||
if (this.usePaginator && cached_file_type_filter) {
|
||||
this.fileTypeFilter = cached_file_type_filter;
|
||||
}
|
||||
|
||||
const sort_order = localStorage.getItem('recent_videos_sort_order');
|
||||
|
||||
if (sort_order) {
|
||||
this.descendingMode = sort_order === 'descending';
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@@ -104,23 +131,9 @@ export class RecentVideosComponent implements OnInit {
|
||||
}
|
||||
});
|
||||
|
||||
// set filter property to cached value
|
||||
const cached_filter_property = localStorage.getItem('filter_property');
|
||||
if (cached_filter_property && this.filterProperties[cached_filter_property]) {
|
||||
this.filterProperty = this.filterProperties[cached_filter_property];
|
||||
}
|
||||
|
||||
// set file type filter to cached value
|
||||
const cached_file_type_filter = localStorage.getItem('file_type_filter');
|
||||
if (this.usePaginator && cached_file_type_filter) {
|
||||
this.fileTypeFilter = cached_file_type_filter;
|
||||
}
|
||||
|
||||
const sort_order = localStorage.getItem('recent_videos_sort_order');
|
||||
|
||||
if (sort_order) {
|
||||
this.descendingMode = sort_order === 'descending';
|
||||
}
|
||||
|
||||
this.selected_data = this.defaultSelected.map(file => file.uid);
|
||||
this.selected_data_objs = this.defaultSelected;
|
||||
|
||||
this.searchChangedSubject
|
||||
.debounceTime(500)
|
||||
@@ -364,20 +377,41 @@ export class RecentVideosComponent implements OnInit {
|
||||
this.getAllFiles();
|
||||
}
|
||||
|
||||
fileSelectionChanged(event): void {
|
||||
fileSelectionChanged(event: { option: { _selected: boolean; value: DatabaseFile; } }): void {
|
||||
const adding = event.option._selected;
|
||||
const value = event.option.value;
|
||||
if (adding)
|
||||
this.selected_data.push(value);
|
||||
else
|
||||
this.selected_data = this.selected_data.filter(e => e !== value);
|
||||
|
||||
let thumbnail_url = null;
|
||||
if (this.selected_data.length) {
|
||||
const file_obj = this.paged_data.find(file => file.uid === this.selected_data[0]);
|
||||
if (file_obj) { thumbnail_url = file_obj['thumbnailURL'] }
|
||||
if (adding) {
|
||||
this.selected_data.push(value.uid);
|
||||
this.selected_data_objs.push(value);
|
||||
} else {
|
||||
this.selected_data = this.selected_data.filter(e => e !== value.uid);
|
||||
this.selected_data_objs = this.selected_data_objs.filter(e => e.uid !== value.uid);
|
||||
}
|
||||
|
||||
this.fileSelectionEmitter.emit({new_selection: this.selected_data, thumbnailURL: thumbnail_url});
|
||||
this.fileSelectionEmitter.emit({new_selection: this.selected_data, thumbnailURL: this.selected_data_objs[0].thumbnailURL});
|
||||
}
|
||||
|
||||
toggleSelectionOrder(): void {
|
||||
this.reverse_order = !this.reverse_order;
|
||||
localStorage.setItem('default_playlist_order_reversed', '' + this.reverse_order);
|
||||
}
|
||||
|
||||
drop(event: CdkDragDrop<string[]>): void {
|
||||
if (this.reverse_order) {
|
||||
event.previousIndex = this.selected_data.length - 1 - event.previousIndex;
|
||||
event.currentIndex = this.selected_data.length - 1 - event.currentIndex;
|
||||
}
|
||||
moveItemInArray(this.selected_data, event.previousIndex, event.currentIndex);
|
||||
moveItemInArray(this.selected_data_objs, event.previousIndex, event.currentIndex);
|
||||
this.fileSelectionEmitter.emit({new_selection: this.selected_data, thumbnailURL: this.selected_data_objs[0].thumbnailURL});
|
||||
}
|
||||
|
||||
removeSelectedFile(index: number): void {
|
||||
if (this.reverse_order) {
|
||||
index = this.selected_data.length - 1 - index;
|
||||
}
|
||||
this.selected_data.splice(index, 1);
|
||||
this.selected_data_objs.splice(index, 1);
|
||||
this.fileSelectionEmitter.emit({new_selection: this.selected_data, thumbnailURL: this.selected_data_objs[0].thumbnailURL});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,29 @@
|
||||
<h4 mat-dialog-title i18n="Create a playlist dialog title">Create a playlist</h4>
|
||||
<form>
|
||||
<div>
|
||||
<div>
|
||||
<mat-form-field color="accent">
|
||||
<input [(ngModel)]="name" matInput placeholder="Name" i18n-placeholder="Playlist name placeholder" type="text" required aria-required [ngModelOptions]="{standalone: true}">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<app-recent-videos [selectMode]="true" [customHeader]="'Select files'" (fileSelectionEmitter)="fileSelectionChanged($event)"></app-recent-videos>
|
||||
</div>
|
||||
</form>
|
||||
<div class="fixActionRow">
|
||||
<h4 mat-dialog-title *ngIf="create_mode" ><ng-container i18n="Create a playlist dialog title">Create a playlist</ng-container></h4>
|
||||
<h4 mat-dialog-title *ngIf="!create_mode"><ng-container i18n="Modify playlist dialog title">Modify playlist</ng-container></h4>
|
||||
|
||||
<div *ngIf="create_in_progress" style="float: left"><mat-spinner [diameter]="25"></mat-spinner></div>
|
||||
<button (click)="createPlaylist()" [disabled]="!name || !filesSelect.value || filesSelect.value.length === 0" color="primary" style="float: right" mat-flat-button>Create</button>
|
||||
<mat-dialog-content style="max-height: 85vh;">
|
||||
<form>
|
||||
<div *ngIf="create_mode || playlist">
|
||||
<div>
|
||||
<mat-form-field color="accent">
|
||||
<input [(ngModel)]="name" matInput placeholder="Name" i18n-placeholder="Playlist name placeholder" type="text" required aria-required [ngModelOptions]="{standalone: true}">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<app-recent-videos [selectMode]="true" [defaultSelected]="preselected_files" [customHeader]="'Select files'" (fileSelectionEmitter)="fileSelectionChanged($event)" [selectedIndex]="create_mode ? 1 : 0"></app-recent-videos>
|
||||
</div>
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
<mat-dialog-actions>
|
||||
<div *ngIf="create_in_progress" style="float: left"><mat-spinner [diameter]="25"></mat-spinner></div>
|
||||
<button *ngIf="create_mode" (click)="createPlaylist()" [disabled]="!name || !filesSelect.value || filesSelect.value.length === 0" color="primary" style="float: right" mat-flat-button>
|
||||
<ng-container i18n="Create button">Create</ng-container>
|
||||
</button>
|
||||
<button *ngIf="!create_mode" (click)="updatePlaylist()" [disabled]="!name || !playlistChanged()" color="primary" style="float: right" mat-flat-button>
|
||||
<ng-container i18n="Save button">Save</ng-container>
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
.fixActionRow {
|
||||
height: 89vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { Component, OnInit, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { PostsService } from 'app/posts.services';
|
||||
import { Playlist } from 'api-types';
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-playlist',
|
||||
@@ -20,9 +21,24 @@ export class CreatePlaylistComponent implements OnInit {
|
||||
cached_thumbnail_url = null;
|
||||
|
||||
create_in_progress = false;
|
||||
create_mode = false;
|
||||
|
||||
constructor(private postsService: PostsService,
|
||||
public dialogRef: MatDialogRef<CreatePlaylistComponent>) { }
|
||||
// playlist modify mode
|
||||
|
||||
playlist: Playlist = null;
|
||||
playlist_id: string = null;
|
||||
preselected_files = [];
|
||||
playlist_updated = false;
|
||||
|
||||
constructor(@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
private postsService: PostsService,
|
||||
public dialogRef: MatDialogRef<CreatePlaylistComponent>) {
|
||||
if (this.data?.create_mode) this.create_mode = true;
|
||||
if (this.data?.playlist_id) {
|
||||
this.playlist_id = this.data.playlist_id;
|
||||
this.getPlaylist();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ngOnInit(): void {}
|
||||
@@ -40,6 +56,17 @@ export class CreatePlaylistComponent implements OnInit {
|
||||
});
|
||||
}
|
||||
|
||||
updatePlaylist(): void {
|
||||
this.playlist['name'] = this.name;
|
||||
this.playlist['uids'] = this.filesSelect.value;
|
||||
this.playlist_updated = true;
|
||||
this.postsService.updatePlaylist(this.playlist).subscribe(() => {
|
||||
this.postsService.openSnackBar('Playlist updated successfully.');
|
||||
this.getPlaylist();
|
||||
this.postsService.playlists_changed.next(true);
|
||||
});
|
||||
}
|
||||
|
||||
getThumbnailURL(): string {
|
||||
return this.cached_thumbnail_url;
|
||||
}
|
||||
@@ -49,4 +76,19 @@ export class CreatePlaylistComponent implements OnInit {
|
||||
if (new_selection.length) this.cached_thumbnail_url = thumbnailURL;
|
||||
else this.cached_thumbnail_url = null;
|
||||
}
|
||||
|
||||
playlistChanged(): boolean {
|
||||
return JSON.stringify(this.playlist.uids) !== JSON.stringify(this.filesSelect.value) || this.name !== this.playlist.name;
|
||||
}
|
||||
|
||||
getPlaylist(): void {
|
||||
this.postsService.getPlaylist(this.playlist_id, null, true).subscribe(res => {
|
||||
if (res['playlist']) {
|
||||
this.filesSelect.setValue(res['file_objs'].map(file => file.uid));
|
||||
this.preselected_files = res['file_objs'];
|
||||
this.playlist = res['playlist'];
|
||||
this.name = this.playlist['name'];
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user