MapComplete/src/Logic/State/SearchState.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

135 lines
5.5 KiB
TypeScript
Raw Normal View History

import GeocodingProvider, { GeocodingUtils, type SearchResult } from "../Search/GeocodingProvider"
import { ImmutableStore, Store, Stores, UIEventSource } from "../UIEventSource"
2024-09-11 01:46:55 +02:00
import CombinedSearcher from "../Search/CombinedSearcher"
import FilterSearch, { FilterSearchResult } from "../Search/FilterSearch"
2024-09-11 01:46:55 +02:00
import LocalElementSearch from "../Search/LocalElementSearch"
import CoordinateSearch from "../Search/CoordinateSearch"
import ThemeSearch from "../Search/ThemeSearch"
import OpenStreetMapIdSearch from "../Search/OpenStreetMapIdSearch"
import PhotonSearch from "../Search/PhotonSearch"
2024-08-30 02:18:29 +02:00
import ThemeViewState from "../../Models/ThemeViewState"
import type { MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
import { Translation } from "../../UI/i18n/Translation"
2024-09-11 01:46:55 +02:00
import GeocodingFeatureSource from "../Search/GeocodingFeatureSource"
2024-08-30 02:18:29 +02:00
import ShowDataLayer from "../../UI/Map/ShowDataLayer"
2024-09-11 01:46:55 +02:00
import LayerSearch from "../Search/LayerSearch"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
2024-08-30 02:18:29 +02:00
export default class SearchState {
public readonly feedback: UIEventSource<Translation> = new UIEventSource<Translation>(undefined)
public readonly searchTerm: UIEventSource<string> = new UIEventSource<string>("")
public readonly searchIsFocused = new UIEventSource(false)
public readonly suggestions: Store<SearchResult[]>
public readonly filterSuggestions: Store<FilterSearchResult[]>
2024-08-30 02:18:29 +02:00
public readonly themeSuggestions: Store<MinimalLayoutInformation[]>
public readonly layerSuggestions: Store<LayerConfig[]>
public readonly locationSearchers: ReadonlyArray<GeocodingProvider>
2024-08-30 02:18:29 +02:00
private readonly state: ThemeViewState
public readonly showSearchDrawer: UIEventSource<boolean>
public readonly suggestionsSearchRunning: Store<boolean>
2024-08-30 02:18:29 +02:00
constructor(state: ThemeViewState) {
this.state = state
this.locationSearchers = [
new LocalElementSearch(state, 5),
2024-08-30 02:18:29 +02:00
new CoordinateSearch(),
new OpenStreetMapIdSearch(state),
2024-09-11 01:46:55 +02:00
new PhotonSearch(), // new NominatimGeocoding(),
]
2024-08-30 02:18:29 +02:00
const bounds = state.mapProperties.bounds
const suggestionsList = this.searchTerm.stabilized(250).mapD(search => {
2024-08-30 02:18:29 +02:00
if (search.length === 0) {
return undefined
}
return this.locationSearchers.map(ls => ls.suggest(search, { bbox: bounds.data }))
2024-09-11 01:46:55 +02:00
}, [bounds],
)
2024-09-11 01:46:55 +02:00
this.suggestionsSearchRunning = suggestionsList.bind(suggestions => {
if (suggestions === undefined) {
return new ImmutableStore(true)
2024-08-30 02:18:29 +02:00
}
return Stores.concat(suggestions).map(suggestions => suggestions.some(list => list === undefined))
})
this.suggestions = suggestionsList.bindD(suggestions =>
2024-09-11 01:46:55 +02:00
Stores.concat(suggestions).map(suggestions => CombinedSearcher.merge(suggestions)),
2024-08-30 02:18:29 +02:00
)
const themeSearch = new ThemeSearch(state)
this.themeSuggestions = this.searchTerm.mapD(query => themeSearch.search(query, 3))
2024-08-30 02:18:29 +02:00
const layerSearch = new LayerSearch(state)
this.layerSuggestions = this.searchTerm.mapD(query => layerSearch.search(query, 5))
2024-08-30 02:18:29 +02:00
const filterSearch = new FilterSearch(state)
2024-09-11 01:46:55 +02:00
this.filterSuggestions = this.searchTerm.stabilized(50)
.mapD(query => filterSearch.search(query))
2024-09-11 01:46:55 +02:00
.mapD(filterResult => {
const active = state.layerState.activeFilters.data
return filterResult.filter(({ filter, index, layer }) => {
2024-09-11 01:46:55 +02:00
const foundMatch = active.some(active =>
active.filter.id === filter.id && layer.id === active.layer.id && active.control.data === index)
return !foundMatch
})
}, [state.layerState.activeFilters])
2024-08-30 02:18:29 +02:00
const geocodedFeatures = new GeocodingFeatureSource(this.suggestions.stabilized(250))
state.featureProperties.trackFeatureSource(geocodedFeatures)
new ShowDataLayer(
state.map,
{
layer: GeocodingUtils.searchLayer,
features: geocodedFeatures,
2024-09-11 01:46:55 +02:00
selectedElement: state.selectedElement,
},
2024-08-30 02:18:29 +02:00
)
this.showSearchDrawer = new UIEventSource(false)
2024-08-30 02:18:29 +02:00
this.searchIsFocused.addCallbackAndRunD(sugg => {
if (sugg) {
this.showSearchDrawer.set(true)
}
})
}
public async apply(result: FilterSearchResult | LayerConfig) {
if (result instanceof LayerConfig) {
2024-09-11 01:46:55 +02:00
return this.applyLayer(result)
}
return this.applyFilter(result)
2024-09-11 01:46:55 +02:00
}
private async applyLayer(layer: LayerConfig) {
2024-09-11 01:46:55 +02:00
for (const [name, otherLayer] of this.state.layerState.filteredLayers) {
otherLayer.isDisplayed.setData(name === layer.id)
2024-09-11 01:46:55 +02:00
}
}
2024-08-30 02:18:29 +02:00
private async applyFilter(payload: FilterSearchResult) {
2024-08-30 02:18:29 +02:00
const state = this.state
2024-09-11 01:46:55 +02:00
const { layer, filter, index } = payload
2024-08-30 02:18:29 +02:00
for (const [name, otherLayer] of state.layerState.filteredLayers) {
2024-09-11 01:46:55 +02:00
otherLayer.isDisplayed.setData(name === layer.id)
2024-08-30 02:18:29 +02:00
}
2024-09-11 01:46:55 +02:00
const flayer = state.layerState.filteredLayers.get(layer.id)
flayer.isDisplayed.set(true)
const filtercontrol = flayer.appliedFilters.get(filter.id)
2024-08-30 02:18:29 +02:00
console.log("Could not apply", layer.id, ".", filter.id, index)
if (filtercontrol.data === index) {
filtercontrol.setData(undefined)
} else {
filtercontrol.setData(index)
}
}
}