File

projects/web-mev/src/app/features/file-manager/components/file-list/file-list.component.ts

Description

View File List Component

Used to display the list of uploaded files

Implements

OnInit

Metadata

changeDetection ChangeDetectionStrategy.OnPush
selector mev-file-list
styleUrls ./file-list.component.scss
templateUrl ./file-list.component.html

Index

Properties
Methods

Constructor

constructor(httpClient: HttpClient, dialog: MatDialog, fileService: FileService, adapter: FileAdapter, notificationService: NotificationService, analysesService: AnalysesService, ref: ChangeDetectorRef)
Parameters :
Name Type Optional
httpClient HttpClient No
dialog MatDialog No
fileService FileService No
adapter FileAdapter No
notificationService NotificationService No
analysesService AnalysesService No
ref ChangeDetectorRef No

Methods

addDropBoxItem
addDropBoxItem()

Open Dropbox pop-up window to add files from Dropbox

Returns : void
addItem
addItem()

Open a modal dialog to upload files

Returns : void
deleteItem
deleteItem(i: number, id: string, file_name: string, readable_resource_type: string)

Open a modal dialog to delete a file

Parameters :
Name Type Optional
i number No
id string No
file_name string No
readable_resource_type string No
Returns : void
editItem
editItem(i: number, id: string, file_name: string, resource_type: string)

Open a modal dialog to edit file properties

Parameters :
Name Type Optional
i number No
id string No
file_name string No
resource_type string No
Returns : void
Public loadData
loadData()
Returns : void
Public ngOnDestroy
ngOnDestroy()
Returns : void
ngOnInit
ngOnInit()
Returns : void
previewItem
previewItem(fileId: string)

Open a modal dialog to preview file

Parameters :
Name Type Optional
fileId string No
Returns : void
refresh
refresh()
Returns : void
viewFileTypes
viewFileTypes()

Open a modal dialog to view detailed information about the available file types and their formats

Returns : void

Properties

