diff --git a/src/app/main/services/datadog-rum.service.ts b/src/app/main/services/datadog-rum.service.ts index ae842a1457..2900128a88 100644 --- a/src/app/main/services/datadog-rum.service.ts +++ b/src/app/main/services/datadog-rum.service.ts @@ -45,7 +45,7 @@ export class DatadogRumService { } setActiveUser(identifier: any, identifierType: 'iri' | 'email' | 'username'): void { - if(datadogRum.getInternalContext().application_id) { + if (datadogRum.getInternalContext().application_id) { datadogRum.setUser({ id: identifier, identifierType: identifierType @@ -54,7 +54,7 @@ export class DatadogRumService { } removeActiveUser(): void { - if(datadogRum.getInternalContext().application_id) { + if (datadogRum.getInternalContext().application_id) { datadogRum.removeUser(); } } diff --git a/src/app/main/status/status.component.html b/src/app/main/status/status.component.html index 9ac9802693..c6bb9e93d9 100644 --- a/src/app/main/status/status.component.html +++ b/src/app/main/status/status.component.html @@ -1,5 +1,4 @@ -
- +
@@ -20,13 +19,11 @@

{{message.type | uppercase}} {{status}} | {{message?.messa

Please come back in a few minutes and try to reload the page.

-

+

-
-
diff --git a/src/app/main/status/status.component.scss b/src/app/main/status/status.component.scss index f32c15740f..477d5a5385 100644 --- a/src/app/main/status/status.component.scss +++ b/src/app/main/status/status.component.scss @@ -1,9 +1,11 @@ @import "../../../assets/style/responsive"; +@import "../../../assets/style/config"; .status-page { background: #fff; height: 100%; width: 100%; + z-index: 999; .container { display: grid; @@ -11,8 +13,8 @@ grid-template-rows: 1fr; align-items: stretch; align-content: space-evenly; - padding-top: 120px; - width: 480px; + padding-top: 16px; + max-width: 480px; margin: 0 auto; .image, @@ -20,8 +22,8 @@ justify-self: stretch; align-self: stretch; box-sizing: border-box; - max-height: 280px; - width: 360px; + min-height: 280px; + max-width: 360px; &.status-message { line-height: 1.5; @@ -35,10 +37,24 @@ } } } + + &.representation-error { + background: none; + + .image { + display: none !important; + } + } } .action { - margin-top: 72px; + width: 360px; + + p { + width: 100%; + word-wrap: break-word; + word-break: break-word; + } } // mobile device: phone diff --git a/src/app/main/status/status.component.ts b/src/app/main/status/status.component.ts index 59dcf2ea84..da3e63dc6e 100644 --- a/src/app/main/status/status.component.ts +++ b/src/app/main/status/status.component.ts @@ -25,10 +25,10 @@ export class StatusComponent implements OnInit { @Input() comment?: string; @Input() url?: string; + @Input() representation?: 'archive' | 'audio' | 'document' | 'still-image' | 'video'; refresh = false; - // error message that will be shown in template message: StatusMsg; @@ -92,6 +92,7 @@ export class StatusComponent implements OnInit { ) { } ngOnInit() { + // status is not defined in Input parameter if (!this.status) { // but status is defined in app.routing @@ -100,12 +101,17 @@ export class StatusComponent implements OnInit { }); } - // set the page title - this._titleService.setTitle('DSP | Error ' + this.status); - // get error message by status this.message = this.getMsgByStatus(this.status); + if (this.representation) { + this.comment = `There was an error loading the ${this.representation} file representation. Try to open it directly by clicking on the file url below:`; + this.message.action = 'goto'; + } else { + // set the page title only in case of main error + this._titleService.setTitle(`DSP | ${this.getTypeByStatus(this.status).toUpperCase()} ${this.status}`); + } + } getMsgByStatus(status: number): StatusMsg { diff --git a/src/app/project/ontology/resource-class-form/resource-class-form.component.ts b/src/app/project/ontology/resource-class-form/resource-class-form.component.ts index dccc5edf38..a476ffae07 100644 --- a/src/app/project/ontology/resource-class-form/resource-class-form.component.ts +++ b/src/app/project/ontology/resource-class-form/resource-class-form.component.ts @@ -311,7 +311,7 @@ export class ResourceClassFormComponent implements OnInit, AfterViewChecked { this.lastModificationDate = classLabelResponse.lastModificationDate; onto4Comment.lastModificationDate = this.lastModificationDate; - if(updateComment.comments.length) { // if the comments array is not empty, send a request to update the comments + if (updateComment.comments.length) { // if the comments array is not empty, send a request to update the comments this._dspApiConnection.v2.onto.updateResourceClass(onto4Comment).subscribe( (classCommentResponse: ResourceClassDefinitionWithAllLanguages) => { this.lastModificationDate = classCommentResponse.lastModificationDate; diff --git a/src/app/workspace/resource/representation/archive/archive.component.html b/src/app/workspace/resource/representation/archive/archive.component.html index 54c8f8f173..7be0f4698d 100644 --- a/src/app/workspace/resource/representation/archive/archive.component.html +++ b/src/app/workspace/resource/representation/archive/archive.component.html @@ -1,5 +1,8 @@
- - -
+ + diff --git a/src/app/workspace/resource/representation/document/document.component.scss b/src/app/workspace/resource/representation/document/document.component.scss index d7c480346f..43475e0ecd 100644 --- a/src/app/workspace/resource/representation/document/document.component.scss +++ b/src/app/workspace/resource/representation/document/document.component.scss @@ -9,6 +9,19 @@ $osd-height: 460px; :host { border: 1px solid $black; + @media (max-height: 636px) { + height: 364px; + } + + .mat-button-disabled { + color: $grey !important; + } + + app-status { + margin-top: 64px; + display: inline-block; + } + .pdf-container { color: $bright; height: 100%; diff --git a/src/app/workspace/resource/representation/document/document.component.ts b/src/app/workspace/resource/representation/document/document.component.ts index 0da8037ea9..c12782cf29 100644 --- a/src/app/workspace/resource/representation/document/document.component.ts +++ b/src/app/workspace/resource/representation/document/document.component.ts @@ -18,6 +18,7 @@ import { DialogComponent } from 'src/app/main/dialog/dialog.component'; import { ErrorHandlerService } from 'src/app/main/services/error-handler.service'; import { EmitEvent, Events, UpdatedFileEventValue, ValueOperationEventService } from '../../services/value-operation-event.service'; import { FileRepresentation } from '../file-representation'; +import { RepresentationService } from '../representation.service'; @Component({ selector: 'app-document', @@ -38,15 +39,18 @@ export class DocumentComponent implements OnInit, AfterViewInit { pdfQuery = ''; + failedToLoad = false; + constructor( @Inject(DspApiConnectionToken) private _dspApiConnection: KnoraApiConnection, private _dialog: MatDialog, private _errorHandler: ErrorHandlerService, + private _rs: RepresentationService, private _valueOperationEventService: ValueOperationEventService ) { } ngOnInit(): void { - + this.failedToLoad = !this._rs.doesFileExist(this.src.fileValue.fileUrl); } ngAfterViewInit() { diff --git a/src/app/workspace/resource/representation/replace-file-form/replace-file-form.component.ts b/src/app/workspace/resource/representation/replace-file-form/replace-file-form.component.ts index 83904b879c..f5f8897e9c 100644 --- a/src/app/workspace/resource/representation/replace-file-form/replace-file-form.component.ts +++ b/src/app/workspace/resource/representation/replace-file-form/replace-file-form.component.ts @@ -31,7 +31,7 @@ export class ReplaceFileFormComponent implements OnInit { saveFile() { const updateVal = this.uploadComponent.getUpdatedValue(this.propId); - if(updateVal instanceof UpdateFileValue) { + if (updateVal instanceof UpdateFileValue) { updateVal.filename = this.fileValue.filename; updateVal.id = this.propId; this.closeDialog.emit(updateVal); @@ -45,7 +45,7 @@ export class ReplaceFileFormComponent implements OnInit { this.warningMessages = []; - if(representationType === undefined){ + if (representationType === undefined){ this.warningMessages.push('File will be replaced.'); this.warningMessages.push('Please note that you are about to replace the file'); } diff --git a/src/app/workspace/resource/representation/representation.service.spec.ts b/src/app/workspace/resource/representation/representation.service.spec.ts new file mode 100644 index 0000000000..ad3da33841 --- /dev/null +++ b/src/app/workspace/resource/representation/representation.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { RepresentationService } from './representation.service'; + +describe('RepresentationService', () => { + let service: RepresentationService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(RepresentationService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/workspace/resource/representation/representation.service.ts b/src/app/workspace/resource/representation/representation.service.ts new file mode 100644 index 0000000000..83f34ca273 --- /dev/null +++ b/src/app/workspace/resource/representation/representation.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class RepresentationService { + + constructor() { } + + /** + * checks if representation file exists + * @param urlToFile sipi url to file representation + * @returns true if file exists + */ + doesFileExist(urlToFile: string): boolean { + // it seems that SIPI does not support HEAD request only --> xhr.open('HEAD') + // this is why we have to grab the sidecar file to check if the file exists + const pathToKnoraJson = urlToFile.substring(0, urlToFile.lastIndexOf('/')) + '/knora.json'; + try { + const xhr = new XMLHttpRequest(); + + xhr.open('GET', pathToKnoraJson, false); + xhr.withCredentials = true; + xhr.send(); + + return xhr.status === 200; + + } catch (e) { + return false; + } + } +} diff --git a/src/app/workspace/resource/representation/still-image/still-image.component.html b/src/app/workspace/resource/representation/still-image/still-image.component.html index 9dacc0e9d0..d3ba934e4e 100644 --- a/src/app/workspace/resource/representation/still-image/still-image.component.html +++ b/src/app/workspace/resource/representation/still-image/still-image.component.html @@ -1,29 +1,31 @@
+ + +
-
@@ -33,7 +35,7 @@ more_vert - + Open file in new tab - - - @@ -75,11 +77,11 @@ @@ -88,14 +90,15 @@ - - diff --git a/src/app/workspace/resource/representation/still-image/still-image.component.scss b/src/app/workspace/resource/representation/still-image/still-image.component.scss index e2d0916537..5440e21c6d 100644 --- a/src/app/workspace/resource/representation/still-image/still-image.component.scss +++ b/src/app/workspace/resource/representation/still-image/still-image.component.scss @@ -1,10 +1,20 @@ +@import "../../../../../assets/style/responsive"; @import "../../../../../assets/style/config"; :host { width: 100%; @media (max-height: 636px) { - height: 300px; + height: 364px; + } + + .mat-button-disabled { + color: $grey !important; + } + + app-status { + margin-top: 16px; + display: inline-block; } .osd-container { @@ -30,7 +40,13 @@ height: 40px; bottom: 0; padding: 8px 0; + + .active { + background: rgba(128,128,128,.39); + font-weight: bolder; + } } + } .navigation { diff --git a/src/app/workspace/resource/representation/still-image/still-image.component.spec.ts b/src/app/workspace/resource/representation/still-image/still-image.component.spec.ts index 692bb65f90..b70449f174 100644 --- a/src/app/workspace/resource/representation/still-image/still-image.component.spec.ts +++ b/src/app/workspace/resource/representation/still-image/still-image.component.spec.ts @@ -10,7 +10,6 @@ import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatToolbarModule } from '@angular/material/toolbar'; -import { By } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { Constants, MockResource, ReadGeomValue, ReadResource, ReadValue } from '@dasch-swiss/dsp-js'; import { AppInitService } from 'src/app/app-init.service'; diff --git a/src/app/workspace/resource/representation/still-image/still-image.component.ts b/src/app/workspace/resource/representation/still-image/still-image.component.ts index db1131a15d..d063270e05 100644 --- a/src/app/workspace/resource/representation/still-image/still-image.component.ts +++ b/src/app/workspace/resource/representation/still-image/still-image.component.ts @@ -44,6 +44,7 @@ import { NotificationService } from 'src/app/main/services/notification.service' import { DspCompoundPosition } from '../../dsp-resource'; import { EmitEvent, Events, UpdatedFileEventValue, ValueOperationEventService } from '../../services/value-operation-event.service'; import { FileRepresentation } from '../file-representation'; +import { RepresentationService } from '../representation.service'; /** @@ -133,6 +134,9 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit @Output() loaded = new EventEmitter(); + loading = true; + failedToLoad = false; + regionDrawMode = false; // stores whether viewer is currently drawing a region private _regionDragInfo; // stores the information of the first click for drawing a region private _viewer: OpenSeadragon.Viewer; @@ -147,6 +151,7 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit private _matIconRegistry: MatIconRegistry, private _notification: NotificationService, private _renderer: Renderer2, + private _rs: RepresentationService, private _valueOperationEventService: ValueOperationEventService ) { OpenSeadragon.setString('Tooltips.Home', ''); @@ -190,6 +195,7 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit // tODO: check if this is necessary or could be handled below // (remove the 'else' before the 'if', so changes['activateRegion'] is always checked for) } + if (this.activateRegion !== undefined) { this._highlightRegion(this.activateRegion); } @@ -241,9 +247,8 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit * prevents navigation by mouse (so that the region can be accurately drawn). */ drawButtonClicked(): void { - - this.regionDrawMode = true; - this._viewer.setMouseNavEnabled(false); + this.regionDrawMode = !this.regionDrawMode; + this._viewer.setMouseNavEnabled(!this.regionDrawMode); } /** @@ -318,7 +323,9 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit const commentValue = (geom.region.properties[Constants.HasComment] ? geom.region.properties[Constants.HasComment][0].strval : ''); - this._createSVGOverlay(geom.region.id, geometry, aspectRatio, imageXOffset, geom.region.label, commentValue); + if (!this.failedToLoad) { + this._createSVGOverlay(geom.region.id, geometry, aspectRatio, imageXOffset, geom.region.label, commentValue); + } imageXOffset++; } @@ -377,6 +384,11 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit }); } + openPage(p: number) { + this.regionDrawMode = false; + this.goToPage.emit(p); + } + private _replaceFile(file: UpdateFileValue) { const updateRes = new UpdateResource(); updateRes.id = this.parentResource.id; @@ -576,8 +588,8 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit // rotateRightButton: 'DSP_OSD_ROTATE_RIGHT', // doesn't work yet showNavigator: true, navigatorPosition: 'ABSOLUTE' as const, - navigatorTop: '40px', - navigatorLeft: 'calc(100% - 160px)', + navigatorTop: 'calc(100% - 136px)', + navigatorLeft: 'calc(100% - 136px)', navigatorHeight: '120px', navigatorWidth: '120px', gestureSettingsMouse: { @@ -606,16 +618,41 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit // the first image has its left side at x = 0, and all images are scaled to have a width of 1 in viewport coordinates. // see also: https://openseadragon.github.io/examples/viewport-coordinates/ + // reset the status + this.failedToLoad = false; + const fileValues: ReadFileValue[] = this.images.map( - (img) => (img.fileValue) + img => img.fileValue ); // display only the defined range of this.images const tileSources: object[] = this._prepareTileSourcesFromFileValues(fileValues); this.removeOverlays(); - this._viewer.open(tileSources); + this._viewer.addOnceHandler('open', (args) => { + // check if the current image exists + if (this.iiifUrl.includes(args.source['@id'])) { + this.failedToLoad = !this._rs.doesFileExist(args.source['@id'] + '/info.json'); + if (this.failedToLoad) { + // failed to laod + // disable mouse navigation incl. zoom + this._viewer.setMouseNavEnabled(false); + // disable the navigator + this._viewer.navigator.element.style.display = 'none'; + // disable the region draw mode + this.regionDrawMode = false; + } else { + // enable mouse navigation incl. zoom + this._viewer.setMouseNavEnabled(true); + // enable the navigator + this._viewer.navigator.element.style.display = 'block'; + } + this.loading = false; + } + }); + + this._viewer.open(tileSources); } /** @@ -631,6 +668,8 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit const imageYOffset = 0; const tileSources = []; + // let i = 0; + for (const image of images) { const sipiBasePath = image.iiifBaseUrl + '/' + image.filename; const width = image.dimX; @@ -651,7 +690,8 @@ export class StillImageComponent implements OnChanges, OnDestroy, AfterViewInit }] }, x: imageXOffset, - y: imageYOffset + y: imageYOffset, + preload: true }); imageXOffset++; diff --git a/src/app/workspace/resource/representation/video/video.component.html b/src/app/workspace/resource/representation/video/video.component.html index 272ae6b471..29e3ffd577 100644 --- a/src/app/workspace/resource/representation/video/video.component.html +++ b/src/app/workspace/resource/representation/video/video.component.html @@ -1,5 +1,8 @@
+
+ +