| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  | import Combine from "../Base/Combine"; | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  | import {Store, Stores} from "../../Logic/UIEventSource"; | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  | import Translations from "../i18n/Translations"; | 
					
						
							|  |  |  | import {SubtleButton} from "../Base/SubtleButton"; | 
					
						
							|  |  |  | import {VariableUiElement} from "../Base/VariableUIElement"; | 
					
						
							|  |  |  | import Title from "../Base/Title"; | 
					
						
							|  |  |  | import InputElementMap from "../Input/InputElementMap"; | 
					
						
							|  |  |  | import BaseUIElement from "../BaseUIElement"; | 
					
						
							|  |  |  | import FileSelectorButton from "../Input/FileSelectorButton"; | 
					
						
							|  |  |  | import {FlowStep} from "./FlowStep"; | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  | import {parse} from "papaparse"; | 
					
						
							| 
									
										
										
										
											2022-02-01 19:02:46 +01:00
										 |  |  | import {FixedUiElement} from "../Base/FixedUiElement"; | 
					
						
							| 
									
										
										
										
											2022-04-04 04:54:54 +02:00
										 |  |  | import {TagUtils} from "../../Logic/Tags/TagUtils"; | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | class FileSelector extends InputElementMap<FileList, { name: string, contents: Promise<string> }> { | 
					
						
							|  |  |  |     constructor(label: BaseUIElement) { | 
					
						
							|  |  |  |         super( | 
					
						
							|  |  |  |             new FileSelectorButton(label, {allowMultiple: false, acceptType: "*"}), | 
					
						
							|  |  |  |             (x0, x1) => { | 
					
						
							|  |  |  |                 // Total hack: x1 is undefined is the backvalue - we effectively make this a one-way-story
 | 
					
						
							|  |  |  |                 return x1 === undefined || x0 === x1; | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             filelist => { | 
					
						
							|  |  |  |                 if (filelist === undefined) { | 
					
						
							|  |  |  |                     return undefined | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 const file = filelist.item(0) | 
					
						
							|  |  |  |                 return {name: file.name, contents: file.text()} | 
					
						
							|  |  |  |             }, | 
					
						
							|  |  |  |             _ => undefined | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * The first step in the import flow: load a file and validate that it is a correct geojson or CSV file | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-03-24 03:11:29 +01:00
										 |  |  | export class RequestFile extends Combine implements FlowStep<{features: any[]}> { | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     public readonly IsValid: Store<boolean> | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * The loaded GeoJSON | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     public readonly Value: Store<{features: any[]}> | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     constructor() { | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |         const t = Translations.t.importHelper.selectFile; | 
					
						
							|  |  |  |         const csvSelector = new FileSelector(new SubtleButton(undefined, t.description)) | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |         const loadedFiles = new VariableUiElement(csvSelector.GetValue().map(file => { | 
					
						
							|  |  |  |             if (file === undefined) { | 
					
						
							|  |  |  |                 return t.noFilesLoaded.SetClass("alert") | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return t.loadedFilesAre.Subs({file: file.name}).SetClass("thanks") | 
					
						
							|  |  |  |         })) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         const text = Stores.flatten( | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |             csvSelector.GetValue().map(v => { | 
					
						
							|  |  |  |                 if (v === undefined) { | 
					
						
							|  |  |  |                     return undefined | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                 return Stores.FromPromise(v.contents) | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |             })) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         const asGeoJson: Store<any | { error: string | BaseUIElement }> = text.map((src: string) => { | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |             if (src === undefined) { | 
					
						
							|  |  |  |                 return undefined | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             try { | 
					
						
							|  |  |  |                 const parsed = JSON.parse(src) | 
					
						
							|  |  |  |                 if (parsed["type"] !== "FeatureCollection") { | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |                     return {error: t.errNotFeatureCollection} | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  |                 if (parsed.features.some(f => f.geometry.type != "Point")) { | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |                     return {error: t.errPointsOnly} | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-04-04 04:54:54 +02:00
										 |  |  |                 parsed.features.forEach(f => { | 
					
						
							|  |  |  |                     const props = f.properties | 
					
						
							|  |  |  |                     for (const key in props) { | 
					
						
							|  |  |  |                         if(props[key] === undefined || props[key] === null || props[key] === ""){ | 
					
						
							|  |  |  |                             delete props[key] | 
					
						
							|  |  |  |                         }  | 
					
						
							|  |  |  |                     if(!TagUtils.isValidKey(key)){ | 
					
						
							|  |  |  |                         return {error: "Probably an invalid key: "+key} | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 }) | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |                 return parsed; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             } catch (e) { | 
					
						
							|  |  |  |                 // Loading as CSV
 | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  |                 var lines: string[][] = <any>parse(src).data; | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |                 const header = lines[0] | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |                 lines.splice(0, 1) | 
					
						
							|  |  |  |                 if (header.indexOf("lat") < 0 || header.indexOf("lon") < 0) { | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |                     return {error: t.errNoLatOrLon} | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |                 if (header.some(h => h.trim() == "")) { | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  |                     return {error: t.errNoName} | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 if (new Set(header).size !== header.length) { | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  |                     return {error: t.errDuplicate} | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |                 const features = [] | 
					
						
							|  |  |  |                 for (let i = 0; i < lines.length; i++) { | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |                     const attrs = lines[i]; | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  |                     if (attrs.length == 0 || (attrs.length == 1 && attrs[0] == "")) { | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |                         // empty line
 | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |                         continue | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     const properties = {} | 
					
						
							|  |  |  |                     for (let i = 0; i < header.length; i++) { | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  |                         const v = attrs[i] | 
					
						
							|  |  |  |                         if (v === undefined || v === "") { | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |                             continue | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         properties[header[i]] = v; | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |                     } | 
					
						
							|  |  |  |                     const coordinates = [Number(properties["lon"]), Number(properties["lat"])] | 
					
						
							|  |  |  |                     delete properties["lat"] | 
					
						
							|  |  |  |                     delete properties["lon"] | 
					
						
							|  |  |  |                     if (coordinates.some(isNaN)) { | 
					
						
							|  |  |  |                         return {error: "A coordinate could not be parsed for line " + (i + 2)} | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     const f = { | 
					
						
							|  |  |  |                         type: "Feature", | 
					
						
							|  |  |  |                         properties, | 
					
						
							|  |  |  |                         geometry: { | 
					
						
							|  |  |  |                             type: "Point", | 
					
						
							|  |  |  |                             coordinates | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     }; | 
					
						
							|  |  |  |                     features.push(f) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 return { | 
					
						
							|  |  |  |                     type: "FeatureCollection", | 
					
						
							|  |  |  |                     features | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const errorIndicator = new VariableUiElement(asGeoJson.map(v => { | 
					
						
							|  |  |  |             if (v === undefined) { | 
					
						
							|  |  |  |                 return undefined; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (v?.error === undefined) { | 
					
						
							|  |  |  |                 return undefined; | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-02-01 19:02:46 +01:00
										 |  |  |             let err: BaseUIElement; | 
					
						
							|  |  |  |             if(typeof v.error === "string"){ | 
					
						
							|  |  |  |                 err = new FixedUiElement(v.error) | 
					
						
							|  |  |  |             }else if(v.error.Clone !== undefined){ | 
					
						
							|  |  |  |                 err = v.error.Clone() | 
					
						
							|  |  |  |             }else{ | 
					
						
							|  |  |  |                 err = v.error | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             return err.SetClass("alert"); | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |         })) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         super([ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             new Title(t.title, 1), | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |             t.fileFormatDescription, | 
					
						
							|  |  |  |             t.fileFormatDescriptionCsv, | 
					
						
							|  |  |  |             t.fileFormatDescriptionGeoJson, | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |             csvSelector, | 
					
						
							|  |  |  |             loadedFiles, | 
					
						
							|  |  |  |             errorIndicator | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         ]); | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |         this.SetClass("flex flex-col wi") | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |         this.IsValid = asGeoJson.map(geojson => geojson !== undefined && geojson["error"] === undefined) | 
					
						
							|  |  |  |         this.Value = asGeoJson | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | } |