Merge pull request #2057 from umap-project/icon-to-modules

chore: move icon.js to modules
This commit is contained in:
Yohan Boniface 2024-08-13 11:10:32 +02:00 committed by GitHub
commit 5a33709cc9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 86 additions and 63 deletions

View file

@ -1,5 +1,6 @@
import { DomEvent, DomUtil, stamp } from '../../vendors/leaflet/leaflet-src.esm.js' import { DomEvent, DomUtil, stamp } from '../../vendors/leaflet/leaflet-src.esm.js'
import { translate } from './i18n.js' import { translate } from './i18n.js'
import * as Icon from './rendering/icon.js'
export default class Browser { export default class Browser {
constructor(map) { constructor(map) {
@ -34,14 +35,14 @@ export default class Browser {
const colorBox = DomUtil.create('i', 'icon icon-16 feature-color', row) const colorBox = DomUtil.create('i', 'icon icon-16 feature-color', row)
const title = DomUtil.create('span', 'feature-title', row) const title = DomUtil.create('span', 'feature-title', row)
const symbol = feature._getIconUrl const symbol = feature._getIconUrl
? U.Icon.prototype.formatUrl(feature._getIconUrl(), feature) ? Icon.formatUrl(feature._getIconUrl(), feature)
: null : null
title.textContent = feature.getDisplayName() || '—' title.textContent = feature.getDisplayName() || '—'
const bgcolor = feature.getPreviewColor() const bgcolor = feature.getPreviewColor()
colorBox.style.backgroundColor = bgcolor colorBox.style.backgroundColor = bgcolor
if (symbol && symbol !== U.SCHEMA.iconUrl.default) { if (symbol && symbol !== U.SCHEMA.iconUrl.default) {
const icon = U.Icon.makeIconElement(symbol, colorBox) const icon = Icon.makeElement(symbol, colorBox)
U.Icon.setIconContrast(icon, colorBox, symbol, bgcolor) Icon.setContrast(icon, colorBox, symbol, bgcolor)
} }
const viewFeature = (e) => { const viewFeature = (e) => {
feature.zoomTo({ ...e, callback: feature.view }) feature.zoomTo({ ...e, callback: feature.view })

View file

@ -25,6 +25,7 @@ import { EditPanel, FullPanel, Panel } from './ui/panel.js'
import Tooltip from './ui/tooltip.js' import Tooltip from './ui/tooltip.js'
import URLs from './urls.js' import URLs from './urls.js'
import * as Utils from './utils.js' import * as Utils from './utils.js'
import * as Icon from './rendering/icon.js'
import { DataLayer, LAYER_TYPES } from './data/layer.js' import { DataLayer, LAYER_TYPES } from './data/layer.js'
import { DataLayerPermissions, MapPermissions } from './permissions.js' import { DataLayerPermissions, MapPermissions } from './permissions.js'
import { Point, LineString, Polygon } from './data/features.js' import { Point, LineString, Polygon } from './data/features.js'
@ -51,6 +52,7 @@ window.U = {
FullPanel, FullPanel,
Help, Help,
HTTPError, HTTPError,
Icon,
Importer, Importer,
LAYER_TYPES, LAYER_TYPES,
LeafletMarker, LeafletMarker,

View file

@ -1,15 +1,36 @@
U.Icon = L.DivIcon.extend({ import {
statics: { DomEvent,
RECENT: [], DomUtil,
}, DivIcon,
Icon,
} from '../../../vendors/leaflet/leaflet-src.esm.js'
import * as Utils from '../utils.js'
import { SCHEMA } from '../schema.js'
export function getClass(name) {
switch (name) {
case 'Circle':
return Circle
case 'Ball':
return Ball
case 'Drop':
return Drop
default:
return DefaultIcon
}
}
export const RECENT = []
const BaseIcon = L.DivIcon.extend({
initialize: function (options) { initialize: function (options) {
const default_options = { const default_options = {
iconSize: null, // Made in css iconSize: null, // Made in css
iconUrl: U.SCHEMA.iconUrl.default, iconUrl: SCHEMA.iconUrl.default,
feature: null, feature: null,
} }
options = L.Util.extend({}, default_options, options) options = L.Util.extend({}, default_options, options)
L.Icon.prototype.initialize.call(this, options) Icon.prototype.initialize.call(this, options)
this.feature = this.options.feature this.feature = this.options.feature
if (this.feature?.isReadOnly()) { if (this.feature?.isReadOnly()) {
this.options.className += ' readonly' this.options.className += ' readonly'
@ -17,10 +38,10 @@ U.Icon = L.DivIcon.extend({
}, },
_setRecent: (url) => { _setRecent: (url) => {
if (U.Utils.hasVar(url)) return if (Utils.hasVar(url)) return
if (url === U.SCHEMA.iconUrl.default) return if (url === SCHEMA.iconUrl.default) return
if (U.Icon.RECENT.indexOf(url) === -1) { if (RECENT.indexOf(url) === -1) {
U.Icon.RECENT.push(url) RECENT.push(url)
} }
}, },
@ -32,29 +53,26 @@ U.Icon = L.DivIcon.extend({
} else { } else {
url = this.options[`${name}Url`] url = this.options[`${name}Url`]
} }
return this.formatUrl(url, this.feature) return formatUrl(url, this.feature)
}, },
_getColor: function () { _getColor: function () {
let color let color
if (this.feature) color = this.feature.getDynamicOption('color') if (this.feature) color = this.feature.getDynamicOption('color')
else if (this.options.color) color = this.options.color else if (this.options.color) color = this.options.color
else color = U.SCHEMA.color.default else color = SCHEMA.color.default
return color return color
}, },
_getOpacity: function () { _getOpacity: function () {
if (this.feature) return this.feature.getOption('iconOpacity') if (this.feature) return this.feature.getOption('iconOpacity')
return U.SCHEMA.iconOpacity.default return SCHEMA.iconOpacity.default
}, },
formatUrl: (url, feature) =>
U.Utils.greedyTemplate(url || '', feature ? feature.extendedProperties() : {}),
onAdd: () => {}, onAdd: () => {},
}) })
U.Icon.Default = U.Icon.extend({ const DefaultIcon = BaseIcon.extend({
default_options: { default_options: {
iconAnchor: new L.Point(16, 40), iconAnchor: new L.Point(16, 40),
popupAnchor: new L.Point(0, -40), popupAnchor: new L.Point(0, -40),
@ -64,11 +82,11 @@ U.Icon.Default = U.Icon.extend({
initialize: function (options) { initialize: function (options) {
options = L.Util.extend({}, this.default_options, options) options = L.Util.extend({}, this.default_options, options)
U.Icon.prototype.initialize.call(this, options) BaseIcon.prototype.initialize.call(this, options)
}, },
_setIconStyles: function (img, name) { _setIconStyles: function (img, name) {
U.Icon.prototype._setIconStyles.call(this, img, name) BaseIcon.prototype._setIconStyles.call(this, img, name)
const color = this._getColor() const color = this._getColor()
const opacity = this._getOpacity() const opacity = this._getOpacity()
this.elements.container.style.backgroundColor = color this.elements.container.style.backgroundColor = color
@ -80,29 +98,29 @@ U.Icon.Default = U.Icon.extend({
onAdd: function () { onAdd: function () {
const src = this._getIconUrl('icon') const src = this._getIconUrl('icon')
const bgcolor = this._getColor() const bgcolor = this._getColor()
U.Icon.setIconContrast(this.elements.icon, this.elements.container, src, bgcolor) setContrast(this.elements.icon, this.elements.container, src, bgcolor)
}, },
createIcon: function () { createIcon: function () {
this.elements = {} this.elements = {}
this.elements.main = L.DomUtil.create('div') this.elements.main = DomUtil.create('div')
this.elements.container = L.DomUtil.create( this.elements.container = DomUtil.create(
'div', 'div',
'icon_container', 'icon_container',
this.elements.main this.elements.main
) )
this.elements.main.dataset.feature = this.feature?.id this.elements.main.dataset.feature = this.feature?.id
this.elements.arrow = L.DomUtil.create('div', 'icon_arrow', this.elements.main) this.elements.arrow = DomUtil.create('div', 'icon_arrow', this.elements.main)
const src = this._getIconUrl('icon') const src = this._getIconUrl('icon')
if (src) { if (src) {
this.elements.icon = U.Icon.makeIconElement(src, this.elements.container) this.elements.icon = makeElement(src, this.elements.container)
} }
this._setIconStyles(this.elements.main, 'icon') this._setIconStyles(this.elements.main, 'icon')
return this.elements.main return this.elements.main
}, },
}) })
U.Icon.Circle = U.Icon.extend({ const Circle = BaseIcon.extend({
initialize: function (options) { initialize: function (options) {
const default_options = { const default_options = {
popupAnchor: new L.Point(0, -6), popupAnchor: new L.Point(0, -6),
@ -110,18 +128,18 @@ U.Icon.Circle = U.Icon.extend({
className: 'umap-circle-icon', className: 'umap-circle-icon',
} }
options = L.Util.extend({}, default_options, options) options = L.Util.extend({}, default_options, options)
U.Icon.prototype.initialize.call(this, options) BaseIcon.prototype.initialize.call(this, options)
}, },
_setIconStyles: function (img, name) { _setIconStyles: function (img, name) {
U.Icon.prototype._setIconStyles.call(this, img, name) BaseIcon.prototype._setIconStyles.call(this, img, name)
this.elements.main.style.backgroundColor = this._getColor() this.elements.main.style.backgroundColor = this._getColor()
this.elements.main.style.opacity = this._getOpacity() this.elements.main.style.opacity = this._getOpacity()
}, },
createIcon: function () { createIcon: function () {
this.elements = {} this.elements = {}
this.elements.main = L.DomUtil.create('div') this.elements.main = DomUtil.create('div')
this.elements.main.innerHTML = ' ' this.elements.main.innerHTML = ' '
this._setIconStyles(this.elements.main, 'icon') this._setIconStyles(this.elements.main, 'icon')
this.elements.main.dataset.feature = this.feature?.id this.elements.main.dataset.feature = this.feature?.id
@ -129,7 +147,7 @@ U.Icon.Circle = U.Icon.extend({
}, },
}) })
U.Icon.Drop = U.Icon.Default.extend({ const Drop = DefaultIcon.extend({
default_options: { default_options: {
iconAnchor: new L.Point(16, 42), iconAnchor: new L.Point(16, 42),
popupAnchor: new L.Point(0, -42), popupAnchor: new L.Point(0, -42),
@ -138,7 +156,7 @@ U.Icon.Drop = U.Icon.Default.extend({
}, },
}) })
U.Icon.Ball = U.Icon.Default.extend({ const Ball = DefaultIcon.extend({
default_options: { default_options: {
iconAnchor: new L.Point(8, 30), iconAnchor: new L.Point(8, 30),
popupAnchor: new L.Point(0, -28), popupAnchor: new L.Point(0, -28),
@ -148,20 +166,20 @@ U.Icon.Ball = U.Icon.Default.extend({
createIcon: function () { createIcon: function () {
this.elements = {} this.elements = {}
this.elements.main = L.DomUtil.create('div') this.elements.main = DomUtil.create('div')
this.elements.container = L.DomUtil.create( this.elements.container = DomUtil.create(
'div', 'div',
'icon_container', 'icon_container',
this.elements.main this.elements.main
) )
this.elements.main.dataset.feature = this.feature?.id this.elements.main.dataset.feature = this.feature?.id
this.elements.arrow = L.DomUtil.create('div', 'icon_arrow', this.elements.main) this.elements.arrow = DomUtil.create('div', 'icon_arrow', this.elements.main)
this._setIconStyles(this.elements.main, 'icon') this._setIconStyles(this.elements.main, 'icon')
return this.elements.main return this.elements.main
}, },
_setIconStyles: function (img, name) { _setIconStyles: function (img, name) {
U.Icon.prototype._setIconStyles.call(this, img, name) BaseIcon.prototype._setIconStyles.call(this, img, name)
const color = this._getColor('color') const color = this._getColor('color')
let background let background
if (L.Browser.ielt9) { if (L.Browser.ielt9) {
@ -176,7 +194,7 @@ U.Icon.Ball = U.Icon.Default.extend({
}, },
}) })
U.Icon.Cluster = L.DivIcon.extend({ export const Cluster = DivIcon.extend({
options: { options: {
iconSize: [40, 40], iconSize: [40, 40],
}, },
@ -187,9 +205,9 @@ U.Icon.Cluster = L.DivIcon.extend({
}, },
createIcon: function () { createIcon: function () {
const container = L.DomUtil.create('div', 'leaflet-marker-icon marker-cluster') const container = DomUtil.create('div', 'leaflet-marker-icon marker-cluster')
const div = L.DomUtil.create('div', '', container) const div = DomUtil.create('div', '', container)
const span = L.DomUtil.create('span', '', div) const span = DomUtil.create('span', '', div)
const backgroundColor = this.datalayer.getColor() const backgroundColor = this.datalayer.getColor()
span.textContent = this.cluster.getChildCount() span.textContent = this.cluster.getChildCount()
div.style.backgroundColor = backgroundColor div.style.backgroundColor = backgroundColor
@ -202,27 +220,28 @@ U.Icon.Cluster = L.DivIcon.extend({
if (this.datalayer.options.cluster?.textColor) { if (this.datalayer.options.cluster?.textColor) {
color = this.datalayer.options.cluster.textColor color = this.datalayer.options.cluster.textColor
} }
return color || L.DomUtil.TextColorFromBackgroundColor(el, backgroundColor) return color || DomUtil.TextColorFromBackgroundColor(el, backgroundColor)
}, },
}) })
U.Icon.isImg = (src) => export function isImg(src) {
U.Utils.isPath(src) || U.Utils.isRemoteUrl(src) || U.Utils.isDataImage(src) return Utils.isPath(src) || Utils.isRemoteUrl(src) || Utils.isDataImage(src)
}
U.Icon.makeIconElement = (src, parent) => { export function makeElement(src, parent) {
let icon let icon
if (U.Icon.isImg(src)) { if (isImg(src)) {
icon = L.DomUtil.create('img') icon = DomUtil.create('img')
icon.src = src icon.src = src
} else { } else {
icon = L.DomUtil.create('span') icon = DomUtil.create('span')
icon.textContent = src icon.textContent = src
} }
parent.appendChild(icon) parent.appendChild(icon)
return icon return icon
} }
U.Icon.setIconContrast = (icon, parent, src, bgcolor) => { export function setContrast(icon, parent, src, bgcolor) {
/* /*
* icon: the element we'll adapt the style, it can be an image or text * icon: the element we'll adapt the style, it can be an image or text
* parent: the element we'll consider to decide whether to adapt the style, * parent: the element we'll consider to decide whether to adapt the style,
@ -233,14 +252,10 @@ U.Icon.setIconContrast = (icon, parent, src, bgcolor) => {
*/ */
if (!icon) return if (!icon) return
if (L.DomUtil.contrastedColor(parent, bgcolor)) { if (DomUtil.contrastedColor(parent, bgcolor)) {
// Decide whether to switch svg to white or not, but do it // Decide whether to switch svg to white or not, but do it
// only for internal SVG, as invert could do weird things // only for internal SVG, as invert could do weird things
if ( if (Utils.isPath(src) && src.endsWith('.svg') && src !== SCHEMA.iconUrl.default) {
U.Utils.isPath(src) &&
src.endsWith('.svg') &&
src !== U.SCHEMA.iconUrl.default
) {
// Must be called after icon container is added to the DOM // Must be called after icon container is added to the DOM
// An image // An image
icon.style.filter = 'invert(1)' icon.style.filter = 'invert(1)'
@ -250,3 +265,7 @@ U.Icon.setIconContrast = (icon, parent, src, bgcolor) => {
} }
} }
} }
export function formatUrl(url, feature) {
return Utils.greedyTemplate(url || '', feature ? feature.extendedProperties() : {})
}

View file

@ -1,10 +1,10 @@
// WARNING must be loaded dynamically, or at least after leaflet.markercluster // WARNING must be loaded dynamically, or at least after leaflet.markercluster
// Uses global L.MarkerCluster and L.MarkerClusterGroup, not exposed as ESM // Uses global L.MarkerCluster and L.MarkerClusterGroup, not exposed as ESM
// Uses global U.Icon not yet a module
import { translate } from '../../i18n.js' import { translate } from '../../i18n.js'
import { LayerMixin } from './base.js' import { LayerMixin } from './base.js'
import * as Utils from '../../utils.js' import * as Utils from '../../utils.js'
import { Evented } from '../../../../vendors/leaflet/leaflet-src.esm.js' import { Evented } from '../../../../vendors/leaflet/leaflet-src.esm.js'
import { Cluster as ClusterIcon } from '../icon.js'
const MarkerCluster = L.MarkerCluster.extend({ const MarkerCluster = L.MarkerCluster.extend({
// Custom class so we can call computeTextColor // Custom class so we can call computeTextColor
@ -34,7 +34,7 @@ export const Cluster = L.MarkerClusterGroup.extend({
polygonOptions: { polygonOptions: {
color: this.datalayer.getColor(), color: this.datalayer.getColor(),
}, },
iconCreateFunction: (cluster) => new U.Icon.Cluster(datalayer, cluster), iconCreateFunction: (cluster) => new ClusterIcon(datalayer, cluster),
} }
if (this.datalayer.options.cluster?.radius) { if (this.datalayer.options.cluster?.radius) {
options.maxClusterRadius = this.datalayer.options.cluster.radius options.maxClusterRadius = this.datalayer.options.cluster.radius

View file

@ -1,6 +1,7 @@
import { DomUtil, DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js' import { DomUtil, DomEvent } from '../../../vendors/leaflet/leaflet-src.esm.js'
import { translate, getLocale } from '../i18n.js' import { translate, getLocale } from '../i18n.js'
import * as Utils from '../utils.js' import * as Utils from '../utils.js'
import * as Icon from './icon.js'
export default function loadTemplate(name, feature, container) { export default function loadTemplate(name, feature, container) {
let klass = PopupTemplate let klass = PopupTemplate
@ -160,9 +161,9 @@ class OSM extends TitleMixin(PopupTemplate) {
const color = feature.getPreviewColor() const color = feature.getPreviewColor()
title.style.backgroundColor = color title.style.backgroundColor = color
const iconUrl = feature.getDynamicOption('iconUrl') const iconUrl = feature.getDynamicOption('iconUrl')
const icon = U.Icon.makeIconElement(iconUrl, title) const icon = Icon.makeElement(iconUrl, title)
DomUtil.addClass(icon, 'icon') DomUtil.addClass(icon, 'icon')
U.Icon.setIconContrast(icon, title, iconUrl, color) Icon.setContrast(icon, title, iconUrl, color)
if (DomUtil.contrastedColor(title, color)) title.style.color = 'white' if (DomUtil.contrastedColor(title, color)) title.style.color = 'white'
DomUtil.add('span', '', title, this.getName(feature)) DomUtil.add('span', '', title, this.getName(feature))
const street = props['addr:street'] const street = props['addr:street']

View file

@ -11,6 +11,7 @@ import {
import { translate } from '../i18n.js' import { translate } from '../i18n.js'
import { uMapAlert as Alert } from '../../components/alerts/alert.js' import { uMapAlert as Alert } from '../../components/alerts/alert.js'
import * as Utils from '../utils.js' import * as Utils from '../utils.js'
import * as Icon from './icon.js'
const FeatureMixin = { const FeatureMixin = {
initialize: function (feature, latlngs) { initialize: function (feature, latlngs) {
@ -219,7 +220,7 @@ export const LeafletMarker = Marker.extend({
}, },
getIcon: function () { getIcon: function () {
const Class = U.Icon[this.getIconClass()] || U.Icon.Default const Class = Icon.getClass(this.getIconClass())
return new Class({ feature: this.feature }) return new Class({ feature: this.feature })
}, },

View file

@ -550,7 +550,7 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
// Do not try to render URL with variables // Do not try to render URL with variables
const box = L.DomUtil.create('div', 'umap-pictogram-choice', this.buttons) const box = L.DomUtil.create('div', 'umap-pictogram-choice', this.buttons)
L.DomEvent.on(box, 'click', this.onDefine, this) L.DomEvent.on(box, 'click', this.onDefine, this)
const icon = U.Icon.makeIconElement(this.value(), box) const icon = U.Icon.makeElement(this.value(), box)
} }
this.button = L.DomUtil.createButton( this.button = L.DomUtil.createButton(
'button action-button', 'button action-button',
@ -571,7 +571,7 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
if (search && U.Utils.normalize(title).indexOf(search) === -1) return if (search && U.Utils.normalize(title).indexOf(search) === -1) return
const className = value === this.value() ? `${baseClass} selected` : baseClass const className = value === this.value() ? `${baseClass} selected` : baseClass
const container = L.DomUtil.create('div', className, parent) const container = L.DomUtil.create('div', className, parent)
U.Icon.makeIconElement(value, container) U.Icon.makeElement(value, container)
container.title = title container.title = title
L.DomEvent.on( L.DomEvent.on(
container, container,

View file

@ -45,7 +45,6 @@
defer></script> defer></script>
<script src="{% static 'umap/js/umap.core.js' %}" defer></script> <script src="{% static 'umap/js/umap.core.js' %}" defer></script>
<script src="{% static 'umap/js/umap.forms.js' %}" defer></script> <script src="{% static 'umap/js/umap.forms.js' %}" defer></script>
<script src="{% static 'umap/js/umap.icon.js' %}" defer></script>
<script src="{% static 'umap/js/umap.controls.js' %}" defer></script> <script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
<script src="{% static 'umap/js/umap.js' %}" defer></script> <script src="{% static 'umap/js/umap.js' %}" defer></script>
<script src="{% static 'umap/js/components/fragment.js' %}" defer></script> <script src="{% static 'umap/js/components/fragment.js' %}" defer></script>