Skip to content

Commit

Permalink
Merge pull request #1445 from umap-project/importer-class
Browse files Browse the repository at this point in the history
Make it easier to open file dialog
  • Loading branch information
yohanboniface authored Dec 8, 2023
2 parents 38ec262 + 50da2c0 commit 7ef3fb2
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 131 deletions.
130 changes: 1 addition & 129 deletions umap/static/umap/js/umap.controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ L.U.ImportAction = L.U.BaseAction.extend({
},

addHooks: function () {
this.map.importPanel()
this.map.importer.open()
},
})

Expand Down Expand Up @@ -1172,134 +1172,6 @@ L.U.Map.include({
this.ui.openPanel({ data: { html: container } })
},

importPanel: function () {
const container = L.DomUtil.create('div', 'umap-upload')
const title = L.DomUtil.create('h4', '', container)
const presetBox = L.DomUtil.create('div', 'formbox', container)
const presetSelect = L.DomUtil.create('select', '', presetBox)
const fileBox = L.DomUtil.create('div', 'formbox', container)
const fileInput = L.DomUtil.create('input', '', fileBox)
const urlInput = L.DomUtil.create('input', '', container)
const rawInput = L.DomUtil.create('textarea', '', container)
const typeLabel = L.DomUtil.create('label', '', container)
const layerLabel = L.DomUtil.create('label', '', container)
const clearLabel = L.DomUtil.create('label', '', container)
const submitInput = L.DomUtil.create('input', '', container)
const map = this
let option
const types = ['geojson', 'csv', 'gpx', 'kml', 'osm', 'georss', 'umap']
title.textContent = L._('Import data')
fileInput.type = 'file'
fileInput.multiple = 'multiple'
submitInput.type = 'button'
submitInput.value = L._('Import')
submitInput.className = 'button'
typeLabel.textContent = L._('Choose the format of the data to import')
this.help.button(typeLabel, 'importFormats')
const typeInput = L.DomUtil.create('select', '', typeLabel)
typeInput.name = 'format'
layerLabel.textContent = L._('Choose the layer to import in')
const layerInput = L.DomUtil.create('select', '', layerLabel)
layerInput.name = 'datalayer'
urlInput.type = 'text'
urlInput.placeholder = L._('Provide an URL here')
rawInput.placeholder = L._('Paste your data here')
clearLabel.textContent = L._('Replace layer content')
const clearFlag = L.DomUtil.create('input', '', clearLabel)
clearFlag.type = 'checkbox'
clearFlag.name = 'clear'
this.eachDataLayerReverse((datalayer) => {
if (datalayer.isLoaded() && !datalayer.isRemoteLayer()) {
const id = L.stamp(datalayer)
option = L.DomUtil.create('option', '', layerInput)
option.value = id
option.textContent = datalayer.options.name
}
})
L.DomUtil.element(
'option',
{ value: '', textContent: L._('Import in a new layer') },
layerInput
)
L.DomUtil.element(
'option',
{ value: '', textContent: L._('Choose the data format') },
typeInput
)
for (let i = 0; i < types.length; i++) {
option = L.DomUtil.create('option', '', typeInput)
option.value = option.textContent = types[i]
}
if (this.options.importPresets.length) {
const noPreset = L.DomUtil.create('option', '', presetSelect)
noPreset.value = noPreset.textContent = L._('Choose a preset')
for (let j = 0; j < this.options.importPresets.length; j++) {
option = L.DomUtil.create('option', '', presetSelect)
option.value = this.options.importPresets[j].url
option.textContent = this.options.importPresets[j].label
}
} else {
presetBox.style.display = 'none'
}

const submit = function () {
let type = typeInput.value
const layerId = layerInput[layerInput.selectedIndex].value
let layer
if (type === 'umap') {
this.once('postsync', function () {
this.setView(this.latLng(this.options.center), this.options.zoom)
})
}
if (layerId) layer = map.datalayers[layerId]
if (layer && clearFlag.checked) layer.empty()
if (fileInput.files.length) {
for (let i = 0, file; (file = fileInput.files[i]); i++) {
this.processFileToImport(file, layer, type)
}
} else {
if (!type)
return this.ui.alert({
content: L._('Please choose a format'),
level: 'error',
})
if (rawInput.value && type === 'umap') {
try {
this.importRaw(rawInput.value, type)
} catch (e) {
this.ui.alert({ content: L._('Invalid umap data'), level: 'error' })
console.error(e)
}
} else {
if (!layer) layer = this.createDataLayer()
if (rawInput.value) layer.importRaw(rawInput.value, type)
else if (urlInput.value) layer.importFromUrl(urlInput.value, type)
else if (presetSelect.selectedIndex > 0)
layer.importFromUrl(presetSelect[presetSelect.selectedIndex].value, type)
}
}
}
L.DomEvent.on(submitInput, 'click', submit, this)
L.DomEvent.on(
fileInput,
'change',
(e) => {
let type = '',
newType
for (let i = 0; i < e.target.files.length; i++) {
newType = L.Util.detectFileType(e.target.files[i])
if (!type && newType) type = newType
if (type && newType !== type) {
type = ''
break
}
}
typeInput.value = type
},
this
)
this.ui.openPanel({ data: { html: container }, className: 'dark' })
},
})

