diff --git a/src/content/listing.js b/src/content/listing.js index 989a97d..07f4b76 100644 --- a/src/content/listing.js +++ b/src/content/listing.js @@ -2,12 +2,14 @@ import {createMaintenancePopup} from "$lib/components/MaintenancePopup.js"; import {createMediaBoxTools} from "$lib/components/MediaBoxTools.js"; import {initializeMediaBox} from "$lib/components/MediaBoxWrapper.js"; import {createMaintenanceStatusIcon} from "$lib/components/MaintenanceStatusIcon.js"; +import {createImageShowFullscreenButton} from "$lib/components/ImageShowFullscreenButton.js"; document.querySelectorAll('.media-box').forEach(mediaBoxElement => { initializeMediaBox(mediaBoxElement, [ createMediaBoxTools( createMaintenancePopup(), createMaintenanceStatusIcon(), + createImageShowFullscreenButton(), ) ]); diff --git a/src/lib/components/ImageShowFullscreenButton.js b/src/lib/components/ImageShowFullscreenButton.js new file mode 100644 index 0000000..0e3198e --- /dev/null +++ b/src/lib/components/ImageShowFullscreenButton.js @@ -0,0 +1,75 @@ +import {BaseComponent} from "$lib/components/base/BaseComponent.js"; +import {getComponent} from "$lib/components/base/ComponentUtils.js"; + +export class ImageShowFullscreenButton extends BaseComponent { + /** + * @type {MediaBoxTools} + */ + #mediaBoxTools; + + build() { + this.container.innerText = '🔍'; + ImageShowFullscreenButton.#resolveFullscreenViewer(); + } + + init() { + this.#mediaBoxTools = getComponent(this.container.parentElement); + + if (!this.#mediaBoxTools) { + throw new Error('Fullscreen button is placed outside of the tools container!'); + } + + this.on('click', this.#onButtonClicked.bind(this)); + } + + #onButtonClicked() { + const imageViewer = ImageShowFullscreenButton.#resolveFullscreenViewer(); + let imageElement = imageViewer.querySelector('img') ?? document.createElement('img'); + + imageElement.src = this.#mediaBoxTools.mediaBox.imageLinks.large; + imageViewer.appendChild(imageElement); + + imageViewer.classList.add('shown'); + } + + /** + * @type {HTMLElement|null} + */ + static #fullscreenViewerElement = null; + + /** + * @return {HTMLElement} + */ + static #resolveFullscreenViewer() { + this.#fullscreenViewerElement ??= this.#buildFullscreenViewer(); + return this.#fullscreenViewerElement; + } + + /** + * @return {HTMLElement} + */ + static #buildFullscreenViewer() { + const element = document.createElement('div'); + element.classList.add('fullscreen-viewer'); + + document.body.append(element); + + document.addEventListener('keydown', event => { + // When ESC pressed + if (event.code === 'Escape' || event.code === 'Esc') { + element.classList.remove('shown'); + } + }); + + return element; + } +} + +export function createImageShowFullscreenButton() { + const element = document.createElement('div'); + element.classList.add('media-box-show-fullscreen'); + + new ImageShowFullscreenButton(element); + + return element; +} diff --git a/src/lib/components/MediaBoxWrapper.js b/src/lib/components/MediaBoxWrapper.js index b7b1433..9ed76d0 100644 --- a/src/lib/components/MediaBoxWrapper.js +++ b/src/lib/components/MediaBoxWrapper.js @@ -54,6 +54,13 @@ export class MediaBoxWrapper extends BaseComponent { this.container.dataset.imageId ); } + + /** + * @return {ImageURIs} + */ + get imageLinks() { + return JSON.parse(this.#thumbnailContainer.dataset.uris); + } } /** @@ -70,3 +77,10 @@ export function initializeMediaBox(mediaBoxContainer, childComponentElements) { getComponent(childComponentElement)?.initialize(); } } + +/** + * @typedef {Object} ImageURIs + * @property {string} full + * @property {string} large + * @property {string} small + */ diff --git a/src/styles/content/listing.scss b/src/styles/content/listing.scss index 19f359f..324c71c 100644 --- a/src/styles/content/listing.scss +++ b/src/styles/content/listing.scss @@ -83,6 +83,14 @@ right: 6px; } + .media-box-show-fullscreen { + position: absolute; + bottom: 6px; + left: 6px; + display: none; + cursor: pointer; + } + .media-box-tools:not(.has-active-profile) .maintenance-status-icon { display: none; } @@ -97,5 +105,36 @@ .maintenance-popup.is-active { display: block; } + + .media-box-show-fullscreen { + display: block; + } + } +} + +.fullscreen-viewer { + pointer-events: none; + z-index: 9999; + opacity: 0; + background-color: black; + transition: opacity 0.1s; + position: fixed; + left: 0; + right: 0; + top: 0; + bottom: 0; + display: flex; + justify-content: stretch; + align-items: stretch; + + img { + object-fit: contain; + width: 100%; + height: 100%; + } + + &.shown { + opacity: 1; + pointer-events: initial; } }