File

src/app/workspace-admin/files/files.component.ts

Implements

OnInit

Metadata

styleUrls ./files.component.css
templateUrl ./files.component.html

Index

Properties
Methods

Constructor

constructor(serverUrl: string, veronaApiVersionSupported: string, bs: BackendService, wds: WorkspaceDataService, confirmDialog: MatDialog, messageDialog: MatDialog, mds: MainDataService, snackBar: MatSnackBar)
Parameters :
Name Type Optional
serverUrl string No
veronaApiVersionSupported string No
bs BackendService No
wds WorkspaceDataService No
confirmDialog MatDialog No
messageDialog MatDialog No
mds MainDataService No
snackBar MatSnackBar No

Methods

Private addFrontendChecksToFile
addFrontendChecksToFile(file: IQBFile)
Parameters :
Name Type Optional
file IQBFile No
Returns : IQBFile
Private addFrontendChecksToFiles
addFrontendChecksToFiles(fileList: GetFileResponseData)
Parameters :
Name Type Optional
fileList GetFileResponseData No
checkAll
checkAll(isChecked: boolean, type: IQBFileType)
Parameters :
Name Type Optional
isChecked boolean No
type IQBFileType No
Returns : void
deleteFiles
deleteFiles()
Returns : void
download
download(file: IQBFile)
Parameters :
Name Type Optional
file IQBFile No
Returns : void
Private Static getStats
getStats(fileList: GetFileResponseData)
Parameters :
Name Type Optional
fileList GetFileResponseData No
Returns : FileStats
ngOnInit
ngOnInit()
Returns : void
setTableSorting
setTableSorting(sort: Sort)
Parameters :
Name Type Optional
sort Sort No
Returns : void
updateFileList
updateFileList(empty)
Parameters :
Name Optional Default value
empty No false
Returns : void

Properties

Public confirmDialog
Type : MatDialog
displayedColumns
Type : []
Default value : ['checked', 'name', 'size', 'modificationTime']
fileNameAlias
Type : string
Default value : 'fileforvo'
files
Default value : {}
fileStats
Type : FileStats
Default value : { total: { count: 0, invalid: 0 }, invalid: {}, testtakers: 0 }
fileTypes
Default value : IQBFileTypes
lastSort
Type : Sort
Default value : { active: 'name', direction: 'asc' }
Public messageDialog
Type : MatDialog
Public snackBar
Type : MatSnackBar
typeLabels
Type : object
Default value : { Testtakers: 'Teilnehmerlisten', Booklet: 'Testhefte', SysCheck: 'System-Check-Definitionen', Resource: 'Ressourcen', Unit: 'Units' }
uploadQueue
Type : IqbFilesUploadQueueComponent
Decorators :
@ViewChild('fileUploadQueue', {static: true})
uploadUrl
Type : string
Default value : ''
Public wds
Type : WorkspaceDataService
import {
  Component, OnInit, Inject, ViewChild
} from '@angular/core';
import { MatTableDataSource } from '@angular/material/table';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatDialog } from '@angular/material/dialog';
import { Sort } from '@angular/material/sort';

import { saveAs } from 'file-saver';
import {
  ConfirmDialogComponent, ConfirmDialogData, MessageDialogComponent,
  MessageDialogData, MessageType
} from 'iqb-components';
import { map } from 'rxjs/operators';
import { WorkspaceDataService } from '../workspacedata.service';
import {
  IQBFileType, GetFileResponseData, IQBFile, IQBFileTypes
} from '../workspace.interfaces';
import { BackendService, FileDeletionReport } from '../backend.service';
import { MainDataService } from '../../maindata.service';
import { IqbFilesUploadQueueComponent } from './iqb-files';

interface FileStats {
  invalid: {
    [type in IQBFileType]?: number;
  }
  total: {
    count: number;
    invalid: number;
  };
  testtakers: number;
}

