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", |     "inspectDataTitle": "Inspect data of {count} features to import", | ||||||
|     "inspectDidAutoDected": "Layer was chosen automatically", |     "inspectDidAutoDected": "Layer was chosen automatically", | ||||||
|     "inspectLooksCorrect": "These values look correct" |     "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 ImportInspectorGui from "./UI/ImportFlow/ImportInspector"; | ||||||
| import {VariableUiElement} from "./UI/Base/VariableUIElement"; | 
 | ||||||
|  | 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