diff --git a/umap/static/umap/js/modules/data/layer.js b/umap/static/umap/js/modules/data/layer.js index d651cabf..0c89e286 100644 --- a/umap/static/umap/js/modules/data/layer.js +++ b/umap/static/umap/js/modules/data/layer.js @@ -252,10 +252,11 @@ export class DataLayer extends ServerStored { } fromGeoJSON(geojson, sync = true) { - this.addData(geojson, sync) + const features = this.addData(geojson, sync) this._geojson = geojson this.onDataLoaded() this.dataChanged() + return features } onDataLoaded() { @@ -315,7 +316,7 @@ export class DataLayer extends ServerStored { const response = await this._umap.request.get(url) if (response?.ok) { this.clear() - this._umap.formatter + return this._umap.formatter .parse(await response.text(), this.options.remoteData.format) .then((geojson) => this.fromGeoJSON(geojson)) } @@ -443,10 +444,11 @@ export class DataLayer extends ServerStored { try { // Do not fail if remote data is somehow invalid, // otherwise the layer becomes uneditable. - this.makeFeatures(geojson, sync) + return this.makeFeatures(geojson, sync) } catch (err) { console.log('Error with DataLayer', this.id) console.error(err) + return [] } } @@ -463,10 +465,13 @@ export class DataLayer extends ServerStored { ? geojson : geojson.features || geojson.geometries if (!collection) return + const features = [] this.sortFeatures(collection) - for (const feature of collection) { - this.makeFeature(feature, sync) + for (const featureJson of collection) { + const feature = this.makeFeature(featureJson, sync) + if (feature) features.push(feature) } + return features } makeFeature(geojson = {}, sync = true, id = null) { @@ -503,31 +508,47 @@ export class DataLayer extends ServerStored { } async importRaw(raw, format) { - this._umap.formatter + return this._umap.formatter .parse(raw, format) .then((geojson) => this.addData(geojson)) - .then(() => this.zoomTo()) - this.isDirty = true + .then((data) => { + if (data?.length) this.isDirty = true + return data + }) } - importFromFiles(files, type) { - for (const f of files) { - this.importFromFile(f, type) + readFile(f) { + return new Promise((resolve) => { + const reader = new FileReader() + reader.onloadend = () => resolve(reader.result) + reader.readAsText(f) + }) + } + + async importFromFiles(files, type) { + let all = [] + for (const file of files) { + const features = await this.importFromFile(file, type) + if (features) { + all = all.concat(features) + } } + return new Promise((resolve) => { + resolve(all) + }) } - importFromFile(f, type) { - const reader = new FileReader() + async importFromFile(file, type) { type = type || Utils.detectFileType(f) - reader.readAsText(f) - reader.onload = (e) => this.importRaw(e.target.result, type) + const raw = await this.readFile(file) + return this.importRaw(raw, type) } async importFromUrl(uri, type) { uri = this._umap.renderUrl(uri) const response = await this._umap.request.get(uri) if (response?.ok) { - this.importRaw(await response.text(), type) + return this.importRaw(await response.text(), type) } } @@ -930,9 +951,9 @@ export class DataLayer extends ServerStored { else this.hide() } - zoomTo() { + zoomTo(bounds) { if (!this.isVisible()) return - const bounds = this.layer.getBounds() + bounds = bounds || this.layer.getBounds() if (bounds.isValid()) { const options = { maxZoom: this.getOption('zoomTo') } this._leafletMap.fitBounds(bounds, options) diff --git a/umap/static/umap/js/modules/importer.js b/umap/static/umap/js/modules/importer.js index 756a1d2e..657dca9c 100644 --- a/umap/static/umap/js/modules/importer.js +++ b/umap/static/umap/js/modules/importer.js @@ -1,4 +1,8 @@ -import { DomEvent, DomUtil } from '../../vendors/leaflet/leaflet-src.esm.js' +import { + DomEvent, + DomUtil, + LatLngBounds, +} from '../../vendors/leaflet/leaflet-src.esm.js' import { uMapAlert as Alert } from '../components/alerts/alert.js' import { translate } from './i18n.js' import { SCHEMA } from './schema.js' @@ -270,16 +274,12 @@ export default class Importer extends Utils.WithTemplate { } submit() { - let hasErrors if (this.format === 'umap') { - hasErrors = !this.full() + this.full() } else if (!this.url) { - hasErrors = !this.copy() + this.copy() } else if (this.action) { - hasErrors = !this[this.action]() - } - if (hasErrors === false) { - Alert.info(translate('Data successfully imported!')) + this[this.action]() } } @@ -294,8 +294,9 @@ export default class Importer extends Utils.WithTemplate { } else if (this.url) { this._umap.importFromUrl(this.url, this.format) } + this.onSuccess() } catch (e) { - Alert.error(translate('Invalid umap data')) + this.onError(translate('Invalid umap data')) console.error(e) return false } @@ -306,7 +307,7 @@ export default class Importer extends Utils.WithTemplate { return false } if (!this.format) { - Alert.error(translate('Please choose a format')) + this.onError(translate('Please choose a format')) return false } const layer = this.layer @@ -318,26 +319,63 @@ export default class Importer extends Utils.WithTemplate { layer.options.remoteData.proxy = true layer.options.remoteData.ttl = SCHEMA.ttl.default } - layer.fetchRemoteData(true) + layer.fetchRemoteData(true).then((features) => { + if (features?.length) { + layer.zoomTo() + this.onSuccess() + } else { + this.onError() + } + }) } - copy() { + async 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')) + this.onError(translate('Please choose a format')) return false } + let promise const layer = this.layer if (this.clear) layer.empty() if (this.files.length) { - for (const file of this.files) { - this._umap.processFileToImport(file, layer, this.format) - } + promise = layer.importFromFiles(this.files, this.format) } else if (this.raw) { - layer.importRaw(this.raw, this.format) + promise = layer.importRaw(this.raw, this.format) } else if (this.url) { - layer.importFromUrl(this.url, this.format) + promise = layer.importFromUrl(this.url, this.format) + } + if (promise) promise.then((data) => this.onCopyFinished(layer, data)) + } + + onError(message = translate('No data has been found for import')) { + Alert.error(message) + } + + onSuccess(count) { + if (count) { + Alert.success(translate(`Successfully imported ${count} feature(s)`)) + } else { + Alert.success(translate('Data successfully imported!')) + } + } + + onCopyFinished(layer, features) { + // undefined features means error, let original error message pop + if (!features) return + if (!features.length) { + this.onError() + } else { + const bounds = new LatLngBounds() + for (const feature of features) { + const featureBounds = feature.ui.getBounds + ? feature.ui.getBounds() + : feature.ui.getCenter() + bounds.extend(featureBounds) + } + this.onSuccess(features.length) + layer.zoomTo(bounds) } } } diff --git a/umap/static/umap/js/modules/umap.js b/umap/static/umap/js/modules/umap.js index 1f6827b0..1dae4466 100644 --- a/umap/static/umap/js/modules/umap.js +++ b/umap/static/umap/js/modules/umap.js @@ -316,12 +316,14 @@ export default class Umap extends ServerStored { dataUrl = this.renderUrl(dataUrl) dataUrl = this.proxyUrl(dataUrl) const datalayer = this.createDataLayer() - await datalayer.importFromUrl(dataUrl, dataFormat) + await datalayer + .importFromUrl(dataUrl, dataFormat) + .then(() => datalayer.zoomTo()) } } else if (data) { data = decodeURIComponent(data) const datalayer = this.createDataLayer() - await datalayer.importRaw(data, dataFormat) + await datalayer.importRaw(data, dataFormat).then(() => datalayer.zoomTo()) } } @@ -1514,7 +1516,7 @@ export default class Umap extends ServerStored { processFileToImport(file, layer, type) { type = type || Utils.detectFileType(file) if (!type) { - U.Alert.error( + Alert.error( translate('Unable to detect format of file {filename}', { filename: file.name, })