diff --git a/src/app/workspace/resource/properties/properties.component.ts b/src/app/workspace/resource/properties/properties.component.ts index 40ee4e727d..8ddc9c5b00 100644 --- a/src/app/workspace/resource/properties/properties.component.ts +++ b/src/app/workspace/resource/properties/properties.component.ts @@ -114,7 +114,7 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy { */ @Output() referredResourceHovered: EventEmitter = new EventEmitter(); - @Output() regionColorChanged: EventEmitter = new EventEmitter(); + @Output() regionChanged: EventEmitter = new EventEmitter(); lastModificationDate: string; @@ -352,6 +352,10 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy { (response: UpdateResourceMetadataResponse) => { this.resource.res.label = payload.label; this.lastModificationDate = response.lastModificationDate; + // if annotations tab is active; a label of a region has been changed --> update regions + if (this.isAnnotation) { + this.regionChanged.emit(); + } }, (error: ApiResponseError) => { this._errorHandler.showMessage(error); @@ -456,8 +460,9 @@ export class PropertiesComponent implements OnInit, OnChanges, OnDestroy { if (updatedValue instanceof ReadTextValueAsXml) { this._updateStandoffLinkValue(); } - if (updatedValue instanceof ReadColorValue) { - this.regionColorChanged.emit(); + // if annotations tab is active; + if (this.isAnnotation) { + this.regionChanged.emit(); } } else { console.warn('No properties exist for this resource'); 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 8a8496dd6c..b54c2f3bbc 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,13 +1,10 @@ +@import "../../../../../assets/style/config"; // sizes $max-width: 800px; $panelSize: 40px; $osd-height: 460px; -// colors -$dark: #000; -$bright: #ccc; - :host { // display: inline-flex; width: 100%; @@ -57,6 +54,26 @@ $bright: #ccc; } } +.annotation-tooltip { + display: none; + position: fixed; + background-color: $black-60-opacity; + color: $bright; + padding: 8px; + border-radius: $border-radius; + min-height: 24px; + max-height: 258px; + max-width: 256px; + box-sizing: border-box; + transition: 0.1s; + transform: translate(16px, 16px); + font-size: small; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + z-index: 1000; +} + /* Overlay styling */ 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 f8e814a21c..9bf134b274 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 @@ -5,9 +5,7 @@ import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; import { HttpClientModule } from '@angular/common/http'; import { Component, OnInit, ViewChild } from '@angular/core'; import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; -import { MatButtonHarness } from '@angular/material/button/testing'; import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; -import { MatDialogHarness } from '@angular/material/dialog/testing'; import { MatIconModule } from '@angular/material/icon'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatToolbarModule } from '@angular/material/toolbar'; @@ -107,13 +105,12 @@ class TestHostComponent implements OnInit { this.readResource = res; }); - this.stillImageFileRepresentations - = [ - new FileRepresentation(stillImageFileValue, - [ - new Region(makeRegion([rectangleGeom], 'first')) - ]) - ]; + this.stillImageFileRepresentations = [ + new FileRepresentation(stillImageFileValue, + [new Region(makeRegion([rectangleGeom], 'first'))] + ) + ]; + } regHovered(regIri: string) { 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 766e28a234..f3b2326855 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 @@ -1,13 +1,11 @@ import { Component, ElementRef, - EventEmitter, - Inject, + EventEmitter, Inject, Input, OnChanges, - OnDestroy, - OnInit, - Output, + OnDestroy, Output, + Renderer2, SimpleChanges } from '@angular/core'; import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; @@ -17,7 +15,6 @@ import { ApiResponseError, Constants, CreateColorValue, - CreateFileValue, CreateGeomValue, CreateLinkValue, CreateResource, @@ -104,7 +101,7 @@ export class GeometryForRegion { */ interface PolygonsForRegion { - [key: string]: HTMLDivElement[]; + [key: string]: HTMLElement[]; } @@ -143,6 +140,7 @@ export class StillImageComponent implements OnChanges, OnDestroy { private _errorHandler: ErrorHandlerService, private _matIconRegistry: MatIconRegistry, private _notification: NotificationService, + private _renderer: Renderer2, private _valueOperationEventService: ValueOperationEventService ) { OpenSeadragon.setString('Tooltips.Home', ''); @@ -185,17 +183,15 @@ export class StillImageComponent implements OnChanges, OnDestroy { this._unhighlightAllRegions(); // 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); - } - if (this.currentTab === 'annotations') { - this.renderRegions(); - } - } else if (changes['activateRegion']) { + } + if (this.activateRegion !== undefined) { + this._highlightRegion(this.activateRegion); + } + if (this.currentTab === 'annotations') { + this.renderRegions(); + } + if (changes['activateRegion']) { this._unhighlightAllRegions(); - if (this.activateRegion !== undefined) { - this._highlightRegion(this.activateRegion); - } } } @@ -310,11 +306,13 @@ export class StillImageComponent implements OnChanges, OnDestroy { geometry.lineColor = colorValues[0].color; } - this._createSVGOverlay(geom.region.id, geometry, aspectRatio, imageXOffset, geom.region.label); + 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); + + imageXOffset++; } - imageXOffset++; } } @@ -326,7 +324,7 @@ export class StillImageComponent implements OnChanges, OnDestroy { for (const reg in this._regions) { if (this._regions.hasOwnProperty(reg)) { for (const pol of this._regions[reg]) { - if (pol instanceof HTMLDivElement) { + if (pol instanceof HTMLElement) { pol.remove(); } } @@ -345,7 +343,7 @@ export class StillImageComponent implements OnChanges, OnDestroy { this._notification.openSnackBar(message); } - openReplaceFileDialog(){ + openReplaceFileDialog() { const propId = this.parentResource.properties[Constants.HasStillImageFileValue][0].id; const dialogConfig: MatDialogConfig = { @@ -354,7 +352,7 @@ export class StillImageComponent implements OnChanges, OnDestroy { position: { top: '112px' }, - data: { mode: 'replaceFile', title: '2D Image (Still Image)', subtitle: 'Update image of the resource' , representation: 'stillImage', id: propId }, + data: { mode: 'replaceFile', title: '2D Image (Still Image)', subtitle: 'Update image of the resource', representation: 'stillImage', id: propId }, disableClose: true }; const dialogRef = this._dialog.open( @@ -476,7 +474,7 @@ export class StillImageComponent implements OnChanges, OnDestroy { if (!this.regionDrawMode) { return; } - const overlayElement = document.createElement('div'); + const overlayElement = this._renderer.createElement('div'); overlayElement.style.background = 'rgba(255,0,0,0.3)'; const viewportPos = this._viewer.viewport.pointFromPixel((event as OpenSeadragon.ViewerEvent).position); this._viewer.addOverlay(overlayElement, new OpenSeadragon.Rect(viewportPos.x, viewportPos.y, 0, 0)); @@ -523,7 +521,7 @@ export class StillImageComponent implements OnChanges, OnDestroy { */ private _highlightRegion(regionIri) { - const activeRegion: HTMLDivElement[] = this._regions[regionIri]; + const activeRegion: HTMLElement[] = this._regions[regionIri]; if (activeRegion !== undefined) { for (const pol of activeRegion) { @@ -658,19 +656,14 @@ export class StillImageComponent implements OnChanges, OnDestroy { * @param xOffset - the x-offset in Openseadragon viewport coordinates of the image on which the geometry should be placed * @param toolTip - the tooltip which should be displayed on mousehover of the svg element */ - private _createSVGOverlay(regionIri: string, geometry: RegionGeometry, aspectRatio: number, xOffset: number, toolTip: string): void { + private _createSVGOverlay(regionIri: string, geometry: RegionGeometry, aspectRatio: number, xOffset: number, regionLabel: string, regionComment: string): void { const lineColor = geometry.lineColor; const lineWidth = geometry.lineWidth; - const elt = document.createElement('div'); - elt.id = 'region-overlay-' + Math.random() * 10000; - elt.className = 'region'; - elt.title = toolTip; - elt.setAttribute('style', 'outline: solid ' + lineColor + ' ' + lineWidth + 'px;'); - - elt.addEventListener('click', (event: MouseEvent) => { - this.regionClicked.emit(regionIri); - }, false); + const regEle: HTMLElement = this._renderer.createElement('div'); + regEle.id = 'region-overlay-' + Math.random() * 10000; + regEle.className = 'region'; + regEle.setAttribute('style', 'outline: solid ' + lineColor + ' ' + lineWidth + 'px;'); const diffX = geometry.points[1].x - geometry.points[0].x; const diffY = geometry.points[1].y - geometry.points[0].y; @@ -684,11 +677,36 @@ export class StillImageComponent implements OnChanges, OnDestroy { loc.y = loc.y * aspectRatio; this._viewer.addOverlay({ - element: elt, - location: loc + element: regEle, + location: loc, + }); + + // mouse tracker has to be activated on the open seadragon viewer + // solution from: https://github.com/openseadragon/openseadragon/issues/1419#issuecomment-371564878 + const tracker = new OpenSeadragon.MouseTracker({ + element: regEle, + clickHandler: function (event) { } + }); + + this._regions[regionIri].push(regEle); + + const comEle: HTMLElement = this._renderer.createElement('div'); + comEle.className = 'annotation-tooltip'; + comEle.innerHTML = `${regionLabel}
${regionComment}`; + regEle.append(comEle); + + regEle.addEventListener('mousemove', (event: MouseEvent) => { + comEle.setAttribute('style', 'display: block; left: ' + event.clientX + 'px; top: ' + event.clientY + 'px'); + }); + regEle.addEventListener('mouseleave', (event: MouseEvent) => { + comEle.setAttribute('style', 'display: none'); + }); + regEle.addEventListener('click', (event: MouseEvent) => { + this.regionClicked.emit(regionIri); }); - this._regions[regionIri].push(elt); } + + } diff --git a/src/app/workspace/resource/resource.component.html b/src/app/workspace/resource/resource.component.html index 84fb7c9817..7b8df5d771 100644 --- a/src/app/workspace/resource/resource.component.html +++ b/src/app/workspace/resource/resource.component.html @@ -63,8 +63,14 @@
- +
diff --git a/src/app/workspace/resource/resource.component.ts b/src/app/workspace/resource/resource.component.ts index d7dcdb0c1b..f4ca4e093d 100644 --- a/src/app/workspace/resource/resource.component.ts +++ b/src/app/workspace/resource/resource.component.ts @@ -238,7 +238,7 @@ export class ResourceComponent implements OnInit, OnChanges, OnDestroy { } else { // if there is no incomingResource and the resource has a still image property, assign the iiiUrl to be passed as an input to the still-image component - if (!this.incomingResource && this.resource.res.properties[Constants.HasStillImageFileValue]){ + if (!this.incomingResource && this.resource.res.properties[Constants.HasStillImageFileValue]) { this.iiifUrl = (this.resource.res.properties[Constants.HasStillImageFileValue][0] as ReadStillImageFileValue).fileUrl; } @@ -304,7 +304,7 @@ export class ResourceComponent implements OnInit, OnChanges, OnDestroy { this.incomingResource = res; // if the resource is a still image, assign the iiiUrl to be passed as an input to the still-image component - if (this.incomingResource.res.properties[Constants.HasStillImageFileValue]){ + if (this.incomingResource.res.properties[Constants.HasStillImageFileValue]) { this.iiifUrl = (this.incomingResource.res.properties[Constants.HasStillImageFileValue][0] as ReadStillImageFileValue).fileUrl; } @@ -430,6 +430,13 @@ export class ResourceComponent implements OnInit, OnChanges, OnDestroy { representations.push(stillImage); this.annotationResources = annotations; + + // developer feature: this keeps the annotations tab open, if you add "/annotations" to the end of the URL + // e.g. http://0.0.0.0:4200/resource/[project-shortcode]/[resource-iri]/annotations + if (this.valueUuid === 'annotations') { + this.selectedTab = (this.incomingResource ? 2 : 1); + this.selectedTabLabel = 'annotations'; + } } } else if (resource.res.properties[Constants.HasDocumentFileValue]) { @@ -584,7 +591,10 @@ export class ResourceComponent implements OnInit, OnChanges, OnDestroy { // and scroll to region with this id const region = document.getElementById(iri); if (region) { - region.scrollIntoView(); + region.scrollIntoView({ + behavior: 'smooth', + block: 'center' + }); } } @@ -599,7 +609,7 @@ export class ResourceComponent implements OnInit, OnChanges, OnDestroy { this.openRegion(iri); } - updateRegionColor(){ + updateRegion() { if (this.stillImageComponent !== undefined) { this.stillImageComponent.updateRegions(); } diff --git a/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.html b/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.html index 59bb3f2eb9..74e0e03975 100644 --- a/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.html +++ b/src/app/workspace/resource/values/text-value/text-value-as-string/text-value-as-string.component.html @@ -1,4 +1,5 @@ + {{commentFormControl.value}} diff --git a/src/assets/style/_elements.scss b/src/assets/style/_elements.scss index 31a5d484e2..bc1b19597a 100644 --- a/src/assets/style/_elements.scss +++ b/src/assets/style/_elements.scss @@ -761,6 +761,14 @@ $gc-small: $form-width - $gc-large - 4; font-size: 11px; } +.annotation-tooltip { + + p { + white-space: normal; + text-overflow: ellipsis; + } +} + // -------------------------------------- //