From da86020a0b9883f4bf981dc70cb44fd2bf8382f8 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Mon, 2 Dec 2024 19:04:30 +0100 Subject: [PATCH 1/7] feat: add icon-magic in 16-white.svg --- umap/static/umap/img/16-white.svg | 8 ++- umap/static/umap/img/16.svg | 2 +- umap/static/umap/img/source/16-white.svg | 79 +++++++++++------------- umap/static/umap/img/source/16.svg | 2 +- 4 files changed, 46 insertions(+), 45 deletions(-) diff --git a/umap/static/umap/img/16-white.svg b/umap/static/umap/img/16-white.svg index 00f6dc672..a9f1484ea 100644 --- a/umap/static/umap/img/16-white.svg +++ b/umap/static/umap/img/16-white.svg @@ -1,4 +1,4 @@ - + @@ -12,6 +12,9 @@ + + + @@ -202,5 +205,8 @@ + + + diff --git a/umap/static/umap/img/16.svg b/umap/static/umap/img/16.svg index 352122700..12221ede7 100644 --- a/umap/static/umap/img/16.svg +++ b/umap/static/umap/img/16.svg @@ -1 +1 @@ -image/svg+xml   +image/svg+xml   diff --git a/umap/static/umap/img/source/16-white.svg b/umap/static/umap/img/source/16-white.svg index 85fcbc168..e0e15e3c7 100644 --- a/umap/static/umap/img/source/16-white.svg +++ b/umap/static/umap/img/source/16-white.svg @@ -1,7 +1,7 @@ - + @@ -13,23 +13,15 @@ - + + + + - + - - - - - - - - - - - - + @@ -40,23 +32,23 @@ - +   - + - + - +   - + @@ -82,7 +74,7 @@ - + @@ -153,53 +145,53 @@ - + - - + + - + - + - + - - - + + + - + - + - - - + + + - + - - - + + + - + - + @@ -208,7 +200,7 @@ - + @@ -224,5 +216,8 @@ + + + diff --git a/umap/static/umap/img/source/16.svg b/umap/static/umap/img/source/16.svg index 2af171a67..9cdf5ab3f 100644 --- a/umap/static/umap/img/source/16.svg +++ b/umap/static/umap/img/source/16.svg @@ -1,4 +1,4 @@ -image/svg+xml   +image/svg+xml   From d64cdae987405255541939d11932a2949a9b756b Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Mon, 2 Dec 2024 19:23:47 +0100 Subject: [PATCH 2/7] chore: make that Importer extends WithTemplate Another step is needed to use this.elements instead of this.qs --- umap/static/umap/js/modules/importer.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/umap/static/umap/js/modules/importer.js b/umap/static/umap/js/modules/importer.js index 7e7343c82..9ea95233f 100644 --- a/umap/static/umap/js/modules/importer.js +++ b/umap/static/umap/js/modules/importer.js @@ -6,6 +6,7 @@ import Dialog from './ui/dialog.js' import * as Utils from './utils.js' const TEMPLATE = ` +

${translate('Import data')}

${translate('Choose data')} @@ -45,10 +46,12 @@ const TEMPLATE = `
+
` -export default class Importer { +export default class Importer extends Utils.WithTemplate { constructor(umap) { + super() this._umap = umap this.TYPES = ['geojson', 'csv', 'gpx', 'kml', 'osm', 'georss', 'umap'] this.IMPORTERS = [] @@ -57,7 +60,9 @@ export default class Importer { } loadImporters() { - for (const [name, config] of Object.entries(this._umap.properties.importers || {})) { + for (const [name, config] of Object.entries( + this._umap.properties.importers || {} + )) { const register = (mod) => { this.IMPORTERS.push(new mod.Importer(this._umap, config)) } @@ -168,7 +173,7 @@ export default class Importer { }) } this._umap.help.parse(this.container) - DomEvent.on(this.qs('[name=submit]'), 'click', this.submit, this) + this.qs('[name=submit]').addEventListener('click', () => this.submit()) DomEvent.on(this.qs('[type=file]'), 'change', this.onFileChange, this) for (const element of this.container.querySelectorAll('[onchange]')) { DomEvent.on(element, 'change', this.onChange, this) From d99fe70e36b664cc16347edd7cfa0bfd2e6b80db Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Mon, 2 Dec 2024 19:27:06 +0100 Subject: [PATCH 3/7] feat: display importers in a dialog instead of direclty in the form The goal is to keep the form smaller, specifically to keep the submit button visible as much as possible. --- umap/static/umap/base.css | 3 ++ umap/static/umap/css/form.css | 3 ++ umap/static/umap/css/icon.css | 3 ++ umap/static/umap/js/modules/importer.js | 48 +++++++++++++++---------- umap/tests/integration/test_import.py | 4 +++ 5 files changed, 42 insertions(+), 19 deletions(-) diff --git a/umap/static/umap/base.css b/umap/static/umap/base.css index e8321da75..a9f3929b5 100644 --- a/umap/static/umap/base.css +++ b/umap/static/umap/base.css @@ -157,6 +157,9 @@ dt { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); } +.grid-container.by4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} .grid-container > * { text-align: center; } diff --git a/umap/static/umap/css/form.css b/umap/static/umap/css/form.css index 912c27978..e2f536eef 100644 --- a/umap/static/umap/css/form.css +++ b/umap/static/umap/css/form.css @@ -602,3 +602,6 @@ input[type=hidden].blur + [type="button"] { input.highlightable:not(:placeholder-shown) { border: 1px solid var(--color-brightCyan); } +.umap-upload [type=url] { + margin-bottom: 0; +} diff --git a/umap/static/umap/css/icon.css b/umap/static/umap/css/icon.css index 43042a540..96bd85e91 100644 --- a/umap/static/umap/css/icon.css +++ b/umap/static/umap/css/icon.css @@ -110,6 +110,9 @@ html[dir="rtl"] .icon { .icon-list { background-position: var(--tile) calc(var(--tile) * 4); } +.icon-magic { + background-position: calc(var(--tile) * 7) 0; +} .icon-marker { background-position: calc(var(--tile) * 3) calc(var(--tile) * 5); } diff --git a/umap/static/umap/js/modules/importer.js b/umap/static/umap/js/modules/importer.js index 9ea95233f..43560e3d7 100644 --- a/umap/static/umap/js/modules/importer.js +++ b/umap/static/umap/js/modules/importer.js @@ -11,13 +11,9 @@ const TEMPLATE = `
${translate('Choose data')} - - + +
${translate( @@ -49,6 +45,14 @@ const TEMPLATE = ` ` +const GRID_TEMPLATE = ` +
+

${translate('Import helpers')}

+

${translate('Import helpers will fill the URL field for you.')}

+
    +
    +` + export default class Importer extends Utils.WithTemplate { constructor(umap) { super() @@ -56,7 +60,7 @@ export default class Importer extends Utils.WithTemplate { this.TYPES = ['geojson', 'csv', 'gpx', 'kml', 'osm', 'georss', 'umap'] this.IMPORTERS = [] this.loadImporters() - this.dialog = new Dialog() + this.dialog = new Dialog({ className: 'importers dark' }) } loadImporters() { @@ -149,20 +153,26 @@ export default class Importer extends Utils.WithTemplate { ) } + showImporters() { + if (!this.IMPORTERS.length) return + const [element, { grid }] = Utils.loadTemplateWithRefs(GRID_TEMPLATE) + for (const plugin of this.IMPORTERS.sort((a, b) => (a.id > b.id ? 1 : -1))) { + const button = Utils.loadTemplate( + `
  • ` + ) + button.addEventListener('click', () => plugin.open(this)) + grid.appendChild(button) + } + this.dialog.open({ template: element, cancel: false, accept: false }) + } + build() { - this.container = DomUtil.create('div', 'umap-upload') - this.container.innerHTML = TEMPLATE + this.container = this.loadTemplate(TEMPLATE) if (this.IMPORTERS.length) { - const parent = this.container.querySelector('.importers ul') - for (const plugin of this.IMPORTERS.sort((a, b) => (a.id > b.id ? 1 : -1))) { - L.DomUtil.createButton( - plugin.id, - DomUtil.element({ tagName: 'li', parent }), - plugin.name, - () => plugin.open(this) - ) - } - this.qs('.importers').toggleAttribute('hidden', false) + // TODO use this.elements instead of this.qs + const button = this.qs('[data-ref=importersButton]') + button.addEventListener('click', () => this.showImporters()) + button.toggleAttribute('hidden', false) } for (const type of this.TYPES) { DomUtil.element({ diff --git a/umap/tests/integration/test_import.py b/umap/tests/integration/test_import.py index dd4b8a7ca..11413eaab 100644 --- a/umap/tests/integration/test_import.py +++ b/umap/tests/integration/test_import.py @@ -607,6 +607,7 @@ def test_overpass_import_with_bbox(page, live_server, tilelayer, settings): } page.goto(f"{live_server.url}/map/new/") page.get_by_role("link", name="Import data").click() + page.get_by_role("button", name="Import helpers").click() page.get_by_role("button", name="Overpass").click() page.get_by_placeholder("amenity=drinking_water").fill("building") page.get_by_role("button", name="Choose this data").click() @@ -657,6 +658,7 @@ def handle(route): page.route(re.compile("https://foobar.io/api.*"), handle) page.goto(f"{live_server.url}/map/new/") page.get_by_role("link", name="Import data").click() + page.get_by_role("button", name="Import helpers").click() page.get_by_role("button", name="Overpass").click() page.get_by_placeholder("amenity=drinking_water").fill("building") page.get_by_placeholder("Type area name, or let empty").click() @@ -669,6 +671,7 @@ def handle(route): expect(page.get_by_placeholder("Provide an URL here")).to_have_value( "https://my.overpass.io/interpreter?data=[out:json];nwr[building](area:3601393025);out geom;" ) + page.get_by_role("button", name="Import helpers").click() page.get_by_role("button", name="Overpass").click() expect(page.locator("#area")).to_contain_text( "Bray-sur-Seine, Seine-et-Marne, Île-de-France, France" @@ -710,6 +713,7 @@ def handle(route): page.goto(f"{live_server.url}/map/new/") expect(page.locator(".leaflet-marker-icon")).to_be_hidden() page.get_by_role("link", name="Import data").click() + page.get_by_role("button", name="Import helpers").click() page.get_by_role("button", name="Datasets").click() page.get_by_role("dialog").get_by_role("combobox").select_option( "https://remote.org/data.json" From 50efbc25be1ee8bfd60c7532c7933b1beed897dc Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Tue, 3 Dec 2024 12:40:33 +0100 Subject: [PATCH 4/7] feat: add a disabled/active mode to the submit button of import panel cf #2302 --- umap/static/umap/css/form.css | 14 ++++++++++++-- umap/static/umap/js/modules/importer.js | 24 +++++++++++++++++++++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/umap/static/umap/css/form.css b/umap/static/umap/css/form.css index e2f536eef..1d61cab72 100644 --- a/umap/static/umap/css/form.css +++ b/umap/static/umap/css/form.css @@ -78,8 +78,7 @@ input[type="submit"] { border-radius: 2px; font-weight: normal; cursor: pointer; - padding: 7px; - width: 100%; + padding: 7px 14px; min-height: 32px; line-height: 32px; border: none; @@ -92,6 +91,12 @@ input[type="submit"] { color: var(--text-color); border: 1px solid #1b1f20; } +.dark .button.primary:not([disabled]), +.dark [type="button"].primary:not([disabled]) { + background-color: var(--color-brightCyan); + color: var(--color-dark); + border: 1px solid #1b1f20; +} .dark .button:hover, .dark [type="button"]:hover, .dark input[type="submit"]:hover { @@ -100,6 +105,11 @@ input[type="submit"] { .dark a { color: var(--text-color); } +.dark [type="button"][disabled], +.dark input[type="submit"][disabled] { + background-color: var(--color-mediumGray); + cursor: not-allowed; +} button.flat, [type="button"].flat, .dark [type="button"].flat { diff --git a/umap/static/umap/js/modules/importer.js b/umap/static/umap/js/modules/importer.js index 43560e3d7..3a9542fbc 100644 --- a/umap/static/umap/js/modules/importer.js +++ b/umap/static/umap/js/modules/importer.js @@ -33,15 +33,15 @@ const TEMPLATE = `
    ${translate('Choose import mode')}
    - + ` @@ -121,6 +121,11 @@ export default class Importer extends Utils.WithTemplate { return this.qs('textarea').value } + set raw(value) { + this.qs('textarea').value = value + this.onChange() + } + get clear() { return Boolean(this.qs('[name=clear]').checked) } @@ -198,6 +203,7 @@ export default class Importer extends Utils.WithTemplate { ) this.qs('[name=layer-name]').toggleAttribute('hidden', Boolean(this.layerId)) this.qs('#clear').toggleAttribute('hidden', !this.layerId) + this.qs('[name=submit').toggleAttribute('disabled', !this.isValid()) } onFileChange(e) { @@ -219,6 +225,7 @@ export default class Importer extends Utils.WithTemplate { this.url = null this.format = undefined this.layerName = null + this.raw = null const layerSelect = this.qs('[name="layer-id"]') layerSelect.innerHTML = '' this._umap.eachDataLayerReverse((datalayer) => { @@ -251,6 +258,17 @@ export default class Importer extends Utils.WithTemplate { this.qs('[type=file]').showPicker() } + isValid() { + if (!this.format) return false + const hasFiles = Boolean(this.files.length) + const hasRaw = Boolean(this.raw) + const hasUrl = Boolean(this.url) + const hasAction = Boolean(this.action) + if (!hasFiles && !hasRaw && !hasUrl) return false + if (this.url) return hasAction + return true + } + submit() { let hasErrors if (this.format === 'umap') { From a9b13c709d5d216032f559ad4e69c48acaa40596 Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Thu, 5 Dec 2024 17:46:44 +0100 Subject: [PATCH 5/7] feat(importer): check "copy" as default action Let's make it simpler for new comers, and advanced users will know where to click to change the behaviour. --- umap/static/umap/js/modules/importer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/umap/static/umap/js/modules/importer.js b/umap/static/umap/js/modules/importer.js index 3a9542fbc..cd9e73567 100644 --- a/umap/static/umap/js/modules/importer.js +++ b/umap/static/umap/js/modules/importer.js @@ -33,7 +33,7 @@ const TEMPLATE = `
    ${translate('Choose import mode')}