forked from MapComplete/MapComplete
		
	Import inspector
This commit is contained in:
		
							parent
							
								
									700b48f693
								
							
						
					
					
						commit
						3a158a6155
					
				
					 3 changed files with 188 additions and 6 deletions
				
			
		|  | @ -1 +1,182 @@ | |||
| export default class ImportInspector {} | ||||
| import Combine from "../Base/Combine"; | ||||
| import UserRelatedState from "../../Logic/State/UserRelatedState"; | ||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import {Utils} from "../../Utils"; | ||||
| import UserDetails from "../../Logic/Osm/OsmConnection"; | ||||
| import {UIEventSource} from "../../Logic/UIEventSource"; | ||||
| import Title from "../Base/Title"; | ||||
| import Translations from "../i18n/Translations"; | ||||
| import Loading from "../Base/Loading"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| import Toggleable from "../Base/Toggleable"; | ||||
| import List from "../Base/List"; | ||||
| import Link from "../Base/Link"; | ||||
| import {DropDown} from "../Input/DropDown"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import ValidatedTextField from "../Input/ValidatedTextField"; | ||||
| import {SubtleButton} from "../Base/SubtleButton"; | ||||
| import Svg from "../../Svg"; | ||||
| import Toggle from "../Input/Toggle"; | ||||
| 
 | ||||
| interface NoteProperties { | ||||
|     "id": number, | ||||
|     "url": string, | ||||
|     "date_created": string | ||||
|     "status": "open" | "closed", | ||||
|     "comments": { | ||||
|         date: string, | ||||
|         uid: number, | ||||
|         user: string, | ||||
|         text: string | ||||
|     }[] | ||||
| } | ||||
| 
 | ||||
