Refactoring: port 'TagApplyButton' to svelte

This commit is contained in:
Pieter Vander Vennet 2025-06-19 01:51:09 +02:00
parent 2bc2a6cddf
commit d1f7ae2462
8 changed files with 109 additions and 99 deletions

View file

@ -79,7 +79,7 @@
"source": {
"geoJson": "https://maproulette.org/api/v2/challenge/view/39519"
},
"isShown": "mr_taskStatus=Created",
"calculatedTags": [
"_closest_osm_poi=closest(feat)('atm')?.properties?.id",
"_closest_osm_poi_distance=Math.round(distanceTo(feat)(feat.properties._closest_osm_poi))",
@ -161,7 +161,7 @@
"it": "Aggiungi tutti i tag suggeriti allo sportello bancomat più vicino"
},
"image": "./assets/svg/addSmall.svg",
"maproulette_id": "mr_taskId"
"maproulette_id": "id"
}
}
},

View file

@ -171,6 +171,7 @@
},
"apply_button": {
"appliedOnAnotherObject": "The object {id} will receive {tags}",
"applying": "Applying changes",
"isApplied": "The changes are applied"
},
"attribution": {

View file

@ -1,42 +1,14 @@
import { Utils } from "../../Utils"
/** This code is autogenerated - do not edit. Edit ./assets/layers/usersettings/usersettings.json instead */
export class ThemeMetaTagging {
public static readonly themeName = "usersettings"
public static readonly themeName = "usersettings"
public metaTaggging_for_usersettings(feat: { properties: Record<string, string> }) {
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_md", () =>
feat.properties._description
.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)
?.at(1)
)
Utils.AddLazyProperty(
feat.properties,
"_d",
() => feat.properties._description?.replace(/&lt;/g, "<")?.replace(/&gt;/g, ">") ?? ""
)
Utils.AddLazyProperty(feat.properties, "_mastodon_candidate_a", () =>
((feat) => {
const e = document.createElement("div")
e.innerHTML = feat.properties._d
return Array.from(e.getElementsByTagName("a")).filter(
(a) => a.href.match(/mastodon|en.osm.town/) !== null
)[0]?.href
})(feat)
)
Utils.AddLazyProperty(feat.properties, "_mastodon_link", () =>
((feat) => {
const e = document.createElement("div")
e.innerHTML = feat.properties._d
return Array.from(e.getElementsByTagName("a")).filter(
(a) => a.getAttribute("rel")?.indexOf("me") >= 0
)[0]?.href
})(feat)
)
Utils.AddLazyProperty(
feat.properties,
"_mastodon_candidate",
() => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a
)
feat.properties["__current_backgroun"] = "initial_value"
}
}
public metaTaggging_for_usersettings(feat: {properties: Record<string, string>}) {
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_md', () => feat.properties._description.match(/\[[^\]]*\]\((.*(mastodon|en.osm.town).*)\).*/)?.at(1) )
Utils.AddLazyProperty(feat.properties, '_d', () => feat.properties._description?.replace(/&lt;/g,'<')?.replace(/&gt;/g,'>') ?? '' )
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate_a', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.href.match(/mastodon|en.osm.town/) !== null)[0]?.href }) (feat) )
Utils.AddLazyProperty(feat.properties, '_mastodon_link', () => (feat => {const e = document.createElement('div');e.innerHTML = feat.properties._d;return Array.from(e.getElementsByTagName("a")).filter(a => a.getAttribute("rel")?.indexOf('me') >= 0)[0]?.href})(feat) )
Utils.AddLazyProperty(feat.properties, '_mastodon_candidate', () => feat.properties._mastodon_candidate_md ?? feat.properties._mastodon_candidate_a )
feat.properties['__current_backgroun'] = 'initial_value'
}
}

View file

@ -1,7 +1,6 @@
import { Utils } from "../../../Utils"
import { ImmutableStore, Store, UIEventSource } from "../../../Logic/UIEventSource"
import { Tag } from "../../../Logic/Tags/Tag"
import TagApplyButton from "../TagApplyButton"
import { PointImportFlowArguments } from "./PointImportFlowState"
import { Translation } from "../../i18n/Translation"
import Translations from "../../i18n/Translations"
@ -11,6 +10,7 @@ import { LayerConfigJson } from "../../../Models/ThemeConfig/Json/LayerConfigJso
import conflation_json from "../../../../assets/layers/conflation/conflation.json"
import { SpecialVisualizationState } from "../../SpecialVisualization"
import { OsmTags } from "../../../Models/OsmFeature"
import TagApplyViz from "../../SpecialVisualisations/TagApplyViz"
export interface ImportFlowArguments {
readonly text: string
@ -96,9 +96,9 @@ ${Utils.special_visualizations_importRequirementDocs}
return new ImmutableStore(tags)
}
newTags = TagApplyButton.generateTagsToApply(items, originalFeatureTags)
newTags = TagApplyViz.generateTagsToApply(items, originalFeatureTags)
} else {
newTags = TagApplyButton.generateTagsToApply(tags, originalFeatureTags)
newTags = TagApplyViz.generateTagsToApply(tags, originalFeatureTags)
}
return newTags
}

