Multiselect in mediafiles
Allows multiselect on mediafiles using the new folder structure Add bulk delete on server Add movement logic and path view
This commit is contained in:
parent
f25a8aefb2
commit
a97ca18c36
@ -135,4 +135,15 @@ export class MediafileRepositoryService extends BaseIsListOfSpeakersContentObjec
|
|||||||
directory_id: directoryId
|
directory_id: directoryId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes many files.
|
||||||
|
*
|
||||||
|
* @param mediafiles The users to delete
|
||||||
|
*/
|
||||||
|
public async bulkDelete(mediafiles: ViewMediafile[]): Promise<void> {
|
||||||
|
await this.httpService.post('/rest/mediafiles/mediafile/bulk_delete/', {
|
||||||
|
ids: mediafiles.map(mediafile => mediafile.id)
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
(click)="openUploadDialog(uploadDialog)"
|
(click)="openUploadDialog(uploadDialog)"
|
||||||
*osPerms="'mediafiles.can_upload'"
|
*osPerms="'mediafiles.can_manage'"
|
||||||
>
|
>
|
||||||
<mat-icon>cloud_upload</mat-icon>
|
<mat-icon>cloud_upload</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<os-head-bar [mainButton]="canUploadFiles" [multiSelectMode]="false" (mainEvent)="onMainEvent()">
|
<os-head-bar [mainButton]="canEdit" [multiSelectMode]="isMultiSelect" (mainEvent)="onMainEvent()">
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<div class="title-slot">
|
<div class="title-slot">
|
||||||
<h2 translate>Files</h2>
|
<h2 translate>Files</h2>
|
||||||
@ -6,35 +6,40 @@
|
|||||||
|
|
||||||
<!-- Menu -->
|
<!-- Menu -->
|
||||||
<div class="menu-slot" *osPerms="'mediafiles.can_manage'">
|
<div class="menu-slot" *osPerms="'mediafiles.can_manage'">
|
||||||
<button type="button" mat-icon-button (click)="createNewFolder(newFolderDialog)">
|
<button type="button" mat-icon-button (click)="createNewFolder(newFolderDialog)" *ngIf="!isMultiSelect">
|
||||||
<mat-icon>create_new_folder</mat-icon>
|
<mat-icon>create_new_folder</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<!--<button type="button" mat-icon-button [matMenuTriggerFor]="mediafilesMenu">
|
<button type="button" mat-icon-button [matMenuTriggerFor]="mediafilesMenu">
|
||||||
<mat-icon>more_vert</mat-icon>
|
<mat-icon>more_vert</mat-icon>
|
||||||
</button>-->
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Multiselect info -->
|
<!-- Multiselect info -->
|
||||||
<!--<div *ngIf="this.isMultiSelect" class="central-info-slot">
|
<div *ngIf="isMultiSelect" class="central-info-slot">
|
||||||
<button mat-icon-button (click)="toggleMultiSelect()"><mat-icon>arrow_back</mat-icon></button>
|
<button mat-icon-button (click)="toggleMultiSelect()"><mat-icon>arrow_back</mat-icon></button>
|
||||||
<span>{{ selectedRows.length }} </span><span translate>selected</span>
|
<span>{{ selectedRows.length }} </span><span translate>selected</span>
|
||||||
</div>-->
|
</div>
|
||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
|
|
||||||
<!-- Folder navigation bar -->
|
<!-- Folder navigation bar -->
|
||||||
<div>
|
<div>
|
||||||
<div class="custom-table-header folder-nav-bar">
|
<div class="custom-table-header folder-nav-bar">
|
||||||
<button class="folder" mat-button (click)="changeDirectory(null)">
|
<button class="folder" mat-button (click)="changeDirectory(null)" [disabled]="isMultiSelect">
|
||||||
<mat-icon class="file-icon">home</mat-icon>
|
<mat-icon class="file-icon">home</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<span *ngFor="let directory of directoryChain; let last = last">
|
<span *ngFor="let directory of directoryChain; let last = last">
|
||||||
<div class="arrow">
|
<div class="arrow">
|
||||||
<mat-icon>chevron_right</mat-icon>
|
<mat-icon>chevron_right</mat-icon>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
<button class="folder" mat-button (click)="changeDirectory(directory.id)" *ngIf="!last">
|
class="folder"
|
||||||
|
mat-button
|
||||||
|
(click)="changeDirectory(directory.id)"
|
||||||
|
[disabled]="isMultiSelect"
|
||||||
|
*ngIf="!last"
|
||||||
|
>
|
||||||
<span class="folder-text">
|
<span class="folder-text">
|
||||||
{{ directory.title }}
|
{{ directory.filename }}
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@ -42,14 +47,15 @@
|
|||||||
mat-button
|
mat-button
|
||||||
[matMenuTriggerFor]="singleMediafileMenu"
|
[matMenuTriggerFor]="singleMediafileMenu"
|
||||||
[matMenuTriggerData]="{ mediafile: directory }"
|
[matMenuTriggerData]="{ mediafile: directory }"
|
||||||
|
[disabled]="isMultiSelect"
|
||||||
*ngIf="last && showFileMenu(directory)"
|
*ngIf="last && showFileMenu(directory)"
|
||||||
>
|
>
|
||||||
<os-icon-container icon="arrow_drop_down" swap="true" size="large">
|
<os-icon-container icon="arrow_drop_down" swap="true" size="large">
|
||||||
{{ directory.title }}
|
{{ directory.filename }}
|
||||||
</os-icon-container>
|
</os-icon-container>
|
||||||
</button>
|
</button>
|
||||||
<span class="folder fake-folder folder-text" *ngIf="last && !showFileMenu(directory)">
|
<span class="folder fake-folder folder-text" *ngIf="last && !showFileMenu(directory)">
|
||||||
{{ directory.title }}
|
{{ directory.filename }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span class="visibility" *ngIf="canEdit && directory && directory.has_inherited_access_groups">
|
<span class="visibility" *ngIf="canEdit && directory && directory.has_inherited_access_groups">
|
||||||
@ -83,23 +89,29 @@
|
|||||||
showHeader="false"
|
showHeader="false"
|
||||||
vScrollAuto
|
vScrollAuto
|
||||||
[dataSource]="dataSource"
|
[dataSource]="dataSource"
|
||||||
|
matCheckboxSelection="selection"
|
||||||
[columns]="columnSet"
|
[columns]="columnSet"
|
||||||
[hideColumns]="hiddenColumns"
|
[hideColumns]="hiddenColumns"
|
||||||
|
(rowClick)="onSelectRow($event)"
|
||||||
>
|
>
|
||||||
<!-- Icon column -->
|
<!-- Icon column -->
|
||||||
<div *pblNgridCellDef="'icon'; row as mediafile" class="fill clickable">
|
<div *pblNgridCellDef="'icon'; row as mediafile" class="fill clickable">
|
||||||
<a class="detail-link" target="_blank" [routerLink]="mediafile.url" *ngIf="mediafile.is_file"> </a>
|
<a class="detail-link" target="_blank" [routerLink]="mediafile.url" *ngIf="mediafile.is_file && !isMultiSelect">
|
||||||
<a class="detail-link" (click)="changeDirectory(mediafile.id)" *ngIf="mediafile.is_directory"> </a>
|
</a>
|
||||||
|
<a class="detail-link" (click)="changeDirectory(mediafile.id)" *ngIf="mediafile.is_directory && !isMultiSelect">
|
||||||
|
</a>
|
||||||
<mat-icon class="file-icon">{{ mediafile.getIcon() }}</mat-icon>
|
<mat-icon class="file-icon">{{ mediafile.getIcon() }}</mat-icon>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Title column -->
|
<!-- Title column -->
|
||||||
<div *pblNgridCellDef="'title'; row as mediafile" class="fill clickable">
|
<div *pblNgridCellDef="'title'; row as mediafile" class="fill clickable">
|
||||||
<a class="detail-link" target="_blank" [routerLink]="mediafile.url" *ngIf="mediafile.is_file"> </a>
|
<a class="detail-link" target="_blank" [routerLink]="mediafile.url" *ngIf="mediafile.is_file && !isMultiSelect">
|
||||||
<a class="detail-link" (click)="changeDirectory(mediafile.id)" *ngIf="mediafile.is_directory"> </a>
|
</a>
|
||||||
|
<a class="detail-link" (click)="changeDirectory(mediafile.id)" *ngIf="mediafile.is_directory && !isMultiSelect">
|
||||||
|
</a>
|
||||||
<div class="innerTable">
|
<div class="innerTable">
|
||||||
<div class="file-title ellipsis-overflow">
|
<div class="file-title ellipsis-overflow">
|
||||||
{{ mediafile.title }}
|
{{ mediafile.filename }}
|
||||||
</div>
|
</div>
|
||||||
<div class="info-text" *ngIf="mediafile.is_file">
|
<div class="info-text" *ngIf="mediafile.is_file">
|
||||||
<span> {{ getDateFromTimestamp(mediafile.timestamp) }} · {{ mediafile.size }} </span>
|
<span> {{ getDateFromTimestamp(mediafile.timestamp) }} · {{ mediafile.size }} </span>
|
||||||
@ -135,6 +147,7 @@
|
|||||||
mat-icon-button
|
mat-icon-button
|
||||||
[matMenuTriggerFor]="singleMediafileMenu"
|
[matMenuTriggerFor]="singleMediafileMenu"
|
||||||
[matMenuTriggerData]="{ mediafile: mediafile }"
|
[matMenuTriggerData]="{ mediafile: mediafile }"
|
||||||
|
[disabled]="isMultiSelect"
|
||||||
*ngIf="showFileMenu(mediafile)"
|
*ngIf="showFileMenu(mediafile)"
|
||||||
>
|
>
|
||||||
<mat-icon>more_vert</mat-icon>
|
<mat-icon>more_vert</mat-icon>
|
||||||
@ -191,7 +204,7 @@
|
|||||||
<mat-icon>edit</mat-icon>
|
<mat-icon>edit</mat-icon>
|
||||||
<span translate>Edit</span>
|
<span translate>Edit</span>
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item (click)="move(moveDialog, mediafile)">
|
<button mat-menu-item (click)="move(moveDialog, [mediafile])">
|
||||||
<mat-icon>near_me</mat-icon>
|
<mat-icon>near_me</mat-icon>
|
||||||
<span translate>Move</span>
|
<span translate>Move</span>
|
||||||
</button>
|
</button>
|
||||||
@ -204,7 +217,7 @@
|
|||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
<!-- Menu for Mediafiles -->
|
<!-- Menu for Mediafiles -->
|
||||||
<!--<mat-menu #mediafilesMenu="matMenu">
|
<mat-menu #mediafilesMenu="matMenu">
|
||||||
<div *ngIf="!isMultiSelect">
|
<div *ngIf="!isMultiSelect">
|
||||||
<button mat-menu-item *osPerms="'mediafiles.can_manage'" (click)="toggleMultiSelect()">
|
<button mat-menu-item *osPerms="'mediafiles.can_manage'" (click)="toggleMultiSelect()">
|
||||||
<mat-icon>library_add</mat-icon>
|
<mat-icon>library_add</mat-icon>
|
||||||
@ -212,6 +225,10 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="isMultiSelect">
|
<div *ngIf="isMultiSelect">
|
||||||
|
<button mat-menu-item [disabled]="!selectedRows.length" (click)="move(moveDialog, selectedRows)">
|
||||||
|
<mat-icon>near_me</mat-icon>
|
||||||
|
<span translate>Move</span>
|
||||||
|
</button>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
<button mat-menu-item (click)="selectAll()">
|
<button mat-menu-item (click)="selectAll()">
|
||||||
<mat-icon>done_all</mat-icon>
|
<mat-icon>done_all</mat-icon>
|
||||||
@ -228,11 +245,11 @@
|
|||||||
[disabled]="!selectedRows.length"
|
[disabled]="!selectedRows.length"
|
||||||
(click)="deleteSelected()"
|
(click)="deleteSelected()"
|
||||||
>
|
>
|
||||||
<mat-icon>delete</mat-icon>
|
<mat-icon color="warn">delete</mat-icon>
|
||||||
<span translate>Delete</span>
|
<span translate>Delete</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</mat-menu>-->
|
</mat-menu>
|
||||||
|
|
||||||
<!-- File edit dialog -->
|
<!-- File edit dialog -->
|
||||||
<ng-template #fileEditDialog>
|
<ng-template #fileEditDialog>
|
||||||
@ -310,7 +327,7 @@
|
|||||||
<h1 mat-dialog-title>
|
<h1 mat-dialog-title>
|
||||||
<span translate>Move into directory</span>
|
<span translate>Move into directory</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div mat-dialog-content>
|
<div class="os-form-card-mobile" mat-dialog-content>
|
||||||
<p translate>Please select the directory:</p>
|
<p translate>Please select the directory:</p>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
ngDefaultControl
|
ngDefaultControl
|
||||||
@ -318,7 +335,7 @@
|
|||||||
[includeNone]="true"
|
[includeNone]="true"
|
||||||
[noneTitle]="'Base folder'"
|
[noneTitle]="'Base folder'"
|
||||||
listname="{{ 'Parent directory' | translate }}"
|
listname="{{ 'Parent directory' | translate }}"
|
||||||
[InputListValues]="directoryBehaviorSubject"
|
[InputListValues]="filteredDirectoryBehaviorSubject"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
</div>
|
</div>
|
||||||
<div mat-dialog-actions>
|
<div mat-dialog-actions>
|
||||||
|
@ -15,8 +15,9 @@ import { Title } from '@angular/platform-browser';
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { columnFactory, createDS, PblDataSource } from '@pebula/ngrid';
|
import { columnFactory, createDS, PblColumnDefinition } from '@pebula/ngrid';
|
||||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
import { PblNgridDataMatrixRow } from '@pebula/ngrid/target-events';
|
||||||
|
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||||
|
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service';
|
import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service';
|
||||||
@ -26,7 +27,7 @@ import { PromptService } from 'app/core/ui-services/prompt.service';
|
|||||||
import { ViewportService } from 'app/core/ui-services/viewport.service';
|
import { ViewportService } from 'app/core/ui-services/viewport.service';
|
||||||
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
|
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
|
||||||
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
import { BaseListViewComponent } from 'app/site/base/base-list-view';
|
||||||
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
|
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
|
||||||
import { ViewGroup } from 'app/site/users/models/view-group';
|
import { ViewGroup } from 'app/site/users/models/view-group';
|
||||||
import { MediafilesSortListService } from '../../services/mediafiles-sort-list.service';
|
import { MediafilesSortListService } from '../../services/mediafiles-sort-list.service';
|
||||||
@ -41,12 +42,7 @@ import { MediafilesSortListService } from '../../services/mediafiles-sort-list.s
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class MediafileListComponent extends BaseViewComponent implements OnInit, OnDestroy {
|
export class MediafileListComponent extends BaseListViewComponent<ViewMediafile> implements OnInit, OnDestroy {
|
||||||
/**
|
|
||||||
* Data source for the files
|
|
||||||
*/
|
|
||||||
public dataSource: PblDataSource<ViewMediafile>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the actions for logos. Updated via an observable
|
* Holds the actions for logos. Updated via an observable
|
||||||
*/
|
*/
|
||||||
@ -65,15 +61,11 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
|||||||
public newDirectoryForm: FormGroup;
|
public newDirectoryForm: FormGroup;
|
||||||
public moveForm: FormGroup;
|
public moveForm: FormGroup;
|
||||||
public directoryBehaviorSubject: BehaviorSubject<ViewMediafile[]>;
|
public directoryBehaviorSubject: BehaviorSubject<ViewMediafile[]>;
|
||||||
|
public filteredDirectoryBehaviorSubject: BehaviorSubject<ViewMediafile[]> = new BehaviorSubject<ViewMediafile[]>(
|
||||||
|
[]
|
||||||
|
);
|
||||||
public groupsBehaviorSubject: BehaviorSubject<ViewGroup[]>;
|
public groupsBehaviorSubject: BehaviorSubject<ViewGroup[]>;
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns true if the user can manage media files
|
|
||||||
*/
|
|
||||||
public get canUploadFiles(): boolean {
|
|
||||||
return this.operator.hasPerms('mediafiles.can_see') && this.operator.hasPerms('mediafiles.can_upload');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the user can manage media files
|
* @return true if the user can manage media files
|
||||||
*/
|
*/
|
||||||
@ -113,6 +105,10 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
|||||||
hidden.push('info');
|
hidden.push('info');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.isMultiSelect) {
|
||||||
|
hidden.push('selection');
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.canAccessFileMenu) {
|
if (!this.canAccessFileMenu) {
|
||||||
hidden.push('menu');
|
hidden.push('menu');
|
||||||
}
|
}
|
||||||
@ -120,45 +116,55 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
|||||||
return hidden;
|
return hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the column definition
|
||||||
|
*/
|
||||||
|
public tableColumnDefinition: PblColumnDefinition[] = [
|
||||||
|
{
|
||||||
|
prop: 'selection',
|
||||||
|
width: '40px'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'icon',
|
||||||
|
label: '',
|
||||||
|
width: '40px'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'title',
|
||||||
|
width: 'auto',
|
||||||
|
minWidth: 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'info',
|
||||||
|
width: '20%',
|
||||||
|
minWidth: 60
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'indicator',
|
||||||
|
label: '',
|
||||||
|
width: '40px'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
prop: 'menu',
|
||||||
|
label: '',
|
||||||
|
width: '40px'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the column set
|
* Create the column set
|
||||||
*/
|
*/
|
||||||
public columnSet = columnFactory()
|
public columnSet = columnFactory()
|
||||||
.table(
|
.table(...this.tableColumnDefinition)
|
||||||
{
|
|
||||||
prop: 'icon',
|
|
||||||
label: '',
|
|
||||||
width: '40px'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'title',
|
|
||||||
width: 'auto',
|
|
||||||
minWidth: 60
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'info',
|
|
||||||
width: '20%',
|
|
||||||
minWidth: 60
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'indicator',
|
|
||||||
label: '',
|
|
||||||
width: '40px'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
prop: 'menu',
|
|
||||||
label: '',
|
|
||||||
width: '40px'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public isMultiselect = false; // TODO
|
|
||||||
private folderSubscription: Subscription;
|
private folderSubscription: Subscription;
|
||||||
private directorySubscription: Subscription;
|
private directorySubscription: Subscription;
|
||||||
public directory: ViewMediafile | null;
|
public directory: ViewMediafile | null;
|
||||||
public directoryChain: ViewMediafile[];
|
public directoryChain: ViewMediafile[];
|
||||||
|
|
||||||
|
private directoryObservable: Observable<ViewMediafile[]> = new Observable();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs the component
|
* Constructs the component
|
||||||
*
|
*
|
||||||
@ -194,6 +200,7 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
|||||||
private cd: ChangeDetectorRef
|
private cd: ChangeDetectorRef
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar);
|
super(titleService, translate, matSnackBar);
|
||||||
|
this.canMultiSelect = true;
|
||||||
|
|
||||||
this.newDirectoryForm = this.formBuilder.group({
|
this.newDirectoryForm = this.formBuilder.group({
|
||||||
title: ['', Validators.required],
|
title: ['', Validators.required],
|
||||||
@ -226,6 +233,8 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
|||||||
this.mediaManage.getFontActions().subscribe(action => {
|
this.mediaManage.getFontActions().subscribe(action => {
|
||||||
this.fontActions = action;
|
this.fontActions = action;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.createDataSource();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnDestroy(): void {
|
public ngOnDestroy(): void {
|
||||||
@ -250,25 +259,38 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
|||||||
return new Date(timestamp).toLocaleString(this.translate.currentLang);
|
return new Date(timestamp).toLocaleString(this.translate.currentLang);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Swap logic to only create DS once and update on filder change
|
||||||
|
* @param mediafiles
|
||||||
|
*/
|
||||||
|
private createDataSource(): void {
|
||||||
|
this.dataSource = createDS<ViewMediafile>()
|
||||||
|
.onTrigger(() => this.directoryObservable)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
this.dataSource.selection.changed.subscribe(selection => {
|
||||||
|
this.selectedRows = selection.source.selected;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public changeDirectory(directoryId: number | null): void {
|
public changeDirectory(directoryId: number | null): void {
|
||||||
this.clearSubscriptions();
|
this.clearSubscriptions();
|
||||||
|
|
||||||
this.folderSubscription = this.repo.getListObservableDirectory(directoryId).subscribe(mediafiles => {
|
this.directoryObservable = this.repo.getListObservableDirectory(directoryId);
|
||||||
|
this.folderSubscription = this.directoryObservable.subscribe(mediafiles => {
|
||||||
if (mediafiles) {
|
if (mediafiles) {
|
||||||
this.dataSource = createDS<ViewMediafile>()
|
this.dataSource.refresh();
|
||||||
.onTrigger(() => mediafiles)
|
this.cd.markForCheck();
|
||||||
.create();
|
|
||||||
this.cd.detectChanges();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (directoryId) {
|
if (directoryId) {
|
||||||
this.directorySubscription = this.repo.getViewModelObservable(directoryId).subscribe(d => {
|
this.directorySubscription = this.repo.getViewModelObservable(directoryId).subscribe(newDirectory => {
|
||||||
this.directory = d;
|
this.directory = newDirectory;
|
||||||
if (d) {
|
if (newDirectory) {
|
||||||
this.directoryChain = d.getDirectoryChain();
|
this.directoryChain = newDirectory.getDirectoryChain();
|
||||||
// Update the URL.
|
// Update the URL.
|
||||||
this.router.navigate(['/mediafiles/files/' + d.path], {
|
this.router.navigate(['/mediafiles/files/' + newDirectory.path], {
|
||||||
replaceUrl: true
|
replaceUrl: true
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -287,8 +309,6 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*/
|
|
||||||
public onMainEvent(): void {
|
public onMainEvent(): void {
|
||||||
const path = '/mediafiles/upload/' + (this.directory ? this.directory.path : '');
|
const path = '/mediafiles/upload/' + (this.directory ? this.directory.path : '');
|
||||||
this.router.navigate([path]);
|
this.router.navigate([path]);
|
||||||
@ -300,20 +320,22 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
|||||||
* @param file the selected file
|
* @param file the selected file
|
||||||
*/
|
*/
|
||||||
public onEditFile(file: ViewMediafile): void {
|
public onEditFile(file: ViewMediafile): void {
|
||||||
this.fileToEdit = file;
|
if (!this.isMultiSelect) {
|
||||||
|
this.fileToEdit = file;
|
||||||
|
|
||||||
this.fileEditForm = this.fb.group({
|
this.fileEditForm = this.fb.group({
|
||||||
title: [file.title, Validators.required],
|
title: [file.filename, Validators.required],
|
||||||
access_groups_id: [file.access_groups_id]
|
access_groups_id: [file.access_groups_id]
|
||||||
});
|
});
|
||||||
|
|
||||||
const dialogRef = this.dialog.open(this.fileEditDialog, infoDialogSettings);
|
const dialogRef = this.dialog.open(this.fileEditDialog, infoDialogSettings);
|
||||||
|
|
||||||
dialogRef.keydownEvents().subscribe((event: KeyboardEvent) => {
|
dialogRef.keydownEvents().subscribe((event: KeyboardEvent) => {
|
||||||
if (event.key === 'Enter' && event.shiftKey && this.fileEditForm.valid) {
|
if (event.key === 'Enter' && event.shiftKey && this.fileEditForm.valid) {
|
||||||
this.onSaveEditedFile(this.fileEditForm.value);
|
this.onSaveEditedFile(this.fileEditForm.value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -338,6 +360,14 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async deleteSelected(): Promise<void> {
|
||||||
|
const title = this.translate.instant('Are you sure you want to delete all selected files and folders?');
|
||||||
|
if (await this.promptService.open(title)) {
|
||||||
|
await this.repo.bulkDelete(this.selectedRows);
|
||||||
|
this.deselectAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the display name of an action
|
* Returns the display name of an action
|
||||||
*
|
*
|
||||||
@ -415,22 +445,50 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
|||||||
parent_id: this.directory ? this.directory.id : null,
|
parent_id: this.directory ? this.directory.id : null,
|
||||||
is_directory: true
|
is_directory: true
|
||||||
});
|
});
|
||||||
this.repo.create(mediafile).then(null, this.raiseError);
|
this.repo.create(mediafile).catch(this.raiseError);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public move(templateRef: TemplateRef<string>, mediafile: ViewMediafile): void {
|
public move(templateRef: TemplateRef<string>, mediafiles: ViewMediafile[]): void {
|
||||||
this.newDirectoryForm.reset();
|
this.moveForm.reset();
|
||||||
|
|
||||||
|
if (mediafiles.some(file => file.is_directory)) {
|
||||||
|
this.filteredDirectoryBehaviorSubject.next(
|
||||||
|
this.directoryBehaviorSubject.value.filter(
|
||||||
|
dir => !mediafiles.some(file => dir.path.startsWith(file.path))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
this.filteredDirectoryBehaviorSubject.next(this.directoryBehaviorSubject.value);
|
||||||
|
}
|
||||||
const dialogRef = this.dialog.open(templateRef, infoDialogSettings);
|
const dialogRef = this.dialog.open(templateRef, infoDialogSettings);
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe(result => {
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
if (result) {
|
if (result) {
|
||||||
this.repo.move([mediafile], this.moveForm.value.directory_id).then(null, this.raiseError);
|
this.repo.move(mediafiles, this.moveForm.value.directory_id).then(() => {
|
||||||
|
this.dataSource.selection.clear();
|
||||||
|
this.cd.markForCheck();
|
||||||
|
}, this.raiseError);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: This is basically a duplicate of onSelectRow of ListViewTableComponent
|
||||||
|
*/
|
||||||
|
public onSelectRow(event: PblNgridDataMatrixRow<ViewMediafile>): void {
|
||||||
|
if (this.isMultiSelect) {
|
||||||
|
const clickedModel: ViewMediafile = event.row;
|
||||||
|
const alreadySelected = this.dataSource.selection.isSelected(clickedModel);
|
||||||
|
if (alreadySelected) {
|
||||||
|
this.dataSource.selection.deselect(clickedModel);
|
||||||
|
} else {
|
||||||
|
this.dataSource.selection.select(clickedModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private clearSubscriptions(): void {
|
private clearSubscriptions(): void {
|
||||||
if (this.folderSubscription) {
|
if (this.folderSubscription) {
|
||||||
this.folderSubscription.unsubscribe();
|
this.folderSubscription.unsubscribe();
|
||||||
|
@ -17,7 +17,7 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'upload',
|
path: 'upload',
|
||||||
data: { basePerm: 'mediafiles.can_upload' },
|
data: { basePerm: 'mediafiles.can_manage' },
|
||||||
children: [{ path: '**', component: MediaUploadComponent }],
|
children: [{ path: '**', component: MediaUploadComponent }],
|
||||||
pathMatch: 'prefix'
|
pathMatch: 'prefix'
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,14 @@ export class ViewMediafile extends BaseViewModelWithListOfSpeakers<Mediafile>
|
|||||||
}
|
}
|
||||||
|
|
||||||
public get title(): string {
|
public get title(): string {
|
||||||
|
if (this.is_directory) {
|
||||||
|
return this.mediafile.path;
|
||||||
|
} else {
|
||||||
|
return this.mediafile.title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get filename(): string {
|
||||||
return this.mediafile.title;
|
return this.mediafile.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,14 @@ class MediafileViewSet(ModelViewSet):
|
|||||||
"""
|
"""
|
||||||
if self.action in ("list", "retrieve", "metadata"):
|
if self.action in ("list", "retrieve", "metadata"):
|
||||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||||
elif self.action in ("create", "partial_update", "update", "move", "destroy"):
|
elif self.action in (
|
||||||
|
"create",
|
||||||
|
"partial_update",
|
||||||
|
"update",
|
||||||
|
"move",
|
||||||
|
"destroy",
|
||||||
|
"bulk_delete",
|
||||||
|
):
|
||||||
result = has_perm(self.request.user, "mediafiles.can_see") and has_perm(
|
result = has_perm(self.request.user, "mediafiles.can_see") and has_perm(
|
||||||
self.request.user, "mediafiles.can_manage"
|
self.request.user, "mediafiles.can_manage"
|
||||||
)
|
)
|
||||||
@ -150,6 +157,49 @@ class MediafileViewSet(ModelViewSet):
|
|||||||
|
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
|
@list_route(methods=["post"])
|
||||||
|
def bulk_delete(self, request):
|
||||||
|
"""
|
||||||
|
Deletes mediafiles *from one directory*. Expected data:
|
||||||
|
{ ids: [<id>, <id>, ...] }
|
||||||
|
It is checked, that every id belongs to the same directory.
|
||||||
|
"""
|
||||||
|
# Validate data:
|
||||||
|
if not isinstance(request.data, dict):
|
||||||
|
raise ValidationError({"detail": "The data must be a dict"})
|
||||||
|
ids = request.data.get("ids")
|
||||||
|
if not isinstance(ids, list):
|
||||||
|
raise ValidationError({"detail": "The ids must be a list"})
|
||||||
|
for id in ids:
|
||||||
|
if not isinstance(id, int):
|
||||||
|
raise ValidationError({"detail": "All ids must be an int"})
|
||||||
|
|
||||||
|
# Get mediafiles
|
||||||
|
mediafiles = []
|
||||||
|
for id in ids:
|
||||||
|
try:
|
||||||
|
mediafiles.append(Mediafile.objects.get(pk=id))
|
||||||
|
except Mediafile.DoesNotExist:
|
||||||
|
raise ValidationError(
|
||||||
|
{"detail": f"The mediafile with id {id} does not exist"}
|
||||||
|
)
|
||||||
|
if not mediafiles:
|
||||||
|
return Response()
|
||||||
|
|
||||||
|
# Validate, that they are in the same directory:
|
||||||
|
directory_id = mediafiles[0].parent_id
|
||||||
|
for mediafile in mediafiles:
|
||||||
|
if mediafile.parent_id != directory_id:
|
||||||
|
raise ValidationError(
|
||||||
|
{"detail": "All mediafiles must be in the same directory."}
|
||||||
|
)
|
||||||
|
|
||||||
|
with watch_and_update_configs():
|
||||||
|
for mediafile in mediafiles:
|
||||||
|
mediafile.delete()
|
||||||
|
|
||||||
|
return Response()
|
||||||
|
|
||||||
|
|
||||||
def get_mediafile(request, path):
|
def get_mediafile(request, path):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user