From 773a63c041e737fa81dc29aabe196d6bd70b64a4 Mon Sep 17 00:00:00 2001 From: Anton Khorev Date: Sun, 12 May 2024 02:09:56 +0300 Subject: [PATCH 01/10] Add dark mode class with enable/disable methods --- vendor/assets/leaflet/leaflet.osm.js | 83 ++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/vendor/assets/leaflet/leaflet.osm.js b/vendor/assets/leaflet/leaflet.osm.js index 0e51f34086..7f8fa7eb31 100644 --- a/vendor/assets/leaflet/leaflet.osm.js +++ b/vendor/assets/leaflet/leaflet.osm.js @@ -1,5 +1,82 @@ L.OSM = {}; +L.OSM.DarkMode = L.Class.extend({ + statics: { + _darkModes: [], + _layers: [], + + _addLayer: function (layer) { + this._layers.push(layer); + this._darkModes.forEach(function (darkMode) { + darkMode._addLayer(layer); + }); + }, + _removeLayer: function (layer) { + this._darkModes.forEach(function (darkMode) { + darkMode._removeLayer(layer); + }); + var index = this._layers.indexOf(layer); + if (index > -1) { + this._layers.splice(index, 1); + } + } + }, + + options: { + darkFilter: '' + }, + + initialize: function (options) { + L.Util.setOptions(this, options); + this._darkFilter = this.options.darkFilter; + this._enabled = false; + L.OSM.DarkMode._darkModes.push(this); + }, + + enable: function () { + if (!this._enabled) { + this._enabled = true; + L.OSM.DarkMode._layers.forEach(function (layer) { + this._enableLayerDarkVariant(layer); + }, this); + } + return this; + }, + disable: function () { + if (this._enabled) { + this._enabled = false; + L.OSM.DarkMode._layers.forEach(function (layer) { + this._disableLayerDarkVariant(layer); + }, this); + } + return this; + }, + + _addLayer: function (layer) { + if (this._enabled) { + this._enableLayerDarkVariant(layer); + } + }, + _removeLayer: function (layer) { + if (this._enabled) { + this._disableLayerDarkVariant(layer); + } + }, + + _enableLayerDarkVariant: function (layer) { + var container = layer.getContainer(); + if (container) { + container.style.setProperty('filter', this._darkFilter); + } + }, + _disableLayerDarkVariant: function (layer) { + var container = layer.getContainer(); + if (container) { + layer.getContainer().style.removeProperty('filter'); + } + } +}); + L.OSM.TileLayer = L.TileLayer.extend({ options: { url: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', @@ -9,6 +86,12 @@ L.OSM.TileLayer = L.TileLayer.extend({ initialize: function (options) { options = L.Util.setOptions(this, options); L.TileLayer.prototype.initialize.call(this, options.url); + + this.on("add", function () { + L.OSM.DarkMode._addLayer(this); + }).on("remove", function () { + L.OSM.DarkMode._removeLayer(this); + }); } }); From 7e64d9749ac529d7859437d8f164d9cc6c37436f Mon Sep 17 00:00:00 2001 From: Anton Khorev Date: Mon, 13 May 2024 19:04:39 +0300 Subject: [PATCH 02/10] Add dark mode toggle method --- vendor/assets/leaflet/leaflet.osm.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/vendor/assets/leaflet/leaflet.osm.js b/vendor/assets/leaflet/leaflet.osm.js index 7f8fa7eb31..56469eb9b5 100644 --- a/vendor/assets/leaflet/leaflet.osm.js +++ b/vendor/assets/leaflet/leaflet.osm.js @@ -51,6 +51,14 @@ L.OSM.DarkMode = L.Class.extend({ } return this; }, + toggle: function () { + if (this._enabled) { + this.disable(); + } else { + this.enable(); + } + return this; + }, _addLayer: function (layer) { if (this._enabled) { From 72af937910a70eb564de7c769143ade4f0ff45ed Mon Sep 17 00:00:00 2001 From: Anton Khorev Date: Mon, 13 May 2024 19:11:39 +0300 Subject: [PATCH 03/10] Add dark mode toggle argument --- vendor/assets/leaflet/leaflet.osm.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/vendor/assets/leaflet/leaflet.osm.js b/vendor/assets/leaflet/leaflet.osm.js index 56469eb9b5..398ca36de6 100644 --- a/vendor/assets/leaflet/leaflet.osm.js +++ b/vendor/assets/leaflet/leaflet.osm.js @@ -51,11 +51,19 @@ L.OSM.DarkMode = L.Class.extend({ } return this; }, - toggle: function () { - if (this._enabled) { - this.disable(); + toggle: function (requestEnable) { + if (requestEnable !== undefined) { + if (requestEnable) { + this.enable(); + } else { + this.disable(); + } } else { - this.enable(); + if (this._enabled) { + this.disable(); + } else { + this.enable(); + } } return this; }, From 7fa2779c4d90e289edad735f11db6ef3e935fa43 Mon Sep 17 00:00:00 2001 From: Anton Khorev Date: Mon, 13 May 2024 18:31:11 +0300 Subject: [PATCH 04/10] Add isEnabled dark mode method --- vendor/assets/leaflet/leaflet.osm.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vendor/assets/leaflet/leaflet.osm.js b/vendor/assets/leaflet/leaflet.osm.js index 398ca36de6..303c953a49 100644 --- a/vendor/assets/leaflet/leaflet.osm.js +++ b/vendor/assets/leaflet/leaflet.osm.js @@ -67,6 +67,9 @@ L.OSM.DarkMode = L.Class.extend({ } return this; }, + isEnabled: function () { + return this._enabled; + }, _addLayer: function (layer) { if (this._enabled) { From e35141b2d1c1733d9a15a01c08b796e4edda6b4e Mon Sep 17 00:00:00 2001 From: Anton Khorev Date: Sun, 12 May 2024 03:10:58 +0300 Subject: [PATCH 05/10] Add prefers-color-scheme watcher --- vendor/assets/leaflet/leaflet.osm.js | 31 ++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/vendor/assets/leaflet/leaflet.osm.js b/vendor/assets/leaflet/leaflet.osm.js index 303c953a49..1a72a8a492 100644 --- a/vendor/assets/leaflet/leaflet.osm.js +++ b/vendor/assets/leaflet/leaflet.osm.js @@ -1,5 +1,36 @@ L.OSM = {}; +L.OSM.PrefersColorSchemeWatcher = L.Class.extend({ + initialize: function (darkMode) { + this._darkMode = darkMode; + }, + + watch: function () { + if (!this._prefersDarkQuery) { + this._darkModeWasEnabled = this._darkMode.isEnabled(); + this._prefersDarkQuery = matchMedia("(prefers-color-scheme: dark)"); + this._prefersDarkListener(); + L.DomEvent.on(this._prefersDarkQuery, 'change', this._prefersDarkListener, this); + } + return this; + }, + unwatch: function () { + if (this._prefersDarkQuery) { + L.DomEvent.off(this._prefersDarkQuery, 'change', this._prefersDarkListener, this); + this._prefersDarkQuery = undefined; + this._darkMode.toggle(this._darkModeWasEnabled); + this._darkModeWasEnabled = undefined; + } + return this; + }, + + _prefersDarkListener: function () { + if (this._prefersDarkQuery) { + this._darkMode.toggle(this._prefersDarkQuery.matches); + } + } +}); + L.OSM.DarkMode = L.Class.extend({ statics: { _darkModes: [], From e795c93bd59e10a2ee7d5219aff7e4f35b7ed8aa Mon Sep 17 00:00:00 2001 From: Anton Khorev Date: Sun, 12 May 2024 03:21:13 +0300 Subject: [PATCH 06/10] Add support for dark tiles url --- vendor/assets/leaflet/leaflet.osm.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/vendor/assets/leaflet/leaflet.osm.js b/vendor/assets/leaflet/leaflet.osm.js index 1a72a8a492..3023d1b5e7 100644 --- a/vendor/assets/leaflet/leaflet.osm.js +++ b/vendor/assets/leaflet/leaflet.osm.js @@ -114,12 +114,27 @@ L.OSM.DarkMode = L.Class.extend({ }, _enableLayerDarkVariant: function (layer) { + if (layer.options.darkUrl) { + layer.setUrl(layer.options.darkUrl); + } else { + this._enableLayerDarkFilter(layer); + } + }, + _disableLayerDarkVariant: function (layer) { + if (layer.options.darkUrl) { + layer.setUrl(layer.options.url); + } else { + this._disableLayerDarkFilter(layer); + } + }, + + _enableLayerDarkFilter: function (layer) { var container = layer.getContainer(); if (container) { container.style.setProperty('filter', this._darkFilter); } }, - _disableLayerDarkVariant: function (layer) { + _disableLayerDarkFilter: function (layer) { var container = layer.getContainer(); if (container) { layer.getContainer().style.removeProperty('filter'); From ec819099cb3b80f6c99bc95aecf167c1a512ca1e Mon Sep 17 00:00:00 2001 From: Anton Khorev Date: Sun, 12 May 2024 03:22:26 +0300 Subject: [PATCH 07/10] Make expected changes to osm website and layers data --- app/assets/javascripts/application.js | 6 ++++++ app/assets/stylesheets/common.scss | 1 - vendor/assets/leaflet/leaflet.osm.js | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index ead01a8ff8..41010b6115 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -145,4 +145,10 @@ $(document).ready(function () { $("#edit_tab") .attr("title", I18n.t("javascripts.site.edit_disabled_tooltip")); + + new L.OSM.PrefersColorSchemeWatcher( + new L.OSM.DarkMode({ + darkFilter: "brightness(.8)" + }) + ).watch(); }); diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 31ce7dd28a..2f99852c7a 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -502,7 +502,6 @@ body.small-nav { } @include color-mode(dark) { - .leaflet-tile-container .leaflet-tile, .mapkey-table-entry td:first-child > * { filter: brightness(.8); } diff --git a/vendor/assets/leaflet/leaflet.osm.js b/vendor/assets/leaflet/leaflet.osm.js index 3023d1b5e7..6f87af1719 100644 --- a/vendor/assets/leaflet/leaflet.osm.js +++ b/vendor/assets/leaflet/leaflet.osm.js @@ -187,6 +187,7 @@ L.OSM.CycleMap = L.OSM.TileLayer.extend({ L.OSM.TransportMap = L.OSM.TileLayer.extend({ options: { url: 'https://{s}.tile.thunderforest.com/transport/{z}/{x}/{y}{r}.png?apikey={apikey}', + darkUrl: 'https://{s}.tile.thunderforest.com/transport-dark/{z}/{x}/{y}{r}.png?apikey={apikey}', maxZoom: 21, attribution: '© OpenStreetMap contributors. Tiles courtesy of Andy Allan' } From a8e8df896f10fa7d30eb3f6c4f2fa263f1854e23 Mon Sep 17 00:00:00 2001 From: Anton Khorev Date: Fri, 10 May 2024 16:17:31 +0300 Subject: [PATCH 08/10] Add context menu with dark mode tile filters --- app/assets/javascripts/application.js | 27 +++++++++--- app/assets/javascripts/index/contextmenu.js | 2 + config/locales/en.yml | 5 +++ vendor/assets/leaflet/leaflet.osm.js | 46 ++++++++++++++++++++- 4 files changed, 74 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 41010b6115..75f505e95a 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -146,9 +146,26 @@ $(document).ready(function () { $("#edit_tab") .attr("title", I18n.t("javascripts.site.edit_disabled_tooltip")); - new L.OSM.PrefersColorSchemeWatcher( - new L.OSM.DarkMode({ - darkFilter: "brightness(.8)" - }) - ).watch(); + OSM.darkMode = new L.OSM.DarkMode({ + darkFilter: "brightness(.8)", + darkFilterMenuItems: [ + { + text: I18n.t("javascripts.map.filters.brightness100"), + filter: "" + }, + { + text: I18n.t("javascripts.map.filters.brightness80"), + filter: "brightness(.8)" + }, + { + text: I18n.t("javascripts.map.filters.brightness60"), + filter: "brightness(.6)" + }, + { + text: I18n.t("javascripts.map.filters.invert"), + filter: "invert(.8) hue-rotate(180deg)" + } + ] + }); + new L.OSM.PrefersColorSchemeWatcher(OSM.darkMode).watch(); }); diff --git a/app/assets/javascripts/index/contextmenu.js b/app/assets/javascripts/index/contextmenu.js index ea284f29b9..7189b4f184 100644 --- a/app/assets/javascripts/index/contextmenu.js +++ b/app/assets/javascripts/index/contextmenu.js @@ -74,6 +74,8 @@ OSM.initializeContextMenu = function (map) { } }); + OSM.darkMode.manageMapContextMenu(map); + map.on("mousedown", function (e) { if (e.originalEvent.shiftKey) map.contextmenu.disable(); else map.contextmenu.enable(); diff --git a/config/locales/en.yml b/config/locales/en.yml index f68488c09c..8ac959316c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -3073,6 +3073,11 @@ en: tracestrack: Tracestrack hotosm_credit: "Tiles style by %{hotosm_link} hosted by %{osm_france_link}" hotosm_name: Humanitarian OpenStreetMap Team + filters: + brightness100: 100% brightness + brightness80: 80% brightness + brightness60: 60% brightness + invert: Invert site: edit_tooltip: Edit the map edit_disabled_tooltip: Zoom in to edit the map diff --git a/vendor/assets/leaflet/leaflet.osm.js b/vendor/assets/leaflet/leaflet.osm.js index 6f87af1719..1b3dd33f34 100644 --- a/vendor/assets/leaflet/leaflet.osm.js +++ b/vendor/assets/leaflet/leaflet.osm.js @@ -54,13 +54,15 @@ L.OSM.DarkMode = L.Class.extend({ }, options: { - darkFilter: '' + darkFilter: '', + darkFilterMenuItems: [] }, initialize: function (options) { L.Util.setOptions(this, options); this._darkFilter = this.options.darkFilter; this._enabled = false; + this._contextMenuUpdateHandlers = []; L.OSM.DarkMode._darkModes.push(this); }, @@ -70,6 +72,9 @@ L.OSM.DarkMode = L.Class.extend({ L.OSM.DarkMode._layers.forEach(function (layer) { this._enableLayerDarkVariant(layer); }, this); + this._contextMenuUpdateHandlers.forEach(function (handler) { + handler(); + }); } return this; }, @@ -79,6 +84,9 @@ L.OSM.DarkMode = L.Class.extend({ L.OSM.DarkMode._layers.forEach(function (layer) { this._disableLayerDarkVariant(layer); }, this); + this._contextMenuUpdateHandlers.forEach(function (handler) { + handler(); + }); } return this; }, @@ -102,6 +110,42 @@ L.OSM.DarkMode = L.Class.extend({ return this._enabled; }, + // requires Leaflet.contextmenu plugin + manageMapContextMenu: function (map) { + var contextMenuElements = []; + + if (this.options.darkFilterMenuItems.length > 0) { + var separator = map.contextmenu.addItem({ + separator: true + }); + contextMenuElements.push(separator); + } + this.options.darkFilterMenuItems.forEach(function (menuItem) { + var menuElement = map.contextmenu.addItem({ + text: menuItem.text, + callback: function () { + this._darkFilter = menuItem.filter; + if (this._enabled) { + L.OSM.DarkMode._layers.forEach(function (layer) { + this._enableLayerDarkVariant(layer); + }, this); + } + }.bind(this) + }); + contextMenuElements.push(menuElement); + }, this); + + var updateContextMenuElements = function () { + contextMenuElements.forEach(function (menuElement) { + menuElement.hidden = !this._enabled; + }, this); + }.bind(this); + updateContextMenuElements(); + this._contextMenuUpdateHandlers.push(updateContextMenuElements); + + return this; + }, + _addLayer: function (layer) { if (this._enabled) { this._enableLayerDarkVariant(layer); From a9d4e4996ad2726fbd103287cbb17e5345bc2826 Mon Sep 17 00:00:00 2001 From: Anton Khorev Date: Fri, 10 May 2024 17:36:22 +0300 Subject: [PATCH 09/10] Mark current dark mode context menu item --- vendor/assets/leaflet/leaflet.osm.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/vendor/assets/leaflet/leaflet.osm.js b/vendor/assets/leaflet/leaflet.osm.js index 1b3dd33f34..9a9db0a82f 100644 --- a/vendor/assets/leaflet/leaflet.osm.js +++ b/vendor/assets/leaflet/leaflet.osm.js @@ -125,6 +125,9 @@ L.OSM.DarkMode = L.Class.extend({ text: menuItem.text, callback: function () { this._darkFilter = menuItem.filter; + this._contextMenuUpdateHandlers.forEach(function (handler) { + handler(); + }); if (this._enabled) { L.OSM.DarkMode._layers.forEach(function (layer) { this._enableLayerDarkVariant(layer); @@ -132,12 +135,16 @@ L.OSM.DarkMode = L.Class.extend({ } }.bind(this) }); + this._decorateContextMenuElement(menuElement, menuItem); contextMenuElements.push(menuElement); }, this); var updateContextMenuElements = function () { contextMenuElements.forEach(function (menuElement) { menuElement.hidden = !this._enabled; + if ('filter' in menuElement.dataset) { + menuElement.firstChild.checked = menuElement.dataset.filter === this._darkFilter; + } }, this); }.bind(this); updateContextMenuElements(); @@ -183,6 +190,17 @@ L.OSM.DarkMode = L.Class.extend({ if (container) { layer.getContainer().style.removeProperty('filter'); } + }, + + _decorateContextMenuElement: function (menuElement, menuItem) { + menuElement.dataset.filter = menuItem.filter; + var radio = document.createElement('input'); + radio.type = 'radio'; + radio.tabIndex = -1; + radio.classList.add('leaflet-contextmenu-icon'); + radio.style.pointerEvents = 'none'; + radio.style.transform = 'scale(80%)'; + menuElement.prepend(radio, " "); } }); From 52a27698a00fce3cea8b2da65f47f889a41cf51b Mon Sep 17 00:00:00 2001 From: Anton Khorev Date: Fri, 10 May 2024 18:40:59 +0300 Subject: [PATCH 10/10] Hide dark mode context menu items when not applicable --- vendor/assets/leaflet/leaflet.osm.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/vendor/assets/leaflet/leaflet.osm.js b/vendor/assets/leaflet/leaflet.osm.js index 9a9db0a82f..59eb1e2a25 100644 --- a/vendor/assets/leaflet/leaflet.osm.js +++ b/vendor/assets/leaflet/leaflet.osm.js @@ -140,8 +140,16 @@ L.OSM.DarkMode = L.Class.extend({ }, this); var updateContextMenuElements = function () { + var numberOfLayersWithApplicableFilter = 0; + map.eachLayer(function (layer) { + if (layer instanceof L.OSM.TileLayer) { + if (!layer.options.darkUrl) { + numberOfLayersWithApplicableFilter++; + } + } + }); contextMenuElements.forEach(function (menuElement) { - menuElement.hidden = !this._enabled; + menuElement.hidden = !this._enabled || numberOfLayersWithApplicableFilter == 0; if ('filter' in menuElement.dataset) { menuElement.firstChild.checked = menuElement.dataset.filter === this._darkFilter; } @@ -149,6 +157,8 @@ L.OSM.DarkMode = L.Class.extend({ }.bind(this); updateContextMenuElements(); this._contextMenuUpdateHandlers.push(updateContextMenuElements); + map.on("layeradd", updateContextMenuElements); + map.on("layerremove", updateContextMenuElements); return this; },