forked from MapComplete/MapComplete
		
	Add layers to search menu
This commit is contained in:
		
							parent
							
								
									e6dab1a83f
								
							
						
					
					
						commit
						c591770eab
					
				
					 33 changed files with 332 additions and 195 deletions
				
			
		|  | @ -109,7 +109,7 @@ Success: user sponteanously interacts with the questions! | |||
| 
 | ||||
| > Then, the 'cuisine' was inspected. As the restaurant they visited is focusing on _vegetarian_ salads, the user wanted to use the freeform to enter 'vegetarian salad' | ||||
| 
 | ||||
|  [ ] Failure: how to properly exp   lain this? Move the 'vegetarian' question up? Should some options, such as 'chicken restaurant' be hidden if `vegetarian=only`? | ||||
|  [ ] Failure: how to properly explain this? Move the 'vegetarian' question up? Should some options, such as 'chicken restaurant' be hidden if `vegetarian=only`? | ||||
| 
 | ||||
|  [ ] UI: issue: the emojis (especially flags) slightly overlaps with the text on this browser | ||||
| 
 | ||||
|  |  | |||
|  | @ -24,6 +24,14 @@ | |||
|     "ca": "Una capa que mostra fonts d'aigua potable", | ||||
|     "cs": "Vrstva zobrazující fontány s pitnou vodou" | ||||
|   }, | ||||
|   "searchTerms": { | ||||
|     "en": [ | ||||
|       "drink","water","fountain","bubbler" | ||||
|     ], | ||||
|     "nl": [ | ||||
|       "drinken","water","drinkwater","waterfontein","fontein","kraan","kraantje" | ||||
|     ] | ||||
|   }, | ||||
|   "source": { | ||||
|     "osmTags": { | ||||
|       "and": [ | ||||
|  |  | |||
|  | @ -40,7 +40,7 @@ | |||
|       ] | ||||
|     } | ||||
|   }, | ||||
|   "minzoom": 12, | ||||
|   "minzoom": 10, | ||||
|   "title": { | ||||
|     "render": { | ||||
|       "en": "Shop", | ||||
|  |  | |||
|  | @ -80,7 +80,7 @@ | |||
|       "override": { | ||||
|         "name=": null, | ||||
|         "minzoom": 17, | ||||
|         "filter": { | ||||
|         "=filter": { | ||||
|           "sameAs": "bike_shop" | ||||
|         } | ||||
|       } | ||||
|  | @ -182,7 +182,7 @@ | |||
|       "builtin": "charging_station", | ||||
|       "override": { | ||||
|         "name": null, | ||||
|         "filter": { | ||||
|         "=filter": { | ||||
|           "sameAs": "charging_station_ebikes" | ||||
|         }, | ||||
|         "minzoom": 18, | ||||
|  | @ -209,7 +209,7 @@ | |||
|       "builtin": "vending_machine", | ||||
|       "override": { | ||||
|         "name": null, | ||||
|         "filter": { | ||||
|         "=filter": { | ||||
|           "sameAs": "vending_machine_bicycle" | ||||
|         }, | ||||
|         "minzoom": 18, | ||||
|  |  | |||
|  | @ -56,7 +56,7 @@ | |||
|     "trolley_bay" | ||||
|   ], | ||||
|   "overrideAll": { | ||||
|     "minzoom": 14, | ||||
|     "minzoom": 12, | ||||
|     "syncSelection": "theme-only" | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -5,6 +5,9 @@ import { Utils } from "../../Utils" | |||
| import Locale from "../../UI/i18n/Locale" | ||||
| import Constants from "../../Models/Constants" | ||||
| 
 | ||||
| /** | ||||
|  * Searches matching filters | ||||
|  */ | ||||
| export default class FilterSearch implements GeocodingProvider { | ||||
|     private readonly _state: SpecialVisualizationState | ||||
| 
 | ||||
|  | @ -13,18 +16,9 @@ export default class FilterSearch implements GeocodingProvider { | |||
|     } | ||||
| 
 | ||||
|     async search(query: string): Promise<SearchResult[]> { | ||||
|         return this.searchDirectlyWrapped(query) | ||||
|         return this.searchDirectly(query) | ||||
|     } | ||||
| 
 | ||||
|     private searchDirectlyWrapped(query: string): FilterResult[] { | ||||
|         return this.searchDirectly(query).map(payload => ({ | ||||
|             payload, | ||||
|             category: "filter", | ||||
|             osm_id: payload.layer.id + "/" + payload.filter.id + "/" + payload.option.osmTags?.asHumanString() ?? "none" | ||||
|         })) | ||||
|     } | ||||
| 
 | ||||
|     public searchDirectly(query: string): FilterPayload[] { | ||||
|     public searchDirectly(query: string): FilterResult[] { | ||||
|         if (query.length === 0) { | ||||
|             return [] | ||||
|         } | ||||
|  | @ -34,11 +28,14 @@ export default class FilterSearch implements GeocodingProvider { | |||
|             } | ||||
|             return query | ||||
|         }).filter(q => q.length > 0) | ||||
|         const possibleFilters: FilterPayload[] = [] | ||||
|         const possibleFilters: FilterResult[] = [] | ||||
|         for (const layer of this._state.layout.layers) { | ||||
|             if (!Array.isArray(layer.filters)) { | ||||
|                 continue | ||||
|             } | ||||
|             if (layer.filterIsSameAs !== undefined) { | ||||
|                 continue | ||||
|             } | ||||
|             for (const filter of layer.filters ?? []) { | ||||
|                 for (let i = 0; i < filter.options.length; i++) { | ||||
|                     const option = filter.options[i] | ||||
|  | @ -64,7 +61,14 @@ export default class FilterSearch implements GeocodingProvider { | |||
|                     if (levehnsteinD > 0.25) { | ||||
|                         continue | ||||
|                     } | ||||
|                     possibleFilters.push({ option, layer, filter, index: i }) | ||||
|                     possibleFilters.push(<FilterResult>{ | ||||
|                         category: "filter", | ||||
|                         osm_id: layer.id + "/" + filter.id + "/" + i, | ||||
|                         payload: { | ||||
|                             option, layer, filter, index: | ||||
|                             i, | ||||
|                         }, | ||||
|                     }) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | @ -72,7 +76,7 @@ export default class FilterSearch implements GeocodingProvider { | |||
|     } | ||||
| 
 | ||||
|     suggest(query: string, options?: GeocodingOptions): Store<SearchResult[]> { | ||||
|         return new ImmutableStore(this.searchDirectlyWrapped(query)) | ||||
|         return new ImmutableStore(this.searchDirectly(query)) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|  | @ -85,7 +89,7 @@ export default class FilterSearch implements GeocodingProvider { | |||
|             if (!Array.isArray(filteredLayer.layerDef.filters)) { | ||||
|                 continue | ||||
|             } | ||||
|             if (Constants.priviliged_layers.indexOf(id) >= 0) { | ||||
|             if (Constants.priviliged_layers.indexOf(<any> id) >= 0) { | ||||
|                 continue | ||||
|             } | ||||
|             for (const filter of filteredLayer.layerDef.filters) { | ||||
|  | @ -99,14 +103,14 @@ export default class FilterSearch implements GeocodingProvider { | |||
|                         option, | ||||
|                         filter, | ||||
|                         index: i, | ||||
|                         layer: filteredLayer.layerDef | ||||
|                         layer: filteredLayer.layerDef, | ||||
|                     }) | ||||
|                 } | ||||
|                 Utils.shuffle(singleFilterResults) | ||||
|                 result.push(...singleFilterResults.slice(0,3)) | ||||
|                 result.push(...singleFilterResults.slice(0, 3)) | ||||
|             } | ||||
|         } | ||||
|         Utils.shuffle(result) | ||||
|         return result.slice(0,6) | ||||
|         return result.slice(0, 6) | ||||
|     } | ||||
| } | ||||
|  | @ -46,9 +46,11 @@ export type GeocodeResult =  { | |||
| } | ||||
| 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 { | ||||
							
								
								
									
										53
									
								
								src/Logic/Search/LayerSearch.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/Logic/Search/LayerSearch.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,53 @@ | |||
| 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" | ||||
| 
 | ||||
| export default class LayerSearch implements GeocodingProvider<LayerResult> { | ||||
| 
 | ||||
|     private readonly _state: SpecialVisualizationState | ||||
|     private readonly _suggestionLimit: number | ||||
|     private readonly _layerWhitelist : Set<string> | ||||
|     constructor(state: SpecialVisualizationState, suggestionLimit: number) { | ||||
|         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)) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     private searchWrapped(query: string, limit: number): LayerResult[] { | ||||
|         return this.searchDirect(query, limit) | ||||
|     } | ||||
| 
 | ||||
|     public searchDirect(query: string, limit: number): LayerResult[] { | ||||
|         if (query.length < 1) { | ||||
|             return [] | ||||
|         } | ||||
|         const scores = MoreScreen.scoreLayers(query, this._layerWhitelist) | ||||
|         const asList:(LayerResult & {score:number})[] = [] | ||||
|         for (const layer in scores) { | ||||
|             asList.push({ | ||||
|                 category: "layer", | ||||
|                 payload: this._state.layout.getLayer(layer), | ||||
|                 osm_id: layer, | ||||
|                 score: scores[layer] | ||||
|             }) | ||||
|         } | ||||
|         asList.sort((a, b) => a.score - b.score) | ||||
| 
 | ||||
|         return asList | ||||
|             .filter(sorted => sorted.score < 2) | ||||
|             .slice(0, limit) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | @ -8,6 +8,7 @@ import Translations from "../../UI/i18n/Translations" | |||
| import { RegexTag } from "../Tags/RegexTag" | ||||
| import { Or } from "../Tags/Or" | ||||
| import FilterConfig from "../../Models/ThemeConfig/FilterConfig" | ||||
| import Constants from "../../Models/Constants" | ||||
| 
 | ||||
| export type ActiveFilter = { | ||||
|     layer: LayerConfig, | ||||
|  | @ -35,6 +36,10 @@ export default class LayerState { | |||
|     private readonly _activeFilters: UIEventSource<ActiveFilter[]> = new UIEventSource([]) | ||||
| 
 | ||||
|     public readonly activeFilters: Store<ActiveFilter[]> = this._activeFilters | ||||
|     private readonly _activeLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>(undefined) | ||||
|     public readonly activeLayers: Store<FilteredLayer[]> = this._activeLayers | ||||
|     private readonly _nonactiveLayers: UIEventSource<FilteredLayer[]> = new UIEventSource<FilteredLayer[]>(undefined) | ||||
|     public readonly nonactiveLayers: Store<FilteredLayer[]> = this._nonactiveLayers | ||||
|     private readonly osmConnection: OsmConnection | ||||
| 
 | ||||
|     /** | ||||
|  | @ -77,8 +82,16 @@ export default class LayerState { | |||
| 
 | ||||
|     private updateActiveFilters(){ | ||||
|         const filters: ActiveFilter[] = [] | ||||
|         const activeLayers: FilteredLayer[] = [] | ||||
|         const nonactiveLayers: FilteredLayer[] =  [] | ||||
|         this.filteredLayers.forEach(fl => { | ||||
|             if(!fl.isDisplayed.data){ | ||||
|                 nonactiveLayers.push(fl) | ||||
|                 return | ||||
|             } | ||||
|             activeLayers.push(fl) | ||||
| 
 | ||||
|             if(fl.layerDef.filterIsSameAs){ | ||||
|                 return | ||||
|             } | ||||
|             for (const [filtername, appliedFilter] of fl.appliedFilters) { | ||||
|  | @ -86,6 +99,7 @@ export default class LayerState { | |||
|                     continue | ||||
|                 } | ||||
|                 const filter = fl.layerDef.filters.find(f => f.id === filtername) | ||||
|                 console.log("Updating active filters for flayer", fl.layerDef.id,"with filterconfig",filter) | ||||
|                 if(typeof appliedFilter.data === "number"){ | ||||
|                     if(filter.options[appliedFilter.data].osmTags === undefined){ | ||||
|                         // This is probably the first, generic option which doesn't _actually_ filter
 | ||||
|  | @ -99,6 +113,8 @@ export default class LayerState { | |||
|                 }) | ||||
|             } | ||||
|         }) | ||||
|         this._activeLayers.set(activeLayers) | ||||
|         this._nonactiveLayers.set(nonactiveLayers) | ||||
|         this._activeFilters.set(filters) | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,25 +1,26 @@ | |||
| import GeocodingProvider, { | ||||
|     FilterPayload, | ||||
|     FilterPayload, FilterResult, | ||||
|     GeocodeResult, | ||||
|     GeocodingUtils, | ||||
|     type SearchResult | ||||
| } from "../Geocoding/GeocodingProvider" | ||||
|     GeocodingUtils, LayerResult, | ||||
|     type SearchResult, | ||||
| } from "../Search/GeocodingProvider" | ||||
| import { ImmutableStore, Store, Stores, UIEventSource } from "../UIEventSource" | ||||
| import CombinedSearcher from "../Geocoding/CombinedSearcher" | ||||
| import FilterSearch from "../Geocoding/FilterSearch" | ||||
| import LocalElementSearch from "../Geocoding/LocalElementSearch" | ||||
| import CoordinateSearch from "../Geocoding/CoordinateSearch" | ||||
| import ThemeSearch from "../Geocoding/ThemeSearch" | ||||
| import OpenStreetMapIdSearch from "../Geocoding/OpenStreetMapIdSearch" | ||||
| import PhotonSearch from "../Geocoding/PhotonSearch" | ||||
| import CombinedSearcher from "../Search/CombinedSearcher" | ||||
| import FilterSearch 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 "../Geocoding/GeocodingFeatureSource" | ||||
| import GeocodingFeatureSource from "../Search/GeocodingFeatureSource" | ||||
| import ShowDataLayer from "../../UI/Map/ShowDataLayer" | ||||
| import LayerSearch from "../Search/LayerSearch" | ||||
| 
 | ||||
| export default class SearchState { | ||||
| 
 | ||||
|  | @ -28,8 +29,9 @@ export default class SearchState { | |||
|     public readonly searchTerm: UIEventSource<string> = new UIEventSource<string>("") | ||||
|     public readonly searchIsFocused = new UIEventSource(false) | ||||
|     public readonly suggestions: Store<SearchResult[]> | ||||
|     public readonly filterSuggestions: Store<FilterPayload[]> | ||||
|     public readonly filterSuggestions: Store<FilterResult[]> | ||||
|     public readonly themeSuggestions: Store<MinimalLayoutInformation[]> | ||||
|     public readonly layerSuggestions: Store<LayerResult[]> | ||||
|     public readonly locationSearchers: ReadonlyArray<GeocodingProvider<GeocodeResult>> | ||||
| 
 | ||||
|     private readonly state: ThemeViewState | ||||
|  | @ -43,7 +45,7 @@ export default class SearchState { | |||
|             // new LocalElementSearch(state, 5),
 | ||||
|             new CoordinateSearch(), | ||||
|             new OpenStreetMapIdSearch(state), | ||||
|             new PhotonSearch() // new NominatimGeocoding(),
 | ||||
|             new PhotonSearch(), // new NominatimGeocoding(),
 | ||||
|         ] | ||||
| 
 | ||||
|         const bounds = state.mapProperties.bounds | ||||
|  | @ -53,27 +55,30 @@ export default class SearchState { | |||
|                 } | ||||
|                 return this.locationSearchers.map(ls => ls.suggest(search, { bbox: bounds.data })) | ||||
| 
 | ||||
|             }, [bounds] | ||||
|             }, [bounds], | ||||
|         ) | ||||
|         this.suggestionsSearchRunning = suggestionsList.bind(suggestions => { | ||||
|             if(suggestions === undefined){ | ||||
|             if (suggestions === undefined) { | ||||
|                 return new ImmutableStore(true) | ||||
|             } | ||||
|             return Stores.concat(suggestions).map(suggestions => suggestions.some(list => list === undefined)) | ||||
|         }) | ||||
|         this.suggestions = suggestionsList.bindD(suggestions => | ||||
|             Stores.concat(suggestions).map(suggestions => CombinedSearcher.merge(suggestions)) | ||||
|             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 layerSearch = new LayerSearch(state, 5) | ||||
|         this.layerSuggestions = this.searchTerm.mapD(query => layerSearch.searchDirect(query, 5)) | ||||
| 
 | ||||
|         const filterSearch = new FilterSearch(state) | ||||
|         this.filterSuggestions = this.searchTerm.stabilized(50).mapD(query => filterSearch.searchDirectly(query) | ||||
|         ).mapD(filterResult => { | ||||
|         this.filterSuggestions = this.searchTerm.stabilized(50) | ||||
|             .mapD(query => filterSearch.searchDirectly(query)) | ||||
|             .mapD(filterResult => { | ||||
|                 const active = state.layerState.activeFilters.data | ||||
|             return filterResult.filter(({ filter, index, layer }) => { | ||||
|                 return filterResult.filter(({ payload: { filter, index, layer } }) => { | ||||
|                     const foundMatch = active.some(active => | ||||
|                         active.filter.id === filter.id && layer.id === active.layer.id && active.control.data === index) | ||||
| 
 | ||||
|  | @ -88,8 +93,8 @@ export default class SearchState { | |||
|             { | ||||
|                 layer: GeocodingUtils.searchLayer, | ||||
|                 features: geocodedFeatures, | ||||
|                 selectedElement: state.selectedElement | ||||
|             } | ||||
|                 selectedElement: state.selectedElement, | ||||
|             }, | ||||
|         ) | ||||
| 
 | ||||
|         this.showSearchDrawer = new UIEventSource(false) | ||||
|  | @ -103,21 +108,31 @@ export default class SearchState { | |||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public async apply(result: FilterResult | LayerResult) { | ||||
|         if (result.category === "filter") { | ||||
|             return this.applyFilter(result.payload) | ||||
|         } | ||||
|         if (result.category === "layer") { | ||||
|             return this.applyLayer(result) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public async apply(payload: FilterPayload) { | ||||
|     private async applyLayer(layer: LayerResult) { | ||||
|         for (const [name, otherLayer] of this.state.layerState.filteredLayers) { | ||||
|             otherLayer.isDisplayed.setData(name === layer.osm_id) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private async applyFilter(payload: FilterPayload) { | ||||
|         const state = this.state | ||||
| 
 | ||||
|         const { layer, filter, index } = payload | ||||
| 
 | ||||
|         const flayer = state.layerState.filteredLayers.get(layer.id) | ||||
|         const filtercontrol = flayer.appliedFilters.get(filter.id) | ||||
|         for (const [name, otherLayer] of state.layerState.filteredLayers) { | ||||
|             if (name === layer.id) { | ||||
|                 otherLayer.isDisplayed.setData(true) | ||||
|                 continue | ||||
|             otherLayer.isDisplayed.setData(name === layer.id) | ||||
|         } | ||||
|             otherLayer.isDisplayed.setData(false) | ||||
|         } | ||||
| 
 | ||||
|         const flayer = state.layerState.filteredLayers.get(layer.id) | ||||
|         flayer.isDisplayed.set(true) | ||||
|         const filtercontrol = flayer.appliedFilters.get(filter.id) | ||||
|         console.log("Could not apply", layer.id, ".", filter.id, index) | ||||
|         if (filtercontrol.data === index) { | ||||
|             filtercontrol.setData(undefined) | ||||
|  | @ -126,76 +141,4 @@ export default class SearchState { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Tries to search and goto a given location | ||||
|      * Returns 'false' if search failed | ||||
|      */ | ||||
|     public async performSearch(): Promise<boolean> { | ||||
|         const query = this.searchTerm.data?.trim() ?? "" | ||||
|         if (query === "") { | ||||
|             return | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| 
 | ||||
|         const geolocationState = this.state.geolocation.geolocationState | ||||
|         const bounds = this.state.mapProperties.bounds | ||||
|         const bbox = this.state.mapProperties.bounds.data | ||||
|         try { | ||||
|             this.isSearching.set(true) | ||||
|             geolocationState?.allowMoving.setData(true) | ||||
|             geolocationState?.requestMoment.setData(undefined) // If the GPS is still searching for a fix, we say that we don't want tozoom to it anymore
 | ||||
|             let poi: SearchResult | ||||
|             if(this.suggestions.data){ | ||||
|                 poi = this.suggestions.data[0] | ||||
|             }else{ | ||||
|                 const results = GeocodingUtils.mergeSimilarResults([].concat(...await Promise.all(this.locationSearchers.map(ls => ls.search(query, { bbox: bounds.data }))))) | ||||
|                 poi = results[0] | ||||
|             } | ||||
| 
 | ||||
|             if (poi.category === "theme") { | ||||
|                 const theme = <MinimalLayoutInformation>poi.payload | ||||
|                 const url = MoreScreen.createUrlFor(theme) | ||||
|                 window.location = <any>url | ||||
|                 return true | ||||
|             } | ||||
|             if (poi.category === "filter") { | ||||
|                 await this.apply(poi.payload) | ||||
|                 return true | ||||
|             } | ||||
|             if (poi.boundingbox) { | ||||
|                 const [lat0, lat1, lon0, lon1] = poi.boundingbox | ||||
|                 // Will trigger a 'fly to'
 | ||||
|                 bounds.set( | ||||
|                     new BBox([ | ||||
|                         [lon0, lat0], | ||||
|                         [lon1, lat1] | ||||
|                     ]).pad(0.01) | ||||
|                 ) | ||||
|             } else if (poi.lon && poi.lat) { | ||||
|                 this.state.mapProperties.flyTo(poi.lon, poi.lat, GeocodingUtils.categoryToZoomLevel[poi.category] ?? 16) | ||||
|             } | ||||
|             const perLayer = this.state.perLayer | ||||
|             if (perLayer !== undefined) { | ||||
|                 const id = poi.osm_type + "/" + poi.osm_id | ||||
|                 const layers = Array.from(perLayer?.values() ?? []) | ||||
|                 for (const layer of layers) { | ||||
|                     const found = layer.features.data.find((f) => f.properties.id === id) | ||||
|                     if (found === undefined) { | ||||
|                         continue | ||||
|                     } | ||||
|                     this.state.selectedElement?.setData(found) | ||||
|                 } | ||||
|             } | ||||
|             return true | ||||
|         } catch (e) { | ||||
|             console.error(e) | ||||
|             this.feedback.set(Translations.t.general.search.error) | ||||
|             return false | ||||
|         } finally { | ||||
|             this.isSearching.set(false) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -20,7 +20,7 @@ import { ThemeMetaTagging } from "./UserSettingsMetaTagging" | |||
| import { MapProperties } from "../../Models/MapProperties" | ||||
| import Showdown from "showdown" | ||||
| import { LocalStorageSource } from "../Web/LocalStorageSource" | ||||
| import { GeocodeResult } from "../Geocoding/GeocodingProvider" | ||||
| import { GeocodeResult } from "../Search/GeocodingProvider" | ||||
| 
 | ||||
| 
 | ||||
| export class OptionallySyncedHistory<T> { | ||||
|  | @ -42,8 +42,6 @@ export class OptionallySyncedHistory<T> { | |||
|             "preference-" + key + "-history", | ||||
|             "sync", | ||||
|         ) | ||||
|         console.log(">>>",key, this.syncPreference) | ||||
| 
 | ||||
|         const synced = this.synced = UIEventSource.asObject<T[]>(osmconnection.GetLongPreference(key + "-history"), []) | ||||
|         const local = this.local = LocalStorageSource.GetParsed<T[]>(key + "-history", []) | ||||
|         const thisSession = this.thisSession = new UIEventSource<T[]>([], "optionally-synced:"+key+"(session only)") | ||||
|  | @ -91,7 +89,6 @@ export class OptionallySyncedHistory<T> { | |||
|         if (this._isSame) { | ||||
|             oldList = oldList.filter(x => !this._isSame(t, x)) | ||||
|         } | ||||
|         console.log("Setting new history:", store, [t, ...oldList]) | ||||
|         store.set([t, ...oldList].slice(0, this._maxHistory)) | ||||
|     } | ||||
| 
 | ||||
|  | @ -198,7 +195,6 @@ export default class UserRelatedState { | |||
|         this.showTags = this.osmConnection.GetPreference("show_tags") | ||||
|         this.showCrosshair = this.osmConnection.GetPreference("show_crosshair") | ||||
|         this.fixateNorth = this.osmConnection.GetPreference("fixate-north") | ||||
|         console.log("Fixate north is:", this.fixateNorth) | ||||
|         this.morePrivacy = this.osmConnection.GetPreference("more_privacy", "no") | ||||
| 
 | ||||
|         this.a11y = this.osmConnection.GetPreference("a11y") | ||||
|  |  | |||
|  | @ -641,4 +641,18 @@ export default class LayerConfig extends WithContextLoader { | |||
|         } | ||||
|         return mostShadowed ?? matchingPresets[0] | ||||
|     } | ||||
| 
 | ||||
|     public isNormal(){ | ||||
|         if(this.id.startsWith("note_import")){ | ||||
|             return false | ||||
|         } | ||||
| 
 | ||||
|         if(Constants.added_by_default.indexOf(<any> this.id) >=0){ | ||||
|             return false | ||||
|         } | ||||
|         if(this.filterIsSameAs !== undefined){ | ||||
|             return false | ||||
|         } | ||||
|         return true | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -67,7 +67,7 @@ import { LayerConfigJson } from "./ThemeConfig/Json/LayerConfigJson" | |||
| import Hash from "../Logic/Web/Hash" | ||||
| import { GeoOperations } from "../Logic/GeoOperations" | ||||
| import { CombinedFetcher } from "../Logic/Web/NearbyImagesSearch" | ||||
| import { GeocodeResult, GeocodingUtils } from "../Logic/Geocoding/GeocodingProvider" | ||||
| import { GeocodeResult, GeocodingUtils } from "../Logic/Search/GeocodingProvider" | ||||
| import SearchState from "../Logic/State/SearchState" | ||||
| 
 | ||||
| /** | ||||
|  |  | |||
|  | @ -8,7 +8,7 @@ | |||
|   import Loading from "./Loading.svelte" | ||||
|   import { onDestroy } from "svelte" | ||||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
|   import { GeocodingUtils } from "../../Logic/Geocoding/GeocodingProvider" | ||||
|   import { GeocodingUtils } from "../../Logic/Search/GeocodingProvider" | ||||
|   import ThemeViewState from "../../Models/ThemeViewState" | ||||
| 
 | ||||
|   export let state: SpecialVisualizationState | ||||
|  |  | |||
|  | @ -66,7 +66,7 @@ export default class MoreScreen { | |||
|             return Infinity | ||||
|         } | ||||
|         language ??= Locale.language.data | ||||
|         const queryParts = query.split(" ").map(q => Utils.simplifyStringForSearch(q)) | ||||
|         const queryParts = query.trim().split(" ").map(q => Utils.simplifyStringForSearch(q)) | ||||
|         let terms: string[] | ||||
|         if (Array.isArray(keywords)) { | ||||
|             terms = keywords | ||||
|  | @ -90,9 +90,12 @@ export default class MoreScreen { | |||
|         return distanceSummed | ||||
|     } | ||||
| 
 | ||||
|     public static scoreLayers(query: string): Record<string, number> { | ||||
|     public static scoreLayers(query: string, layerWhitelist?: Set<string>): Record<string, number> { | ||||
|         const result: Record<string, number> = {} | ||||
|         for (const id in this.officialThemes.layers) { | ||||
|             if(layerWhitelist !== undefined && !layerWhitelist.has(id)){ | ||||
|                 continue | ||||
|             } | ||||
|             const keywords = this.officialThemes.layers[id] | ||||
|             const distance = this.scoreKeywords(query, keywords) | ||||
|             result[id] = distance | ||||
|  |  | |||
|  | @ -3,7 +3,7 @@ | |||
|    * Shows the current address when shaken | ||||
|    **/ | ||||
|   import Motion from "../../Sensors/Motion" | ||||
|   import { NominatimGeocoding } from "../../Logic/Geocoding/NominatimGeocoding" | ||||
|   import { NominatimGeocoding } from "../../Logic/Search/NominatimGeocoding" | ||||
|   import Hotkeys from "../Base/Hotkeys" | ||||
|   import Translations from "../i18n/Translations" | ||||
|   import Locale from "../i18n/Locale" | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| <script lang="ts"> | ||||
|   import type { ActiveFilter } from "../../Logic/State/LayerState" | ||||
|   import FilterOption from "./FilterOption.svelte" | ||||
|   import { XMarkIcon } from "@babeard/svelte-heroicons/mini" | ||||
|   import Loading from "../Base/Loading.svelte" | ||||
|   import FilterToggle from "./FilterToggle.svelte" | ||||
| 
 | ||||
| 
 | ||||
|   export let activeFilter: ActiveFilter | ||||
|  | @ -21,10 +21,7 @@ | |||
| {#if loading} | ||||
|   <Loading /> | ||||
| {:else } | ||||
|   <div class="badge button-unstyled w-fit"> | ||||
|   <FilterToggle  on:click={() => clear()}> | ||||
|     <FilterOption option={$option} /> | ||||
|     <button on:click={() => clear()}> | ||||
|       <XMarkIcon class="w-5 h-5 pl-1" color="gray" /> | ||||
|     </button> | ||||
|   </div> | ||||
|   </FilterToggle> | ||||
| {/if} | ||||
|  |  | |||
|  | @ -3,13 +3,33 @@ | |||
|   import type { ActiveFilter } from "../../Logic/State/LayerState" | ||||
|   import Loading from "../Base/Loading.svelte" | ||||
|   import SidebarUnit from "../Base/SidebarUnit.svelte" | ||||
|   import type { SpecialVisualizationState } from "../SpecialVisualization" | ||||
|   import FilteredLayer from "../../Models/FilteredLayer" | ||||
|   import FilterToggle from "./FilterToggle.svelte" | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte" | ||||
|   import Tr from "../Base/Tr.svelte" | ||||
|   import Constants from "../../Models/Constants" | ||||
|   import { Store } from "../../Logic/UIEventSource" | ||||
| 
 | ||||
|   export let activeFilters: ActiveFilter[] | ||||
|   export let state: SpecialVisualizationState | ||||
|   let loading = false | ||||
| 
 | ||||
| 
 | ||||
|   let activeLayers: Store<FilteredLayer[]> = state.layerState.activeLayers.mapD(l => l.filter(l => l.layerDef.isNormal())) | ||||
|   let nonactiveLayers: Store<FilteredLayer[]> = state.layerState.nonactiveLayers.mapD(l => l.filter(l => l.layerDef.isNormal())) | ||||
| 
 | ||||
|   function enableAllLayers() { | ||||
|     for (const flayer of $nonactiveLayers) { | ||||
|       flayer.isDisplayed.set(true) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function clear() { | ||||
|     loading = true | ||||
|     requestIdleCallback(() => { | ||||
|       enableAllLayers() | ||||
| 
 | ||||
| 
 | ||||
|       for (const activeFilter of activeFilters) { | ||||
|         activeFilter.control.setData(undefined) | ||||
|  | @ -18,14 +38,43 @@ | |||
|     }) | ||||
|   } | ||||
| </script> | ||||
| {#if activeFilters.length > 0} | ||||
| 
 | ||||
| {#if activeFilters.length > 0 || $activeLayers.length === 1 || $nonactiveLayers.length > 0} | ||||
|   <SidebarUnit> | ||||
|     <div class="flex justify-between"> | ||||
|       <h3>Active filters</h3> | ||||
| 
 | ||||
|       <button class="as-link subtle self-end" on:click={() => clear()} style="margin-right: 0.75rem"> | ||||
|         Clear filters | ||||
|       </button> | ||||
|     </div> | ||||
|     {#if loading} | ||||
|       <Loading /> | ||||
|     {:else} | ||||
|       <div class="flex flex-wrap gap-x-1 gap-y-2"> | ||||
| 
 | ||||
|       <div class="flex flex-wrap gap-x-1 gap-y-2 overflow-x-hidden overflow-y-auto"> | ||||
|         {#if $activeLayers.length === 1} | ||||
|           <FilterToggle on:click={() => enableAllLayers()}> | ||||
|             <div class="w-8 h-8 p-1"> | ||||
|               <ToSvelte construct={$activeLayers[0].layerDef.defaultIcon()} /> | ||||
|             </div> | ||||
|             <b> | ||||
|               <Tr t={$activeLayers[0].layerDef.name} /> | ||||
|             </b> | ||||
|           </FilterToggle> | ||||
|         {:else if $nonactiveLayers.length > 0} | ||||
|           {#each $nonactiveLayers as nonActive (nonActive.layerDef.id)} | ||||
|             <FilterToggle on:click={() => nonActive.isDisplayed.set(true)}> | ||||
|               <div class="w-8 h-8 p-1"> | ||||
|                 <ToSvelte construct={nonActive.layerDef.defaultIcon()} /> | ||||
|               </div> | ||||
|               <del class="block-ruby"> | ||||
|                 <Tr t={nonActive.layerDef.name} /> | ||||
|               </del> | ||||
|             </FilterToggle> | ||||
|           {/each} | ||||
|         {/if} | ||||
| 
 | ||||
| 
 | ||||
|         {#each activeFilters as activeFilter (activeFilter)} | ||||
|           <div> | ||||
|  | @ -34,9 +83,7 @@ | |||
|         {/each} | ||||
|       </div> | ||||
| 
 | ||||
|       <button class="as-link subtle self-end" on:click={() => clear()} style="margin-right: 0.75rem"> | ||||
|         Clear filters | ||||
|       </button> | ||||
| 
 | ||||
|     {/if} | ||||
|   </SidebarUnit> | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,12 +1,13 @@ | |||
| <script lang="ts"> | ||||
|   import type { SpecialVisualizationState } from "../SpecialVisualization" | ||||
|   import Tr from "../Base/Tr.svelte" | ||||
|   import type { FilterPayload } from "../../Logic/Geocoding/GeocodingProvider" | ||||
|   import type { FilterPayload, FilterResult, LayerResult } from "../../Logic/Search/GeocodingProvider" | ||||
|   import { createEventDispatcher } from "svelte" | ||||
|   import Icon from "../Map/Icon.svelte" | ||||
|   import Marker from "../Map/Marker.svelte" | ||||
|   import ToSvelte from "../Base/ToSvelte.svelte" | ||||
| 
 | ||||
|   export let entry: FilterPayload | ||||
|   let { option } = entry | ||||
|   export let entry: FilterResult | LayerResult | ||||
|   export let state: SpecialVisualizationState | ||||
|   let dispatch = createEventDispatcher<{ select }>() | ||||
| 
 | ||||
|  | @ -18,10 +19,18 @@ | |||
| </script> | ||||
| <button on:click={() => apply()}> | ||||
|   <div class="flex flex-col items-start"> | ||||
| 
 | ||||
|     <div class="flex items-center gap-x-1"> | ||||
|       <Icon icon={option.icon ?? option.emoji} clss="w-4 h-4" emojiHeight="14px" /> | ||||
|       <Tr cls="whitespace-nowrap" t={option.question} /> | ||||
|       {#if entry.category === "layer"} | ||||
|         <div class="w-8 h-8 p-1"> | ||||
|           <ToSvelte construct={entry.payload.defaultIcon()} /> | ||||
|         </div> | ||||
|         <b> | ||||
|           <Tr t={entry.payload.name} /> | ||||
|         </b> | ||||
|       {:else} | ||||
|         <Icon icon={entry.payload.option.icon ?? entry.payload. option.emoji} clss="w-4 h-4" emojiHeight="14px" /> | ||||
|         <Tr cls="whitespace-nowrap" t={entry.payload.option.question} /> | ||||
|       {/if} | ||||
|     </div> | ||||
|   </div> | ||||
| </button> | ||||
|  |  | |||
							
								
								
									
										9
									
								
								src/UI/Search/FilterToggle.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/UI/Search/FilterToggle.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| <script lang="ts"> | ||||
|   import { XMarkIcon } from "@babeard/svelte-heroicons/mini" | ||||
| </script> | ||||
| <div class="badge button-unstyled w-fit"> | ||||
|   <slot/> | ||||
|     <button on:click> | ||||
|       <XMarkIcon class="w-5 h-5 pl-1" color="gray" /> | ||||
|     </button> | ||||
| </div> | ||||
|  | @ -1,6 +1,6 @@ | |||
| <script lang="ts"> | ||||
|   import { GeocodingUtils } from "../../Logic/Geocoding/GeocodingProvider" | ||||
|   import type { GeocodeResult } from "../../Logic/Geocoding/GeocodingProvider" | ||||
|   import { GeocodingUtils } from "../../Logic/Search/GeocodingProvider" | ||||
|   import type { GeocodeResult } from "../../Logic/Search/GeocodingProvider" | ||||
|   import { GeoOperations } from "../../Logic/GeoOperations" | ||||
|   import LayerConfig from "../../Models/ThemeConfig/LayerConfig" | ||||
|   import { UIEventSource } from "../../Logic/UIEventSource" | ||||
|  |  | |||
|  | @ -1,5 +1,5 @@ | |||
| <script lang="ts"> | ||||
|   import type { SearchResult } from "../../Logic/Geocoding/GeocodingProvider" | ||||
|   import type { SearchResult } from "../../Logic/Search/GeocodingProvider" | ||||
| 
 | ||||
|   import ThemeResult from "../Search/ThemeResult.svelte" | ||||
|   import FilterResult from "./FilterResult.svelte" | ||||
|  |  | |||
|  | @ -1,51 +1,79 @@ | |||
| <script lang="ts"> | ||||
|   import { Store, UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import { Store } from "../../Logic/UIEventSource" | ||||
|   import Loading from "../Base/Loading.svelte" | ||||
|   import Tr from "../Base/Tr.svelte" | ||||
|   import Translations from "../i18n/Translations" | ||||
|   import { default as SearchResultSvelte } from "./SearchResult.svelte" | ||||
|   import MoreScreen from "../BigComponents/MoreScreen" | ||||
|   import type { GeocodeResult } from "../../Logic/Geocoding/GeocodingProvider" | ||||
|   import type { FilterResult, GeocodeResult, LayerResult } from "../../Logic/Search/GeocodingProvider" | ||||
| 
 | ||||
|   import ActiveFilters from "./ActiveFilters.svelte" | ||||
|   import Constants from "../../Models/Constants" | ||||
|   import type { ActiveFilter } from "../../Logic/State/LayerState" | ||||
|   import ThemeViewState from "../../Models/ThemeViewState" | ||||
|   import FilterResult from "./FilterResult.svelte" | ||||
|   import {default as FilterResultSvelte} from "./FilterResult.svelte" | ||||
|   import ThemeResult from "./ThemeResult.svelte" | ||||
|   import SidebarUnit from "../Base/SidebarUnit.svelte" | ||||
|   import { TrashIcon } from "@babeard/svelte-heroicons/mini" | ||||
|   import { Dropdown, DropdownItem } from "flowbite-svelte" | ||||
|   import DotsCircleHorizontal from "@rgossiaux/svelte-heroicons/solid/DotsCircleHorizontal" | ||||
|   import DotMenu from "../Base/DotMenu.svelte" | ||||
|   import { CogIcon } from "@rgossiaux/svelte-heroicons/solid" | ||||
| 
 | ||||
|   export let state: ThemeViewState | ||||
|   let activeFilters: Store<ActiveFilter[]> = state.layerState.activeFilters.map(fs => fs.filter(f => Constants.priviliged_layers.indexOf(<any>f.layer.id) < 0)) | ||||
|   let recentlySeen: Store<GeocodeResult[]> = state.userRelatedState.recentlyVisitedSearch.value | ||||
|   let recentThemes = state.userRelatedState.recentlyVisitedThemes.value.map(themes => themes.filter(th => th.id !== state.layout.id).slice(0, 6)) | ||||
|   let recentThemes = state.userRelatedState.recentlyVisitedThemes.value.map(themes => themes.filter(th => th !== state.layout.id).slice(0, 6)) | ||||
|   let allowOtherThemes = state.featureSwitches.featureSwitchBackToThemeOverview | ||||
|   let searchTerm = state.searchState.searchTerm | ||||
|   let results = state.searchState.suggestions | ||||
|   let isSearching = state.searchState.suggestionsSearchRunning | ||||
|   let filterResults = state.searchState.filterSuggestions | ||||
|   let activeLayers = state.layerState.activeLayers | ||||
|   let layerResults = state.searchState.layerSuggestions.map(layers => { | ||||
|     const nowActive = activeLayers.data.filter(al => al.layerDef.isNormal()) | ||||
|     if(nowActive.length === 1){ | ||||
|       const shownInActiveFiltersView = nowActive[0] | ||||
|       layers = layers.filter(l => l.payload.id !== shownInActiveFiltersView.layerDef.id) | ||||
|     } | ||||
|     return layers | ||||
|   }, [activeLayers]) | ||||
| 
 | ||||
| 
 | ||||
|   let filterResultsClipped = filterResults.mapD(filters => { | ||||
|     let layers = layerResults.data | ||||
|     const ls : (FilterResult | LayerResult)[] = [].concat(layers, filters) | ||||
|     if (ls.length <= 8) { | ||||
|       return ls | ||||
|     } | ||||
|     return ls.slice(0, 6) | ||||
|   }, [layerResults, activeLayers]) | ||||
|   let themeResults = state.searchState.themeSuggestions | ||||
| 
 | ||||
| </script> | ||||
| <div class="p-4 low-interaction flex gap-y-2 flex-col"> | ||||
| 
 | ||||
|   <ActiveFilters activeFilters={$activeFilters} /> | ||||
|   <ActiveFilters {state} activeFilters={$activeFilters} /> | ||||
| 
 | ||||
|   {#if $searchTerm.length > 0 && $filterResults.length > 0} | ||||
|   {#if $searchTerm.length === 0 && $filterResults.length === 0 && $activeFilters.length === 0 && $recentThemes.length === 0} | ||||
|     <div class="p-8 items-center text-center"> | ||||
|       <b>Use the search bar above to search for locations, filters and other maps</b> | ||||
|     </div> | ||||
|   {/if} | ||||
| 
 | ||||
|   {#if $searchTerm.length > 0 && ($filterResults.length > 0 || $layerResults.length > 0)} | ||||
|     <SidebarUnit> | ||||
| 
 | ||||
|       <h3>Pick a filter below</h3> | ||||
| 
 | ||||
|       <div class="flex flex-wrap"> | ||||
|         {#each $filterResults as filterResult (filterResult)} | ||||
|           <FilterResult {state} entry={filterResult} /> | ||||
|         {#each $filterResultsClipped as filterResult (filterResult)} | ||||
|           <FilterResultSvelte {state} entry={filterResult} /> | ||||
|         {/each} | ||||
|       </div> | ||||
|       {#if $filterResults.length + $layerResults.length > $filterResultsClipped.length} | ||||
|         <div class="flex justify-center"> | ||||
|           ... and {$filterResults.length + $layerResults.length - $filterResultsClipped.length} more ... | ||||
|         </div> | ||||
|       {/if} | ||||
|     </SidebarUnit> | ||||
|   {/if} | ||||
| 
 | ||||
|  | @ -68,7 +96,7 @@ | |||
| 
 | ||||
|       {:else if !$isSearching} | ||||
|         <b class="flex justify-center p-4"> | ||||
|           <Tr t={Translations.t.general.search.nothingFor.Subs({term: $searchTerm})} /> | ||||
|           <Tr t={Translations.t.general.search.nothingFor.Subs({term: "<i>"+$searchTerm+"</i>"})} /> | ||||
|         </b> | ||||
|       {/if} | ||||
|     </SidebarUnit> | ||||
|  | @ -88,8 +116,16 @@ | |||
|     </SidebarUnit> | ||||
|   {/if} | ||||
| 
 | ||||
|   {#if $searchTerm.length === 0 && $recentlySeen?.length === 0 && $recentThemes.length === 0} | ||||
|     <SidebarUnit> | ||||
|       <h3> | ||||
| 
 | ||||
|   {#if $searchTerm.length == 0 && $recentlySeen?.length > 0} | ||||
|         Suggestions | ||||
|       </h3> | ||||
| 
 | ||||
|     </SidebarUnit> | ||||
|   {/if} | ||||
|   {#if $searchTerm.length === 0 && $recentlySeen?.length > 0} | ||||
|     <SidebarUnit> | ||||
|       <div class="flex justify-between"> | ||||
| 
 | ||||
|  |  | |||
|  | @ -326,7 +326,7 @@ | |||
| 
 | ||||
|     <div | ||||
|       id="top-bar" | ||||
|       class="flex bg-black-light-transparent pointer-events-auto items-center justify-between px-4 py-1 flex-wrap-reverse"> | ||||
|       class="flex bg-black-light-transparent pointer-events-auto items-center justify-between px-4 py-1 flex-wrap"> | ||||
|       <!-- Top bar with tools --> | ||||
|       <div class="flex items-center"> | ||||
| 
 | ||||
|  | @ -361,7 +361,7 @@ | |||
|       {/if} | ||||
| 
 | ||||
|       <If condition={state.featureSwitches.featureSwitchSearch}> | ||||
|         <div class="flex items-center"> | ||||
|         <div class="flex items-center flex-grow justify-end"> | ||||
|           <div class="w-full sm:w-64"> | ||||
|             <Searchbar value={state.searchState.searchTerm} isFocused={state.searchState.searchIsFocused} /> | ||||
|           </div> | ||||
|  |  | |||
|  | @ -391,8 +391,8 @@ h2.group { | |||
|     align-items: center; | ||||
|     white-space: nowrap; | ||||
|     border-radius: 999rem; | ||||
|     padding-left: 0.5rem; | ||||
|     padding-right: 0.5rem; | ||||
|     padding-left: 0.25rem; | ||||
|     padding-right: 0.25rem; | ||||
|     border: 1px solid var(--subtle-detail-color-light-contrast); | ||||
|     background-color: var(--low-interaction-background); | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue