forked from MapComplete/MapComplete
		
	Fix import flow for more advanced scenarios
This commit is contained in:
		
							parent
							
								
									913dc07eea
								
							
						
					
					
						commit
						700b48f693
					
				
					 18 changed files with 871 additions and 575 deletions
				
			
		|  | @ -39,6 +39,9 @@ export default class SharedTagRenderings { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         dict.forEach((value, key) => { |         dict.forEach((value, key) => { | ||||||
|  |             if(key === "id"){ | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|             value.id = value.id ?? key; |             value.id = value.id ?? key; | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -62,4 +62,9 @@ export default class Toggleable extends Combine { | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public Collapse() : Toggleable{ | ||||||
|  |         this.isVisible.setData(false) | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  |      | ||||||
| } | } | ||||||
|  | @ -22,9 +22,12 @@ export default class Histogram<T> extends VariableUiElement { | ||||||
|     constructor(values: UIEventSource<string[]>, |     constructor(values: UIEventSource<string[]>, | ||||||
|                 title: string | BaseUIElement, |                 title: string | BaseUIElement, | ||||||
|                 countTitle: string | BaseUIElement, |                 countTitle: string | BaseUIElement, | ||||||
|                 assignColor?: (t0: string) => string |                 options?:{ | ||||||
|  |                 assignColor?: (t0: string) => string, | ||||||
|  |                     sortMode?: "name" | "name-rev" | "count" | "count-rev" | ||||||
|  |                 } | ||||||
|     ) { |     ) { | ||||||
|         const sortMode = new UIEventSource<"name" | "name-rev" | "count" | "count-rev">("name") |         const sortMode = new UIEventSource<"name" | "name-rev" | "count" | "count-rev">(options?.sortMode ??"name") | ||||||
|         const sortName = new VariableUiElement(sortMode.map(m => { |         const sortName = new VariableUiElement(sortMode.map(m => { | ||||||
|             switch (m) { |             switch (m) { | ||||||
|                 case "name": |                 case "name": | ||||||
|  | @ -107,11 +110,11 @@ export default class Histogram<T> extends VariableUiElement { | ||||||
|                 return Histogram.defaultPalette[index % Histogram.defaultPalette.length] |                 return Histogram.defaultPalette[index % Histogram.defaultPalette.length] | ||||||
|             }; |             }; | ||||||
|             let actualAssignColor = undefined; |             let actualAssignColor = undefined; | ||||||
|             if (assignColor === undefined) { |             if (options?.assignColor === undefined) { | ||||||
|                 actualAssignColor = fallbackColor; |                 actualAssignColor = fallbackColor; | ||||||
|             } else { |             } else { | ||||||
|                 actualAssignColor = (keyValue: string) => { |                 actualAssignColor = (keyValue: string) => { | ||||||
|                     return assignColor(keyValue) ?? fallbackColor(keyValue) |                     return options.assignColor(keyValue) ?? fallbackColor(keyValue) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -40,7 +40,11 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ | ||||||
|             tagRenderings: new Map() |             tagRenderings: new Map() | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const layerConfig = known_layers.filter(l => l.id === params.layer.id)[0] |          | ||||||
|  |         const layerConfig = known_layers.layers.filter(l => l.id === params.layer.id)[0] | ||||||
|  |         if(layerConfig === undefined){ | ||||||
|  |             console.error("WEIRD: layer not found in the builtin layer overview") | ||||||
|  |         } | ||||||
|         const importLayerJson = new CreateNoteImportLayer(365).convertStrict(convertState, <LayerConfigJson> layerConfig, "CompareToAlreadyExistingNotes") |         const importLayerJson = new CreateNoteImportLayer(365).convertStrict(convertState, <LayerConfigJson> layerConfig, "CompareToAlreadyExistingNotes") | ||||||
|         const importLayer = new LayerConfig(importLayerJson, "import-layer-dynamic") |         const importLayer = new LayerConfig(importLayerJson, "import-layer-dynamic") | ||||||
|         const flayer: FilteredLayer = { |         const flayer: FilteredLayer = { | ||||||
|  | @ -48,8 +52,8 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ | ||||||
|             isDisplayed: new UIEventSource<boolean>(true), |             isDisplayed: new UIEventSource<boolean>(true), | ||||||
|             layerDef: importLayer |             layerDef: importLayer | ||||||
|         } |         } | ||||||
|         const unfiltered = new GeoJsonSource(flayer, params.bbox.padAbsolute(0.0001)) |         const allNotesWithinBbox = new GeoJsonSource(flayer, params.bbox.padAbsolute(0.0001)) | ||||||
|         unfiltered.features.map(f => MetaTagging.addMetatags( |         allNotesWithinBbox.features.map(f => MetaTagging.addMetatags( | ||||||
|                 f, |                 f, | ||||||
|                 { |                 { | ||||||
|                     memberships: new RelationsTracker(), |                     memberships: new RelationsTracker(), | ||||||
|  | @ -65,30 +69,30 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ | ||||||
|                 } |                 } | ||||||
|             ) |             ) | ||||||
|         ) |         ) | ||||||
|         const data = new FilteringFeatureSource(state, undefined, unfiltered) |         const alreadyOpenImportNotes = new FilteringFeatureSource(state, undefined, allNotesWithinBbox) | ||||||
|         data.features.addCallbackD(features => console.log("Loaded and filtered features are", features)) |         alreadyOpenImportNotes.features.addCallbackD(features => console.log("Loaded and filtered features are", features)) | ||||||
|         const map = Minimap.createMiniMap() |         const map = Minimap.createMiniMap() | ||||||
|         map.SetClass("w-full").SetStyle("height: 500px") |         map.SetClass("w-full").SetStyle("height: 500px") | ||||||
| 
 | 
 | ||||||
|         const comparison = Minimap.createMiniMap({ |         const comparisonMap = Minimap.createMiniMap({ | ||||||
|             location: map.location, |             location: map.location, | ||||||
| 
 | 
 | ||||||
|         }) |         }) | ||||||
|         comparison.SetClass("w-full").SetStyle("height: 500px") |         comparisonMap.SetClass("w-full").SetStyle("height: 500px") | ||||||
| 
 | 
 | ||||||
|         new ShowDataLayer({ |         new ShowDataLayer({ | ||||||
|             layerToShow: importLayer, |             layerToShow: importLayer, | ||||||
|             state, |             state, | ||||||
|             zoomToFeatures: true, |             zoomToFeatures: true, | ||||||
|             leafletMap: map.leafletMap, |             leafletMap: map.leafletMap, | ||||||
|             features: data, |             features: alreadyOpenImportNotes, | ||||||
|             popup: (tags, layer) => new FeatureInfoBox(tags, layer, state) |             popup: (tags, layer) => new FeatureInfoBox(tags, layer, state) | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         const maxDistance = new UIEventSource<number>(5) |         const maxDistance = new UIEventSource<number>(5) | ||||||
| 
 | 
 | ||||||
|         const partitionedImportPoints = ImportUtils.partitionFeaturesIfNearby(params.geojson, data.features |         const partitionedImportPoints = ImportUtils.partitionFeaturesIfNearby(params.geojson, alreadyOpenImportNotes.features | ||||||
|             .map(ff => ({features: ff.map(ff => ff.feature)})), maxDistance) |             .map(ff => ({features: ff.map(ff => ff.feature)})), maxDistance) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -96,16 +100,22 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ | ||||||
|             layerToShow: new LayerConfig(import_candidate), |             layerToShow: new LayerConfig(import_candidate), | ||||||
|             state, |             state, | ||||||
|             zoomToFeatures: true, |             zoomToFeatures: true, | ||||||
|             leafletMap: comparison.leafletMap, |             leafletMap: comparisonMap.leafletMap, | ||||||
|             features: new StaticFeatureSource(partitionedImportPoints.map(p => p.hasNearby), false), |             features: new StaticFeatureSource(partitionedImportPoints.map(p => p.hasNearby), false), | ||||||
|             popup: (tags, layer) => new FeatureInfoBox(tags, layer, state) |             popup: (tags, layer) => new FeatureInfoBox(tags, layer, state) | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         super([ |         super([ | ||||||
|             new Title("Compare with already existing 'to-import'-notes"), |             new Title("Compare with already existing 'to-import'-notes"), | ||||||
|             new Toggle( |             new VariableUiElement( | ||||||
|                 new Loading("Fetching notes from OSM"), |                 alreadyOpenImportNotes.features.map(notesWithImport  => { | ||||||
|                 new Combine([ |                     if(allNotesWithinBbox.features.data === undefined || allNotesWithinBbox.features.data.length === 0){ | ||||||
|  |                        return  new Loading("Fetching notes from OSM") | ||||||
|  |                     } | ||||||
|  |                     if(notesWithImport.length === 0){ | ||||||
|  |                         return new FixedUiElement("No previous note to import found").SetClass("thanks") | ||||||
|  |                     } | ||||||
|  |                     return new Combine([ | ||||||
|                         map, |                         map, | ||||||
|                         "The following (red) elements are elements to import which are nearby a matching element that is already up for import. These won't be imported", |                         "The following (red) elements are elements to import which are nearby a matching element that is already up for import. These won't be imported", | ||||||
| 
 | 
 | ||||||
|  | @ -114,9 +124,10 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ | ||||||
|                             new VariableUiElement(partitionedImportPoints.map(({noNearby}) => noNearby.length + " elements can be imported")).SetClass("thanks p-8"), |                             new VariableUiElement(partitionedImportPoints.map(({noNearby}) => noNearby.length + " elements can be imported")).SetClass("thanks p-8"), | ||||||
|                             partitionedImportPoints.map(({noNearby}) => noNearby.length === 0) |                             partitionedImportPoints.map(({noNearby}) => noNearby.length === 0) | ||||||
|                         ).SetClass("w-full"), |                         ).SetClass("w-full"), | ||||||
|                     comparison, |                         comparisonMap, | ||||||
|                 ]).SetClass("flex flex-col"), |                     ]).SetClass("flex flex-col") | ||||||
|                 unfiltered.features.map(ff => ff === undefined || ff.length === 0) |                      | ||||||
|  |                 }, [allNotesWithinBbox.features]) | ||||||
|             ), |             ), | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -128,7 +139,18 @@ export class CompareToAlreadyExistingNotes extends Combine implements FlowStep<{ | ||||||
|             layer: params.layer |             layer: params.layer | ||||||
|         })) |         })) | ||||||
| 
 | 
 | ||||||
|         this.IsValid = data.features.map(ff => ff.length > 0 && partitionedImportPoints.data.noNearby.length > 0, [partitionedImportPoints]) |         this.IsValid = alreadyOpenImportNotes.features.map(ff => { | ||||||
|  |             if(allNotesWithinBbox.features.data.length === 0){ | ||||||
|  |                 // Not yet loaded
 | ||||||
|  |                 return false | ||||||
|  |             } | ||||||
|  |             if(ff.length == 0){ | ||||||
|  |                 // No import notes at all
 | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  |              | ||||||
|  |             return partitionedImportPoints.data.noNearby.length > 0; // at least _something_ can be imported
 | ||||||
|  |         }, [partitionedImportPoints, allNotesWithinBbox.features]) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  | @ -7,6 +7,7 @@ import Translations from "../i18n/Translations"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import Toggle from "../Input/Toggle"; | import Toggle from "../Input/Toggle"; | ||||||
| import {UIElement} from "../UIElement"; | import {UIElement} from "../UIElement"; | ||||||
|  | import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
| 
 | 
 | ||||||
| export interface FlowStep<T> extends BaseUIElement { | export interface FlowStep<T> extends BaseUIElement { | ||||||
|     readonly IsValid: UIEventSource<boolean> |     readonly IsValid: UIEventSource<boolean> | ||||||
|  | @ -95,10 +96,12 @@ export class FlowPanel<T> extends Toggle { | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         let elements: (BaseUIElement | string)[] = [] |         let elements: (BaseUIElement | string)[] = [] | ||||||
|  |         const isError = new UIEventSource(false) | ||||||
|         if (initial !== undefined) { |         if (initial !== undefined) { | ||||||
|             // Startup the flow
 |             // Startup the flow
 | ||||||
|             elements = [ |             elements = [ | ||||||
|                 initial, |                 initial, | ||||||
|  |        | ||||||
|                 new Combine([ |                 new Combine([ | ||||||
|                     backbutton, |                     backbutton, | ||||||
|                     new Toggle( |                     new Toggle( | ||||||
|  | @ -107,14 +110,25 @@ export class FlowPanel<T> extends Toggle { | ||||||
|                                 Svg.back_svg().SetStyle("transform: rotate(180deg);"), |                                 Svg.back_svg().SetStyle("transform: rotate(180deg);"), | ||||||
|                             isConfirm ? t.confirm : t.next |                             isConfirm ? t.confirm : t.next | ||||||
|                         ).onClick(() => { |                         ).onClick(() => { | ||||||
|  |                             try { | ||||||
|  | 
 | ||||||
|                                 const v = initial.Value.data; |                                 const v = initial.Value.data; | ||||||
|                                 nextStep.setData(constructNextstep(v, backButtonForNextStep)) |                                 nextStep.setData(constructNextstep(v, backButtonForNextStep)) | ||||||
|                                 currentStepActive.setData(false) |                                 currentStepActive.setData(false) | ||||||
|  |                             } catch (e) { | ||||||
|  |                                 console.error(e) | ||||||
|  |                                 isError.setData(true) | ||||||
|  |                             } | ||||||
|                         }), |                         }), | ||||||
|                         "Select a valid value to continue", |                         "Select a valid value to continue", | ||||||
|                         initial.IsValid |                         initial.IsValid | ||||||
|                     ) |                     ), | ||||||
|                 ]).SetClass("flex w-full justify-end space-x-2") |                     new Toggle( | ||||||
|  |                         new FixedUiElement("Something went wrong...").SetClass("alert"), | ||||||
|  |                         undefined, | ||||||
|  |                         isError), | ||||||
|  |                 ]).SetClass("flex w-full justify-end space-x-2"), | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|             ] |             ] | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -1,17 +1,14 @@ | ||||||
| import Combine from "../Base/Combine"; | import Combine from "../Base/Combine"; | ||||||
| import {LoginToggle} from "../Popup/LoginButton"; |  | ||||||
| import Toggle from "../Input/Toggle"; | import Toggle from "../Input/Toggle"; | ||||||
| import LanguagePicker from "../LanguagePicker"; | import LanguagePicker from "../LanguagePicker"; | ||||||
| import BackToIndex from "../BigComponents/BackToIndex"; | import BackToIndex from "../BigComponents/BackToIndex"; | ||||||
| import UserRelatedState from "../../Logic/State/UserRelatedState"; | import UserRelatedState from "../../Logic/State/UserRelatedState"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import MoreScreen from "../BigComponents/MoreScreen"; |  | ||||||
| import MinimapImplementation from "../Base/MinimapImplementation"; | import MinimapImplementation from "../Base/MinimapImplementation"; | ||||||
| import Translations from "../i18n/Translations"; | import Translations from "../i18n/Translations"; | ||||||
| import Constants from "../../Models/Constants"; |  | ||||||
| import {FlowPanelFactory} from "./FlowStep"; | import {FlowPanelFactory} from "./FlowStep"; | ||||||
| import {RequestFile} from "./RequestFile"; | import {RequestFile} from "./RequestFile"; | ||||||
| import {DataPanel} from "./DataPanel"; | import {PreviewPanel} from "./PreviewPanel"; | ||||||
| import ConflationChecker from "./ConflationChecker"; | import ConflationChecker from "./ConflationChecker"; | ||||||
| import {AskMetadata} from "./AskMetadata"; | import {AskMetadata} from "./AskMetadata"; | ||||||
| import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | import LayerConfig from "../../Models/ThemeConfig/LayerConfig"; | ||||||
|  | @ -21,8 +18,11 @@ import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import List from "../Base/List"; | import List from "../Base/List"; | ||||||
| import {CompareToAlreadyExistingNotes} from "./CompareToAlreadyExistingNotes"; | import {CompareToAlreadyExistingNotes} from "./CompareToAlreadyExistingNotes"; | ||||||
|  | import Introdution from "./Introdution"; | ||||||
|  | import LoginToImport from "./LoginToImport"; | ||||||
|  | import {MapPreview} from "./MapPreview"; | ||||||
| 
 | 
 | ||||||
| export default class ImportHelperGui extends LoginToggle { | export default class ImportHelperGui extends Combine { | ||||||
|     constructor() { |     constructor() { | ||||||
|         const t = Translations.t.importHelper; |         const t = Translations.t.importHelper; | ||||||
| 
 | 
 | ||||||
|  | @ -33,8 +33,11 @@ export default class ImportHelperGui extends LoginToggle { | ||||||
| 
 | 
 | ||||||
|         const {flow, furthestStep, titles} = |         const {flow, furthestStep, titles} = | ||||||
|             FlowPanelFactory |             FlowPanelFactory | ||||||
|                 .start("Select file", new RequestFile()) |                 .start("Introduction", new Introdution()) | ||||||
|                 .then("Inspect data", geojson => new DataPanel(state, geojson)) |                 .then("Login", _ => new LoginToImport(state)) | ||||||
|  |                 .then("Select file", _ => new RequestFile()) | ||||||
|  |                 .then("Inspect attributes", geojson => new PreviewPanel(state, geojson)) | ||||||
|  |                 .then("Inspect data", geojson => new MapPreview(state, geojson)) | ||||||
|                 .then("Compare with open notes", v => new CompareToAlreadyExistingNotes(state, v)) |                 .then("Compare with open notes", v => new CompareToAlreadyExistingNotes(state, v)) | ||||||
|                 .then("Compare with existing data", v => new ConflationChecker(state, v)) |                 .then("Compare with existing data", v => new ConflationChecker(state, v)) | ||||||
|                 .then("License and community check", v => new ConfirmProcess(v)) |                 .then("License and community check", v => new ConfirmProcess(v)) | ||||||
|  | @ -71,24 +74,12 @@ export default class ImportHelperGui extends LoginToggle { | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         super( |         super([ | ||||||
|             new Toggle( |  | ||||||
|                 new Combine([ |                 new Combine([ | ||||||
|                     leftBar, |                     leftBar, | ||||||
|                     flow.SetClass("m-8 w-full mb-24") |                     flow.SetClass("m-8 w-full mb-24") | ||||||
|                 ]).SetClass("h-full block md:flex") |                 ]).SetClass("h-full block md:flex")]) | ||||||
| 
 | 
 | ||||||
|                 , |  | ||||||
|                 new Combine([ |  | ||||||
|                     t.lockNotice.Subs(Constants.userJourney), |  | ||||||
|                     MoreScreen.CreateProffessionalSerivesButton() |  | ||||||
|                 ]) |  | ||||||
| 
 |  | ||||||
|                 , |  | ||||||
|                 state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.importHelperUnlock)), |  | ||||||
| 
 |  | ||||||
|             "Login needed...", |  | ||||||
|             state) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								UI/ImportFlow/ImportInspector.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								UI/ImportFlow/ImportInspector.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1 @@ | ||||||
|  | export default class ImportInspector {} | ||||||
|  | @ -7,9 +7,11 @@ export class ImportUtils { | ||||||
|             if (osmData?.features === undefined) { |             if (osmData?.features === undefined) { | ||||||
|                 return undefined |                 return undefined | ||||||
|             } |             } | ||||||
|  |             if(osmData.features.length === 0){ | ||||||
|  |                 return {noNearby: toPartitionFeatureCollection.features, hasNearby: []} | ||||||
|  |             } | ||||||
|             const maxDist = cutoffDistanceInMeters.data |             const maxDist = cutoffDistanceInMeters.data | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
|             const hasNearby = [] |             const hasNearby = [] | ||||||
|             const noNearby = [] |             const noNearby = [] | ||||||
|             for (const toImportElement of toPartitionFeatureCollection.features) { |             for (const toImportElement of toPartitionFeatureCollection.features) { | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								UI/ImportFlow/Introdution.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								UI/ImportFlow/Introdution.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,20 @@ | ||||||
|  | import Combine from "../Base/Combine"; | ||||||
|  | import {FlowStep} from "./FlowStep"; | ||||||
|  | import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
|  | import Translations from "../i18n/Translations"; | ||||||
|  | import Title from "../Base/Title"; | ||||||
|  | 
 | ||||||
|  | export default class Introdution extends Combine implements FlowStep<void> { | ||||||
|  |     readonly IsValid: UIEventSource<boolean> = new UIEventSource<boolean>(true); | ||||||
|  |     readonly Value: UIEventSource<void> = new UIEventSource<void>(undefined); | ||||||
|  |      | ||||||
|  |     constructor() { | ||||||
|  |         super([ | ||||||
|  |           new Title(  Translations.t.importHelper.title), | ||||||
|  |             Translations.t.importHelper.description, | ||||||
|  |             Translations.t.importHelper.importFormat, | ||||||
|  |         ]); | ||||||
|  |         this.SetClass("flex flex-col") | ||||||
|  |     } | ||||||
|  |      | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								UI/ImportFlow/LoginToImport.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								UI/ImportFlow/LoginToImport.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | ||||||
|  | import Combine from "../Base/Combine"; | ||||||
|  | import {FlowStep} from "./FlowStep"; | ||||||
|  | import UserRelatedState from "../../Logic/State/UserRelatedState"; | ||||||
|  | import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
|  | import Translations from "../i18n/Translations"; | ||||||
|  | import Title from "../Base/Title"; | ||||||
|  | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
|  | import {LoginToggle} from "../Popup/LoginButton"; | ||||||
|  | import Img from "../Base/Img"; | ||||||
|  | import Constants from "../../Models/Constants"; | ||||||
|  | import Toggle from "../Input/Toggle"; | ||||||
|  | import {SubtleButton} from "../Base/SubtleButton"; | ||||||
|  | import Svg from "../../Svg"; | ||||||
|  | import MoreScreen from "../BigComponents/MoreScreen"; | ||||||
|  | 
 | ||||||
|  | export default class LoginToImport extends Combine implements FlowStep<UserRelatedState> { | ||||||
|  |     readonly IsValid: UIEventSource<boolean>; | ||||||
|  |     readonly Value: UIEventSource<UserRelatedState>; | ||||||
|  | 
 | ||||||
|  |     constructor(state: UserRelatedState) { | ||||||
|  |         const t = Translations.t.importHelper | ||||||
|  |         const isValid = state.osmConnection.userDetails.map(ud => ud.csCount >= Constants.userJourney.importHelperUnlock) | ||||||
|  |         super([ | ||||||
|  |             new Title(t.userAccountTitle), | ||||||
|  |             new LoginToggle( | ||||||
|  |                 new VariableUiElement(state.osmConnection.userDetails.map(ud => { | ||||||
|  |                     if (ud === undefined) { | ||||||
|  |                         return undefined | ||||||
|  |                     } | ||||||
|  |                     return new Combine([ | ||||||
|  |                         new Img(ud.img ?? "./assets/svgs/help.svg").SetClass("w-16 h-16 rounded-full"), | ||||||
|  |                         t.loggedInWith.Subs(ud), | ||||||
|  |                         new SubtleButton(Svg.logout_svg().SetClass("h-8"), Translations.t.general.logout) | ||||||
|  |                             .onClick(() => state.osmConnection.LogOut()) | ||||||
|  |                     ]); | ||||||
|  |                 })), | ||||||
|  |                 new Combine([t.loginRequired, | ||||||
|  |                     MoreScreen.CreateProffessionalSerivesButton() | ||||||
|  |                 ]), | ||||||
|  |                 state | ||||||
|  |             ), | ||||||
|  |             new Toggle(undefined, | ||||||
|  |                 new Combine( | ||||||
|  |                     [t.lockNotice.Subs(Constants.userJourney).SetClass("alert"), | ||||||
|  |                         MoreScreen.CreateProffessionalSerivesButton()]) | ||||||
|  | 
 | ||||||
|  |                 , isValid) | ||||||
|  |         ]) | ||||||
|  |         this.Value = new UIEventSource<UserRelatedState>(state) | ||||||
|  |         this.IsValid = isValid; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -17,13 +17,13 @@ import ShowDataMultiLayer from "../ShowDataLayer/ShowDataMultiLayer"; | ||||||
| import FilteredLayer, {FilterState} from "../../Models/FilteredLayer"; | import FilteredLayer, {FilterState} from "../../Models/FilteredLayer"; | ||||||
| import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource"; | ||||||
| import Toggle from "../Input/Toggle"; | import Toggle from "../Input/Toggle"; | ||||||
| import Table from "../Base/Table"; |  | ||||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | import {VariableUiElement} from "../Base/VariableUIElement"; | ||||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | import {FixedUiElement} from "../Base/FixedUiElement"; | ||||||
| import {FlowStep} from "./FlowStep"; | import {FlowStep} from "./FlowStep"; | ||||||
| import ScrollableFullScreen from "../Base/ScrollableFullScreen"; | import ScrollableFullScreen from "../Base/ScrollableFullScreen"; | ||||||
| import {AllTagsPanel} from "../SpecialVisualizations"; | import {AllTagsPanel} from "../SpecialVisualizations"; | ||||||
| import Title from "../Base/Title"; | import Title from "../Base/Title"; | ||||||
|  | import CheckBoxes from "../Input/Checkboxes"; | ||||||
| 
 | 
 | ||||||
| class PreviewPanel extends ScrollableFullScreen { | class PreviewPanel extends ScrollableFullScreen { | ||||||
|      |      | ||||||
|  | @ -42,14 +42,14 @@ class PreviewPanel extends ScrollableFullScreen { | ||||||
| /** | /** | ||||||
|  * Shows the data to import on a map, asks for the correct layer to be selected |  * Shows the data to import on a map, asks for the correct layer to be selected | ||||||
|  */ |  */ | ||||||
| export class DataPanel extends Combine implements FlowStep<{ bbox: BBox, layer: LayerConfig, geojson: any }>{ | export class MapPreview extends Combine implements FlowStep<{ bbox: BBox, layer: LayerConfig, geojson: any }>{ | ||||||
|     public readonly IsValid: UIEventSource<boolean>; |     public readonly IsValid: UIEventSource<boolean>; | ||||||
|     public readonly Value: UIEventSource<{ bbox: BBox, layer: LayerConfig, geojson: any }> |     public readonly Value: UIEventSource<{ bbox: BBox, layer: LayerConfig, geojson: any }> | ||||||
|      |      | ||||||
|     constructor( |     constructor( | ||||||
|         state: UserRelatedState, |         state: UserRelatedState, | ||||||
|         geojson: { features: { properties: any, geometry: { coordinates: [number, number] } }[] }) { |         geojson: { features: { properties: any, geometry: { coordinates: [number, number] } }[] }) { | ||||||
|         const t = Translations.t.importHelper; |         const t = Translations.t.importHelper.mapPreview; | ||||||
| 
 | 
 | ||||||
|         const propertyKeys = new Set<string>() |         const propertyKeys = new Set<string>() | ||||||
|         for (const f of geojson.features) { |         for (const f of geojson.features) { | ||||||
|  | @ -58,7 +58,7 @@ export class DataPanel extends Combine implements FlowStep<{ bbox: BBox, layer: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         const availableLayers = AllKnownLayouts.AllPublicLayers().filter(l => l.name !== undefined && Constants.priviliged_layers.indexOf(l.id) < 0) |         const availableLayers = AllKnownLayouts.AllPublicLayers().filter(l => l.name !== undefined && Constants.priviliged_layers.indexOf(l.id) < 0) | ||||||
|         const layerPicker = new DropDown("Which layer does this import match with?", |         const layerPicker = new DropDown(t.selectLayer, | ||||||
|             [{shown: t.selectLayer, value: undefined}].concat(availableLayers.map(l => ({ |             [{shown: t.selectLayer, value: undefined}].concat(availableLayers.map(l => ({ | ||||||
|                 shown: l.name, |                 shown: l.name, | ||||||
|                 value: l |                 value: l | ||||||
|  | @ -126,18 +126,8 @@ export class DataPanel extends Combine implements FlowStep<{ bbox: BBox, layer: | ||||||
|         }) |         }) | ||||||
|         var bbox = matching.map(feats => BBox.bboxAroundAll(feats.map(f => new BBox([f.geometry.coordinates])))) |         var bbox = matching.map(feats => BBox.bboxAroundAll(feats.map(f => new BBox([f.geometry.coordinates])))) | ||||||
| 
 | 
 | ||||||
|         super([ |          | ||||||
|             new Title(geojson.features.length + " features to import"), |         const mismatchIndicator =   new VariableUiElement(matching.map(matching => { | ||||||
|             layerPicker, |  | ||||||
|             new Toggle("Automatically detected layer", undefined, autodetected), |  | ||||||
|             new Table(["", "Key", "Values", "Unique values seen"], |  | ||||||
|                 Array.from(propertyKeys).map(key => { |  | ||||||
|                     const uniqueValues = Utils.Dedup(Utils.NoNull(geojson.features.map(f => f.properties[key]))) |  | ||||||
|                     uniqueValues.sort() |  | ||||||
|                     return [geojson.features.filter(f => f.properties[key] !== undefined).length + "", key, uniqueValues.join(", "), "" + uniqueValues.length] |  | ||||||
|                 }) |  | ||||||
|             ).SetClass("zebra-table table-auto"), |  | ||||||
|             new VariableUiElement(matching.map(matching => { |  | ||||||
|             if (matching === undefined) { |             if (matching === undefined) { | ||||||
|                 return undefined |                 return undefined | ||||||
|             } |             } | ||||||
|  | @ -146,9 +136,18 @@ export class DataPanel extends Combine implements FlowStep<{ bbox: BBox, layer: | ||||||
|                 return undefined |                 return undefined | ||||||
|             } |             } | ||||||
|             const obligatory = layerPicker.GetValue().data?.source?.osmTags?.asHumanString(false, false, {}); |             const obligatory = layerPicker.GetValue().data?.source?.osmTags?.asHumanString(false, false, {}); | ||||||
|                 return new FixedUiElement(`${diff} features will _not_ match this layer. Make sure that all obligatory objects are present: ${obligatory}`).SetClass("alert"); |             return t.mismatch.Subs({count: diff, tags: obligatory}).SetClass("alert") | ||||||
|             })), |         })) | ||||||
|             map |          | ||||||
|  |         const confirm = new CheckBoxes([t.confirm]); | ||||||
|  |         super([ | ||||||
|  |             new Title(t.title, 1), | ||||||
|  |             layerPicker, | ||||||
|  |             new Toggle(t.autodetected.SetClass("thank"), undefined, autodetected), | ||||||
|  |            | ||||||
|  |         mismatchIndicator  , | ||||||
|  |             map, | ||||||
|  |             confirm | ||||||
|         ]); |         ]); | ||||||
| 
 | 
 | ||||||
|         this.Value = bbox.map(bbox => |         this.Value = bbox.map(bbox => | ||||||
|  | @ -157,13 +156,17 @@ export class DataPanel extends Combine implements FlowStep<{ bbox: BBox, layer: | ||||||
|                 geojson, |                 geojson, | ||||||
|                 layer: layerPicker.GetValue().data |                 layer: layerPicker.GetValue().data | ||||||
|             }), [layerPicker.GetValue()]) |             }), [layerPicker.GetValue()]) | ||||||
|  |          | ||||||
|         this.IsValid = matching.map(matching => { |         this.IsValid = matching.map(matching => { | ||||||
|             if (matching === undefined) { |             if (matching === undefined) { | ||||||
|                 return false |                 return false | ||||||
|             } |             } | ||||||
|  |             if(confirm.GetValue().data.length !== 1){ | ||||||
|  |                 return false | ||||||
|  |             } | ||||||
|             const diff = geojson.features.length - matching.length; |             const diff = geojson.features.length - matching.length; | ||||||
|             return diff === 0; |             return diff === 0; | ||||||
|         }) |         }, [confirm.GetValue()]) | ||||||
|          |          | ||||||
|     } |     } | ||||||
| } | } | ||||||
							
								
								
									
										98
									
								
								UI/ImportFlow/PreviewPanel.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								UI/ImportFlow/PreviewPanel.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,98 @@ | ||||||
|  | import Combine from "../Base/Combine"; | ||||||
|  | import {UIEventSource} from "../../Logic/UIEventSource"; | ||||||
|  | import UserRelatedState from "../../Logic/State/UserRelatedState"; | ||||||
|  | import Translations from "../i18n/Translations"; | ||||||
|  | import {Utils} from "../../Utils"; | ||||||
|  | import {FlowStep} from "./FlowStep"; | ||||||
|  | import Title from "../Base/Title"; | ||||||
|  | import BaseUIElement from "../BaseUIElement"; | ||||||
|  | import Histogram from "../BigComponents/Histogram"; | ||||||
|  | import Toggleable from "../Base/Toggleable"; | ||||||
|  | import List from "../Base/List"; | ||||||
|  | import CheckBoxes from "../Input/Checkboxes"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Shows the data to import on a map, asks for the correct layer to be selected | ||||||
|  |  */ | ||||||
|  | export class PreviewPanel extends Combine implements FlowStep<{ features: { properties: any, geometry: { coordinates: [number, number] } }[] }>{ | ||||||
|  |     public readonly IsValid: UIEventSource<boolean>; | ||||||
|  |     public readonly Value: UIEventSource< { features: { properties: any, geometry: { coordinates: [number, number] } }[] }> | ||||||
|  |      | ||||||
|  |     constructor( | ||||||
|  |         state: UserRelatedState, | ||||||
|  |         geojson: { features: { properties: any, geometry: { coordinates: [number, number] } }[] }) { | ||||||
|  |         const t = Translations.t.importHelper; | ||||||
|  |         console.log("Datapanel received", geojson) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         const propertyKeys = new Set<string>() | ||||||
|  |         for (const f of geojson.features) { | ||||||
|  |             Object.keys(f.properties).forEach(key => propertyKeys.add(key)) | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         const attributeOverview : BaseUIElement[] = [] | ||||||
|  | 
 | ||||||
|  |         const n = geojson.features.length; | ||||||
|  |         for (const key of Array.from(propertyKeys)) { | ||||||
|  |              | ||||||
|  |             const values = Utils.NoNull(geojson.features.map(f => f.properties[key])) | ||||||
|  |             const allSame = !values.some(v => v !== values[0]) | ||||||
|  |             if(allSame){ | ||||||
|  |                 attributeOverview.push(new Title(key+"="+values[0])) | ||||||
|  |                 if(values.length === n){ | ||||||
|  |                 attributeOverview.push(t.allAttributesSame) | ||||||
|  |                 }else{ | ||||||
|  |                     attributeOverview.push(t.someHaveSame.Subs({count: values.length, percentage: Math.floor(100 * values.length / n)})) | ||||||
|  |                 } | ||||||
|  |                 continue | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             const uniqueCount = new Set(values).size | ||||||
|  |             if(uniqueCount !== values.length){ | ||||||
|  |                 attributeOverview.push() | ||||||
|  |                 // There are some overlapping values: histogram time!
 | ||||||
|  |                 let hist : BaseUIElement = new Histogram( | ||||||
|  |                     new UIEventSource<string[]>(values), | ||||||
|  |                     "Value", | ||||||
|  |                     "Occurence", | ||||||
|  |                     { | ||||||
|  |                         sortMode: "count-rev" | ||||||
|  |                     } | ||||||
|  |                      | ||||||
|  |                 ) | ||||||
|  |                  | ||||||
|  |                 const title = new Title(key+"=*") | ||||||
|  |                 if(uniqueCount > 15){ | ||||||
|  |                     hist = new Toggleable(title,  | ||||||
|  |                         hist.SetClass("block") | ||||||
|  |                     ).Collapse() | ||||||
|  |                      | ||||||
|  |                 }else{ | ||||||
|  |                     attributeOverview.push(title) | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  |                 attributeOverview.push(hist) | ||||||
|  |                 continue | ||||||
|  |             } | ||||||
|  |          | ||||||
|  |             // All values are different, we add a boring (but collapsable) list
 | ||||||
|  |             attributeOverview.push(new Toggleable( | ||||||
|  |                 new Title(key+"=*"), | ||||||
|  |                     new List(values) | ||||||
|  |             ) ) | ||||||
|  | 
 | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         const confirm = new CheckBoxes([t.inspectLooksCorrect]) | ||||||
|  |          | ||||||
|  |         super([ | ||||||
|  |             new Title(t.inspectDataTitle.Subs({count:geojson.features.length })), | ||||||
|  |             ...attributeOverview, | ||||||
|  |             confirm | ||||||
|  |         ]); | ||||||
|  | 
 | ||||||
|  |         this.Value = new UIEventSource<{features: {properties: any; geometry: {coordinates: [number, number]}}[]}>(geojson) | ||||||
|  |         this.IsValid = confirm.GetValue().map(selected => selected.length == 1) | ||||||
|  |          | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -9,6 +9,7 @@ import InputElementMap from "../Input/InputElementMap"; | ||||||
| import BaseUIElement from "../BaseUIElement"; | import BaseUIElement from "../BaseUIElement"; | ||||||
| import FileSelectorButton from "../Input/FileSelectorButton"; | import FileSelectorButton from "../Input/FileSelectorButton"; | ||||||
| import {FlowStep} from "./FlowStep"; | import {FlowStep} from "./FlowStep"; | ||||||
|  | import { parse } from "papaparse"; | ||||||
| 
 | 
 | ||||||
| class FileSelector extends InputElementMap<FileList, { name: string, contents: Promise<string> }> { | class FileSelector extends InputElementMap<FileList, { name: string, contents: Promise<string> }> { | ||||||
|     constructor(label: BaseUIElement) { |     constructor(label: BaseUIElement) { | ||||||
|  | @ -42,8 +43,8 @@ export class RequestFile extends Combine implements FlowStep<any> { | ||||||
|     public readonly Value: UIEventSource<any> |     public readonly Value: UIEventSource<any> | ||||||
| 
 | 
 | ||||||
|     constructor() { |     constructor() { | ||||||
|         const t = Translations.t.importHelper; |         const t = Translations.t.importHelper.selectFile; | ||||||
|         const csvSelector = new FileSelector(new SubtleButton(undefined, t.selectFile)) |         const csvSelector = new FileSelector(new SubtleButton(undefined, t.description)) | ||||||
|         const loadedFiles = new VariableUiElement(csvSelector.GetValue().map(file => { |         const loadedFiles = new VariableUiElement(csvSelector.GetValue().map(file => { | ||||||
|             if (file === undefined) { |             if (file === undefined) { | ||||||
|                 return t.noFilesLoaded.SetClass("alert") |                 return t.noFilesLoaded.SetClass("alert") | ||||||
|  | @ -59,39 +60,53 @@ export class RequestFile extends Combine implements FlowStep<any> { | ||||||
|                 return UIEventSource.FromPromise(v.contents) |                 return UIEventSource.FromPromise(v.contents) | ||||||
|             })) |             })) | ||||||
| 
 | 
 | ||||||
|         const asGeoJson: UIEventSource<any | { error: string }> = text.map(src => { |         const asGeoJson: UIEventSource<any | { error: string | BaseUIElement }> = text.map(src => { | ||||||
|             if (src === undefined) { |             if (src === undefined) { | ||||||
|                 return undefined |                 return undefined | ||||||
|             } |             } | ||||||
|             try { |             try { | ||||||
|                 const parsed = JSON.parse(src) |                 const parsed = JSON.parse(src) | ||||||
|                 if (parsed["type"] !== "FeatureCollection") { |                 if (parsed["type"] !== "FeatureCollection") { | ||||||
|                     return {error: "The loaded JSON-file is not a geojson-featurecollection"} |                     return {error: t.errNotFeatureCollection} | ||||||
|                 } |                 } | ||||||
|                 if (parsed.features.some(f => f.geometry.type != "Point")) { |                 if (parsed.features.some(f => f.geometry.type != "Point")) { | ||||||
|                     return {error: "The loaded JSON-file should only contain points"} |                     return {error: t.errPointsOnly} | ||||||
|                 } |                 } | ||||||
|                 return parsed; |                 return parsed; | ||||||
| 
 | 
 | ||||||
|             } catch (e) { |             } catch (e) { | ||||||
|                 // Loading as CSV
 |                 // Loading as CSV
 | ||||||
|                 const lines = src.split("\n") |                 var lines : string[][] = <any> parse(src).data; | ||||||
|                 const header = lines[0].split(",") |                 const header = lines[0] | ||||||
|                 lines.splice(0, 1) |                 lines.splice(0, 1) | ||||||
|                 if (header.indexOf("lat") < 0 || header.indexOf("lon") < 0) { |                 if (header.indexOf("lat") < 0 || header.indexOf("lon") < 0) { | ||||||
|                     return {error: "The header does not contain `lat` or `lon`"} |                     return {error: t.errNoLatOrLon} | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|  |                 if (header.some(h => h.trim() == "")) { | ||||||
|  |                     return {error:t.errNoName} | ||||||
|  |                 } | ||||||
|  |              | ||||||
|  | 
 | ||||||
|  |                 if (new Set(header).size !== header.length) { | ||||||
|  |                     return {error:t.errDuplicate} | ||||||
|  |                 } | ||||||
|  |              | ||||||
|  | 
 | ||||||
|                 const features = [] |                 const features = [] | ||||||
|                 for (let i = 0; i < lines.length; i++) { |                 for (let i = 0; i < lines.length; i++) { | ||||||
|                     const line = lines[i]; |                     const attrs = lines[i]; | ||||||
|                     if (line.trim() === "") { |                     if(attrs.length == 0 || (attrs.length == 1 && attrs[0] == "")){ | ||||||
|  |                         // empty line
 | ||||||
|                         continue |                         continue | ||||||
|                     } |                     } | ||||||
|                     const attrs = line.split(",") |  | ||||||
|                     const properties = {} |                     const properties = {} | ||||||
|                     for (let i = 0; i < header.length; i++) { |                     for (let i = 0; i < header.length; i++) { | ||||||
|                         properties[header[i]] = attrs[i]; |                         const v =  attrs[i] | ||||||
|  |                         if(v === undefined || v === ""){ | ||||||
|  |                             continue | ||||||
|  |                         } | ||||||
|  |                         properties[header[i]] = v; | ||||||
|                     } |                     } | ||||||
|                     const coordinates = [Number(properties["lon"]), Number(properties["lat"])] |                     const coordinates = [Number(properties["lon"]), Number(properties["lat"])] | ||||||
|                     delete properties["lat"] |                     delete properties["lat"] | ||||||
|  | @ -125,18 +140,21 @@ export class RequestFile extends Combine implements FlowStep<any> { | ||||||
|             if (v?.error === undefined) { |             if (v?.error === undefined) { | ||||||
|                 return undefined; |                 return undefined; | ||||||
|             } |             } | ||||||
|             return new FixedUiElement(v?.error).SetClass("alert"); |             return v.error.Clone().SetClass("alert"); | ||||||
|         })) |         })) | ||||||
| 
 | 
 | ||||||
|         super([ |         super([ | ||||||
| 
 | 
 | ||||||
|             new Title(t.title, 1), |             new Title(t.title, 1), | ||||||
|             t.description, |             t.fileFormatDescription, | ||||||
|  |             t.fileFormatDescriptionCsv, | ||||||
|  |             t.fileFormatDescriptionGeoJson, | ||||||
|             csvSelector, |             csvSelector, | ||||||
|             loadedFiles, |             loadedFiles, | ||||||
|             errorIndicator |             errorIndicator | ||||||
| 
 | 
 | ||||||
|         ]); |         ]); | ||||||
|  |         this.SetClass("flex flex-col wi") | ||||||
|         this.IsValid = asGeoJson.map(geojson => geojson !== undefined && geojson["error"] === undefined) |         this.IsValid = asGeoJson.map(geojson => geojson !== undefined && geojson["error"] === undefined) | ||||||
|         this.Value = asGeoJson |         this.Value = asGeoJson | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | @ -455,7 +455,7 @@ export default class SpecialVisualizations { | ||||||
|                                     return undefined; |                                     return undefined; | ||||||
|                                 } |                                 } | ||||||
|                             }) |                             }) | ||||||
|                         return new Histogram(listSource, args[1], args[2], assignColors) |                         return new Histogram(listSource, args[1], args[2], {assignColor: assignColors}) | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|                 { |                 { | ||||||
|  |  | ||||||
|  | @ -70,6 +70,7 @@ | ||||||
|     "readMessages": "You have unread messages. Read these before deleting a point - someone might have feedback" |     "readMessages": "You have unread messages. Read these before deleting a point - someone might have feedback" | ||||||
|   }, |   }, | ||||||
|   "general": { |   "general": { | ||||||
|  |     "logout": "Log out", | ||||||
|     "next": "Next", |     "next": "Next", | ||||||
|     "confirm": "Confirm", |     "confirm": "Confirm", | ||||||
|     "back": "Back", |     "back": "Back", | ||||||
|  | @ -466,18 +467,47 @@ | ||||||
|     "layerName": "Possible {title}", |     "layerName": "Possible {title}", | ||||||
|     "description": "A layer which imports entries for {title}", |     "description": "A layer which imports entries for {title}", | ||||||
|     "popupTitle": "Possible {title}", |     "popupTitle": "Possible {title}", | ||||||
|         "importButton": "import_button({layerId}, _tags, There might be a {title} here,./assets/svg/addSmall.svg,,,id)", |     "importButton": "import_button({layerId}, _tags, I have found a {title} here - add it to the map,./assets/svg/addSmall.svg,,,id)", | ||||||
|  |     "notFound": "I could not find {title} - remove it", | ||||||
|  |     "alreadyMapped": "There already is another {title} on the map - this point is a duplicate", | ||||||
|     "importHandled": "<div class='thanks'>This feature has been handled! Thanks for your effort</div>" |     "importHandled": "<div class='thanks'>This feature has been handled! Thanks for your effort</div>" | ||||||
|   }, |   }, | ||||||
|   "importHelper": { |   "importHelper": { | ||||||
|     "title": "Import helper", |     "title": "Import helper", | ||||||
|         "description": "The import helper converts an external dataset to notes", |     "description": "The import helper converts an external dataset to notes. The external dataset must match one of the existing MapComplete layers. For every item you put in the importer, a single note will be created. These notes will be shown together with the relevant features in these maps to easily add them.", | ||||||
|  |     "importFormat": "A text in a note should have the following format in order to be picked up: <br/><div class='literal-code'>[A bit of introduction]<br/>https://mapcomplete.osm.be/[themename].html?[parameters such as lat and lon]#import<br/>[all tags of the feature] </div>", | ||||||
|  |     "userAccountTitle": "Select user account", | ||||||
|  |     "loggedInWith": "You are currently logged in as {name} and have made {csCount} changesets", | ||||||
|  |     "loginRequired": "You have to be logged in to continue", | ||||||
|  |     "locked": "You need at least {importHelperUnlock} to use the import helper", | ||||||
|     "lockNotice": "This page is locked. You need {importHelperUnlock} changesets before you can access here.", |     "lockNotice": "This page is locked. You need {importHelperUnlock} changesets before you can access here.", | ||||||
|         "selectFile": "Select a .csv or .geojson file to get started", |  | ||||||
|         "loadedFilesAre": "Currently loaded file is {file}", |  | ||||||
|         "noFilesLoaded": "No file is currently loaded", |  | ||||||
|     "selectLayer": "Select a layer...", |     "selectLayer": "Select a layer...", | ||||||
|         "selectFileTitle": "Select file", |     "selectFile": { | ||||||
|         "validateDataTitle": "Validate data" |       "title": "Select file", | ||||||
|  |       "description": "Select a .csv or .geojson file to get started", | ||||||
|  |       "fileFormatDescription": "Select a <b class='code'>.csv</b> or a <b class='code'>.geojson</b> file", | ||||||
|  |       "fileFormatDescriptionCsv": "In the CSV-file, there should be a column <span class='literal-code'>lat</span> and <span class='literal-code'>lon</span> with the coordinates in WGS84. There should be an additional column for every attribute.", | ||||||
|  |       "fileFormatDescriptionGeoJson": "In the geojson file, only points should be present. The properties should be exactly those properties that should go into OpenStreetMap", | ||||||
|  |       "errNoName": "Some columns don't have a name", | ||||||
|  |       "noFilesLoaded": "No file is currently loaded", | ||||||
|  |       "errDuplicate": "Some columns have the same name", | ||||||
|  |       "loadedFilesAre": "Currently loaded file is {file}", | ||||||
|  |       "errNoLatOrLon":"The header does not contain `lat` or `lon`", | ||||||
|  |       "errPointsOnly": "The loaded JSON-file should only contain points", | ||||||
|  |       "errNotFeatureCollection": "The loaded JSON-file is not a geojson-featurecollection" | ||||||
|  |     }, | ||||||
|  |     "mapPreview": { | ||||||
|  |       "title": "Map preview", | ||||||
|  |       "autodetected": "The layer was automatically deducted based on the properties", | ||||||
|  |       "selectLayer": "Which layer does this import match with?", | ||||||
|  |       "mismatch": "{count} features did not match the selected layer. Make sure that the tags to indicate the type are present, namely {tags}", | ||||||
|  |       "confirm": "The features are on the right location on the map" | ||||||
|  |     }, | ||||||
|  |     "validateDataTitle": "Validate data", | ||||||
|  |     "allAttributesSame": "All features to import have this tag", | ||||||
|  |     "someHaveSame": "{count} features to import have this tag, this is {percentage}% of the total", | ||||||
|  |     "inspectDataTitle": "Inspect data of {count} features to import", | ||||||
|  |     "inspectDidAutoDected": "Layer was chosen automatically", | ||||||
|  |     "inspectLooksCorrect": "These values look correct" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										34
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										34
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							|  | @ -20,6 +20,7 @@ | ||||||
|         "@types/leaflet-providers": "^1.2.0", |         "@types/leaflet-providers": "^1.2.0", | ||||||
|         "@types/leaflet.markercluster": "^1.4.3", |         "@types/leaflet.markercluster": "^1.4.3", | ||||||
|         "@types/lz-string": "^1.3.34", |         "@types/lz-string": "^1.3.34", | ||||||
|  |         "@types/papaparse": "^5.3.1", | ||||||
|         "@types/prompt-sync": "^4.1.0", |         "@types/prompt-sync": "^4.1.0", | ||||||
|         "@types/wikidata-sdk": "^6.1.0", |         "@types/wikidata-sdk": "^6.1.0", | ||||||
|         "country-language": "^0.1.7", |         "country-language": "^0.1.7", | ||||||
|  | @ -44,6 +45,7 @@ | ||||||
|         "opening_hours": "^3.6.0", |         "opening_hours": "^3.6.0", | ||||||
|         "osm-auth": "^1.0.2", |         "osm-auth": "^1.0.2", | ||||||
|         "osmtogeojson": "^3.0.0-beta.4", |         "osmtogeojson": "^3.0.0-beta.4", | ||||||
|  |         "papaparse": "^5.3.1", | ||||||
|         "parcel": "^1.2.4", |         "parcel": "^1.2.4", | ||||||
|         "prompt-sync": "^4.2.0", |         "prompt-sync": "^4.2.0", | ||||||
|         "svg-resizer": "github:vieron/svg-resizer", |         "svg-resizer": "github:vieron/svg-resizer", | ||||||
|  | @ -3254,8 +3256,15 @@ | ||||||
|     "node_modules/@types/node": { |     "node_modules/@types/node": { | ||||||
|       "version": "7.10.14", |       "version": "7.10.14", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/node/-/node-7.10.14.tgz", |       "resolved": "https://registry.npmjs.org/@types/node/-/node-7.10.14.tgz", | ||||||
|       "integrity": "sha512-29GS75BE8asnTno3yB6ubOJOO0FboExEqNJy4bpz0GSmW/8wPTNL4h9h63c6s1uTrOopCmJYe/4yJLh5r92ZUA==", |       "integrity": "sha512-29GS75BE8asnTno3yB6ubOJOO0FboExEqNJy4bpz0GSmW/8wPTNL4h9h63c6s1uTrOopCmJYe/4yJLh5r92ZUA==" | ||||||
|       "dev": true |     }, | ||||||
|  |     "node_modules/@types/papaparse": { | ||||||
|  |       "version": "5.3.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.1.tgz", | ||||||
|  |       "integrity": "sha512-1lbngk9wty2kCyQB42LjqSa12SEop3t9wcEC7/xYr3ujTSTmv7HWKjKYXly0GkMfQ42PRb2lFPFEibDOiMXS0g==", | ||||||
|  |       "dependencies": { | ||||||
|  |         "@types/node": "*" | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/@types/parse-json": { |     "node_modules/@types/parse-json": { | ||||||
|       "version": "4.0.0", |       "version": "4.0.0", | ||||||
|  | @ -10090,6 +10099,11 @@ | ||||||
|       "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", |       "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", | ||||||
|       "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" |       "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/papaparse": { | ||||||
|  |       "version": "5.3.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.1.tgz", | ||||||
|  |       "integrity": "sha512-Dbt2yjLJrCwH2sRqKFFJaN5XgIASO9YOFeFP8rIBRG2Ain8mqk5r1M6DkfvqEVozVcz3r3HaUGw253hA1nLIcA==" | ||||||
|  |     }, | ||||||
|     "node_modules/parcel": { |     "node_modules/parcel": { | ||||||
|       "version": "1.12.4", |       "version": "1.12.4", | ||||||
|       "resolved": "https://registry.npmjs.org/parcel/-/parcel-1.12.4.tgz", |       "resolved": "https://registry.npmjs.org/parcel/-/parcel-1.12.4.tgz", | ||||||
|  | @ -19227,8 +19241,15 @@ | ||||||
|     "@types/node": { |     "@types/node": { | ||||||
|       "version": "7.10.14", |       "version": "7.10.14", | ||||||
|       "resolved": "https://registry.npmjs.org/@types/node/-/node-7.10.14.tgz", |       "resolved": "https://registry.npmjs.org/@types/node/-/node-7.10.14.tgz", | ||||||
|       "integrity": "sha512-29GS75BE8asnTno3yB6ubOJOO0FboExEqNJy4bpz0GSmW/8wPTNL4h9h63c6s1uTrOopCmJYe/4yJLh5r92ZUA==", |       "integrity": "sha512-29GS75BE8asnTno3yB6ubOJOO0FboExEqNJy4bpz0GSmW/8wPTNL4h9h63c6s1uTrOopCmJYe/4yJLh5r92ZUA==" | ||||||
|       "dev": true |     }, | ||||||
|  |     "@types/papaparse": { | ||||||
|  |       "version": "5.3.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.3.1.tgz", | ||||||
|  |       "integrity": "sha512-1lbngk9wty2kCyQB42LjqSa12SEop3t9wcEC7/xYr3ujTSTmv7HWKjKYXly0GkMfQ42PRb2lFPFEibDOiMXS0g==", | ||||||
|  |       "requires": { | ||||||
|  |         "@types/node": "*" | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     "@types/parse-json": { |     "@types/parse-json": { | ||||||
|       "version": "4.0.0", |       "version": "4.0.0", | ||||||
|  | @ -24635,6 +24656,11 @@ | ||||||
|       "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", |       "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", | ||||||
|       "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" |       "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" | ||||||
|     }, |     }, | ||||||
|  |     "papaparse": { | ||||||
|  |       "version": "5.3.1", | ||||||
|  |       "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.3.1.tgz", | ||||||
|  |       "integrity": "sha512-Dbt2yjLJrCwH2sRqKFFJaN5XgIASO9YOFeFP8rIBRG2Ain8mqk5r1M6DkfvqEVozVcz3r3HaUGw253hA1nLIcA==" | ||||||
|  |     }, | ||||||
|     "parcel": { |     "parcel": { | ||||||
|       "version": "1.12.4", |       "version": "1.12.4", | ||||||
|       "resolved": "https://registry.npmjs.org/parcel/-/parcel-1.12.4.tgz", |       "resolved": "https://registry.npmjs.org/parcel/-/parcel-1.12.4.tgz", | ||||||
|  |  | ||||||
|  | @ -64,6 +64,7 @@ | ||||||
|     "@types/leaflet-providers": "^1.2.0", |     "@types/leaflet-providers": "^1.2.0", | ||||||
|     "@types/leaflet.markercluster": "^1.4.3", |     "@types/leaflet.markercluster": "^1.4.3", | ||||||
|     "@types/lz-string": "^1.3.34", |     "@types/lz-string": "^1.3.34", | ||||||
|  |     "@types/papaparse": "^5.3.1", | ||||||
|     "@types/prompt-sync": "^4.1.0", |     "@types/prompt-sync": "^4.1.0", | ||||||
|     "@types/wikidata-sdk": "^6.1.0", |     "@types/wikidata-sdk": "^6.1.0", | ||||||
|     "country-language": "^0.1.7", |     "country-language": "^0.1.7", | ||||||
|  | @ -88,6 +89,7 @@ | ||||||
|     "opening_hours": "^3.6.0", |     "opening_hours": "^3.6.0", | ||||||
|     "osm-auth": "^1.0.2", |     "osm-auth": "^1.0.2", | ||||||
|     "osmtogeojson": "^3.0.0-beta.4", |     "osmtogeojson": "^3.0.0-beta.4", | ||||||
|  |     "papaparse": "^5.3.1", | ||||||
|     "parcel": "^1.2.4", |     "parcel": "^1.2.4", | ||||||
|     "prompt-sync": "^4.2.0", |     "prompt-sync": "^4.2.0", | ||||||
|     "svg-resizer": "github:vieron/svg-resizer", |     "svg-resizer": "github:vieron/svg-resizer", | ||||||
|  |  | ||||||
|  | @ -72,10 +72,16 @@ class LayerOverviewUtils { | ||||||
|         const dict = new Map<string, TagRenderingConfigJson>(); |         const dict = new Map<string, TagRenderingConfigJson>(); | ||||||
| 
 | 
 | ||||||
|         for (const key in questions["default"]) { |         for (const key in questions["default"]) { | ||||||
|  |             if(key==="id"){ | ||||||
|  |                 continue | ||||||
|  |             } | ||||||
|             questions[key].id = key; |             questions[key].id = key; | ||||||
|             dict.set(key, <TagRenderingConfigJson>questions[key]) |             dict.set(key, <TagRenderingConfigJson>questions[key]) | ||||||
|         } |         } | ||||||
|         for (const key in icons["default"]) { |         for (const key in icons["default"]) { | ||||||
|  |             if(key==="id"){ | ||||||
|  |                 continue | ||||||
|  |             } | ||||||
|             if (typeof icons[key] !== "object") { |             if (typeof icons[key] !== "object") { | ||||||
|                 continue |                 continue | ||||||
|             } |             } | ||||||
|  | @ -105,7 +111,7 @@ class LayerOverviewUtils { | ||||||
|             "themes": Array.from(sharedThemes.values()) |             "themes": Array.from(sharedThemes.values()) | ||||||
|         })) |         })) | ||||||
| 
 | 
 | ||||||
|         writeFileSync("./assets/generated/known_layers.json", JSON.stringify(Array.from(sharedLayers.values()))) |         writeFileSync("./assets/generated/known_layers.json", JSON.stringify({layers: Array.from(sharedLayers.values())})) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         { |         { | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue