diff --git a/umap/static/umap/js/modules/browser.js b/umap/static/umap/js/modules/browser.js index ac2fea86..5f7d14e6 100644 --- a/umap/static/umap/js/modules/browser.js +++ b/umap/static/umap/js/modules/browser.js @@ -5,6 +5,7 @@ import { translate } from './i18n.js' import * as Icon from './rendering/icon.js' import ContextMenu from './ui/contextmenu.js' import * as Utils from './utils.js' +import { SCHEMA } from './schema.js' export default class Browser { constructor(umap, leafletMap) { @@ -21,35 +22,24 @@ export default class Browser { addFeature(feature, parent) { if (feature.isFiltered()) return if (this.options.inBbox && !feature.isOnScreen(this.bounds)) return - const row = DomUtil.create('li', `${feature.getClassName()} feature`) - const zoom_to = DomUtil.createButtonIcon( - row, - 'icon-zoom', - translate('Bring feature to center') - ) - const edit = DomUtil.createButtonIcon( - row, - 'show-on-edit icon-edit', - translate('Edit this feature') - ) - const del = DomUtil.createButtonIcon( - row, - 'show-on-edit icon-delete', - translate('Delete this feature') - ) - const colorBox = DomUtil.create( - 'i', - `icon icon-16 icon-${feature.getClassName()} feature-color`, - row - ) - const title = DomUtil.create('span', 'feature-title', row) + const template = ` +
  • + + + + + +
  • + ` + const [row, { zoom, edit, remove, colorBox, label }] = + Utils.loadTemplateWithRefs(template) + label.textContent = label.title = feature.getDisplayName() || '—' const symbol = feature._getIconUrl ? Icon.formatUrl(feature._getIconUrl(), feature) : null - title.textContent = title.title = feature.getDisplayName() || '—' const bgcolor = feature.getPreviewColor() colorBox.style.backgroundColor = bgcolor - if (symbol && symbol !== U.SCHEMA.iconUrl.default) { + if (symbol && symbol !== SCHEMA.iconUrl.default) { const icon = Icon.makeElement(symbol, colorBox) Icon.setContrast(icon, colorBox, symbol, bgcolor) } else if (DomUtil.contrastedColor(colorBox, bgcolor)) { @@ -58,10 +48,10 @@ export default class Browser { const viewFeature = (e) => { feature.zoomTo({ ...e, callback: () => feature.view() }) } - DomEvent.on(zoom_to, 'click', viewFeature) - DomEvent.on(title, 'click', viewFeature) - DomEvent.on(edit, 'click', feature.edit, feature) - DomEvent.on(del, 'click', feature.del, feature) + zoom.addEventListener('click', viewFeature) + label.addEventListener('click', viewFeature) + edit.addEventListener('click', () => feature.edit()) + remove.addEventListener('click', () => feature.del()) // HOTFIX. Remove when this is released: // https://github.com/Leaflet/Leaflet/pull/9052 DomEvent.disableClickPropagation(row) @@ -75,45 +65,51 @@ export default class Browser { addDataLayer(datalayer, parent) { let className = `datalayer ${datalayer.getHidableClass()}` if (this.mode !== 'layers') className += ' show-list' - const container = DomUtil.create('div', className, parent) - const headline = DomUtil.create('h5', '', container) - container.id = this.datalayerId(datalayer) - const ul = DomUtil.create('ul', '', container) + const [container, { headline, toolbox, toggle, label }] = + Utils.loadTemplateWithRefs(` +
    +
    + + + + +
    + +
    + `) + datalayer.renderToolbox(toolbox) + parent.appendChild(container) + const toggleList = () => parent.classList.toggle('show-list') + toggle.addEventListener('click', toggleList) + label.addEventListener('click', toggleList) this.updateDatalayer(datalayer) } updateDatalayer(datalayer) { // Compute once, but use it for each feature later. this.bounds = this._leafletMap.getBounds() - const parent = DomUtil.get(this.datalayerId(datalayer)) + const id = this.datalayerId(datalayer) + const parent = document.getElementById(id) // Panel is not open if (!parent) return parent.classList.toggle('off', !datalayer.isVisible()) + const label = parent.querySelector('.datalayer-name') const container = parent.querySelector('ul') - const headline = parent.querySelector('h5') - const toggleList = () => parent.classList.toggle('show-list') - headline.innerHTML = '' - const toggle = DomUtil.create('i', 'icon icon-16 datalayer-toggle-list', headline) - DomEvent.on(toggle, 'click', toggleList) - datalayer.renderToolbox(headline) - const name = DomUtil.create('span', 'datalayer-name', headline) - name.textContent = name.title = datalayer.options.name - DomEvent.on(name, 'click', toggleList) container.innerHTML = '' datalayer.eachFeature((feature) => this.addFeature(feature, container)) - + datalayer.propagate(['properties.name']) const total = datalayer.count() if (!total) return const current = container.querySelectorAll('li').length const count = total === current ? total : `${current}/${total}` - const counter = DomUtil.create('span', 'datalayer-counter', headline) + const counter = parent.querySelector('.datalayer-counter') counter.textContent = `(${count})` counter.title = translate(`Features in this layer: ${count}`) } toggleBadge() { - U.Utils.toggleBadge(this.filtersTitle, this.hasFilters()) - U.Utils.toggleBadge('.umap-control-browse', this.hasFilters()) + Utils.toggleBadge(this.filtersTitle, this.hasFilters()) + Utils.toggleBadge('.umap-control-browse', this.hasFilters()) } onFormChange() { @@ -157,21 +153,51 @@ export default class Browser { open(mode) { // Force only if mode is known, otherwise keep current mode. if (mode) this.mode = mode - const container = DomUtil.create('div') + const template = ` +
    +

    ${translate('Data browser')}

    +
    + ${translate('Filters')} +
    +
    +
    + +
    +
    +
    + + + +
    +
    +
    + ` + const [ + container, + { + details, + filtersTitle, + toggle, + fitBounds, + download, + dataContainer, + formContainer, + reset, + }, + ] = Utils.loadTemplateWithRefs(template) // HOTFIX. Remove when this is released: // https://github.com/Leaflet/Leaflet/pull/9052 DomEvent.disableClickPropagation(container) + details.open = this.mode === 'filters' + toggle.addEventListener('click', () => this.toggleLayers()) + fitBounds.addEventListener('click', () => this._umap.fitDataBounds()) + download.addEventListener('click', () => this.downloadVisible(download)) + download.hidden = this._umap.getProperty('embedControl') === false - DomUtil.createTitle(container, translate('Data browser'), 'icon-layers') - this.formContainer = DomUtil.createFieldset(container, L._('Filters'), { - on: this.mode === 'filters', - className: 'filters', - icon: 'icon-filters', - }) - this.filtersTitle = container.querySelector('summary') + this.filtersTitle = filtersTitle + this.dataContainer = dataContainer + this.formContainer = formContainer this.toggleBadge() - this.addMainToolbox(container) - this.dataContainer = DomUtil.create('div', '', container) let fields = [ [ @@ -184,27 +210,19 @@ export default class Browser { builder.on('set', () => this.onFormChange()) let filtersBuilder this.formContainer.appendChild(builder.build()) - DomEvent.on(builder.form, 'reset', () => { + builder.form.addEventListener('reset', () => { window.setTimeout(builder.syncAll.bind(builder)) }) if (this._umap.properties.facetKey) { fields = this._umap.facets.build() filtersBuilder = new Form(this._umap.facets, fields) filtersBuilder.on('set', () => this.onFormChange()) - DomEvent.on(filtersBuilder.form, 'reset', () => { + filtersBuilder.form.addEventListener('reset', () => { window.setTimeout(filtersBuilder.syncAll.bind(filtersBuilder)) }) this.formContainer.appendChild(filtersBuilder.build()) } - const reset = DomUtil.createButton('flat', this.formContainer, '', () => - this.resetFilters() - ) - DomUtil.createIcon(reset, 'icon-restore') - DomUtil.element({ - tagName: 'span', - parent: reset, - textContent: translate('Reset all'), - }) + reset.addEventListener('click', () => this.resetFilters()) this._umap.panel.open({ content: container, @@ -220,21 +238,6 @@ export default class Browser { } } - addMainToolbox(container) { - const [toolbox, { toggle, fitBounds, download }] = Utils.loadTemplateWithRefs(` -
    - - - -
    - `) - container.appendChild(toolbox) - toggle.addEventListener('click', () => this.toggleLayers()) - fitBounds.addEventListener('click', () => this._umap.fitDataBounds()) - download.addEventListener('click', () => this.downloadVisible(download)) - download.hidden = this._umap.getProperty('embedControl') === false - } - downloadVisible(element) { const menu = new ContextMenu({ fixed: true }) const items = [] @@ -265,15 +268,13 @@ export default class Browser { } static backButton(umap) { - const button = DomUtil.createButtonIcon( - DomUtil.create('li', '', undefined), - 'icon-back', - translate('Back to browser') + const button = Utils.loadTemplate( + `` ) // Fixme: remove me when this is merged and released // https://github.com/Leaflet/Leaflet/pull/9052 DomEvent.disableClickPropagation(button) - DomEvent.on(button, 'click', () => umap.openBrowser()) + button.addEventListener('click', () => umap.openBrowser()) return button } } diff --git a/umap/static/umap/js/modules/data/layer.js b/umap/static/umap/js/modules/data/layer.js index 617fea1a..1de3f731 100644 --- a/umap/static/umap/js/modules/data/layer.js +++ b/umap/static/umap/js/modules/data/layer.js @@ -130,6 +130,10 @@ export class DataLayer { } render(fields, builder) { + // Propagate will remove the fields it has already + // processed + fields = this.propagate(fields) + const impacts = Utils.getImpactsFromSchema(fields) for (const impact of impacts) { @@ -153,6 +157,29 @@ export class DataLayer { } } + // This method does a targeted update of the UI, + // it whould be merged with `render`` method and the + // SCHEMA at some point + propagate(fields = []) { + const impacts = { + 'properties.name': () => { + Utils.eachElement('.datalayer-name', (el) => { + if (el.dataset.id === this.id) { + el.textContent = this.getName() + el.title = this.getName() + } + }) + }, + } + for (const [field, impact] of Object.entries(impacts)) { + if (!fields.length || fields.includes(field)) { + impact() + fields = fields.filter((item) => item !== field) + } + } + return fields + } + showAtLoad() { return this.autoLoaded() && this.showAtZoom() } diff --git a/umap/static/umap/js/modules/rendering/controls.js b/umap/static/umap/js/modules/rendering/controls.js index 6379b827..c50c1a2d 100644 --- a/umap/static/umap/js/modules/rendering/controls.js +++ b/umap/static/umap/js/modules/rendering/controls.js @@ -99,7 +99,10 @@ const BaseButton = Control.extend({ ` const [container, { button }] = Utils.loadTemplateWithRefs(template) - button.addEventListener('click', () => this.onClick()) + button.addEventListener('click', (event) => { + event.stopPropagation() + this.onClick() + }) button.addEventListener('dblclick', (event) => { event.stopPropagation() }) diff --git a/umap/tests/integration/test_browser.py b/umap/tests/integration/test_browser.py index 453d7275..558808b1 100644 --- a/umap/tests/integration/test_browser.py +++ b/umap/tests/integration/test_browser.py @@ -465,19 +465,19 @@ def test_main_toolbox_toggle_all_layers(live_server, map, page): expect(page.locator(".datalayer.off")).to_have_count(1) # Click on button - page.locator(".umap-browser [data-ref=toggle]").click() + page.locator(".umap-browser").get_by_title("Show/hide all layers").click() # Should have hidden the two other layers expect(page.locator(".datalayer.off")).to_have_count(3) expect(markers).to_have_count(0) # Click again - page.locator(".umap-browser [data-ref=toggle]").click() + page.locator(".umap-browser").get_by_title("Show/hide all layers").click() # Should shown all layers expect(page.locator(".datalayer.off")).to_have_count(0) expect(markers).to_have_count(3) # Click again - page.locator(".umap-browser [data-ref=toggle]").click() + page.locator(".umap-browser").get_by_title("Show/hide all layers").click() # Should hidden again all layers expect(page.locator(".datalayer.off")).to_have_count(3) expect(markers).to_have_count(0) diff --git a/umap/tests/integration/test_edit_map.py b/umap/tests/integration/test_edit_map.py index 02744d2e..71eaffda 100644 --- a/umap/tests/integration/test_edit_map.py +++ b/umap/tests/integration/test_edit_map.py @@ -180,9 +180,9 @@ def test_sortkey_impacts_datalayerindex(map, live_server, page): first_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(0) second_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(1) third_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(2) - assert "X Third" == first_listed_feature.text_content() - assert "Y Second" == second_listed_feature.text_content() - assert "Z First" == third_listed_feature.text_content() + assert "X Third" == first_listed_feature.text_content().strip() + assert "Y Second" == second_listed_feature.text_content().strip() + assert "Z First" == third_listed_feature.text_content().strip() # Change the default sortkey to be "key" page.get_by_role("button", name="Edit").click() @@ -201,9 +201,9 @@ def test_sortkey_impacts_datalayerindex(map, live_server, page): first_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(0) second_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(1) third_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(2) - assert "Z First" == first_listed_feature.text_content() - assert "Y Second" == second_listed_feature.text_content() - assert "X Third" == third_listed_feature.text_content() + assert "Z First" == first_listed_feature.text_content().strip() + assert "Y Second" == second_listed_feature.text_content().strip() + assert "X Third" == third_listed_feature.text_content().strip() def test_hover_tooltip_setting_should_be_persistent(live_server, map, page):