Skip to content
Snippets Groups Projects
files.component.ts 7.72 KiB
Newer Older
paf's avatar
paf committed
import {
  Component, OnInit, Inject, ViewChild
} from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
paf's avatar
paf committed
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatDialog } from '@angular/material/dialog';
import { Sort } from '@angular/material/sort';
rhenck's avatar
rhenck committed

import { saveAs } from 'file-saver';
rhenck's avatar
rhenck committed
import {
  ConfirmDialogComponent, ConfirmDialogData, MessageDialogComponent,
  MessageDialogData, MessageType
} from 'iqb-components';
import { map } from 'rxjs/operators';
rhenck's avatar
rhenck committed
import { WorkspaceDataService } from '../workspacedata.service';
import {
  IQBFileType, GetFileResponseData, IQBFile, IQBFileTypes
} from '../workspace.interfaces';
rhenck's avatar
rhenck committed
import { BackendService, FileDeletionReport } from '../backend.service';
import { MainDataService } from '../../maindata.service';
paf's avatar
paf committed
import { IqbFilesUploadQueueComponent } from './iqb-files';
paflov's avatar
paflov committed
interface FileStats {
  invalid: {
    [type in IQBFileType]?: number;
paflov's avatar
paflov committed
  }
  total: {
    count: number;
    invalid: number;
  };
Martin Mechtel's avatar
Martin Mechtel committed
  templateUrl: './files.component.html',
  styleUrls: ['./files.component.css']
export class FilesComponent implements OnInit {
  files: { [type in IQBFileType]?: MatTableDataSource<IQBFile> } = {};
  fileTypes = IQBFileTypes;
  displayedColumns = ['checked', 'name', 'size', 'modificationTime'];
  uploadUrl = '';
  fileNameAlias = 'fileforvo';
  lastSort:Sort = {
    active: 'name',
    direction: 'asc'
  typeLabels = {
    Testtakers: 'Teilnehmerlisten',
    Booklet: 'Testhefte',
    SysCheck: 'System-Check-Definitionen',
    Resource: 'Ressourcen',
    Unit: 'Units'
  };
paf's avatar
paf committed

  fileStats: FileStats = {
    total: {
      count: 0,
      invalid: 0
    },
    invalid: {},
paflov's avatar
paflov committed
  };
  @ViewChild('fileUploadQueue', { static: true }) uploadQueue: IqbFilesUploadQueueComponent;
paf's avatar
paf committed

  constructor(
    @Inject('SERVER_URL') private serverUrl: string,
    @Inject('VERONA_API_VERSION_SUPPORTED') private veronaApiVersionSupported: string,
    private bs: BackendService,
mechtelm's avatar
mechtelm committed
    public wds: WorkspaceDataService,
    public confirmDialog: MatDialog,
    public messageDialog: MatDialog,
    private mds: MainDataService,
    public snackBar: MatSnackBar
rhenck's avatar
rhenck committed
  ngOnInit(): void {
    this.uploadUrl = `${this.serverUrl}workspace/${this.wds.wsId}/file`;
mechtelm's avatar
mechtelm committed
    setTimeout(() => {
      this.mds.setSpinnerOn();
mechtelm's avatar
mechtelm committed
      this.updateFileList();
paf's avatar
paf committed
    });
  checkAll(isChecked: boolean, type: IQBFileType): void {
    this.files[type].data = this.files[type].data.map(file => {
      file.isChecked = isChecked;
      return file;
  deleteFiles(): void {
    if (this.wds.wsRole !== 'RW') {
      return;
    }

    const filesToDelete = [];
paf's avatar
paf committed
    Object.keys(this.files).forEach(type => {
      this.files[type].data.forEach(file => {
        if (file.isChecked) {
          filesToDelete.push(`${file.type}/${file.name}`);
    if (filesToDelete.length > 0) {
      const p = filesToDelete.length > 1;
      const dialogRef = this.confirmDialog.open(ConfirmDialogComponent, {
        width: '400px',
        data: <ConfirmDialogData>{
          title: 'Löschen von Dateien',
          content: `Sie haben ${p ? filesToDelete.length : 'eine'} Datei${p ? 'en' : ''}\` 
            ausgewählt. Soll${p ? 'en' : ''}  diese gelöscht werden?`,
          confirmbuttonlabel: 'Löschen',
          showcancel: true
        }
      });
      dialogRef.afterClosed().subscribe(result => {
        if (result !== false) {
          this.mds.setSpinnerOn();
          this.bs.deleteFiles(filesToDelete).subscribe((fileDeletionReport: FileDeletionReport) => {
            const message = [];
            if (fileDeletionReport.deleted.length > 0) {
              message.push(`${fileDeletionReport.deleted.length} Dateien erfolgreich gelöscht.`);
            }
            if (fileDeletionReport.not_allowed.length > 0) {
              message.push(`${fileDeletionReport.not_allowed.length} Dateien konnten nicht gelöscht werden.`);
            }
            if (fileDeletionReport.was_used.length > 0) {
              message.push(`${fileDeletionReport.was_used.length} Dateien werden von anderen verwendet 
              und wurden nicht gelöscht.`);
            }
            this.snackBar.open(message.join('<br>'), message.length > 1 ? 'Achtung' : '', { duration: 1000 });
            this.updateFileList();
          });
        }
      });
    } else {
      this.messageDialog.open(MessageDialogComponent, {
        width: '400px',
        data: <MessageDialogData>{
          title: 'Löschen von Dateien',
          content: 'Bitte markieren Sie erst Dateien!',
          type: MessageType.error
        }
      });
  updateFileList(empty = false): void {
      this.files = {};
      this.mds.setSpinnerOff();
      this.bs.getFiles()
        .pipe(map(fileList => this.addFrontendChecksToFiles(fileList)))
        .subscribe(fileList => {
          this.files = {};
          Object.keys(fileList)
            .forEach(type => {
              this.files[type] = new MatTableDataSource(fileList[type]);
            });
paf's avatar
paf committed
          this.fileStats = FilesComponent.getStats(fileList);
          this.setTableSorting(this.lastSort);
          this.mds.setSpinnerOff();
  private static getStats(fileList: GetFileResponseData): FileStats {
paflov's avatar
paflov committed
    const stats: FileStats = {
      total: {
        count: 0,
        invalid: 0
      },
      invalid: {},
paflov's avatar
paflov committed
    };
    Object.keys(fileList)
      .forEach(type => {
        fileList[type].forEach(file => {
          if (typeof stats.invalid[type] === 'undefined') {
            stats.invalid[type] = 0;
          }
          stats.total.count += 1;
          if (file.report.error && file.report.error.length) {
            stats.invalid[type] += 1;
            stats.total.invalid += 1;
            stats.testtakers += (typeof file.info.testtakers === 'number') ? file.info.testtakers : 0;
          }
        });
      });
paflov's avatar
paflov committed
    return stats;
paf's avatar
paf committed
  }

  private addFrontendChecksToFiles(fileList: GetFileResponseData): GetFileResponseData {
    Object.keys(fileList).forEach(type => {
      // eslint-disable-next-line no-param-reassign
      fileList[type] = fileList[type].map(files => this.addFrontendChecksToFile(files));
    });
    return fileList;
  private addFrontendChecksToFile(file: IQBFile): IQBFile {
    if (typeof file.info['verona-version'] !== 'undefined') {
      const fileMayor = file.info['verona-version'].toString().split('.').shift();
      const systemMayor = this.veronaApiVersionSupported.split('.').shift();
      if (fileMayor !== systemMayor) {
        if (typeof file.report.error === 'undefined') {
paf's avatar
paf committed
          // eslint-disable-next-line no-param-reassign
          file.report.error = [];
        }
        file.report.error.push(`Verona Version of this Player is not compatible 
          with this system's version (\`${this.veronaApiVersionSupported}\`)!`);
      }
    }
    return file;
  }

  download(file: IQBFile): void {
    this.mds.setSpinnerOn();
    this.bs.downloadFile(file.type, file.name)
          this.mds.setSpinnerOff();
            saveAs(fileData as Blob, file.name);

  setTableSorting(sort: Sort): void {
    this.lastSort = sort;
    function compare(a: number | string, b: number | string, isAsc: boolean) {
      if ((typeof a === 'string') && (typeof b === 'string')) {
        return a.localeCompare(b) * (isAsc ? 1 : -1);
      }
      return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
    }
    Object.keys(this.files).forEach(type => {
      this.files[type].data = this.files[type].data
        .sort((a, b) => compare(a[sort.active], b[sort.active], (sort.direction === 'asc')));
    });
  }