Add search for filters

This commit is contained in:
Pieter Vander Vennet 2024-08-26 17:24:12 +02:00
parent 1378c1a779
commit c94393e825
24 changed files with 405 additions and 254 deletions

View file

@ -14,6 +14,7 @@
import Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations"
import { Utils } from "../../Utils"
import Icon from "../Map/Icon.svelte"
export let filteredLayer: FilteredLayer
export let highlightedLayer: Store<string | undefined> = new ImmutableStore(undefined)
@ -76,8 +77,8 @@
<Dropdown value={getStateFor(filter)}>
{#each filter.options as option, i}
<option value={i}>
{#if Utils.isEmoji(option.icon)}
{option.icon}
{#if option.emoji}
{option.emoji}
{/if}
<Tr t={option.question} />
</option>

View file

@ -1,20 +1,20 @@
<script lang="ts">
import type { ActiveFilter } from "../../Logic/State/LayerState"
import { Badge } from "flowbite-svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import Tr from "../Base/Tr.svelte"
import Icon from "../Map/Icon.svelte"
import { Badge } from "flowbite-svelte"
import FilterOption from "./FilterOption.svelte"
import { XMarkIcon } from "@babeard/svelte-heroicons/mini"
export let state: SpecialVisualizationState
export let activeFilter: ActiveFilter
let { control, layer, filter } = activeFilter
let option = control.map(c => {
if (typeof c === "number") {
return filter.options[c]
}
return filter.options[0]
})
let option = control.map(c => filter.options[c] ?? filter.options[0])
</script>
<Badge dismissable large border rounded color="dark" on:close={() =>{ console.log( "dismiss"); return control.setData(undefined) }}>
<Tr cls="whitespace-nowrap" t={$option.question} />
</Badge>
<div class="badge">
<FilterOption option={$option} />
<button on:click={() => control.setData(undefined)}>
<XMarkIcon class="w-5 h-5 pl-1" color="gray" />
</button>
</div>

View file

@ -1,17 +1,24 @@
<script lang="ts">
import type { SpecialVisualizationState } from "../SpecialVisualization"
import { Badge } from "flowbite-svelte"
import ActiveFilter from "./ActiveFilter.svelte"
export let state: SpecialVisualizationState
let activeFilters = state.layerState.activeFilters
import { default as ActiveFilterSvelte } from "./ActiveFilter.svelte"
import type { ActiveFilter } from "../../Logic/State/LayerState"
export let activeFilters: ActiveFilter[]
function clear() {
for (const activeFilter of activeFilters) {
activeFilter.control.setData(undefined)
}
}
</script>
<div class="flex flex-wrap gap-y-1 gap-x-1 button-unstyled">
{#if activeFilters.length > 0}
<div class="flex flex-wrap gap-y-1 gap-x-1 button-unstyled">
{#each activeFilters as activeFilter (activeFilter)}
<ActiveFilterSvelte {activeFilter} />
{/each}
{#each $activeFilters as activeFilter (activeFilter)}
<ActiveFilter {activeFilter} {state} />
{/each}
</div>
<button class="as-link subtle" on:click={() => clear()}>
Clear filters
</button>
</div>
{/if}

View file

@ -0,0 +1,10 @@
<script lang="ts">
import type { FilterConfigOption } from "../../Models/ThemeConfig/FilterConfig"
import Tr from "../Base/Tr.svelte"
import Icon from "../Map/Icon.svelte"
export let option : FilterConfigOption
</script>
<Icon icon={option.icon ?? option.emoji} clss="w-5 h-5" emojiHeight="14px" />
<Tr t={option.question} />

View file

@ -1,14 +1,10 @@
<script lang="ts">
import type FilterConfig from "../../Models/ThemeConfig/FilterConfig"
import type { FilterConfigOption } from "../../Models/ThemeConfig/FilterConfig"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import Filter from "../../assets/svg/Filter.svelte"
import Tr from "../Base/Tr.svelte"
import type { FilterPayload } from "../../Logic/Geocoding/GeocodingProvider"
import { createEventDispatcher } from "svelte"
import { FilterIcon as FilterSolid } from "@rgossiaux/svelte-heroicons/solid"
import { FilterIcon as FilterOutline } from "@rgossiaux/svelte-heroicons/outline"
import Icon from "../Map/Icon.svelte"
import SearchResultUtils from "./SearchResultUtils"
export let entry: {
category: "filter",
@ -18,38 +14,19 @@
export let state: SpecialVisualizationState
let dispatch = createEventDispatcher<{ select }>()
let flayer = state.layerState.filteredLayers.get(layer.id)
let filtercontrol = flayer.appliedFilters.get(filter.id)
let isActive = filtercontrol.map(c => c === index)
function apply() {
for (const [name, otherLayer] of state.layerState.filteredLayers) {
if(name === layer.id){
otherLayer.isDisplayed.setData(true)
continue
}
otherLayer.isDisplayed.setData(false)
}
if(filtercontrol.data === index){
filtercontrol.setData(undefined)
}else{
filtercontrol.setData(index)
}
SearchResultUtils.apply(entry.payload, state)
dispatch("select")
}
</script>
<button on:click={() => apply()}>
{#if $isActive}
<FilterSolid class="w-8 h-8 shrink-0" />
{:else}
<FilterOutline class="w-8 h-8 shrink-0" />
{/if}
<Tr t={option.question} />
<div class="subtle">
{layer.id}
<div class="flex flex-col items-start">
<div class="flex items-center gap-x-1">
<Icon icon={option.icon ?? option.emoji} clss="w-12 h-12 mr-2" emojiHeight="14px" />
<Tr cls="whitespace-nowrap" t={option.question} />
</div>
</div>
</button>

View file

@ -23,6 +23,7 @@
import ThemeViewState from "../../Models/ThemeViewState"
import GeocodingFeatureSource from "../../Logic/Geocoding/GeocodingFeatureSource"
import MoreScreen from "../BigComponents/MoreScreen"
import SearchResultUtils from "./SearchResultUtils"
export let perLayer: ReadonlyMap<string, GeoIndexedStoreForLayer> | undefined = undefined
export let bounds: UIEventSource<BBox>
@ -81,7 +82,6 @@
return
}
const result = await searcher.search(searchContentsData, { bbox: bounds.data, limit: 10 })
console.log("Results are", result)
if (result.length == 0) {
feedback = Translations.t.general.search.nothing.txt
focusOnSearch()
@ -91,11 +91,13 @@
if (poi.category === "theme") {
const theme = <MinimalLayoutInformation>poi.payload
const url = MoreScreen.createUrlFor(theme, false)
console.log("Found a theme, going to", url)
// @ts-ignore
window.location = url
return
}
if(poi.category === "filter"){
SearchResultUtils.apply(poi.payload, state)
}
if(poi.category === "filter"){
return // Should not happen
}
@ -120,7 +122,6 @@
continue
}
selectedElement?.setData(found)
console.log("Found an element that probably matches:", selectedElement?.data)
break
}
}
@ -146,7 +147,6 @@
return Stores.holdDefined(bounds.bindD(bbox => searcher.suggest(search, { bbox, limit: 15 })))
}
)
suggestions.addCallbackAndRun(suggestions => console.log(">>> suggestions are", suggestions))
let geocededFeatures= new GeocodingFeatureSource(suggestions.stabilized(250))
state.featureProperties.trackFeatureSource(geocededFeatures)

View file

@ -11,10 +11,9 @@
</script>
{#if entry.category === "theme"}
<ThemeResult {entry} />
<ThemeResult {entry} on:select />
{:else if entry.category === "filter"}
<FilterResult {entry} {state} />
<FilterResult {entry} {state} on:select />
{:else}
<GeocodeResult {entry} {state} />
<GeocodeResult {entry} {state} on:select />
{/if}

View file

@ -0,0 +1,25 @@
import { SpecialVisualizationState } from "../SpecialVisualization"
import { FilterPayload } from "../../Logic/Geocoding/GeocodingProvider"
export default class SearchResultUtils {
static apply(payload: FilterPayload, state: SpecialVisualizationState) {
const { layer, filter, index, option } = payload
let flayer = state.layerState.filteredLayers.get(layer.id)
let 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(false)
}
if (filtercontrol.data === index) {
filtercontrol.setData(undefined)
} else {
filtercontrol.setData(index)
}
}
}

View file

@ -8,14 +8,16 @@
import MoreScreen from "../BigComponents/MoreScreen"
import type { GeocodeResult, SearchResult } from "../../Logic/Geocoding/GeocodingProvider"
import ActiveFilters from "./ActiveFilters.svelte"
import Constants from "../../Models/Constants"
import type { ActiveFilter } from "../../Logic/State/LayerState"
export let state: SpecialVisualizationState
export let results: SearchResult[]
export let searchTerm: Store<string>
export let isFocused: UIEventSource<boolean>
let hasActiveFilters = state.layerState.activeFilters.map(af => af.length > 0)
let activeFilters: Store<ActiveFilter[]> = state.layerState.activeFilters.map(fs => fs.filter(f => Constants.priviliged_layers.indexOf(<any>f.layer.id) < 0))
console.log("Results are", results)
let hasActiveFilters = activeFilters.map(afs => afs.length > 0)
let recentlySeen: Store<GeocodeResult[]> = state.recentlySearched.seenThisSession
let recentThemes = state.userRelatedState.recentlyVisitedThemes.mapD(thms => thms.filter(th => th !== state.layout.id).slice(0, 3))
@ -24,7 +26,7 @@
<div class="relative w-full h-full collapsable " class:collapsed={!$isFocused && !$hasActiveFilters}>
<div class="searchbox normal-background">
<ActiveFilters {state} />
<ActiveFilters activeFilters={$activeFilters} />
{#if $isFocused}
{#if $searchTerm.length > 0 && results === undefined}
<div class="flex justify-center m-4 my-8">
@ -64,7 +66,7 @@
</h3>
{#each $recentThemes as themeId (themeId)}
<SearchResultSvelte
entry={{payload: MoreScreen.officialThemesById.get(themeId), display_name: themeId, lat: 0, lon: 0}}
entry={{payload: MoreScreen.officialThemesById.get(themeId), osm_id: themeId, category: "theme"}}
{state}
on:select />
{/each}