forked from MapComplete/MapComplete
		
	Finish import_viewer gui
This commit is contained in:
		
							parent
							
								
									cd09efca94
								
							
						
					
					
						commit
						33ef83c4a9
					
				
					 14 changed files with 198 additions and 103 deletions
				
			
		|  | @ -6,7 +6,8 @@ import Toggle from "../Input/Toggle"; | |||
| import Loading from "../Base/Loading"; | ||||
| import {VariableUiElement} from "../Base/VariableUIElement"; | ||||
| import {FixedUiElement} from "../Base/FixedUiElement"; | ||||
| import Link from "../Base/Link"; | ||||
| import {SubtleButton} from "../Base/SubtleButton"; | ||||
| import Svg from "../../Svg"; | ||||
| 
 | ||||
| export class CreateNotes extends Combine { | ||||
| 
 | ||||
|  | @ -24,7 +25,7 @@ export class CreateNotes extends Combine { | |||
| 
 | ||||
|             const tags: string [] = [] | ||||
|             for (const key in f.properties) { | ||||
|                 if(f.properties[key] === ""){ | ||||
|                 if (f.properties[key] === "") { | ||||
|                     continue | ||||
|                 } | ||||
|                 tags.push(key + "=" + f.properties[key].replace(/=/, "\\=").replace(/;/g, "\\;").replace(/\n/g, "\\n")) | ||||
|  | @ -56,7 +57,13 @@ export class CreateNotes extends Combine { | |||
|             "Hang on while we are importing...", | ||||
|             new Toggle( | ||||
|                 new Loading(new VariableUiElement(currentNote.map(count => new FixedUiElement("Imported <b>" + count + "</b> out of " + v.features.length + " notes")))), | ||||
|                 new FixedUiElement("All done!"), | ||||
|                 new Combine([ | ||||
|                         new FixedUiElement("All done!").SetClass("thanks"), | ||||
|                         new SubtleButton(Svg.note_svg(), "Inspect the progress of your notes in the 'import_viewer'", { | ||||
|                             url: "import_viewer.html" | ||||
|                         }) | ||||
|                     ] | ||||
|                 ), | ||||
|                 currentNote.map(count => count < v.features.length) | ||||
|             ), | ||||
|             new VariableUiElement(failed.map(failed => { | ||||
|  | @ -69,11 +76,6 @@ export class CreateNotes extends Combine { | |||
|                     ...failed | ||||
|                 ]).SetClass("flex flex-col") | ||||
| 
 | ||||
|             })), | ||||
|             new VariableUiElement(createdNotes.map(notes => { | ||||
|                 const links = notes.map(n => | ||||
|                     new Link(new FixedUiElement("https://openstreetmap.org/note/" + n), "https://openstreetmap.org/note/" + n, true)); | ||||
|                 return new Combine(links).SetClass("flex flex-col"); | ||||
|             })) | ||||
|         ]) | ||||
|         this.SetClass("flex flex-col"); | ||||
|  |  | |||
|  | @ -1,7 +1,6 @@ | |||
| import Combine from "../Base/Combine"; | ||||
| import Toggle from "../Input/Toggle"; | ||||
| import LanguagePicker from "../LanguagePicker"; | ||||
| import BackToIndex from "../BigComponents/BackToIndex"; | ||||
| import UserRelatedState from "../../Logic/State/UserRelatedState"; | ||||
| import BaseUIElement from "../BaseUIElement"; | ||||
| import MinimapImplementation from "../Base/MinimapImplementation"; | ||||
|  | @ -21,11 +20,11 @@ import {CompareToAlreadyExistingNotes} from "./CompareToAlreadyExistingNotes"; | |||
| import Introdution from "./Introdution"; | ||||
| import LoginToImport from "./LoginToImport"; | ||||
| import {MapPreview} from "./MapPreview"; | ||||
| import LeftIndex from "../Base/LeftIndex"; | ||||
| import {SubtleButton} from "../Base/SubtleButton"; | ||||
| 
 | ||||
| export default class ImportHelperGui extends Combine { | ||||
| export default class ImportHelperGui extends LeftIndex { | ||||
|     constructor() { | ||||
|         const t = Translations.t.importHelper; | ||||
| 
 | ||||
|         const state = new UserRelatedState(undefined) | ||||
| 
 | ||||
|         // We disable the userbadge, as various 'showData'-layers will give a read-only view in this case
 | ||||
|  | @ -61,24 +60,17 @@ export default class ImportHelperGui extends Combine { | |||
|             , true) | ||||
|          | ||||
|         const leftContents: BaseUIElement[] = [ | ||||
|             new BackToIndex().SetClass("block pl-4"), | ||||
|             new SubtleButton(undefined,"Inspect your preview imports", { | ||||
|                 url:"import_viewer.html" | ||||
|             }), | ||||
|             toc, | ||||
|             new Toggle(new FixedUiElement("Testmode - won't actually import notes").SetClass("alert"), undefined, state.featureSwitchIsTesting), | ||||
|             LanguagePicker.CreateLanguagePicker(Translations.t.importHelper.title.SupportedLanguages())?.SetClass("mt-4 self-end flex-col"), | ||||
|         ].map(el => el?.SetClass("pl-4")) | ||||
| 
 | ||||
|         const leftBar = new Combine([ | ||||
|             new Combine(leftContents).SetClass("sticky top-4 m-4"), | ||||
|           ]).SetClass("block w-full md:w-2/6 lg:w-1/6") | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         super([ | ||||
|                 new Combine([ | ||||
|                     leftBar, | ||||
|                     flow.SetClass("m-8 w-full mb-24") | ||||
|                 ]).SetClass("h-full block md:flex")]) | ||||
|         super( | ||||
|             leftContents, | ||||
|             flow) | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -8,8 +8,6 @@ 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"; | ||||
|  | @ -17,11 +15,18 @@ import ValidatedTextField from "../Input/ValidatedTextField"; | |||
| import {SubtleButton} from "../Base/SubtleButton"; | ||||
| import Svg from "../../Svg"; | ||||
| import Toggle from "../Input/Toggle"; | ||||
| import Table from "../Base/Table"; | ||||
| import LeftIndex from "../Base/LeftIndex"; | ||||
| import Toggleable, {Accordeon} from "../Base/Toggleable"; | ||||
| import TableOfContents from "../Base/TableOfContents"; | ||||
| import LoginButton from "../Popup/LoginButton"; | ||||
| import BackToIndex from "../BigComponents/BackToIndex"; | ||||
| 
 | ||||
| interface NoteProperties { | ||||
|     "id": number, | ||||
|     "url": string, | ||||
|     "date_created": string | ||||
|     "date_created": string, | ||||
|     closed_at?: string, | ||||
|     "status": "open" | "closed", | ||||
|     "comments": { | ||||
|         date: string, | ||||
|  | @ -31,9 +36,15 @@ interface NoteProperties { | |||
|     }[] | ||||
| } | ||||
| 
 | ||||
| interface NoteState { | ||||
|     props: NoteProperties, | ||||
|     theme: string, | ||||
|     intro: string, | ||||
|     dateStr: string, | ||||
|     status: "imported" | "already_mapped" | "invalid" | "closed" | "not_found" | "open" | ||||
| } | ||||
| 
 | ||||
| class MassAction extends Combine { | ||||
| 
 | ||||
| 
 | ||||
|     constructor(state: UserRelatedState, props: NoteProperties[]) { | ||||
|         const textField = ValidatedTextField.InputForType("text") | ||||
| 
 | ||||
|  | @ -54,6 +65,16 @@ class MassAction extends Combine { | |||
|                     } | ||||
|                 }, | ||||
|                 shown: "Add comment to every open note and close all notes" | ||||
|             }, | ||||
|             { | ||||
|                 value: { | ||||
|                     predicate: p => p.status === "open", | ||||
|                     action: async p => { | ||||
|                         const txt = textField.GetValue().data | ||||
|                         state.osmConnection.addCommentToNode(p.id, txt) | ||||
|                     } | ||||
|                 }, | ||||
|                 shown: "Add comment to every open note" | ||||
|             } | ||||
|         ]) | ||||
| 
 | ||||
|  | @ -96,7 +117,7 @@ class MassAction extends Combine { | |||
|                 actions.GetValue().map(v => v !== undefined && textField.GetValue()?.data?.length > 15, [textField.GetValue()]) | ||||
|             ), | ||||
|             new Toggle( | ||||
|             new FixedUiElement   ( "Testmode enable").SetClass("alert"), undefined, | ||||
|                 new FixedUiElement("Testmode enable").SetClass("alert"), undefined, | ||||
|                 state.featureSwitchIsTesting | ||||
|             ) | ||||
|         ]); | ||||
|  | @ -104,6 +125,36 @@ class MassAction extends Combine { | |||
| 
 | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| class BatchView extends Toggleable { | ||||
|     constructor(state: UserRelatedState, noteStates: NoteState[]) { | ||||
|         const {theme, intro, dateStr} = noteStates[0] | ||||
|         console.log("Creating a batchview for ", noteStates) | ||||
|         super( | ||||
|             new Title(theme + ": " + intro, 2), | ||||
|             new Combine([ | ||||
|                 new FixedUiElement(dateStr), | ||||
|                 new FixedUiElement("Click to expand/collapse table"), | ||||
| 
 | ||||
| 
 | ||||
|                 new Table( | ||||
|                     ["id", "status", "last comment"], | ||||
|                     noteStates.map(ns => { | ||||
|                         const link = new Link( | ||||
|                             "" + ns.props.id, | ||||
|                             "https://openstreetmap.org/note/" + ns.props.id, true | ||||
|                         ) | ||||
|                         const last_comment = ns.props.comments[ns.props.comments.length - 1].text | ||||
|                         return [link, ns.status, last_comment] | ||||
|                     }) | ||||
|                 ).SetClass("zebra-table link-underline"), | ||||
| 
 | ||||
| 
 | ||||
|                 new Title("Mass apply an action"), | ||||
|                 new MassAction(state, noteStates.map(ns => ns.props)).SetClass("block")]).SetClass("flex flex-col")) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| class ImportInspector extends VariableUiElement { | ||||
| 
 | ||||
|     constructor(userDetails: UserDetails, state: UserRelatedState) { | ||||
|  | @ -122,61 +173,86 @@ class ImportInspector extends VariableUiElement { | |||
|                 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 props: NoteProperties[] = notes["success"].features.map(f => f.properties) | ||||
|             const perBatch: NoteState[][] = Array.from(ImportInspector.SplitNotesIntoBatches(props).values()); | ||||
|             const els: Toggleable[] = perBatch.map(noteStates => new BatchView(state, noteStates)) | ||||
| 
 | ||||
|             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) | ||||
|             const accordeon = new Accordeon(els) | ||||
|             const content = new Combine([ | ||||
|                 new Title(Translations.t.importInspector.title, 1), | ||||
|                 new SubtleButton(undefined, "Create a new batch of imports",{url:'import_helper.html'}), | ||||
|                 accordeon]) | ||||
|             return new LeftIndex( | ||||
|                 [new TableOfContents(content, {noTopLevel: true, maxDepth: 1}).SetClass("subtle")], | ||||
|                 content | ||||
|             ) | ||||
| 
 | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Creates distinct batches of note, where 'date', 'intro' and 'theme' are identical | ||||
|      */ | ||||
|     private static SplitNotesIntoBatches(props: NoteProperties[]): Map<string, NoteState[]> { | ||||
|         const perBatch = new Map<string, NoteState[]>() | ||||
|         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 date = Utils.ParseDate(prop.date_created) | ||||
|             const dateStr = date.getFullYear() + "-" + date.getMonth() + "-" + date.getDate() | ||||
|             const key = theme + lines[0] + dateStr | ||||
|             if (!perBatch.has(key)) { | ||||
|                 perBatch.set(key, []) | ||||
|             } | ||||
|             let status: "open" | "closed" | "imported" | "invalid" | "already_mapped" | "not_found" = "open" | ||||
|             if (prop.closed_at !== undefined) { | ||||
|                 const lastComment = prop.comments[prop.comments.length - 1].text.toLowerCase() | ||||
|                 if (lastComment.indexOf("does not exist") >= 0) { | ||||
|                     status = "not_found" | ||||
|                 } else if (lastComment.indexOf("already mapped") >= 0) { | ||||
|                     status = "already_mapped" | ||||
|                 } else if (lastComment.indexOf("invalid") >= 0 || lastComment.indexOf("incorrecto") >= 0) { | ||||
|                     status = "invalid" | ||||
|                 } else if (lastComment.indexOf("imported") >= 0) { | ||||
|                     status = "imported" | ||||
|                 } else { | ||||
|                     status = "closed" | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             perBatch.get(key).push({ | ||||
|                 props: prop, | ||||
|                 intro: lines[0], | ||||
|                 theme, | ||||
|                 dateStr, | ||||
|                 status | ||||
|             }) | ||||
|         } | ||||
|         return perBatch; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export default class ImportInspectorGui extends Combine { | ||||
| class ImportViewerGui 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 Combine([new LoginButton("Login to inspect your import flows", state), | ||||
|                     new BackToIndex() | ||||
|                     ]) | ||||
|                 } | ||||
|                 return new ImportInspector(ud, state); | ||||
|             })) | ||||
|         ]); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
| new ImportViewerGui().AttachTo("main") | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue