forked from MapComplete/MapComplete
Fix: maproulette import flow
This commit is contained in:
parent
f80054558f
commit
5f7cc351c9
18 changed files with 331 additions and 114 deletions
|
@ -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}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue