forked from MapComplete/MapComplete
Search feature: refactor, add translations
This commit is contained in:
parent
b3492930b8
commit
bd3bddc89c
21 changed files with 499 additions and 507 deletions
|
|
@ -2,11 +2,11 @@ import GeocodingProvider, { SearchResult, GeocodingOptions, GeocodeResult } from
|
|||
import { Utils } from "../../Utils"
|
||||
import { Store, Stores } from "../UIEventSource"
|
||||
|
||||
export default class CombinedSearcher implements GeocodingProvider <GeocodeResult> {
|
||||
private _providers: ReadonlyArray<GeocodingProvider<GeocodeResult>>
|
||||
private _providersWithSuggest: ReadonlyArray<GeocodingProvider<GeocodeResult>>
|
||||
export default class CombinedSearcher implements GeocodingProvider {
|
||||
private _providers: ReadonlyArray<GeocodingProvider>
|
||||
private _providersWithSuggest: ReadonlyArray<GeocodingProvider>
|
||||
|
||||
constructor(...providers: ReadonlyArray<GeocodingProvider<GeocodeResult>>) {
|
||||
constructor(...providers: ReadonlyArray<GeocodingProvider>) {
|
||||
this._providers = Utils.NoNull(providers)
|
||||
this._providersWithSuggest = this._providers.filter(pr => pr.suggest !== undefined)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { ImmutableStore, Store } from "../UIEventSource"
|
|||
/**
|
||||
* A simple search-class which interprets possible locations
|
||||
*/
|
||||
export default class CoordinateSearch implements GeocodingProvider<GeocodeResult> {
|
||||
export default class CoordinateSearch implements GeocodingProvider {
|
||||
private static readonly latLonRegexes: ReadonlyArray<RegExp> = [
|
||||
/^(-?[0-9]+\.[0-9]+)[ ,;/\\]+(-?[0-9]+\.[0-9]+)/,
|
||||
/lat[:=]? *['"]?(-?[0-9]+\.[0-9]+)['"]?[ ,;&]+lon[:=]? *['"]?(-?[0-9]+\.[0-9]+)['"]?/,
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
import { ImmutableStore, Store } from "../UIEventSource"
|
||||
import GeocodingProvider, { FilterPayload, FilterResult, GeocodingOptions, SearchResult } from "./GeocodingProvider"
|
||||
import { SpecialVisualizationState } from "../../UI/SpecialVisualization"
|
||||
import { Utils } from "../../Utils"
|
||||
import Locale from "../../UI/i18n/Locale"
|
||||
import Constants from "../../Models/Constants"
|
||||
import FilterConfig, { FilterConfigOption } from "../../Models/ThemeConfig/FilterConfig"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
|
||||
export type FilterSearchResult = { option: FilterConfigOption, filter: FilterConfig, layer: LayerConfig, index: number }
|
||||
|
||||
|
||||
/**
|
||||
* Searches matching filters
|
||||
*/
|
||||
export default class FilterSearch implements GeocodingProvider {
|
||||
export default class FilterSearch {
|
||||
private readonly _state: SpecialVisualizationState
|
||||
|
||||
constructor(state: SpecialVisualizationState) {
|
||||
this._state = state
|
||||
}
|
||||
|
||||
async search(query: string): Promise<SearchResult[]> {
|
||||
return this.searchDirectly(query)
|
||||
}
|
||||
public searchDirectly(query: string): FilterResult[] {
|
||||
public search(query: string): FilterSearchResult[] {
|
||||
if (query.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
|
@ -28,7 +28,7 @@ export default class FilterSearch implements GeocodingProvider {
|
|||
}
|
||||
return query
|
||||
}).filter(q => q.length > 0)
|
||||
const possibleFilters: FilterResult[] = []
|
||||
const possibleFilters: FilterSearchResult[] = []
|
||||
for (const layer of this._state.layout.layers) {
|
||||
if (!Array.isArray(layer.filters)) {
|
||||
continue
|
||||
|
|
@ -61,13 +61,9 @@ export default class FilterSearch implements GeocodingProvider {
|
|||
if (levehnsteinD > 0.25) {
|
||||
continue
|
||||
}
|
||||
possibleFilters.push(<FilterResult>{
|
||||
category: "filter",
|
||||
osm_id: layer.id + "/" + filter.id + "/" + i,
|
||||
payload: {
|
||||
possibleFilters.push({
|
||||
option, layer, filter, index:
|
||||
i,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -75,16 +71,11 @@ export default class FilterSearch implements GeocodingProvider {
|
|||
return possibleFilters
|
||||
}
|
||||
|
||||
suggest(query: string, options?: GeocodingOptions): Store<SearchResult[]> {
|
||||
return new ImmutableStore(this.searchDirectly(query))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a random list of filters
|
||||
*/
|
||||
getSuggestions(): FilterPayload[] {
|
||||
const result: FilterPayload[] = []
|
||||
getSuggestions(): FilterSearchResult[] {
|
||||
const result: FilterSearchResult[] = []
|
||||
for (const [id, filteredLayer] of this._state.layerState.filteredLayers) {
|
||||
if (!Array.isArray(filteredLayer.layerDef.filters)) {
|
||||
continue
|
||||
|
|
@ -93,7 +84,7 @@ export default class FilterSearch implements GeocodingProvider {
|
|||
continue
|
||||
}
|
||||
for (const filter of filteredLayer.layerDef.filters) {
|
||||
const singleFilterResults: FilterPayload[] = []
|
||||
const singleFilterResults: FilterSearchResult[] = []
|
||||
for (let i = 0; i < Math.min(filter.options.length, 5); i++) {
|
||||
const option = filter.options[i]
|
||||
if (option.osmTags === undefined) {
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ import { Store } from "../UIEventSource"
|
|||
import * as search from "../../assets/generated/layers/search.json"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson"
|
||||
import FilterConfig, { FilterConfigOption } from "../../Models/ThemeConfig/FilterConfig"
|
||||
import { MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import { GeoOperations } from "../GeoOperations"
|
||||
|
||||
export type GeocodingCategory =
|
||||
|
|
@ -44,13 +42,7 @@ export type GeocodeResult = {
|
|||
payload?: object,
|
||||
source?: string
|
||||
}
|
||||
export type FilterPayload = { option: FilterConfigOption, filter: FilterConfig, layer: LayerConfig, index: number }
|
||||
export type FilterResult = { category: "filter", osm_id: string, payload: FilterPayload }
|
||||
export type LayerResult = {category: "layer", osm_id: string, payload: LayerConfig}
|
||||
export type SearchResult =
|
||||
| FilterResult
|
||||
| { category: "theme", osm_id: string, payload: MinimalLayoutInformation }
|
||||
| LayerResult
|
||||
| GeocodeResult
|
||||
|
||||
export interface GeocodingOptions {
|
||||
|
|
@ -58,16 +50,16 @@ export interface GeocodingOptions {
|
|||
}
|
||||
|
||||
|
||||
export default interface GeocodingProvider<T extends SearchResult = SearchResult> {
|
||||
export default interface GeocodingProvider {
|
||||
|
||||
|
||||
search(query: string, options?: GeocodingOptions): Promise<T[]>
|
||||
search(query: string, options?: GeocodingOptions): Promise<GeocodeResult[]>
|
||||
|
||||
/**
|
||||
* @param query
|
||||
* @param options
|
||||
*/
|
||||
suggest?(query: string, options?: GeocodingOptions): Store<T[]>
|
||||
suggest?(query: string, options?: GeocodingOptions): Store<GeocodeResult[]>
|
||||
}
|
||||
|
||||
export type ReverseGeocodingResult = Feature<Geometry, {
|
||||
|
|
|
|||
|
|
@ -1,44 +1,41 @@
|
|||
import GeocodingProvider, { GeocodingOptions, LayerResult, SearchResult } from "./GeocodingProvider"
|
||||
import { SpecialVisualizationState } from "../../UI/SpecialVisualization"
|
||||
import MoreScreen from "../../UI/BigComponents/MoreScreen"
|
||||
import { ImmutableStore, Store } from "../UIEventSource"
|
||||
import Constants from "../../Models/Constants"
|
||||
import SearchUtils from "./SearchUtils"
|
||||
import ThemeSearch from "./ThemeSearch"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
|
||||
export default class LayerSearch implements GeocodingProvider<LayerResult> {
|
||||
export default class LayerSearch {
|
||||
|
||||
private readonly _state: SpecialVisualizationState
|
||||
private readonly _suggestionLimit: number
|
||||
private readonly _layerWhitelist : Set<string>
|
||||
constructor(state: SpecialVisualizationState, suggestionLimit: number) {
|
||||
constructor(state: SpecialVisualizationState) {
|
||||
this._state = state
|
||||
this._layerWhitelist = new Set(state.layout.layers.map(l => l.id).filter(id => Constants.added_by_default.indexOf(<any> id) < 0))
|
||||
this._suggestionLimit = suggestionLimit
|
||||
}
|
||||
|
||||
async search(query: string): Promise<LayerResult[]> {
|
||||
return this.searchWrapped(query, 99)
|
||||
}
|
||||
|
||||
suggest(query: string, options?: GeocodingOptions): Store<LayerResult[]> {
|
||||
return new ImmutableStore(this.searchWrapped(query, this._suggestionLimit ?? 4))
|
||||
static scoreLayers(query: string, layerWhitelist?: Set<string>): Record<string, number> {
|
||||
const result: Record<string, number> = {}
|
||||
for (const id in ThemeSearch.officialThemes.layers) {
|
||||
if(layerWhitelist !== undefined && !layerWhitelist.has(id)){
|
||||
continue
|
||||
}
|
||||
const keywords = ThemeSearch.officialThemes.layers[id]
|
||||
const distance = SearchUtils.scoreKeywords(query, keywords)
|
||||
result[id] = distance
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
private searchWrapped(query: string, limit: number): LayerResult[] {
|
||||
return this.searchDirect(query, limit)
|
||||
}
|
||||
|
||||
public searchDirect(query: string, limit: number): LayerResult[] {
|
||||
public search(query: string, limit: number): LayerConfig[] {
|
||||
if (query.length < 1) {
|
||||
return []
|
||||
}
|
||||
const scores = MoreScreen.scoreLayers(query, this._layerWhitelist)
|
||||
const asList:(LayerResult & {score:number})[] = []
|
||||
const scores = LayerSearch.scoreLayers(query, this._layerWhitelist)
|
||||
const asList:({layer: LayerConfig, score:number})[] = []
|
||||
for (const layer in scores) {
|
||||
asList.push({
|
||||
category: "layer",
|
||||
payload: this._state.layout.getLayer(layer),
|
||||
osm_id: layer,
|
||||
layer: this._state.layout.getLayer(layer),
|
||||
score: scores[layer]
|
||||
})
|
||||
}
|
||||
|
|
@ -47,6 +44,7 @@ export default class LayerSearch implements GeocodingProvider<LayerResult> {
|
|||
return asList
|
||||
.filter(sorted => sorted.score < 2)
|
||||
.slice(0, limit)
|
||||
.map(l => l.layer)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import GeocodingProvider, { GeocodingOptions, GeocodeResult } from "./GeocodingP
|
|||
import { OsmId } from "../../Models/OsmFeature"
|
||||
import { SpecialVisualizationState } from "../../UI/SpecialVisualization"
|
||||
|
||||
export default class OpenStreetMapIdSearch implements GeocodingProvider<GeocodeResult> {
|
||||
export default class OpenStreetMapIdSearch implements GeocodingProvider {
|
||||
private static readonly regex = /((https?:\/\/)?(www.)?(osm|openstreetmap).org\/)?(n|node|w|way|r|relation)[/ ]?([0-9]+)/
|
||||
|
||||
private static readonly types: Readonly<Record<string, "node" | "way" | "relation">> = {
|
||||
|
|
|
|||
75
src/Logic/Search/SearchUtils.ts
Normal file
75
src/Logic/Search/SearchUtils.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import Locale from "../../UI/i18n/Locale"
|
||||
import { Utils } from "../../Utils"
|
||||
import ThemeSearch from "./ThemeSearch"
|
||||
|
||||
export default class SearchUtils {
|
||||
|
||||
|
||||
/** Applies special search terms, such as 'studio', 'osmcha', ...
|
||||
* Returns 'false' if nothing is matched.
|
||||
* Doesn't return control flow if a match is found (navigates to another page in this case)
|
||||
*/
|
||||
public static applySpecialSearch(searchTerm: string, ) {
|
||||
searchTerm = searchTerm.toLowerCase()
|
||||
if (!searchTerm) {
|
||||
return false
|
||||
}
|
||||
if (searchTerm === "personal") {
|
||||
window.location.href = ThemeSearch.createUrlFor({ id: "personal" }, undefined)
|
||||
}
|
||||
if (searchTerm === "bugs" || searchTerm === "issues") {
|
||||
window.location.href = "https://github.com/pietervdvn/MapComplete/issues"
|
||||
}
|
||||
if (searchTerm === "source") {
|
||||
window.location.href = "https://github.com/pietervdvn/MapComplete"
|
||||
}
|
||||
if (searchTerm === "docs") {
|
||||
window.location.href = "https://github.com/pietervdvn/MapComplete/tree/develop/Docs"
|
||||
}
|
||||
if (searchTerm === "osmcha" || searchTerm === "stats") {
|
||||
window.location.href = Utils.OsmChaLinkFor(7)
|
||||
}
|
||||
if (searchTerm === "studio") {
|
||||
window.location.href = "./studio.html"
|
||||
}
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Searches for the smallest distance in words; will split both the query and the terms
|
||||
*
|
||||
* SearchUtils.scoreKeywords("drinking water", {"en": ["A layer with drinking water points"]}, "en") // => 0
|
||||
* SearchUtils.scoreKeywords("waste", {"en": ["A layer with drinking water points"]}, "en") // => 2
|
||||
*
|
||||
*/
|
||||
public static scoreKeywords(query: string, keywords: Record<string, string[]> | string[], language?: string): number {
|
||||
if(!keywords){
|
||||
return Infinity
|
||||
}
|
||||
language ??= Locale.language.data
|
||||
const queryParts = query.trim().split(" ").map(q => Utils.simplifyStringForSearch(q))
|
||||
let terms: string[]
|
||||
if (Array.isArray(keywords)) {
|
||||
terms = keywords
|
||||
} else {
|
||||
terms = (keywords[language] ?? []).concat(keywords["*"])
|
||||
}
|
||||
const termsAll = Utils.NoNullInplace(terms).flatMap(t => t.split(" "))
|
||||
|
||||
let distanceSummed = 0
|
||||
for (let i = 0; i < queryParts.length; i++) {
|
||||
const q = queryParts[i]
|
||||
let minDistance: number = 99
|
||||
for (const term of termsAll) {
|
||||
const d = Utils.levenshteinDistance(q, Utils.simplifyStringForSearch(term))
|
||||
if (d < minDistance) {
|
||||
minDistance = d
|
||||
}
|
||||
}
|
||||
distanceSummed += minDistance
|
||||
}
|
||||
return distanceSummed
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +1,55 @@
|
|||
import GeocodingProvider, { GeocodingOptions, SearchResult } from "./GeocodingProvider"
|
||||
import { MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import { SpecialVisualizationState } from "../../UI/SpecialVisualization"
|
||||
import MoreScreen from "../../UI/BigComponents/MoreScreen"
|
||||
import { ImmutableStore, Store } from "../UIEventSource"
|
||||
import { Store } from "../UIEventSource"
|
||||
import UserRelatedState from "../State/UserRelatedState"
|
||||
import { Utils } from "../../Utils"
|
||||
import Locale from "../../UI/i18n/Locale"
|
||||
import themeOverview from "../../assets/generated/theme_overview.json"
|
||||
import LayerSearch from "./LayerSearch"
|
||||
import SearchUtils from "./SearchUtils"
|
||||
|
||||
|
||||
type ThemeSearchScore = {
|
||||
theme: MinimalLayoutInformation,
|
||||
lowest: number,
|
||||
perLayer?: Record<string, number>,
|
||||
other: number
|
||||
}
|
||||
|
||||
|
||||
export default class ThemeSearch {
|
||||
|
||||
public static readonly officialThemes: {
|
||||
themes: MinimalLayoutInformation[],
|
||||
layers: Record<string, Record<string, string[]>>
|
||||
} = themeOverview
|
||||
public static readonly officialThemesById: Map<string, MinimalLayoutInformation> = new Map<string, MinimalLayoutInformation>()
|
||||
static {
|
||||
for (const th of ThemeSearch.officialThemes.themes ?? []) {
|
||||
ThemeSearch.officialThemesById.set(th.id, th)
|
||||
}
|
||||
}
|
||||
|
||||
export default class ThemeSearch implements GeocodingProvider {
|
||||
|
||||
private readonly _state: SpecialVisualizationState
|
||||
private readonly _knownHiddenThemes: Store<Set<string>>
|
||||
private readonly _suggestionLimit: number
|
||||
private readonly _layersToIgnore: string[]
|
||||
private readonly _otherThemes: MinimalLayoutInformation[]
|
||||
|
||||
constructor(state: SpecialVisualizationState, suggestionLimit: number) {
|
||||
constructor(state: SpecialVisualizationState) {
|
||||
this._state = state
|
||||
this._layersToIgnore = state.layout.layers.map(l => l.id)
|
||||
this._suggestionLimit = suggestionLimit
|
||||
this._knownHiddenThemes = UserRelatedState.initDiscoveredHiddenThemes(this._state.osmConnection).map(list => new Set(list))
|
||||
this._otherThemes = MoreScreen.officialThemes.themes
|
||||
this._otherThemes = ThemeSearch.officialThemes.themes
|
||||
.filter(th => th.id !== state.layout.id)
|
||||
}
|
||||
|
||||
async search(query: string): Promise<SearchResult[]> {
|
||||
return this.searchWrapped(query, 99)
|
||||
}
|
||||
|
||||
suggest(query: string, options?: GeocodingOptions): Store<SearchResult[]> {
|
||||
return new ImmutableStore(this.searchWrapped(query, this._suggestionLimit ?? 4))
|
||||
}
|
||||
|
||||
|
||||
private searchWrapped(query: string, limit: number): SearchResult[] {
|
||||
return this.searchDirect(query, limit).map(match => <SearchResult>{
|
||||
payload: match,
|
||||
category: "theme",
|
||||
osm_id: match.id
|
||||
})
|
||||
}
|
||||
|
||||
public searchDirect(query: string, limit: number): MinimalLayoutInformation[] {
|
||||
public search(query: string, limit: number): MinimalLayoutInformation[] {
|
||||
if (query.length < 1) {
|
||||
return []
|
||||
}
|
||||
const sorted = MoreScreen.sortedByLowest(query, this._otherThemes, this._layersToIgnore)
|
||||
const sorted = ThemeSearch.sortedByLowestScores(query, this._otherThemes, this._layersToIgnore)
|
||||
return sorted
|
||||
.filter(sorted => sorted.lowest < 2)
|
||||
.map(th => th.theme)
|
||||
|
|
@ -51,5 +57,96 @@ export default class ThemeSearch implements GeocodingProvider {
|
|||
.slice(0, limit)
|
||||
}
|
||||
|
||||
public static createUrlFor(
|
||||
layout: { id: string },
|
||||
state?: { layoutToUse?: { id } },
|
||||
): string {
|
||||
if (layout === undefined) {
|
||||
return undefined
|
||||
}
|
||||
if (layout.id === undefined) {
|
||||
console.error("ID is undefined for layout", layout)
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (layout.id === state?.layoutToUse?.id) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
let path = window.location.pathname
|
||||
// Path starts with a '/' and contains everything, e.g. '/dir/dir/page.html'
|
||||
path = path.substr(0, path.lastIndexOf("/"))
|
||||
// Path will now contain '/dir/dir', or empty string in case of nothing
|
||||
if (path === "") {
|
||||
path = "."
|
||||
}
|
||||
|
||||
let linkPrefix = `${path}/${layout.id.toLowerCase()}.html?`
|
||||
if (location.hostname === "localhost" || location.hostname === "127.0.0.1") {
|
||||
linkPrefix = `${path}/theme.html?layout=${layout.id}&`
|
||||
}
|
||||
|
||||
if (layout.id.startsWith("http://") || layout.id.startsWith("https://")) {
|
||||
linkPrefix = `${path}/theme.html?userlayout=${layout.id}&`
|
||||
}
|
||||
|
||||
|
||||
return `${linkPrefix}`
|
||||
}
|
||||
|
||||
private static scoreThemes(query: string, themes: MinimalLayoutInformation[], ignoreLayers: string[] = []): Record<string, ThemeSearchScore> {
|
||||
if (query?.length < 1) {
|
||||
return undefined
|
||||
}
|
||||
themes = Utils.NoNullInplace(themes)
|
||||
const layerScores = LayerSearch.scoreLayers(query)
|
||||
for (const ignoreLayer of ignoreLayers) {
|
||||
delete layerScores[ignoreLayer]
|
||||
}
|
||||
const results: Record<string, ThemeSearchScore> = {}
|
||||
for (const layoutInfo of themes) {
|
||||
const theme = layoutInfo.id
|
||||
if (theme === "personal") {
|
||||
continue
|
||||
}
|
||||
if (Utils.simplifyStringForSearch(theme) === query) {
|
||||
results[theme] = {
|
||||
theme: layoutInfo,
|
||||
lowest: -1,
|
||||
other: 0,
|
||||
}
|
||||
continue
|
||||
}
|
||||
const perLayer = Utils.asRecord(
|
||||
layoutInfo.layers ?? [], layer => layerScores[layer],
|
||||
)
|
||||
const language = Locale.language.data
|
||||
|
||||
const keywords = Utils.NoNullInplace([layoutInfo.shortDescription, layoutInfo.title])
|
||||
.map(item => typeof item === "string" ? item : (item[language] ?? item["*"]))
|
||||
|
||||
|
||||
const other = Math.min(SearchUtils.scoreKeywords(query, keywords), SearchUtils.scoreKeywords(query, layoutInfo.keywords))
|
||||
const lowest = Math.min(other, ...Object.values(perLayer))
|
||||
results[theme] = {
|
||||
theme: layoutInfo,
|
||||
perLayer,
|
||||
other,
|
||||
lowest,
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
public static sortedByLowestScores(search: string, themes: MinimalLayoutInformation[], ignoreLayers: string[] = []): ThemeSearchScore[] {
|
||||
const scored = Object.values(this.scoreThemes(search, themes, ignoreLayers))
|
||||
scored.sort((a, b) => a.lowest - b.lowest)
|
||||
return scored
|
||||
}
|
||||
|
||||
public static sortedByLowest(search: string, themes: MinimalLayoutInformation[], ignoreLayers: string[] = [], maxDiff: number): MinimalLayoutInformation[] {
|
||||
return this.sortedByLowestScores(search, themes, ignoreLayers)
|
||||
.map(th => th.theme)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,30 @@
|
|||
import GeocodingProvider, {
|
||||
FilterPayload, FilterResult,
|
||||
GeocodeResult,
|
||||
GeocodingUtils, LayerResult,
|
||||
type SearchResult,
|
||||
} from "../Search/GeocodingProvider"
|
||||
import GeocodingProvider, { GeocodingUtils, type SearchResult } from "../Search/GeocodingProvider"
|
||||
import { ImmutableStore, Store, Stores, UIEventSource } from "../UIEventSource"
|
||||
import CombinedSearcher from "../Search/CombinedSearcher"
|
||||
import FilterSearch from "../Search/FilterSearch"
|
||||
import FilterSearch, { FilterSearchResult } from "../Search/FilterSearch"
|
||||
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"
|
||||
import ThemeViewState from "../../Models/ThemeViewState"
|
||||
import Translations from "../../UI/i18n/Translations"
|
||||
import type { MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
|
||||
import MoreScreen from "../../UI/BigComponents/MoreScreen"
|
||||
import { BBox } from "../BBox"
|
||||
import { Translation } from "../../UI/i18n/Translation"
|
||||
import GeocodingFeatureSource from "../Search/GeocodingFeatureSource"
|
||||
import ShowDataLayer from "../../UI/Map/ShowDataLayer"
|
||||
import LayerSearch from "../Search/LayerSearch"
|
||||
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
|
||||
|
||||
export default class SearchState {
|
||||
|
||||
public readonly isSearching = new UIEventSource(false)
|
||||
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<FilterResult[]>
|
||||
public readonly filterSuggestions: Store<FilterSearchResult[]>
|
||||
public readonly themeSuggestions: Store<MinimalLayoutInformation[]>
|
||||
public readonly layerSuggestions: Store<LayerResult[]>
|
||||
public readonly locationSearchers: ReadonlyArray<GeocodingProvider<GeocodeResult>>
|
||||
public readonly layerSuggestions: Store<LayerConfig[]>
|
||||
public readonly locationSearchers: ReadonlyArray<GeocodingProvider>
|
||||
|
||||
private readonly state: ThemeViewState
|
||||
public readonly showSearchDrawer: UIEventSource<boolean>
|
||||
|
|
@ -42,7 +34,7 @@ export default class SearchState {
|
|||
this.state = state
|
||||
|
||||
this.locationSearchers = [
|
||||
// new LocalElementSearch(state, 5),
|
||||
new LocalElementSearch(state, 5),
|
||||
new CoordinateSearch(),
|
||||
new OpenStreetMapIdSearch(state),
|
||||
new PhotonSearch(), // new NominatimGeocoding(),
|
||||
|
|
@ -67,18 +59,18 @@ export default class SearchState {
|
|||
Stores.concat(suggestions).map(suggestions => CombinedSearcher.merge(suggestions)),
|
||||
)
|
||||
|
||||
const themeSearch = new ThemeSearch(state, 3)
|
||||
this.themeSuggestions = this.searchTerm.mapD(query => themeSearch.searchDirect(query, 3))
|
||||
const themeSearch = new ThemeSearch(state)
|
||||
this.themeSuggestions = this.searchTerm.mapD(query => themeSearch.search(query, 3))
|
||||
|
||||
const layerSearch = new LayerSearch(state, 5)
|
||||
this.layerSuggestions = this.searchTerm.mapD(query => layerSearch.searchDirect(query, 5))
|
||||
const layerSearch = new LayerSearch(state)
|
||||
this.layerSuggestions = this.searchTerm.mapD(query => layerSearch.search(query, 5))
|
||||
|
||||
const filterSearch = new FilterSearch(state)
|
||||
this.filterSuggestions = this.searchTerm.stabilized(50)
|
||||
.mapD(query => filterSearch.searchDirectly(query))
|
||||
.mapD(query => filterSearch.search(query))
|
||||
.mapD(filterResult => {
|
||||
const active = state.layerState.activeFilters.data
|
||||
return filterResult.filter(({ payload: { filter, index, layer } }) => {
|
||||
return filterResult.filter(({ filter, index, layer }) => {
|
||||
const foundMatch = active.some(active =>
|
||||
active.filter.id === filter.id && layer.id === active.layer.id && active.control.data === index)
|
||||
|
||||
|
|
@ -108,22 +100,20 @@ export default class SearchState {
|
|||
|
||||
}
|
||||
|
||||
public async apply(result: FilterResult | LayerResult) {
|
||||
if (result.category === "filter") {
|
||||
return this.applyFilter(result.payload)
|
||||
}
|
||||
if (result.category === "layer") {
|
||||
public async apply(result: FilterSearchResult | LayerConfig) {
|
||||
if (result instanceof LayerConfig) {
|
||||
return this.applyLayer(result)
|
||||
}
|
||||
return this.applyFilter(result)
|
||||
}
|
||||
|
||||
private async applyLayer(layer: LayerResult) {
|
||||
private async applyLayer(layer: LayerConfig) {
|
||||
for (const [name, otherLayer] of this.state.layerState.filteredLayers) {
|
||||
otherLayer.isDisplayed.setData(name === layer.osm_id)
|
||||
otherLayer.isDisplayed.setData(name === layer.id)
|
||||
}
|
||||
}
|
||||
|
||||
private async applyFilter(payload: FilterPayload) {
|
||||
private async applyFilter(payload: FilterSearchResult) {
|
||||
const state = this.state
|
||||
|
||||
const { layer, filter, index } = payload
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue