feat: allow to create remote data layer from import panel

And make the form conditional.
This commit is contained in:
Yohan Boniface 2024-06-04 19:05:23 +02:00
parent dbafe19b7f
commit 4e3228d114
5 changed files with 164 additions and 54 deletions

View file

@ -389,6 +389,9 @@ fieldset legend {
border-radius: 50%;
content: attr(data-badge);
}
[hidden] {
display: none;
}
/* Switch */
input.switch:empty {

View file

@ -14,6 +14,7 @@
border: 1px solid var(--color-lightGray);
bottom: calc(var(--current-footer-height) + var(--panel-bottom));
box-sizing: border-box;
counter-reset: step;
}
.panel.dark {
border: 1px solid #222;
@ -44,6 +45,15 @@
.panel h3 {
line-height: 120%;
}
.panel .counter::before {
counter-increment: step;
content: counter(step) ". ";
}
.panel .counter {
display: block;
margin-top: var(--panel-gutter);
font-weight: bold;
}
@media all and (orientation:landscape) {
.panel {
top: var(--current-header-height);

View file

@ -2,28 +2,26 @@ import { DomUtil, DomEvent } from '../../vendors/leaflet/leaflet-src.esm.js'
import { translate } from './i18n.js'
import { uMapAlert as Alert } from '../components/alerts/alert.js'
import Dialog from './ui/dialog.js'
import { SCHEMA } from './schema.js'
const TEMPLATE = `
<h3><i class="icon icon-16 icon-upload"></i><span>${translate('Import data')}</span></h3>
<h3><i class="icon icon-16 icon-upload"></i><span>${translate('Add data')}</span></h3>
<label class="counter">${translate('Choose data')}</label>
<div class="formbox">
<input type="file" multiple autofocus />
<input type="url" placeholder="${translate('Provide an URL here')}" />
<textarea placeholder="${translate('Paste your data here')}"></textarea>
<input type="file" multiple autofocus onchange />
<input type="url" placeholder="${translate('Provide an URL here')}" onchange />
<textarea onchange placeholder="${translate('Paste your data here')}"></textarea>
</div>
<div class="importers">
<h4>${translate('Import from:')}</h4>
<h5>${translate('Import helpers:')}</h5>
<div class="button-bar by4" id="importers">
</div>
</div>
<label data-help="importFormats">
${translate('Choose the format of the data to import')}
<select name="format">
<option>${translate('Choose the data format')}</option>
</select>
<label class="counter">${translate('Choose the data format')}
<select name="format" onchange></select>
</label>
<div class="destination">
<label>
${translate('Choose the layer to import in')}
<label class="counter">${translate('Choose the layer to import in')}
<select name="layer-id"></select>
</label>
<label>
@ -31,7 +29,10 @@ const TEMPLATE = `
<input type="checkbox" name="clear" />
</label>
</div>
<input type="button" class="button" name="import" value="${translate('Import')}" />
<label class="counter">${translate('Choose import mode')}</label>
<input type="button" class="button" name="copy" value="${translate('Copy into the layer')}" />
<input hidden type="button" class="button" name="link" value="${translate('Link to the layer as remote data')}" />
<input hidden type="button" class="button" name="full" value="${translate('Import full map data')}" />
`
export default class Importer {
@ -60,7 +61,8 @@ export default class Importer {
}
set url(value) {
return (this.qs('[type=url]').value = value)
this.qs('[type=url]').value = value
this.onChange()
}
get format() {
@ -68,7 +70,8 @@ export default class Importer {
}
set format(value) {
return (this.qs('[name=format]').value = value)
this.qs('[name=format]').value = value
this.onChange()
}
get files() {
@ -85,7 +88,7 @@ export default class Importer {
get layer() {
const layerId = this.qs('[name=layer-id]').value
if (layerId) return this.map.datalayers[layerId]
return this.map.datalayers[layerId] || this.map.createDataLayer()
}
build() {
@ -100,8 +103,7 @@ export default class Importer {
() => plugin.open(this)
)
}
} else {
this.qs('.importers').style.display = 'none'
this.qs('.importers').toggleAttribute('hidden', true)
}
for (const type of this.TYPES) {
DomUtil.element({
@ -111,8 +113,20 @@ export default class Importer {
textContent: type,
})
}
DomEvent.on(this.qs('[name=import]'), 'click', this.submit, this)
DomEvent.on(this.qs('[name=copy]'), 'click', this.copy, this)
DomEvent.on(this.qs('[name=link]'), 'click', this.link, this)
DomEvent.on(this.qs('[name=full]'), 'click', this.full, this)
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)
}
}
onChange() {
this.qs('[name=link]').toggleAttribute('hidden', !this.url || this.format === 'umap')
this.qs('[name=full]').toggleAttribute('hidden', this.format !== 'umap')
this.qs('[name=copy]').toggleAttribute('hidden', this.format === 'umap')
this.qs('.destination').toggleAttribute('hidden', this.format === 'umap')
}
onFileChange(e) {
@ -131,6 +145,8 @@ export default class Importer {
onLoad() {
this.qs('[type=file]').value = null
this.url = null
this.format = undefined
const layerSelect = this.qs('[name="layer-id"]')
layerSelect.innerHTML = ''
this.map.eachDataLayerReverse((datalayer) => {
@ -162,31 +178,60 @@ export default class Importer {
this.fileInput.showPicker()
}
submit() {
let layer = this.layer
if (this.format === 'umap') {
full() {
if (this.format !== 'umap') return
this.map.once('postsync', this.map._setDefaultCenter)
try {
if (this.files.length) {
for (const file of this.files) {
this.map.processFileToImport(file, null, 'umap')
}
if (layer && this.clear) layer.empty()
} else if (this.raw) {
this.map.importRaw(this.raw)
} else if (this.url) {
this.map.importFromUrl(this.url, this.format)
}
} catch (e) {
this.map.alert.open({ content: translate('Invalid umap data'), level: 'error' })
console.error(e)
}
}
link() {
if (!this.url) return
if (!this.format) {
Alert.error(translate('Please choose a format'))
return
}
let layer = this.layer
layer.options.remoteData = {
url: this.url,
format: this.format,
}
if (this.map.options.urls.ajax_proxy) {
layer.options.remoteData.proxy = true
layer.options.remoteData.ttl = SCHEMA.ttl.default
}
layer.fetchRemoteData(true)
}
copy() {
// Format may be guessed from file later.
// Usefull in case of multiple files with different formats.
if (!this.format && !this.files.length) {
Alert.error(translate('Please choose a format'))
return
}
let layer = this.layer
if (this.clear) layer.empty()
if (this.files.length) {
for (const file of this.files) {
this.map.processFileToImport(file, layer, this.format)
}
} else {
if (!this.format)
return Alert.error(translate('Please choose a format'))
if (this.raw && this.format === 'umap') {
try {
this.map.importRaw(this.raw, this.format)
} catch (e) {
Alert.error(L._('Invalid umap data'))
console.error(e)
}
} else {
if (!layer) layer = this.map.createDataLayer()
if (this.raw) layer.importRaw(this.raw, this.format)
else if (this.url) layer.importFromUrl(this.url, this.format)
}
} else if (this.raw) {
layer.importRaw(this.raw, this.format)
} else if (this.url) {
layer.importFromUrl(this.url, this.format)
}
}
}

View file

@ -888,6 +888,13 @@ U.Map = L.Map.extend({
}
},
importFromUrl: async function (uri) {
const response = await this.request.get(uri)
if (response && response.ok) {
this.importRaw(await response.text())
}
},
importRaw: function (rawData) {
const importedData = JSON.parse(rawData)

View file

@ -31,6 +31,12 @@ def test_layers_list_is_updated(live_server, tilelayer, page):
)
# Now layer should be visible in the options
page.get_by_label("Choose the layer to import").select_option(label="foobar")
expect(
page.get_by_role("button", name="Import full map data", exact=True)
).to_be_hidden()
expect(
page.get_by_role("button", name="Link to the layer as remote data", exact=True)
).to_be_hidden()
def test_umap_import_from_file(live_server, tilelayer, page):
@ -42,7 +48,10 @@ def test_umap_import_from_file(live_server, tilelayer, page):
file_chooser = fc_info.value
path = Path(__file__).parent.parent / "fixtures/display_on_load.umap"
file_chooser.set_files(path)
button = page.get_by_role("button", name="Import", exact=True)
button = page.get_by_role("button", name="Import full map data", exact=True)
expect(
page.get_by_role("button", name="Copy into the layer", exact=True)
).to_be_hidden()
expect(button).to_be_visible()
button.click()
assert file_input.input_value()
@ -71,7 +80,7 @@ def test_umap_import_from_textarea(live_server, tilelayer, page, settings):
path = Path(__file__).parent.parent / "fixtures/test_upload_data.umap"
textarea.fill(path.read_text())
page.locator('select[name="format"]').select_option("umap")
page.get_by_role("button", name="Import", exact=True).click()
page.get_by_role("button", name="Import full map data", exact=True).click()
layers = page.locator(".umap-browser .datalayer")
expect(layers).to_have_count(2)
expect(page.locator(".umap-main-edit-toolbox .map-name")).to_have_text(
@ -106,7 +115,7 @@ def test_import_geojson_from_textarea(tilelayer, live_server, page):
path = Path(__file__).parent.parent / "fixtures/test_upload_data.json"
textarea.fill(path.read_text())
page.locator('select[name="format"]').select_option("geojson")
button = page.get_by_role("button", name="Import", exact=True)
button = page.get_by_role("button", name="Copy into the layer", exact=True)
expect(button).to_be_visible()
button.click()
# A layer has been created
@ -131,7 +140,7 @@ def test_import_kml_from_textarea(tilelayer, live_server, page):
path = Path(__file__).parent.parent / "fixtures/test_upload_data.kml"
textarea.fill(path.read_text())
page.locator('select[name="format"]').select_option("kml")
button = page.get_by_role("button", name="Import", exact=True)
button = page.get_by_role("button", name="Copy into the layer", exact=True)
expect(button).to_be_visible()
button.click()
# A layer has been created
@ -156,7 +165,7 @@ def test_import_gpx_from_textarea(tilelayer, live_server, page):
path = Path(__file__).parent.parent / "fixtures/test_upload_data.gpx"
textarea.fill(path.read_text())
page.locator('select[name="format"]').select_option("gpx")
button = page.get_by_role("button", name="Import", exact=True)
button = page.get_by_role("button", name="Copy into the layer", exact=True)
expect(button).to_be_visible()
button.click()
# A layer has been created
@ -179,7 +188,7 @@ def test_import_osm_from_textarea(tilelayer, live_server, page):
path = Path(__file__).parent.parent / "fixtures/test_upload_data_osm.json"
textarea.fill(path.read_text())
page.locator('select[name="format"]').select_option("osm")
page.get_by_role("button", name="Import", exact=True).click()
page.get_by_role("button", name="Copy into the layer", exact=True).click()
# A layer has been created
expect(layers).to_have_count(1)
expect(markers).to_have_count(2)
@ -199,7 +208,7 @@ def test_import_csv_from_textarea(tilelayer, live_server, page):
path = Path(__file__).parent.parent / "fixtures/test_upload_data.csv"
textarea.fill(path.read_text())
page.locator('select[name="format"]').select_option("csv")
page.get_by_role("button", name="Import", exact=True).click()
page.get_by_role("button", name="Copy into the layer", exact=True).click()
# A layer has been created
expect(layers).to_have_count(1)
expect(markers).to_have_count(2)
@ -218,7 +227,7 @@ def test_can_import_in_existing_datalayer(live_server, datalayer, page, openmap)
path = Path(__file__).parent.parent / "fixtures/test_upload_data.csv"
textarea.fill(path.read_text())
page.locator('select[name="format"]').select_option("csv")
page.get_by_role("button", name="Import", exact=True).click()
page.get_by_role("button", name="Copy into the layer", exact=True).click()
# No layer has been created
expect(layers).to_have_count(1)
expect(markers).to_have_count(3)
@ -238,7 +247,7 @@ def test_can_replace_datalayer_data(live_server, datalayer, page, openmap):
textarea.fill(path.read_text())
page.locator('select[name="format"]').select_option("csv")
page.get_by_label("Replace layer content").check()
page.get_by_role("button", name="Import", exact=True).click()
page.get_by_role("button", name="Copy into the layer", exact=True).click()
# No layer has been created
expect(layers).to_have_count(1)
expect(markers).to_have_count(2)
@ -260,7 +269,7 @@ def test_can_import_in_new_datalayer(live_server, datalayer, page, openmap):
page.get_by_label("Choose the layer to import").select_option(
label="Import in a new layer"
)
page.get_by_role("button", name="Import", exact=True).click()
page.get_by_role("button", name="Copy into the layer", exact=True).click()
# A new layer has been created
expect(layers).to_have_count(2)
expect(markers).to_have_count(3)
@ -302,7 +311,7 @@ def test_should_remove_dot_in_property_names(live_server, page, settings, tilela
textarea = page.locator(".umap-upload textarea")
textarea.fill(json.dumps(data))
page.locator('select[name="format"]').select_option("geojson")
page.get_by_role("button", name="Import", exact=True).click()
page.get_by_role("button", name="Copy into the layer", exact=True).click()
with page.expect_response(re.compile(r".*/datalayer/create/.*")):
page.get_by_role("button", name="Save").click()
datalayer = DataLayer.objects.last()
@ -363,7 +372,7 @@ def test_import_geometry_collection(live_server, page, tilelayer):
textarea = page.locator(".umap-upload textarea")
textarea.fill(json.dumps(data))
page.locator('select[name="format"]').select_option("geojson")
page.get_by_role("button", name="Import", exact=True).click()
page.get_by_role("button", name="Copy into the layer", exact=True).click()
# A layer has been created
expect(layers).to_have_count(1)
expect(markers).to_have_count(1)
@ -397,7 +406,7 @@ def test_import_multipolygon(live_server, page, tilelayer):
textarea = page.locator(".umap-upload textarea")
textarea.fill(json.dumps(data))
page.locator('select[name="format"]').select_option("geojson")
page.get_by_role("button", name="Import", exact=True).click()
page.get_by_role("button", name="Copy into the layer", exact=True).click()
# A layer has been created
expect(layers).to_have_count(1)
expect(paths).to_have_count(1)
@ -429,7 +438,7 @@ def test_import_multipolyline(live_server, page, tilelayer):
textarea = page.locator(".umap-upload textarea")
textarea.fill(json.dumps(data))
page.locator('select[name="format"]').select_option("geojson")
page.get_by_role("button", name="Import", exact=True).click()
page.get_by_role("button", name="Copy into the layer", exact=True).click()
# A layer has been created
expect(layers).to_have_count(1)
expect(paths).to_have_count(1)
@ -444,8 +453,44 @@ def test_import_csv_without_valid_latlon_headers(tilelayer, live_server, page):
textarea = page.locator(".umap-upload textarea")
textarea.fill("a,b,c\n12.23,48.34,mypoint\n12.23,48.34,mypoint2")
page.locator('select[name="format"]').select_option("csv")
page.get_by_role("button", name="Import", exact=True).click()
page.get_by_role("button", name="Copy into the layer", exact=True).click()
# FIXME do not create a layer
expect(layers).to_have_count(1)
expect(markers).to_have_count(0)
expect(page.locator('umap-alert div[data-level="error"]')).to_be_visible()
def test_create_remote_data(page, live_server, tilelayer):
def handle(route):
route.fulfill(
json={
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [4.3375, 51.2707],
},
}
],
}
)
# Intercept the route to the proxy
page.route("*/**/ajax-proxy/**", handle)
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 (Ctrl+I)").click()
page.get_by_placeholder("Provide an URL here").click()
page.get_by_placeholder("Provide an URL here").fill("https://remote.org/data.json")
page.get_by_label("Choose the data format").select_option("geojson")
page.get_by_role("button", name="Link to the layer as remote").click()
expect(page.locator(".leaflet-marker-icon")).to_be_visible()
page.get_by_role("link", name="Manage layers").click()
page.get_by_role("button", name="Edit", exact=True).click()
page.locator("summary").filter(has_text="Remote data").click()
expect(page.locator('input[name="url"]')).to_have_value(
"https://remote.org/data.json"
)