forked from MapComplete/MapComplete
		
	
		
			
				
	
	
		
			192 lines
		
	
	
	
		
			6.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			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>[] = AllKnownLayouts.layoutsList
 | 
						|
            .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.layoutsList.find((th) => th.id === 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")
 | 
						|
    }
 | 
						|
}
 |