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
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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"
|
||||
mat-icon-button
|
||||
(click)="openUploadDialog(uploadDialog)"
|
||||
*osPerms="'mediafiles.can_upload'"
|
||||
*osPerms="'mediafiles.can_manage'"
|
||||
>
|
||||
<mat-icon>cloud_upload</mat-icon>
|
||||
</button>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<os-head-bar [mainButton]="canUploadFiles" [multiSelectMode]="false" (mainEvent)="onMainEvent()">
|
||||
<os-head-bar [mainButton]="canEdit" [multiSelectMode]="isMultiSelect" (mainEvent)="onMainEvent()">
|
||||
<!-- Title -->
|
||||
<div class="title-slot">
|
||||
<h2 translate>Files</h2>
|
||||
@ -6,35 +6,40 @@
|
||||
|
||||
<!-- Menu -->
|
||||
<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>
|
||||
</button>
|
||||
<!--<button type="button" mat-icon-button [matMenuTriggerFor]="mediafilesMenu">
|
||||
<button type="button" mat-icon-button [matMenuTriggerFor]="mediafilesMenu">
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>-->
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
<span>{{ selectedRows.length }} </span><span translate>selected</span>
|
||||
</div>-->
|
||||
</div>
|
||||
</os-head-bar>
|
||||
|
||||
<!-- Folder navigation bar -->
|
||||
<div>
|
||||
<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>
|
||||
</button>
|
||||
<span *ngFor="let directory of directoryChain; let last = last">
|
||||
<div class="arrow">
|
||||
<mat-icon>chevron_right</mat-icon>
|
||||
</div>
|
||||
|
||||
<button class="folder" mat-button (click)="changeDirectory(directory.id)" *ngIf="!last">
|
||||
<button
|
||||
class="folder"
|
||||
mat-button
|
||||
(click)="changeDirectory(directory.id)"
|
||||
[disabled]="isMultiSelect"
|
||||
*ngIf="!last"
|
||||
>
|
||||
<span class="folder-text">
|
||||
{{ directory.title }}
|
||||
{{ directory.filename }}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
@ -42,14 +47,15 @@
|
||||
mat-button
|
||||
[matMenuTriggerFor]="singleMediafileMenu"
|
||||
[matMenuTriggerData]="{ mediafile: directory }"
|
||||
[disabled]="isMultiSelect"
|
||||
*ngIf="last && showFileMenu(directory)"
|
||||
>
|
||||
<os-icon-container icon="arrow_drop_down" swap="true" size="large">
|
||||
{{ directory.title }}
|
||||
{{ directory.filename }}
|
||||
</os-icon-container>
|
||||
</button>
|
||||
<span class="folder fake-folder folder-text" *ngIf="last && !showFileMenu(directory)">
|
||||
{{ directory.title }}
|
||||
{{ directory.filename }}
|
||||
</span>
|
||||
</span>
|
||||
<span class="visibility" *ngIf="canEdit && directory && directory.has_inherited_access_groups">
|
||||
@ -83,23 +89,29 @@
|
||||
showHeader="false"
|
||||
vScrollAuto
|
||||
[dataSource]="dataSource"
|
||||
matCheckboxSelection="selection"
|
||||
[columns]="columnSet"
|
||||
[hideColumns]="hiddenColumns"
|
||||
(rowClick)="onSelectRow($event)"
|
||||
>
|
||||
<!-- Icon column -->
|
||||
<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" (click)="changeDirectory(mediafile.id)" *ngIf="mediafile.is_directory"> </a>
|
||||
<a class="detail-link" target="_blank" [routerLink]="mediafile.url" *ngIf="mediafile.is_file && !isMultiSelect">
|
||||
</a>
|
||||
<a class="detail-link" (click)="changeDirectory(mediafile.id)" *ngIf="mediafile.is_directory && !isMultiSelect">
|
||||
</a>
|
||||
<mat-icon class="file-icon">{{ mediafile.getIcon() }}</mat-icon>
|
||||
</div>
|
||||
|
||||
<!-- Title column -->
|
||||
<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" (click)="changeDirectory(mediafile.id)" *ngIf="mediafile.is_directory"> </a>
|
||||
<a class="detail-link" target="_blank" [routerLink]="mediafile.url" *ngIf="mediafile.is_file && !isMultiSelect">
|
||||
</a>
|
||||
<a class="detail-link" (click)="changeDirectory(mediafile.id)" *ngIf="mediafile.is_directory && !isMultiSelect">
|
||||
</a>
|
||||
<div class="innerTable">
|
||||
<div class="file-title ellipsis-overflow">
|
||||
{{ mediafile.title }}
|
||||
{{ mediafile.filename }}
|
||||
</div>
|
||||
<div class="info-text" *ngIf="mediafile.is_file">
|
||||
<span> {{ getDateFromTimestamp(mediafile.timestamp) }} · {{ mediafile.size }} </span>
|
||||
@ -135,6 +147,7 @@
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="singleMediafileMenu"
|
||||
[matMenuTriggerData]="{ mediafile: mediafile }"
|
||||
[disabled]="isMultiSelect"
|
||||
*ngIf="showFileMenu(mediafile)"
|
||||
>
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
@ -191,7 +204,7 @@
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span translate>Edit</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="move(moveDialog, mediafile)">
|
||||
<button mat-menu-item (click)="move(moveDialog, [mediafile])">
|
||||
<mat-icon>near_me</mat-icon>
|
||||
<span translate>Move</span>
|
||||
</button>
|
||||
@ -204,7 +217,7 @@
|
||||
</mat-menu>
|
||||
|
||||
<!-- Menu for Mediafiles -->
|
||||
<!--<mat-menu #mediafilesMenu="matMenu">
|
||||
<mat-menu #mediafilesMenu="matMenu">
|
||||
<div *ngIf="!isMultiSelect">
|
||||
<button mat-menu-item *osPerms="'mediafiles.can_manage'" (click)="toggleMultiSelect()">
|
||||
<mat-icon>library_add</mat-icon>
|
||||
@ -212,6 +225,10 @@
|
||||
</button>
|
||||
</div>
|
||||
<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>
|
||||
<button mat-menu-item (click)="selectAll()">
|
||||
<mat-icon>done_all</mat-icon>
|
||||
@ -228,11 +245,11 @@
|
||||
[disabled]="!selectedRows.length"
|
||||
(click)="deleteSelected()"
|
||||
>
|
||||
<mat-icon>delete</mat-icon>
|
||||
<mat-icon color="warn">delete</mat-icon>
|
||||
<span translate>Delete</span>
|
||||
</button>
|
||||
</div>
|
||||
</mat-menu>-->
|
||||
</mat-menu>
|
||||
|
||||
<!-- File edit dialog -->
|
||||
<ng-template #fileEditDialog>
|
||||
@ -310,7 +327,7 @@
|
||||
<h1 mat-dialog-title>
|
||||
<span translate>Move into directory</span>
|
||||
</h1>
|
||||
<div mat-dialog-content>
|
||||
<div class="os-form-card-mobile" mat-dialog-content>
|
||||
<p translate>Please select the directory:</p>
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
@ -318,7 +335,7 @@
|
||||
[includeNone]="true"
|
||||
[noneTitle]="'Base folder'"
|
||||
listname="{{ 'Parent directory' | translate }}"
|
||||
[InputListValues]="directoryBehaviorSubject"
|
||||
[InputListValues]="filteredDirectoryBehaviorSubject"
|
||||
></os-search-value-selector>
|
||||
</div>
|
||||
<div mat-dialog-actions>
|
||||
|
@ -15,8 +15,9 @@ import { Title } from '@angular/platform-browser';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { columnFactory, createDS, PblDataSource } from '@pebula/ngrid';
|
||||
import { BehaviorSubject, Subscription } from 'rxjs';
|
||||
import { columnFactory, createDS, PblColumnDefinition } from '@pebula/ngrid';
|
||||
import { PblNgridDataMatrixRow } from '@pebula/ngrid/target-events';
|
||||
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { OperatorService } from 'app/core/core-services/operator.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 { Mediafile } from 'app/shared/models/mediafiles/mediafile';
|
||||
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 { ViewGroup } from 'app/site/users/models/view-group';
|
||||
import { MediafilesSortListService } from '../../services/mediafiles-sort-list.service';
|
||||
@ -41,12 +42,7 @@ import { MediafilesSortListService } from '../../services/mediafiles-sort-list.s
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class MediafileListComponent extends BaseViewComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Data source for the files
|
||||
*/
|
||||
public dataSource: PblDataSource<ViewMediafile>;
|
||||
|
||||
export class MediafileListComponent extends BaseListViewComponent<ViewMediafile> implements OnInit, OnDestroy {
|
||||
/**
|
||||
* Holds the actions for logos. Updated via an observable
|
||||
*/
|
||||
@ -65,15 +61,11 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
||||
public newDirectoryForm: FormGroup;
|
||||
public moveForm: FormGroup;
|
||||
public directoryBehaviorSubject: BehaviorSubject<ViewMediafile[]>;
|
||||
public filteredDirectoryBehaviorSubject: BehaviorSubject<ViewMediafile[]> = new BehaviorSubject<ViewMediafile[]>(
|
||||
[]
|
||||
);
|
||||
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
|
||||
*/
|
||||
@ -113,6 +105,10 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
||||
hidden.push('info');
|
||||
}
|
||||
|
||||
if (!this.isMultiSelect) {
|
||||
hidden.push('selection');
|
||||
}
|
||||
|
||||
if (!this.canAccessFileMenu) {
|
||||
hidden.push('menu');
|
||||
}
|
||||
@ -120,45 +116,55 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
||||
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
|
||||
*/
|
||||
public columnSet = columnFactory()
|
||||
.table(
|
||||
{
|
||||
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'
|
||||
}
|
||||
)
|
||||
.table(...this.tableColumnDefinition)
|
||||
.build();
|
||||
|
||||
public isMultiselect = false; // TODO
|
||||
private folderSubscription: Subscription;
|
||||
private directorySubscription: Subscription;
|
||||
public directory: ViewMediafile | null;
|
||||
public directoryChain: ViewMediafile[];
|
||||
|
||||
private directoryObservable: Observable<ViewMediafile[]> = new Observable();
|
||||
|
||||
/**
|
||||
* Constructs the component
|
||||
*
|
||||
@ -194,6 +200,7 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
||||
private cd: ChangeDetectorRef
|
||||
) {
|
||||
super(titleService, translate, matSnackBar);
|
||||
this.canMultiSelect = true;
|
||||
|
||||
this.newDirectoryForm = this.formBuilder.group({
|
||||
title: ['', Validators.required],
|
||||
@ -226,6 +233,8 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
||||
this.mediaManage.getFontActions().subscribe(action => {
|
||||
this.fontActions = action;
|
||||
});
|
||||
|
||||
this.createDataSource();
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
@ -250,25 +259,38 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
||||
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 {
|
||||
this.clearSubscriptions();
|
||||
|
||||
this.folderSubscription = this.repo.getListObservableDirectory(directoryId).subscribe(mediafiles => {
|
||||
this.directoryObservable = this.repo.getListObservableDirectory(directoryId);
|
||||
this.folderSubscription = this.directoryObservable.subscribe(mediafiles => {
|
||||
if (mediafiles) {
|
||||
this.dataSource = createDS<ViewMediafile>()
|
||||
.onTrigger(() => mediafiles)
|
||||
.create();
|
||||
this.cd.detectChanges();
|
||||
this.dataSource.refresh();
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
});
|
||||
|
||||
if (directoryId) {
|
||||
this.directorySubscription = this.repo.getViewModelObservable(directoryId).subscribe(d => {
|
||||
this.directory = d;
|
||||
if (d) {
|
||||
this.directoryChain = d.getDirectoryChain();
|
||||
this.directorySubscription = this.repo.getViewModelObservable(directoryId).subscribe(newDirectory => {
|
||||
this.directory = newDirectory;
|
||||
if (newDirectory) {
|
||||
this.directoryChain = newDirectory.getDirectoryChain();
|
||||
// Update the URL.
|
||||
this.router.navigate(['/mediafiles/files/' + d.path], {
|
||||
this.router.navigate(['/mediafiles/files/' + newDirectory.path], {
|
||||
replaceUrl: true
|
||||
});
|
||||
} else {
|
||||
@ -287,8 +309,6 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public onMainEvent(): void {
|
||||
const path = '/mediafiles/upload/' + (this.directory ? this.directory.path : '');
|
||||
this.router.navigate([path]);
|
||||
@ -300,20 +320,22 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
||||
* @param file the selected file
|
||||
*/
|
||||
public onEditFile(file: ViewMediafile): void {
|
||||
this.fileToEdit = file;
|
||||
if (!this.isMultiSelect) {
|
||||
this.fileToEdit = file;
|
||||
|
||||
this.fileEditForm = this.fb.group({
|
||||
title: [file.title, Validators.required],
|
||||
access_groups_id: [file.access_groups_id]
|
||||
});
|
||||
this.fileEditForm = this.fb.group({
|
||||
title: [file.filename, Validators.required],
|
||||
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) => {
|
||||
if (event.key === 'Enter' && event.shiftKey && this.fileEditForm.valid) {
|
||||
this.onSaveEditedFile(this.fileEditForm.value);
|
||||
}
|
||||
});
|
||||
dialogRef.keydownEvents().subscribe((event: KeyboardEvent) => {
|
||||
if (event.key === 'Enter' && event.shiftKey && this.fileEditForm.valid) {
|
||||
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
|
||||
*
|
||||
@ -415,22 +445,50 @@ export class MediafileListComponent extends BaseViewComponent implements OnInit,
|
||||
parent_id: this.directory ? this.directory.id : null,
|
||||
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 {
|
||||
this.newDirectoryForm.reset();
|
||||
public move(templateRef: TemplateRef<string>, mediafiles: ViewMediafile[]): void {
|
||||
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);
|
||||
|
||||
dialogRef.afterClosed().subscribe(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 {
|
||||
if (this.folderSubscription) {
|
||||
this.folderSubscription.unsubscribe();
|
||||
|
@ -17,7 +17,7 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'upload',
|
||||
data: { basePerm: 'mediafiles.can_upload' },
|
||||
data: { basePerm: 'mediafiles.can_manage' },
|
||||
children: [{ path: '**', component: MediaUploadComponent }],
|
||||
pathMatch: 'prefix'
|
||||
}
|
||||
|
@ -50,6 +50,14 @@ export class ViewMediafile extends BaseViewModelWithListOfSpeakers<Mediafile>
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -32,7 +32,14 @@ class MediafileViewSet(ModelViewSet):
|
||||
"""
|
||||
if self.action in ("list", "retrieve", "metadata"):
|
||||
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(
|
||||
self.request.user, "mediafiles.can_manage"
|
||||
)
|
||||
@ -150,6 +157,49 @@ class MediafileViewSet(ModelViewSet):
|
||||
|
||||
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):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user