dataSource
Type : ExampleDataSource | null
Public dialog
Type : MatDialog
displayedColumns
Type : []
Default value : [ 'name', 'resource_type', 'status', 'size', 'created', 'workspace', 'is_active', 'is_public', 'actions' ]
dropboxUploadCompleteMsg
Type : string
Default value : 'File(s) uploaded successfully. Please assign the specific type for the file(s) uploaded.'
dropboxUploadInProgressMsg
Type : string
Default value : ''
exampleDatabase
Type : FileService | null
Public fileService
Type : FileService
Private fileUploadProgressSubscription
Type : Subscription
Default value : new Subscription()
filter
Type : ElementRef
Decorators :
@ViewChild('filter', {static: true})
Public httpClient
Type : HttpClient
id
Type : string
isWait
Default value : false
Object
Default value : Object
paginator
Type : MatPaginator
Decorators :
@ViewChild(MatPaginator, {static: true})
sort
Type : MatSort
Decorators :
@ViewChild(MatSort, {static: true})
uploadInProgressMsg
Type : string
Default value : ''
uploadProgressData
Type : Map<string | object>
import {
  Component,
  ElementRef,
  OnInit,
  ViewChild,
  ChangeDetectionStrategy,
  ChangeDetectorRef
} from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { DataSource } from '@angular/cdk/collections';
import {
  BehaviorSubject,
  fromEvent,
  merge,
  Observable,
  Subscription
} from 'rxjs';
import { map, debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { MatDialog } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';

import { NotificationService } from '@core/core.module';
import { FileService } from '@file-manager/services/file-manager.service';
import { File, FileAdapter } from '@app/shared/models/file';
import { AddFileDialogComponent } from '@app/features/file-manager/components/dialogs/add-file-dialog/add-file-dialog.component';
import { EditFileDialogComponent } from '@app/features/file-manager/components/dialogs/edit-file-dialog/edit-file-dialog.component';
import { DeleteFileDialogComponent } from '@app/features/file-manager/components/dialogs/delete-file-dialog/delete-file-dialog.component';
import { Dropbox, DropboxChooseOptions } from '@file-manager/models/dropbox';
import { AnalysesService } from '@app/features/analysis/services/analysis.service';
import { PreviewDialogComponent } from '@app/features/workspace-detail/components/dialogs/preview-dialog/preview-dialog.component';
import { ViewFileTypesDialogComponent } from '../dialogs/view-file-types-dialog/view-file-types-dialog.component';

declare var Dropbox: Dropbox;

/**
 * View File List Component
 *
 * Used to display the list of uploaded files
 */
@Component({
  selector: 'mev-file-list',
  templateUrl: './file-list.component.html',
  styleUrls: ['./file-list.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class FileListComponent implements OnInit {
  dropboxUploadInProgressMsg = '';
  dropboxUploadCompleteMsg =
    'File(s) uploaded successfully. Please assign the specific type for the file(s) uploaded.';
  uploadInProgressMsg = '';

  displayedColumns = [
    'name',
    'resource_type',
    'status',
    'size',
    'created',
    'workspace',
    'is_active',
    'is_public',
    'actions'
  ];
  exampleDatabase: FileService | null;
  dataSource: ExampleDataSource | null;
  id: string;
  uploadProgressData: Map<string, object>;
  isWait = false;
  Object = Object;
  private fileUploadProgressSubscription: Subscription = new Subscription();

  constructor(
    public httpClient: HttpClient,
    public dialog: MatDialog,
    public fileService: FileService,
    private adapter: FileAdapter,
    private readonly notificationService: NotificationService,
    private readonly analysesService: AnalysesService,
    private ref: ChangeDetectorRef
  ) {}

  @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
  @ViewChild(MatSort, { static: true }) sort: MatSort;
  @ViewChild('filter', { static: true }) filter: ElementRef;

  ngOnInit() {
    this.loadData();

    this.fileUploadProgressSubscription = this.fileService.fileUploadsProgress.subscribe(
      uploadProgressData => {
        this.uploadProgressData = uploadProgressData;

        // show % of upload
        let txt = '';
        for (const key of Object.keys(uploadProgressData)) {
          txt += `File ${key} is ${uploadProgressData[key].percent}% uploaded. \n`;
        }
        this.uploadInProgressMsg = txt;
        this.ref.markForCheck();

        // refresh table if all files are uploaded
        const allFilesUploaded = Object.keys(uploadProgressData).every(
          key => uploadProgressData[key].isUploaded
        );
        if (allFilesUploaded) {
          this.refresh();
          this.uploadInProgressMsg = '';
          this.ref.markForCheck();
        }
      }
    );
  }

  public ngOnDestroy(): void {
    this.fileUploadProgressSubscription.unsubscribe();
  }

  refresh() {
    this.loadData();
  }

  /**
   * Open a modal dialog to upload files
   *
   */
  addItem() {
    const dialogRef = this.dialog.open(AddFileDialogComponent, {
      data: { file: File }
    });

    dialogRef.afterClosed().subscribe(result => {
      if (result === 1) {
        // After dialog is closed we're doing frontend updates
        // For add we're just pushing a new row inside FileService

        this.exampleDatabase.dataChange.value.push(
          this.fileService.getDialogData()
        );
      }
    });
  }

  /**
   * Open Dropbox pop-up window to add files from Dropbox
   *
   */
  addDropBoxItem() {
    const options: DropboxChooseOptions = {
      success: files => {
        const fileNames = files.map(file => file.name).join("' ,'");
        this.dropboxUploadInProgressMsg = `Uploading file(s) '${fileNames}' from Dropbox...`;
        this.ref.markForCheck();
        const filesToUpload = files.map(file => ({
          download_link: file.link,
          filename: file.name
        }));
        this.fileService.addDropboxFile(filesToUpload).subscribe(data => {
          this.notificationService.success(this.dropboxUploadCompleteMsg);
          this.dropboxUploadInProgressMsg = '';
          this.ref.markForCheck();
          this.refresh();
        });
      },
      cancel: () => {},
      linkType: 'direct',
      multiselect: true,
      folderselect: false
    };
    Dropbox.choose(options);
  }

  /**
   * Open a modal dialog to edit file properties
   *
   */
  editItem(i: number, id: string, file_name: string, resource_type: string) {
    this.id = id;
    const dialogRef = this.dialog.open(EditFileDialogComponent, {
      data: { id: id, name: file_name, resource_type: resource_type }
    });

    dialogRef.afterClosed().subscribe(result => {
      if (result === 1) {
        // When using an edit things are little different, firstly we find record inside FileService by id
        const foundIndex = this.exampleDatabase.dataChange.value.findIndex(
          x => x.id === this.id
        );
        // Then you update that record using data from dialogData (values you entered)
        this.exampleDatabase.dataChange.value[
          foundIndex
        ] = this.fileService.getDialogData();
        // And lastly refresh table
        this.refresh();
      }
    });
  }

  /**
   * Open a modal dialog to view detailed information about the available file types and their formats
   *
   */
  viewFileTypes() {
    this.dialog.open(ViewFileTypesDialogComponent);
  }

  /**
   * Open a modal dialog to preview file
   *
   */
  previewItem(fileId: string) {
    this.isWait = true;
    this.fileService.getFilePreview(fileId).subscribe(data => {
      const previewData = {};
      if (data?.results?.length && 'rowname' in data.results[0]) {
        const minN = Math.min(data.results.length, 10);
        let slicedData = data.results.slice(0, minN);
        const columns = Object.keys(slicedData[0].values);
        const rows = slicedData.map(elem => elem.rowname);
        const values = slicedData.map(elem => {
          let rowValues = [];
          const elemValues = elem.values;
          columns.forEach(col => rowValues.push(elemValues[col]));
          return rowValues;
        });
        previewData['columns'] = columns;
        previewData['rows'] = rows;
        previewData['values'] = values;
      }
      this.isWait = false;
      this.ref.markForCheck();
      this.dialog.open(PreviewDialogComponent, {
        data: {
          previewData: previewData
        }
      });
    });
  }

  /**
   * Open a modal dialog to delete a file
   *
   */
  deleteItem(
    i: number,
    id: string,
    file_name: string,
    readable_resource_type: string
  ) {
    this.id = id;
    const dialogRef = this.dialog.open(DeleteFileDialogComponent, {
      data: {
        id: id,
        name: file_name,
        readable_resource_type: readable_resource_type
      }
    });

    dialogRef.afterClosed().subscribe(result => {
      if (result === 1) {
        const foundIndex = this.exampleDatabase.dataChange.value.findIndex(
          x => x.id === this.id
        );
        // for delete we use splice in order to remove single object from FileService
        this.exampleDatabase.dataChange.value.splice(foundIndex, 1);
        this.refresh();
      }
    });
  }

  public loadData() {
    this.exampleDatabase = new FileService(
      this.httpClient,
      this.adapter,
      this.analysesService
    );
    this.dataSource = new ExampleDataSource(
      this.exampleDatabase,
      this.paginator,
      this.sort
    );
    fromEvent(this.filter.nativeElement, 'keyup')
      .pipe(debounceTime(150), distinctUntilChanged())
      .subscribe(() => {
        if (!this.dataSource) {
          return;
        }
        this.dataSource.filter = this.filter.nativeElement.value;
      });
  }
}

export class ExampleDataSource extends DataSource<File> {
  _filterChange = new BehaviorSubject('');

  get filter(): string {
    return this._filterChange.value;
  }

  set filter(filter: string) {
    this._filterChange.next(filter);
  }

  filteredData: File[] = [];
  renderedData: File[] = [];

  constructor(
    public _exampleDatabase: FileService,
    public _paginator: MatPaginator,
    public _sort: MatSort
  ) {
    super();
    // Reset to the first page when the user changes the filter.
    this._filterChange.subscribe(() => (this._paginator.pageIndex = 0));
  }

  /**
   * Connect function called by the table to retrieve one stream containing the data to render.
   */
  connect(): Observable<File[]> {
    // Listen for any changes in the base data, sorting, filtering, or pagination
    const displayDataChanges = [
      this._exampleDatabase.dataChange,
      this._sort.sortChange,
      this._filterChange,
      this._paginator.page
    ];
    this._exampleDatabase.getAllFiles();

    return merge(...displayDataChanges).pipe(
      map(() => {
        // Filter data

        this.filteredData = this._exampleDatabase.data
          .slice()
          .filter((file: File) => {
            const searchStr = (file.name + file.workspaces).toLowerCase();
            return searchStr.indexOf(this.filter.toLowerCase()) !== -1;
          });

        // Sort filtered data
        const sortedData = this.sortData(this.filteredData.slice());

        // Grab the page's slice of the filtered sorted data.
        const startIndex = this._paginator.pageIndex * this._paginator.pageSize;

        this.renderedData = sortedData.splice(
          startIndex,
          this._paginator.pageSize
        );

        return this.renderedData;
      })
    );
  }

  disconnect() {}

  /**
   * Returns a sorted copy of the database data.
   *
   */
  sortData(data: File[]): File[] {
    if (!this._sort.active || this._sort.direction === '') {
      return data;
    }

    return data.sort((a, b) => {
      let propertyA: any = '';
      let propertyB: any = '';

      switch (this._sort.active) {
        case 'id':
          [propertyA, propertyB] = [a.id, b.id];
          break;
        case 'name':
          [propertyA, propertyB] = [a.name, b.name];
          break;
        case 'resource_type':
          [propertyA, propertyB] = [a.resource_type, b.resource_type];
          break;
        case 'status':
          [propertyA, propertyB] = [a.status, b.status];
          break;
        case 'workspace':
          [propertyA, propertyB] = [a.workspaces, b.workspaces];
          break;
        case 'url':
          [propertyA, propertyB] = [a.url, b.url];
          break;
        case 'created':
          [propertyA, propertyB] = [new Date(a.created), new Date(b.created)];
          break;
        case 'size':
          [propertyA, propertyB] = [a.size, b.size];
          break;
      }

      const valueA = isNaN(+propertyA) ? propertyA : +propertyA;
      const valueB = isNaN(+propertyB) ? propertyB : +propertyB;

      return (
        (valueA < valueB ? -1 : 1) * (this._sort.direction === 'asc' ? 1 : -1)
      );
    });
  }
}
<mev-spinner-overlay *ngIf="isWait"></mev-spinner-overlay>

<div class="instruction">
  Upload and manage your files here. The table below shows all files associated with your
  account. After your files have been uploaded and validated, you can add them to
  workspaces to conduct your analysis.
  Note that we need to know what type of file we are working with. Hence, after upload, we
  ask that you specify a file type. To learn more about the available file types and their
  formats, <a (click)="viewFileTypes()">click here</a>. If applicable, you can preview the file to check that we have parsed it correctly. If
  something does not appear to be correct, you may have to edit your file offline and
  upload again.
</div>
<div class="file-btn-group">
  <div>
    <button mat-raised-button color="accent" (click)="addItem()">
      <mat-icon aria-label="Upload a file from computer">add</mat-icon>
      Upload a file from computer
    </button>
    <button mat-raised-button color="accent" (click)="addDropBoxItem()">
      <mat-icon fontSet="material-icons-outlined" aria-label="Upload from Dropbox">backup</mat-icon>
      Upload from Dropbox
    </button>
  </div>
  <button mat-raised-button color="accent" (click)="refresh()">
    <mat-icon aria-label="Refresh">refresh</mat-icon>
    Refresh
  </button>
</div>


<div class="file-list-container">

  <div class="form">
    <mat-form-field floatPlaceholder="never" color="accent">
      <input matInput #filter placeholder="Filter files">
    </mat-form-field>
  </div>
  <div *ngIf="uploadInProgressMsg" class="process-status">{{ uploadInProgressMsg }}</div>
  <div *ngIf="dropboxUploadInProgressMsg" class="process-status">{{ dropboxUploadInProgressMsg }}</div>

  <mat-table #table [dataSource]="dataSource" matSort matSortActive="created" matSortDirection="desc" class="mat-cell">

    <ng-container matColumnDef="id">
      <mat-header-cell *matHeaderCellDef mat-sort-header>Id</mat-header-cell>
      <mat-cell *matCellDef="let row">{{row.id}}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="name">
      <mat-header-cell *matHeaderCellDef mat-sort-header class="file-table-header">File name</mat-header-cell>
      <mat-cell *matCellDef="let row" [class.warning-cell]="!row.resource_type"> {{row.name}}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="resource_type">
      <mat-header-cell *matHeaderCellDef mat-sort-header class="file-table-header">File type</mat-header-cell>
      <mat-cell *matCellDef="let row"
        [ngClass]="{'warning-cell': !row.resource_type, 'warning-cell--asterisk ': !row.resource_type}">
        {{ row.readable_resource_type || 'Please specify file type' }}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="status">
      <mat-header-cell *matHeaderCellDef mat-sort-header class="file-table-header">Status</mat-header-cell>
      <mat-cell *matCellDef="let row"> {{ row.status}}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="size">
      <mat-header-cell *matHeaderCellDef mat-sort-header class="file-table-header">File size</mat-header-cell>
      <mat-cell *matCellDef="let row"> {{ row.size | byteName }}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="created">
      <mat-header-cell *matHeaderCellDef mat-sort-header class="file-table-header">Creation date</mat-header-cell>
      <mat-cell *matCellDef="let row"> {{ row.created | date :'medium' }}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="is_active">
      <mat-header-cell *matHeaderCellDef mat-sort-header class="file-table-header">Active</mat-header-cell>
      <mat-cell *matCellDef="let row"> {{ row.is_active ? 'Active' : 'Not active' }} </mat-cell>
    </ng-container>

    <ng-container matColumnDef="is_public">
      <mat-header-cell *matHeaderCellDef mat-sort-header class="file-table-header">Public</mat-header-cell>
      <mat-cell *matCellDef="let row"> {{row.is_public ? 'Public' : 'Private'}}</mat-cell>
    </ng-container>

    <ng-container matColumnDef="workspace">
      <mat-header-cell *matHeaderCellDef mat-sort-header class="file-table-header">Workspace(s)</mat-header-cell>
      <mat-cell *matCellDef="let row"> {{row.workspaces}}</mat-cell>
    </ng-container>


    <!-- actions -->
    <ng-container matColumnDef="actions">
      <mat-header-cell *matHeaderCellDef class="file-table-header">Actions</mat-header-cell>

      <mat-cell *matCellDef="let row; let i=index;">
        <button mat-icon-button color="accent" title="Preview"
          (click)="previewItem(row.id)">
          <mat-icon aria-label="Preview">visibility</mat-icon>
        </button>

        <button mat-icon-button color="accent" title="Edit" [disabled]="!row.is_active"
          (click)="editItem(i, row.id, row.name, row.resource_type)">
          <mat-icon aria-label="Edit">edit</mat-icon>
        </button>

        <button mat-icon-button color="accent" title="Delete file" [disabled]="!row.is_active"
          (click)="deleteItem(i, row.id, row.name, row.readable_resource_type)">
          <mat-icon aria-label="Delete">delete</mat-icon>
        </button>

        <button mat-icon-button color="accent" title="Download" disabled>
          <mat-icon aria-label="Download">get_app</mat-icon>
        </button>

        <button mat-icon-button color="accent" title="Download to DropBox" disabled>
          <mat-icon aria-label="Download">cloud_download</mat-icon>
        </button>

      </mat-cell>
    </ng-container>

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

  <div class="no-results" [style.display]="dataSource.renderedData.length == 0 ? '' : 'none'">
    No results are available
  </div>

  <mat-paginator #paginator [length]="dataSource.filteredData.length" [pageIndex]="0" [pageSize]="10"
    [pageSizeOptions]="[10, 25, 100]">
  </mat-paginator>

  <div class="warning-note" [style.display]="dataSource.renderedData.length >= 0 ? '' : 'none'">
    * Please assign file types by clicking the Edit button in the table row
  </div>

</div>

./file-list.component.scss

a:hover {
  cursor: pointer;
}

.instruction {
  font-size: 14px;
  padding-top: 10px;
}

.mat-header-cell {
  background: #eeeeee;
}
.mat-column-actions {
  min-width: 250px;
}

.file-btn-group {
  display: flex;
  justify-content: space-between;
  padding-top: 20px;
}

button {
  margin-right: 10px;
}

mat-cell {
  word-wrap: break-word;
  display: inline-block;
  padding: 3px;
  padding-right: 5px;
}

.warning-cell {
  color: red;
  font-weight: bold;
}

.warning-cell--asterisk {
  &:after {
    content: ' *';
  }
}

.warning-note {
  font-family: Roboto, 'Helvetica Neue', sans-serif;
  font-size: 14px;
  font-weight: bold;
  color: grey;
  padding-top: 10px;
  padding-bottom: 10px;
}

.no-results {
  font-family: Roboto, 'Helvetica Neue', sans-serif;
  font-size: 12px;
}

.process-status {
  font-size: 14px;
  color: #666;
  white-space: pre;
}

.mat-column-actions {
  word-wrap: break-word !important;
  white-space: unset !important;
  flex: 0 0 20% !important;
  width: 20% !important;
}
Legend
Html element
Component
Html element with directive

result-matching ""

    No results matching ""