@Component({
  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'
  };

  fileStats: FileStats = {
    total: {
      count: 0,
      invalid: 0
    },
    invalid: {},
    testtakers: 0
  };

  @ViewChild('fileUploadQueue', { static: true }) uploadQueue: IqbFilesUploadQueueComponent;

  constructor(
    @Inject('SERVER_URL') private serverUrl: string,
    @Inject('VERONA_API_VERSION_SUPPORTED') private veronaApiVersionSupported: string,
    private bs: BackendService,
    public wds: WorkspaceDataService,
    public confirmDialog: MatDialog,
    public messageDialog: MatDialog,
    private mds: MainDataService,
    public snackBar: MatSnackBar
  ) { }

  ngOnInit(): void {
    this.uploadUrl = `${this.serverUrl}workspace/${this.wds.wsId}/file`;
    setTimeout(() => {
      this.mds.setSpinnerOn();
      this.updateFileList();
    });
  }

  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 = [];
    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 {
    if (empty) {
      this.files = {};
      this.mds.setSpinnerOff();
    } else {
      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]);
            });
          this.fileStats = FilesComponent.getStats(fileList);
          this.setTableSorting(this.lastSort);
          this.mds.setSpinnerOff();
        });
    }
  }

  private static getStats(fileList: GetFileResponseData): FileStats {
    const stats: FileStats = {
      total: {
        count: 0,
        invalid: 0
      },
      invalid: {},
      testtakers: 0
    };
    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;
          }
        });
      });
    return stats;
  }

  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') {
          // 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)
      .subscribe(
        (fileData: Blob|boolean) => {
          this.mds.setSpinnerOff();
          if (fileData !== false) {
            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')));
    });
  }
}
<div class="columnhost">
  <div class="filelist">

    <mat-accordion class="example-headers-align" multi="true">
      <ng-container *ngFor="let type of fileTypes">
        <mat-expansion-panel [expanded]="true" *ngIf="files && files[type]">
          <mat-expansion-panel-header>
            <mat-panel-title>{{typeLabels[type]}}</mat-panel-title>
            <mat-panel-description>
              <span>{{files[type].data.length}} Datei{{files[type].data.length === 1 ? '' : 'en'}}</span>
              <span *ngIf="fileStats.invalid[type]">, davon {{fileStats.invalid[type]}} Fehlerhaft</span>
              <span *ngIf="type=='Testtakers'">, {{fileStats.testtakers}} Teilnehmer</span>
            </mat-panel-description>
          </mat-expansion-panel-header>

          <mat-table [dataSource]="files[type]" matSort (matSortChange)="setTableSorting($event)">
            <ng-container matColumnDef="checked">
              <mat-header-cell *matHeaderCellDef class="checkboxcell">
                <mat-checkbox (change)="checkAll($event.checked, type)"></mat-checkbox>
              </mat-header-cell>
              <mat-cell *matCellDef="let element" class="checkboxcell">
                <mat-checkbox [checked]="element.isChecked" (change)="element.isChecked=$event.checked"></mat-checkbox>
              </mat-cell>
            </ng-container>

            <ng-container matColumnDef="name">
              <mat-header-cell *matHeaderCellDef mat-sort-header class="namecell">Dateiname</mat-header-cell>
              <mat-cell *matCellDef="let element" class="namecell">
                <div class="file-report">
                  <button mat-button (click)="download(element)">{{element.name}}</button>
                  <span class="vertical-align-middle">
                    <ng-container *ngIf="element.report.error && element.report.error?.length; else: noError">
                      <mat-icon class="report-error">error</mat-icon>
                    </ng-container>
                    <ng-template #noError>
                      <ng-container *ngIf="element.report.warning && element.report.warning?.length">
                        <mat-icon class="report-warning">warning</mat-icon>
                      </ng-container>
                    </ng-template>
                  </span>
                  <mat-card class="full-file-report">
                    <mat-card-header *ngIf="element.info.label || element.id">
                      <mat-card-title>
                        {{element.info.label}}
                        <span
                            *ngIf="element.id !== element.name.toUpperCase()"
                            style="{{element.info.label ? 'color:silver' : ''}}">
                        #{{element.id}}
                      </span>
                      </mat-card-title>
                      <mat-card-subtitle>{{element.info.description}}</mat-card-subtitle>
                    </mat-card-header>
                    <mat-card-content>
                      <ng-container *ngFor="let level of ['error', 'warning', 'info']">
                        <div *ngFor="let message of element.report[level]">
                          <alert [level]="level" [text]="message"></alert>
                        </div>
                      </ng-container>
                    </mat-card-content>
                  </mat-card>
                </div>
              </mat-cell>
            </ng-container>

            <ng-container matColumnDef="modificationTime">
              <mat-header-cell *matHeaderCellDef mat-sort-header class="datecell"> Letzte Änderung </mat-header-cell>
              <mat-cell *matCellDef="let element" class="datecell">
                {{(element.modificationTime * 1000) | date: 'dd.MM.yy hh:mm'}}
              </mat-cell>
            </ng-container>

            <ng-container matColumnDef="size">
              <mat-header-cell *matHeaderCellDef mat-sort-header> Volle Größe  </mat-header-cell>
              <mat-cell *matCellDef="let element" style="white-space: nowrap;">
                {{(element.info.totalSize || element.size) | bytes}}
              </mat-cell>
            </ng-container>

            <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
            <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
          </mat-table>

        </mat-expansion-panel>
      </ng-container>
    </mat-accordion>
  </div>

  <div class="sidebar">

    <div class="buttons">
      <button mat-raised-button (click)="deleteFiles()" matTooltip="Markierte Dateien löschen" matTooltipPosition="above" [disabled]="wds.wsRole !== 'RW'">
        <mat-icon>delete</mat-icon>
      </button>
      <button mat-raised-button (click)="hiddenfileinput.click()" matTooltip="Dateien hochladen/aktualisieren" matTooltipPosition="above" [disabled]="wds.wsRole !== 'RW'">
        <mat-icon>cloud_upload</mat-icon>
      </button>
    </div>

    <input #hiddenfileinput type="file" name="fileforvo" multiple [iqbFilesUploadInputFor]="fileUploadQueue" [hidden]="true"/>

    <iqb-files-upload-queue #fileUploadQueue
      [httpUrl]="uploadUrl"
      [fileAlias]="fileNameAlias"
      [folderName]="'ws'"
      [folder]="'workspace'"
      (uploadCompleteEvent)="updateFileList()">
    </iqb-files-upload-queue>

    <div *ngIf="!uploadQueue.files?.length" class="workspace-report">
      <alert *ngIf="fileStats.total.invalid; else: workspaceValid" level="error" text="{{fileStats.total.invalid}}
        Datei{{fileStats.total.invalid == 1 ? '' : 'en'}} von {{fileStats.total.count}}
        {{fileStats.total.invalid == 1 ? 'ist' : 'sind'}} nicht valide oder
        {{fileStats.total.invalid == 1 ? 'hat' : 'haben'}} fehlende Abhängigkeiten
        und {{fileStats.total.invalid == 1 ? 'wird' : 'werden'}} ignoriert!">
      </alert>
      <ng-template #workspaceValid>
        <alert level="success" *ngIf="fileStats.total.count" text="Alle
          {{fileStats.total.count > 1 ? fileStats.total.count : ''}} Dateien im Workspace sind Valide."></alert>
      </ng-template>
    </div>

  </div>
