| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | import Combine from "../Base/Combine" | 
					
						
							|  |  |  | import UserRelatedState from "../../Logic/State/UserRelatedState" | 
					
						
							|  |  |  | import { VariableUiElement } from "../Base/VariableUIElement" | 
					
						
							|  |  |  | import { Utils } from "../../Utils" | 
					
						
							|  |  |  | 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 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, { ClickableToggle } 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 { LoginToggle } from "../Popup/LoginButton" | 
					
						
							|  |  |  | import { QueryParameters } from "../../Logic/Web/QueryParameters" | 
					
						
							|  |  |  | import Lazy from "../Base/Lazy" | 
					
						
							|  |  |  | import { Button } from "../Base/Button" | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | interface NoteProperties { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     id: number | 
					
						
							|  |  |  |     url: string | 
					
						
							|  |  |  |     date_created: string | 
					
						
							|  |  |  |     closed_at?: string | 
					
						
							|  |  |  |     status: "open" | "closed" | 
					
						
							|  |  |  |     comments: { | 
					
						
							|  |  |  |         date: string | 
					
						
							|  |  |  |         uid: number | 
					
						
							|  |  |  |         user: string | 
					
						
							|  |  |  |         text: string | 
					
						
							| 
									
										
										
										
											2022-01-25 21:55:51 +01:00
										 |  |  |         html: string | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |     }[] | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  | interface NoteState { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     props: NoteProperties | 
					
						
							|  |  |  |     theme: string | 
					
						
							|  |  |  |     intro: string | 
					
						
							|  |  |  |     dateStr: string | 
					
						
							|  |  |  |     status: | 
					
						
							|  |  |  |         | "imported" | 
					
						
							|  |  |  |         | "already_mapped" | 
					
						
							|  |  |  |         | "invalid" | 
					
						
							|  |  |  |         | "closed" | 
					
						
							|  |  |  |         | "not_found" | 
					
						
							|  |  |  |         | "open" | 
					
						
							|  |  |  |         | "has_comments" | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-23 02:14:31 +02:00
										 |  |  | class DownloadStatisticsButton extends SubtleButton { | 
					
						
							|  |  |  |     constructor(states: NoteState[][]) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         super(Svg.statistics_svg(), "Download statistics") | 
					
						
							| 
									
										
										
										
											2022-04-23 02:14:31 +02:00
										 |  |  |         this.onClick(() => { | 
					
						
							|  |  |  |             const st: NoteState[] = [].concat(...states) | 
					
						
							| 
									
										
										
										
											2022-06-29 03:05:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-23 02:14:31 +02:00
										 |  |  |             const fields = [ | 
					
						
							|  |  |  |                 "id", | 
					
						
							|  |  |  |                 "status", | 
					
						
							|  |  |  |                 "theme", | 
					
						
							|  |  |  |                 "date_created", | 
					
						
							|  |  |  |                 "date_closed", | 
					
						
							|  |  |  |                 "days_open", | 
					
						
							|  |  |  |                 "intro", | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 "...comments", | 
					
						
							| 
									
										
										
										
											2022-04-23 02:14:31 +02:00
										 |  |  |             ] | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             const values: string[][] = st.map((note) => { | 
					
						
							|  |  |  |                 return [ | 
					
						
							|  |  |  |                     note.props.id + "", | 
					
						
							| 
									
										
										
										
											2022-04-23 02:14:31 +02:00
										 |  |  |                     note.status, | 
					
						
							|  |  |  |                     note.theme, | 
					
						
							|  |  |  |                     note.props.date_created?.substr(0, note.props.date_created.length - 3), | 
					
						
							|  |  |  |                     note.props.closed_at?.substr(0, note.props.closed_at.length - 3) ?? "", | 
					
						
							| 
									
										
										
										
											2022-06-29 03:05:25 +02:00
										 |  |  |                     JSON.stringify(note.intro), | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     ...note.props.comments.map( | 
					
						
							|  |  |  |                         (c) => JSON.stringify(c.user) + ": " + JSON.stringify(c.text) | 
					
						
							|  |  |  |                     ), | 
					
						
							| 
									
										
										
										
											2022-04-23 02:14:31 +02:00
										 |  |  |                 ] | 
					
						
							|  |  |  |             }) | 
					
						
							| 
									
										
										
										
											2022-06-29 03:05:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-23 02:14:31 +02:00
										 |  |  |             Utils.offerContentsAsDownloadableFile( | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 [fields, ...values].map((c) => c.join(", ")).join("\n"), | 
					
						
							| 
									
										
										
										
											2022-04-23 02:14:31 +02:00
										 |  |  |                 "mapcomplete_import_notes_overview.csv", | 
					
						
							|  |  |  |                 { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     mimetype: "text/csv", | 
					
						
							| 
									
										
										
										
											2022-04-23 02:14:31 +02:00
										 |  |  |                 } | 
					
						
							|  |  |  |             ) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  | class MassAction extends Combine { | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |     constructor(state: UserRelatedState, props: NoteProperties[]) { | 
					
						
							| 
									
										
										
										
											2022-02-12 02:53:41 +01:00
										 |  |  |         const textField = ValidatedTextField.ForType("text").ConstructInputElement() | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         const actions = new DropDown<{ | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             predicate: (p: NoteProperties) => boolean | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |             action: (p: NoteProperties) => Promise<void> | 
					
						
							|  |  |  |         }>("On which notes should an action be performed?", [ | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 value: undefined, | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 shown: <string | BaseUIElement>"Pick an option...", | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |             }, | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 value: { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     predicate: (p) => p.status === "open", | 
					
						
							|  |  |  |                     action: async (p) => { | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |                         const txt = textField.GetValue().data | 
					
						
							|  |  |  |                         state.osmConnection.closeNote(p.id, txt) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     }, | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |                 }, | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 shown: "Add comment to every open note and close all notes", | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  |             }, | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 value: { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     predicate: (p) => p.status === "open", | 
					
						
							|  |  |  |                     action: async (p) => { | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  |                         const txt = textField.GetValue().data | 
					
						
							| 
									
										
										
										
											2022-06-20 11:26:55 +02:00
										 |  |  |                         state.osmConnection.addCommentToNote(p.id, txt) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     }, | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  |                 }, | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 shown: "Add comment to every open note", | 
					
						
							| 
									
										
										
										
											2022-02-11 02:40:23 +01:00
										 |  |  |             }, | 
					
						
							|  |  |  |             /* | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2022-11-16 01:10:13 +01:00
										 |  |  |                // This was a one-off for one of the first imports
 | 
					
						
							| 
									
										
										
										
											2022-02-11 02:40:23 +01:00
										 |  |  |                 value:{ | 
					
						
							|  |  |  |                     predicate: p => p.status === "open" && p.comments[0].text.split("\n").find(l => l.startsWith("note=")) !== undefined, | 
					
						
							|  |  |  |                     action: async p => { | 
					
						
							|  |  |  |                         const note = p.comments[0].text.split("\n").find(l => l.startsWith("note=")).substr("note=".length) | 
					
						
							|  |  |  |                         state.osmConnection.addCommentToNode(p.id, note) | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 }, | 
					
						
							|  |  |  |                 shown:"On every open note, read the 'note='-tag and and this note as comment. (This action ignores the textfield)" | 
					
						
							|  |  |  |             },//*/
 | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |         ]) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const handledNotesCounter = new UIEventSource<number>(undefined) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         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 | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 await action(prop) | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             handledNotesCounter.setData(props.length) | 
					
						
							|  |  |  |         }) | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |         super([ | 
					
						
							|  |  |  |             actions, | 
					
						
							|  |  |  |             textField.SetClass("w-full border border-black"), | 
					
						
							|  |  |  |             new Toggle( | 
					
						
							|  |  |  |                 new Toggle( | 
					
						
							|  |  |  |                     apply, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     new Toggle( | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                         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) | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |                     ), | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                     handledNotesCounter.map((s) => s === undefined) | 
					
						
							|  |  |  |                 ), | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 new VariableUiElement( | 
					
						
							|  |  |  |                     textField | 
					
						
							|  |  |  |                         .GetValue() | 
					
						
							|  |  |  |                         .map( | 
					
						
							|  |  |  |                             (txt) => | 
					
						
							|  |  |  |                                 "Type a text of at least 15 characters to apply the action. Currently, there are " + | 
					
						
							|  |  |  |                                 (txt?.length ?? 0) + | 
					
						
							|  |  |  |                                 " characters" | 
					
						
							|  |  |  |                         ) | 
					
						
							|  |  |  |                 ).SetClass("alert"), | 
					
						
							|  |  |  |                 actions | 
					
						
							|  |  |  |                     .GetValue() | 
					
						
							|  |  |  |                     .map( | 
					
						
							|  |  |  |                         (v) => v !== undefined && textField.GetValue()?.data?.length > 15, | 
					
						
							|  |  |  |                         [textField.GetValue()] | 
					
						
							|  |  |  |                     ) | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |             ), | 
					
						
							|  |  |  |             new Toggle( | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 new FixedUiElement("Testmode enable").SetClass("alert"), | 
					
						
							|  |  |  |                 undefined, | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |                 state.featureSwitchIsTesting | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             ), | 
					
						
							|  |  |  |         ]) | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-29 00:20:10 +02:00
										 |  |  | class NoteTable extends Combine { | 
					
						
							| 
									
										
										
										
											2022-06-29 03:05:25 +02:00
										 |  |  |     private static individualActions: [() => BaseUIElement, string][] = [ | 
					
						
							|  |  |  |         [Svg.not_found_svg, "This feature does not exist"], | 
					
						
							|  |  |  |         [Svg.addSmall_svg, "imported"], | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         [Svg.duplicate_svg, "Already mapped"], | 
					
						
							| 
									
										
										
										
											2022-06-29 03:05:25 +02:00
										 |  |  |     ] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-29 00:20:10 +02:00
										 |  |  |     constructor(noteStates: NoteState[], state?: UserRelatedState) { | 
					
						
							|  |  |  |         const typicalComment = noteStates[0].props.comments[0].html | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const table = new Table( | 
					
						
							| 
									
										
										
										
											2022-06-29 03:05:25 +02:00
										 |  |  |             ["id", "status", "last comment", "last modified by", "actions"], | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             noteStates.map((ns) => NoteTable.noteField(ns, state)), | 
					
						
							|  |  |  |             { sortable: true } | 
					
						
							|  |  |  |         ).SetClass("zebra-table link-underline") | 
					
						
							| 
									
										
										
										
											2022-03-29 00:20:10 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         super([ | 
					
						
							|  |  |  |             new Title("Mass apply an action on " + noteStates.length + " notes below"), | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             state !== undefined | 
					
						
							|  |  |  |                 ? new MassAction( | 
					
						
							|  |  |  |                       state, | 
					
						
							|  |  |  |                       noteStates.map((ns) => ns.props) | 
					
						
							|  |  |  |                   ).SetClass("block") | 
					
						
							|  |  |  |                 : undefined, | 
					
						
							| 
									
										
										
										
											2022-03-29 00:20:10 +02:00
										 |  |  |             table, | 
					
						
							|  |  |  |             new Title("Example note", 4), | 
					
						
							|  |  |  |             new FixedUiElement(typicalComment).SetClass("literal-code link-underline"), | 
					
						
							|  |  |  |         ]) | 
					
						
							|  |  |  |         this.SetClass("flex flex-col") | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-06-29 03:05:25 +02:00
										 |  |  |     private static noteField(ns: NoteState, state: UserRelatedState) { | 
					
						
							|  |  |  |         const link = new Link( | 
					
						
							|  |  |  |             "" + ns.props.id, | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             "https://openstreetmap.org/note/" + ns.props.id, | 
					
						
							|  |  |  |             true | 
					
						
							| 
									
										
										
										
											2022-06-29 03:05:25 +02:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         let last_comment = "" | 
					
						
							| 
									
										
										
										
											2022-06-29 03:05:25 +02:00
										 |  |  |         const last_comment_props = ns.props.comments[ns.props.comments.length - 1] | 
					
						
							|  |  |  |         const before_last_comment = ns.props.comments[ns.props.comments.length - 2] | 
					
						
							|  |  |  |         if (ns.props.comments.length > 1) { | 
					
						
							|  |  |  |             last_comment = last_comment_props.text | 
					
						
							|  |  |  |             if (last_comment === undefined && before_last_comment?.uid === last_comment_props.uid) { | 
					
						
							|  |  |  |                 last_comment = before_last_comment.text | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const statusIcon = BatchView.icons[ns.status]().SetClass("h-4 w-4 shrink-0") | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const togglestate = new UIEventSource(false) | 
					
						
							|  |  |  |         const changed = new UIEventSource<string>(undefined) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const lazyButtons = new Lazy(() => | 
					
						
							|  |  |  |             new Combine( | 
					
						
							|  |  |  |                 this.individualActions.map(([img, text]) => | 
					
						
							|  |  |  |                     img() | 
					
						
							|  |  |  |                         .onClick(async () => { | 
					
						
							|  |  |  |                             if (ns.props.status === "closed") { | 
					
						
							|  |  |  |                                 await state.osmConnection.reopenNote(ns.props.id) | 
					
						
							|  |  |  |                             } | 
					
						
							|  |  |  |                             await state.osmConnection.closeNote(ns.props.id, text) | 
					
						
							|  |  |  |                             changed.setData(text) | 
					
						
							|  |  |  |                         }) | 
					
						
							|  |  |  |                         .SetClass("h-8 w-8") | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |             ).SetClass("flex") | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const appliedButtons = new VariableUiElement( | 
					
						
							|  |  |  |             changed.map((currentState) => (currentState === undefined ? lazyButtons : currentState)) | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const buttons = Toggle.If( | 
					
						
							|  |  |  |             state?.osmConnection?.isLoggedIn, | 
					
						
							|  |  |  |             () => | 
					
						
							|  |  |  |                 new ClickableToggle( | 
					
						
							|  |  |  |                     appliedButtons, | 
					
						
							|  |  |  |                     new Button("edit...", () => { | 
					
						
							|  |  |  |                         console.log("Enabling...") | 
					
						
							|  |  |  |                         togglestate.setData(true) | 
					
						
							|  |  |  |                     }), | 
					
						
							|  |  |  |                     togglestate | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         return [ | 
					
						
							|  |  |  |             link, | 
					
						
							|  |  |  |             new Combine([statusIcon, ns.status]).SetClass("flex"), | 
					
						
							|  |  |  |             last_comment, | 
					
						
							|  |  |  |             new Link( | 
					
						
							|  |  |  |                 last_comment_props.user, | 
					
						
							|  |  |  |                 "https://www.openstreetmap.org/user/" + last_comment_props.user, | 
					
						
							|  |  |  |                 true | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             buttons, | 
					
						
							| 
									
										
										
										
											2022-06-29 03:05:25 +02:00
										 |  |  |         ] | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-03-29 00:20:10 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  | class BatchView extends Toggleable { | 
					
						
							| 
									
										
										
										
											2022-03-29 00:20:10 +02:00
										 |  |  |     public static icons = { | 
					
						
							| 
									
										
										
										
											2022-01-25 21:55:51 +01:00
										 |  |  |         open: Svg.compass_svg, | 
					
						
							|  |  |  |         has_comments: Svg.speech_bubble_svg, | 
					
						
							|  |  |  |         imported: Svg.addSmall_svg, | 
					
						
							|  |  |  |         already_mapped: Svg.checkmark_svg, | 
					
						
							|  |  |  |         not_found: Svg.not_found_svg, | 
					
						
							| 
									
										
										
										
											2022-03-29 00:20:10 +02:00
										 |  |  |         closed: Svg.close_svg, | 
					
						
							|  |  |  |         invalid: Svg.invalid_svg, | 
					
						
							| 
									
										
										
										
											2022-01-25 21:55:51 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     constructor(noteStates: NoteState[], state?: UserRelatedState) { | 
					
						
							|  |  |  |         noteStates.sort((a, b) => a.props.id - b.props.id) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const { theme, intro, dateStr } = noteStates[0] | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-25 21:55:51 +01:00
										 |  |  |         const statusHist = new Map<string, number>() | 
					
						
							|  |  |  |         for (const noteState of noteStates) { | 
					
						
							|  |  |  |             const st = noteState.status | 
					
						
							|  |  |  |             const c = statusHist.get(st) ?? 0 | 
					
						
							|  |  |  |             statusHist.set(st, c + 1) | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const unresolvedTotal = | 
					
						
							|  |  |  |             (statusHist.get("open") ?? 0) + (statusHist.get("has_comments") ?? 0) | 
					
						
							|  |  |  |         const badges: BaseUIElement[] = [ | 
					
						
							| 
									
										
										
										
											2022-03-29 00:20:10 +02:00
										 |  |  |             new FixedUiElement(dateStr).SetClass("literal-code rounded-full"), | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             new FixedUiElement(noteStates.length + " total") | 
					
						
							|  |  |  |                 .SetClass("literal-code rounded-full ml-1 border-4 border-gray") | 
					
						
							| 
									
										
										
										
											2022-03-29 00:20:10 +02:00
										 |  |  |                 .onClick(() => filterOn.setData(undefined)), | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             unresolvedTotal === 0 | 
					
						
							|  |  |  |                 ? new Combine([Svg.party_svg().SetClass("h-6 m-1"), "All done!"]).SetClass( | 
					
						
							|  |  |  |                       "flex ml-1 mb-1 pl-1 pr-3 items-center rounded-full border border-black" | 
					
						
							|  |  |  |                   ) | 
					
						
							|  |  |  |                 : new FixedUiElement( | 
					
						
							|  |  |  |                       Math.round(100 - (100 * unresolvedTotal) / noteStates.length) + "%" | 
					
						
							|  |  |  |                   ).SetClass("literal-code rounded-full ml-1"), | 
					
						
							| 
									
										
										
										
											2022-03-29 00:20:10 +02:00
										 |  |  |         ] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         const filterOn = new UIEventSource<string>(undefined) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         Object.keys(BatchView.icons).forEach((status) => { | 
					
						
							| 
									
										
										
										
											2022-03-29 00:20:10 +02:00
										 |  |  |             const count = statusHist.get(status) | 
					
						
							|  |  |  |             if (count === undefined) { | 
					
						
							|  |  |  |                 return undefined | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             const normal = new Combine([ | 
					
						
							|  |  |  |                 BatchView.icons[status]().SetClass("h-6 m-1"), | 
					
						
							|  |  |  |                 count + " " + status, | 
					
						
							|  |  |  |             ]).SetClass("flex ml-1 mb-1 pl-1 pr-3 items-center rounded-full border border-black") | 
					
						
							|  |  |  |             const selected = new Combine([ | 
					
						
							|  |  |  |                 BatchView.icons[status]().SetClass("h-6 m-1"), | 
					
						
							|  |  |  |                 count + " " + status, | 
					
						
							|  |  |  |             ]).SetClass( | 
					
						
							|  |  |  |                 "flex ml-1 mb-1 pl-1 pr-3 items-center rounded-full border-4 border-black animate-pulse" | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2022-03-29 00:20:10 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             const toggle = new ClickableToggle( | 
					
						
							|  |  |  |                 selected, | 
					
						
							|  |  |  |                 normal, | 
					
						
							|  |  |  |                 filterOn.sync( | 
					
						
							|  |  |  |                     (f) => f === status, | 
					
						
							|  |  |  |                     [], | 
					
						
							|  |  |  |                     (selected, previous) => { | 
					
						
							|  |  |  |                         if (selected) { | 
					
						
							|  |  |  |                             return status | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         if (previous === status) { | 
					
						
							|  |  |  |                             return undefined | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         return previous | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |             ).ToggleOnClick() | 
					
						
							| 
									
										
										
										
											2022-03-29 00:20:10 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             badges.push(toggle) | 
					
						
							| 
									
										
										
										
											2022-01-25 21:55:51 +01:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const fullTable = new NoteTable(noteStates, state) | 
					
						
							| 
									
										
										
										
											2022-01-25 21:55:51 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |         super( | 
					
						
							|  |  |  |             new Combine([ | 
					
						
							|  |  |  |                 new Title(theme + ": " + intro, 2), | 
					
						
							|  |  |  |                 new Combine(badges).SetClass("flex flex-wrap"), | 
					
						
							|  |  |  |             ]), | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             new VariableUiElement( | 
					
						
							|  |  |  |                 filterOn.map((filter) => { | 
					
						
							|  |  |  |                     if (filter === undefined) { | 
					
						
							|  |  |  |                         return fullTable | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     return new NoteTable( | 
					
						
							|  |  |  |                         noteStates.filter((ns) => ns.status === filter), | 
					
						
							|  |  |  |                         state | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 }) | 
					
						
							|  |  |  |             ), | 
					
						
							| 
									
										
										
										
											2022-01-25 21:55:51 +01:00
										 |  |  |             { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 closeOnClick: false, | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  | class ImportInspector extends VariableUiElement { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |     constructor( | 
					
						
							|  |  |  |         userDetails: { uid: number } | { display_name: string; search?: string }, | 
					
						
							|  |  |  |         state: UserRelatedState | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |         let url | 
					
						
							| 
									
										
										
										
											2022-02-15 15:42:09 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-25 21:55:51 +01:00
										 |  |  |         if (userDetails["uid"] !== undefined) { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             url = | 
					
						
							|  |  |  |                 "https://api.openstreetmap.org/api/0.6/notes/search.json?user=" + | 
					
						
							|  |  |  |                 userDetails["uid"] + | 
					
						
							|  |  |  |                 "&closed=730&limit=10000&sort=created_at&q=%23import" | 
					
						
							| 
									
										
										
										
											2022-01-25 21:55:51 +01:00
										 |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             url = | 
					
						
							|  |  |  |                 "https://api.openstreetmap.org/api/0.6/notes/search.json?display_name=" + | 
					
						
							|  |  |  |                 encodeURIComponent(userDetails["display_name"]) + | 
					
						
							|  |  |  |                 "&limit=10000&closed=730&sort=created_at&q=" + | 
					
						
							|  |  |  |                 encodeURIComponent(userDetails["search"] ?? "#import") | 
					
						
							| 
									
										
										
										
											2022-01-25 21:55:51 +01:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const notes: UIEventSource< | 
					
						
							|  |  |  |             { error: string } | { success: { features: { properties: NoteProperties }[] } } | 
					
						
							|  |  |  |         > = UIEventSource.FromPromiseWithErr(Utils.downloadJson(url)) | 
					
						
							|  |  |  |         super( | 
					
						
							|  |  |  |             notes.map((notes) => { | 
					
						
							|  |  |  |                 if (notes === undefined) { | 
					
						
							|  |  |  |                     return new Loading("Loading 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
 | 
					
						
							| 
									
										
										
										
											2022-11-16 01:10:13 +01:00
										 |  |  |                 let props: NoteProperties[] = notes["success"].features.map((f) => f.properties) | 
					
						
							|  |  |  |                 if(userDetails["uid"]){ | 
					
						
							|  |  |  |                     props = props.filter(n => n.comments[0].uid === userDetails["uid"]) | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 const perBatch: NoteState[][] = Array.from( | 
					
						
							|  |  |  |                     ImportInspector.SplitNotesIntoBatches(props).values() | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |                 const els: Toggleable[] = perBatch.map( | 
					
						
							|  |  |  |                     (noteStates) => new BatchView(noteStates, state) | 
					
						
							|  |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 const accordeon = new Accordeon(els) | 
					
						
							|  |  |  |                 let contents = [] | 
					
						
							|  |  |  |                 if (state?.osmConnection?.isLoggedIn?.data) { | 
					
						
							|  |  |  |                     contents = [ | 
					
						
							| 
									
										
										
										
											2022-01-25 21:55:51 +01:00
										 |  |  |                         new Title(Translations.t.importInspector.title, 1), | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                         new SubtleButton(undefined, "Create a new batch of imports", { | 
					
						
							|  |  |  |                             url: "import_helper.html", | 
					
						
							|  |  |  |                         }), | 
					
						
							|  |  |  |                     ] | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 contents.push(accordeon) | 
					
						
							|  |  |  |                 const content = new Combine(contents) | 
					
						
							|  |  |  |                 return new LeftIndex( | 
					
						
							|  |  |  |                     [ | 
					
						
							|  |  |  |                         new TableOfContents(content, { noTopLevel: true, maxDepth: 1 }).SetClass( | 
					
						
							|  |  |  |                             "subtle" | 
					
						
							|  |  |  |                         ), | 
					
						
							|  |  |  |                         new DownloadStatisticsButton(perBatch), | 
					
						
							|  |  |  |                     ], | 
					
						
							|  |  |  |                     content | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  |     /** | 
					
						
							|  |  |  |      * 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") | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             const trigger = lines.findIndex((l) => l.startsWith(prefix) && l.endsWith("#import")) | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  |             if (trigger < 0) { | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             let theme = lines[trigger].substr(prefix.length) | 
					
						
							|  |  |  |             theme = theme.substr(0, theme.indexOf(".")) | 
					
						
							|  |  |  |             const date = Utils.ParseDate(prop.date_created) | 
					
						
							| 
									
										
										
										
											2022-01-25 21:55:51 +01:00
										 |  |  |             const dateStr = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  |             const key = theme + lines[0] + dateStr | 
					
						
							|  |  |  |             if (!perBatch.has(key)) { | 
					
						
							|  |  |  |                 perBatch.set(key, []) | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             let status: | 
					
						
							|  |  |  |                 | "open" | 
					
						
							|  |  |  |                 | "closed" | 
					
						
							|  |  |  |                 | "imported" | 
					
						
							|  |  |  |                 | "invalid" | 
					
						
							|  |  |  |                 | "already_mapped" | 
					
						
							|  |  |  |                 | "not_found" | 
					
						
							|  |  |  |                 | "has_comments" = "open" | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  |             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" | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 } else if ( | 
					
						
							|  |  |  |                     lastComment.indexOf("invalid") >= 0 || | 
					
						
							|  |  |  |                     lastComment.indexOf("incorrecto") >= 0 | 
					
						
							|  |  |  |                 ) { | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  |                     status = "invalid" | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |                 } else if ( | 
					
						
							|  |  |  |                     ["imported", "erbij", "toegevoegd", "added"].some( | 
					
						
							|  |  |  |                         (keyword) => lastComment.toLowerCase().indexOf(keyword) >= 0 | 
					
						
							|  |  |  |                     ) | 
					
						
							|  |  |  |                 ) { | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  |                     status = "imported" | 
					
						
							|  |  |  |                 } else { | 
					
						
							|  |  |  |                     status = "closed" | 
					
						
							|  |  |  |                 } | 
					
						
							| 
									
										
										
										
											2022-01-25 21:55:51 +01:00
										 |  |  |             } else if (prop.comments.length > 1) { | 
					
						
							|  |  |  |                 status = "has_comments" | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             perBatch.get(key).push({ | 
					
						
							|  |  |  |                 props: prop, | 
					
						
							|  |  |  |                 intro: lines[0], | 
					
						
							|  |  |  |                 theme, | 
					
						
							|  |  |  |                 dateStr, | 
					
						
							| 
									
										
										
										
											2022-06-29 03:05:25 +02:00
										 |  |  |                 status, | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         return perBatch | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-15 15:42:09 +01:00
										 |  |  | class ImportViewerGui extends LoginToggle { | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |     constructor() { | 
					
						
							|  |  |  |         const state = new UserRelatedState(undefined) | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |         const displayNameParam = QueryParameters.GetQueryParameter( | 
					
						
							|  |  |  |             "user", | 
					
						
							|  |  |  |             "", | 
					
						
							|  |  |  |             "The username of the person whom you want to see the notes for" | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         const searchParam = QueryParameters.GetQueryParameter( | 
					
						
							|  |  |  |             "search", | 
					
						
							|  |  |  |             "", | 
					
						
							|  |  |  |             "A text that should be included in the first comment of the note to be shown" | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-02-15 15:42:09 +01:00
										 |  |  |         super( | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  |             new VariableUiElement( | 
					
						
							|  |  |  |                 state.osmConnection.userDetails.map( | 
					
						
							|  |  |  |                     (ud) => { | 
					
						
							|  |  |  |                         const display_name = displayNameParam.data | 
					
						
							|  |  |  |                         const search = searchParam.data | 
					
						
							|  |  |  |                         if (display_name !== "" && search !== "") { | 
					
						
							|  |  |  |                             return new ImportInspector({ display_name, search }, undefined) | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         return new ImportInspector(ud, state) | 
					
						
							|  |  |  |                     }, | 
					
						
							|  |  |  |                     [displayNameParam, searchParam] | 
					
						
							|  |  |  |                 ) | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             "Login to inspect your import flows", | 
					
						
							|  |  |  |             state | 
					
						
							| 
									
										
										
										
											2022-02-15 15:42:09 +01:00
										 |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2022-01-24 03:09:21 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2022-01-22 04:01:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-09-08 21:40:48 +02:00
										 |  |  | new ImportViewerGui().AttachTo("main") |