fix: browser inBbox setting not persistent and features click not opening popup

Refactor the way it updates itself in the process.
This commit is contained in:
Yohan Boniface 2024-02-08 12:57:08 +01:00
parent a94a8b75cb
commit 355cdd9f07
4 changed files with 123 additions and 64 deletions

View file

@ -679,6 +679,9 @@ input[type=hidden].blur + [type="button"] {
overflow-x: hidden; overflow-x: hidden;
} }
#umap-ui-container { #umap-ui-container {
/* Added for playwright to consider the element as non visible */
/* as being out of the visible viewport is not enough */
visibility: hidden;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
padding: 0 10px; padding: 0 10px;
@ -970,6 +973,7 @@ input:invalid {
} }
.umap-ui #umap-ui-container { .umap-ui #umap-ui-container {
right: 0; right: 0;
visibility: visible;
} }
} }
@media all and (orientation:portrait) { @media all and (orientation:portrait) {
@ -982,6 +986,7 @@ input:invalid {
} }
.umap-ui #umap-ui-container { .umap-ui #umap-ui-container {
right: 0; right: 0;
visibility: visible;
} }
.umap-ui .leaflet-right { .umap-ui .leaflet-right {
right: 0; right: 0;

View file

@ -6,9 +6,13 @@ L.U.Browser = L.Class.extend({
initialize: function (map) { initialize: function (map) {
this.map = map this.map = map
this.map.on('moveend', this.onMoveEnd, this)
}, },
addFeature: function (feature) { addFeature: function (feature, parent) {
const filter = this.options.filter
if (filter && !feature.matchFilter(filter, this.filterKeys)) return
if (this.options.inBbox && !feature.isOnScreen(this.bounds)) return
const feature_li = L.DomUtil.create('li', `${feature.getClassName()} feature`), const feature_li = L.DomUtil.create('li', `${feature.getClassName()} feature`),
zoom_to = L.DomUtil.create('i', 'feature-zoom_to', feature_li), zoom_to = L.DomUtil.create('i', 'feature-zoom_to', feature_li),
edit = L.DomUtil.create('i', 'show-on-edit feature-edit', feature_li), edit = L.DomUtil.create('i', 'show-on-edit feature-edit', feature_li),
@ -48,58 +52,76 @@ L.U.Browser = L.Class.extend({
) )
L.DomEvent.on(edit, 'click', feature.edit, feature) L.DomEvent.on(edit, 'click', feature.edit, feature)
L.DomEvent.on(del, 'click', feature.confirmDelete, feature) L.DomEvent.on(del, 'click', feature.confirmDelete, feature)
return feature_li // HOTFIX. Remove when this is released:
// https://github.com/Leaflet/Leaflet/pull/9052
L.DomEvent.disableClickPropagation(feature_li)
parent.appendChild(feature_li)
}, },
addDatalayer: function (datalayer, dataContainer) { datalayerId: function (datalayer) {
const filterKeys = this.map.getFilterKeys() return `browse_data_datalayer_${L.stamp(datalayer)}`
const container = L.DomUtil.create( },
'div',
datalayer.getHidableClass(), onDataLayerChanged: function (e) {
dataContainer this.updateDatalayer(e.target)
), },
headline = L.DomUtil.create('h5', '', container)
container.id = `browse_data_datalayer_${datalayer.umap_id}` addDataLayer: function (datalayer, parent) {
const container = L.DomUtil.create('div', datalayer.getHidableClass(), parent),
headline = L.DomUtil.create('h5', '', container),
counter = L.DomUtil.create('span', 'datalayer-counter', headline)
container.id = this.datalayerId(datalayer)
datalayer.renderToolbox(headline) datalayer.renderToolbox(headline)
L.DomUtil.add('span', '', headline, datalayer.options.name) L.DomUtil.add('span', '', headline, datalayer.options.name)
const ul = L.DomUtil.create('ul', '', container) const ul = L.DomUtil.create('ul', '', container)
L.DomUtil.classIf(container, 'off', !datalayer.isVisible()) this.updateDatalayer(datalayer)
datalayer.on('datachanged', this.onDataLayerChanged, this)
const build = () => { this.map.ui.once('panel:closed', () => {
ul.innerHTML = '' datalayer.off('datachanged', this.onDataLayerChanged, this)
const bounds = this.map.getBounds()
datalayer.eachFeature((feature) => {
if (
this.options.filter &&
!feature.matchFilter(this.options.filter, filterKeys)
)
return
if (this.options.inBbox && !feature.isOnScreen(bounds)) return
ul.appendChild(this.addFeature(feature))
}) })
} },
updateDatalayer: function (datalayer) {
// Compute once, but use it for each feature later.
this.bounds = this.map.getBounds()
const parent = L.DomUtil.get(this.datalayerId(datalayer))
// Panel is not open
if (!parent) return
L.DomUtil.classIf(parent, 'off', !datalayer.isVisible())
const container = parent.querySelector('ul'),
counter = parent.querySelector('.datalayer-counter')
container.innerHTML = ''
datalayer.eachFeature((feature) => this.addFeature(feature, container))
build()
let total = datalayer.count(), let total = datalayer.count(),
current = ul.querySelectorAll('li').length, current = container.querySelectorAll('li').length,
count = total == current ? total : `${current}/${total}` count = total == current ? total : `${current}/${total}`
const counter = L.DomUtil.add('span', 'datalayer-counter', headline, count) counter.textContent = count
counter.title = L._('Features in this layer: {count}', { count: count }) counter.title = L._('Features in this layer: {count}', { count: count })
datalayer.on('datachanged', build) },
datalayer.map.ui.once('panel:closed', () => {
datalayer.off('datachanged', build) onFormChange: function () {
this.map.off('moveend', build) this.map.eachBrowsableDataLayer((datalayer) => {
}) datalayer.resetLayer(true)
datalayer.map.ui.once('panel:ready', () => { this.updateDatalayer(datalayer)
datalayer.map.ui.once('panel:ready', () => {
datalayer.off('datachanged', build)
}) })
},
onMoveEnd: function () {
const isBrowserOpen = !!document.querySelector('.umap-browse-data')
if (!isBrowserOpen) return
const isListDynamic = this.options.inBbox
this.map.eachBrowsableDataLayer((datalayer) => {
if (!isListDynamic && !datalayer.hasDynamicData()) return
this.updateDatalayer(datalayer)
}) })
}, },
open: function () { open: function () {
// Get once but use it for each feature later
this.filterKeys = this.map.getFilterKeys()
const container = L.DomUtil.create('div', 'umap-browse-data') const container = L.DomUtil.create('div', 'umap-browse-data')
// HOTFIX. Remove when this is merged and released: // HOTFIX. Remove when this is released:
// https://github.com/Leaflet/Leaflet/pull/9052 // https://github.com/Leaflet/Leaflet/pull/9052
L.DomEvent.disableClickPropagation(container) L.DomEvent.disableClickPropagation(container)
@ -113,39 +135,23 @@ L.U.Browser = L.Class.extend({
const formContainer = L.DomUtil.create('div', '', container) const formContainer = L.DomUtil.create('div', '', container)
const dataContainer = L.DomUtil.create('div', 'umap-browse-features', container) const dataContainer = L.DomUtil.create('div', 'umap-browse-features', container)
const rebuildHTML = () => {
dataContainer.innerHTML = ''
this.map.eachBrowsableDataLayer((datalayer) => {
this.addDatalayer(datalayer, dataContainer)
})
}
const redrawDataLayers = () => {
this.map.eachBrowsableDataLayer((datalayer) => {
datalayer.resetLayer(true)
})
}
const fields = [ const fields = [
['options.filter', { handler: 'Input', placeholder: L._('Filter') }], ['options.filter', { handler: 'Input', placeholder: L._('Filter') }],
['options.inBbox', { handler: 'Switch', label: L._('Current map view') }], ['options.inBbox', { handler: 'Switch', label: L._('Current map view') }],
] ]
const builder = new L.U.FormBuilder(this, fields, { const builder = new L.U.FormBuilder(this, fields, {
makeDirty: false, makeDirty: false,
callback: (e) => { callback: () => this.onFormChange(),
if (e.helper.field === 'options.inBbox') {
if (this.options.inBbox) this.map.on('moveend', rebuildHTML)
else this.map.off('moveend', rebuildHTML)
}
redrawDataLayers()
rebuildHTML()
},
}) })
formContainer.appendChild(builder.build()) formContainer.appendChild(builder.build())
rebuildHTML()
this.map.ui.openPanel({ this.map.ui.openPanel({
data: { html: container }, data: { html: container },
actions: [this.map._aboutLink()], actions: [this.map._aboutLink()],
}) })
this.map.eachBrowsableDataLayer((datalayer) => {
this.addDataLayer(datalayer, dataContainer)
})
}, },
}) })

View file

@ -743,9 +743,13 @@ L.U.DataLayer = L.Evented.extend({
return !((!isNaN(from) && zoom < from) || (!isNaN(to) && zoom > to)) return !((!isNaN(from) && zoom < from) || (!isNaN(to) && zoom > to))
}, },
hasDynamicData: function () {
return !!(this.options.remoteData && this.options.remoteData.dynamic)
},
fetchRemoteData: async function (force) { fetchRemoteData: async function (force) {
if (!this.isRemoteLayer()) return if (!this.isRemoteLayer()) return
if (!this.options.remoteData.dynamic && 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.map.localizeUrl(this.options.remoteData.url)
if (this.options.remoteData.proxy) { if (this.options.remoteData.proxy) {

View file

@ -153,6 +153,50 @@ def test_data_browser_bbox_limit_should_be_dynamic(live_server, page, bootstrap,
expect(page.get_by_text("one line in new zeland")).to_be_hidden() expect(page.get_by_text("one line in new zeland")).to_be_hidden()
def test_data_browser_bbox_filter_should_be_persistent(
live_server, page, bootstrap, map
):
# Zoom on Europe
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/51.000/2.000")
el = page.get_by_text("Current map view")
expect(el).to_be_visible()
el.click()
browser = page.locator("#umap-ui-container")
expect(browser.get_by_text("one point in france")).to_be_visible()
expect(browser.get_by_text("one line in new zeland")).to_be_hidden()
expect(browser.get_by_text("one polygon in greenland")).to_be_hidden()
# Close and reopen the browser to make sure this settings is persistent
close = browser.get_by_text("Close")
close.click()
expect(browser).to_be_hidden()
sleep(0.5)
expect(browser.get_by_text("one point in france")).to_be_hidden()
expect(browser.get_by_text("one line in new zeland")).to_be_hidden()
expect(browser.get_by_text("one polygon in greenland")).to_be_hidden()
page.get_by_title("See data layers").click()
page.get_by_role("button", name="Browse data").click()
expect(browser.get_by_text("one point in france")).to_be_visible()
expect(browser.get_by_text("one line in new zeland")).to_be_hidden()
expect(browser.get_by_text("one polygon in greenland")).to_be_hidden()
def test_data_browser_bbox_filtered_is_clickable(live_server, page, bootstrap, map):
popup = page.locator(".leaflet-popup")
# Zoom on Europe
page.goto(f"{live_server.url}{map.get_absolute_url()}#6/51.000/2.000")
el = page.get_by_text("Current map view")
expect(el).to_be_visible()
el.click()
browser = page.locator("#umap-ui-container")
expect(browser.get_by_text("one point in france")).to_be_visible()
expect(browser.get_by_text("one line in new zeland")).to_be_hidden()
expect(browser.get_by_text("one polygon in greenland")).to_be_hidden()
expect(popup).to_be_hidden()
browser.get_by_text("one point in france").click()
expect(popup).to_be_visible()
expect(popup.get_by_text("one point in france")).to_be_visible()
def test_data_browser_with_variable_in_name(live_server, page, bootstrap, map): def test_data_browser_with_variable_in_name(live_server, page, bootstrap, map):
# Include a variable # Include a variable
map.settings["properties"]["labelKey"] = "{name} ({foo})" map.settings["properties"]["labelKey"] = "{name} ({foo})"