</div>

./files.component.css

.columnhost {
  width: 100%;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: left;
}

.filelist {
  flex:  10 0 400px;
  margin-top: 0.7em;
}

mat-expansion-panel {
  overflow: visible;
}

mat-cell:first-of-type, mat-header-cell:first-of-type, mat-footer-cell:first-of-type {
  padding-left: 0
}

.checkboxcell {
  overflow: visible;
  flex: 0 0 30px;
}

.namecell {
  flex: 3 3 60px;
}

.namecell.mat-header-cell {
  padding-left: 16px;
}

.datecell {
  flex: 1 1 5px;
}

.sidebar {
  flex:  10 0 200px;
  padding-left: 1em;
}

.sidebar .buttons {
  margin-top: 0.7em;
  margin-bottom: 0.7em;
  display: inline-flex;
  vertical-align: middle;
  align-items: center;
}

.checkerror, .checkwarning, .checkinfo  {
  margin: 20px;
  font-size: 0.8em;
}

.report-error {
  color: #821324;
}

.report-warning {
  color: silver;
}

.report-info {
  color: blue;
}

.mat-raised-button {
  min-width: 100px;
  margin: 2px;
}

.file-report {
  cursor: pointer
}

.full-file-report {
  display: none;
}

.file-report:hover .full-file-report {
  display: block;
  position: absolute ;
  background: white;
  box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12);
  z-index: 1000;
  cursor: default;
  margin: 2px;
}

.full-file-report .mat-card-title,
.full-file-report .mat-card-subtitle {
  margin-left: -16px;
}

.full-file-report mat-card-content {
  max-height: 15em;
  overflow-y: auto
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""