View file

@ -58,7 +58,7 @@
}, 50)
}
const _htmlElement = new UIEventSource<HTMLElement>(undefined)
let _htmlElement = new UIEventSource<HTMLElement>(undefined)
$: _htmlElement.setData(htmlElem)
function setHighlighting() {

View file

@ -2,7 +2,6 @@ import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisua
import Maproulette from "../../Logic/Maproulette"
import SvelteUIElement from "../Base/SvelteUIElement"
import MaprouletteSetStatus from "../MapRoulette/MaprouletteSetStatus.svelte"
import TagApplyButton from "../Popup/TagApplyButton"
import { PointImportButtonViz } from "../Popup/ImportButtons/PointImportButtonViz"
import WayImportButtonViz from "../Popup/ImportButtons/WayImportButtonViz"
import ConflateImportButtonViz from "../Popup/ImportButtons/ConflateImportButtonViz"
@ -16,11 +15,12 @@ import LinkedDataLoader from "../../Logic/Web/LinkedDataLoader"
import Toggle from "../Input/Toggle"
import ComparisonTool from "../Comparison/ComparisonTool.svelte"
import { Utils } from "../../Utils"
import TagApplyViz from "./TagApplyViz"
export class DataImportSpecialVisualisations {
public static initList(): (SpecialVisualization & { group })[] {
return [
new TagApplyButton(),
new TagApplyViz(),
new PointImportButtonViz(),
new WayImportButtonViz(),
new ConflateImportButtonViz(),

View file

@ -0,0 +1,65 @@
<script lang="ts">
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { Tag } from "../../Logic/Tags/Tag"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import LoginToggle from "../Base/LoginToggle.svelte"
import Tr from "../Base/Tr.svelte"
import Loading from "../Base/Loading.svelte"
import TagApplyViz from "./TagApplyViz"
import Icon from "../Map/Icon.svelte"
import TagExplanation from "../Popup/TagExplanation.svelte"
import { And } from "../../Logic/Tags/And"
import Translations from "../i18n/Translations"
/**
* Works closely together with 'TagApplyViz
*/
export let msg: string
export let image: string | undefined
export let tags: Store<Record<string, string>>
export let tagsToApply: Store<Tag[]>
export let targetIdKey: string
export let onApply: () => Promise<void>
export let state: SpecialVisualizationState
const t = Translations.t.general.apply_button
// THis button might be shown on MapRoulette-items, which might already have been applied
// This will default to 'false' for non-maproulette challenges
let isMaprouletteAndApplied = tags?.data?.["mr_taskStatus"] !== undefined &&
tags?.data?.["mr_taskStatus"] !== "Created"
let currentState: UIEventSource<"init" | "applying" | "applied"> = new UIEventSource(
isMaprouletteAndApplied ? "applied" : "init")
async function apply() {
currentState.set("applying")
await onApply()
currentState.set("applied")
}
</script>
<LoginToggle {state} ignoreLoading>
{#if $currentState === "init"}
<button on:click={() => apply()}>
<Icon icon={image} />
<div class="flex flex-col">
<div>{msg}</div>
{#if targetIdKey}
<Tr cls="subtle break-all"
t={t.appliedOnAnotherObject.Subs({ id: $tags[targetIdKey], tags: new And($tagsToApply).asHumanString(false, false, {}) })} />
{:else}
<TagExplanation tagsFilter={new And($tagsToApply)} />
{/if}
</div>
</button>
{:else if $currentState === "applying"}
<Loading>
<Tr t={t.applying} />
</Loading>
{:else if $currentState === "applied"}
<Tr t={t.isApplied} cls="thanks" />
{/if}
</LoginToggle>

View file

@ -1,23 +1,16 @@
import { AutoAction } from "./AutoApplyButtonVis"
import Translations from "../i18n/Translations"
import { VariableUiElement } from "../Base/VariableUIElement"
import BaseUIElement from "../BaseUIElement"
import { FixedUiElement } from "../Base/FixedUiElement"
import { AutoAction } from "../Popup/AutoApplyButtonVis"
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import { Utils } from "../../Utils"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { SubtleButton } from "../Base/SubtleButton"
import Combine from "../Base/Combine"
import { Tag } from "../../Logic/Tags/Tag"
import { Feature } from "geojson"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import { And } from "../../Logic/Tags/And"
import Toggle from "../Input/Toggle"
import { Utils } from "../../Utils"
import { Tag } from "../../Logic/Tags/Tag"
import { SpecialVisualization, SpecialVisualizationState } from "../SpecialVisualization"
import { Feature } from "geojson"
import Maproulette from "../../Logic/Maproulette"
import SvelteUIElement from "../Base/SvelteUIElement"
import Icon from "../Map/Icon.svelte"
import TagApplyButton from "./TagApplyButton.svelte"
export default class TagApplyButton implements AutoAction, SpecialVisualization {
export default class TagApplyViz implements AutoAction, SpecialVisualization {
public readonly funcName = "tag_apply"
needsUrls = []
group = "data_import"
@ -47,7 +40,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
{
name: "maproulette_id",
defaultValue: undefined,
doc: "If specified, this maproulette-challenge will be closed when the tags are applied. This should be the ID of the task, _not_ the task_id.",
doc: "If specified, this maproulette-challenge will be closed when the tags are applied. This should be the `id` of the individual task, _not_ the task_id (which corresponds with the challenge).",
},
]
public readonly example =
@ -55,7 +48,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
public static generateTagsToApply(
spec: string,
tagSource: Store<Record<string, string>>
tagSource: Store<Record<string, string>>,
): Store<Tag[]> {
// Check whether we need to look up a single value
@ -73,7 +66,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
tgsSpec.push([key, properties[key]])
}
} else {
tgsSpec = TagApplyButton.parseTagSpec(spec)
tgsSpec = TagApplyViz.parseTagSpec(spec)
}
return tagSource.map((tags) => {
@ -135,9 +128,9 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
_: Feature,
state: SpecialVisualizationState,
tags: UIEventSource<any>,
args: string[]
args: string[],
): Promise<void> {
const tagsToApply = TagApplyButton.generateTagsToApply(args[0], tags)
const tagsToApply = TagApplyViz.generateTagsToApply(args[0], tags)
const targetIdKey = args[3]
const targetId = tags.data[targetIdKey] ?? tags.data.id
@ -148,7 +141,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
{
theme: state.theme.id,
changeType: "answer",
}
},
)
await state.changes.applyAction(changeAction)
try {
@ -159,6 +152,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
const maproulette_id_key = args[4]
if (maproulette_id_key) {
const maproulette_id = tags.data[maproulette_id_key]
console.log("Looking for maproulette feature with id", maproulette_id,"based on key", maproulette_id_key)
const maproulette_feature = state.indexedFeatures.featuresById.data.get(maproulette_id)
const maproulette_task_id = Number(maproulette_feature.properties.mr_taskId)
await Maproulette.singleton.closeTask(
@ -167,7 +161,7 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
state,
{
comment: "Tags are copied onto " + targetId + " with MapComplete",
}
},
)
maproulette_feature.properties["mr_taskStatus"] = "Fixed"
state.featureProperties.getStore(maproulette_id).ping()
@ -177,46 +171,24 @@ export default class TagApplyButton implements AutoAction, SpecialVisualization
public constr(
state: SpecialVisualizationState,
tags: UIEventSource<Record<string, string>>,
args: string[],
feature: Feature
): BaseUIElement {
const tagsToApply = TagApplyButton.generateTagsToApply(args[0], tags)
args: string[], feature: Feature,
): SvelteUIElement {
const tagsToApply: Store<Tag[]> = TagApplyViz.generateTagsToApply(args[0], tags)
const msg = args[1]
let image = args[2]?.trim()
if (image === "" || image === "undefined") {
image = undefined
}
const targetIdKey = args[3]
const t = Translations.t.general.apply_button
const tagsExplanation = new VariableUiElement(
tagsToApply.map((tagsToApply) => {
const tagsStr = tagsToApply.map((t) => t.asHumanString(false, true)).join("&")
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 })
}
return el
})
).SetClass("subtle break-all")
const applied = new UIEventSource(
tags?.data?.["mr_taskStatus"] !== undefined &&
tags?.data?.["mr_taskStatus"] !== "Created"
) // This will default to 'false' for non-maproulette challenges
const applyButton = new SubtleButton(
new SvelteUIElement(Icon, { icon: image }),
new Combine([msg, tagsExplanation]).SetClass("flex flex-col")
).onClick(async () => {
applied.setData(true)
const onApply = async () => {
await this.applyActionOn(feature, state, tags, args)
})
}
return new Toggle(
new Toggle(t.isApplied.SetClass("thanks"), applyButton, applied),
undefined,
state.osmConnection.isLoggedIn
)
return new SvelteUIElement(TagApplyButton, {
state, tags, tagsToApply, msg, image, targetIdKey, onApply
})
}
}