mapcomplete/UI/ImportFlow/SelectTheme.ts

192 lines
6.8 KiB
TypeScript

import { FlowStep } from "./FlowStep"
import Combine from "../Base/Combine"
import { Store } from "../../Logic/UIEventSource"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { InputElement } from "../Input/InputElement"
import { AllKnownLayouts } from "../../Customizations/AllKnownLayouts"
import { FixedInputElement } from "../Input/FixedInputElement"
import Img from "../Base/Img"
import Title from "../Base/Title"
import { RadioButton } from "../Input/RadioButton"
import { And } from "../../Logic/Tags/And"
import { VariableUiElement } from "../Base/VariableUIElement"
import Toggleable from "../Base/Toggleable"
import { BBox } from "../../Logic/BBox"
import BaseUIElement from "../BaseUIElement"
import PresetConfig from "../../Models/ThemeConfig/PresetConfig"
import List from "../Base/List"
import Translations from "../i18n/Translations"
export default class SelectTheme
extends Combine
implements
FlowStep<{
features: any[]
theme: string
layer: LayerConfig
bbox: BBox
}>
{
public readonly Value: Store<{
features: any[]
theme: string
layer: LayerConfig
bbox: BBox
}>
public readonly IsValid: Store<boolean>
constructor(params: { features: any[]; layer: LayerConfig; bbox: BBox }) {
const t = Translations.t.importHelper.selectTheme
let options: InputElement<string>[] = Array.from(AllKnownLayouts.allKnownLayouts.values())
.filter((th) => th.layers.some((l) => l.id === params.layer.id))
.filter((th) => th.id !== "personal")
.map(
(th) =>
new FixedInputElement<string>(
new Combine([
new Img(th.icon).SetClass("block h-12 w-12 br-4"),
new Title(th.title),
]).SetClass("flex items-center"),
th.id
)
)
const themeRadios = new RadioButton<string>(options, {
selectFirstAsDefault: false,
})
const applicablePresets = themeRadios.GetValue().map((theme) => {
if (theme === undefined) {
return []
}
// we get the layer with the correct ID via the actual theme config, as the actual theme might have different presets due to overrides
const themeConfig = AllKnownLayouts.allKnownLayouts.get(theme)
const layer = themeConfig.layers.find((l) => l.id === params.layer.id)
return layer.presets
})
const nonMatchedElements = applicablePresets.map((presets) => {
if (presets === undefined || presets.length === 0) {
return undefined
}
return params.features.filter(
(feat) =>
!presets.some((preset) =>
new And(preset.tags).matchesProperties(feat.properties)
)
)
})
super([
new Title(t.title),
t.intro,
themeRadios,
new VariableUiElement(
applicablePresets.map(
(applicablePresets) => {
if (themeRadios.GetValue().data === undefined) {
return undefined
}
if (applicablePresets === undefined || applicablePresets.length === 0) {
return t.noMatchingPresets.SetClass("alert")
}
},
[themeRadios.GetValue()]
)
),
new VariableUiElement(
nonMatchedElements.map(
(unmatched) =>
SelectTheme.nonMatchedElementsPanel(unmatched, applicablePresets.data),
[applicablePresets]
)
),
])
this.SetClass("flex flex-col")
this.Value = themeRadios.GetValue().map((theme) => ({
features: params.features,
layer: params.layer,
bbox: params.bbox,
theme,
}))
this.IsValid = this.Value.map(
(obj) => {
if (obj === undefined) {
return false
}
if ([obj.theme, obj.features].some((v) => v === undefined)) {
return false
}
if (applicablePresets.data === undefined || applicablePresets.data.length === 0) {
return false
}
if ((nonMatchedElements.data?.length ?? 0) > 0) {
return false
}
return true
},
[applicablePresets]
)
}
private static nonMatchedElementsPanel(
unmatched: any[],
applicablePresets: PresetConfig[]
): BaseUIElement {
if (unmatched === undefined || unmatched.length === 0) {
return
}
const t = Translations.t.importHelper.selectTheme
const applicablePresetsOverview = applicablePresets.map((preset) =>
t.needsTags
.Subs({
title: preset.title,
tags: preset.tags.map((t) => t.asHumanString()).join(" & "),
})
.SetClass("thanks")
)
const unmatchedPanels: BaseUIElement[] = []
for (const feat of unmatched) {
const parts: BaseUIElement[] = []
parts.push(
new Combine(
Object.keys(feat.properties).map((k) => k + "=" + feat.properties[k])
).SetClass("flex flex-col")
)
for (const preset of applicablePresets) {
const tags = new And(preset.tags).asChange({})
const missing = []
for (const { k, v } of tags) {
if (preset[k] === undefined) {
missing.push(t.missing.Subs({ k, v }))
} else if (feat.properties[k] !== v) {
missing.push(t.misMatch.Subs({ k, v, properties: feat.properties }))
}
}
if (missing.length > 0) {
parts.push(
new Combine([t.notApplicable.Subs(preset), new List(missing)]).SetClass(
"flex flex-col alert"
)
)
}
}
unmatchedPanels.push(new Combine(parts).SetClass("flex flex-col"))
}
return new Combine([
t.displayNonMatchingCount.Subs(unmatched).SetClass("alert"),
...applicablePresetsOverview,
new Toggleable(new Title(t.unmatchedTitle), new Combine(unmatchedPanels)),
]).SetClass("flex flex-col")
}
}