From e4ba426ed9ed11d58fd8de167d0bc492b03b5014 Mon Sep 17 00:00:00 2001 From: Serhii Myshko Date: Mon, 29 Jul 2024 16:34:52 +0300 Subject: [PATCH] using the in-house developed Leaflet_FullScreen_Button plugin for the corresponding functionality --- js/fullScreen.js | 251 ++++++++++++++++++++++++++++++++++------------- js/map.js | 20 ++-- js/styles.js | 39 ++++++++ 3 files changed, 227 insertions(+), 83 deletions(-) diff --git a/js/fullScreen.js b/js/fullScreen.js index 41b4935..fdad66e 100644 --- a/js/fullScreen.js +++ b/js/fullScreen.js @@ -1,87 +1,198 @@ /* Copyright (c) 2023-2024 Serhii I. Myshko https://github.com/sergeiown/Map_with_Marker_Clusters/blob/main/LICENSE */ -export function toggleFullScreen(element) { - function handleFullScreenRequest(promise) { - promise - .then(() => { - updateFullScreenButton(true); - }) - .catch((err) => { - console.error(`Error attempting to enable full-screen mode: ${err.message} (${err.name})`); - }); +class FullScreenButton extends L.Control { + options = { + position: 'topleft', + title: 'Toggle fullscreen', + enterFullScreenIcon: null, + exitFullScreenIcon: null, + enterFullScreenTitle: 'Enter fullscreen mode', + exitFullScreenTitle: 'Exit fullscreen mode', + onFullScreenChange: null, + showNotification: true, + }; + + onAdd(map) { + const container = L.DomUtil.create('div', 'leaflet-control-fullscreen leaflet-bar leaflet-control'); + container.title = this.options.title; + + this._updateIcon(container, false); + + container.onclick = () => { + this.toggleFullScreen(map); + }; + + this._eventHandlers = { + fullscreenchange: this._throttle(() => this._handleFullScreenChange(container, map), 100), + keydown: this._preventF11Default.bind(this), + }; + + this._addEventListeners(); + + return container; + } + + onRemove() { + this._removeEventListeners(); } - function handleFullScreenExit(promise) { - promise - .then(() => { - updateFullScreenButton(false); - }) - .catch((err) => { - console.error(`Error attempting to disable full-screen mode: ${err.message} (${err.name})`); + async toggleFullScreen(map) { + const mapContainer = map.getContainer(); + const isFullScreen = this._isFullScreen(mapContainer); + + try { + await this._toggleFullScreenElement(mapContainer, !isFullScreen); + this._updateIcon(this._container, !isFullScreen); + this._handleFullScreenChange(mapContainer, map); + } catch (err) { + console.error(`Error switching to full-screen mode: ${err.message} (${err.name})`); + if (this.options.showNotification) { + this._showNotification(`Error switching to full-screen mode`, mapContainer); + } + } + } + + async _toggleFullScreenElement(element, enterFullScreen = true) { + const methods = { + enter: ['requestFullscreen', 'mozRequestFullScreen', 'webkitRequestFullscreen', 'msRequestFullscreen'], + exit: ['exitFullscreen', 'mozCancelFullScreen', 'webkitExitFullscreen', 'msExitFullscreen'], + }; + + const target = enterFullScreen ? element : document; + + for (const method of methods[enterFullScreen ? 'enter' : 'exit']) { + if (target[method]) { + await target[method](); + return; + } + } + + element.classList.toggle('pseudo-fullscreen', enterFullScreen); + + map.invalidateSize(); + } + + _handleFullScreenChange(container, map) { + const isFullScreen = this._isFullScreen(container); + this._updateIcon(container, isFullScreen); + this._container.title = isFullScreen ? this.options.exitFullScreenTitle : this.options.enterFullScreenTitle; + + if (typeof this.options.onFullScreenChange === 'function') { + if (this._isHandlingFullScreenChange) return; + this._isHandlingFullScreenChange = true; + + requestAnimationFrame(() => { + this.options.onFullScreenChange(isFullScreen); + this._isHandlingFullScreenChange = false; }); + } + + if (this.options.showNotification) { + this._showNotification( + isFullScreen ? 'Full-screen mode is ON' : 'Full-screen mode is OFF', + map.getContainer() + ); + } } - if ( - !document.fullscreenElement && - !document.mozFullScreenElement && - !document.webkitFullscreenElement && - !document.msFullscreenElement - ) { - if (element.requestFullscreen) { - handleFullScreenRequest(element.requestFullscreen()); - } else if (element.mozRequestFullScreen) { - handleFullScreenRequest(element.mozRequestFullScreen()); // Firefox - } else if (element.webkitRequestFullscreen) { - handleFullScreenRequest(element.webkitRequestFullscreen()); // Chrome, Safari, Opera - } else if (element.msRequestFullscreen) { - handleFullScreenRequest(element.msRequestFullscreen()); // IE/Edge - } else { - console.error('Fullscreen API is not supported by this browser.'); + _preventF11Default(event) { + if (event.key === 'F11') { + event.preventDefault(); + this.toggleFullScreen(map); } - } else { - if (document.exitFullscreen) { - handleFullScreenExit(document.exitFullscreen()); - } else if (document.mozCancelFullScreen) { - handleFullScreenExit(document.mozCancelFullScreen()); - } else if (document.webkitExitFullscreen) { - handleFullScreenExit(document.webkitExitFullscreen()); - } else if (document.msExitFullscreen) { - handleFullScreenExit(document.msExitFullscreen()); - } else { - console.error('Fullscreen API is not supported by this browser.'); + } + + _updateIcon(container, isFullScreen) { + const enterFullScreenIconDefault = ` + + `; + + const exitFullScreenIconDefault = ` + + `; + + const iconUrl = isFullScreen + ? this.options.exitFullScreenIcon || `data:image/svg+xml;base64,${btoa(exitFullScreenIconDefault)}` + : this.options.enterFullScreenIcon || `data:image/svg+xml;base64,${btoa(enterFullScreenIconDefault)}`; + + if (container.classList.contains('leaflet-control-fullscreen')) { + container.style.backgroundImage = `url('${iconUrl}')`; } } -} -function updateButtonOnFullScreenChange() { - updateFullScreenButton( - !!document.fullscreenElement || - !!document.mozFullScreenElement || - !!document.webkitFullscreenElement || - !!document.msFullscreenElement - ); -} + _addEventListeners() { + document.addEventListener('fullscreenchange', this._eventHandlers.fullscreenchange); + document.addEventListener('mozfullscreenchange', this._eventHandlers.fullscreenchange); + document.addEventListener('webkitfullscreenchange', this._eventHandlers.fullscreenchange); + document.addEventListener('MSFullscreenChange', this._eventHandlers.fullscreenchange); + document.addEventListener('keydown', this._eventHandlers.keydown); + } -function updateFullScreenButton(isFullScreen) { - const fullScreenButton = document.querySelector('.full-screen-button'); - if (fullScreenButton) { - if (isFullScreen) { - fullScreenButton.title = 'Exit full screen mode'; - fullScreenButton.querySelector('img').src = './markers/general_screen.svg'; - } else { - fullScreenButton.title = 'Set full screen mode'; - fullScreenButton.querySelector('img').src = './markers/full_screen.svg'; + _removeEventListeners() { + if (this._eventHandlers) { + document.removeEventListener('fullscreenchange', this._eventHandlers.fullscreenchange); + document.removeEventListener('mozfullscreenchange', this._eventHandlers.fullscreenchange); + document.removeEventListener('webkitfullscreenchange', this._eventHandlers.fullscreenchange); + document.removeEventListener('MSFullscreenChange', this._eventHandlers.fullscreenchange); + document.removeEventListener('keydown', this._eventHandlers.keydown); + delete this._eventHandlers; } } -} -document.addEventListener('keydown', (event) => { - if (event.key === 'F11') { - event.preventDefault(); + _throttle(func, limit) { + let inThrottle; + return function () { + const args = arguments; + const context = this; + if (!inThrottle) { + func.apply(context, args); + inThrottle = true; + setTimeout(() => (inThrottle = false), limit); + } + }; } -}); -document.addEventListener('fullscreenchange', updateButtonOnFullScreenChange); -document.addEventListener('mozfullscreenchange', updateButtonOnFullScreenChange); -document.addEventListener('webkitfullscreenchange', updateButtonOnFullScreenChange); -document.addEventListener('MSFullscreenChange', updateButtonOnFullScreenChange); + + _isFullScreen(element) { + return ( + document.fullscreenElement || + document.mozFullScreenElement || + document.webkitFullscreenElement || + document.msFullscreenElement || + element.classList.contains('pseudo-fullscreen') + ); + } + + _showNotification(message, mapContainer) { + if (this._notificationElement) { + mapContainer.removeChild(this._notificationElement); + this._notificationElement = null; + clearTimeout(this._notificationTimeout); + } + + const notification = L.DomUtil.create('div', '', mapContainer); + notification.id = 'map-notification'; + notification.innerText = message; + mapContainer.appendChild(notification); + + this._notificationElement = notification; + + this._notificationTimeout = setTimeout(() => { + notification.style.transition = 'opacity 1s'; + notification.style.opacity = '0'; + + setTimeout(() => { + if (notification.parentElement) { + mapContainer.removeChild(notification); + } + this._notificationElement = null; + }, 1000); + }, 3000); + } +} + +L.control.fullScreenButton = function (options) { + return new FullScreenButton(options); +}; + +export default L.control.fullScreenButton; diff --git a/js/map.js b/js/map.js index 03bd87e..d1d2634 100644 --- a/js/map.js +++ b/js/map.js @@ -5,9 +5,9 @@ import * as layers from '../js/layers.js'; import { isMobile } from '../js/mobileDetector.js'; import { createControlButton } from '../js/buttons.js'; import { addLegend } from '../js/legend.js'; -import { toggleFullScreen } from '../js/fullScreen.js'; import { openPopupWindow } from '../js/popupWindow.js'; import { updateControlStyle, updateLayer, gradualOpacityAnimation } from '../js/mapUtils.js'; +import '../js/fullScreen.js'; export function initializeMap() { const initialZoom = isMobile ? 5 : 6; @@ -27,19 +27,13 @@ export function initializeMap() { }); map.addControl(new centerButton()); - // Creation of full screen button for desktop device + // Creat a full screen button if (!isMobile) { - const fullScreenButton = createControlButton({ - position: 'topleft', - title: 'Set full screen mode', - imageSrc: './markers/full_screen.svg', - extraClass: 'full-screen-button', - onClick: function () { - const mapContainer = document.getElementById('map'); - toggleFullScreen(mapContainer); - }, - }); - map.addControl(new fullScreenButton()); + L.control + .fullScreenButton({ + position: 'topleft', + }) + .addTo(map); } // Create a button to call the external map frame diff --git a/js/styles.js b/js/styles.js index 0634c4c..a77ad18 100644 --- a/js/styles.js +++ b/js/styles.js @@ -210,6 +210,45 @@ export function addStyles() { border: 1px solid rgb(150, 150, 150); border-radius: 5px; } + + .pseudo-fullscreen { + background-color: #ffffff; + top: 0; + left: 0; + position: fixed !important; + width: 100% !important; + height: 100% !important; + z-index: 9999 !important; + } + + .leaflet-control-fullscreen { + background-color: rgb(245, 245, 245); + background-size: 40px 40px; + background-repeat: no-repeat; + background-position: center; + width: 50px; + height: 50px; + border: 2px solid rgb(150, 150, 150) !important; + border-radius: 5px; + cursor: pointer; + z-index: 9999; + } + + #map-notification { + position: absolute; + left: 50%; + bottom: 20px; + transform: translateX(-50%); + padding: 10px 20px; + background-color: #00000099; + color: #ffffff; + font-size: 1rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + border-radius: 5px; + z-index: 9999; + } `; document.head.appendChild(styleTag);