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
|
|
@ -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,25 +38,52 @@
|
|||
})
|
||||
}
|
||||
</script>
|
||||
{#if activeFilters.length > 0}
|
||||
|
||||
{#if activeFilters.length > 0 || $activeLayers.length === 1 || $nonactiveLayers.length > 0}
|
||||
<SidebarUnit>
|
||||
<h3>Active filters</h3>
|
||||
|
||||
{#if loading}
|
||||
<Loading />
|
||||
{:else}
|
||||
<div class="flex flex-wrap gap-x-1 gap-y-2">
|
||||
|
||||
{#each activeFilters as activeFilter (activeFilter)}
|
||||
<div>
|
||||
<ActiveFilterSvelte {activeFilter} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<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 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>
|
||||
<ActiveFilterSvelte {activeFilter} />
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
|
||||
{/if}
|
||||
</SidebarUnit>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,27 +1,36 @@
|
|||
<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 }>()
|
||||
|
||||
|
||||
function apply() {
|
||||
state.searchState.apply(entry)
|
||||
dispatch("select")
|
||||
state.searchState.apply(entry)
|
||||
dispatch("select")
|
||||
}
|
||||
</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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue