chore: split umap.js in two modules

This commit is contained in:
Yohan Boniface 2024-11-05 11:53:57 +01:00
parent 256d4487e7
commit c952fed96a
33 changed files with 2605 additions and 2358 deletions

View file

@ -1,6 +1,8 @@
import Umap from '../modules/umap.js'
class UmapFragment extends HTMLElement { class UmapFragment extends HTMLElement {
connectedCallback() { connectedCallback() {
new U.Map(this.firstElementChild.id, JSON.parse(this.dataset.settings)) new Umap(this.firstElementChild.id, JSON.parse(this.dataset.settings))
} }
} }

View file

@ -6,9 +6,9 @@ import { EXPORT_FORMATS } from './formatter.js'
import ContextMenu from './ui/contextmenu.js' import ContextMenu from './ui/contextmenu.js'
export default class Browser { export default class Browser {
constructor(map) { constructor(umap) {
this.map = map this.umap = umap
this.map.on('moveend', this.onMoveEnd, this) this.umap._leafletMap.on('moveend', this.onMoveEnd, this)
this.options = { this.options = {
filter: '', filter: '',
inBbox: false, inBbox: false,
@ -82,7 +82,7 @@ export default class Browser {
updateDatalayer(datalayer) { updateDatalayer(datalayer) {
// Compute once, but use it for each feature later. // Compute once, but use it for each feature later.
this.bounds = this.map.getBounds() this.bounds = this.umap._leafletMap.getBounds()
const parent = DomUtil.get(this.datalayerId(datalayer)) const parent = DomUtil.get(this.datalayerId(datalayer))
// Panel is not open // Panel is not open
if (!parent) return if (!parent) return
@ -115,10 +115,10 @@ export default class Browser {
} }
onFormChange() { onFormChange() {
this.map.eachBrowsableDataLayer((datalayer) => { this.umap.eachBrowsableDataLayer((datalayer) => {
datalayer.resetLayer(true) datalayer.resetLayer(true)
this.updateDatalayer(datalayer) this.updateDatalayer(datalayer)
if (this.map.fullPanel?.isOpen()) datalayer.tableEdit() if (this.umap.fullPanel?.isOpen()) datalayer.tableEdit()
}) })
this.toggleBadge() this.toggleBadge()
} }
@ -132,13 +132,13 @@ export default class Browser {
} }
hasFilters() { hasFilters() {
return !!this.options.filter || this.map.facets.isActive() return !!this.options.filter || this.umap.facets.isActive()
} }
onMoveEnd() { onMoveEnd() {
if (!this.isOpen()) return if (!this.isOpen()) return
const isListDynamic = this.options.inBbox const isListDynamic = this.options.inBbox
this.map.eachBrowsableDataLayer((datalayer) => { this.umap.eachBrowsableDataLayer((datalayer) => {
if (!isListDynamic && !datalayer.hasDynamicData()) return if (!isListDynamic && !datalayer.hasDynamicData()) return
this.updateDatalayer(datalayer) this.updateDatalayer(datalayer)
}) })
@ -147,7 +147,7 @@ export default class Browser {
update() { update() {
if (!this.isOpen()) return if (!this.isOpen()) return
this.dataContainer.innerHTML = '' this.dataContainer.innerHTML = ''
this.map.eachBrowsableDataLayer((datalayer) => { this.umap.eachBrowsableDataLayer((datalayer) => {
this.addDataLayer(datalayer, this.dataContainer) this.addDataLayer(datalayer, this.dataContainer)
}) })
} }
@ -186,9 +186,9 @@ export default class Browser {
DomEvent.on(builder.form, 'reset', () => { DomEvent.on(builder.form, 'reset', () => {
window.setTimeout(builder.syncAll.bind(builder)) window.setTimeout(builder.syncAll.bind(builder))
}) })
if (this.map.options.facetKey) { if (this.umap.properties.facetKey) {
fields = this.map.facets.build() fields = this.umap.facets.build()
filtersBuilder = new L.FormBuilder(this.map.facets, fields, { filtersBuilder = new L.FormBuilder(this.umap.facets, fields, {
callback: () => this.onFormChange(), callback: () => this.onFormChange(),
}) })
DomEvent.on(filtersBuilder.form, 'reset', () => { DomEvent.on(filtersBuilder.form, 'reset', () => {
@ -206,7 +206,7 @@ export default class Browser {
textContent: translate('Reset all'), textContent: translate('Reset all'),
}) })
this.map.panel.open({ this.umap.panel.open({
content: container, content: container,
className: 'umap-browser', className: 'umap-browser',
}) })
@ -230,7 +230,7 @@ export default class Browser {
`) `)
container.appendChild(toolbox) container.appendChild(toolbox)
toggle.addEventListener('click', () => this.toggleLayers()) toggle.addEventListener('click', () => this.toggleLayers())
fitBounds.addEventListener('click', () => this.map.fitDataBounds()) fitBounds.addEventListener('click', () => this.umap.fitDataBounds())
download.addEventListener('click', () => this.downloadVisible(download)) download.addEventListener('click', () => this.downloadVisible(download))
} }
@ -240,7 +240,7 @@ export default class Browser {
for (const format of Object.keys(EXPORT_FORMATS)) { for (const format of Object.keys(EXPORT_FORMATS)) {
items.push({ items.push({
label: format, label: format,
action: () => this.map.share.download(format), action: () => this.umap.share.download(format),
}) })
} }
menu.openBelow(element, items) menu.openBelow(element, items)
@ -250,10 +250,10 @@ export default class Browser {
// If at least one layer is shown, hide it // If at least one layer is shown, hide it
// otherwise show all // otherwise show all
let allHidden = true let allHidden = true
this.map.eachBrowsableDataLayer((datalayer) => { this.umap.eachBrowsableDataLayer((datalayer) => {
if (datalayer.isVisible()) allHidden = false if (datalayer.isVisible()) allHidden = false
}) })
this.map.eachBrowsableDataLayer((datalayer) => { this.umap.eachBrowsableDataLayer((datalayer) => {
if (allHidden) { if (allHidden) {
datalayer.show() datalayer.show()
} else { } else {
@ -271,7 +271,7 @@ export default class Browser {
// Fixme: remove me when this is merged and released // Fixme: remove me when this is merged and released
// https://github.com/Leaflet/Leaflet/pull/9052 // https://github.com/Leaflet/Leaflet/pull/9052
DomEvent.disableClickPropagation(button) DomEvent.disableClickPropagation(button)
DomEvent.on(button, 'click', map.openBrowser, map) DomEvent.on(button, 'click', map.umap.openBrowser, map.umap)
return button return button
} }
} }

View file

@ -3,8 +3,8 @@ import { translate } from './i18n.js'
import * as Utils from './utils.js' import * as Utils from './utils.js'
export default class Caption { export default class Caption {
constructor(map) { constructor(umap) {
this.map = map this.umap = umap
} }
isOpen() { isOpen() {
@ -21,38 +21,36 @@ export default class Caption {
const hgroup = DomUtil.element({ tagName: 'hgroup', parent: container }) const hgroup = DomUtil.element({ tagName: 'hgroup', parent: container })
DomUtil.createTitle( DomUtil.createTitle(
hgroup, hgroup,
this.map.getDisplayName(), this.umap.getDisplayName(),
'icon-caption icon-block', 'icon-caption icon-block',
'map-name' 'map-name'
) )
this.map.addAuthorLink('h4', hgroup) const title = Utils.loadTemplate('<h4></h4>')
if (this.map.options.description) { hgroup.appendChild(title)
this.umap.addAuthorLink(title)
if (this.umap.properties.description) {
const description = DomUtil.element({ const description = DomUtil.element({
tagName: 'div', tagName: 'div',
className: 'umap-map-description text', className: 'umap-map-description text',
safeHTML: Utils.toHTML(this.map.options.description), safeHTML: Utils.toHTML(this.umap.properties.description),
parent: container, parent: container,
}) })
} }
const datalayerContainer = DomUtil.create('div', 'datalayer-container', container) const datalayerContainer = DomUtil.create('div', 'datalayer-container', container)
this.map.eachDataLayerReverse((datalayer) => this.umap.eachDataLayerReverse((datalayer) =>
this.addDataLayer(datalayer, datalayerContainer) this.addDataLayer(datalayer, datalayerContainer)
) )
const creditsContainer = DomUtil.create('div', 'credits-container', container) const creditsContainer = DomUtil.create('div', 'credits-container', container)
this.addCredits(creditsContainer) this.addCredits(creditsContainer)
this.map.panel.open({ content: container }).then(() => { this.umap.panel.open({ content: container }).then(() => {
// Create the legend when the panel is actually on the DOM // Create the legend when the panel is actually on the DOM
this.map.eachDataLayerReverse((datalayer) => datalayer.renderLegend()) this.umap.eachDataLayerReverse((datalayer) => datalayer.renderLegend())
}) })
} }
addDataLayer(datalayer, container) { addDataLayer(datalayer, container) {
if (!datalayer.options.inCaption) return if (!datalayer.options.inCaption) return
const p = DomUtil.create( const p = DomUtil.create('p', `caption-item ${datalayer.cssId}`, container)
'p',
`caption-item ${datalayer.cssId}`,
container
)
const legend = DomUtil.create('span', 'datalayer-legend', p) const legend = DomUtil.create('span', 'datalayer-legend', p)
const headline = DomUtil.create('strong', '', p) const headline = DomUtil.create('strong', '', p)
if (datalayer.options.description) { if (datalayer.options.description) {
@ -69,16 +67,16 @@ export default class Caption {
addCredits(container) { addCredits(container) {
const credits = DomUtil.createFieldset(container, translate('Credits')) const credits = DomUtil.createFieldset(container, translate('Credits'))
let title = DomUtil.add('h5', '', credits, translate('User content credits')) let title = DomUtil.add('h5', '', credits, translate('User content credits'))
if (this.map.options.shortCredit || this.map.options.longCredit) { if (this.umap.properties.shortCredit || this.umap.properties.longCredit) {
DomUtil.element({ DomUtil.element({
tagName: 'p', tagName: 'p',
parent: credits, parent: credits,
safeHTML: Utils.toHTML( safeHTML: Utils.toHTML(
this.map.options.longCredit || this.map.options.shortCredit this.umap.properties.longCredit || this.umap.properties.shortCredit
), ),
}) })
} }
if (this.map.options.licence) { if (this.umap.properties.licence) {
const licence = DomUtil.add( const licence = DomUtil.add(
'p', 'p',
'', '',
@ -88,8 +86,8 @@ export default class Caption {
DomUtil.createLink( DomUtil.createLink(
'', '',
licence, licence,
this.map.options.licence.name, this.umap.properties.licence.name,
this.map.options.licence.url this.umap.properties.licence.url
) )
} else { } else {
DomUtil.add('p', '', credits, translate('No licence has been set')) DomUtil.add('p', '', credits, translate('No licence has been set'))
@ -100,19 +98,19 @@ export default class Caption {
DomUtil.element({ DomUtil.element({
tagName: 'strong', tagName: 'strong',
parent: tilelayerCredit, parent: tilelayerCredit,
textContent: `${this.map.selected_tilelayer.options.name} `, textContent: `${this.umap._leafletMap.selectedTilelayer.options.name} `,
}) })
DomUtil.element({ DomUtil.element({
tagName: 'span', tagName: 'span',
parent: tilelayerCredit, parent: tilelayerCredit,
safeHTML: this.map.selected_tilelayer.getAttribution(), safeHTML: this.umap._leafletMap.selectedTilelayer.getAttribution(),
}) })
const urls = { const urls = {
leaflet: 'http://leafletjs.com', leaflet: 'http://leafletjs.com',
django: 'https://www.djangoproject.com', django: 'https://www.djangoproject.com',
umap: 'https://umap-project.org/', umap: 'https://umap-project.org/',
changelog: 'https://docs.umap-project.org/en/master/changelog/', changelog: 'https://docs.umap-project.org/en/master/changelog/',
version: this.map.options.umap_version, version: this.umap.properties.umap_version,
} }
const creditHTML = translate( const creditHTML = translate(
` `

View file

@ -19,7 +19,7 @@ import loadPopup from '../rendering/popup.js'
class Feature { class Feature {
constructor(datalayer, geojson = {}, id = null) { constructor(datalayer, geojson = {}, id = null) {
this.sync = datalayer.map.sync_engine.proxy(this) this.sync = datalayer.umap.sync_engine.proxy(this)
this._marked_for_deletion = false this._marked_for_deletion = false
this._isDirty = false this._isDirty = false
this._ui = null this._ui = null
@ -69,8 +69,8 @@ class Feature {
return this._ui return this._ui
} }
get map() { get umap() {
return this.datalayer?.map return this.datalayer?.umap
} }
get center() { get center() {
@ -168,7 +168,7 @@ class Feature {
} }
getSlug() { getSlug() {
return this.properties[this.map.getOption('slugKey') || 'name'] || '' return this.properties[this.umap.getOption('slugKey') || 'name'] || ''
} }
getPermalink() { getPermalink() {
@ -196,10 +196,10 @@ class Feature {
return return
} }
// TODO deal with an event instead? // TODO deal with an event instead?
if (this.map.slideshow) { if (this.umap.slideshow) {
this.map.slideshow.current = this this.umap.slideshow.current = this
} }
this.map.currentFeature = this this.umap.currentFeature = this
this.attachPopup() this.attachPopup()
this.ui.openPopup(latlng || this.center) this.ui.openPopup(latlng || this.center)
} }
@ -209,7 +209,7 @@ class Feature {
return field.startsWith('properties.') return field.startsWith('properties.')
}) })
if (impactData) { if (impactData) {
if (this.map.currentFeature === this) { if (this.umap.currentFeature === this) {
this.view() this.view()
} }
} }
@ -217,7 +217,7 @@ class Feature {
} }
edit(event) { edit(event) {
if (!this.map.editEnabled || this.isReadOnly()) return if (!this.umap.editEnabled || this.isReadOnly()) return
const container = DomUtil.create('div', 'umap-feature-container') const container = DomUtil.create('div', 'umap-feature-container')
DomUtil.createTitle( DomUtil.createTitle(
container, container,
@ -256,12 +256,12 @@ class Feature {
translate('Advanced actions') translate('Advanced actions')
) )
this.getAdvancedEditActions(advancedActions) this.getAdvancedEditActions(advancedActions)
const onLoad = this.map.editPanel.open({ content: container }) const onLoad = this.umap.editPanel.open({ content: container })
onLoad.then(() => { onLoad.then(() => {
builder.helpers['properties.name'].input.focus() builder.helpers['properties.name'].input.focus()
}) })
this.map.editedFeature = this this.umap.editedFeature = this
if (!this.ui.isOnScreen(this.map.getBounds())) this.zoomTo(event) if (!this.ui.isOnScreen(this.umap._leafletMap.getBounds())) this.zoomTo(event)
} }
getAdvancedEditActions(container) { getAdvancedEditActions(container) {
@ -270,7 +270,7 @@ class Feature {
<i class="icon icon-24 icon-delete"></i>${translate('Delete')} <i class="icon icon-24 icon-delete"></i>${translate('Delete')}
</button>`) </button>`)
button.addEventListener('click', () => { button.addEventListener('click', () => {
this.confirmDelete().then(() => this.map.editPanel.close()) this.confirmDelete().then(() => this.umap.editPanel.close())
}) })
container.appendChild(button) container.appendChild(button)
} }
@ -333,7 +333,7 @@ class Feature {
if (this.datalayer.isRemoteLayer() && this.datalayer.options.remoteData.dynamic) { if (this.datalayer.isRemoteLayer() && this.datalayer.options.remoteData.dynamic) {
return false return false
} }
return this.map.getOption('displayPopupFooter') return this.umap.getOption('displayPopupFooter')
} }
getPopupClass() { getPopupClass() {
@ -347,7 +347,7 @@ class Feature {
} }
async confirmDelete() { async confirmDelete() {
const confirmed = await this.map.dialog.confirm( const confirmed = await this.umap.dialog.confirm(
translate('Are you sure you want to delete the feature?') translate('Are you sure you want to delete the feature?')
) )
if (confirmed) { if (confirmed) {
@ -359,7 +359,7 @@ class Feature {
del(sync) { del(sync) {
this.isDirty = true this.isDirty = true
this.map.closePopup() this.umap._leafletMap.closePopup()
if (this.datalayer) { if (this.datalayer) {
this.datalayer.removeFeature(this, sync) this.datalayer.removeFeature(this, sync)
} }
@ -422,7 +422,7 @@ class Feature {
} else if (this.datalayer) { } else if (this.datalayer) {
value = this.datalayer.getOption(option, this) value = this.datalayer.getOption(option, this)
} else { } else {
value = this.map.getOption(option) value = this.umap.getOption(option)
} }
return value return value
} }
@ -432,19 +432,19 @@ class Feature {
// There is a variable inside. // There is a variable inside.
if (U.Utils.hasVar(value)) { if (U.Utils.hasVar(value)) {
value = U.Utils.greedyTemplate(value, this.properties, true) value = U.Utils.greedyTemplate(value, this.properties, true)
if (U.Utils.hasVar(value)) value = this.map.getDefaultOption(option) if (U.Utils.hasVar(value)) value = this.umap.getDefaultOption(option)
} }
return value return value
} }
zoomTo({ easing, latlng, callback } = {}) { zoomTo({ easing, latlng, callback } = {}) {
if (easing === undefined) easing = this.map.getOption('easing') if (easing === undefined) easing = this.umap.getOption('easing')
if (callback) this.map.once('moveend', callback.bind(this)) if (callback) this.umap._leafletMap.once('moveend', callback.bind(this))
if (easing) { if (easing) {
this.map.flyTo(this.center, this.getBestZoom()) this.umap._leafletMap.flyTo(this.center, this.getBestZoom())
} else { } else {
latlng = latlng || this.center latlng = latlng || this.center
this.map.setView(latlng, this.getBestZoom() || this.map.getZoom()) this.umap._leafletMap.setView(latlng, this.getBestZoom() || this.umap._leafletMap.getZoom())
} }
} }
@ -500,7 +500,7 @@ class Feature {
isFiltered() { isFiltered() {
const filterKeys = this.datalayer.getFilterKeys() const filterKeys = this.datalayer.getFilterKeys()
const filter = this.map.browser.options.filter const filter = this.umap.browser.options.filter
if (filter && !this.matchFilter(filter, filterKeys)) return true if (filter && !this.matchFilter(filter, filterKeys)) return true
if (!this.matchFacets()) return true if (!this.matchFacets()) return true
return false return false
@ -525,10 +525,10 @@ class Feature {
} }
matchFacets() { matchFacets() {
const selected = this.map.facets.selected const selected = this.umap.facets.selected
for (const [name, { type, min, max, choices }] of Object.entries(selected)) { for (const [name, { type, min, max, choices }] of Object.entries(selected)) {
let value = this.properties[name] let value = this.properties[name]
const parser = this.map.facets.getParser(type) const parser = this.umap.facets.getParser(type)
value = parser(value) value = parser(value)
switch (type) { switch (type) {
case 'date': case 'date':
@ -562,10 +562,10 @@ class Feature {
extendedProperties() { extendedProperties() {
// Include context properties // Include context properties
const properties = this.map.getGeoContext() const properties = this.umap.getGeoContext()
const locale = L.getLocale() const locale = L.getLocale()
if (locale) properties.locale = locale if (locale) properties.locale = locale
if (L.lang) properties.lang = L.lang if (U.lang) properties.lang = U.lang
properties.rank = this.getRank() + 1 properties.rank = this.getRank() + 1
properties.layer = this.datalayer.getName() properties.layer = this.datalayer.getName()
if (this.ui._map && this.hasGeom()) { if (this.ui._map && this.hasGeom()) {
@ -612,10 +612,10 @@ class Feature {
label: translate('Copy as GeoJSON'), label: translate('Copy as GeoJSON'),
action: () => { action: () => {
L.Util.copyToClipboard(JSON.stringify(this.toGeoJSON())) L.Util.copyToClipboard(JSON.stringify(this.toGeoJSON()))
this.map.tooltip.open({ content: L._('✅ Copied!') }) this.umap.tooltip.open({ content: L._('✅ Copied!') })
}, },
}) })
if (this.map.editEnabled && !this.isReadOnly()) { if (this.umap.editEnabled && !this.isReadOnly()) {
items = items.concat(this.getContextMenuEditItems(event)) items = items.concat(this.getContextMenuEditItems(event))
} }
return items return items
@ -623,7 +623,7 @@ class Feature {
getContextMenuEditItems() { getContextMenuEditItems() {
let items = ['-'] let items = ['-']
if (this.map.editedFeature !== this) { if (this.umap.editedFeature !== this) {
items.push({ items.push({
label: `${translate('Edit this feature')} (⇧+Click)`, label: `${translate('Edit this feature')} (⇧+Click)`,
action: () => this.edit(), action: () => this.edit(),
@ -631,7 +631,7 @@ class Feature {
} }
items = items.concat( items = items.concat(
{ {
label: this.map.help.displayLabel('EDIT_FEATURE_LAYER'), label: this.umap.help.displayLabel('EDIT_FEATURE_LAYER'),
action: () => this.datalayer.edit(), action: () => this.datalayer.edit(),
}, },
{ {
@ -750,17 +750,17 @@ class Path extends Feature {
} }
edit(event) { edit(event) {
if (this.map.editEnabled) { if (this.umap.editEnabled) {
super.edit(event) super.edit(event)
if (!this.ui.editEnabled()) this.ui.makeGeometryEditable() if (!this.ui.editEnabled()) this.ui.makeGeometryEditable()
} }
} }
_toggleEditing(event) { _toggleEditing(event) {
if (this.map.editEnabled) { if (this.umap.editEnabled) {
if (this.ui.editEnabled()) { if (this.ui.editEnabled()) {
this.endEdit() this.endEdit()
this.map.editPanel.close() this.umap.editPanel.close()
} else { } else {
this.edit(event) this.edit(event)
} }
@ -786,7 +786,9 @@ class Path extends Feature {
} }
getBestZoom() { getBestZoom() {
return this.getOption('zoomTo') || this.map.getBoundsZoom(this.bounds, true) return (
this.getOption('zoomTo') || this.umap._leafletMap.getBoundsZoom(this.bounds, true)
)
} }
endEdit() { endEdit() {
@ -825,11 +827,14 @@ class Path extends Feature {
zoomTo({ easing, callback }) { zoomTo({ easing, callback }) {
// Use bounds instead of centroid for paths. // Use bounds instead of centroid for paths.
easing = easing || this.map.getOption('easing') easing = easing || this.umap.getOption('easing')
if (easing) { if (easing) {
this.map.flyToBounds(this.bounds, this.getBestZoom()) this.umap._leafletMap.flyToBounds(this.bounds, this.getBestZoom())
} else { } else {
this.map.fitBounds(this.bounds, this.getBestZoom() || this.map.getZoom()) this.umap._leafletMap.fitBounds(
this.bounds,
this.getBestZoom() || this.umap._leafletMap.getZoom()
)
} }
if (callback) callback.call(this) if (callback) callback.call(this)
} }
@ -840,7 +845,7 @@ class Path extends Feature {
label: translate('Display measure'), label: translate('Display measure'),
action: () => Alert.info(this.ui.getMeasure()), action: () => Alert.info(this.ui.getMeasure()),
}) })
if (this.map.editEnabled && !this.isReadOnly() && this.isMulti()) { if (this.umap.editEnabled && !this.isReadOnly() && this.isMulti()) {
items.push(...this.getContextMenuMultiItems(event)) items.push(...this.getContextMenuMultiItems(event))
} }
return items return items
@ -871,11 +876,11 @@ class Path extends Feature {
getContextMenuEditItems(event) { getContextMenuEditItems(event) {
const items = super.getContextMenuEditItems(event) const items = super.getContextMenuEditItems(event)
if (this.map?.editedFeature !== this && this.isSameClass(this.map.editedFeature)) { if (this.map?.editedFeature !== this && this.isSameClass(this.umap.editedFeature)) {
items.push({ items.push({
label: translate('Transfer shape to edited feature'), label: translate('Transfer shape to edited feature'),
action: () => { action: () => {
this.transferShape(event.latlng, this.map.editedFeature) this.transferShape(event.latlng, this.umap.editedFeature)
}, },
}) })
} }
@ -977,8 +982,8 @@ export class LineString extends Path {
} }
const a = toMerge[0] const a = toMerge[0]
const b = toMerge[1] const b = toMerge[1]
const p1 = this.map.latLngToContainerPoint(a[a.length - 1]) const p1 = this.umap._leafletMap.latLngToContainerPoint(a[a.length - 1])
const p2 = this.map.latLngToContainerPoint(b[0]) const p2 = this.umap._leafletMap.latLngToContainerPoint(b[0])
const tolerance = 5 // px on screen const tolerance = 5 // px on screen
if (Math.abs(p1.x - p2.x) <= tolerance && Math.abs(p1.y - p2.y) <= tolerance) { if (Math.abs(p1.x - p2.x) <= tolerance && Math.abs(p1.y - p2.y) <= tolerance) {
a.pop() a.pop()
@ -1022,7 +1027,7 @@ export class LineString extends Path {
}) })
} else if (index === 0 || index === event.vertex.getLastIndex()) { } else if (index === 0 || index === event.vertex.getLastIndex()) {
items.push({ items.push({
label: this.map.help.displayLabel('CONTINUE_LINE'), label: this.umap.help.displayLabel('CONTINUE_LINE'),
action: () => event.vertex.continue(), action: () => event.vertex.continue(),
}) })
} }

View file

@ -37,10 +37,10 @@ const LAYER_MAP = LAYER_TYPES.reduce((acc, klass) => {
}, {}) }, {})
export class DataLayer extends ServerStored { export class DataLayer extends ServerStored {
constructor(map, data) { constructor(umap, data) {
super() super()
this.map = map this.umap = umap
this.sync = map.sync_engine.proxy(this) this.sync = umap.sync_engine.proxy(this)
this._index = Array() this._index = Array()
this._features = {} this._features = {}
this._geojson = null this._geojson = null
@ -48,8 +48,11 @@ export class DataLayer extends ServerStored {
this._loaded = false // Are layer metadata loaded this._loaded = false // Are layer metadata loaded
this._dataloaded = false // Are layer data loaded this._dataloaded = false // Are layer data loaded
this.parentPane = this.map.getPane('overlayPane') this.parentPane = this.umap._leafletMap.getPane('overlayPane')
this.pane = this.map.createPane(`datalayer${stamp(this)}`, this.parentPane) this.pane = this.umap._leafletMap.createPane(
`datalayer${stamp(this)}`,
this.parentPane
)
this.pane.dataset.id = stamp(this) this.pane.dataset.id = stamp(this)
// FIXME: should be on layer // FIXME: should be on layer
this.renderer = L.svg({ pane: this.pane }) this.renderer = L.svg({ pane: this.pane })
@ -128,7 +131,7 @@ export class DataLayer extends ServerStored {
for (const impact of impacts) { for (const impact of impacts) {
switch (impact) { switch (impact) {
case 'ui': case 'ui':
this.map.onDataLayersChanged() this.umap.onDataLayersChanged()
break break
case 'data': case 'data':
if (fields.includes('options.type')) { if (fields.includes('options.type')) {
@ -153,8 +156,8 @@ export class DataLayer extends ServerStored {
} }
autoLoaded() { autoLoaded() {
if (!this.map.datalayersFromQueryString) return this.options.displayOnLoad if (!this.umap.datalayersFromQueryString) return this.options.displayOnLoad
const datalayerIds = this.map.datalayersFromQueryString const datalayerIds = this.umap.datalayersFromQueryString
let loadMe = datalayerIds.includes(this.umap_id.toString()) let loadMe = datalayerIds.includes(this.umap_id.toString())
if (this.options.old_id) { if (this.options.old_id) {
loadMe = loadMe || datalayerIds.includes(this.options.old_id.toString()) loadMe = loadMe || datalayerIds.includes(this.options.old_id.toString())
@ -192,7 +195,7 @@ export class DataLayer extends ServerStored {
const visible = this.isVisible() const visible = this.isVisible()
if (this.layer) this.layer.clearLayers() if (this.layer) this.layer.clearLayers()
// delete this.layer? // delete this.layer?
if (visible) this.map.removeLayer(this.layer) if (visible) this.umap._leafletMap.removeLayer(this.layer)
const Class = LAYER_MAP[this.options.type] || DefaultLayer const Class = LAYER_MAP[this.options.type] || DefaultLayer
this.layer = new Class(this) this.layer = new Class(this)
// Rendering layer changed, so let's force reset the feature rendering too. // Rendering layer changed, so let's force reset the feature rendering too.
@ -213,7 +216,7 @@ export class DataLayer extends ServerStored {
if (!this.umap_id) return if (!this.umap_id) return
if (this._loading) return if (this._loading) return
this._loading = true this._loading = true
const [geojson, response, error] = await this.map.server.get(this._dataUrl()) const [geojson, response, error] = await this.umap.server.get(this._dataUrl())
if (!error) { if (!error) {
this._reference_version = response.headers.get('X-Datalayer-Version') this._reference_version = response.headers.get('X-Datalayer-Version')
// FIXME: for now this property is set dynamically from backend // FIXME: for now this property is set dynamically from backend
@ -234,7 +237,7 @@ export class DataLayer extends ServerStored {
dataChanged() { dataChanged() {
if (!this.hasDataLoaded()) return if (!this.hasDataLoaded()) return
this.map.onDataLayersChanged() this.umap.onDataLayersChanged()
this.layer.dataChanged() this.layer.dataChanged()
} }
@ -275,14 +278,14 @@ export class DataLayer extends ServerStored {
reindex() { reindex() {
const features = Object.values(this._features) const features = Object.values(this._features)
Utils.sortFeatures(features, this.map.getOption('sortKey'), L.lang) Utils.sortFeatures(features, this.umap.getOption('sortKey'), U.lang)
this._index = features.map((feature) => stamp(feature)) this._index = features.map((feature) => stamp(feature))
} }
showAtZoom() { showAtZoom() {
const from = Number.parseInt(this.options.fromZoom, 10) const from = Number.parseInt(this.options.fromZoom, 10)
const to = Number.parseInt(this.options.toZoom, 10) const to = Number.parseInt(this.options.toZoom, 10)
const zoom = this.map.getZoom() const zoom = this.umap._leafletMap.getZoom()
return !((!Number.isNaN(from) && zoom < from) || (!Number.isNaN(to) && zoom > to)) return !((!Number.isNaN(from) && zoom < from) || (!Number.isNaN(to) && zoom > to))
} }
@ -294,14 +297,14 @@ export class DataLayer extends ServerStored {
if (!this.isRemoteLayer()) return if (!this.isRemoteLayer()) return
if (!this.hasDynamicData() && this.hasDataLoaded() && !force) return if (!this.hasDynamicData() && this.hasDataLoaded() && !force) return
if (!this.isVisible()) return if (!this.isVisible()) return
let url = this.map.localizeUrl(this.options.remoteData.url) let url = this.umap.localizeUrl(this.options.remoteData.url)
if (this.options.remoteData.proxy) { if (this.options.remoteData.proxy) {
url = this.map.proxyUrl(url, this.options.remoteData.ttl) url = this.umap.proxyUrl(url, this.options.remoteData.ttl)
} }
const response = await this.map.request.get(url) const response = await this.umap.request.get(url)
if (response?.ok) { if (response?.ok) {
this.clear() this.clear()
this.map.formatter this.umap.formatter
.parse(await response.text(), this.options.remoteData.format) .parse(await response.text(), this.options.remoteData.format)
.then((geojson) => this.fromGeoJSON(geojson)) .then((geojson) => this.fromGeoJSON(geojson))
} }
@ -341,25 +344,23 @@ export class DataLayer extends ServerStored {
connectToMap() { connectToMap() {
const id = stamp(this) const id = stamp(this)
if (!this.map.datalayers[id]) { if (!this.umap.datalayers[id]) {
this.map.datalayers[id] = this this.umap.datalayers[id] = this
} }
if (!this.map.datalayers_index.includes(this)) { if (!this.umap.datalayersIndex.includes(this)) {
this.map.datalayers_index.push(this) this.umap.datalayersIndex.push(this)
} }
this.map.onDataLayersChanged() this.umap.onDataLayersChanged()
} }
_dataUrl() { _dataUrl() {
const template = this.map.options.urls.datalayer_view let url = this.umap.urls.get('datalayer_view', {
let url = Utils.template(template, {
pk: this.umap_id, pk: this.umap_id,
map_id: this.map.options.umap_id, map_id: this.umap.properties.umap_id,
}) })
// No browser cache for owners/editors. // No browser cache for owners/editors.
if (this.map.hasEditMode()) url = `${url}?${Date.now()}` if (this.umap.hasEditMode()) url = `${url}?${Date.now()}`
return url return url
} }
@ -386,7 +387,7 @@ export class DataLayer extends ServerStored {
this._index.push(id) this._index.push(id)
this._features[id] = feature this._features[id] = feature
this.indexProperties(feature) this.indexProperties(feature)
this.map.features_index[feature.getSlug()] = feature this.umap.featuresIndex[feature.getSlug()] = feature
this.showFeature(feature) this.showFeature(feature)
this.dataChanged() this.dataChanged()
} }
@ -395,7 +396,7 @@ export class DataLayer extends ServerStored {
const id = stamp(feature) const id = stamp(feature)
if (sync !== false) feature.sync.delete() if (sync !== false) feature.sync.delete()
this.hideFeature(feature) this.hideFeature(feature)
delete this.map.features_index[feature.getSlug()] delete this.umap.featuresIndex[feature.getSlug()]
feature.disconnectFromDataLayer(this) feature.disconnectFromDataLayer(this)
this._index.splice(this._index.indexOf(id), 1) this._index.splice(this._index.indexOf(id), 1)
delete this._features[id] delete this._features[id]
@ -446,7 +447,8 @@ export class DataLayer extends ServerStored {
const collection = Array.isArray(geojson) const collection = Array.isArray(geojson)
? geojson ? geojson
: geojson.features || geojson.geometries : geojson.features || geojson.geometries
Utils.sortFeatures(collection, this.map.getOption('sortKey'), L.lang) if (!collection) return
Utils.sortFeatures(collection, this.umap.getOption('sortKey'), U.lang)
for (const feature of collection) { for (const feature of collection) {
this.makeFeature(feature, sync) this.makeFeature(feature, sync)
} }
@ -486,7 +488,7 @@ export class DataLayer extends ServerStored {
} }
async importRaw(raw, format) { async importRaw(raw, format) {
this.map.formatter this.umap.formatter
.parse(raw, format) .parse(raw, format)
.then((geojson) => this.addData(geojson)) .then((geojson) => this.addData(geojson))
.then(() => this.zoomTo()) .then(() => this.zoomTo())
@ -507,35 +509,35 @@ export class DataLayer extends ServerStored {
} }
async importFromUrl(uri, type) { async importFromUrl(uri, type) {
uri = this.map.localizeUrl(uri) uri = this.umap.localizeUrl(uri)
const response = await this.map.request.get(uri) const response = await this.umap.request.get(uri)
if (response?.ok) { if (response?.ok) {
this.importRaw(await response.text(), type) this.importRaw(await response.text(), type)
} }
} }
getColor() { getColor() {
return this.options.color || this.map.getOption('color') return this.options.color || this.umap.getOption('color')
} }
getDeleteUrl() { getDeleteUrl() {
return Utils.template(this.map.options.urls.datalayer_delete, { return this.umap.urls.get('datalayer_delete', {
pk: this.umap_id, pk: this.umap_id,
map_id: this.map.options.umap_id, map_id: this.umap.properties.umap_id,
}) })
} }
getVersionsUrl() { getVersionsUrl() {
return Utils.template(this.map.options.urls.datalayer_versions, { return this.umap.urls.get('datalayer_versions', {
pk: this.umap_id, pk: this.umap_id,
map_id: this.map.options.umap_id, map_id: this.umap.properties.umap_id,
}) })
} }
getVersionUrl(name) { getVersionUrl(name) {
return Utils.template(this.map.options.urls.datalayer_version, { return this.umap.urls.get('datalayer_version', {
pk: this.umap_id, pk: this.umap_id,
map_id: this.map.options.umap_id, map_id: this.umap.properties.umap_id,
name: name, name: name,
}) })
} }
@ -556,17 +558,17 @@ export class DataLayer extends ServerStored {
options.name = translate('Clone of {name}', { name: this.options.name }) options.name = translate('Clone of {name}', { name: this.options.name })
delete options.id delete options.id
const geojson = Utils.CopyJSON(this._geojson) const geojson = Utils.CopyJSON(this._geojson)
const datalayer = this.map.createDataLayer(options) const datalayer = this.umap.createDataLayer(options)
datalayer.fromGeoJSON(geojson) datalayer.fromGeoJSON(geojson)
return datalayer return datalayer
} }
erase() { erase() {
this.hide() this.hide()
this.map.datalayers_index.splice(this.getRank(), 1) this.umap.datalayersIndex.splice(this.getRank(), 1)
this.parentPane.removeChild(this.pane) this.parentPane.removeChild(this.pane)
this.map.onDataLayersChanged() this.umap.onDataLayersChanged()
this.layer.onDelete(this.map) this.layer.onDelete(this.umap._leafletMap)
this.propagateDelete() this.propagateDelete()
this._leaflet_events_bk = this._leaflet_events this._leaflet_events_bk = this._leaflet_events
this.clear() this.clear()
@ -597,7 +599,7 @@ export class DataLayer extends ServerStored {
} }
edit() { edit() {
if (!this.map.editEnabled || !this.isLoaded()) { if (!this.umap.editEnabled || !this.isLoaded()) {
return return
} }
const container = DomUtil.create('div', 'umap-layer-properties-container') const container = DomUtil.create('div', 'umap-layer-properties-container')
@ -631,7 +633,7 @@ export class DataLayer extends ServerStored {
DomUtil.createTitle(container, translate('Layer properties'), 'icon-layers') DomUtil.createTitle(container, translate('Layer properties'), 'icon-layers')
let builder = new U.FormBuilder(this, metadataFields, { let builder = new U.FormBuilder(this, metadataFields, {
callback(e) { callback(e) {
this.map.onDataLayersChanged() this.umap.onDataLayersChanged()
if (e.helper.field === 'options.type') { if (e.helper.field === 'options.type') {
this.edit() this.edit()
} }
@ -742,7 +744,7 @@ export class DataLayer extends ServerStored {
}, },
], ],
] ]
if (this.map.options.urls.ajax_proxy) { if (this.umap.properties.urls.ajax_proxy) {
remoteDataFields.push([ remoteDataFields.push([
'options.remoteData.proxy', 'options.remoteData.proxy',
{ {
@ -768,7 +770,8 @@ export class DataLayer extends ServerStored {
this this
) )
if (this.map.options.urls.datalayer_versions) this.buildVersionsFieldset(container) if (this.umap.properties.urls.datalayer_versions)
this.buildVersionsFieldset(container)
const advancedActions = DomUtil.createFieldset( const advancedActions = DomUtil.createFieldset(
container, container,
@ -781,7 +784,7 @@ export class DataLayer extends ServerStored {
</button>`) </button>`)
deleteButton.addEventListener('click', () => { deleteButton.addEventListener('click', () => {
this._delete() this._delete()
this.map.editPanel.close() this.umap.editPanel.close()
}) })
advancedButtons.appendChild(deleteButton) advancedButtons.appendChild(deleteButton)
@ -820,9 +823,9 @@ export class DataLayer extends ServerStored {
// Fixme: remove me when this is merged and released // Fixme: remove me when this is merged and released
// https://github.com/Leaflet/Leaflet/pull/9052 // https://github.com/Leaflet/Leaflet/pull/9052
DomEvent.disableClickPropagation(backButton) DomEvent.disableClickPropagation(backButton)
DomEvent.on(backButton, 'click', this.map.editDatalayers, this.map) DomEvent.on(backButton, 'click', this.umap.editDatalayers, this.umap)
this.map.editPanel.open({ this.umap.editPanel.open({
content: container, content: container,
actions: [backButton], actions: [backButton],
}) })
@ -843,13 +846,13 @@ export class DataLayer extends ServerStored {
if (this.layer?.defaults?.[option]) { if (this.layer?.defaults?.[option]) {
return this.layer.defaults[option] return this.layer.defaults[option]
} }
return this.map.getOption(option, feature) return this.umap.getOption(option, feature)
} }
async buildVersionsFieldset(container) { async buildVersionsFieldset(container) {
const appendVersion = (data) => { const appendVersion = (data) => {
const date = new Date(Number.parseInt(data.at, 10)) const date = new Date(Number.parseInt(data.at, 10))
const content = `${date.toLocaleString(L.lang)} (${Number.parseInt(data.size) / 1000}Kb)` const content = `${date.toLocaleString(U.lang)} (${Number.parseInt(data.size) / 1000}Kb)`
const el = DomUtil.create('div', 'umap-datalayer-version', versionsContainer) const el = DomUtil.create('div', 'umap-datalayer-version', versionsContainer)
const button = DomUtil.createButton( const button = DomUtil.createButton(
'', '',
@ -864,7 +867,7 @@ export class DataLayer extends ServerStored {
const versionsContainer = DomUtil.createFieldset(container, translate('Versions'), { const versionsContainer = DomUtil.createFieldset(container, translate('Versions'), {
async callback() { async callback() {
const [{ versions }, response, error] = await this.map.server.get( const [{ versions }, response, error] = await this.umap.server.get(
this.getVersionsUrl() this.getVersionsUrl()
) )
if (!error) versions.forEach(appendVersion) if (!error) versions.forEach(appendVersion)
@ -874,11 +877,11 @@ export class DataLayer extends ServerStored {
} }
async restore(version) { async restore(version) {
if (!this.map.editEnabled) return if (!this.umap.editEnabled) return
this.map.dialog this.umap.dialog
.confirm(translate('Are you sure you want to restore this version?')) .confirm(translate('Are you sure you want to restore this version?'))
.then(async () => { .then(async () => {
const [geojson, response, error] = await this.map.server.get( const [geojson, response, error] = await this.umap.server.get(
this.getVersionUrl(version) this.getVersionUrl(version)
) )
if (!error) { if (!error) {
@ -899,13 +902,13 @@ export class DataLayer extends ServerStored {
} }
async show() { async show() {
this.map.addLayer(this.layer) this.umap._leafletMap.addLayer(this.layer)
if (!this.isLoaded()) await this.fetchData() if (!this.isLoaded()) await this.fetchData()
this.propagateShow() this.propagateShow()
} }
hide() { hide() {
this.map.removeLayer(this.layer) this.umap._leafletMap.removeLayer(this.layer)
this.propagateHide() this.propagateHide()
} }
@ -922,7 +925,7 @@ export class DataLayer extends ServerStored {
const bounds = this.layer.getBounds() const bounds = this.layer.getBounds()
if (bounds.isValid()) { if (bounds.isValid()) {
const options = { maxZoom: this.getOption('zoomTo') } const options = { maxZoom: this.getOption('zoomTo') }
this.map.fitBounds(bounds, options) this.umap._leafletMap.fitBounds(bounds, options)
} }
} }
@ -953,7 +956,7 @@ export class DataLayer extends ServerStored {
} }
isVisible() { isVisible() {
return Boolean(this.layer && this.map.hasLayer(this.layer)) return Boolean(this.layer && this.umap._leafletMap.hasLayer(this.layer))
} }
getFeatureByIndex(index) { getFeatureByIndex(index) {
@ -990,7 +993,7 @@ export class DataLayer extends ServerStored {
getPreviousBrowsable() { getPreviousBrowsable() {
let id = this.getRank() let id = this.getRank()
let next let next
const index = this.map.datalayers_index const index = this.umap.datalayersIndex
while (((id = index[++id] ? id : 0), (next = index[id]))) { while (((id = index[++id] ? id : 0), (next = index[id]))) {
if (next === this || next.canBrowse()) break if (next === this || next.canBrowse()) break
} }
@ -1000,7 +1003,7 @@ export class DataLayer extends ServerStored {
getNextBrowsable() { getNextBrowsable() {
let id = this.getRank() let id = this.getRank()
let prev let prev
const index = this.map.datalayers_index const index = this.umap.datalayersIndex
while (((id = index[--id] ? id : index.length - 1), (prev = index[id]))) { while (((id = index[--id] ? id : index.length - 1), (prev = index[id]))) {
if (prev === this || prev.canBrowse()) break if (prev === this || prev.canBrowse()) break
} }
@ -1016,7 +1019,7 @@ export class DataLayer extends ServerStored {
} }
getRank() { getRank() {
return this.map.datalayers_index.indexOf(this) return this.umap.datalayersIndex.indexOf(this)
} }
isReadOnly() { isReadOnly() {
@ -1043,8 +1046,8 @@ export class DataLayer extends ServerStored {
// Filename support is shaky, don't do it for now. // Filename support is shaky, don't do it for now.
const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' }) const blob = new Blob([JSON.stringify(geojson)], { type: 'application/json' })
formData.append('geojson', blob) formData.append('geojson', blob)
const saveUrl = this.map.urls.get('datalayer_save', { const saveUrl = this.umap.urls.get('datalayer_save', {
map_id: this.map.options.umap_id, map_id: this.umap.properties.umap_id,
pk: this.umap_id, pk: this.umap_id,
}) })
const headers = this._reference_version const headers = this._reference_version
@ -1056,7 +1059,7 @@ export class DataLayer extends ServerStored {
} }
async _trySave(url, headers, formData) { async _trySave(url, headers, formData) {
const [data, response, error] = await this.map.server.post(url, headers, formData) const [data, response, error] = await this.umap.server.post(url, headers, formData)
if (error) { if (error) {
if (response && response.status === 412) { if (response && response.status === 412) {
AlertConflict.error( AlertConflict.error(
@ -1072,7 +1075,7 @@ export class DataLayer extends ServerStored {
// Call the main save, in case something else needs to be saved // Call the main save, in case something else needs to be saved
// as the conflict stopped the saving flow // as the conflict stopped the saving flow
await this.map.saveAll() await this.umap.saveAll()
} }
} }
) )
@ -1101,9 +1104,9 @@ export class DataLayer extends ServerStored {
async saveDelete() { async saveDelete() {
if (this.umap_id) { if (this.umap_id) {
await this.map.server.post(this.getDeleteUrl()) await this.umap.server.post(this.getDeleteUrl())
} }
delete this.map.datalayers[stamp(this)] delete this.umap.datalayers[stamp(this)]
return true return true
} }
@ -1125,9 +1128,9 @@ export class DataLayer extends ServerStored {
// This keys will be used to filter feature from the browser text input. // This keys will be used to filter feature from the browser text input.
// By default, it will we use the "name" property, which is also the one used as label in the features list. // By default, it will we use the "name" property, which is also the one used as label in the features list.
// When map owner has configured another label or sort key, we try to be smart and search in the same keys. // When map owner has configured another label or sort key, we try to be smart and search in the same keys.
if (this.map.options.filterKey) return this.map.options.filterKey if (this.umap.properties.filterKey) return this.umap.properties.filterKey
if (this.getOption('labelKey')) return this.getOption('labelKey') if (this.getOption('labelKey')) return this.getOption('labelKey')
if (this.map.options.sortKey) return this.map.options.sortKey if (this.umap.properties.sortKey) return this.umap.properties.sortKey
return 'displayName' return 'displayName'
} }
@ -1178,7 +1181,7 @@ export class DataLayer extends ServerStored {
'click', 'click',
function () { function () {
if (!this.isVisible()) return if (!this.isVisible()) return
this.map.dialog this.umap.dialog
.confirm(translate('Are you sure you want to delete this layer?')) .confirm(translate('Are you sure you want to delete this layer?'))
.then(() => { .then(() => {
this._delete() this._delete()

View file

@ -3,8 +3,8 @@ import { translate } from './i18n.js'
import * as Utils from './utils.js' import * as Utils from './utils.js'
export default class Facets { export default class Facets {
constructor(map) { constructor(umap) {
this.map = map this.umap = umap
this.selected = {} this.selected = {}
} }
@ -24,7 +24,7 @@ export default class Facets {
this.selected[name] = selected this.selected[name] = selected
} }
this.map.eachBrowsableDataLayer((datalayer) => { this.umap.eachBrowsableDataLayer((datalayer) => {
datalayer.eachFeature((feature) => { datalayer.eachFeature((feature) => {
for (const name of names) { for (const name of names) {
let value = feature.properties[name] let value = feature.properties[name]
@ -108,8 +108,8 @@ export default class Facets {
const defaultType = 'checkbox' const defaultType = 'checkbox'
const allowedTypes = [defaultType, 'radio', 'number', 'date', 'datetime'] const allowedTypes = [defaultType, 'radio', 'number', 'date', 'datetime']
const defined = new Map() const defined = new Map()
if (!this.map.options.facetKey) return defined if (!this.umap.properties.facetKey) return defined
return (this.map.options.facetKey || '').split(',').reduce((acc, curr) => { return (this.umap.properties.facetKey || '').split(',').reduce((acc, curr) => {
let [name, label, type] = curr.split('|') let [name, label, type] = curr.split('|')
type = allowedTypes.includes(type) ? type : defaultType type = allowedTypes.includes(type) ? type : defaultType
acc.set(name, { label: label || name, type: type }) acc.set(name, { label: label || name, type: type })
@ -146,15 +146,15 @@ export default class Facets {
const defined = this.getDefined() const defined = this.getDefined()
if (!defined.has(property)) { if (!defined.has(property)) {
defined.set(property, { label, type }) defined.set(property, { label, type })
this.map.options.facetKey = this.dumps(defined) this.umap.properties.facetKey = this.dumps(defined)
this.map.isDirty = true this.umap.isDirty = true
} }
} }
remove(property) { remove(property) {
const defined = this.getDefined() const defined = this.getDefined()
defined.delete(property) defined.delete(property)
this.map.options.facetKey = this.dumps(defined) this.umap.properties.facetKey = this.dumps(defined)
this.map.isDirty = true this.umap.isDirty = true
} }
} }

View file

@ -3,24 +3,24 @@ import { translate } from './i18n.js'
export const EXPORT_FORMATS = { export const EXPORT_FORMATS = {
geojson: { geojson: {
formatter: async (map) => JSON.stringify(map.toGeoJSON(), null, 2), formatter: async (umap) => JSON.stringify(umap.toGeoJSON(), null, 2),
ext: '.geojson', ext: '.geojson',
filetype: 'application/json', filetype: 'application/json',
}, },
gpx: { gpx: {
formatter: async (map) => await map.formatter.toGPX(map.toGeoJSON()), formatter: async (umap) => await umap.formatter.toGPX(umap.toGeoJSON()),
ext: '.gpx', ext: '.gpx',
filetype: 'application/gpx+xml', filetype: 'application/gpx+xml',
}, },
kml: { kml: {
formatter: async (map) => await map.formatter.toKML(map.toGeoJSON()), formatter: async (umap) => await umap.formatter.toKML(umap.toGeoJSON()),
ext: '.kml', ext: '.kml',
filetype: 'application/vnd.google-earth.kml+xml', filetype: 'application/vnd.google-earth.kml+xml',
}, },
csv: { csv: {
formatter: async (map) => { formatter: async (umap) => {
const table = [] const table = []
map.eachFeature((feature) => { umap.eachFeature((feature) => {
const row = feature.toGeoJSON().properties const row = feature.toGeoJSON().properties
const center = feature.center const center = feature.center
delete row._umap_options delete row._umap_options

View file

@ -48,8 +48,8 @@ const TEMPLATE = `
` `
export default class Importer { export default class Importer {
constructor(map) { constructor(umap) {
this.map = map this.umap = umap
this.TYPES = ['geojson', 'csv', 'gpx', 'kml', 'osm', 'georss', 'umap'] this.TYPES = ['geojson', 'csv', 'gpx', 'kml', 'osm', 'georss', 'umap']
this.IMPORTERS = [] this.IMPORTERS = []
this.loadImporters() this.loadImporters()
@ -57,9 +57,9 @@ export default class Importer {
} }
loadImporters() { loadImporters() {
for (const [name, config] of Object.entries(this.map.options.importers || {})) { for (const [name, config] of Object.entries(this.umap.properties.importers || {})) {
const register = (mod) => { const register = (mod) => {
this.IMPORTERS.push(new mod.Importer(this.map, config)) this.IMPORTERS.push(new mod.Importer(this.umap, config))
} }
// We need to have explicit static paths for Django's collectstatic with hashes. // We need to have explicit static paths for Django's collectstatic with hashes.
switch (name) { switch (name) {
@ -139,8 +139,8 @@ export default class Importer {
get layer() { get layer() {
return ( return (
this.map.datalayers[this.layerId] || this.umap.datalayers[this.layerId] ||
this.map.createDataLayer({ name: this.layerName }) this.umap.createDataLayer({ name: this.layerName })
) )
} }
@ -167,7 +167,7 @@ export default class Importer {
textContent: type, textContent: type,
}) })
} }
this.map.help.parse(this.container) this.umap.help.parse(this.container)
DomEvent.on(this.qs('[name=submit]'), 'click', this.submit, this) DomEvent.on(this.qs('[name=submit]'), 'click', this.submit, this)
DomEvent.on(this.qs('[type=file]'), 'change', this.onFileChange, this) DomEvent.on(this.qs('[type=file]'), 'change', this.onFileChange, this)
for (const element of this.container.querySelectorAll('[onchange]')) { for (const element of this.container.querySelectorAll('[onchange]')) {
@ -206,7 +206,7 @@ export default class Importer {
this.layerName = null this.layerName = null
const layerSelect = this.qs('[name="layer-id"]') const layerSelect = this.qs('[name="layer-id"]')
layerSelect.innerHTML = '' layerSelect.innerHTML = ''
this.map.eachDataLayerReverse((datalayer) => { this.umap.eachDataLayerReverse((datalayer) => {
if (datalayer.isLoaded() && !datalayer.isRemoteLayer()) { if (datalayer.isLoaded() && !datalayer.isRemoteLayer()) {
DomUtil.element({ DomUtil.element({
tagName: 'option', tagName: 'option',
@ -227,7 +227,7 @@ export default class Importer {
open() { open() {
if (!this.container) this.build() if (!this.container) this.build()
const onLoad = this.map.editPanel.open({ content: this.container }) const onLoad = this.umap.editPanel.open({ content: this.container })
onLoad.then(() => this.onLoad()) onLoad.then(() => this.onLoad())
} }
@ -251,16 +251,16 @@ export default class Importer {
} }
full() { full() {
this.map.once('postsync', this.map._setDefaultCenter) this.umap._leafletMap.once('postsync', this.umap._leafletMap._setDefaultCenter)
try { try {
if (this.files.length) { if (this.files.length) {
for (const file of this.files) { for (const file of this.files) {
this.map.processFileToImport(file, null, 'umap') this.umap.processFileToImport(file, null, 'umap')
} }
} else if (this.raw) { } else if (this.raw) {
this.map.importRaw(this.raw) this.umap.importRaw(this.raw)
} else if (this.url) { } else if (this.url) {
this.map.importFromUrl(this.url, this.format) this.umap.importFromUrl(this.url, this.format)
} }
} catch (e) { } catch (e) {
Alert.error(translate('Invalid umap data')) Alert.error(translate('Invalid umap data'))
@ -282,7 +282,7 @@ export default class Importer {
url: this.url, url: this.url,
format: this.format, format: this.format,
} }
if (this.map.options.urls.ajax_proxy) { if (this.umap.properties.urls.ajax_proxy) {
layer.options.remoteData.proxy = true layer.options.remoteData.proxy = true
layer.options.remoteData.ttl = SCHEMA.ttl.default layer.options.remoteData.ttl = SCHEMA.ttl.default
} }
@ -300,7 +300,7 @@ export default class Importer {
if (this.clear) layer.empty() if (this.clear) layer.empty()
if (this.files.length) { if (this.files.length) {
for (const file of this.files) { for (const file of this.files) {
this.map.processFileToImport(file, layer, this.format) this.umap.processFileToImport(file, layer, this.format)
} }
} else if (this.raw) { } else if (this.raw) {
layer.importRaw(this.raw, this.format) layer.importRaw(this.raw, this.format)

View file

@ -7,10 +7,10 @@ import * as Utils from './utils.js'
// Dedicated object so we can deal with a separate dirty status, and thus // Dedicated object so we can deal with a separate dirty status, and thus
// call the endpoint only when needed, saving one call at each save. // call the endpoint only when needed, saving one call at each save.
export class MapPermissions extends ServerStored { export class MapPermissions extends ServerStored {
constructor(map) { constructor(umap) {
super() super()
this.setOptions(map.options.permissions) this.setOptions(umap.properties.permissions)
this.map = map this.umap = umap
this._isDirty = false this._isDirty = false
} }
@ -28,11 +28,11 @@ export class MapPermissions extends ServerStored {
} }
isOwner() { isOwner() {
return Boolean(this.map.options.user?.is_owner) return Boolean(this.umap.properties.user?.is_owner)
} }
isAnonymousMap() { isAnonymousMap() {
return !this.map.options.permissions.owner return !this.umap.properties.permissions.owner
} }
_editAnonymous(container) { _editAnonymous(container) {
@ -43,7 +43,7 @@ export class MapPermissions extends ServerStored {
{ {
handler: 'IntSelect', handler: 'IntSelect',
label: translate('Who can edit'), label: translate('Who can edit'),
selectOptions: this.map.options.edit_statuses, selectOptions: this.umap.properties.edit_statuses,
}, },
]) ])
const builder = new U.FormBuilder(this, fields) const builder = new U.FormBuilder(this, fields)
@ -58,7 +58,7 @@ export class MapPermissions extends ServerStored {
) )
} }
if (this.map.options.user?.id) { if (this.umap.properties.user?.id) {
// We have a user, and this user has come through here, so they can edit the map, so let's allow to own the map. // We have a user, and this user has come through here, so they can edit the map, so let's allow to own the map.
// Note: real check is made on the back office anyway. // Note: real check is made on the back office anyway.
const advancedActions = DomUtil.createFieldset( const advancedActions = DomUtil.createFieldset(
@ -90,7 +90,7 @@ export class MapPermissions extends ServerStored {
{ {
handler: 'IntSelect', handler: 'IntSelect',
label: translate('Who can edit'), label: translate('Who can edit'),
selectOptions: this.map.options.edit_statuses, selectOptions: this.umap.properties.edit_statuses,
}, },
]) ])
topFields.push([ topFields.push([
@ -98,20 +98,20 @@ export class MapPermissions extends ServerStored {
{ {
handler: 'IntSelect', handler: 'IntSelect',
label: translate('Who can view'), label: translate('Who can view'),
selectOptions: this.map.options.share_statuses, selectOptions: this.umap.properties.share_statuses,
}, },
]) ])
collaboratorsFields.push([ collaboratorsFields.push([
'options.owner', 'options.owner',
{ handler: 'ManageOwner', label: translate("Map's owner") }, { handler: 'ManageOwner', label: translate("Map's owner") },
]) ])
if (this.map.options.user?.teams?.length) { if (this.umap.properties.user?.teams?.length) {
collaboratorsFields.push([ collaboratorsFields.push([
'options.team', 'options.team',
{ {
handler: 'ManageTeam', handler: 'ManageTeam',
label: translate('Attach map to a team'), label: translate('Attach map to a team'),
teams: this.map.options.user.teams, teams: this.umap.properties.user.teams,
}, },
]) ])
} }
@ -136,20 +136,20 @@ export class MapPermissions extends ServerStored {
} }
_editDatalayers(container) { _editDatalayers(container) {
if (this.map.hasLayers()) { if (this.umap.hasLayers()) {
const fieldset = Utils.loadTemplate( const fieldset = Utils.loadTemplate(
`<fieldset class="separator"><legend>${translate('Datalayers')}</legend></fieldset>` `<fieldset class="separator"><legend>${translate('Datalayers')}</legend></fieldset>`
) )
container.appendChild(fieldset) container.appendChild(fieldset)
this.map.eachDataLayer((datalayer) => { this.umap.eachDataLayer((datalayer) => {
datalayer.permissions.edit(fieldset) datalayer.permissions.edit(fieldset)
}) })
} }
} }
edit() { edit() {
if (this.map.options.editMode !== 'advanced') return if (this.umap.properties.editMode !== 'advanced') return
if (!this.map.options.umap_id) { if (!this.umap.properties.umap_id) {
Alert.info(translate('Please save the map first')) Alert.info(translate('Please save the map first'))
return return
} }
@ -158,15 +158,15 @@ export class MapPermissions extends ServerStored {
if (this.isAnonymousMap()) this._editAnonymous(container) if (this.isAnonymousMap()) this._editAnonymous(container)
else this._editWithOwner(container) else this._editWithOwner(container)
this._editDatalayers(container) this._editDatalayers(container)
this.map.editPanel.open({ content: container, className: 'dark' }) this.umap.editPanel.open({ content: container, className: 'dark' })
} }
async attach() { async attach() {
const [data, response, error] = await this.map.server.post(this.getAttachUrl()) const [data, response, error] = await this.umap.server.post(this.getAttachUrl())
if (!error) { if (!error) {
this.options.owner = this.map.options.user this.options.owner = this.umap.properties.user
Alert.success(translate('Map has been attached to your account')) Alert.success(translate('Map has been attached to your account'))
this.map.editPanel.close() this.umap.editPanel.close()
} }
} }
@ -186,40 +186,41 @@ export class MapPermissions extends ServerStored {
formData.append('team', this.options.team?.id || '') formData.append('team', this.options.team?.id || '')
formData.append('share_status', this.options.share_status) formData.append('share_status', this.options.share_status)
} }
const [data, response, error] = await this.map.server.post( const [data, response, error] = await this.umap.server.post(
this.getUrl(), this.getUrl(),
{}, {},
formData formData
) )
if (!error) { if (!error) {
this.commit() this.commit()
this.map.fire('postsync') this.umap._leafletMap.fire('postsync')
return true return true
} }
} }
getUrl() { getUrl() {
return Utils.template(this.map.options.urls.map_update_permissions, { return this.umap.urls.get('map_update_permissions', {
map_id: this.map.options.umap_id, map_id: this.umap.properties.umap_id,
}) })
} }
getAttachUrl() { getAttachUrl() {
return Utils.template(this.map.options.urls.map_attach_owner, { return this.umap.urls.get('map_attach_owner', {
map_id: this.map.options.umap_id, map_id: this.umap.properties.umap_id,
}) })
} }
commit() { commit() {
this.map.options.permissions = Object.assign( this.umap.properties.permissions = Object.assign(
this.map.options.permissions, {},
this.umap.properties.permissions,
this.options this.options
) )
} }
getShareStatusDisplay() { getShareStatusDisplay() {
if (this.map.options.share_statuses) { if (this.umap.properties.share_statuses) {
return Object.fromEntries(this.map.options.share_statuses)[ return Object.fromEntries(this.umap.properties.share_statuses)[
this.options.share_status this.options.share_status
] ]
} }
@ -239,8 +240,8 @@ export class DataLayerPermissions extends ServerStored {
this.datalayer = datalayer this.datalayer = datalayer
} }
get map() { get umap() {
return this.datalayer.map return this.datalayer.umap
} }
edit(container) { edit(container) {
@ -252,7 +253,7 @@ export class DataLayerPermissions extends ServerStored {
label: translate('Who can edit "{layer}"', { label: translate('Who can edit "{layer}"', {
layer: this.datalayer.getName(), layer: this.datalayer.getName(),
}), }),
selectOptions: this.map.options.datalayer_edit_statuses, selectOptions: this.umap.properties.datalayer_edit_statuses,
}, },
], ],
] ]
@ -264,8 +265,8 @@ export class DataLayerPermissions extends ServerStored {
} }
getUrl() { getUrl() {
return this.map.urls.get('datalayer_permissions', { return this.umap.urls.get('datalayer_permissions', {
map_id: this.map.options.umap_id, map_id: this.umap.properties.umap_id,
pk: this.datalayer.umap_id, pk: this.datalayer.umap_id,
}) })
} }
@ -274,7 +275,7 @@ export class DataLayerPermissions extends ServerStored {
if (!this.isDirty) return if (!this.isDirty) return
const formData = new FormData() const formData = new FormData()
formData.append('edit_status', this.options.edit_status) formData.append('edit_status', this.options.edit_status)
const [data, response, error] = await this.map.server.post( const [data, response, error] = await this.umap.server.post(
this.getUrl(), this.getUrl(),
{}, {},
formData formData

View file

@ -73,7 +73,7 @@ export const Default = FeatureGroup.extend({
initialize: function (datalayer) { initialize: function (datalayer) {
this.datalayer = datalayer this.datalayer = datalayer
FeatureGroup.prototype.initialize.call(this) FeatureGroup.prototype.initialize.call(this)
LayerMixin.onInit.call(this, this.datalayer.map) LayerMixin.onInit.call(this, this.datalayer.umap._leafletMap)
}, },
onAdd: function (map) { onAdd: function (map) {

View file

@ -20,7 +20,7 @@ const ClassifiedMixin = {
} }
this.ensureOptions(this.datalayer.options[key]) this.ensureOptions(this.datalayer.options[key])
FeatureGroup.prototype.initialize.call(this, [], this.datalayer.options[key]) FeatureGroup.prototype.initialize.call(this, [], this.datalayer.options[key])
LayerMixin.onInit.call(this, this.datalayer.map) LayerMixin.onInit.call(this, this.datalayer.umap._leafletMap)
}, },
ensureOptions: () => {}, ensureOptions: () => {},

View file

@ -40,7 +40,7 @@ export const Cluster = L.MarkerClusterGroup.extend({
options.maxClusterRadius = this.datalayer.options.cluster.radius options.maxClusterRadius = this.datalayer.options.cluster.radius
} }
L.MarkerClusterGroup.prototype.initialize.call(this, options) L.MarkerClusterGroup.prototype.initialize.call(this, options)
LayerMixin.onInit.call(this, this.datalayer.map) LayerMixin.onInit.call(this, this.datalayer.umap._leafletMap)
this._markerCluster = MarkerCluster this._markerCluster = MarkerCluster
this._layers = [] this._layers = []
}, },

View file

@ -21,7 +21,7 @@ export const Heat = L.HeatLayer.extend({
initialize: function (datalayer) { initialize: function (datalayer) {
this.datalayer = datalayer this.datalayer = datalayer
L.HeatLayer.prototype.initialize.call(this, [], this.datalayer.options.heat) L.HeatLayer.prototype.initialize.call(this, [], this.datalayer.options.heat)
LayerMixin.onInit.call(this, this.datalayer.map) LayerMixin.onInit.call(this, this.datalayer.umap._leafletMap)
if (!Utils.isObject(this.datalayer.options.heat)) { if (!Utils.isObject(this.datalayer.options.heat)) {
this.datalayer.options.heat = {} this.datalayer.options.heat = {}
} }

View file

@ -0,0 +1,583 @@
// Goes here all code related to Leaflet, DOM and user interactions.
import {
Map as BaseMap,
DomUtil,
DomEvent,
latLngBounds,
latLng,
Control,
setOptions,
} from '../../../vendors/leaflet/leaflet-src.esm.js'
import { translate } from '../i18n.js'
import { uMapAlert as Alert } from '../../components/alerts/alert.js'
import * as Utils from '../utils.js'
import * as Icon from './icon.js'
// Those options are not saved on the server, so they can live here
// instead of in umap.properties
BaseMap.mergeOptions({
demoTileInfos: { s: 'a', z: 9, x: 265, y: 181, '-y': 181, r: '' },
attributionControl: false,
})
const ControlsMixin = {
HIDDABLE_CONTROLS: [
'zoom',
'search',
'fullscreen',
'embed',
'datalayers',
'caption',
'locate',
'measure',
'editinosm',
'star',
'tilelayers',
],
initControls: function () {
this.helpMenuActions = {}
this._controls = {}
if (this.umap.hasEditMode() && !this.options.noControl) {
new U.EditControl(this).addTo(this)
new U.DrawToolbar({ map: this }).addTo(this)
const editActions = [
U.EditCaptionAction,
U.EditPropertiesAction,
U.EditLayersAction,
U.ChangeTileLayerAction,
U.UpdateExtentAction,
U.UpdatePermsAction,
U.ImportAction,
]
if (this.options.editMode === 'advanced') {
new U.SettingsToolbar({ actions: editActions }).addTo(this)
}
}
this._controls.zoom = new Control.Zoom({
zoomInTitle: translate('Zoom in'),
zoomOutTitle: translate('Zoom out'),
})
this._controls.datalayers = new U.DataLayersControl(this.umap)
this._controls.caption = new U.CaptionControl(this.umap)
this._controls.locate = new U.Locate(this, {
strings: {
title: translate('Center map on your location'),
},
showPopup: false,
// We style this control in our own CSS for consistency with other controls,
// but the control breaks if we don't specify a class here, so a fake class
// will do.
icon: 'umap-fake-class',
iconLoading: 'umap-fake-class',
flyTo: this.options.easing,
onLocationError: (err) => U.Alert.error(err.message),
})
this._controls.fullscreen = new Control.Fullscreen({
title: {
false: translate('View Fullscreen'),
true: translate('Exit Fullscreen'),
},
})
this._controls.search = new U.SearchControl()
this._controls.embed = new Control.Embed(this.umap)
this._controls.tilelayersChooser = new U.TileLayerChooser(this)
if (this.options.user?.id) this._controls.star = new U.StarControl(this.umap)
this._controls.editinosm = new Control.EditInOSM({
position: 'topleft',
widgetOptions: {
helpText: translate(
'Open this map extent in a map editor to provide more accurate data to OpenStreetMap'
),
},
})
this._controls.measure = new L.MeasureControl().initHandler(this)
this._controls.more = new U.MoreControls()
this._controls.scale = L.control.scale()
this._controls.permanentCredit = new U.PermanentCreditsControl(this)
if (this.options.scrollWheelZoom) this.scrollWheelZoom.enable()
else this.scrollWheelZoom.disable()
this.umap.drop = new U.DropControl(this)
this._controls.tilelayers = new U.TileLayerControl(this)
},
renderControls: function () {
const hasSlideshow = Boolean(this.options.slideshow?.active)
const barEnabled = this.options.captionBar || hasSlideshow
document.body.classList.toggle('umap-caption-bar-enabled', barEnabled)
document.body.classList.toggle('umap-slideshow-enabled', hasSlideshow)
for (const control of Object.values(this._controls)) {
this.removeControl(control)
}
if (this.options.noControl) return
this._controls.attribution = new U.AttributionControl().addTo(this)
if (this.options.miniMap) {
this.whenReady(function () {
if (this.selectedTilelayer) {
this._controls.miniMap = new Control.MiniMap(this.selectedTilelayer, {
aimingRectOptions: {
color: this.umap.getOption('color'),
fillColor: this.umap.getOption('fillColor'),
stroke: this.umap.getOption('stroke'),
fill: this.umap.getOption('fill'),
weight: this.umap.getOption('weight'),
opacity: this.umap.getOption('opacity'),
fillOpacity: this.umap.getOption('fillOpacity'),
},
}).addTo(this)
this._controls.miniMap._miniMap.invalidateSize()
}
})
}
for (const name of this.HIDDABLE_CONTROLS) {
const status = this.umap.getOption(`${name}Control`)
if (status === false) continue
const control = this._controls[name]
if (!control) continue
control.addTo(this)
if (status === undefined || status === null) {
DomUtil.addClass(control._container, 'display-on-more')
} else {
DomUtil.removeClass(control._container, 'display-on-more')
}
}
if (this.umap.getOption('permanentCredit'))
this._controls.permanentCredit.addTo(this)
if (this.umap.getOption('moreControl')) this._controls.more.addTo(this)
if (this.umap.getOption('scaleControl')) this._controls.scale.addTo(this)
this._controls.tilelayers.setLayers()
},
renderEditToolbar: function () {
const className = 'umap-main-edit-toolbox'
const container =
document.querySelector(`.${className}`) ||
DomUtil.create('div', `${className} with-transition dark`, this._controlContainer)
container.innerHTML = ''
const leftContainer = DomUtil.create('div', 'umap-left-edit-toolbox', container)
const rightContainer = DomUtil.create('div', 'umap-right-edit-toolbox', container)
const logo = DomUtil.create('div', 'logo', leftContainer)
DomUtil.createLink('', logo, 'uMap', '/', null, translate('Go to the homepage'))
const nameButton = DomUtil.createButton('map-name', leftContainer, '')
DomEvent.on(nameButton, 'mouseover', () => {
this.umap.tooltip.open({
content: translate('Edit the title of the map'),
anchor: nameButton,
position: 'bottom',
delay: 500,
duration: 5000,
})
})
const shareStatusButton = DomUtil.createButton(
'share-status',
leftContainer,
'',
this.umap.permissions.edit,
this.umap.permissions
)
DomEvent.on(shareStatusButton, 'mouseover', () => {
this.umap.tooltip.open({
content: translate('Update who can see and edit the map'),
anchor: shareStatusButton,
position: 'bottom',
delay: 500,
duration: 5000,
})
})
if (this.options.editMode === 'advanced') {
DomEvent.on(nameButton, 'click', this.umap.editCaption, this.umap)
DomEvent.on(
shareStatusButton,
'click',
this.umap.permissions.edit,
this.umap.permissions
)
}
if (this.options.user?.id) {
const button = U.Utils.loadTemplate(`
<button class="umap-user flat" type="button">
<i class="icon icon-16 icon-profile"></i>
<span>${this.options.user.name}</span>
</button>
`)
rightContainer.appendChild(button)
const menu = new U.ContextMenu({ className: 'dark', fixed: true })
const actions = [
{
label: translate('New map'),
action: this.umap.urls.get('map_new'),
},
{
label: translate('My maps'),
action: this.umap.urls.get('user_dashboard'),
},
{
label: translate('My teams'),
action: this.umap.urls.get('user_teams'),
},
]
if (this.umap.urls.has('user_profile')) {
actions.push({
label: translate('My profile'),
action: this.umap.urls.get('user_profile'),
})
}
button.addEventListener('click', () => {
menu.openBelow(button, actions)
})
}
const connectedPeers = this.umap.sync.getNumberOfConnectedPeers()
if (connectedPeers !== 0) {
const connectedPeersCount = DomUtil.createButton(
'leaflet-control-connected-peers',
rightContainer,
''
)
DomEvent.on(connectedPeersCount, 'mouseover', () => {
this.umap.tooltip.open({
content: translate(
'{connectedPeers} peer(s) currently connected to this map',
{
connectedPeers: connectedPeers,
}
),
anchor: connectedPeersCount,
position: 'bottom',
delay: 500,
duration: 5000,
})
})
const updateConnectedPeersCount = () => {
connectedPeersCount.innerHTML = this.sync.getNumberOfConnectedPeers()
}
updateConnectedPeersCount()
}
this.umap.help.getStartedLink(rightContainer)
const controlEditCancel = DomUtil.createButton(
'leaflet-control-edit-cancel',
rightContainer,
DomUtil.add('span', '', null, translate('Cancel edits')),
() => this.umap.askForReset()
)
DomEvent.on(controlEditCancel, 'mouseover', () => {
this.umap.tooltip.open({
content: this.umap.help.displayLabel('CANCEL'),
anchor: controlEditCancel,
position: 'bottom',
delay: 500,
duration: 5000,
})
})
const controlEditDisable = DomUtil.createButton(
'leaflet-control-edit-disable',
rightContainer,
DomUtil.add('span', '', null, translate('View')),
this.umap.disableEdit,
this.umap
)
DomEvent.on(controlEditDisable, 'mouseover', () => {
this.umap.tooltip.open({
content: this.umap.help.displayLabel('PREVIEW'),
anchor: controlEditDisable,
position: 'bottom',
delay: 500,
duration: 5000,
})
})
const controlEditSave = DomUtil.createButton(
'leaflet-control-edit-save button',
rightContainer,
DomUtil.add('span', '', null, translate('Save')),
() => this.umap.saveAll()
)
DomEvent.on(controlEditSave, 'mouseover', () => {
this.umap.tooltip.open({
content: this.umap.help.displayLabel('SAVE'),
anchor: controlEditSave,
position: 'bottom',
delay: 500,
duration: 5000,
})
})
},
initCaptionBar: function () {
const container = DomUtil.create('div', 'umap-caption-bar', this._controlContainer)
const name = DomUtil.create('h3', 'map-name', container)
DomEvent.disableClickPropagation(container)
this.umap.addAuthorLink(container)
if (this.umap.getOption('captionMenus')) {
DomUtil.createButton(
'umap-about-link flat',
container,
translate('Open caption'),
this.umap.openCaption,
this
)
DomUtil.createButton(
'umap-open-browser-link flat',
container,
translate('Browse data'),
() => this.openBrowser('data')
)
if (this.options.facetKey) {
DomUtil.createButton(
'umap-open-filter-link flat',
container,
translate('Filter data'),
() => this.openBrowser('filters')
)
}
}
this.umap.onceDatalayersLoaded(function () {
this.slideshow.renderToolbox(container)
})
},
}
const ManageTilelayerMixin = {
initTileLayers: function () {
this.tilelayers = []
for (const props of this.options.tilelayers) {
const layer = this.createTileLayer(props)
this.tilelayers.push(layer)
if (
this.options.tilelayer &&
this.options.tilelayer.url_template === props.url_template
) {
// Keep control over the displayed attribution for non custom tilelayers
this.options.tilelayer.attribution = props.attribution
}
}
if (this.options.tilelayer?.url_template && this.options.tilelayer.attribution) {
this.customTilelayer = this.createTileLayer(this.options.tilelayer)
this.selectTileLayer(this.customTilelayer)
} else {
this.selectTileLayer(this.tilelayers[0])
}
if (this._controls) this._controls.tilelayers.setLayers()
},
createTileLayer: (tilelayer) => new L.TileLayer(tilelayer.url_template, tilelayer),
selectTileLayer: function (tilelayer) {
if (tilelayer === this.selectedTilelayer) {
return
}
try {
this.addLayer(tilelayer)
this.fire('baselayerchange', { layer: tilelayer })
if (this.selectedTilelayer) {
this.removeLayer(this.selectedTilelayer)
}
this.selectedTilelayer = tilelayer
if (
!Number.isNaN(this.selectedTilelayer.options.minZoom) &&
this.getZoom() < this.selectedTilelayer.options.minZoom
) {
this.setZoom(this.selectedTilelayer.options.minZoom)
}
if (
!Number.isNaN(this.selectedTilelayer.options.maxZoom) &&
this.getZoom() > this.selectedTilelayer.options.maxZoom
) {
this.setZoom(this.selectedTilelayer.options.maxZoom)
}
} catch (e) {
console.error(e)
this.removeLayer(tilelayer)
Alert.error(`${translate('Error in the tilelayer URL')}: ${tilelayer._url}`)
// Users can put tilelayer URLs by hand, and if they add wrong {variable},
// Leaflet throw an error, and then the map is no more editable
}
this.setOverlay()
},
eachTileLayer: function (callback, context) {
const urls = []
const callOne = (layer) => {
// Prevent adding a duplicate background,
// while adding selected/custom on top of the list
const url = layer.options.url_template
if (urls.indexOf(url) !== -1) return
callback.call(context, layer)
urls.push(url)
}
if (this.selectedTilelayer) callOne(this.selectedTilelayer)
if (this.customTilelayer) callOne(this.customTilelayer)
this.tilelayers.forEach(callOne)
},
setOverlay: function () {
if (!this.options.overlay || !this.options.overlay.url_template) return
const overlay = this.createTileLayer(this.options.overlay)
try {
this.addLayer(overlay)
if (this.overlay) this.removeLayer(this.overlay)
this.overlay = overlay
} catch (e) {
this.removeLayer(overlay)
console.error(e)
Alert.error(`${translate('Error in the overlay URL')}: ${overlay._url}`)
}
},
updateTileLayers: function () {
const callback = (tilelayer) => {
this.options.tilelayer = tilelayer.toJSON()
this.umap.isDirty = true
}
if (this._controls.tilelayersChooser) {
this._controls.tilelayersChooser.openSwitcher({ callback, edit: true })
}
},
}
const EditMixin = {
startMarker: function () {
return this.editTools.startMarker()
},
startPolyline: function () {
return this.editTools.startPolyline()
},
startPolygon: function () {
return this.editTools.startPolygon()
},
initEditTools: function () {
this.editTools = new U.Editable(this.umap)
this.renderEditToolbar()
},
}
export const LeafletMap = BaseMap.extend({
includes: [ControlsMixin, ManageTilelayerMixin, EditMixin],
initialize: function (umap, element) {
this.umap = umap
const options = this.umap.properties
BaseMap.prototype.initialize.call(this, element, options)
// After calling parent initialize, as we are doing initCenter our-selves
this.loader = new Control.Loading()
this.loader.onAdd(this)
if (!this.options.noControl) {
DomEvent.on(document.body, 'dataloading', (e) => this.fire('dataloading', e))
DomEvent.on(document.body, 'dataload', (e) => this.fire('dataload', e))
this.on('click', this.closeInplaceToolbar)
}
this.on('baselayerchange', (e) => {
if (this._controls.miniMap) this._controls.miniMap.onMainMapBaseLayerChange(e)
})
},
attachToDom: function () {
this.initControls()
// Needs locate control and hash to exist
this.initCenter()
this.initTileLayers()
// Needs tilelayer to exist for minimap
this.renderControls()
this.handleLimitBounds()
},
setOptions: function (options) {
setOptions(this, options)
},
closeInplaceToolbar: function () {
const toolbar = this._toolbars[L.Toolbar.Popup._toolbar_class_id]
if (toolbar) toolbar.remove()
},
latLng: (a, b, c) => {
// manage geojson case and call original method
if (!(a instanceof L.LatLng) && a.coordinates) {
// Guess it's a geojson
a = [a.coordinates[1], a.coordinates[0]]
}
return latLng(a, b, c)
},
_setDefaultCenter: function () {
this.options.center = this.latLng(this.options.center)
this.setView(this.options.center, this.options.zoom)
},
initCenter: function () {
this._setDefaultCenter()
if (this.options.hash) this.addHash()
if (this.options.hash && this._hash.parseHash(location.hash)) {
// FIXME An invalid hash will cause the load to fail
this._hash.update()
} else if (this.options.defaultView === 'locate' && !this.options.noControl) {
this._controls.locate.start()
} else if (this.options.defaultView === 'data') {
this.umap.onceDataLoaded(this.umap.fitDataBounds)
} else if (this.options.defaultView === 'latest') {
this.umap.onceDataLoaded(() => {
if (!this.umap.hasData()) return
const datalayer = this.umap.firstVisibleDatalayer()
let feature
if (datalayer) {
const feature = datalayer.getFeatureByIndex(-1)
if (feature) {
feature.zoomTo({ callback: this.options.noControl ? null : feature.view })
return
}
}
})
}
},
handleLimitBounds: function () {
const south = Number.parseFloat(this.options.limitBounds.south)
const west = Number.parseFloat(this.options.limitBounds.west)
const north = Number.parseFloat(this.options.limitBounds.north)
const east = Number.parseFloat(this.options.limitBounds.east)
if (
!Number.isNaN(south) &&
!Number.isNaN(west) &&
!Number.isNaN(north) &&
!Number.isNaN(east)
) {
const bounds = latLngBounds([
[south, west],
[north, east],
])
this.options.minZoom = this.getBoundsZoom(bounds, false)
try {
this.setMaxBounds(bounds)
} catch (e) {
// Unusable bounds, like -2 -2 -2 -2?
console.error('Error limiting bounds', e)
}
} else {
this.options.minZoom = 0
this.setMaxBounds()
}
},
setMaxBounds: function (bounds) {
// Hack. Remove me when fix is released:
// https://github.com/Leaflet/Leaflet/pull/4494
bounds = latLngBounds(bounds)
if (!bounds.isValid()) {
this.options.maxBounds = null
return this.off('moveend', this._panInsideMaxBounds)
}
return BaseMap.prototype.setMaxBounds.call(this, bounds)
},
})

View file

@ -62,8 +62,8 @@ const Panel = Popup.extend({
}, },
onAdd: function (map) { onAdd: function (map) {
map.panel.setDefaultMode('expanded') map.umap.panel.setDefaultMode('expanded')
map.panel.open({ map.umap.panel.open({
content: this._content, content: this._content,
actions: [Browser.backButton(map)], actions: [Browser.backButton(map)],
}) })
@ -79,7 +79,7 @@ const Panel = Popup.extend({
}, },
onRemove: function (map) { onRemove: function (map) {
map.panel.close() map.umap.panel.close()
// fire events as in base class Popup.js:onRemove // fire events as in base class Popup.js:onRemove
map.fire('popupclose', { popup: this }) map.fire('popupclose', { popup: this })

View file

@ -52,7 +52,7 @@ const FeatureMixin = {
onClick: function (event) { onClick: function (event) {
if (this._map.measureTools?.enabled()) return if (this._map.measureTools?.enabled()) return
this._popupHandlersAdded = true // Prevent leaflet from managing event this._popupHandlersAdded = true // Prevent leaflet from managing event
if (!this._map.editEnabled) { if (!this._map.umap.editEnabled) {
this.feature.view(event) this.feature.view(event)
} else if (!this.feature.isReadOnly()) { } else if (!this.feature.isReadOnly()) {
if (event.originalEvent.shiftKey) { if (event.originalEvent.shiftKey) {
@ -96,8 +96,8 @@ const FeatureMixin = {
DomEvent.stop(event) DomEvent.stop(event)
const items = this.feature const items = this.feature
.getContextMenuItems(event) .getContextMenuItems(event)
.concat(this._map.getContextMenuItems(event)) .concat(this._map.umap.getContextMenuItems(event))
this._map.contextmenu.open(event.originalEvent, items) this._map.umap.contextmenu.open(event.originalEvent, items)
}, },
onCommit: function () { onCommit: function () {
@ -134,7 +134,7 @@ const PointMixin = {
_enableDragging: function () { _enableDragging: function () {
// TODO: start dragging after 1 second on mouse down // TODO: start dragging after 1 second on mouse down
if (this._map.editEnabled) { if (this._map.umap.editEnabled) {
if (!this.editEnabled()) this.enableEdit() if (!this.editEnabled()) this.enableEdit()
// Enabling dragging on the marker override the Draggable._OnDown // Enabling dragging on the marker override the Draggable._OnDown
// event, which, as it stopPropagation, refrain the call of // event, which, as it stopPropagation, refrain the call of
@ -146,7 +146,7 @@ const PointMixin = {
}, },
_disableDragging: function () { _disableDragging: function () {
if (this._map.editEnabled) { if (this._map.umap.editEnabled) {
if (this.editor?.drawing) return // when creating a new marker, the mouse can trigger the mouseover/mouseout event if (this.editor?.drawing) return // when creating a new marker, the mouse can trigger the mouseover/mouseout event
// do not listen to them // do not listen to them
this.disableEdit() this.disableEdit()
@ -253,21 +253,21 @@ export const LeafletMarker = Marker.extend({
const PathMixin = { const PathMixin = {
_onMouseOver: function () { _onMouseOver: function () {
if (this._map.measureTools?.enabled()) { if (this._map.measureTools?.enabled()) {
this._map.tooltip.open({ content: this.getMeasure(), anchor: this }) this._map.umap.tooltip.open({ content: this.getMeasure(), anchor: this })
} else if (this._map.editEnabled && !this._map.editedFeature) { } else if (this._map.umap.editEnabled && !this._map.umap.editedFeature) {
this._map.tooltip.open({ content: translate('Click to edit'), anchor: this }) this._map.umap.tooltip.open({ content: translate('Click to edit'), anchor: this })
} }
}, },
makeGeometryEditable: function () { makeGeometryEditable: function () {
if (this._map.editedFeature !== this.feature) { if (this._map.umap.editedFeature !== this.feature) {
this.disableEdit() this.disableEdit()
return return
} }
this._map.once('moveend', this.makeGeometryEditable, this) this._map.once('moveend', this.makeGeometryEditable, this)
const pointsCount = this._parts.reduce((acc, part) => acc + part.length, 0) const pointsCount = this._parts.reduce((acc, part) => acc + part.length, 0)
if (pointsCount > 100 && this._map.getZoom() < this._map.getMaxZoom()) { if (pointsCount > 100 && this._map.getZoom() < this._map.getMaxZoom()) {
this._map.tooltip.open({ content: L._('Please zoom in to edit the geometry') }) this._map.umap.tooltip.open({ content: L._('Please zoom in to edit the geometry') })
this.disableEdit() this.disableEdit()
} else { } else {
this.enableEdit() this.enableEdit()

View file

@ -21,10 +21,10 @@ class Rule {
set isDirty(status) { set isDirty(status) {
this._isDirty = status this._isDirty = status
if (status) this.map.isDirty = status if (status) this.umap.isDirty = status
} }
constructor(map, condition = '', options = {}) { constructor(umap, condition = '', options = {}) {
// TODO make this public properties when browser coverage is ok // TODO make this public properties when browser coverage is ok
// cf https://caniuse.com/?search=public%20class%20field // cf https://caniuse.com/?search=public%20class%20field
this._condition = null this._condition = null
@ -37,14 +37,14 @@ class Rule {
['!=', this.not_equal], ['!=', this.not_equal],
['=', this.equal], ['=', this.equal],
] ]
this.map = map this.umap = umap
this.active = true this.active = true
this.options = options this.options = options
this.condition = condition this.condition = condition
} }
render(fields) { render(fields) {
this.map.render(fields) this.umap.render(fields)
} }
equal(other) { equal(other) {
@ -102,7 +102,7 @@ class Rule {
} }
getMap() { getMap() {
return this.map return this.umap
} }
getOption(option) { getOption(option) {
@ -136,7 +136,7 @@ class Rule {
const defaultShapeProperties = DomUtil.add('div', '', container) const defaultShapeProperties = DomUtil.add('div', '', container)
defaultShapeProperties.appendChild(builder.build()) defaultShapeProperties.appendChild(builder.build())
const autocomplete = new AutocompleteDatalist(builder.helpers.condition.input) const autocomplete = new AutocompleteDatalist(builder.helpers.condition.input)
const properties = this.map.allProperties() const properties = this.umap.allProperties()
autocomplete.suggestions = properties autocomplete.suggestions = properties
autocomplete.input.addEventListener('input', (event) => { autocomplete.input.addEventListener('input', (event) => {
const value = event.target.value const value = event.target.value
@ -144,12 +144,12 @@ class Rule {
autocomplete.suggestions = [`${value}=`, `${value}!=`, `${value}>`, `${value}<`] autocomplete.suggestions = [`${value}=`, `${value}!=`, `${value}>`, `${value}<`]
} else if (value.endsWith('=')) { } else if (value.endsWith('=')) {
const key = value.split('!')[0].split('=')[0] const key = value.split('!')[0].split('=')[0]
autocomplete.suggestions = this.map autocomplete.suggestions = this.umap
.sortedValues(key) .sortedValues(key)
.map((str) => `${value}${str || ''}`) .map((str) => `${value}${str || ''}`)
} }
}) })
this.map.editPanel.open({ content: container }) this.umap.editPanel.open({ content: container })
} }
renderToolbox(row) { renderToolbox(row) {
@ -176,7 +176,7 @@ class Rule {
function () { function () {
if (!confirm(translate('Are you sure you want to delete this rule?'))) return if (!confirm(translate('Are you sure you want to delete this rule?'))) return
this._delete() this._delete()
this.map.editPanel.close() this.umap.editPanel.close()
}, },
this this
) )
@ -186,27 +186,27 @@ class Rule {
DomEvent.on(toggle, 'click', () => { DomEvent.on(toggle, 'click', () => {
this.active = !this.active this.active = !this.active
row.classList.toggle('off', !this.active) row.classList.toggle('off', !this.active)
this.map.render(['rules']) this.umap.render(['rules'])
}) })
} }
_delete() { _delete() {
this.map.rules.rules = this.map.rules.rules.filter((rule) => rule !== this) this.umap.rules.rules = this.umap.rules.rules.filter((rule) => rule !== this)
} }
} }
export default class Rules { export default class Rules {
constructor(map) { constructor(umap) {
this.map = map this.umap = umap
this.rules = [] this.rules = []
this.loadRules() this.loadRules()
} }
loadRules() { loadRules() {
if (!this.map.options.rules?.length) return if (!this.umap.properties.rules?.length) return
for (const { condition, options } of this.map.options.rules) { for (const { condition, options } of this.umap.properties.rules) {
if (!condition) continue if (!condition) continue
this.rules.push(new Rule(this.map, condition, options)) this.rules.push(new Rule(this.umap, condition, options))
} }
} }
@ -225,7 +225,7 @@ export default class Rules {
else newIdx = referenceIdx + 1 else newIdx = referenceIdx + 1
this.rules.splice(newIdx, 0, moved) this.rules.splice(newIdx, 0, moved)
moved.isDirty = true moved.isDirty = true
this.map.render(['rules']) this.umap.render(['rules'])
} }
edit(container) { edit(container) {
@ -243,14 +243,14 @@ export default class Rules {
} }
addRule() { addRule() {
const rule = new Rule(this.map) const rule = new Rule(this.umap)
rule.isDirty = true rule.isDirty = true
this.rules.push(rule) this.rules.push(rule)
rule.edit(map) rule.edit(map)
} }
commit() { commit() {
this.map.options.rules = this.rules.map((rule) => { this.umap.properties.rules = this.rules.map((rule) => {
return { return {
condition: rule.condition, condition: rule.condition,
options: rule.options, options: rule.options,

View file

@ -25,7 +25,6 @@ export function has(obj) {
} }
function _onUpdate() { function _onUpdate() {
console.log(_queue)
isDirty = Boolean(_queue.size) isDirty = Boolean(_queue.size)
document.body.classList.toggle('umap-is-dirty', isDirty) document.body.classList.toggle('umap-is-dirty', isDirty)
} }

View file

@ -4,8 +4,8 @@ import { translate } from './i18n.js'
import * as Utils from './utils.js' import * as Utils from './utils.js'
export default class Share { export default class Share {
constructor(map) { constructor(umap) {
this.map = map this.umap = umap
} }
build() { build() {
@ -22,11 +22,11 @@ export default class Share {
window.location.protocol + Utils.getBaseUrl() window.location.protocol + Utils.getBaseUrl()
) )
if (this.map.options.shortUrl) { if (this.umap.properties.shortUrl) {
DomUtil.createCopiableInput( DomUtil.createCopiableInput(
this.container, this.container,
translate('Short link'), translate('Short link'),
this.map.options.shortUrl this.umap.properties.shortUrl
) )
} }
@ -60,8 +60,8 @@ export default class Share {
this.container, this.container,
translate('All data and settings of the map') translate('All data and settings of the map')
) )
const downloadUrl = Utils.template(this.map.options.urls.map_download, { const downloadUrl = this.umap.urls.get('map_download', {
map_id: this.map.options.umap_id, map_id: this.umap.properties.umap_id,
}) })
const link = Utils.loadTemplate(` const link = Utils.loadTemplate(`
<div> <div>
@ -115,10 +115,11 @@ export default class Share {
'queryString.captionBar', 'queryString.captionBar',
'queryString.captionMenus', 'queryString.captionMenus',
] ]
for (let i = 0; i < this.map.HIDDABLE_CONTROLS.length; i++) { // TODO: move HIDDABLE_CONTROLS to SCHEMA ?
UIFields.push(`queryString.${this.map.HIDDABLE_CONTROLS[i]}Control`) for (const name of this.umap._leafletMap.HIDDABLE_CONTROLS) {
UIFields.push(`queryString.${name}Control`)
} }
const iframeExporter = new IframeExporter(this.map) const iframeExporter = new IframeExporter(this.umap)
const buildIframeCode = () => { const buildIframeCode = () => {
iframe.textContent = iframeExporter.build() iframe.textContent = iframeExporter.build()
exportUrl.value = window.location.protocol + iframeExporter.buildUrl() exportUrl.value = window.location.protocol + iframeExporter.buildUrl()
@ -136,13 +137,13 @@ export default class Share {
open() { open() {
if (!this.container) this.build() if (!this.container) this.build()
this.map.panel.open({ content: this.container }) this.umap.panel.open({ content: this.container })
} }
async format(mode) { async format(mode) {
const type = EXPORT_FORMATS[mode] const type = EXPORT_FORMATS[mode]
const content = await type.formatter(this.map) const content = await type.formatter(this.umap)
const filename = Utils.slugify(this.map.options.name) + type.ext const filename = Utils.slugify(this.umap.properties.name) + type.ext
return { content, filetype: type.filetype, filename } return { content, filetype: type.filetype, filename }
} }
@ -161,8 +162,8 @@ export default class Share {
} }
class IframeExporter { class IframeExporter {
constructor(map) { constructor(umap) {
this.map = map this.umap = umap
this.baseUrl = Utils.getBaseUrl() this.baseUrl = Utils.getBaseUrl()
this.options = { this.options = {
includeFullScreenLink: true, includeFullScreenLink: true,
@ -192,22 +193,18 @@ class IframeExporter {
height: '300px', height: '300px',
} }
// Use map default, not generic default // Use map default, not generic default
this.queryString.onLoadPanel = this.map.getOption('onLoadPanel') this.queryString.onLoadPanel = this.umap.getOption('onLoadPanel')
}
getMap() {
return this.map
} }
buildUrl(options) { buildUrl(options) {
const datalayers = [] const datalayers = []
if (this.options.viewCurrentFeature && this.map.currentFeature) { if (this.options.viewCurrentFeature && this.umap.currentFeature) {
this.queryString.feature = this.map.currentFeature.getSlug() this.queryString.feature = this.umap.currentFeature.getSlug()
} else { } else {
delete this.queryString.feature delete this.queryString.feature
} }
if (this.options.keepCurrentDatalayers) { if (this.options.keepCurrentDatalayers) {
this.map.eachDataLayer((datalayer) => { this.umap.eachDataLayer((datalayer) => {
if (datalayer.isVisible() && datalayer.umap_id) { if (datalayer.isVisible() && datalayer.umap_id) {
datalayers.push(datalayer.umap_id) datalayers.push(datalayer.umap_id)
} }

View file

@ -13,20 +13,20 @@ const TOOLBOX_TEMPLATE = `
` `
export default class Slideshow extends WithTemplate { export default class Slideshow extends WithTemplate {
constructor(map, options) { constructor(umap, options) {
super() super()
this.map = map this.umap = umap
this._id = null this._id = null
this.CLASSNAME = 'umap-slideshow-active' this.CLASSNAME = 'umap-slideshow-active'
this.setOptions(options) this.setOptions(options)
this._current = null this._current = null
if (this.options.autoplay) { if (this.options.autoplay) {
this.map.onceDataLoaded(function () { this.umap.onceDataLoaded(function () {
this.play() this.play()
}, this) }, this)
} }
this.map.on( this.umap._leafletMap.on(
'edit:enabled', 'edit:enabled',
function () { function () {
this.stop() this.stop()
@ -65,7 +65,7 @@ export default class Slideshow extends WithTemplate {
} }
defaultDatalayer() { defaultDatalayer() {
return this.map.findDataLayer((d) => d.canBrowse()) return this.umap.findDataLayer((d) => d.canBrowse())
} }
startSpinner() { startSpinner() {
@ -83,7 +83,7 @@ export default class Slideshow extends WithTemplate {
play() { play() {
if (this._id) return if (this._id) return
if (this.map.editEnabled || !this.map.options.slideshow.active) return if (this.umap.editEnabled || !this.umap.options.slideshow.active) return
L.DomUtil.addClass(document.body, this.CLASSNAME) L.DomUtil.addClass(document.body, this.CLASSNAME)
this._id = window.setInterval(L.bind(this.loop, this), this.options.delay) this._id = window.setInterval(L.bind(this.loop, this), this.options.delay)
this.startSpinner() this.startSpinner()

View file

@ -17,7 +17,7 @@ export default class TableEditor extends WithTemplate {
constructor(datalayer) { constructor(datalayer) {
super() super()
this.datalayer = datalayer this.datalayer = datalayer
this.map = this.datalayer.map this.umap = this.datalayer.umap
this.contextmenu = new ContextMenu({ className: 'dark' }) this.contextmenu = new ContextMenu({ className: 'dark' })
this.table = this.loadTemplate(TEMPLATE) this.table = this.loadTemplate(TEMPLATE)
if (!this.datalayer.isRemoteLayer()) { if (!this.datalayer.isRemoteLayer()) {
@ -36,20 +36,20 @@ export default class TableEditor extends WithTemplate {
openHeaderMenu(property) { openHeaderMenu(property) {
const actions = [] const actions = []
let filterItem let filterItem
if (this.map.facets.has(property)) { if (this.umap.facets.has(property)) {
filterItem = { filterItem = {
label: translate('Remove filter for this column'), label: translate('Remove filter for this column'),
action: () => { action: () => {
this.map.facets.remove(property) this.umap.facets.remove(property)
this.map.browser.open('filters') this.umap.browser.open('filters')
}, },
} }
} else { } else {
filterItem = { filterItem = {
label: translate('Add filter for this column'), label: translate('Add filter for this column'),
action: () => { action: () => {
this.map.facets.add(property) this.umap.facets.add(property)
this.map.browser.open('filters') this.umap.browser.open('filters')
}, },
} }
} }
@ -86,8 +86,8 @@ export default class TableEditor extends WithTemplate {
} }
renderBody() { renderBody() {
const bounds = this.map.getBounds() const bounds = this.umap._leafletMap.getBounds()
const inBbox = this.map.browser.options.inBbox const inBbox = this.umap.browser.options.inBbox
let html = '' let html = ''
this.datalayer.eachFeature((feature) => { this.datalayer.eachFeature((feature) => {
if (feature.isFiltered()) return if (feature.isFiltered()) return
@ -121,7 +121,7 @@ export default class TableEditor extends WithTemplate {
} }
renameProperty(property) { renameProperty(property) {
this.map.dialog this.umap.dialog
.prompt(translate('Please enter the new name of this property')) .prompt(translate('Please enter the new name of this property'))
.then(({ prompt }) => { .then(({ prompt }) => {
if (!prompt || !this.validateName(prompt)) return if (!prompt || !this.validateName(prompt)) return
@ -135,7 +135,7 @@ export default class TableEditor extends WithTemplate {
} }
deleteProperty(property) { deleteProperty(property) {
this.map.dialog this.umap.dialog
.confirm( .confirm(
translate('Are you sure you want to delete this property on all the features?') translate('Are you sure you want to delete this property on all the features?')
) )
@ -150,7 +150,7 @@ export default class TableEditor extends WithTemplate {
} }
addProperty() { addProperty() {
this.map.dialog this.umap.dialog
.prompt(translate('Please enter the name of the property')) .prompt(translate('Please enter the name of the property'))
.then(({ prompt }) => { .then(({ prompt }) => {
if (!prompt || !this.validateName(prompt)) return if (!prompt || !this.validateName(prompt)) return
@ -187,10 +187,10 @@ export default class TableEditor extends WithTemplate {
<button class="flat" type="button" data-ref="filters"> <button class="flat" type="button" data-ref="filters">
<i class="icon icon-16 icon-filters"></i>${translate('Filter data')} <i class="icon icon-16 icon-filters"></i>${translate('Filter data')}
</button>`) </button>`)
filterButton.addEventListener('click', () => this.map.browser.open('filters')) filterButton.addEventListener('click', () => this.umap.browser.open('filters'))
actions.push(filterButton) actions.push(filterButton)
this.map.fullPanel.open({ this.umap.fullPanel.open({
content: this.table, content: this.table,
className: 'umap-table-editor', className: 'umap-table-editor',
actions: actions, actions: actions,
@ -304,7 +304,7 @@ export default class TableEditor extends WithTemplate {
deleteRows() { deleteRows() {
const selectedRows = this.getSelectedRows() const selectedRows = this.getSelectedRows()
if (!selectedRows.length) return if (!selectedRows.length) return
this.map.dialog this.umap.dialog
.confirm( .confirm(
translate('Found {count} rows. Are you sure you want to delete all?', { translate('Found {count} rows. Are you sure you want to delete all?', {
count: selectedRows.length, count: selectedRows.length,
@ -320,9 +320,9 @@ export default class TableEditor extends WithTemplate {
this.datalayer.show() this.datalayer.show()
this.datalayer.dataChanged() this.datalayer.dataChanged()
this.renderBody() this.renderBody()
if (this.map.browser.isOpen()) { if (this.umap.browser.isOpen()) {
this.map.browser.resetFilters() this.umap.browser.resetFilters()
this.map.browser.open('filters') this.umap.browser.open('filters')
} }
}) })
} }

View file

@ -2,9 +2,9 @@ import { DomEvent, DomUtil } from '../../../vendors/leaflet/leaflet-src.esm.js'
import { translate } from '../i18n.js' import { translate } from '../i18n.js'
export class Panel { export class Panel {
constructor(map) { constructor(umap) {
this.parent = map._controlContainer this.parent = umap._leafletMap._controlContainer
this.map = map this.umap = umap
this.container = DomUtil.create('div', '', this.parent) this.container = DomUtil.create('div', '', this.parent)
// This will be set once according to the panel configurated at load // This will be set once according to the panel configurated at load
// or by using panels as popups // or by using panels as popups
@ -80,26 +80,26 @@ export class Panel {
onClose() { onClose() {
if (DomUtil.hasClass(this.container, 'on')) { if (DomUtil.hasClass(this.container, 'on')) {
DomUtil.removeClass(this.container, 'on') DomUtil.removeClass(this.container, 'on')
this.map.invalidateSize({ pan: false }) this.umap._leafletMap.invalidateSize({ pan: false })
} }
} }
} }
export class EditPanel extends Panel { export class EditPanel extends Panel {
constructor(map) { constructor(umap) {
super(map) super(umap)
this.className = 'right dark' this.className = 'right dark'
} }
onClose() { onClose() {
super.onClose() super.onClose()
this.map.editedFeature = null this.umap.editedFeature = null
} }
} }
export class FullPanel extends Panel { export class FullPanel extends Panel {
constructor(map) { constructor(umap) {
super(map) super(umap)
this.className = 'full dark' this.className = 'full dark'
this.mode = 'expanded' this.mode = 'expanded'
} }

File diff suppressed because it is too large Load diff

View file

@ -41,7 +41,7 @@ export function getImpactsFromSchema(fields, schema) {
// remove the option prefix for fields // remove the option prefix for fields
// And only keep the first part in case of a subfield // And only keep the first part in case of a subfield
// (e.g "options.limitBounds.foobar" will just return "limitBounds") // (e.g "options.limitBounds.foobar" will just return "limitBounds")
return field.replace('options.', '').split('.')[0] return field.replace('options.', '').replace('properties.', '').split('.')[0]
}) })
.reduce((acc, field) => { .reduce((acc, field) => {
// retrieve the "impacts" field from the schema // retrieve the "impacts" field from the schema

View file

@ -2,7 +2,7 @@ U.BaseAction = L.ToolbarAction.extend({
initialize: function (map) { initialize: function (map) {
this.map = map this.map = map
if (this.options.label) { if (this.options.label) {
this.options.tooltip = this.map.help.displayLabel( this.options.tooltip = this.map.umap.help.displayLabel(
this.options.label, this.options.label,
(withKbdTag = false) (withKbdTag = false)
) )
@ -25,7 +25,7 @@ U.ImportAction = U.BaseAction.extend({
}, },
addHooks: function () { addHooks: function () {
this.map.importer.open() this.map.umap.importer.open()
}, },
}) })
@ -37,7 +37,7 @@ U.EditLayersAction = U.BaseAction.extend({
}, },
addHooks: function () { addHooks: function () {
this.map.editDatalayers() this.map.umap.editDatalayers()
}, },
}) })
@ -49,7 +49,7 @@ U.EditCaptionAction = U.BaseAction.extend({
}, },
addHooks: function () { addHooks: function () {
this.map.editCaption() this.map.umap.editCaption()
}, },
}) })
@ -61,7 +61,7 @@ U.EditPropertiesAction = U.BaseAction.extend({
}, },
addHooks: function () { addHooks: function () {
this.map.edit() this.map.umap.edit()
}, },
}) })
@ -84,7 +84,7 @@ U.UpdateExtentAction = U.BaseAction.extend({
}, },
addHooks: function () { addHooks: function () {
this.map.setCenterAndZoom() this.map.umap.setCenterAndZoom()
}, },
}) })
@ -95,7 +95,7 @@ U.UpdatePermsAction = U.BaseAction.extend({
}, },
addHooks: function () { addHooks: function () {
this.map.permissions.edit() this.map.umap.permissions.edit()
}, },
}) })
@ -142,7 +142,8 @@ U.AddPolylineShapeAction = U.BaseAction.extend({
}, },
addHooks: function () { addHooks: function () {
this.map.editedFeature.ui.editor.newShape() // FIXME: smells bad
this.map.umap.editedFeature.ui.editor.newShape()
}, },
}) })
@ -305,18 +306,24 @@ U.DrawToolbar = L.Toolbar.Control.extend({
appendToContainer: function (container) { appendToContainer: function (container) {
this.options.actions = [] this.options.actions = []
if (this.map.options.enableMarkerDraw) { if (this.map.umap.properties.enableMarkerDraw) {
this.options.actions.push(U.DrawMarkerAction) this.options.actions.push(U.DrawMarkerAction)
} }
if (this.map.options.enablePolylineDraw) { if (this.map.umap.properties.enablePolylineDraw) {
this.options.actions.push(U.DrawPolylineAction) this.options.actions.push(U.DrawPolylineAction)
if (this.map.editedFeature && this.map.editedFeature instanceof U.LineString) { if (
this.map.umap.editedFeature &&
this.map.umap.editedFeature instanceof U.LineString
) {
this.options.actions.push(U.AddPolylineShapeAction) this.options.actions.push(U.AddPolylineShapeAction)
} }
} }
if (this.map.options.enablePolygonDraw) { if (this.map.umap.properties.enablePolygonDraw) {
this.options.actions.push(U.DrawPolygonAction) this.options.actions.push(U.DrawPolygonAction)
if (this.map.editedFeature && this.map.editedFeature instanceof U.Polygon) { if (
this.map.umap.editedFeature &&
this.map.umap.editedFeature instanceof U.Polygon
) {
this.options.actions.push(U.AddPolygonShapeAction) this.options.actions.push(U.AddPolygonShapeAction)
} }
} }
@ -360,14 +367,14 @@ U.DropControl = L.Class.extend({
L.DomEvent.stop(e) L.DomEvent.stop(e)
}, },
drop: function (e) { drop: function (event) {
this.map.scrollWheelZoom.enable() this.map.scrollWheelZoom.enable()
this.dropzone.classList.remove('umap-dragover') this.dropzone.classList.remove('umap-dragover')
L.DomEvent.stop(e) L.DomEvent.stop(e)
for (let i = 0, file; (file = e.dataTransfer.files[i]); i++) { for (const file of event.dataTransfer.files) {
this.map.processFileToImport(file) this.map.umap.processFileToImport(file)
} }
this.map.onceDataLoaded(this.map.fitDataBounds) this.map.umap.onceDataLoaded(this.map.umap.fitDataBounds)
}, },
dragleave: function () { dragleave: function () {
@ -387,15 +394,15 @@ U.EditControl = L.Control.extend({
'', '',
container, container,
L._('Edit'), L._('Edit'),
map.enableEdit, map.umap.enableEdit,
map map.umap
) )
L.DomEvent.on( L.DomEvent.on(
enableEditing, enableEditing,
'mouseover', 'mouseover',
() => { () => {
map.tooltip.open({ map.umap.tooltip.open({
content: map.help.displayLabel('TOGGLE_EDIT'), content: map.umap.help.displayLabel('TOGGLE_EDIT'),
anchor: enableEditing, anchor: enableEditing,
position: 'bottom', position: 'bottom',
delay: 750, delay: 750,
@ -476,8 +483,8 @@ U.PermanentCreditsControl = L.Control.extend({
}) })
L.Control.Button = L.Control.extend({ L.Control.Button = L.Control.extend({
initialize: function (map, options) { initialize: function (umap, options) {
this.map = map this.umap = umap
L.Control.prototype.initialize.call(this, options) L.Control.prototype.initialize.call(this, options)
}, },
@ -510,11 +517,11 @@ U.DataLayersControl = L.Control.Button.extend({
}, },
afterAdd: function (container) { afterAdd: function (container) {
U.Utils.toggleBadge(container, this.map.browser.hasFilters()) U.Utils.toggleBadge(container, this.umap.browser?.hasFilters())
}, },
onClick: function () { onClick: function () {
this.map.openBrowser() this.umap.openBrowser()
}, },
}) })
@ -526,7 +533,7 @@ U.CaptionControl = L.Control.Button.extend({
}, },
onClick: function () { onClick: function () {
this.map.openCaption() this.umap.openCaption()
}, },
}) })
@ -537,12 +544,13 @@ U.StarControl = L.Control.Button.extend({
}, },
getClassName: function () { getClassName: function () {
const status = this.map.options.starred ? ' starred' : '' const status = this.umap.properties.starred ? ' starred' : ''
return `leaflet-control-star umap-control${status}` return `leaflet-control-star umap-control${status}`
}, },
onClick: function () { onClick: function () {
this.map.star() console.log(this.umap)
this.umap.star()
}, },
}) })
@ -554,248 +562,10 @@ L.Control.Embed = L.Control.Button.extend({
}, },
onClick: function () { onClick: function () {
this.map.share.open() this.umap.share.open()
}, },
}) })
const ControlsMixin = {
HIDDABLE_CONTROLS: [
'zoom',
'search',
'fullscreen',
'embed',
'datalayers',
'caption',
'locate',
'measure',
'editinosm',
'star',
'tilelayers',
],
renderEditToolbar: function () {
const className = 'umap-main-edit-toolbox'
const container =
document.querySelector(`.${className}`) ||
L.DomUtil.create(
'div',
`${className} with-transition dark`,
this._controlContainer
)
container.innerHTML = ''
const leftContainer = L.DomUtil.create('div', 'umap-left-edit-toolbox', container)
const rightContainer = L.DomUtil.create('div', 'umap-right-edit-toolbox', container)
const logo = L.DomUtil.create('div', 'logo', leftContainer)
L.DomUtil.createLink('', logo, 'uMap', '/', null, L._('Go to the homepage'))
const nameButton = L.DomUtil.createButton('map-name', leftContainer, '')
L.DomEvent.on(
nameButton,
'mouseover',
function () {
this.tooltip.open({
content: L._('Edit the title of the map'),
anchor: nameButton,
position: 'bottom',
delay: 500,
duration: 5000,
})
},
this
)
const shareStatusButton = L.DomUtil.createButton(
'share-status',
leftContainer,
'',
this.permissions.edit,
this.permissions
)
L.DomEvent.on(
shareStatusButton,
'mouseover',
function () {
this.tooltip.open({
content: L._('Update who can see and edit the map'),
anchor: shareStatusButton,
position: 'bottom',
delay: 500,
duration: 5000,
})
},
this
)
if (this.options.editMode === 'advanced') {
L.DomEvent.on(nameButton, 'click', this.editCaption, this)
L.DomEvent.on(shareStatusButton, 'click', this.permissions.edit, this.permissions)
}
if (this.options.user?.id) {
const button = U.Utils.loadTemplate(`
<button class="umap-user flat" type="button">
<i class="icon icon-16 icon-profile"></i>
<span>${this.options.user.name}</span>
</button>
`)
rightContainer.appendChild(button)
const menu = new U.ContextMenu({ className: 'dark', fixed: true })
const actions = [
{
label: L._('New map'),
action: this.urls.get('map_new'),
},
{
label: L._('My maps'),
action: this.urls.get('user_dashboard'),
},
{
label: L._('My teams'),
action: this.urls.get('user_teams'),
},
]
if (this.urls.has('user_profile')) {
actions.push({
label: L._('My profile'),
action: this.urls.get('user_profile'),
})
}
button.addEventListener('click', () => {
menu.openBelow(button, actions)
})
}
const connectedPeers = this.sync.getNumberOfConnectedPeers()
if (connectedPeers !== 0) {
const connectedPeersCount = L.DomUtil.createButton(
'leaflet-control-connected-peers',
rightContainer,
''
)
L.DomEvent.on(connectedPeersCount, 'mouseover', () => {
this.tooltip.open({
content: L._('{connectedPeers} peer(s) currently connected to this map', {
connectedPeers: connectedPeers,
}),
anchor: connectedPeersCount,
position: 'bottom',
delay: 500,
duration: 5000,
})
})
const updateConnectedPeersCount = () => {
connectedPeersCount.innerHTML =
'<span>' + this.sync.getNumberOfConnectedPeers() + '</span>'
}
updateConnectedPeersCount()
}
this.help.getStartedLink(rightContainer)
const controlEditCancel = L.DomUtil.createButton(
'leaflet-control-edit-cancel',
rightContainer,
L.DomUtil.add('span', '', null, L._('Cancel edits')),
this.askForReset,
this
)
L.DomEvent.on(
controlEditCancel,
'mouseover',
function () {
this.tooltip.open({
content: this.help.displayLabel('CANCEL'),
anchor: controlEditCancel,
position: 'bottom',
delay: 500,
duration: 5000,
})
},
this
)
const controlEditDisable = L.DomUtil.createButton(
'leaflet-control-edit-disable',
rightContainer,
L.DomUtil.add('span', '', null, L._('View')),
this.disableEdit,
this
)
L.DomEvent.on(
controlEditDisable,
'mouseover',
function () {
this.tooltip.open({
content: this.help.displayLabel('PREVIEW'),
anchor: controlEditDisable,
position: 'bottom',
delay: 500,
duration: 5000,
})
},
this
)
const controlEditSave = L.DomUtil.createButton(
'leaflet-control-edit-save button',
rightContainer,
L.DomUtil.add('span', '', null, L._('Save')),
this.saveAll,
this
)
L.DomEvent.on(
controlEditSave,
'mouseover',
function () {
this.tooltip.open({
content: this.help.displayLabel('SAVE'),
anchor: controlEditSave,
position: 'bottom',
delay: 500,
duration: 5000,
})
},
this
)
},
editDatalayers: function () {
if (!this.editEnabled) return
const container = L.DomUtil.create('div')
L.DomUtil.createTitle(container, L._('Manage layers'), 'icon-layers')
const ul = L.DomUtil.create('ul', '', container)
this.eachDataLayerReverse((datalayer) => {
const row = L.DomUtil.create('li', 'orderable', ul)
L.DomUtil.createIcon(row, 'icon-drag', L._('Drag to reorder'))
datalayer.renderToolbox(row)
const title = L.DomUtil.add('span', '', row, datalayer.options.name)
row.classList.toggle('off', !datalayer.isVisible())
title.textContent = datalayer.options.name
row.dataset.id = L.stamp(datalayer)
})
const onReorder = (src, dst, initialIndex, finalIndex) => {
const layer = this.datalayers[src.dataset.id]
const other = this.datalayers[dst.dataset.id]
const minIndex = Math.min(layer.getRank(), other.getRank())
const maxIndex = Math.max(layer.getRank(), other.getRank())
if (finalIndex === 0) layer.bringToTop()
else if (finalIndex > initialIndex) layer.insertBefore(other)
else layer.insertAfter(other)
this.eachDataLayerReverse((datalayer) => {
if (datalayer.getRank() >= minIndex && datalayer.getRank() <= maxIndex)
datalayer.isDirty = true
})
this.indexDatalayers()
}
const orderable = new U.Orderable(ul, onReorder)
const bar = L.DomUtil.create('div', 'button-bar', container)
L.DomUtil.createButton(
'show-on-edit block add-datalayer button',
bar,
L._('Add a layer'),
this.newDataLayer,
this
)
this.editPanel.open({ content: container })
},
}
/* Used in view mode to define the current tilelayer */ /* Used in view mode to define the current tilelayer */
U.TileLayerControl = L.Control.IconLayers.extend({ U.TileLayerControl = L.Control.IconLayers.extend({
initialize: function (map, options) { initialize: function (map, options) {
@ -819,7 +589,7 @@ U.TileLayerControl = L.Control.IconLayers.extend({
// Fixme when https://github.com/Leaflet/Leaflet/pull/9201 is released // Fixme when https://github.com/Leaflet/Leaflet/pull/9201 is released
const icon = U.Utils.template( const icon = U.Utils.template(
layer.options.url_template, layer.options.url_template,
this.map.demoTileInfos this.map.options.demoTileInfos
) )
layers.push({ layers.push({
title: layer.options.name, title: layer.options.name,
@ -885,7 +655,7 @@ U.TileLayerChooser = L.Control.extend({
L.DomUtil.createTitle(container, L._('Change tilelayers'), 'icon-tilelayer') L.DomUtil.createTitle(container, L._('Change tilelayers'), 'icon-tilelayer')
this._tilelayers_container = L.DomUtil.create('ul', '', container) this._tilelayers_container = L.DomUtil.create('ul', '', container)
this.buildList(options) this.buildList(options)
const panel = options.edit ? this.map.editPanel : this.map.panel const panel = options.edit ? this.map.umap.editPanel : this.map.umap.panel
panel.open({ content: container }) panel.open({ content: container })
}, },
@ -905,7 +675,7 @@ U.TileLayerChooser = L.Control.extend({
const el = L.DomUtil.create('li', selectedClass, this._tilelayers_container) const el = L.DomUtil.create('li', selectedClass, this._tilelayers_container)
const img = L.DomUtil.create('img', '', el) const img = L.DomUtil.create('img', '', el)
const name = L.DomUtil.create('div', '', el) const name = L.DomUtil.create('div', '', el)
img.src = U.Utils.template(tilelayer.options.url_template, this.map.demoTileInfos) img.src = U.Utils.template(tilelayer.options.url_template, this.map.options.demoTileInfos)
img.loading = 'lazy' img.loading = 'lazy'
name.textContent = tilelayer.options.name name.textContent = tilelayer.options.name
L.DomEvent.on( L.DomEvent.on(
@ -935,8 +705,8 @@ U.AttributionControl = L.Control.Attribution.extend({
this._container.innerHTML = '' this._container.innerHTML = ''
const container = L.DomUtil.create('div', 'attribution-container', this._container) const container = L.DomUtil.create('div', 'attribution-container', this._container)
container.innerHTML = credits container.innerHTML = credits
const shortCredit = this._map.getOption('shortCredit') const shortCredit = this._map.umap.getOption('shortCredit')
const captionMenus = this._map.getOption('captionMenus') const captionMenus = this._map.umap.getOption('captionMenus')
if (shortCredit) { if (shortCredit) {
L.DomUtil.element({ L.DomUtil.element({
tagName: 'span', tagName: 'span',
@ -947,7 +717,7 @@ U.AttributionControl = L.Control.Attribution.extend({
if (captionMenus) { if (captionMenus) {
const link = L.DomUtil.add('a', '', container, `${L._('Open caption')}`) const link = L.DomUtil.add('a', '', container, `${L._('Open caption')}`)
L.DomEvent.on(link, 'click', L.DomEvent.stop) L.DomEvent.on(link, 'click', L.DomEvent.stop)
.on(link, 'click', this._map.openCaption, this._map) .on(link, 'click', () => this._map.umap.openCaption())
.on(link, 'dblclick', L.DomEvent.stop) .on(link, 'dblclick', L.DomEvent.stop)
} }
if (window.top === window.self && captionMenus) { if (window.top === window.self && captionMenus) {
@ -1139,7 +909,7 @@ U.SearchControl = L.Control.extend({
this.map.fire('dataload', { id: id }) this.map.fire('dataload', { id: id })
}) })
this.search.resultsContainer = resultsContainer this.search.resultsContainer = resultsContainer
this.map.panel.open({ content: container }).then(input.focus()) this.map.umap.panel.open({ content: container }).then(input.focus())
}, },
}) })
@ -1179,8 +949,9 @@ L.Control.Loading.include({
}) })
U.Editable = L.Editable.extend({ U.Editable = L.Editable.extend({
initialize: function (map, options) { initialize: function (umap, options) {
L.Editable.prototype.initialize.call(this, map, options) this.umap = umap
L.Editable.prototype.initialize.call(this, umap._leafletMap, options)
this.on('editable:drawing:click editable:drawing:move', this.drawingTooltip) this.on('editable:drawing:click editable:drawing:move', this.drawingTooltip)
// Layer for items added by users // Layer for items added by users
this.on('editable:drawing:cancel', (event) => { this.on('editable:drawing:cancel', (event) => {
@ -1188,7 +959,7 @@ U.Editable = L.Editable.extend({
}) })
this.on('editable:drawing:commit', function (event) { this.on('editable:drawing:commit', function (event) {
event.layer.feature.isDirty = true event.layer.feature.isDirty = true
if (this.map.editedFeature !== event.layer) event.layer.feature.edit(event) if (this.umap.editedFeature !== event.layer) event.layer.feature.edit(event)
}) })
this.on('editable:editing', (event) => { this.on('editable:editing', (event) => {
const feature = event.layer.feature const feature = event.layer.feature
@ -1210,7 +981,7 @@ U.Editable = L.Editable.extend({
}, },
createPolyline: function (latlngs) { createPolyline: function (latlngs) {
const datalayer = this.map.defaultEditDataLayer() const datalayer = this.umap.defaultEditDataLayer()
const point = new U.LineString(datalayer, { const point = new U.LineString(datalayer, {
geometry: { type: 'LineString', coordinates: [] }, geometry: { type: 'LineString', coordinates: [] },
}) })
@ -1218,7 +989,7 @@ U.Editable = L.Editable.extend({
}, },
createPolygon: function (latlngs) { createPolygon: function (latlngs) {
const datalayer = this.map.defaultEditDataLayer() const datalayer = this.umap.defaultEditDataLayer()
const point = new U.Polygon(datalayer, { const point = new U.Polygon(datalayer, {
geometry: { type: 'Polygon', coordinates: [] }, geometry: { type: 'Polygon', coordinates: [] },
}) })
@ -1226,7 +997,7 @@ U.Editable = L.Editable.extend({
}, },
createMarker: function (latlng) { createMarker: function (latlng) {
const datalayer = this.map.defaultEditDataLayer() const datalayer = this.umap.defaultEditDataLayer()
const point = new U.Point(datalayer, { const point = new U.Point(datalayer, {
geometry: { type: 'Point', coordinates: [latlng.lng, latlng.lat] }, geometry: { type: 'Point', coordinates: [latlng.lng, latlng.lat] },
}) })
@ -1235,15 +1006,15 @@ U.Editable = L.Editable.extend({
_getDefaultProperties: function () { _getDefaultProperties: function () {
const result = {} const result = {}
if (this.map.options.featuresHaveOwner?.user) { if (this.umap.properties.featuresHaveOwner?.user) {
result.geojson = { properties: { owner: this.map.options.user.id } } result.geojson = { properties: { owner: this.umap.properties.user.id } }
} }
return result return result
}, },
connectCreatedToMap: function (layer) { connectCreatedToMap: function (layer) {
// Overrided from Leaflet.Editable // Overrided from Leaflet.Editable
const datalayer = this.map.defaultEditDataLayer() const datalayer = this.umap.defaultEditDataLayer()
datalayer.addFeature(layer.feature) datalayer.addFeature(layer.feature)
layer.isDirty = true layer.isDirty = true
return layer return layer
@ -1251,7 +1022,7 @@ U.Editable = L.Editable.extend({
drawingTooltip: function (e) { drawingTooltip: function (e) {
if (e.layer instanceof L.Marker && e.type === 'editable:drawing:start') { if (e.layer instanceof L.Marker && e.type === 'editable:drawing:start') {
this.map.tooltip.open({ content: L._('Click to add a marker') }) this.umap.tooltip.open({ content: L._('Click to add a marker') })
} }
if (!(e.layer instanceof L.Polyline)) { if (!(e.layer instanceof L.Polyline)) {
// only continue with Polylines and Polygons // only continue with Polylines and Polygons
@ -1298,12 +1069,12 @@ U.Editable = L.Editable.extend({
} }
} }
if (content) { if (content) {
this.map.tooltip.open({ content: content }) this.umap.tooltip.open({ content: content })
} }
}, },
closeTooltip: function () { closeTooltip: function () {
this.map.ui.closeTooltip() this.umap.closeTooltip()
}, },
onVertexRawClick: (e) => { onVertexRawClick: (e) => {
@ -1314,7 +1085,7 @@ U.Editable = L.Editable.extend({
onEscape: function () { onEscape: function () {
this.once('editable:drawing:end', (event) => { this.once('editable:drawing:end', (event) => {
this.map.tooltip.close() this.umap.tooltip.close()
// Leaflet.Editable will delete the drawn shape if invalid // Leaflet.Editable will delete the drawn shape if invalid
// (eg. line has only one drawn point) // (eg. line has only one drawn point)
// So let's check if the layer has no more shape // So let's check if the layer has no more shape

View file

@ -25,48 +25,6 @@ L.Util.copyToClipboard = (textToCopy) => {
} }
} }
L.Util.queryString = (name, fallback) => {
const decode = (s) => decodeURIComponent(s.replace(/\+/g, ' '))
const qs = window.location.search.slice(1).split('&')
const qa = {}
for (const i in qs) {
const key = qs[i].split('=')
if (!key) continue
qa[decode(key[0])] = key[1] ? decode(key[1]) : 1
}
return qa[name] || fallback
}
L.Util.booleanFromQueryString = (name) => {
const value = L.Util.queryString(name)
return value === '1' || value === 'true'
}
L.Util.setFromQueryString = (options, name) => {
const value = L.Util.queryString(name)
if (typeof value !== 'undefined') options[name] = value
}
L.Util.setBooleanFromQueryString = (options, name) => {
const value = L.Util.queryString(name)
if (typeof value !== 'undefined') options[name] = value === '1' || value === 'true'
}
L.Util.setNumberFromQueryString = (options, name) => {
const value = +L.Util.queryString(name)
if (!Number.isNaN(value)) options[name] = value
}
L.Util.setNullableBooleanFromQueryString = (options, name) => {
let value = L.Util.queryString(name)
if (typeof value !== 'undefined') {
if (value === 'null') value = null
else if (value === '0' || value === 'false') value = false
else value = true
options[name] = value
}
}
L.DomUtil.add = (tagName, className, container, content) => { L.DomUtil.add = (tagName, className, container, content) => {
const el = L.DomUtil.create(tagName, className, container) const el = L.DomUtil.create(tagName, className, container)
if (content) { if (content) {

View file

@ -221,14 +221,14 @@ L.FormBuilder.Element.include({
this.label = L.DomUtil.create('label', '', this.getLabelParent()) this.label = L.DomUtil.create('label', '', this.getLabelParent())
this.label.textContent = this.label.title = this.options.label this.label.textContent = this.label.title = this.options.label
if (this.options.helpEntries) { if (this.options.helpEntries) {
this.builder.map.help.button(this.label, this.options.helpEntries) this.builder.umap.help.button(this.label, this.options.helpEntries)
} else if (this.options.helpTooltip) { } else if (this.options.helpTooltip) {
const info = L.DomUtil.create('i', 'info', this.label) const info = L.DomUtil.create('i', 'info', this.label)
L.DomEvent.on( L.DomEvent.on(
info, info,
'mouseover', 'mouseover',
function () { function () {
this.builder.map.tooltip.open({ this.builder.umap.tooltip.open({
anchor: info, anchor: info,
content: this.options.helpTooltip, content: this.options.helpTooltip,
position: 'top', position: 'top',
@ -359,7 +359,7 @@ L.FormBuilder.SlideshowDelay = L.FormBuilder.IntSelect.extend({
L.FormBuilder.DataLayerSwitcher = L.FormBuilder.Select.extend({ L.FormBuilder.DataLayerSwitcher = L.FormBuilder.Select.extend({
getOptions: function () { getOptions: function () {
const options = [] const options = []
this.builder.map.eachDataLayerReverse((datalayer) => { this.builder.umap.eachDataLayerReverse((datalayer) => {
if ( if (
datalayer.isLoaded() && datalayer.isLoaded() &&
!datalayer.isDataReadOnly() && !datalayer.isDataReadOnly() &&
@ -376,11 +376,11 @@ L.FormBuilder.DataLayerSwitcher = L.FormBuilder.Select.extend({
}, },
toJS: function () { toJS: function () {
return this.builder.map.datalayers[this.value()] return this.builder.umap.datalayers[this.value()]
}, },
set: function () { set: function () {
this.builder.map.lastUsedDataLayer = this.toJS() this.builder.umap.lastUsedDataLayer = this.toJS()
this.obj.changeDataLayer(this.toJS()) this.obj.changeDataLayer(this.toJS())
}, },
}) })
@ -400,7 +400,7 @@ L.FormBuilder.DataFormat = L.FormBuilder.Select.extend({
L.FormBuilder.LicenceChooser = L.FormBuilder.Select.extend({ L.FormBuilder.LicenceChooser = L.FormBuilder.Select.extend({
getOptions: function () { getOptions: function () {
const licences = [] const licences = []
const licencesList = this.builder.obj.options.licences const licencesList = this.builder.obj.properties.licences
let licence let licence
for (const i in licencesList) { for (const i in licencesList) {
licence = licencesList[i] licence = licencesList[i]
@ -414,7 +414,7 @@ L.FormBuilder.LicenceChooser = L.FormBuilder.Select.extend({
}, },
toJS: function () { toJS: function () {
return this.builder.obj.options.licences[this.value()] return this.builder.obj.properties.licences[this.value()]
}, },
}) })
@ -469,8 +469,8 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
onDefine: async function () { onDefine: async function () {
this.buttons.innerHTML = '' this.buttons.innerHTML = ''
this.footer.innerHTML = '' this.footer.innerHTML = ''
const [{ pictogram_list }, response, error] = await this.builder.map.server.get( const [{ pictogram_list }, response, error] = await this.builder.umap.server.get(
this.builder.map.options.urls.pictogram_list_json this.builder.umap.properties.urls.pictogram_list_json
) )
if (!error) this.pictogram_list = pictogram_list if (!error) this.pictogram_list = pictogram_list
this.buildTabs() this.buildTabs()
@ -616,7 +616,7 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
categories[category].push(props) categories[category].push(props)
} }
const sorted = Object.entries(categories).toSorted(([a], [b]) => const sorted = Object.entries(categories).toSorted(([a], [b]) =>
U.Utils.naturalSort(a, b, L.lang) U.Utils.naturalSort(a, b, U.lang)
) )
for (const [name, items] of sorted) { for (const [name, items] of sorted) {
this.addCategory(items, name) this.addCategory(items, name)
@ -1167,8 +1167,8 @@ U.FormBuilder = L.FormBuilder.extend({
} }
}, },
initialize: function (obj, fields, options) { initialize: function (obj, fields, options = {}) {
this.map = obj.map || obj.getMap() this.umap = obj.umap || options.umap
this.computeDefaultOptions() this.computeDefaultOptions()
L.FormBuilder.prototype.initialize.call(this, obj, fields, options) L.FormBuilder.prototype.initialize.call(this, obj, fields, options)
this.on('finish', this.finish) this.on('finish', this.finish)

File diff suppressed because it is too large Load diff

View file

@ -63,7 +63,11 @@ L.FormBuilder = L.Evented.extend({
const path = field.split('.') const path = field.split('.')
let value = this.obj let value = this.obj
for (const sub of path) { for (const sub of path) {
value = value[sub] try {
value = value[sub]
} catch {
console.log(field)
}
} }
return value return value
}, },

View file

@ -41,5 +41,4 @@
<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.controls.js' %}" defer></script> <script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
<script src="{% static 'umap/js/umap.js' %}" defer></script> <script type="module" src="{% static 'umap/js/components/fragment.js' %}" defer></script>
<script src="{% static 'umap/js/components/fragment.js' %}" defer></script>

View file

@ -1,12 +1,11 @@
{% load umap_tags %} {% load umap_tags static %}
{% include "umap/messages.html" %} {% include "umap/messages.html" %}
<div id="map"> <div id="map">
</div> </div>
<!-- djlint:off --> <!-- djlint:off -->
<script defer type="text/javascript"> <script defer type="module">
window.addEventListener('DOMContentLoaded', (event) => { import Umap from '{% static "umap/js/modules/umap.js" %}'
U.MAP = new U.Map("map", {{ map_settings|notag|safe }}) U.MAP = new Umap("map", {{ map_settings|notag|safe }})
})
</script> </script>
<!-- djlint:on --> <!-- djlint:on -->

View file

@ -1,6 +1,6 @@
{% extends "umap/content.html" %} {% extends "umap/content.html" %}
{% load i18n %} {% load i18n static %}
{% block head_title %} {% block head_title %}
{{ SITE_NAME }} - {% trans "My Dashboard" %} {{ SITE_NAME }} - {% trans "My Dashboard" %}
@ -45,23 +45,22 @@
{% endblock maincontent %} {% endblock maincontent %}
{% block bottom_js %} {% block bottom_js %}
{{ block.super }} {{ block.super }}
<script type="text/javascript"> <script type="module">
!(function () { import Umap from '{% static "umap/js/modules/umap.js" %}'
const CACHE = {} const CACHE = {}
for (const mapOpener of document.querySelectorAll("button.map-opener")) { for (const mapOpener of document.querySelectorAll("button.map-opener")) {
mapOpener.addEventListener('click', (event) => { mapOpener.addEventListener('click', (event) => {
const button = event.target.closest('button') const button = event.target.closest('button')
button.nextElementSibling.showModal() button.nextElementSibling.showModal()
const mapId = button.dataset.mapId const mapId = button.dataset.mapId
if (!document.querySelector(`#${mapId}_target`).children.length) { if (!document.querySelector(`#${mapId}_target`).children.length) {
const previewSettings = JSON.parse(document.getElementById(mapId).textContent) const previewSettings = JSON.parse(document.getElementById(mapId).textContent)
const map = new U.Map(`${mapId}_target`, previewSettings) const map = new Umap(`${mapId}_target`, previewSettings)
CACHE[mapId] = map CACHE[mapId] = map
} else { } else {
CACHE[mapId].invalidateSize() CACHE[mapId].invalidateSize()
} }
}) })
} }
})()
</script> </script>
{% endblock bottom_js %} {% endblock bottom_js %}

View file

@ -93,10 +93,10 @@ def test_umap_import_from_textarea(live_server, tilelayer, page, settings):
page.locator('img[src="https://tile.openstreetmap.fr/hot/6/32/21.png"]') page.locator('img[src="https://tile.openstreetmap.fr/hot/6/32/21.png"]')
).to_be_visible() ).to_be_visible()
# Should not have imported umap_id, while in the file options # Should not have imported umap_id, while in the file options
assert not page.evaluate("U.MAP.options.umap_id") assert not page.evaluate("U.MAP.properties.umap_id")
with page.expect_response(re.compile(r".*/datalayer/create/.*")): with page.expect_response(re.compile(r".*/datalayer/create/.*")):
page.get_by_role("button", name="Save").click() page.get_by_role("button", name="Save").click()
assert page.evaluate("U.MAP.options.umap_id") assert page.evaluate("U.MAP.properties.umap_id")
def test_import_geojson_from_textarea(tilelayer, live_server, page): def test_import_geojson_from_textarea(tilelayer, live_server, page):