feat: show last used pictograms in list

This feature was planned in the initial rework of the pictogram
form UI, but not yet done. Some recent discussion in the OSM French
forum reactivated the need for it.

cf https://forum.openstreetmap.fr/t/marker-and-marker-colors/21051
This commit is contained in:
Yohan Boniface 2024-02-12 14:09:23 +01:00
parent 188a535f7d
commit 126e47eef9
3 changed files with 48 additions and 25 deletions

View file

@ -537,9 +537,13 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
this.on('define', this.onDefine) this.on('define', this.onDefine)
}, },
onDefine: 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(
this.builder.map.options.urls.pictogram_list_json
)
if (!error) this.pictogram_list = pictogram_list
this.buildTabs() this.buildTabs()
const value = this.value() const value = this.value()
if (!value || L.Util.isPath(value)) this.showSymbolsTab() if (!value || L.Util.isPath(value)) this.showSymbolsTab()
@ -624,12 +628,11 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
search = L.Util.normalize(this.searchInput.value), search = L.Util.normalize(this.searchInput.value),
title = pictogram.attribution title = pictogram.attribution
? `${pictogram.name} — © ${pictogram.attribution}` ? `${pictogram.name} — © ${pictogram.attribution}`
: pictogram.name : pictogram.name || pictogram.src
if (search && L.Util.normalize(title).indexOf(search) === -1) return if (search && L.Util.normalize(title).indexOf(search) === -1) return
const className = value === this.value() ? `${baseClass} selected` : baseClass, const className = value === this.value() ? `${baseClass} selected` : baseClass,
container = L.DomUtil.create('div', className, parent), container = L.DomUtil.create('div', className, parent)
img = L.DomUtil.create('img', '', container) U.Icon.makeIconElement(value, container)
img.src = value
container.title = title container.title = title
L.DomEvent.on( L.DomEvent.on(
container, container,
@ -664,6 +667,15 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
if (status) this.grid.appendChild(parent) if (status) this.grid.appendChild(parent)
}, },
showLastUsed: function () {
if (U.Icon.LAST_USED.length) {
const items = U.Icon.LAST_USED.map((src) => ({
src,
}))
this.addCategory(L._('Used in this map'), items)
}
},
buildSymbolsList: function () { buildSymbolsList: function () {
this.grid.innerHTML = '' this.grid.innerHTML = ''
const categories = {} const categories = {}
@ -676,6 +688,7 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
const sorted = Object.entries(categories).toSorted(([a], [b]) => const sorted = Object.entries(categories).toSorted(([a], [b]) =>
L.Util.naturalSort(a, b) L.Util.naturalSort(a, b)
) )
this.showLastUsed()
for (let [category, items] of sorted) { for (let [category, items] of sorted) {
this.addCategory(category, items) this.addCategory(category, items)
} }
@ -692,17 +705,7 @@ L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
this.searchInput.placeholder = L._('Search') this.searchInput.placeholder = L._('Search')
this.grid = L.DomUtil.create('div', '', this.body) this.grid = L.DomUtil.create('div', '', this.body)
L.DomEvent.on(this.searchInput, 'input', this.buildSymbolsList, this) L.DomEvent.on(this.searchInput, 'input', this.buildSymbolsList, this)
if (this.pictogram_list) {
this.buildSymbolsList() this.buildSymbolsList()
} else {
const [{ pictogram_list }, response, error] = await this.builder.map.server.get(
this.builder.map.options.urls.pictogram_list_json
)
if (!error) {
this.pictogram_list = pictogram_list
this.buildSymbolsList()
}
}
}, },
showCharsTab: function () { showCharsTab: function () {
@ -989,10 +992,7 @@ L.FormBuilder.ManageEditors = L.FormBuilder.Element.extend({
on_select: L.bind(this.onSelect, this), on_select: L.bind(this.onSelect, this),
on_unselect: L.bind(this.onUnselect, this), on_unselect: L.bind(this.onUnselect, this),
} }
this.autocomplete = new U.AutoComplete.Ajax.SelectMultiple( this.autocomplete = new U.AutoComplete.Ajax.SelectMultiple(this.parentNode, options)
this.parentNode,
options
)
this._values = this.toHTML() this._values = this.toHTML()
if (this._values) if (this._values)
for (let i = 0; i < this._values.length; i++) for (let i = 0; i < this._values.length; i++)

View file

@ -1,4 +1,7 @@
U.Icon = L.DivIcon.extend({ U.Icon = L.DivIcon.extend({
statics: {
LAST_USED: [],
},
initialize: function (map, options) { initialize: function (map, options) {
this.map = map this.map = map
const default_options = { const default_options = {
@ -14,11 +17,22 @@ U.Icon = L.DivIcon.extend({
} }
}, },
_setLastUsed: function (url) {
if (L.Util.hasVar(url)) return
if (url === this.map.options.default_iconUrl) return
if (U.Icon.LAST_USED.indexOf(url) === -1) {
U.Icon.LAST_USED.push(url)
}
},
_getIconUrl: function (name) { _getIconUrl: function (name) {
let url let url
if (this.feature && this.feature._getIconUrl(name)) if (this.feature && this.feature._getIconUrl(name)) {
url = this.feature._getIconUrl(name) url = this.feature._getIconUrl(name)
else url = this.options[`${name}Url`] this._setLastUsed(url)
} else {
url = this.options[`${name}Url`]
}
return this.formatUrl(url, this.feature) return this.formatUrl(url, this.feature)
}, },

View file

@ -94,7 +94,7 @@ def test_can_change_picto_at_datalayer_level(map, live_server, page, pictos):
expect(undefine).to_be_hidden() expect(undefine).to_be_hidden()
define.click() define.click()
symbols = page.locator(".umap-pictogram-choice") symbols = page.locator(".umap-pictogram-choice")
expect(symbols).to_have_count(2) expect(symbols).to_have_count(3)
search = page.locator(".umap-pictogram-body input") search = page.locator(".umap-pictogram-body input")
search.type("circle") search.type("circle")
expect(symbols).to_have_count(1) expect(symbols).to_have_count(1)
@ -128,7 +128,7 @@ def test_can_change_picto_at_marker_level(map, live_server, page, pictos):
expect(undefine).to_be_hidden() expect(undefine).to_be_hidden()
define.click() define.click()
symbols = page.locator(".umap-pictogram-choice") symbols = page.locator(".umap-pictogram-choice")
expect(symbols).to_have_count(2) expect(symbols).to_have_count(3) # Include "Last used" picto
search = page.locator(".umap-pictogram-body input") search = page.locator(".umap-pictogram-body input")
search.type("circle") search.type("circle")
expect(symbols).to_have_count(1) expect(symbols).to_have_count(1)
@ -157,6 +157,10 @@ def test_can_use_remote_url_as_picto(map, live_server, page, pictos):
define = page.locator(".umap-field-iconUrl .define") define = page.locator(".umap-field-iconUrl .define")
expect(define).to_be_visible() expect(define).to_be_visible()
define.click() define.click()
expect(page.get_by_text("Used in this map")).to_be_hidden()
# Only default symbols
symbols = page.locator(".umap-pictogram-choice")
expect(symbols).to_have_count(2)
url_tab = page.get_by_role("button", name="URL") url_tab = page.get_by_role("button", name="URL")
input_el = page.get_by_placeholder("Add image URL") input_el = page.get_by_placeholder("Add image URL")
expect(input_el).to_be_hidden() expect(input_el).to_be_hidden()
@ -177,6 +181,11 @@ def test_can_use_remote_url_as_picto(map, live_server, page, pictos):
modify.click() modify.click()
# Should be on URL tab # Should be on URL tab
expect(input_el).to_be_visible() expect(input_el).to_be_visible()
# Symbol should be visible in symbols b
symbols_tab = page.get_by_role("button", name="Symbol")
symbols_tab.click()
expect(page.get_by_text("Used in this map")).to_be_visible()
expect(symbols).to_have_count(3)
def test_can_use_char_as_picto(map, live_server, page, pictos): def test_can_use_char_as_picto(map, live_server, page, pictos):