diff --git a/src/components/Lightbox.vue b/src/components/Lightbox.vue
new file mode 100644
index 0000000..8495ee8
--- /dev/null
+++ b/src/components/Lightbox.vue
@@ -0,0 +1,585 @@
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
diff --git a/src/components/Project.vue b/src/components/Project.vue
index 74f07f3..200ec65 100644
--- a/src/components/Project.vue
+++ b/src/components/Project.vue
@@ -1,47 +1,19 @@
+
+
+
+
diff --git a/src/components/ProjectPost.vue b/src/components/ProjectPost.vue
index 5129c48..fcc7f60 100644
--- a/src/components/ProjectPost.vue
+++ b/src/components/ProjectPost.vue
@@ -71,11 +71,7 @@
relatedMarker() {
//Find corresponding marker
if(!this.options.longitude && !this.options.latitude && this.options.type == 'media') {
- return this.project.markers.find((marker) => {
- return (marker.medias || []).some((media) => {
- return media.id_media == this.options.id_media;
- });
- }) || null;
+ return this.map.findMarkerByMediaId(this.options.id_media);
}
else if(
['message', 'media'].includes(this.options.type)
diff --git a/src/scripts/common.js b/src/scripts/common.js
index 76d1395..f0181f0 100644
--- a/src/scripts/common.js
+++ b/src/scripts/common.js
@@ -1,5 +1,12 @@
/* Common Functions */
+//All the custom properties this reads (--space, --track-*, --trans-*, --zoom-scale...)
+//are declared on :root (_common.scss), so there's no need to read them from any
+//specific component's own element - document.documentElement always has them.
+export function getStyleProperty(sProperty) {
+ return getComputedStyle(document.documentElement).getPropertyValue(sProperty).trim();
+}
+
export function copyTextToClipboard(text) {
if(!navigator.clipboard) {
let textArea = document.createElement('textarea');
diff --git a/src/scripts/lightbox.js b/src/scripts/lightbox.js
deleted file mode 100644
index be3da6c..0000000
--- a/src/scripts/lightbox.js
+++ /dev/null
@@ -1,650 +0,0 @@
-import { icon } from '@fortawesome/fontawesome-svg-core';
-import { getIcon } from '@scripts/icons';
-
-export default class Lightbox {
- constructor(options = {}) {
- this.album = [];
- this.currentImageIndex = 0;
- this.options = {
- alwaysShowNavOnTouchDevices: false,
- fadeDuration: 600,
- imageFadeDuration: 600,
- positionFromTop: 50,
- resizeDuration: 700,
- wrapAround: false,
- disableScrolling: false,
- sanitizeTitle: false,
- hasVideo: true,
- onMediaChange: () => {},
- onClosing: () => {}
- };
- this.option(options);
- this.gMouseDownOffsetX = 0;
- this.gMouseDownOffsetY = 0;
- this.resizeTimer = null;
- this.boundOnBodyClick = this.onBodyClick.bind(this);
- this.boundOnResize = this.sizeOverlay.bind(this);
- this.boundOnKeyUp = this.keyboardAction.bind(this);
- this.boundOnWheel = this.onWheel.bind(this);
- this.boundOnDragStart = this.onDragStart.bind(this);
- this.boundOnDragMove = this.onDragMove.bind(this);
- this.boundOnDragEnd = this.onDragEnd.bind(this);
- this.init();
- }
-
- option(options = {}) {
- Object.assign(this.options, options);
- }
-
- init() {
- if(document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', () => {
- this.build();
- this.enable();
- }, { once: true });
- } else {
- this.build();
- this.enable();
- }
- }
-
- enable() {
- document.body.addEventListener('click', this.boundOnBodyClick);
- }
-
- disable() {
- document.body.removeEventListener('click', this.boundOnBodyClick);
- }
-
- onBodyClick(event) {
- const link = event.target.closest('a[data-lightbox], area[data-lightbox]');
- if(!link) return;
- event.preventDefault();
- this.start(link);
- }
-
- renderIcon(name, sClass=null) {
- return icon(getIcon(name), {classes: ['app-icon', name, sClass]}).html;
- }
-
- build() {
- if(!document.getElementById('lightbox')) {
- const wrapper = document.createElement('div');
- wrapper.innerHTML = `
-
-
-
-
-

-
-
-
-
-
-
-
- `;
- document.body.append(...wrapper.children);
- }
-
- this.overlay = document.getElementById('lightboxOverlay');
- this.lightbox = document.getElementById('lightbox');
- this.outerContainer = this.lightbox.querySelector('.lb-outerContainer');
- this.container = this.lightbox.querySelector('.lb-container');
- this.image = this.lightbox.querySelector('.lb-image');
- this.nav = this.lightbox.querySelector('.lb-nav');
- this.loader = this.lightbox.querySelector('.lb-loader');
- this.caption = this.lightbox.querySelector('.lb-caption');
- this.closeButton = this.lightbox.querySelector('.lb-close');
- this.dataContainer = this.lightbox.querySelector('.lb-dataContainer');
- this.prev = this.lightbox.querySelector('.lb-prev');
- this.next = this.lightbox.querySelector('.lb-next');
- this.video = this.lightbox.querySelector('.lb-video');
-
- this.setVisible(this.overlay, false);
- this.setVisible(this.lightbox, false);
-
- this.containerPadding = this.getBoxMetrics(this.container, 'padding');
- this.imageBorderWidth = this.getBoxMetrics(this.image, 'border');
- this.videoBorderWidth = this.getBoxMetrics(this.video, 'border');
-
- this.overlay.addEventListener('click', () => this.end());
- this.dataContainer.addEventListener('click', () => this.end());
- this.lightbox.addEventListener('click', (event) => {
- if(event.target === this.lightbox) this.end();
- });
- this.outerContainer.addEventListener('click', (event) => {
- if(event.target === this.outerContainer) this.end();
- event.stopPropagation();
- });
-
- this.prev.addEventListener('click', (event) => {
- event.preventDefault();
- if(this.currentImageIndex === 0) this.changeImage(this.album.length - 1);
- else this.changeImage(this.currentImageIndex - 1);
- });
- this.next.addEventListener('click', (event) => {
- event.preventDefault();
- if(this.currentImageIndex === this.album.length - 1) this.changeImage(0);
- else this.changeImage(this.currentImageIndex + 1);
- });
-
- this.loader.addEventListener('click', (event) => {
- event.preventDefault();
- this.end();
- });
- this.closeButton.addEventListener('click', (event) => {
- event.preventDefault();
- event.stopPropagation();
- this.end();
- });
- this.closeButton.addEventListener('keyup', (event) => {
- if(event.key === 'Enter' || event.key === ' ') this.end();
- });
-
- this.nav.addEventListener('wheel', this.boundOnWheel, { passive: false });
- this.nav.addEventListener('mousedown', this.boundOnDragStart);
- window.addEventListener('mouseup', this.boundOnDragEnd);
- }
-
- getBoxMetrics(element, type) {
- const styles = getComputedStyle(element);
- return {
- top: parseInt(styles[`${type}-top-width`], 10) || 0,
- right: parseInt(styles[`${type}-right-width`], 10) || 0,
- bottom: parseInt(styles[`${type}-bottom-width`], 10) || 0,
- left: parseInt(styles[`${type}-left-width`], 10) || 0
- };
- }
-
- start(link) {
- this.sizeOverlay();
- this.album = [];
- let imageNumber = 0;
- const setName = link.getAttribute('data-lightbox');
-
- const links = [...document.querySelectorAll(`${link.tagName}[data-lightbox="${CSS.escape(setName)}"]`)];
- links.forEach((item, index) => {
- this.addToAlbum(item);
- if(item === link) imageNumber = index;
- });
-
- this.fade(this.overlay, true, this.options.fadeDuration);
- this.fade(this.lightbox, true, this.options.fadeDuration);
-
- if(this.options.disableScrolling) document.body.classList.add('lb-disable-scrolling');
-
- window.addEventListener('resize', this.boundOnResize);
- this.changeImage(imageNumber);
- }
-
- addToAlbum(link) {
- const img = link.querySelector('img');
- this.album.push({
- alt: link.getAttribute('data-alt') || '',
- link: link.getAttribute('href'),
- title: link.getAttribute('data-title') || link.getAttribute('title') || '',
- orientation: parseInt(link.getAttribute('data-orientation') || '0', 10),
- type: link.getAttribute('data-type') || 'image',
- id: link.getAttribute('data-id'),
- width: parseInt(img?.getAttribute('width') || '0', 10),
- height: parseInt(img?.getAttribute('height') || '0', 10),
- set: link.getAttribute('data-lightbox') || ''
- });
- }
-
- hasMediaAfterCurrent() {
- return this.currentImageIndex < this.album.length - 1;
- }
-
- refreshAlbum() {
- const current = this.album[this.currentImageIndex];
- if(!current?.set) return;
-
- const links = [...document.querySelectorAll(`a[data-lightbox="${CSS.escape(current.set)}"], area[data-lightbox="${CSS.escape(current.set)}"]`)];
- if(!links.length) return;
-
- const existingKeys = new Set(this.album.map((media) => this.getMediaKey(media)));
- links.forEach((link) => {
- const key = this.getLinkMediaKey(link);
- if(existingKeys.has(key)) return;
-
- this.addToAlbum(link);
- existingKeys.add(key);
- });
-
- this.updateNav();
- }
-
- getMediaKey(media) {
- return `${media.set}:${media.id}`;
- }
-
- getLinkMediaKey(link) {
- return `${link.getAttribute('data-lightbox') || ''}:${link.getAttribute('data-id')}`;
- }
-
- getMaxSizes(mediaType) {
- let maxWidth = window.innerWidth - this.containerPadding.left - this.containerPadding.right;
- let maxHeight = window.innerHeight - this.containerPadding.top - this.containerPadding.bottom - this.options.positionFromTop;
- const border = mediaType === 'image' ? this.imageBorderWidth : this.videoBorderWidth;
- maxWidth -= border.left + border.right;
- maxHeight -= border.top + border.bottom;
- maxHeight -= this.getDataContainerHeight(maxWidth + this.containerPadding.left + this.containerPadding.right + border.left + border.right);
-
- return {
- maxWidth: Math.max(maxWidth, 1),
- maxHeight: Math.max(maxHeight, 1)
- };
- }
-
- getDataContainerHeight(width = null) {
- if(!this.dataContainer) return 0;
-
- const currentWidth = this.dataContainer.style.width;
- if(width !== null) this.dataContainer.style.width = `${width}px`;
- const height = Math.ceil(this.dataContainer.getBoundingClientRect().height || this.dataContainer.offsetHeight || 0);
- this.dataContainer.style.width = currentWidth;
-
- return height;
- }
-
- getMediaSize(media, maxWidth, maxHeight) {
- if(media.width <= maxWidth && media.height <= maxHeight) {
- return {
- width: media.width,
- height: media.height
- };
- }
-
- const widthRatio = media.width / maxWidth;
- const heightRatio = media.height / maxHeight;
-
- if(widthRatio > heightRatio) {
- return {
- width: maxWidth,
- height: Math.round(media.height / widthRatio)
- };
- }
-
- return {
- width: Math.round(media.width / heightRatio),
- height: maxHeight
- };
- }
-
- fitSizeWithDataContainer(size, mediaType) {
- const border = mediaType === 'image' ? this.imageBorderWidth : this.videoBorderWidth;
- const maxOuterHeight = Math.max(window.innerHeight - this.options.positionFromTop, 1);
- let fittedSize = size;
-
- for(let i = 0; i < 5; i++) {
- const containerWidth = fittedSize.width + this.containerPadding.left + this.containerPadding.right + border.left + border.right;
- const containerHeight = fittedSize.height + this.containerPadding.top + this.containerPadding.bottom + border.top + border.bottom;
- const dataHeight = this.getDataContainerHeight(containerWidth);
- const overflow = Math.ceil(containerHeight + dataHeight - maxOuterHeight);
- if(overflow <= 0 || fittedSize.height <= 1) break;
-
- const height = Math.max(fittedSize.height - overflow, 1);
- fittedSize = {
- width: Math.max(Math.round(fittedSize.width * (height / fittedSize.height)), 1),
- height
- };
- }
-
- return fittedSize;
- }
-
- updateSize(index) {
- const media = this.album[index];
- const maxSizes = this.getMaxSizes(media.type);
- const maxWidth = this.options.maxWidth ? Math.min(this.options.maxWidth, maxSizes.maxWidth) : maxSizes.maxWidth;
- const maxHeight = this.options.maxHeight ? Math.min(this.options.maxHeight, maxSizes.maxHeight) : maxSizes.maxHeight;
- const size = this.fitSizeWithDataContainer(this.getMediaSize(media, maxWidth, maxHeight), media.type);
-
- const target = media.type === 'video' ? this.video : this.image;
- target.width = size.width;
- target.height = size.height;
- this.sizeContainer(size.width, size.height, media.type);
- }
-
- changeImage(index) {
- const media = this.album[index];
- if(!media) return;
-
- this.updateDetails(media, false);
- this.hideElements([this.dataContainer]);
- this.disableKeyboardNav();
- this.fade(this.overlay, true, this.options.fadeDuration);
- this.fade(this.loader, true, 200);
- this.hideElements([this.image, this.video, this.nav, this.prev, this.next]);
- this.resetImageTransform();
- this.outerContainer.classList.add('animating');
- this.container.classList.remove('moveable', 'moving', 'lb-video-nav');
- this.currentImageIndex = index;
-
- this.options.onMediaChange(media);
-
- if(media.type === 'video') {
- this.image.removeAttribute('src');
- this.container.classList.add('lb-video-nav');
- this.video.onloadedmetadata = () => {
- media.width = this.video.videoWidth;
- media.height = this.video.videoHeight;
- this.video.onloadedmetadata = null;
- this.updateSize(index);
- };
- this.video.src = media.link;
- } else {
- this.video.pause();
- this.video.removeAttribute('src');
- this.image.onload = () => {
- this.image.alt = media.alt;
- let width = this.image.naturalWidth;
- let height = this.image.naturalHeight;
- if(Math.abs(media.orientation) === 90 && width > height) {
- const tmp = width;
- width = height;
- height = tmp;
- }
- media.width = width;
- media.height = height;
- this.image.onload = null;
- this.updateSize(index);
- };
- this.image.src = media.link;
- }
- }
-
- sizeOverlay() {
- if(this.resizeTimer) clearTimeout(this.resizeTimer);
- if(!this.album.length) return;
-
- this.resizeTimer = window.setTimeout(() => {
- const current = this.album[this.currentImageIndex];
- if(!current) return;
- if(current.type === 'image') this.changeImage(this.currentImageIndex);
- else this.updateSize(this.currentImageIndex);
- }, 200);
- }
-
- sizeContainer(width, height, mediaType = 'image') {
- const border = mediaType === 'image' ? this.imageBorderWidth : this.videoBorderWidth;
- const newWidth = width + this.containerPadding.left + this.containerPadding.right + border.left + border.right;
- const newHeight = height + this.containerPadding.top + this.containerPadding.bottom + border.top + border.bottom;
- const dataHeight = this.getDataContainerHeight(newWidth);
-
- this.outerContainer.style.transition = `width ${this.options.resizeDuration}ms, height ${this.options.resizeDuration}ms`;
- this.outerContainer.style.width = `${newWidth}px`;
- this.outerContainer.style.height = `${newHeight + dataHeight}px`;
- this.container.style.height = `${newHeight}px`;
-
- window.setTimeout(() => {
- this.overlay.focus();
- this.showImage();
- this.outerContainer.style.transition = '';
- }, this.options.resizeDuration);
- }
-
- showImage() {
- this.fade(this.loader, false, 0);
- if(this.options.hasVideo && this.album[this.currentImageIndex].type === 'video') this.fade(this.video, true, this.options.imageFadeDuration);
- else this.fade(this.image, true, this.options.imageFadeDuration);
-
- this.updateNav();
- this.updateDetails();
- this.preloadNeighboringImages();
- this.enableKeyboardNav();
- }
-
- updateNav() {
- this.setVisible(this.nav, true);
- this.setVisible(this.prev, false);
- this.setVisible(this.next, false);
-
- const alwaysShowNav = ('ontouchstart' in window) && this.options.alwaysShowNavOnTouchDevices;
- if(this.album.length <= 1) return;
-
- if(this.options.wrapAround) {
- this.setVisible(this.prev, true);
- this.setVisible(this.next, true);
- } else {
- if(this.currentImageIndex > 0) this.setVisible(this.prev, true);
- if(this.currentImageIndex < this.album.length - 1) this.setVisible(this.next, true);
- }
-
- if(alwaysShowNav) {
- this.prev.style.opacity = '1';
- this.next.style.opacity = '1';
- } else {
- this.prev.style.opacity = '';
- this.next.style.opacity = '';
- }
- }
-
- updateDetails(media = this.album[this.currentImageIndex], show = true) {
- if(!media) return;
-
- if(media.title) {
- if(this.options.sanitizeTitle) this.caption.textContent = media.title;
- else this.caption.innerHTML = media.title;
- if(show) this.fade(this.caption, true, 200);
- else this.setVisible(this.caption, true);
- } else {
- this.caption.textContent = '';
- this.setVisible(this.caption, false);
- }
-
- if(show) {
- this.fade(this.closeButton, true, 200);
- this.outerContainer.classList.remove('animating');
- this.fade(this.dataContainer, true, this.options.resizeDuration);
- } else {
- this.setVisible(this.closeButton, true);
- this.setVisible(this.dataContainer, false);
- this.dataContainer.style.transition = '';
- this.dataContainer.style.opacity = '0';
- }
- }
-
- preloadNeighboringImages() {
- const next = this.album[this.currentImageIndex + 1];
- const prev = this.album[this.currentImageIndex - 1];
- if(next && next.type === 'image') {
- const preloadNext = new Image();
- preloadNext.src = next.link;
- }
- if(prev && prev.type === 'image') {
- const preloadPrev = new Image();
- preloadPrev.src = prev.link;
- }
- }
-
- enableKeyboardNav() {
- this.disableKeyboardNav();
- this.lightbox.addEventListener('keyup', this.boundOnKeyUp);
- this.overlay.addEventListener('keyup', this.boundOnKeyUp);
- }
-
- disableKeyboardNav() {
- this.lightbox?.removeEventListener('keyup', this.boundOnKeyUp);
- this.overlay?.removeEventListener('keyup', this.boundOnKeyUp);
- }
-
- keyboardAction(event) {
- switch(event.key) {
- case 'Escape':
- event.stopPropagation();
- this.end();
- break;
- case 'ArrowLeft':
- if(this.currentImageIndex !== 0) this.changeImage(this.currentImageIndex - 1);
- else if(this.options.wrapAround && this.album.length > 1) this.changeImage(this.album.length - 1);
- break;
- case 'ArrowRight':
- if(this.currentImageIndex !== this.album.length - 1) this.changeImage(this.currentImageIndex + 1);
- else if(this.options.wrapAround && this.album.length > 1) this.changeImage(0);
- break;
- }
- }
-
- onWheel(event) {
- const media = this.album[this.currentImageIndex];
- if(!media || media.type === 'video') return;
- event.preventDefault();
-
- const rect = this.image.getBoundingClientRect();
- const oldTransform = this.getImageTransform();
- const oldZoom = oldTransform.scale;
- const maxZoom = Math.max(media.width / Math.max(this.image.width, 1), media.height / Math.max(this.image.height, 1), 1);
- const newZoom = Math.min(Math.max(oldZoom + (-Math.sign(event.deltaY) / 10), 1), maxZoom);
-
- const imageCenterX = rect.left + rect.width / 2 - oldTransform.translateX;
- const imageCenterY = rect.top + rect.height / 2 - oldTransform.translateY;
- const cursorX = event.clientX - imageCenterX;
- const cursorY = event.clientY - imageCenterY;
- const zoomRatio = newZoom / oldZoom;
- const transform = this.clampImageTransform({
- scale: newZoom,
- translateX: cursorX - zoomRatio * (cursorX - oldTransform.translateX),
- translateY: cursorY - zoomRatio * (cursorY - oldTransform.translateY)
- });
-
- this.container.classList.toggle('moveable', newZoom > 1);
- this.setImageTransform(transform);
- }
-
- onDragStart(event) {
- const scale = parseFloat(this.image.style.getPropertyValue('--scale') || '1');
- if(scale <= 1) return;
-
- this.gMouseDownOffsetX = event.clientX - parseFloat(this.image.style.getPropertyValue('--translate-x') || '0');
- this.gMouseDownOffsetY = event.clientY - parseFloat(this.image.style.getPropertyValue('--translate-y') || '0');
- this.container.classList.add('moving');
- window.addEventListener('mousemove', this.boundOnDragMove);
- }
-
- onDragMove(event) {
- const zoom = parseFloat(this.image.style.getPropertyValue('--scale') || '1');
- const transform = this.clampImageTransform({
- scale: zoom,
- translateX: event.clientX - this.gMouseDownOffsetX,
- translateY: event.clientY - this.gMouseDownOffsetY
- });
-
- this.setImageTransform(transform);
- }
-
- onDragEnd() {
- window.removeEventListener('mousemove', this.boundOnDragMove);
- this.container?.classList.remove('moving');
- }
-
- resetImageTransform() {
- this.setImageTransform({scale: 1, translateX: 0, translateY: 0});
- }
-
- getImageTransform() {
- return {
- scale: parseFloat(this.image.style.getPropertyValue('--scale') || '1'),
- translateX: parseFloat(this.image.style.getPropertyValue('--translate-x') || '0'),
- translateY: parseFloat(this.image.style.getPropertyValue('--translate-y') || '0')
- };
- }
-
- clampImageTransform(transform) {
- const maxTranslateX = (transform.scale - 1) * this.image.width / 2;
- const maxTranslateY = (transform.scale - 1) * this.image.height / 2;
-
- return {
- scale: transform.scale,
- translateX: Math.max(Math.min(transform.translateX, maxTranslateX), -maxTranslateX),
- translateY: Math.max(Math.min(transform.translateY, maxTranslateY), -maxTranslateY)
- };
- }
-
- setImageTransform(transform) {
- if(!this.image) return;
- this.image.style.setProperty('--scale', String(transform.scale));
- this.image.style.setProperty('--translate-x', `${transform.translateX}px`);
- this.image.style.setProperty('--translate-y', `${transform.translateY}px`);
- }
-
- hideElements(elements) {
- elements.forEach((element) => {
- this.setVisible(element, false);
- });
- }
-
- setVisible(element, visible) {
- if(!element) return;
- element.style.visibility = visible ? 'visible' : 'hidden';
- element.style.pointerEvents = visible ? '' : 'none';
- }
-
- fade(element, show, duration, done) {
- if(!element) return;
-
- const safeDuration = duration || 0;
- element.style.transition = `opacity ${safeDuration}ms`;
- if(show) {
- this.setVisible(element, true);
- requestAnimationFrame(() => {
- element.style.opacity = element === this.overlay ? '0.8' : '1';
- });
- } else {
- element.style.opacity = '0';
- element.style.pointerEvents = 'none';
- window.setTimeout(() => {
- this.setVisible(element, false);
- }, safeDuration);
- }
-
- if(typeof done === 'function') {
- window.setTimeout(done, safeDuration);
- }
- }
-
- end(dispose = false) {
- this.disableKeyboardNav();
- this.video?.pause();
- this.video?.removeAttribute('src');
- this.container?.classList.remove('lb-video-nav', 'moveable', 'moving');
- window.removeEventListener('resize', this.boundOnResize);
- window.removeEventListener('mousemove', this.boundOnDragMove);
-
- if(dispose) {
- this.disable();
- if(this.resizeTimer) clearTimeout(this.resizeTimer);
- window.removeEventListener('mouseup', this.boundOnDragEnd);
- this.lightbox?.remove();
- this.overlay?.remove();
- this.album = [];
- }
- else {
- this.fade(this.lightbox, false, this.options.fadeDuration);
- this.fade(this.overlay, false, this.options.fadeDuration);
- this.options.onClosing();
- }
-
- if(this.options.disableScrolling) document.body.classList.remove('lb-disable-scrolling');
- }
-}