diff --git a/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts b/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts index 2862cb59c9..e490683a5e 100644 --- a/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts +++ b/apps/datahub-e2e/src/e2e/datasetDetailPage.cy.ts @@ -103,6 +103,20 @@ describe('dataset pages', () => { expect(text).not.to.equal('') }) }) + it.only('should display the thumbnail image and magnify', () => { + cy.get('datahub-record-metadata') + .find('[id="about"]') + .find('gn-ui-image-overlay-preview') + .as('overlay') + .should('have.length', 1) + cy.get('@overlay').find('gn-ui-button').click() + cy.get('[class="basicLightbox__placeholder"]') + .as('lightbox') + .find('img') + .should('have.length', 1) + cy.get('body').click() + cy.get('@lightbox').should('have.length', 0) + }) it('should display the contact details', () => { cy.get('datahub-record-metadata') .find('[id="about"]') diff --git a/apps/datahub/project.json b/apps/datahub/project.json index aa1c97bd2d..7d3ed4e6a4 100644 --- a/apps/datahub/project.json +++ b/apps/datahub/project.json @@ -31,6 +31,7 @@ "tailwind.base.css", "apps/datahub/src/styles.css", "node_modules/tippy.js/dist/tippy.css", + "node_modules/basiclightbox/dist/basicLightbox.min.css", "node_modules/ol/ol.css", "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css" ], diff --git a/apps/datahub/src/app/record/record-metadata/record-metadata.component.html b/apps/datahub/src/app/record/record-metadata/record-metadata.component.html index c07d56fccc..c4e7a0fcb1 100644 --- a/apps/datahub/src/app/record/record-metadata/record-metadata.component.html +++ b/apps/datahub/src/app/record/record-metadata/record-metadata.component.html @@ -33,13 +33,21 @@ [metadataQualityDisplay]="metadataQualityDisplay" > - - - - + +
+ + + + +
diff --git a/apps/datahub/src/app/record/record-metadata/record-metadata.component.spec.ts b/apps/datahub/src/app/record/record-metadata/record-metadata.component.spec.ts index 4e449bfc01..35243aa0ae 100644 --- a/apps/datahub/src/app/record/record-metadata/record-metadata.component.spec.ts +++ b/apps/datahub/src/app/record/record-metadata/record-metadata.component.spec.ts @@ -148,6 +148,15 @@ export class MockMetadataCatalogComponent { export class MockRecordApiFormComponent { @Input() apiLink: DatasetServiceDistribution } +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'gn-ui-image-overlay-preview', + template: '
', +}) +export class MockImgOverlayPreviewComponent { + @Input() imageUrl: string + @Output() isPlaceholderShown = new EventEmitter() +} describe('RecordMetadataComponent', () => { let component: RecordMetadataComponent @@ -172,6 +181,7 @@ describe('RecordMetadataComponent', () => { MockMetadataCatalogComponent, MockMetadataContactComponent, MockRecordApiFormComponent, + MockImgOverlayPreviewComponent, ], schemas: [NO_ERRORS_SCHEMA], imports: [TranslateModule.forRoot()], @@ -267,6 +277,56 @@ describe('RecordMetadataComponent', () => { fixture.debugElement.query(By.directive(MockMetadataCatalogComponent)) ).toBeFalsy() }) + it('does not display the image overlay preview', () => { + expect( + fixture.debugElement.query( + By.directive(MockImgOverlayPreviewComponent) + ) + ).toBeFalsy() + }) + }) + describe('Image Overlay Preview', () => { + describe('if metadata without overview', () => { + let imgOverlayPreview: MockImgOverlayPreviewComponent + beforeEach(() => { + facade.isPresent$.next(true) + facade.metadata$.next({}) + fixture.detectChanges() + imgOverlayPreview = fixture.debugElement.query( + By.directive(MockImgOverlayPreviewComponent) + ).componentInstance + }) + it('should send undefined as imageUrl to imgOverlayPreview component', () => { + expect(imgOverlayPreview).toBeTruthy() + expect(imgOverlayPreview.imageUrl).toBe(undefined) + }) + }) + describe('if metadata with overview', () => { + let imgOverlayPreview: MockImgOverlayPreviewComponent + beforeEach(() => { + facade.isPresent$.next(true) + fixture.detectChanges() + imgOverlayPreview = fixture.debugElement.query( + By.directive(MockImgOverlayPreviewComponent) + ).componentInstance + }) + describe('and url defined', () => { + it('should send the imageUrl to imgOverlayPreview component', () => { + expect(imgOverlayPreview).toBeTruthy() + expect(imgOverlayPreview.imageUrl).toBeDefined() + }) + }) + describe('and url undefined', () => { + beforeEach(() => { + facade.metadata$.next({ overviews: [] }) + fixture.detectChanges() + }) + it('should send the imagUrl as null to imgOverlayPreview component', () => { + expect(imgOverlayPreview).toBeTruthy() + expect(imgOverlayPreview.imageUrl).toBeNull() + }) + }) + }) }) }) diff --git a/apps/datahub/src/app/record/record-metadata/record-metadata.component.ts b/apps/datahub/src/app/record/record-metadata/record-metadata.component.ts index ac9d0aeabe..58d718ee5d 100644 --- a/apps/datahub/src/app/record/record-metadata/record-metadata.component.ts +++ b/apps/datahub/src/app/record/record-metadata/record-metadata.component.ts @@ -55,6 +55,21 @@ export class RecordMetadataComponent { errorTypes = ErrorType selectedTabIndex$ = new BehaviorSubject(0) + thumbnailUrl$ = this.facade.metadata$.pipe( + map((metadata) => { + // in order to differentiate between metadata not loaded yet + // and url not defined + // the content-ghost of image-overlay-preview relies on this differentiation + if (metadata?.overviews === undefined) { + return undefined + } else { + return metadata?.overviews?.[0]?.url ?? null + } + }) + ) + + showOverlay = true + constructor( public facade: MdViewFacade, private searchService: SearchService, diff --git a/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.ts b/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.ts index a324fe1742..24b67007df 100644 --- a/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.ts +++ b/libs/api/metadata-converter/src/lib/gn4/gn4.metadata.mapper.ts @@ -34,6 +34,7 @@ export class Gn4MetadataMapper extends MetadataBaseMapper { useLimitations: [], spatialExtents: [], temporalExtents: [], + overviews: [], } const record: CatalogRecord = Object.keys(_source).reduce( (prev, fieldName) => diff --git a/libs/ui/elements/src/index.ts b/libs/ui/elements/src/index.ts index ed3b7f3ad5..a0ee32142f 100644 --- a/libs/ui/elements/src/index.ts +++ b/libs/ui/elements/src/index.ts @@ -20,3 +20,4 @@ export * from './lib/search-results-error/search-results-error.component' export * from './lib/user-preview/user-preview.component' export * from './lib/record-api-form/record-api-form.component' export * from './lib/markdown-parser/markdown-parser.component' +export * from './lib/image-overlay-preview/image-overlay-preview.component' diff --git a/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css b/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html b/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html new file mode 100644 index 0000000000..024d2bdbfa --- /dev/null +++ b/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.html @@ -0,0 +1,29 @@ + +
+ +
+ + zoom_out_map + +
+
+
diff --git a/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.spec.ts b/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.spec.ts new file mode 100644 index 0000000000..2e4c996979 --- /dev/null +++ b/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing' + +import { ImageOverlayPreviewComponent } from './image-overlay-preview.component' + +describe('ImageOverlayPreviewComponent', () => { + let component: ImageOverlayPreviewComponent + let fixture: ComponentFixture + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [ImageOverlayPreviewComponent], + }) + fixture = TestBed.createComponent(ImageOverlayPreviewComponent) + component = fixture.componentInstance + fixture.detectChanges() + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) +}) diff --git a/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts b/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts new file mode 100644 index 0000000000..c7f24db928 --- /dev/null +++ b/libs/ui/elements/src/lib/image-overlay-preview/image-overlay-preview.component.ts @@ -0,0 +1,15 @@ +import { Component, EventEmitter, Input, Output } from '@angular/core' +import * as basicLightbox from 'basiclightbox' + +@Component({ + selector: 'gn-ui-image-overlay-preview', + templateUrl: './image-overlay-preview.component.html', + styleUrls: ['./image-overlay-preview.component.css'], +}) +export class ImageOverlayPreviewComponent { + @Input() imageUrl: string + @Output() isPlaceholderShown = new EventEmitter() + openLightbox(src: string) { + basicLightbox.create(``).show() + } +} diff --git a/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.html b/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.html index 6670ab21ec..804dfe6a5e 100644 --- a/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.html +++ b/libs/ui/elements/src/lib/metadata-contact/metadata-contact.component.html @@ -20,7 +20,7 @@ (click)="onOrganizationClick()" data-cy="organization-name" > - {{ shownOrganization.name }} + {{ shownOrganization?.name }}
diff --git a/libs/ui/elements/src/lib/related-record-card/related-record-card.component.html b/libs/ui/elements/src/lib/related-record-card/related-record-card.component.html index b6fb95eba8..932c405b23 100644 --- a/libs/ui/elements/src/lib/related-record-card/related-record-card.component.html +++ b/libs/ui/elements/src/lib/related-record-card/related-record-card.component.html @@ -6,7 +6,7 @@
diff --git a/libs/ui/elements/src/lib/thumbnail/thumbnail.component.ts b/libs/ui/elements/src/lib/thumbnail/thumbnail.component.ts index be4e85eaf5..239dc4ca52 100644 --- a/libs/ui/elements/src/lib/thumbnail/thumbnail.component.ts +++ b/libs/ui/elements/src/lib/thumbnail/thumbnail.component.ts @@ -10,6 +10,8 @@ import { Optional, SimpleChanges, ViewChild, + Output, + EventEmitter, } from '@angular/core' export const THUMBNAIL_PLACEHOLDER = new InjectionToken( @@ -36,6 +38,7 @@ export class ThumbnailComponent implements OnInit, OnChanges { @Input() fit: FitOptions | FitOptions[] = 'cover' @ViewChild('imageElement') imgElement: ElementRef @ViewChild('containerElement') containerElement: ElementRef + @Output() placeholderShown = new EventEmitter() imgUrl: string imgFit: FitOptions placeholderUrl = this.optionalPlaceholderUrl || DEFAULT_PLACEHOLDER @@ -85,6 +88,7 @@ export class ThumbnailComponent implements OnInit, OnChanges { private setNewSrcImage(image: ThumbnailImageObject) { this.imgFit = image.fit this.imgUrl = image.url + this.placeholderShown.emit(this.isPlaceholder) } private setPlaceholder(): void { diff --git a/libs/ui/elements/src/lib/ui-elements.module.ts b/libs/ui/elements/src/lib/ui-elements.module.ts index eac4060e3b..0fd0093efa 100644 --- a/libs/ui/elements/src/lib/ui-elements.module.ts +++ b/libs/ui/elements/src/lib/ui-elements.module.ts @@ -30,6 +30,7 @@ import { PaginationButtonsComponent } from './pagination-buttons/pagination-butt import { MaxLinesComponent } from './max-lines/max-lines.component' import { RecordApiFormComponent } from './record-api-form/record-api-form.component' import { MarkdownParserComponent } from './markdown-parser/markdown-parser.component' +import { ImageOverlayPreviewComponent } from './image-overlay-preview/image-overlay-preview.component' @NgModule({ imports: [ @@ -67,6 +68,7 @@ import { MarkdownParserComponent } from './markdown-parser/markdown-parser.compo MaxLinesComponent, RecordApiFormComponent, MarkdownParserComponent, + ImageOverlayPreviewComponent, ], exports: [ MetadataInfoComponent, @@ -88,6 +90,7 @@ import { MarkdownParserComponent } from './markdown-parser/markdown-parser.compo PaginationButtonsComponent, RecordApiFormComponent, MarkdownParserComponent, + ImageOverlayPreviewComponent, ], }) export class UiElementsModule {} diff --git a/package-lock.json b/package-lock.json index e1d431a51d..065a5cf555 100644 --- a/package-lock.json +++ b/package-lock.json @@ -40,6 +40,7 @@ "@rgrove/parse-xml": "~4.0.1", "alasql": "^3.1.0", "axios": "^1.6.0", + "basiclightbox": "^5.0.4", "chart.js": "^4.2.0", "chroma-js": "^2.1.2", "cypress-browser-permissions": "^1.1.0", @@ -14708,6 +14709,11 @@ "node": ">= 0.8" } }, + "node_modules/basiclightbox": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/basiclightbox/-/basiclightbox-5.0.4.tgz", + "integrity": "sha512-EsuNWmfcFXWZOe0txKXsllYOC7bDpoaVLc4HHHlYKB/roymlZs+FBdLUU6rx2yPpnJZhulwheKdPjqr2k0+NGQ==" + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", diff --git a/package.json b/package.json index 7c3bde2dd8..2287327f1f 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "@rgrove/parse-xml": "~4.0.1", "alasql": "^3.1.0", "axios": "^1.6.0", + "basiclightbox": "^5.0.4", "chart.js": "^4.2.0", "chroma-js": "^2.1.2", "cypress-browser-permissions": "^1.1.0",