2023-04-06 01:33:08 +02:00
|
|
|
import { Store, UIEventSource } from "../Logic/UIEventSource"
|
2021-08-07 23:11:34 +02:00
|
|
|
import LayerConfig from "./ThemeConfig/LayerConfig"
|
2023-03-28 05:13:48 +02:00
|
|
|
import { OsmConnection } from "../Logic/Osm/OsmConnection"
|
|
|
|
import { LocalStorageSource } from "../Logic/Web/LocalStorageSource"
|
|
|
|
import { QueryParameters } from "../Logic/Web/QueryParameters"
|
2023-04-06 01:33:08 +02:00
|
|
|
import { FilterConfigOption } from "./ThemeConfig/FilterConfig"
|
|
|
|
import { TagsFilter } from "../Logic/Tags/TagsFilter"
|
|
|
|
import { Utils } from "../Utils"
|
|
|
|
import { TagUtils } from "../Logic/Tags/TagUtils"
|
|
|
|
import { And } from "../Logic/Tags/And"
|
2022-01-08 04:22:50 +01:00
|
|
|
|
2023-03-28 05:13:48 +02:00
|
|
|
export default class FilteredLayer {
|
|
|
|
/**
|
|
|
|
* Wether or not the specified layer is shown
|
|
|
|
*/
|
2021-07-27 19:39:57 +02:00
|
|
|
readonly isDisplayed: UIEventSource<boolean>
|
2023-03-28 05:13:48 +02:00
|
|
|
/**
|
2023-04-06 01:33:08 +02:00
|
|
|
* Maps the filter.option.id onto the actual used state.
|
|
|
|
* This state is either the chosen option (as number) or a representation of the fields
|
2023-03-28 05:13:48 +02:00
|
|
|
*/
|
2023-04-06 01:33:08 +02:00
|
|
|
readonly appliedFilters: ReadonlyMap<string, UIEventSource<undefined | number | string>>
|
2021-07-27 19:39:57 +02:00
|
|
|
readonly layerDef: LayerConfig
|
2023-03-28 05:13:48 +02:00
|
|
|
|
2023-04-06 01:33:08 +02:00
|
|
|
/**
|
|
|
|
* Indicates if some filter is set.
|
|
|
|
* If this is the case, adding a new element of this type might be a bad idea
|
|
|
|
*/
|
|
|
|
readonly hasFilter: Store<boolean>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Contains the current properties a feature should fulfill in order to match the filter
|
|
|
|
*/
|
|
|
|
readonly currentFilter: Store<TagsFilter | undefined>
|
2023-03-28 05:13:48 +02:00
|
|
|
constructor(
|
|
|
|
layer: LayerConfig,
|
|
|
|
appliedFilters?: Map<string, UIEventSource<undefined | number | string>>,
|
|
|
|
isDisplayed?: UIEventSource<boolean>
|
|
|
|
) {
|
|
|
|
this.layerDef = layer
|
|
|
|
this.isDisplayed = isDisplayed ?? new UIEventSource(true)
|
|
|
|
this.appliedFilters =
|
|
|
|
appliedFilters ?? new Map<string, UIEventSource<number | string | undefined>>()
|
2023-04-06 01:33:08 +02:00
|
|
|
|
|
|
|
const hasFilter = new UIEventSource<boolean>(false)
|
|
|
|
const self = this
|
|
|
|
const currentTags = new UIEventSource<TagsFilter>(undefined)
|
|
|
|
|
|
|
|
this.appliedFilters.forEach((filterSrc) => {
|
|
|
|
filterSrc.addCallbackAndRun((filter) => {
|
|
|
|
if ((filter ?? 0) !== 0) {
|
|
|
|
hasFilter.setData(true)
|
|
|
|
currentTags.setData(self.calculateCurrentTags())
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const hf = Array.from(self.appliedFilters.values()).some((f) => (f.data ?? 0) !== 0)
|
|
|
|
if (hf) {
|
|
|
|
currentTags.setData(self.calculateCurrentTags())
|
|
|
|
} else {
|
|
|
|
currentTags.setData(undefined)
|
|
|
|
}
|
|
|
|
hasFilter.setData(hf)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
currentTags.addCallbackAndRunD((t) => console.log("Current filter is", t))
|
|
|
|
|
|
|
|
this.currentFilter = currentTags
|
|
|
|
}
|
|
|
|
|
|
|
|
private calculateCurrentTags(): TagsFilter {
|
|
|
|
let needed: TagsFilter[] = []
|
|
|
|
for (const filter of this.layerDef.filters) {
|
|
|
|
const state = this.appliedFilters.get(filter.id)
|
|
|
|
if (state.data === undefined) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if (filter.options[0].fields.length > 0) {
|
|
|
|
const fieldProperties = FilteredLayer.stringToFieldProperties(<string>state.data)
|
|
|
|
const asTags = FilteredLayer.fieldsToTags(filter.options[0], fieldProperties)
|
|
|
|
needed.push(asTags)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
needed.push(filter.options[state.data].osmTags)
|
|
|
|
}
|
|
|
|
needed = Utils.NoNull(needed)
|
|
|
|
if (needed.length == 0) {
|
|
|
|
return undefined
|
|
|
|
}
|
|
|
|
let tags: TagsFilter
|
|
|
|
|
|
|
|
if (needed.length == 1) {
|
|
|
|
tags = needed[1]
|
|
|
|
} else {
|
|
|
|
tags = new And(needed)
|
|
|
|
}
|
|
|
|
let optimized = tags.optimize()
|
|
|
|
if (optimized === true) {
|
|
|
|
return undefined
|
|
|
|
}
|
|
|
|
if (optimized === false) {
|
|
|
|
return tags
|
|
|
|
}
|
|
|
|
return optimized
|
|
|
|
}
|
|
|
|
|
|
|
|
public static fieldsToString(values: Record<string, string>): string {
|
|
|
|
return JSON.stringify(values)
|
|
|
|
}
|
|
|
|
|
|
|
|
public static stringToFieldProperties(value: string): Record<string, string> {
|
|
|
|
return JSON.parse(value)
|
|
|
|
}
|
|
|
|
|
|
|
|
private static fieldsToTags(
|
|
|
|
option: FilterConfigOption,
|
|
|
|
fieldstate: string | Record<string, string>
|
|
|
|
): TagsFilter {
|
|
|
|
let properties: Record<string, string>
|
|
|
|
if (typeof fieldstate === "string") {
|
|
|
|
properties = FilteredLayer.stringToFieldProperties(fieldstate)
|
|
|
|
} else {
|
|
|
|
properties = fieldstate
|
|
|
|
}
|
|
|
|
console.log("Building tagsspec with properties", properties)
|
|
|
|
const tagsSpec = Utils.WalkJson(option.originalTagsSpec, (v) => {
|
|
|
|
if (typeof v !== "string") {
|
|
|
|
return v
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const key in properties) {
|
|
|
|
v = (<string>v).replace("{" + key + "}", properties[key])
|
|
|
|
}
|
|
|
|
|
|
|
|
return v
|
|
|
|
})
|
|
|
|
return TagUtils.Tag(tagsSpec)
|
|
|
|
}
|
|
|
|
|
|
|
|
public disableAllFilters(): void {
|
|
|
|
this.appliedFilters.forEach((value) => value.setData(undefined))
|
2023-03-28 05:13:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates a FilteredLayer which is tied into the QueryParameters and/or user preferences
|
|
|
|
*/
|
|
|
|
public static initLinkedState(
|
|
|
|
layer: LayerConfig,
|
|
|
|
context: string,
|
|
|
|
osmConnection: OsmConnection
|
|
|
|
) {
|
|
|
|
let isDisplayed: UIEventSource<boolean>
|
|
|
|
if (layer.syncSelection === "local") {
|
|
|
|
isDisplayed = LocalStorageSource.GetParsed(
|
|
|
|
context + "-layer-" + layer.id + "-enabled",
|
|
|
|
layer.shownByDefault
|
|
|
|
)
|
|
|
|
} else if (layer.syncSelection === "theme-only") {
|
|
|
|
isDisplayed = FilteredLayer.getPref(
|
|
|
|
osmConnection,
|
|
|
|
context + "-layer-" + layer.id + "-enabled",
|
|
|
|
layer
|
|
|
|
)
|
|
|
|
} else if (layer.syncSelection === "global") {
|
|
|
|
isDisplayed = FilteredLayer.getPref(
|
|
|
|
osmConnection,
|
|
|
|
"layer-" + layer.id + "-enabled",
|
|
|
|
layer
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
isDisplayed = QueryParameters.GetBooleanQueryParameter(
|
|
|
|
"layer-" + layer.id,
|
|
|
|
layer.shownByDefault,
|
|
|
|
"Whether or not layer " + layer.id + " is shown"
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
const appliedFilters = new Map<string, UIEventSource<undefined | number | string>>()
|
|
|
|
for (const subfilter of layer.filters) {
|
|
|
|
appliedFilters.set(subfilter.id, subfilter.initState())
|
|
|
|
}
|
|
|
|
return new FilteredLayer(layer, appliedFilters, isDisplayed)
|
|
|
|
}
|
|
|
|
private static getPref(
|
|
|
|
osmConnection: OsmConnection,
|
|
|
|
key: string,
|
|
|
|
layer: LayerConfig
|
|
|
|
): UIEventSource<boolean> {
|
|
|
|
return osmConnection.GetPreference(key, layer.shownByDefault + "").sync(
|
|
|
|
(v) => {
|
|
|
|
if (v === undefined) {
|
|
|
|
return undefined
|
|
|
|
}
|
|
|
|
return v === "true"
|
|
|
|
},
|
|
|
|
[],
|
|
|
|
(b) => {
|
|
|
|
if (b === undefined) {
|
|
|
|
return undefined
|
|
|
|
}
|
|
|
|
return "" + b
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
2021-07-27 19:39:57 +02:00
|
|
|
}
|