L.U.TileLayerControl = L.Control.extend({
Expand Down
1 change: 1 addition & 0 deletions umap/static/umap/js/umap.core.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ L.U.Keys = {
I: 73,
L: 76,
M: 77,
O: 79,
P: 80,
S: 83,
Z: 90,
Expand Down
166 changes: 166 additions & 0 deletions umap/static/umap/js/umap.importer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
L.U.Importer = L.Class.extend({
TYPES: ['geojson', 'csv', 'gpx', 'kml', 'osm', 'georss', 'umap'],
initialize: function (map) {
this.map = map
this.presets = map.options.importPresets
},

build: function () {
this.container = L.DomUtil.create('div', 'umap-upload')
this.title = L.DomUtil.add('h4', '', this.container, L._('Import data'))
this.presetBox = L.DomUtil.create('div', 'formbox', this.container)
this.presetSelect = L.DomUtil.create('select', '', this.presetBox)
this.fileBox = L.DomUtil.create('div', 'formbox', this.container)
this.fileInput = L.DomUtil.element(
'input',
{ type: 'file', multiple: 'multiple', autofocus: true },
this.fileBox
)
this.urlInput = L.DomUtil.element(
'input',
{ type: 'text', placeholder: L._('Provide an URL here') },
this.container
)
this.rawInput = L.DomUtil.element(
'textarea',
{ placeholder: L._('Paste your data here') },
this.container
)
this.typeLabel = L.DomUtil.add(
'label',
'',
this.container,
L._('Choose the format of the data to import')
)
this.layerLabel = L.DomUtil.add(
'label',
'',
this.container,
L._('Choose the layer to import in')
)
this.clearLabel = L.DomUtil.add(
'label',
'',
this.container,
L._('Replace layer content')
)
this.submitInput = L.DomUtil.element(
'input',
{ type: 'button', value: L._('Import'), className: 'button' },
this.container
)
this.map.help.button(this.typeLabel, 'importFormats')
this.typeInput = L.DomUtil.element('select', { name: 'format' }, this.typeLabel)
this.layerInput = L.DomUtil.element(
'select',
{ name: 'datalayer' },
this.layerLabel
)
this.clearFlag = L.DomUtil.element(
'input',
{ type: 'checkbox', name: 'clear' },
this.clearLabel
)
let option
this.map.eachDataLayerReverse((datalayer) => {
if (datalayer.isLoaded() && !datalayer.isRemoteLayer()) {
const id = L.stamp(datalayer)
option = L.DomUtil.add('option', '', this.layerInput, datalayer.options.name)
option.value = id
}
})
L.DomUtil.element(
'option',
{ value: '', textContent: L._('Import in a new layer') },
this.layerInput
)
L.DomUtil.element(
'option',
{ value: '', textContent: L._('Choose the data format') },
this.typeInput
)
for (let i = 0; i < this.TYPES.length; i++) {
option = L.DomUtil.create('option', '', this.typeInput)
option.value = option.textContent = this.TYPES[i]
}
if (this.presets.length) {
const noPreset = L.DomUtil.create('option', '', this.presetSelect)
noPreset.value = noPreset.textContent = L._('Choose a preset')
for (let j = 0; j < this.presets.length; j++) {
option = L.DomUtil.create('option', '', presetSelect)
option.value = this.presets[j].url
option.textContent = this.presets[j].label
}
} else {
this.presetBox.style.display = 'none'
}
L.DomEvent.on(this.submitInput, 'click', this.submit, this)
L.DomEvent.on(
this.fileInput,
'change',
(e) => {
let type = '',
newType
for (let i = 0; i < e.target.files.length; i++) {
newType = L.Util.detectFileType(e.target.files[i])
if (!type && newType) type = newType
if (type && newType !== type) {
type = ''
break
}
}
this.typeInput.value = type
},
this
)
},

open: function () {
if (!this.container) this.build()
this.map.ui.openPanel({ data: { html: this.container }, className: 'dark' })
},

openFiles: function () {
this.open()
this.fileInput.showPicker()
},

submit: function () {
let type = this.typeInput.value
const layerId = this.layerInput[this.layerInput.selectedIndex].value
let layer
if (type === 'umap') {
this.map.once('postsync', this.map._setDefaultCenter)
}
if (layerId) layer = this.map.datalayers[layerId]
if (layer && this.clearFlag.checked) layer.empty()
if (this.fileInput.files.length) {
for (let i = 0, file; (file = this.fileInput.files[i]); i++) {
this.map.processFileToImport(file, layer, type)
}
} else {
if (!type)
return this.map.ui.alert({
content: L._('Please choose a format'),
level: 'error',
})
if (this.rawInput.value && type === 'umap') {
try {
this.map.importRaw(this.rawInput.value, type)
} catch (e) {
this.ui.alert({ content: L._('Invalid umap data'), level: 'error' })
console.error(e)
}
} else {
if (!layer) layer = this.map.createDataLayer()
if (this.rawInput.value) layer.importRaw(this.rawInput.value, type)
else if (this.urlInput.value) layer.importFromUrl(this.urlInput.value, type)
else if (this.presetSelect.selectedIndex > 0)
layer.importFromUrl(
this.presetSelect[this.presetSelect.selectedIndex].value,
type
)
}
}
},
})
7 changes: 6 additions & 1 deletion umap/static/umap/js/umap.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ L.U.Map.include({
if (this.options.scrollWheelZoom) this.scrollWheelZoom.enable()
else this.scrollWheelZoom.disable()
this.browser = new L.U.Browser(this)
this.importer = new L.U.Importer(this)
this.drop = new L.U.DropControl(this)
this.renderControls()
},
Expand Down Expand Up @@ -560,7 +561,11 @@ L.U.Map.include({
}
if (key === L.U.Keys.I && modifierKey && this.editEnabled) {
L.DomEvent.stop(e)
this.importPanel()
this.importer.open()
}
if (key === L.U.Keys.O && modifierKey && this.editEnabled) {
L.DomEvent.stop(e)
this.importer.openFiles()
}
if (key === L.U.Keys.H && modifierKey && this.editEnabled) {
L.DomEvent.stop(e)
Expand Down
1 change: 1 addition & 0 deletions umap/static/umap/test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<script src="../js/umap.permissions.js"></script>
<script src="../js/umap.datalayer.permissions.js"></script>
<script src="../js/umap.browser.js"></script>
<script src="../js/umap.importer.js"></script>
<script src="../js/umap.js"></script>
<script src="../js/umap.ui.js"></script>
<link rel="stylesheet" href="../vendors/leaflet/leaflet.css" />
Expand Down
1 change: 1 addition & 0 deletions umap/templates/umap/js.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<script src="{{ STATIC_URL }}umap/js/umap.slideshow.js"></script>
<script src="{{ STATIC_URL }}umap/js/umap.tableeditor.js"></script>
<script src="{{ STATIC_URL }}umap/js/umap.browser.js"></script>
<script src="{{ STATIC_URL }}umap/js/umap.importer.js"></script>
<script src="{{ STATIC_URL }}umap/js/umap.js"></script>
<script src="{{ STATIC_URL }}umap/js/umap.ui.js"></script>
{% endcompress %}
26 changes: 25 additions & 1 deletion umap/tests/integration/test_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
pytestmark = pytest.mark.django_db


def test_umap_import(live_server, datalayer, page):
def test_umap_import_from_file(live_server, datalayer, page):
page.goto(f"{live_server.url}/map/new/")
button = page.get_by_title("Import data (Ctrl+I)")
expect(button).to_be_visible()
Expand All @@ -23,3 +23,27 @@ def test_umap_import(live_server, datalayer, page):
expect(layers).to_have_count(3)
nonloaded = page.locator(".umap-browse-datalayers li.off")
expect(nonloaded).to_have_count(1)


def test_umap_import_geojson_from_textarea(live_server, datalayer, page):
page.goto(f"{live_server.url}/map/new/")
layers = page.locator(".umap-browse-datalayers li")
markers = page.locator(".leaflet-marker-icon")
paths = page.locator("path")
expect(markers).to_have_count(0)
expect(paths).to_have_count(0)
expect(layers).to_have_count(1)
button = page.get_by_title("Import data (Ctrl+I)")
expect(button).to_be_visible()
button.click()
textarea = page.locator(".umap-upload textarea")
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)
expect(button).to_be_visible()
button.click()
# No layer has been created
expect(layers).to_have_count(1)
expect(markers).to_have_count(2)
expect(paths).to_have_count(3)

0 comments on commit 7ef3fb2

Please sign in to comment.