Merge branch 'master' of https://github.com/Tzahi12345/YoutubeDL-Material into homepage-redesign

This commit is contained in:
Isaac Abadi
2020-08-02 16:24:07 -04:00
38 changed files with 524 additions and 427 deletions

View File

@@ -23,7 +23,7 @@
<span i18n="Dark mode toggle label">Dark</span>
<mat-slide-toggle class="theme-slide-toggle" [checked]="postsService.theme.key === 'dark'"></mat-slide-toggle>
</button>
<button *ngIf="!postsService.isLoggedIn || postsService.permissions.includes('settings')" (click)="openSettingsDialog()" mat-menu-item>
<button *ngIf="postsService.config && (!postsService.config.Advanced.multi_user_mode || (postsService.isLoggedIn && postsService.permissions.includes('settings')))" (click)="openSettingsDialog()" mat-menu-item>
<mat-icon>settings</mat-icon>
<span i18n="Settings menu label">Settings</span>
</button>

View File

@@ -76,6 +76,7 @@ import { ModifyPlaylistComponent } from './dialogs/modify-playlist/modify-playli
import { ConfirmDialogComponent } from './dialogs/confirm-dialog/confirm-dialog.component';
import { UnifiedFileCardComponent } from './components/unified-file-card/unified-file-card.component';
import { RecentVideosComponent } from './components/recent-videos/recent-videos.component';
import { EditSubscriptionDialogComponent } from './dialogs/edit-subscription-dialog/edit-subscription-dialog.component';
registerLocaleData(es, 'es');
@@ -118,7 +119,8 @@ export function isVisible({ event, element, scrollContainer, offset }: IsVisible
ModifyPlaylistComponent,
ConfirmDialogComponent,
UnifiedFileCardComponent,
RecentVideosComponent
RecentVideosComponent,
EditSubscriptionDialogComponent
],
imports: [
CommonModule,

View File

@@ -18,7 +18,7 @@
<h5 style="margin-top: 10px;">Installation details:</h5>
<p>
<ng-container i18n="Version label">Installed version:</ng-container>&nbsp;{{current_version_tag}} - <span style="display: inline-block" *ngIf="checking_for_updates"><mat-spinner class="version-spinner" [diameter]="22"></mat-spinner>&nbsp;<ng-container i18n="Checking for updates text">Checking for updates...</ng-container></span>
<mat-icon *ngIf="!checking_for_updates" class="version-checked-icon">done</mat-icon>&nbsp;&nbsp;<span *ngIf="!checking_for_updates && latestGithubRelease['tag_name'] !== current_version_tag"><a [href]="latestUpdateLink" target="_blank"><ng-container i18n="View latest update">Update available</ng-container> - {{latestGithubRelease['tag_name']}}</a>. <ng-container i18n="Update through settings menu hint">You can update from the settings menu.</ng-container></span>
<mat-icon *ngIf="!checking_for_updates" class="version-checked-icon">done</mat-icon>&nbsp;&nbsp;<ng-container *ngIf="!checking_for_updates && latestGithubRelease['tag_name'] !== current_version_tag"><a [href]="latestUpdateLink" target="_blank"><ng-container i18n="View latest update">Update available</ng-container> - {{latestGithubRelease['tag_name']}}</a>. <ng-container i18n="Update through settings menu hint">You can update from the settings menu.</ng-container></ng-container>
<span *ngIf="!checking_for_updates && latestGithubRelease['tag_name'] === current_version_tag">You are up to date.</span>
</p>
<p>

View File

@@ -1,12 +1,15 @@
<h4 mat-dialog-title>{{dialogTitle}}</h4>
<mat-dialog-content>
<div>
<div style="margin-bottom: 10px;">
{{dialogText}}
</div>
</mat-dialog-content>
<mat-dialog-actions>
<!-- The mat-dialog-close directive optionally accepts a value as a result for the dialog. -->
<button color="primary" mat-flat-button type="submit" [mat-dialog-close]="true">{{submitText}}</button>
<button color="primary" mat-flat-button type="submit" (click)="confirmClicked()">{{submitText}}</button>
<div class="mat-spinner" *ngIf="submitClicked">
<mat-spinner [diameter]="25"></mat-spinner>
</div>
<span class="spacer"></span>
<button style="float: right;" mat-stroked-button mat-dialog-close>Cancel</button>
</mat-dialog-actions>

View File

@@ -1 +1,5 @@
.spacer {flex: 1 1 auto;}
.spacer {flex: 1 1 auto;}
.mat-spinner {
margin-left: 8px;
}

View File

@@ -1,5 +1,5 @@
import { Component, OnInit, Inject } from '@angular/core';
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Component, OnInit, Inject, EventEmitter } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
@Component({
selector: 'app-confirm-dialog',
@@ -8,14 +8,34 @@ import { MAT_DIALOG_DATA } from '@angular/material/dialog';
})
export class ConfirmDialogComponent implements OnInit {
dialogTitle: 'Confirm';
dialogText: 'Would you like to confirm?';
submitText: 'Yes'
dialogTitle = 'Confirm';
dialogText = 'Would you like to confirm?';
submitText = 'Yes'
submitClicked = false;
constructor(@Inject(MAT_DIALOG_DATA) public data: any) {
doneEmitter: EventEmitter<any> = null;
onlyEmitOnDone = false;
constructor(@Inject(MAT_DIALOG_DATA) public data: any, public dialogRef: MatDialogRef<ConfirmDialogComponent>) {
if (this.data.dialogTitle) { this.dialogTitle = this.data.dialogTitle };
if (this.data.dialogText) { this.dialogText = this.data.dialogText };
if (this.data.submitText) { this.submitText = this.data.submitText };
// checks if emitter exists, if so don't autoclose as it should be handled by caller
if (this.data.doneEmitter) {
this.doneEmitter = this.data.doneEmitter;
this.onlyEmitOnDone = true;
}
}
confirmClicked() {
if (this.onlyEmitOnDone) {
this.doneEmitter.emit(true);
this.submitClicked = true;
} else {
this.dialogRef.close(true);
}
}
ngOnInit(): void {

View File

@@ -0,0 +1,62 @@
<h4 mat-dialog-title i18n="Edit subscription dialog title">Editing {{sub.name}}</h4>
<mat-dialog-content>
<div class="container-fluid">
<div class="row">
<div class="col-12 mt-3">
<mat-checkbox (change)="downloadAllToggled()" [(ngModel)]="download_all"><ng-container i18n="Download all uploads subscription setting">Download all uploads</ng-container></mat-checkbox>
</div>
<div class="col-12" *ngIf="!download_all && editor_initialized">
<ng-container i18n="Download time range prefix">Download videos uploaded in the last</ng-container>
<mat-form-field color="accent" style="width: 50px; text-align: center; margin-left: 10px;">
<input type="number" matInput [(ngModel)]="timerange_amount" (ngModelChange)="timerangeChanged($event, false)">
</mat-form-field>
<mat-form-field class="unit-select">
<mat-select color="accent" [(ngModel)]="timerange_unit" (ngModelChange)="timerangeChanged($event, true)">
<mat-option *ngFor="let time_unit of time_units" [value]="time_unit + (timerange_amount === 1 ? '' : 's')">
{{time_unit + (timerange_amount === 1 ? '' : 's')}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="col-12">
<div>
<mat-checkbox [disabled]="true" [(ngModel)]="audioOnlyMode"><ng-container i18n="Streaming-only mode">Audio-only mode</ng-container></mat-checkbox>
</div>
</div>
<div class="col-12">
<div>
<mat-checkbox [disabled]="new_sub.type === 'audio'" [(ngModel)]="new_sub.streamingOnly"><ng-container i18n="Streaming-only mode">Streaming-only mode</ng-container></mat-checkbox>
</div>
</div>
<div class="col-12 mb-3">
<mat-form-field color="accent">
<input [(ngModel)]="new_sub.custom_args" matInput placeholder="Custom args" i18n-placeholder="Subscription custom args placeholder">
<button class="args-edit-button" (click)="openArgsModifierDialog()" mat-icon-button><mat-icon>edit</mat-icon></button>
<mat-hint>
<ng-container i18n="Custom args hint">These are added after the standard args.</ng-container>
</mat-hint>
</mat-form-field>
</div>
<div class="col-12">
<mat-form-field color="accent">
<input [(ngModel)]="new_sub.custom_output" matInput placeholder="Custom file output" i18n-placeholder="Subscription custom file output placeholder">
<mat-hint>
<a target="_blank" href="https://github.com/ytdl-org/youtube-dl/blob/master/README.md#output-template">
<ng-container i18n="Custom output template documentation link">Documentation</ng-container></a>.
<ng-container i18n="Custom Output input hint">Path is relative to the config download path. Don't include extension.</ng-container>
</mat-hint>
</mat-form-field>
</div>
</div>
</div>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close><ng-container i18n="Subscribe cancel button">Cancel</ng-container></button>
<!-- The mat-dialog-close directive optionally accepts a value as a result for the dialog. -->
<button mat-button [disabled]="updating || !subChanged()" type="submit" (click)="saveClicked()"><ng-container i18n="Save button">Save</ng-container></button>
<div class="mat-spinner" *ngIf="updating">
<mat-spinner [diameter]="25"></mat-spinner>
</div>
</mat-dialog-actions>

View File

@@ -0,0 +1,9 @@
.args-edit-button {
position: absolute;
margin-left: 10px;
}
.unit-select {
width: 75px;
margin-left: 20px;
}

View File

@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { EditSubscriptionDialogComponent } from './edit-subscription-dialog.component';
describe('EditSubscriptionDialogComponent', () => {
let component: EditSubscriptionDialogComponent;
let fixture: ComponentFixture<EditSubscriptionDialogComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ EditSubscriptionDialogComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EditSubscriptionDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,124 @@
import { Component, OnInit, Inject, ChangeDetectorRef } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
import { PostsService } from 'app/posts.services';
import { ArgModifierDialogComponent } from '../arg-modifier-dialog/arg-modifier-dialog.component';
@Component({
selector: 'app-edit-subscription-dialog',
templateUrl: './edit-subscription-dialog.component.html',
styleUrls: ['./edit-subscription-dialog.component.scss']
})
export class EditSubscriptionDialogComponent implements OnInit {
updating = false;
sub = null;
new_sub = null;
editor_initialized = false;
timerange_amount: number;
timerange_unit = 'days';
audioOnlyMode = null;
download_all = null;
time_units = [
'day',
'week',
'month',
'year'
];
constructor(@Inject(MAT_DIALOG_DATA) public data: any, private dialog: MatDialog, private postsService: PostsService) {
this.sub = this.data.sub;
this.new_sub = JSON.parse(JSON.stringify(this.sub));
this.audioOnlyMode = this.sub.type === 'audio';
this.download_all = !this.sub.timerange;
if (this.sub.timerange) {
const timerange_str = this.sub.timerange.split('-')[1];
console.log(timerange_str);
const number = timerange_str.replace(/\D/g,'');
let units = timerange_str.replace(/[0-9]/g, '');
console.log(units);
// // remove plural on units
// if (units[units.length-1] === 's') {
// units = units.substring(0, units.length-1);
// }
this.timerange_amount = parseInt(number);
this.timerange_unit = units;
this.editor_initialized = true;
} else {
this.editor_initialized = true
}
}
ngOnInit(): void {
}
downloadAllToggled() {
if (this.download_all) {
this.new_sub.timerange = null;
} else {
console.log('checking');
this.timerangeChanged(null, null);
}
}
saveSubscription() {
this.postsService.updateSubscription(this.sub).subscribe(res => {
this.sub = this.new_sub;
this.new_sub = JSON.parse(JSON.stringify(this.sub));
})
}
getSubscription() {
this.postsService.getSubscription(this.sub.id).subscribe(res => {
this.sub = res['subscription'];
this.new_sub = JSON.parse(JSON.stringify(this.sub));
});
}
timerangeChanged(value, select_changed) {
console.log(this.timerange_amount);
console.log(this.timerange_unit);
if (this.timerange_amount && this.timerange_unit && !this.download_all) {
this.new_sub.timerange = 'now-' + this.timerange_amount.toString() + this.timerange_unit;
console.log(this.new_sub.timerange);
} else {
this.new_sub.timerange = null;
}
}
saveClicked() {
this.saveSubscription();
}
// modify custom args
openArgsModifierDialog() {
if (!this.new_sub.custom_args) {
this.new_sub.custom_args = '';
}
const dialogRef = this.dialog.open(ArgModifierDialogComponent, {
data: {
initial_args: this.new_sub.custom_args
}
});
dialogRef.afterClosed().subscribe(new_args => {
if (new_args !== null && new_args !== undefined) {
this.new_sub.custom_args = new_args;
}
});
}
subChanged() {
return JSON.stringify(this.new_sub) !== JSON.stringify(this.sub);
}
}

View File

@@ -24,14 +24,16 @@
</div>
<div class="col-12" *ngIf="!download_all">
<ng-container i18n="Download time range prefix">Download videos uploaded in the last</ng-container>
<mat-form-field color="accent" style="width: 50px; text-align: center">
<mat-form-field color="accent" style="width: 50px; text-align: center; margin-left: 10px;">
<input type="number" matInput [(ngModel)]="timerange_amount">
</mat-form-field>
<mat-select color="accent" class="unit-select" [(ngModel)]="timerange_unit">
<mat-option *ngFor="let time_unit of time_units" [value]="time_unit + (timerange_amount === 1 ? '' : 's')">
{{time_unit + (timerange_amount === 1 ? '' : 's')}}
</mat-option>
</mat-select>
<mat-form-field class="unit-select">
<mat-select color="accent" [(ngModel)]="timerange_unit">
<mat-option *ngFor="let time_unit of time_units" [value]="time_unit + (timerange_amount === 1 ? '' : 's')">
{{time_unit + (timerange_amount === 1 ? '' : 's')}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="col-12">
<div>

View File

@@ -82,6 +82,7 @@ export class PostsService implements CanActivate {
if (result) {
this.config = result['YoutubeDLMaterial'];
if (this.config['Advanced']['multi_user_mode']) {
this.checkAdminCreationStatus();
// login stuff
if (localStorage.getItem('jwt_token') && localStorage.getItem('jwt_token') !== 'null') {
this.token = localStorage.getItem('jwt_token');
@@ -173,12 +174,8 @@ export class PostsService implements CanActivate {
ui_uid: ui_uid}, this.httpOptions);
}
getFileStatusMp3(name: string) {
return this.http.post(this.path + 'fileStatusMp3', {name: name}, this.httpOptions);
}
getFileStatusMp4(name: string) {
return this.http.post(this.path + 'fileStatusMp4', {name: name}, this.httpOptions);
killAllDownloads() {
return this.http.post(this.path + 'killAllDownloads', {}, this.httpOptions);
}
loadNavItems() {
@@ -299,6 +296,10 @@ export class PostsService implements CanActivate {
audioOnly: audioOnly, customArgs: customArgs, customFileOutput: customFileOutput}, this.httpOptions);
}
updateSubscription(subscription) {
return this.http.post(this.path + 'updateSubscription', {subscription: subscription}, this.httpOptions);
}
unsubscribe(sub, deleteMode = false) {
return this.http.post(this.path + 'unsubscribe', {sub: sub, deleteMode: deleteMode}, this.httpOptions)
}
@@ -421,7 +422,6 @@ export class PostsService implements CanActivate {
}
sendToLogin() {
this.checkAdminCreationStatus();
if (!this.initialized) {
this.setInitialized();
}
@@ -451,8 +451,8 @@ export class PostsService implements CanActivate {
password: password}, this.httpOptions);
}
checkAdminCreationStatus(skip_check = false) {
if (!skip_check && !this.config['Advanced']['multi_user_mode']) {
checkAdminCreationStatus(force_show = false) {
if (!force_show && !this.config['Advanced']['multi_user_mode']) {
return;
}
this.adminExists().subscribe(res => {

View File

@@ -19,7 +19,7 @@
<mat-hint><ng-container i18n="URL setting input hint">URL this app will be accessed from, without the port.</ng-container></mat-hint>
</mat-form-field>
</div>
<div class="col-12 mb-4">
<div class="col-12 mb-4 mt-3">
<mat-form-field class="text-field" color="accent">
<input [(ngModel)]="new_config['Host']['port']" matInput placeholder="Port" i18n-placeholder="Port input placeholder" required>
<mat-hint><ng-container i18n="Port setting input hint">The desired port. Default is 17442.</ng-container></mat-hint>
@@ -140,7 +140,7 @@
</mat-form-field>
</div>
<div class="col-12 mt-5">
<div class="col-12 mt-4">
<mat-form-field class="text-field" style="margin-right: 12px;" color="accent">
<textarea matInput [(ngModel)]="new_config['Downloader']['custom_args']" placeholder="Custom args" i18n-placeholder="Custom args input placeholder"></textarea>
<mat-hint><ng-container i18n="Custom args setting input hint">Global custom args for downloads on the home page. Args are delimited using two commas like so: ,,</ng-container></mat-hint>
@@ -148,14 +148,18 @@
</mat-form-field>
</div>
<div class="col-12 mt-4">
<div class="col-12 mt-5">
<mat-checkbox color="accent" [(ngModel)]="new_config['Downloader']['use_youtubedl_archive']"><ng-container i18n="Use youtubedl archive setting">Use youtube-dl archive</ng-container></mat-checkbox>
<p><ng-container i18n="youtubedl archive setting Note">Note: This setting only applies to downloads on the Home page. If you would like to use youtube-dl archive functionality in subscriptions, head to the Main tab and activate this option there.</ng-container></p>
</div>
<div class="col-12 mt-3">
<div class="col-12 mt-2">
<mat-checkbox color="accent" [(ngModel)]="new_config['Downloader']['safe_download_override']"><ng-container i18n="Safe download override setting">Safe download override</ng-container></mat-checkbox>
</div>
<div class="col-12 mt-2">
<button (click)="killAllDownloads()" mat-stroked-button color="warn"><ng-container i18n="Kill all downloads button">Kill all downloads</ng-container></button>
</div>
</div>
</div>
</ng-template>

View File

@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, EventEmitter } from '@angular/core';
import { PostsService } from 'app/posts.services';
import { isoLangs } from './locales_list';
import { MatSnackBar } from '@angular/material/snack-bar';
@@ -8,6 +8,7 @@ import { ArgModifierDialogComponent } from 'app/dialogs/arg-modifier-dialog/arg-
import { CURRENT_VERSION } from 'app/consts';
import { MatCheckboxChange } from '@angular/material/checkbox';
import { CookiesUploaderDialogComponent } from 'app/dialogs/cookies-uploader-dialog/cookies-uploader-dialog.component';
import { ConfirmDialogComponent } from 'app/dialogs/confirm-dialog/confirm-dialog.component';
@Component({
selector: 'app-settings',
@@ -154,6 +155,34 @@ export class SettingsComponent implements OnInit {
});
}
killAllDownloads() {
const done = new EventEmitter<any>();
const dialogRef = this.dialog.open(ConfirmDialogComponent, {
data: {
dialogTitle: 'Kill downloads',
dialogText: 'Are you sure you want to kill all downloads? Any subscription and non-subscription downloads will end immediately, though this operation may take a minute or so to complete.',
submitText: 'Kill all downloads',
doneEmitter: done
}
});
done.subscribe(confirmed => {
if (confirmed) {
this.postsService.killAllDownloads().subscribe(res => {
if (res['success']) {
dialogRef.close();
this.postsService.openSnackBar('Successfully killed all downloads!');
} else {
dialogRef.close();
this.postsService.openSnackBar('Failed to kill all downloads! Check logs for details.');
}
}, err => {
dialogRef.close();
this.postsService.openSnackBar('Failed to kill all downloads! Check logs for details.');
});
}
});
}
// snackbar helper
public openSnackBar(message: string, action: string = '') {
this.snackBar.open(message, action, {

View File

@@ -42,5 +42,6 @@
</div>
</div>
</div>
<button class="edit-button" color="primary" (click)="editSubscription()" [disabled]="downloading" mat-fab><mat-icon class="save-icon">edit</mat-icon></button>
<button class="save-button" color="primary" (click)="downloadContent()" [disabled]="downloading" mat-fab><mat-icon class="save-icon">save</mat-icon><mat-spinner *ngIf="downloading" class="spinner" [diameter]="50"></mat-spinner></button>
</div>

View File

@@ -58,6 +58,12 @@
bottom: 25px;
}
.edit-button {
left: 25px;
position: absolute;
bottom: 25px;
}
.save-icon {
bottom: 1px;
position: relative;

View File

@@ -1,6 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { PostsService } from 'app/posts.services';
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
import { MatDialog } from '@angular/material/dialog';
import { EditSubscriptionDialogComponent } from 'app/dialogs/edit-subscription-dialog/edit-subscription-dialog.component';
@Component({
selector: 'app-subscription',
@@ -45,7 +47,7 @@ export class SubscriptionComponent implements OnInit {
initialized = false;
constructor(private postsService: PostsService, private route: ActivatedRoute, private router: Router) { }
constructor(private postsService: PostsService, private route: ActivatedRoute, private router: Router, private dialog: MatDialog) { }
ngOnInit() {
this.route.paramMap.subscribe((params: ParamMap) => {
@@ -165,4 +167,12 @@ export class SubscriptionComponent implements OnInit {
});
}
editSubscription() {
this.dialog.open(EditSubscriptionDialogComponent, {
data: {
sub: this.subscription
}
});
}
}