| class MassAction extends Combine { | ||||
| 
 | ||||
| 
 | ||||
|     constructor(state: UserRelatedState, props: NoteProperties[]) { | ||||
|         const textField = ValidatedTextField.InputForType("text") | ||||
| 
 | ||||
|         const actions = new DropDown<{ | ||||
|             predicate: (p: NoteProperties) => boolean, | ||||
|             action: (p: NoteProperties) => Promise<void> | ||||
|         }>("On which notes should an action be performed?", [ | ||||
|             { | ||||
|                 value: undefined, | ||||
|                 shown: <string | BaseUIElement>"Pick an option..." | ||||
|             }, | ||||
|             { | ||||
|                 value: { | ||||
|                     predicate: p => p.status === "open", | ||||
|                     action: async p => { | ||||
|                         const txt = textField.GetValue().data | ||||
|                         state.osmConnection.closeNote(p.id, txt) | ||||
|                     } | ||||
|                 }, | ||||
|                 shown: "Add comment to every open note and close all notes" | ||||
|             } | ||||
|         ]) | ||||
| 
 | ||||
|         const handledNotesCounter = new UIEventSource<number>(undefined) | ||||
|         const apply = new SubtleButton(Svg.checkmark_svg(), "Apply action") | ||||
|             .onClick(async () => { | ||||
|                 const {predicate, action} = actions.GetValue().data | ||||
|                 for (let i = 0; i < props.length; i++) { | ||||
|                     handledNotesCounter.setData(i) | ||||
|                     const prop = props[i] | ||||
|                     if (!predicate(prop)) { | ||||
|                         continue | ||||
|                     } | ||||
|                     await action(prop) | ||||
|                 } | ||||
|                 handledNotesCounter.setData(props.length) | ||||
|             }) | ||||
|         super([ | ||||
| 
 | ||||
|             actions, | ||||
|             textField.SetClass("w-full border border-black"), | ||||
|             new Toggle( | ||||
|                 new Toggle( | ||||
|                     apply, | ||||
| 
 | ||||
|                     new Toggle( | ||||
|                         new Loading(new VariableUiElement(handledNotesCounter.map(state => { | ||||
|                             if (state === props.length) { | ||||
|                                 return "All done!" | ||||
|                             } | ||||
|                             return "Handling note " + (state + 1) + " out of " + props.length; | ||||
|                         }))), | ||||
|                         new Combine([Svg.checkmark_svg().SetClass("h-8"), "All done!"]).SetClass("thanks flex p-4"), | ||||
|                         handledNotesCounter.map(s => s < props.length) | ||||
|                     ), | ||||
|                     handledNotesCounter.map(s => s === undefined) | ||||
|                 ) | ||||
| 
 | ||||
|                 , undefined, | ||||
|                 actions.GetValue().map(v => v !== undefined && textField.GetValue()?.data?.length > 15, [textField.GetValue()]) | ||||
|             ), | ||||
|             new Toggle( | ||||
|             new FixedUiElement   ( "Testmode enable").SetClass("alert"), undefined, | ||||
|                 state.featureSwitchIsTesting | ||||
|             ) | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| class ImportInspector extends VariableUiElement { | ||||
| 
 | ||||
|     constructor(userDetails: UserDetails, state: UserRelatedState) { | ||||
|         const t = Translations.t.importInspector; | ||||
| 
 | ||||
| 
 | ||||
|         const url = "https://api.openstreetmap.org/api/0.6/notes/search.json?user=" + userDetails.uid + "&limit=10000&sort=created_at&q=%23import" | ||||
|         const notes: UIEventSource<{ error: string } | { success: { features: { properties: NoteProperties }[] } }> = UIEventSource.FromPromiseWithErr(Utils.downloadJson(url)) | ||||
|         notes.addCallbackAndRun(n => console.log("Notes are:", n)) | ||||
|         super(notes.map(notes => { | ||||
| 
 | ||||
|             if (notes === undefined) { | ||||
|                 return new Loading("Loading your notes which mention '#import'") | ||||
|             } | ||||
|             if (notes["error"] !== undefined) { | ||||
|                 return new FixedUiElement("Something went wrong: " + notes["error"]).SetClass("alert") | ||||
|             } | ||||
|             // We only care about the properties here
 | ||||
|             const props = notes["success"].features.map(f => f.properties) | ||||
| 
 | ||||
|             const perBatch = new Map<string, { props: NoteProperties[], theme: string }>() | ||||
|             const prefix = "https://mapcomplete.osm.be/" | ||||
|             for (const prop of props) { | ||||
|                 const lines = prop.comments[0].text.split("\n") | ||||
|                 const trigger = lines.findIndex(l => l.startsWith(prefix) && l.endsWith("#import")) | ||||
|                 if (trigger < 0) { | ||||
|                     continue | ||||
|                 } | ||||
|                 let theme = lines[trigger].substr(prefix.length) | ||||
|                 theme = theme.substr(0, theme.indexOf(".")) | ||||
|                 const key = lines[0] | ||||
|                 if (!perBatch.has(key)) { | ||||
|                     perBatch.set(key, {props: [], theme}) | ||||
|                 } | ||||
|                 perBatch.get(key).props.push(prop) | ||||
|             } | ||||
|             const els = [] | ||||
|             perBatch.forEach(({props, theme}, intro) => { | ||||
|                 els.push(new Combine([ | ||||
|                     new Title(theme + ": " + intro + " (" + props.length + " features)", 2), | ||||
|                     new Toggleable(new FixedUiElement("Notes"), | ||||
|                         new List(props.map(prop => new Link( | ||||
|                             "" + prop.id, | ||||
|                             "https://openstreetmap.org/note/" + prop.id, true | ||||
|                         )))), | ||||
|                     new Title("Mass apply an action"), | ||||
|                     new MassAction(state, props).SetClass("block") | ||||
| 
 | ||||
| 
 | ||||
|                 ])) | ||||
|             }) | ||||
|             return new Combine(els) | ||||
| 
 | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export default class ImportInspectorGui extends Combine { | ||||
| 
 | ||||
|     constructor() { | ||||
|         const state = new UserRelatedState(undefined) | ||||
|         const t = Translations.t.importInspector; | ||||
|         super([ | ||||
|             new Title(t.title, 1), | ||||
|             new VariableUiElement(state.osmConnection.userDetails.map(ud => { | ||||
|                 if (ud === undefined || ud.loggedIn === false) { | ||||
|                     return undefined | ||||
|                 } | ||||
|                 return new ImportInspector(ud, state); | ||||
|             })) | ||||
|         ]); | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -509,5 +509,8 @@ | |||
|     "inspectDataTitle": "Inspect data of {count} features to import", | ||||
|     "inspectDidAutoDected": "Layer was chosen automatically", | ||||
|     "inspectLooksCorrect": "These values look correct" | ||||
|   }, | ||||
|   "importInspector": { | ||||
|     "title": "Inspect and manage import notes" | ||||
|   } | ||||
| } | ||||
|  |  | |||
							
								
								
									
										8
									
								
								test.ts
									
										
									
									
									
								
							
							
						
						
									
										8
									
								
								test.ts
									
										
									
									
									
								
							|  | @ -1,6 +1,4 @@ | |||
| import ValidatedTextField from "./UI/Input/ValidatedTextField"; | ||||
| import {VariableUiElement} from "./UI/Base/VariableUIElement"; | ||||
| import ImportInspectorGui from "./UI/ImportFlow/ImportInspector"; | ||||
| 
 | ||||
| new ImportInspectorGui().AttachTo("maindiv") | ||||
| 
 | ||||
| const tf = ValidatedTextField.InputForType("url") | ||||
| tf.AttachTo("maindiv") | ||||
| new VariableUiElement(tf.GetValue()).AttachTo("extradiv") | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue