forked from MapComplete/MapComplete
Add multi-apply box/feature, use it in etymology-theme to apply tags onto all segments of the same street
This commit is contained in:
parent
d0dfe9f607
commit
d3550fefbe
22 changed files with 355 additions and 78 deletions
158
UI/Popup/MultiApply.ts
Normal file
158
UI/Popup/MultiApply.ts
Normal file
|
@ -0,0 +1,158 @@
|
|||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import BaseUIElement from "../BaseUIElement";
|
||||
import Combine from "../Base/Combine";
|
||||
import {SubtleButton} from "../Base/SubtleButton";
|
||||
import {Changes} from "../../Logic/Osm/Changes";
|
||||
import {FixedUiElement} from "../Base/FixedUiElement";
|
||||
import Translations from "../i18n/Translations";
|
||||
import {VariableUiElement} from "../Base/VariableUIElement";
|
||||
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction";
|
||||
import {Tag} from "../../Logic/Tags/Tag";
|
||||
import {ElementStorage} from "../../Logic/ElementStorage";
|
||||
import {And} from "../../Logic/Tags/And";
|
||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig";
|
||||
import Toggle from "../Input/Toggle";
|
||||
import {OsmConnection} from "../../Logic/Osm/OsmConnection";
|
||||
|
||||
|
||||
export interface MultiApplyParams {
|
||||
featureIds: UIEventSource<string[]>,
|
||||
keysToApply: string[],
|
||||
text: string,
|
||||
autoapply: boolean,
|
||||
overwrite: boolean,
|
||||
tagsSource: UIEventSource<any>,
|
||||
state: {
|
||||
changes: Changes,
|
||||
allElements: ElementStorage,
|
||||
layoutToUse: LayoutConfig,
|
||||
osmConnection: OsmConnection
|
||||
}
|
||||
}
|
||||
|
||||
class MultiApplyExecutor {
|
||||
|
||||
private readonly originalValues = new Map<string, string>()
|
||||
private readonly params: MultiApplyParams;
|
||||
|
||||
private constructor(params: MultiApplyParams) {
|
||||
this.params = params;
|
||||
const p = params
|
||||
|
||||
for (const key of p.keysToApply) {
|
||||
this.originalValues.set(key, p.tagsSource.data[key])
|
||||
}
|
||||
|
||||
if (p.autoapply) {
|
||||
|
||||
const self = this;
|
||||
const relevantValues = p.tagsSource.map(tags => {
|
||||
const currentValues = p.keysToApply.map(key => tags[key])
|
||||
const v = JSON.stringify(currentValues) // By stringifying, we have a very clear ping when they changec
|
||||
console.log("Values are", v)
|
||||
return v;
|
||||
})
|
||||
relevantValues.addCallbackD(_ => {
|
||||
self.applyTaggingOnOtherFeatures()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public applyTaggingOnOtherFeatures() {
|
||||
console.log("Multi-applying changes...")
|
||||
const featuresToChange = this.params.featureIds.data
|
||||
const changes = this.params.state.changes
|
||||
const allElements = this.params.state.allElements
|
||||
const keysToChange = this.params.keysToApply
|
||||
const overwrite = this.params.overwrite
|
||||
const selfTags = this.params.tagsSource.data;
|
||||
const theme = this.params.state.layoutToUse.id
|
||||
for (const id of featuresToChange) {
|
||||
const tagsToApply: Tag[] = []
|
||||
const otherFeatureTags = allElements.getEventSourceById(id).data
|
||||
for (const key of keysToChange) {
|
||||
const newValue = selfTags[key]
|
||||
if (newValue === undefined) {
|
||||
continue
|
||||
}
|
||||
const otherValue = otherFeatureTags[key]
|
||||
if (newValue === otherValue) {
|
||||
continue;// No changes to be made
|
||||
}
|
||||
|
||||
if (overwrite) {
|
||||
tagsToApply.push(new Tag(key, newValue))
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (otherValue === undefined || otherValue === "" || otherValue === this.originalValues.get(key)) {
|
||||
tagsToApply.push(new Tag(key, newValue))
|
||||
}
|
||||
}
|
||||
|
||||
if (tagsToApply.length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
changes.applyAction(
|
||||
new ChangeTagAction(id, new And(tagsToApply), otherFeatureTags, {
|
||||
theme,
|
||||
changeType: "answer"
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
private static executorCache = new Map<string, MultiApplyExecutor>()
|
||||
|
||||
public static GetApplicator(id: string, params: MultiApplyParams): MultiApplyExecutor {
|
||||
if (MultiApplyExecutor.executorCache.has(id)) {
|
||||
return MultiApplyExecutor.executorCache.get(id)
|
||||
}
|
||||
const applicator = new MultiApplyExecutor(params)
|
||||
MultiApplyExecutor.executorCache.set(id, applicator)
|
||||
return applicator
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default class MultiApply extends Toggle {
|
||||
|
||||
constructor(params: MultiApplyParams) {
|
||||
const p = params
|
||||
const t = Translations.t.multi_apply
|
||||
|
||||
|
||||
const featureId = p.tagsSource.data.id
|
||||
|
||||
if (featureId === undefined) {
|
||||
throw "MultiApply needs a feature id"
|
||||
}
|
||||
|
||||
const applicator = MultiApplyExecutor.GetApplicator(featureId, params)
|
||||
|
||||
const elems: (string | BaseUIElement)[] = []
|
||||
if (p.autoapply) {
|
||||
elems.push(new Combine([new FixedUiElement(p.text).SetClass("block") ]).SetClass("flex"))
|
||||
elems.push(new VariableUiElement(p.featureIds.map(featureIds =>
|
||||
t.autoApply.Subs({
|
||||
attr_names: p.keysToApply.join(", "),
|
||||
count: "" + featureIds.length
|
||||
}))).SetClass("block subtle text-sm"))
|
||||
} else {
|
||||
elems.push(
|
||||
new SubtleButton(undefined, p.text).onClick(() => applicator.applyTaggingOnOtherFeatures())
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const isShown: UIEventSource<boolean> = p.state.osmConnection.isLoggedIn.map(loggedIn => {
|
||||
return loggedIn && p.featureIds.data.length > 0
|
||||
}, [p.featureIds])
|
||||
super(new Combine(elems), undefined, isShown);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -28,6 +28,7 @@ import Minimap from "./Base/Minimap";
|
|||
import AllImageProviders from "../Logic/ImageProviders/AllImageProviders";
|
||||
import WikipediaBox from "./Wikipedia/WikipediaBox";
|
||||
import SimpleMetaTagger from "../Logic/SimpleMetaTagger";
|
||||
import MultiApply from "./Popup/MultiApply";
|
||||
|
||||
export interface SpecialVisualization {
|
||||
funcName: string,
|
||||
|
@ -484,8 +485,38 @@ There are also some technicalities in your theme to keep in mind:
|
|||
args[2], args[1], tagSource, rewrittenTags, lat, lon, Number(args[3]), state
|
||||
)
|
||||
}
|
||||
},
|
||||
{funcName: "multi_apply",
|
||||
docs: "A button to apply the tagging of this object onto a list of other features. This is an advanced feature for which you'll need calculatedTags",
|
||||
args:[
|
||||
{name: "feature_ids", doc: "A JSOn-serialized list of IDs of features to apply the tagging on"},
|
||||
{name: "keys", doc: "One key (or multiple keys, seperated by ';') of the attribute that should be copied onto the other features." },
|
||||
{name: "text", doc: "The text to show on the button"},
|
||||
{name:"autoapply",doc:"A boolean indicating wether this tagging should be applied automatically if the relevant tags on this object are changed. A visual element indicating the multi_apply is still shown"},
|
||||
{name:"overwrite",doc:"If set to 'true', the tags on the other objects will always be overwritten. The default behaviour will be to only change the tags on other objects if they are either undefined or had the same value before the change"}
|
||||
],
|
||||
example: "{multi_apply(_features_with_the_same_name_within_100m, name:etymology:wikidata;name:etymology, Apply etymology information on all nearby objects with the same name)}",
|
||||
constr: (state, tagsSource, args) => {
|
||||
const featureIdsKey = args[0]
|
||||
const keysToApply = args[1].split(";")
|
||||
const text = args[2]
|
||||
const autoapply = args[3]?.toLowerCase() === "true"
|
||||
const overwrite = args[4]?.toLowerCase() === "true"
|
||||
const featureIds : UIEventSource<string[]> = tagsSource.map(tags => JSON.parse(tags[featureIdsKey]))
|
||||
return new MultiApply(
|
||||
{
|
||||
featureIds,
|
||||
keysToApply,
|
||||
text,
|
||||
autoapply,
|
||||
overwrite,
|
||||
tagsSource,
|
||||
state
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage();
|
||||
|
|
|
@ -53,7 +53,6 @@ export default class WikidataPreviewBox extends VariableUiElement {
|
|||
]).SetClass("flex"),
|
||||
Wikidata.IdToArticle(wikidata.id) ,true).SetClass("must-link")
|
||||
|
||||
console.log(wikidata)
|
||||
let info = new Combine( [
|
||||
new Combine([Translation.fromMap(wikidata.labels).SetClass("font-bold"),
|
||||
link]).SetClass("flex justify-between"),
|
||||
|
|
|
@ -217,7 +217,7 @@ export class Translation extends BaseUIElement {
|
|||
|
||||
static fromMap(transl: Map<string, string>) {
|
||||
const translations = {}
|
||||
transl.forEach((value, key) => {
|
||||
transl?.forEach((value, key) => {
|
||||
translations[key] = value
|
||||
})
|
||||
return new Translation(translations);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue