mapcomplete/UI/Popup/MultiApply.ts

159 lines
5.5 KiB
TypeScript

import { Store } 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: Store<string[]>
keysToApply: string[]
text: string
autoapply: boolean
overwrite: boolean
tagsSource: Store<any>
state: {
changes: Changes
allElements: ElementStorage
layoutToUse: LayoutConfig
osmConnection: OsmConnection
}
}
class MultiApplyExecutor {
private static executorCache = new Map<string, 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])
// By stringifying, we have a very clear ping when they changec
return JSON.stringify(currentValues)
})
relevantValues.addCallbackD((_) => {
self.applyTaggingOnOtherFeatures()
})
}
}
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
}
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",
})
)
}
}
}
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 FixedUiElement(p.text).SetClass("block"))
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: Store<boolean> = p.state.osmConnection.isLoggedIn.map(
(loggedIn) => {
return loggedIn && p.featureIds.data.length > 0
},
[p.featureIds]
)
super(new Combine(elems), undefined, isShown)
}
}