Fix: maproulette import flow

This commit is contained in:
Pieter Vander Vennet 2023-06-09 16:13:35 +02:00
parent f80054558f
commit 5f7cc351c9
18 changed files with 331 additions and 114 deletions

View file

@ -116,11 +116,12 @@
currentFlowStep = "imported"
dispatch("confirm")
}}>
<span slot="image">
{#if importFlow.args.icon}
<img src={importFlow.args.icon}>
{/if}
<span slot="image" class="w-8 h-8 pr-4">
{#if importFlow.args.icon}
<img src={importFlow.args.icon}>
{:else}
<ToSvelte construct={Svg.confirm_svg().SetClass("w-8 h-8 pr-4")}/>
{/if}
</span>
<slot name="confirm-text">
{importFlow.args.text}

View file

@ -1,6 +1,6 @@
import {SpecialVisualizationState} from "../../SpecialVisualization";
import {Utils} from "../../../Utils";
import {Store, UIEventSource} from "../../../Logic/UIEventSource";
import {ImmutableStore, Store, UIEventSource} from "../../../Logic/UIEventSource";
import {Tag} from "../../../Logic/Tags/Tag";
import TagApplyButton from "../TagApplyButton";
import {PointImportFlowArguments} from "./PointImportFlowState";
@ -11,6 +11,7 @@ import FilteredLayer from "../../../Models/FilteredLayer";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import {LayerConfigJson} from "../../../Models/ThemeConfig/Json/LayerConfigJson";
import conflation_json from "../../../assets/layers/conflation/conflation.json";
import {And} from "../../../Logic/Tags/And";
export interface ImportFlowArguments {
readonly text: string
@ -85,6 +86,15 @@ ${Utils.special_visualizations_importRequirementDocs}
"] of this object, namely ",
items
)
if(items.startsWith("{")){
// This is probably a JSON
const properties: Record<string, string> = JSON.parse(items)
const keys = Object.keys(properties)
const tags = keys.map(k => new Tag(k, properties[k]))
return new ImmutableStore((tags))
}
newTags = TagApplyButton.generateTagsToApply(items, originalFeatureTags)
} else {
newTags = TagApplyButton.generateTagsToApply(tags, originalFeatureTags)

View file

@ -1,22 +1,23 @@
import { AutoAction } from "./AutoApplyButton"
import {AutoAction} from "./AutoApplyButton"
import Translations from "../i18n/Translations"
import { VariableUiElement } from "../Base/VariableUIElement"
import {VariableUiElement} from "../Base/VariableUIElement"
import BaseUIElement from "../BaseUIElement"
import { FixedUiElement } from "../Base/FixedUiElement"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { SubtleButton } from "../Base/SubtleButton"
import {FixedUiElement} from "../Base/FixedUiElement"
import {Store, UIEventSource} from "../../Logic/UIEventSource"
import {SubtleButton} from "../Base/SubtleButton"
import Combine from "../Base/Combine"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import { And } from "../../Logic/Tags/And"
import {And} from "../../Logic/Tags/And"
import Toggle from "../Input/Toggle"
import { Utils } from "../../Utils"
import { Tag } from "../../Logic/Tags/Tag"
import {Utils} from "../../Utils"
import {Tag} from "../../Logic/Tags/Tag"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { Changes } from "../../Logic/Osm/Changes"
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import {Changes} from "../../Logic/Osm/Changes"
import {SpecialVisualization, SpecialVisualizationState} from "../SpecialVisualization"
import {IndexedFeatureSource} from "../../Logic/FeatureSource/FeatureSource";
import {Feature} from "geojson";
import LayerConfig from "../../Models/ThemeConfig/LayerConfig";
import Maproulette from "../../Logic/Maproulette";
export default class TagApplyButton implements AutoAction, SpecialVisualization {
public readonly funcName = "tag_apply"
@ -27,7 +28,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
public readonly args = [
{
name: "tags_to_apply",
doc: "A specification of the tags to apply",
doc: "A specification of the tags to apply. This is either hardcoded in the layer or the `$name` of a property containing the tags to apply. If redirected and the value of the linked property starts with `{`, the other property will be interpreted as a json object",
},
{
name: "message",
@ -42,10 +43,62 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
defaultValue: undefined,
doc: "If specified, applies the the tags onto _another_ object. The id will be read from properties[id_of_object_to_apply_this_one] of the selected object. The tags are still calculated based on the tags of the _selected_ element",
},
{
name:"maproulette_task_id",
defaultValue: undefined,
doc: "If specified, this maproulette-challenge will be closed when the tags are applied"
}
]
public readonly example =
"`{tag_apply(survey_date=$_now:date, Surveyed today!)}`, `{tag_apply(addr:street=$addr:street, Apply the address, apply_icon.svg, _closest_osm_id)"
public static generateTagsToApply(
spec: string,
tagSource: Store<Record<string, string>>
): Store<Tag[]> {
// Check whether we need to look up a single value
if (!spec.includes(";") && !spec.includes("=") && spec.startsWith("$")) {
// We seem to be dealing with a single value, fetch it
spec = tagSource.data[spec.replace("$", "")]
}
let tgsSpec: [string, string][]
if (spec.startsWith("{")) {
const properties = JSON.parse(spec)
tgsSpec = []
for (const key of Object.keys(properties)) {
tgsSpec.push([key, properties[key]])
}
} else {
tgsSpec = TagApplyButton.parseTagSpec(spec)
}
return tagSource.map((tags) => {
const newTags: Tag[] = []
for (const [key, value] of tgsSpec) {
if (value.indexOf("$") >= 0) {
let parts = value.split("$")
// THe first of the split won't start with a '$', so no substitution needed
let actualValue = parts[0]
parts.shift()
for (const part of parts) {
const [_, varName, leftOver] = part.match(/([a-zA-Z0-9_:]*)(.*)/)
actualValue += (tags[varName] ?? "") + leftOver
}
newTags.push(new Tag(key, actualValue))
} else {
newTags.push(new Tag(key, value))
}
}
return newTags
})
}
/**
* Parses a tag specification
*
@ -79,41 +132,6 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
return tgsSpec
}
public static generateTagsToApply(
spec: string,
tagSource: Store<Record<string, string>>
): Store<Tag[]> {
// Check whether we need to look up a single value
if (!spec.includes(";") && !spec.includes("=") && spec.includes("$")) {
// We seem to be dealing with a single value, fetch it
spec = tagSource.data[spec.replace("$", "")]
}
const tgsSpec = TagApplyButton.parseTagSpec(spec)
return tagSource.map((tags) => {
const newTags: Tag[] = []
for (const [key, value] of tgsSpec) {
if (value.indexOf("$") >= 0) {
let parts = value.split("$")
// THe first of the split won't start with a '$', so no substitution needed
let actualValue = parts[0]
parts.shift()
for (const part of parts) {
const [_, varName, leftOver] = part.match(/([a-zA-Z0-9_:]*)(.*)/)
actualValue += (tags[varName] ?? "") + leftOver
}
newTags.push(new Tag(key, actualValue))
} else {
newTags.push(new Tag(key, value))
}
}
return newTags
})
}
public async applyActionOn(
feature: Feature,
state: {
@ -123,7 +141,6 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
},
tags: UIEventSource<any>,
args: string[],
): Promise<void> {
const tagsToApply = TagApplyButton.generateTagsToApply(args[0], tags)
const targetIdKey = args[3]
@ -139,6 +156,13 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
}
)
await state.changes.applyAction(changeAction)
const maproulette_id_key = args[4]
if(maproulette_id_key){
const maproulette_id = Number(tags.data[maproulette_id_key])
await Maproulette.singleton.closeTask(maproulette_id, Maproulette.STATUS_FIXED, {
comment: "Tags are copied onto "+targetId+" with MapComplete"
})
}
}
public constr(
@ -163,7 +187,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
let el: BaseUIElement = new FixedUiElement(tagsStr)
if (targetIdKey !== undefined) {
const targetId = tags.data[targetIdKey] ?? tags.data.id
el = t.appliedOnAnotherObject.Subs({ tags: tagsStr, id: targetId })
el = t.appliedOnAnotherObject.Subs({tags: tagsStr, id: targetId})
}
return el
})

View file

@ -11,27 +11,27 @@
export let tags: UIEventSource<Record<string, string> | undefined>;
let _tags: Record<string, string>;
onDestroy(tags.addCallbackAndRun(tags => {
_tags = tags;
}));
let trs: { then: Translation; icon?: string; iconClass?: string }[];
export let state: SpecialVisualizationState;
export let selectedElement: Feature;
export let layer: LayerConfig;
export let config: TagRenderingConfig;
export let extraClasses: string= ""
if (config === undefined) {
throw "Config is undefined in tagRenderingAnswer";
}
export let layer: LayerConfig;
let trs: { then: Translation; icon?: string; iconClass?: string }[];
$:{
onDestroy(tags.addCallbackAndRun(tags => {
_tags = tags;
trs = Utils.NoNull(config?.GetRenderValues(_tags));
}
export let extraClasses: string= ""
}));
let classes = ""
$:classes = config?.classes?.join(" ") ?? "";
</script>
{#if config !== undefined && (config?.condition === undefined || config.condition.matchesProperties(_tags))}
<div class={"link-underline flex flex-col w-full overflow-hidden "+classes+" "+extraClasses}>
<div class={"link-underline flex flex-col w-full "+classes+" "+extraClasses}>
{#if trs.length === 1}
<TagRenderingMapping mapping={trs[0]} {tags} {state} {selectedElement} {layer}></TagRenderingMapping>
{/if}

View file

@ -78,7 +78,7 @@
<XCircleIcon slot="upper-right" class="w-8 h-8 cursor-pointer" on:click={() => {editMode = false}}/>
</TagRenderingQuestion>
{:else}
<div class="flex justify-between low-interaction items-center rounded px-2">
<div class="flex justify-between low-interaction items-center rounded px-2 overflow-hidden">
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/>
<button on:click={() => {editMode = true}}
class="shrink-0 w-8 h-8 rounded-full p-1 secondary self-start">
@ -87,6 +87,8 @@
</div>
{/if}
{:else }
<div class="p-2 overflow-hidden">
<TagRenderingAnswer {config} {tags} {selectedElement} {state} {layer}/>
</div>
{/if}
</div>

View file

@ -959,24 +959,6 @@ export default class SpecialVisualizations {
defaultValue: "id",
},
],
example:
" The following example sets the status to '2' (false positive)\n" +
"\n" +
"```json\n" +
"{\n" +
' "id": "mark_duplicate",\n' +
' "render": {\n' +
' "special": {\n' +
' "type": "maproulette_set_status",\n' +
' "message": {\n' +
' "en": "Mark as not found or false positive"\n' +
" },\n" +
' "status": "2",\n' +
' "image": "close"\n' +
" }\n" +
" }\n" +
"}\n" +
"```",
constr: (state, tags, args) => {
const isUploading = new UIEventSource(false)
const t = Translations.t.notes
@ -1080,6 +1062,24 @@ export default class SpecialVisualizations {
{
funcName: "maproulette_set_status",
docs: "Change the status of the given MapRoulette task",
example:
" The following example sets the status to '2' (false positive)\n" +
"\n" +
"```json\n" +
"{\n" +
' "id": "mark_duplicate",\n' +
' "render": {\n' +
' "special": {\n' +
' "type": "maproulette_set_status",\n' +
' "message": {\n' +
' "en": "Mark as not found or false positive"\n' +
" },\n" +
' "status": "2",\n' +
' "image": "close"\n' +
" }\n" +
" }\n" +
"}\n" +
"```",
args: [
{
name: "message",
@ -1110,11 +1110,14 @@ export default class SpecialVisualizations {
if (image === "") {
image = "confirm"
}
if(maproulette_id_key === "" || maproulette_id_key === undefined){
maproulette_id_key = "mr_taskId"
}
if (Svg.All[image] !== undefined || Svg.All[image + ".svg"] !== undefined) {
if (image.endsWith(".svg")) {
image = image.substring(0, image.length - 4)
}
image = Svg[image + "_ui"]()
image = Svg[image + "_svg"]()
}
const failed = new UIEventSource(false)
@ -1122,7 +1125,7 @@ export default class SpecialVisualizations {
Translations.t.general.loading,
async () => {
const maproulette_id =
tagsSource.data[maproulette_id_key] ?? tagsSource.data.id
tagsSource.data[maproulette_id_key] ?? tagsSource.data.mr_taskId ?? tagsSource.data.id
try {
await Maproulette.singleton.closeTask(
Number(maproulette_id),
@ -1150,13 +1153,19 @@ export default class SpecialVisualizations {
return new VariableUiElement(
tagsSource
.map(
(tgs) =>
tgs["status"] ??
Maproulette.STATUS_MEANING[tgs["mr_taskStatus"]]
(tgs) => {
if(tgs["status"]){
return tgs["status"]
}
const code = tgs["mr_taskStatus"]
console.log("Code is", code, Maproulette.codeToIndex(code))
return Maproulette.codeToIndex(code)
}
)
.map(Number)
.map(
(status) => {
console.log("Close MR button: status is", status)
if (failed.data) {
return new FixedUiElement(
"ERROR - could not close the MapRoulette task"