forked from MapComplete/MapComplete
Reformat all files with prettier
This commit is contained in:
parent
e22d189376
commit
b541d3eab4
382 changed files with 50893 additions and 35566 deletions
|
@ -1,56 +1,62 @@
|
|||
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";
|
||||
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"
|
||||
|
||||
interface NoteProperties {
|
||||
"id": number,
|
||||
"url": string,
|
||||
"date_created": string,
|
||||
closed_at?: string,
|
||||
"status": "open" | "closed",
|
||||
"comments": {
|
||||
date: string,
|
||||
uid: number,
|
||||
user: string,
|
||||
text: string,
|
||||
id: number
|
||||
url: string
|
||||
date_created: string
|
||||
closed_at?: string
|
||||
status: "open" | "closed"
|
||||
comments: {
|
||||
date: string
|
||||
uid: number
|
||||
user: string
|
||||
text: string
|
||||
html: string
|
||||
}[]
|
||||
}
|
||||
|
||||
interface NoteState {
|
||||
props: NoteProperties,
|
||||
theme: string,
|
||||
intro: string,
|
||||
dateStr: string,
|
||||
status: "imported" | "already_mapped" | "invalid" | "closed" | "not_found" | "open" | "has_comments"
|
||||
props: NoteProperties
|
||||
theme: string
|
||||
intro: string
|
||||
dateStr: string
|
||||
status:
|
||||
| "imported"
|
||||
| "already_mapped"
|
||||
| "invalid"
|
||||
| "closed"
|
||||
| "not_found"
|
||||
| "open"
|
||||
| "has_comments"
|
||||
}
|
||||
|
||||
class DownloadStatisticsButton extends SubtleButton {
|
||||
constructor(states: NoteState[][]) {
|
||||
super(Svg.statistics_svg(), "Download statistics");
|
||||
super(Svg.statistics_svg(), "Download statistics")
|
||||
this.onClick(() => {
|
||||
|
||||
const st: NoteState[] = [].concat(...states)
|
||||
|
||||
const fields = [
|
||||
|
@ -61,26 +67,27 @@ class DownloadStatisticsButton extends SubtleButton {
|
|||
"date_closed",
|
||||
"days_open",
|
||||
"intro",
|
||||
"...comments"
|
||||
"...comments",
|
||||
]
|
||||
const values: string[][] = st.map(note => {
|
||||
|
||||
|
||||
return [note.props.id + "",
|
||||
const values: string[][] = st.map((note) => {
|
||||
return [
|
||||
note.props.id + "",
|
||||
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) ?? "",
|
||||
JSON.stringify(note.intro),
|
||||
...note.props.comments.map(c => JSON.stringify(c.user) + ": " + JSON.stringify(c.text))
|
||||
...note.props.comments.map(
|
||||
(c) => JSON.stringify(c.user) + ": " + JSON.stringify(c.text)
|
||||
),
|
||||
]
|
||||
})
|
||||
|
||||
Utils.offerContentsAsDownloadableFile(
|
||||
[fields, ...values].map(c => c.join(", ")).join("\n"),
|
||||
[fields, ...values].map((c) => c.join(", ")).join("\n"),
|
||||
"mapcomplete_import_notes_overview.csv",
|
||||
{
|
||||
mimetype: "text/csv"
|
||||
mimetype: "text/csv",
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -92,32 +99,32 @@ class MassAction extends Combine {
|
|||
const textField = ValidatedTextField.ForType("text").ConstructInputElement()
|
||||
|
||||
const actions = new DropDown<{
|
||||
predicate: (p: NoteProperties) => boolean,
|
||||
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..."
|
||||
shown: <string | BaseUIElement>"Pick an option...",
|
||||
},
|
||||
{
|
||||
value: {
|
||||
predicate: p => p.status === "open",
|
||||
action: async p => {
|
||||
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"
|
||||
shown: "Add comment to every open note and close all notes",
|
||||
},
|
||||
{
|
||||
value: {
|
||||
predicate: p => p.status === "open",
|
||||
action: async p => {
|
||||
predicate: (p) => p.status === "open",
|
||||
action: async (p) => {
|
||||
const txt = textField.GetValue().data
|
||||
state.osmConnection.addCommentToNote(p.id, txt)
|
||||
}
|
||||
},
|
||||
},
|
||||
shown: "Add comment to every open note"
|
||||
shown: "Add comment to every open note",
|
||||
},
|
||||
/*
|
||||
{
|
||||
|
@ -131,25 +138,22 @@ class MassAction extends Combine {
|
|||
},
|
||||
shown:"On every open note, read the 'note='-tag and and this note as comment. (This action ignores the textfield)"
|
||||
},//*/
|
||||
|
||||
])
|
||||
|
||||
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)
|
||||
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
|
||||
}
|
||||
handledNotesCounter.setData(props.length)
|
||||
})
|
||||
await action(prop)
|
||||
}
|
||||
handledNotesCounter.setData(props.length)
|
||||
})
|
||||
super([
|
||||
|
||||
actions,
|
||||
textField.SetClass("w-full border border-black"),
|
||||
new Toggle(
|
||||
|
@ -157,37 +161,57 @@ class MassAction extends Combine {
|
|||
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)
|
||||
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)
|
||||
)
|
||||
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()])
|
||||
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()]
|
||||
)
|
||||
),
|
||||
new Toggle(
|
||||
new FixedUiElement("Testmode enable").SetClass("alert"), undefined,
|
||||
new FixedUiElement("Testmode enable").SetClass("alert"),
|
||||
undefined,
|
||||
state.featureSwitchIsTesting
|
||||
)
|
||||
]);
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class NoteTable extends Combine {
|
||||
|
||||
private static individualActions: [() => BaseUIElement, string][] = [
|
||||
[Svg.not_found_svg, "This feature does not exist"],
|
||||
[Svg.addSmall_svg, "imported"],
|
||||
[Svg.duplicate_svg, "Already mapped"]
|
||||
[Svg.duplicate_svg, "Already mapped"],
|
||||
]
|
||||
|
||||
constructor(noteStates: NoteState[], state?: UserRelatedState) {
|
||||
|
@ -195,18 +219,21 @@ class NoteTable extends Combine {
|
|||
|
||||
const table = new Table(
|
||||
["id", "status", "last comment", "last modified by", "actions"],
|
||||
noteStates.map(ns => NoteTable.noteField(ns, state)),
|
||||
{sortable: true}
|
||||
).SetClass("zebra-table link-underline");
|
||||
|
||||
noteStates.map((ns) => NoteTable.noteField(ns, state)),
|
||||
{ sortable: true }
|
||||
).SetClass("zebra-table link-underline")
|
||||
|
||||
super([
|
||||
new Title("Mass apply an action on " + noteStates.length + " notes below"),
|
||||
state !== undefined ? new MassAction(state, noteStates.map(ns => ns.props)).SetClass("block") : undefined,
|
||||
state !== undefined
|
||||
? new MassAction(
|
||||
state,
|
||||
noteStates.map((ns) => ns.props)
|
||||
).SetClass("block")
|
||||
: undefined,
|
||||
table,
|
||||
new Title("Example note", 4),
|
||||
new FixedUiElement(typicalComment).SetClass("literal-code link-underline"),
|
||||
|
||||
])
|
||||
this.SetClass("flex flex-col")
|
||||
}
|
||||
|
@ -214,9 +241,10 @@ class NoteTable extends Combine {
|
|||
private static noteField(ns: NoteState, state: UserRelatedState) {
|
||||
const link = new Link(
|
||||
"" + ns.props.id,
|
||||
"https://openstreetmap.org/note/" + ns.props.id, true
|
||||
"https://openstreetmap.org/note/" + ns.props.id,
|
||||
true
|
||||
)
|
||||
let last_comment = "";
|
||||
let last_comment = ""
|
||||
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) {
|
||||
|
@ -226,41 +254,56 @@ class NoteTable extends Combine {
|
|||
}
|
||||
}
|
||||
const statusIcon = BatchView.icons[ns.status]().SetClass("h-4 w-4 shrink-0")
|
||||
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
|
||||
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,
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class BatchView extends Toggleable {
|
||||
|
||||
public static icons = {
|
||||
open: Svg.compass_svg,
|
||||
has_comments: Svg.speech_bubble_svg,
|
||||
|
@ -272,10 +315,9 @@ class BatchView extends Toggleable {
|
|||
}
|
||||
|
||||
constructor(noteStates: NoteState[], state?: UserRelatedState) {
|
||||
|
||||
noteStates.sort((a, b) => a.props.id - b.props.id)
|
||||
|
||||
const {theme, intro, dateStr} = noteStates[0]
|
||||
const { theme, intro, dateStr } = noteStates[0]
|
||||
|
||||
const statusHist = new Map<string, number>()
|
||||
for (const noteState of noteStates) {
|
||||
|
@ -284,109 +326,151 @@ class BatchView extends Toggleable {
|
|||
statusHist.set(st, c + 1)
|
||||
}
|
||||
|
||||
const unresolvedTotal = (statusHist.get("open") ?? 0) + (statusHist.get("has_comments") ?? 0)
|
||||
const badges: (BaseUIElement)[] = [
|
||||
const unresolvedTotal =
|
||||
(statusHist.get("open") ?? 0) + (statusHist.get("has_comments") ?? 0)
|
||||
const badges: BaseUIElement[] = [
|
||||
new FixedUiElement(dateStr).SetClass("literal-code rounded-full"),
|
||||
new FixedUiElement(noteStates.length + " total").SetClass("literal-code rounded-full ml-1 border-4 border-gray")
|
||||
new FixedUiElement(noteStates.length + " total")
|
||||
.SetClass("literal-code rounded-full ml-1 border-4 border-gray")
|
||||
.onClick(() => filterOn.setData(undefined)),
|
||||
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")
|
||||
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"),
|
||||
]
|
||||
|
||||
const filterOn = new UIEventSource<string>(undefined)
|
||||
Object.keys(BatchView.icons).forEach(status => {
|
||||
Object.keys(BatchView.icons).forEach((status) => {
|
||||
const count = statusHist.get(status)
|
||||
if (count === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
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")
|
||||
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"
|
||||
)
|
||||
|
||||
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()
|
||||
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()
|
||||
|
||||
badges.push(toggle)
|
||||
})
|
||||
|
||||
|
||||
const fullTable = new NoteTable(noteStates, state);
|
||||
|
||||
const fullTable = new NoteTable(noteStates, state)
|
||||
|
||||
super(
|
||||
new Combine([
|
||||
new Title(theme + ": " + intro, 2),
|
||||
new Combine(badges).SetClass("flex flex-wrap"),
|
||||
]),
|
||||
new VariableUiElement(filterOn.map(filter => {
|
||||
if (filter === undefined) {
|
||||
return fullTable
|
||||
}
|
||||
return new NoteTable(noteStates.filter(ns => ns.status === filter), state)
|
||||
})),
|
||||
new VariableUiElement(
|
||||
filterOn.map((filter) => {
|
||||
if (filter === undefined) {
|
||||
return fullTable
|
||||
}
|
||||
return new NoteTable(
|
||||
noteStates.filter((ns) => ns.status === filter),
|
||||
state
|
||||
)
|
||||
})
|
||||
),
|
||||
{
|
||||
closeOnClick: false
|
||||
})
|
||||
|
||||
closeOnClick: false,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ImportInspector extends VariableUiElement {
|
||||
|
||||
constructor(userDetails: { uid: number } | { display_name: string, search?: string }, state: UserRelatedState) {
|
||||
let url;
|
||||
constructor(
|
||||
userDetails: { uid: number } | { display_name: string; search?: string },
|
||||
state: UserRelatedState
|
||||
) {
|
||||
let url
|
||||
|
||||
if (userDetails["uid"] !== undefined) {
|
||||
url = "https://api.openstreetmap.org/api/0.6/notes/search.json?user=" + userDetails["uid"] + "&closed=730&limit=10000&sort=created_at&q=%23import"
|
||||
url =
|
||||
"https://api.openstreetmap.org/api/0.6/notes/search.json?user=" +
|
||||
userDetails["uid"] +
|
||||
"&closed=730&limit=10000&sort=created_at&q=%23import"
|
||||
} else {
|
||||
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")
|
||||
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")
|
||||
}
|
||||
|
||||
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
|
||||
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(noteStates, state)
|
||||
)
|
||||
|
||||
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
|
||||
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(noteStates, state))
|
||||
|
||||
const accordeon = new Accordeon(els)
|
||||
let contents = [];
|
||||
if (state?.osmConnection?.isLoggedIn?.data) {
|
||||
contents =
|
||||
[
|
||||
const accordeon = new Accordeon(els)
|
||||
let contents = []
|
||||
if (state?.osmConnection?.isLoggedIn?.data) {
|
||||
contents = [
|
||||
new Title(Translations.t.importInspector.title, 1),
|
||||
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
|
||||
)
|
||||
|
||||
}));
|
||||
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
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -397,7 +481,7 @@ class ImportInspector extends VariableUiElement {
|
|||
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"))
|
||||
const trigger = lines.findIndex((l) => l.startsWith(prefix) && l.endsWith("#import"))
|
||||
if (trigger < 0) {
|
||||
continue
|
||||
}
|
||||
|
@ -409,16 +493,30 @@ class ImportInspector extends VariableUiElement {
|
|||
if (!perBatch.has(key)) {
|
||||
perBatch.set(key, [])
|
||||
}
|
||||
let status: "open" | "closed" | "imported" | "invalid" | "already_mapped" | "not_found" | "has_comments" = "open"
|
||||
let status:
|
||||
| "open"
|
||||
| "closed"
|
||||
| "imported"
|
||||
| "invalid"
|
||||
| "already_mapped"
|
||||
| "not_found"
|
||||
| "has_comments" = "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) {
|
||||
} else if (
|
||||
lastComment.indexOf("invalid") >= 0 ||
|
||||
lastComment.indexOf("incorrecto") >= 0
|
||||
) {
|
||||
status = "invalid"
|
||||
} else if (["imported", "erbij", "toegevoegd", "added"].some(keyword => lastComment.toLowerCase().indexOf(keyword) >= 0)) {
|
||||
} else if (
|
||||
["imported", "erbij", "toegevoegd", "added"].some(
|
||||
(keyword) => lastComment.toLowerCase().indexOf(keyword) >= 0
|
||||
)
|
||||
) {
|
||||
status = "imported"
|
||||
} else {
|
||||
status = "closed"
|
||||
|
@ -435,28 +533,41 @@ class ImportInspector extends VariableUiElement {
|
|||
status,
|
||||
})
|
||||
}
|
||||
return perBatch;
|
||||
return perBatch
|
||||
}
|
||||
}
|
||||
|
||||
class ImportViewerGui extends LoginToggle {
|
||||
|
||||
constructor() {
|
||||
const state = new UserRelatedState(undefined)
|
||||
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")
|
||||
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"
|
||||
)
|
||||
super(
|
||||
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
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
new ImportViewerGui().AttachTo("main")
|
||||
new ImportViewerGui().AttachTo("main")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue