Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(replace-file): Replace Uploaded Files (DEV-684) #695

Merged
merged 13 commits into from Apr 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Expand Up @@ -48,3 +48,6 @@ docs/bin
.DS_Store
Thumbs.db
*.code-workspace

# Generated docs
/site
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Expand Up @@ -153,6 +153,7 @@ import { SearchSelectOntologyComponent } from './workspace/search/advanced-searc
import { ExpertSearchComponent } from './workspace/search/expert-search/expert-search.component';
import { FulltextSearchComponent } from './workspace/search/fulltext-search/fulltext-search.component';
import { SearchPanelComponent } from './workspace/search/search-panel/search-panel.component';
import { ReplaceFileFormComponent } from './workspace/resource/representation/replace-file-form/replace-file-form.component';

// translate: AoT requires an exported function for factories
export function httpLoaderFactory(httpClient: HttpClient) {
Expand Down Expand Up @@ -294,6 +295,7 @@ export function httpLoaderFactory(httpClient: HttpClient) {
UserMenuComponent,
UsersComponent,
UsersListComponent,
ReplaceFileFormComponent,
],
imports: [
AngularSplitModule.forRoot(),
Expand Down
7 changes: 7 additions & 0 deletions src/app/main/dialog/dialog.component.html
Expand Up @@ -392,6 +392,13 @@
<app-add-region-form [resourceIri]="data.id"></app-add-region-form>
</div>

<div *ngSwitchCase="'replaceFile'">
<app-dialog-header [title]="data.title" [subtitle]="data.subtitle"></app-dialog-header>
<mat-dialog-content>
<app-replace-file-form [representation]="data.representation" [propId]="data.id" (closeDialog)="dialogRef.close($event)"></app-replace-file-form>
</mat-dialog-content>
</div>

<div *ngSwitchCase="'linkResources'">
<app-dialog-header [title]="data.title" [subtitle]="'Link resources'"></app-dialog-header>
<mat-dialog-content>
Expand Down
1 change: 1 addition & 0 deletions src/app/main/dialog/dialog.component.ts
Expand Up @@ -23,6 +23,7 @@ export interface DialogData {
resourceClassDefinition?: string;
fullSize?: boolean;
ontoIri?: string;
representation?: string; // respresentation type (stillImage, audio, etc.)
}

export interface ConfirmationWithComment {
Expand Down
Expand Up @@ -5,6 +5,9 @@
</mat-icon>
Click to download
</button>
<button mat-button matTooltip="Replace archive file" (click)="openReplaceFileDialog()">
<mat-icon>cloud_upload</mat-icon>
</button>
</div>
<div *ngIf="!src || !src.fileValue.fileUrl">
No valid file url found for this resource.
Expand Down
@@ -1,6 +1,12 @@
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Component, Input, OnInit } from '@angular/core';
import { Component, Inject, Input, OnInit } from '@angular/core';
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
import { Constants, UpdateFileValue, UpdateResource, UpdateValue, WriteValueResponse, ReadResource, ApiResponseError, KnoraApiConnection, ReadArchiveFileValue } from '@dasch-swiss/dsp-js';
import { mergeMap } from 'rxjs/operators';
import { DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens';
import { DialogComponent } from 'src/app/main/dialog/dialog.component';
import { ErrorHandlerService } from 'src/app/main/error/error-handler.service';
import { EmitEvent, Events, UpdatedFileEventValue, ValueOperationEventService } from '../../services/value-operation-event.service';
import { FileRepresentation } from '../file-representation';

@Component({
Expand All @@ -11,27 +17,21 @@ import { FileRepresentation } from '../file-representation';
export class ArchiveComponent implements OnInit {

@Input() src: FileRepresentation;
@Input() parentResource: ReadResource;

originalFilename: string;
temp: string;

constructor(
@Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection,
private readonly _http: HttpClient,
private _errorHandler: ErrorHandlerService
private _dialog: MatDialog,
private _errorHandler: ErrorHandlerService,
private _valueOperationEventService: ValueOperationEventService
) { }

ngOnInit(): void {
const requestOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
withCredentials: true
};

const pathToJson = this.src.fileValue.fileUrl.substring(0, this.src.fileValue.fileUrl.lastIndexOf('/')) + '/knora.json';

this._http.get(pathToJson, requestOptions).subscribe(
res => {
this.originalFilename = res['originalFilename'];
}
);
this._getOriginalFilename();
}

// https://stackoverflow.com/questions/66986983/angular-10-download-file-from-firebase-link-without-opening-into-new-tab
Expand Down Expand Up @@ -60,4 +60,68 @@ export class ArchiveComponent implements OnInit {
e.click();
document.body.removeChild(e);
}

openReplaceFileDialog(){
const propId = this.parentResource.properties[Constants.HasArchiveFileValue][0].id;

const dialogConfig: MatDialogConfig = {
width: '800px',
maxHeight: '80vh',
position: {
top: '112px'
},
data: { mode: 'replaceFile', title: 'Archive (zip, x-tar, gzip)', subtitle: 'Update the archive file of this resource' , representation: 'archive', id: propId },
disableClose: true
};
const dialogRef = this._dialog.open(
DialogComponent,
dialogConfig
);

dialogRef.afterClosed().subscribe((data) => {
this._replaceFile(data);
});
}

private _getOriginalFilename() {
const requestOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' }),
withCredentials: true
};

const pathToJson = this.src.fileValue.fileUrl.substring(0, this.src.fileValue.fileUrl.lastIndexOf('/')) + '/knora.json';

this._http.get(pathToJson, requestOptions).subscribe(
res => {
this.originalFilename = res['originalFilename'];
}
);
}

private _replaceFile(file: UpdateFileValue) {
const updateRes = new UpdateResource();
updateRes.id = this.parentResource.id;
updateRes.type = this.parentResource.type;
updateRes.property = Constants.HasArchiveFileValue;
updateRes.value = file;

this._dspApiConnection.v2.values.updateValue(updateRes as UpdateResource<UpdateValue>).pipe(
mergeMap((res: WriteValueResponse) => this._dspApiConnection.v2.values.getValue(this.parentResource.id, res.uuid))
).subscribe(
(res2: ReadResource) => {
this.src.fileValue.fileUrl = (res2.properties[Constants.HasArchiveFileValue][0] as ReadArchiveFileValue).fileUrl;
this.src.fileValue.filename = (res2.properties[Constants.HasArchiveFileValue][0] as ReadArchiveFileValue).filename;
this.src.fileValue.strval = (res2.properties[Constants.HasArchiveFileValue][0] as ReadArchiveFileValue).strval;

this._getOriginalFilename();

this._valueOperationEventService.emit(
new EmitEvent(Events.FileValueUpdated, new UpdatedFileEventValue(
res2.properties[Constants.HasArchiveFileValue][0])));
},
(error: ApiResponseError) => {
this._errorHandler.showMessage(error);
}
);
}
}
@@ -1,4 +1,13 @@
<audio controls preload="auto">
<source [src]="audio" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
<div class="controls">
<div class="audio-player">
<audio id="audio" controls preload="auto">
<source [src]="audio" type="audio/mpeg">
Your browser does not support the audio element.
</audio>
</div>
<div class="upload-button">
<button mat-button matTooltip="Replace audio file" (click)="openReplaceFileDialog()">
<mat-icon>cloud_upload</mat-icon>
</button>
</div>
</div>
@@ -1,3 +1,18 @@
audio {
.controls {
width: 100%;

.audio-player,
.upload-button {
display: inline-block;
vertical-align: middle;
}

.audio-player {
width: 80%;

audio {
width: 100%;
}
}
}

71 changes: 69 additions & 2 deletions src/app/workspace/resource/representation/audio/audio.component.ts
@@ -1,5 +1,12 @@
import { Component, Input, OnInit } from '@angular/core';
import { Component, Inject, Input, OnInit } from '@angular/core';
import { MatDialog, MatDialogConfig } from '@angular/material/dialog';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { UpdateFileValue, UpdateResource, Constants, UpdateValue, WriteValueResponse, ReadResource, ApiResponseError, KnoraApiConnection, ReadAudioFileValue } from '@dasch-swiss/dsp-js';
import { mergeMap } from 'rxjs/operators';
import { DspApiConnectionToken } from 'src/app/main/declarations/dsp-api-tokens';
import { DialogComponent } from 'src/app/main/dialog/dialog.component';
import { ErrorHandlerService } from 'src/app/main/error/error-handler.service';
import { EmitEvent, Events, UpdatedFileEventValue, ValueOperationEventService } from '../../services/value-operation-event.service';

import { FileRepresentation } from '../file-representation';

Expand All @@ -11,15 +18,75 @@ import { FileRepresentation } from '../file-representation';
export class AudioComponent implements OnInit {

@Input() src: FileRepresentation;
@Input() parentResource: ReadResource;

audio: SafeUrl;

constructor(
private _sanitizer: DomSanitizer
@Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection,
private _sanitizer: DomSanitizer,
private _dialog: MatDialog,
private _errorHandler: ErrorHandlerService,
private _valueOperationEventService: ValueOperationEventService
) { }

ngOnInit(): void {
this.audio = this._sanitizer.bypassSecurityTrustUrl(this.src.fileValue.fileUrl);
}

openReplaceFileDialog(){
const propId = this.parentResource.properties[Constants.HasAudioFileValue][0].id;

const dialogConfig: MatDialogConfig = {
width: '800px',
maxHeight: '80vh',
position: {
top: '112px'
},
data: { mode: 'replaceFile', title: 'Audio', subtitle: 'Update the audio file of this resource' , representation: 'audio', id: propId },
disableClose: true
};
const dialogRef = this._dialog.open(
DialogComponent,
dialogConfig
);

dialogRef.afterClosed().subscribe((data) => {
this._replaceFile(data);
});
}

private _replaceFile(file: UpdateFileValue) {
const updateRes = new UpdateResource();
updateRes.id = this.parentResource.id;
updateRes.type = this.parentResource.type;
updateRes.property = Constants.HasAudioFileValue;
updateRes.value = file;

this._dspApiConnection.v2.values.updateValue(updateRes as UpdateResource<UpdateValue>).pipe(
mergeMap((res: WriteValueResponse) => this._dspApiConnection.v2.values.getValue(this.parentResource.id, res.uuid))
).subscribe(
(res2: ReadResource) => {

this.src.fileValue.fileUrl = (res2.properties[Constants.HasAudioFileValue][0] as ReadAudioFileValue).fileUrl;
this.src.fileValue.filename = (res2.properties[Constants.HasAudioFileValue][0] as ReadAudioFileValue).filename;
this.src.fileValue.strval = (res2.properties[Constants.HasAudioFileValue][0] as ReadAudioFileValue).strval;
this.src.fileValue.valueCreationDate = (res2.properties[Constants.HasAudioFileValue][0] as ReadAudioFileValue).valueCreationDate;

this.audio = this._sanitizer.bypassSecurityTrustUrl(this.src.fileValue.fileUrl);

this._valueOperationEventService.emit(
new EmitEvent(Events.FileValueUpdated, new UpdatedFileEventValue(
res2.properties[Constants.HasAudioFileValue][0])));

const audioElem = document.getElementById('audio');
(audioElem as HTMLAudioElement).load();

},
(error: ApiResponseError) => {
this._errorHandler.showMessage(error);
}
);
}

}
Expand Up @@ -27,6 +27,9 @@
<button mat-icon-button id="DSP_PDF_ZOOM_IN" matTooltip="Zoom in" (click)="zoomFactor = zoomFactor + 0.2">
<mat-icon>add_circle_outline</mat-icon>
</button>
<button mat-icon-button id="DSP_PDF_REPLACE_FILE" class="replace-file" matTooltip="Replace PDF file" (click)="openReplaceFileDialog()">
<mat-icon>cloud_upload</mat-icon>
</button>
<a [href]="src.fileValue.fileUrl" download mat-icon-button id="DSP_PDF_DOWNLOAD" matTooltip="Open pdf in new tab">
<mat-icon>launch</mat-icon>
</a>
Expand Down