| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | import Combine from "../Base/Combine" | 
					
						
							|  |  |  | import { Store, Stores } from "../../Logic/UIEventSource" | 
					
						
							|  |  |  | 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" | 
					
						
							|  |  |  | import { parse } from "papaparse" | 
					
						
							|  |  |  | import { FixedUiElement } from "../Base/FixedUiElement" | 
					
						
							|  |  |  | import { TagUtils } from "../../Logic/Tags/TagUtils" | 
					
						
							| 
									
										
										
										
											2023-01-12 01:16:22 +01:00
										 |  |  | import { Feature, Point } from "geojson" | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | class FileSelector extends InputElementMap<FileList, { name: string; contents: Promise<string> }> { | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |     constructor(label: BaseUIElement) { | 
					
						
							|  |  |  |         super( | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             new FileSelectorButton(label, { allowMultiple: false, acceptType: "*" }), | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |             (x0, x1) => { | 
					
						
							|  |  |  |                 // Total hack: x1 is undefined is the backvalue - we effectively make this a one-way-story
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 return x1 === undefined || x0 === x1 | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |             }, | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             (filelist) => { | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |                 if (filelist === undefined) { | 
					
						
							|  |  |  |                     return undefined | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 const file = filelist.item(0) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 return { name: file.name, contents: file.text() } | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |             }, | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             (_) => undefined | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |         ) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * The first step in the import flow: load a file and validate that it is a correct geojson or CSV file | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | export class RequestFile extends Combine implements FlowStep<{ features: any[] }> { | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |     public readonly IsValid: Store<boolean> | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * The loaded GeoJSON | 
					
						
							|  |  |  |      */ | 
					
						
							| 
									
										
										
										
											2023-01-12 01:16:22 +01:00
										 |  |  |     public readonly Value: Store<{ features: Feature<Point>[] }> | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     constructor() { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const t = Translations.t.importHelper.selectFile | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |         const csvSelector = new FileSelector(new SubtleButton(undefined, t.description)) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02: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-01-19 20:34:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |         const text = Stores.flatten( | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             csvSelector.GetValue().map((v) => { | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |                 if (v === undefined) { | 
					
						
							|  |  |  |                     return undefined | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-06-05 02:24:14 +02:00
										 |  |  |                 return Stores.FromPromise(v.contents) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             }) | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const asGeoJson: Store<any | { error: string | BaseUIElement }> = text.map( | 
					
						
							|  |  |  |             (src: string) => { | 
					
						
							|  |  |  |                 if (src === undefined) { | 
					
						
							|  |  |  |                     return undefined | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 try { | 
					
						
							|  |  |  |                     const parsed = JSON.parse(src) | 
					
						
							|  |  |  |                     if (parsed["type"] !== "FeatureCollection") { | 
					
						
							|  |  |  |                         return { error: t.errNotFeatureCollection } | 
					
						
							| 
									
										
										
										
											2022-04-04 04:54:54 +02:00
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     if (parsed.features.some((f) => f.geometry.type != "Point")) { | 
					
						
							|  |  |  |                         return { error: t.errPointsOnly } | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     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 } | 
					
						
							|  |  |  |                             } | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     }) | 
					
						
							|  |  |  |                     return parsed | 
					
						
							|  |  |  |                 } catch (e) { | 
					
						
							|  |  |  |                     // Loading as CSV
 | 
					
						
							|  |  |  |                     var lines: string[][] = <any>parse(src).data | 
					
						
							|  |  |  |                     const header = lines[0] | 
					
						
							|  |  |  |                     lines.splice(0, 1) | 
					
						
							|  |  |  |                     if (header.indexOf("lat") < 0 || header.indexOf("lon") < 0) { | 
					
						
							|  |  |  |                         return { error: t.errNoLatOrLon } | 
					
						
							| 
									
										
										
										
											2022-04-04 04:54:54 +02:00
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2022-01-26 21:40:38 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     if (header.some((h) => h.trim() == "")) { | 
					
						
							|  |  |  |                         return { error: t.errNoName } | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     if (new Set(header).size !== header.length) { | 
					
						
							|  |  |  |                         return { error: t.errDuplicate } | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |                     } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                     const features = [] | 
					
						
							|  |  |  |                     for (let i = 0; i < lines.length; i++) { | 
					
						
							|  |  |  |                         const attrs = lines[i] | 
					
						
							|  |  |  |                         if (attrs.length == 0 || (attrs.length == 1 && attrs[0] == "")) { | 
					
						
							|  |  |  |                             // empty line
 | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |                             continue | 
					
						
							|  |  |  |                         } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                         const properties = {} | 
					
						
							|  |  |  |                         for (let i = 0; i < header.length; i++) { | 
					
						
							|  |  |  |                             const v = attrs[i] | 
					
						
							|  |  |  |                             if (v === undefined || v === "") { | 
					
						
							|  |  |  |                                 continue | 
					
						
							|  |  |  |                             } | 
					
						
							|  |  |  |                             properties[header[i]] = v | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |                         } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02: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) | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     return { | 
					
						
							|  |  |  |                         type: "FeatureCollection", | 
					
						
							|  |  |  |                         features, | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const errorIndicator = new VariableUiElement( | 
					
						
							|  |  |  |             asGeoJson.map((v) => { | 
					
						
							|  |  |  |                 if (v === undefined) { | 
					
						
							|  |  |  |                     return undefined | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 if (v?.error === undefined) { | 
					
						
							|  |  |  |                     return undefined | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 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, | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             errorIndicator, | 
					
						
							|  |  |  |         ]) | 
					
						
							| 
									
										
										
										
											2022-01-22 02:56:35 +01:00
										 |  |  |         this.SetClass("flex flex-col wi") | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         this.IsValid = asGeoJson.map( | 
					
						
							|  |  |  |             (geojson) => geojson !== undefined && geojson["error"] === undefined | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-01-19 20:34:04 +01:00
										 |  |  |         this.Value = asGeoJson | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | } |