forked from MapComplete/MapComplete
		
	Search: use 'searchbar' where applicable, refactoring
This commit is contained in:
		
							parent
							
								
									bcd53405c8
								
							
						
					
					
						commit
						9b8c300e77
					
				
					 28 changed files with 403 additions and 582 deletions
				
			
		|  | @ -33,7 +33,7 @@ | |||
|         rightOffset="inset-y-0 right-0" | ||||
|         bind:hidden={hidden}> | ||||
| 
 | ||||
|   <div class="normal-background h-screen"> | ||||
|   <div class="low-interaction h-screen"> | ||||
|     <div class="h-full" style={`padding-top: ${height}px`}> | ||||
|       <div class="flex flex-col h-full overflow-y-auto"> | ||||
|         <slot /> | ||||
|  |  | |||
|  | @ -15,18 +15,35 @@ | |||
|   $: value.set(_value) | ||||
| 
 | ||||
|   const dispatch = createEventDispatcher<{ search }>() | ||||
|   export let placeholder: Translation =  Translations.t.general.search.search | ||||
|   export let placeholder: Translation = Translations.t.general.search.search | ||||
|   export let isFocused: UIEventSource<boolean> = undefined | ||||
|   let inputElement: HTMLInputElement | ||||
| 
 | ||||
|   isFocused?.addCallback(focussed => { | ||||
|     if (focussed) { | ||||
|       requestAnimationFrame(() => { | ||||
|         if (document.activeElement !== inputElement) { | ||||
|           inputElement.focus() | ||||
|           inputElement.select() | ||||
|         } | ||||
|       }) | ||||
|     } | ||||
|   }) | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| 
 | ||||
| <form | ||||
|   class="flex justify-center" | ||||
|   class="w-full" | ||||
|   on:submit|preventDefault={() => dispatch("search")} | ||||
| > | ||||
|   <label | ||||
|     class="neutral-label my-2 flex w-full items-center rounded-full border-2 border-black sm:w-1/2  box-shadow" | ||||
|     class="neutral-label normal-background flex w-full items-center rounded-full border-2 border-black box-shadow" | ||||
|   > | ||||
|     <input | ||||
|       bind:this={inputElement} | ||||
|       on:focus={() => {isFocused?.setData(true)}} | ||||
|       on:blur={() => {isFocused?.setData(false)}} | ||||
|       type="search" | ||||
|       style=" --tw-ring-color: rgb(0 0 0 / 0) !important;" | ||||
|       class="ml-4 pl-1 w-full outline-none border-none" | ||||
|  | @ -35,9 +52,9 @@ | |||
|         }} | ||||
|       bind:value={_value} | ||||
|       use:set_placeholder={placeholder} | ||||
|       use:ariaLabel={Translations.t.general.search.search} | ||||
|       use:ariaLabel={placeholder} | ||||
|     /> | ||||
|     <SearchIcon aria-hidden="true" class="h-8 w-8 mx-2" /> | ||||
|     <SearchIcon aria-hidden="true" class="h-8 w-8 mx-3" /> | ||||
| 
 | ||||
|   </label> | ||||
| </form> | ||||
|  |  | |||
							
								
								
									
										55
									
								
								src/UI/Base/SidebarUnit.svelte
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/UI/Base/SidebarUnit.svelte
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,55 @@ | |||
| <div class="sidebar-unit"> | ||||
|   <slot/> | ||||
| </div> | ||||
| 
 | ||||
| <style> | ||||
|     :global(.sidebar-unit) { | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         row-gap: 0.25rem; | ||||
|         background: var(--background-color); | ||||
|         padding: 0.5rem; | ||||
|         border-radius: 0.5rem; | ||||
|     } | ||||
| 
 | ||||
|     :global(.sidebar-unit > h3) { | ||||
|         margin-top: 0; | ||||
|         margin-bottom: 0.5rem; | ||||
|         padding: 0.25rem; | ||||
|     } | ||||
| 
 | ||||
|     :global(.sidebar-button svg, .sidebar-button img) { | ||||
|         width: 1.5rem; | ||||
|         height: 1.5rem; | ||||
|         margin-right: 0.5rem; | ||||
|         flex-shrink: 0; | ||||
|     } | ||||
| 
 | ||||
|     :global(.sidebar-button .weblate-link > svg) { | ||||
|         width: 0.75rem; | ||||
|         height: 0.75rem; | ||||
|         flex-shrink: 0; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     :global(.sidebar-button, .sidebar-unit > a) { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         border-radius: 0.25rem !important; | ||||
|         padding: 0.4rem 0.75rem !important; | ||||
|         text-decoration: none !important; | ||||
|         width: 100%; | ||||
|         text-align: start; | ||||
|     } | ||||
| 
 | ||||
|     :global(.sidebar-button > svg , .sidebar-button > img, .sidebar-unit > a img, .sidebar-unit > a svg) { | ||||
|         margin-right: 0.5rem; | ||||
|         flex-shrink: 0; | ||||
|     } | ||||
| 
 | ||||
|     :global(.sidebar-button:hover, .sidebar-unit > a:hover) { | ||||
|         background: var(--low-interaction-background) !important; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| </style> | ||||
|  | @ -11,7 +11,7 @@ | |||
|   import CommunityIndexView from "./CommunityIndexView.svelte" | ||||
|   import Community from "../../assets/svg/Community.svelte" | ||||
|   import LoginToggle from "../Base/LoginToggle.svelte" | ||||
|   import { CloseButton, Sidebar } from "flowbite-svelte" | ||||
|   import { CloseButton } from "flowbite-svelte" | ||||
|   import HotkeyTable from "./HotkeyTable.svelte" | ||||
|   import { Utils } from "../../Utils" | ||||
|   import Constants from "../../Models/Constants" | ||||
|  | @ -24,7 +24,6 @@ | |||
|   import MapillaryLink from "./MapillaryLink.svelte" | ||||
|   import Github from "../../assets/svg/Github.svelte" | ||||
|   import Bug from "../../assets/svg/Bug.svelte" | ||||
|   import Add from "../../assets/svg/Add.svelte" | ||||
|   import CopyrightPanel from "./CopyrightPanel.svelte" | ||||
|   import CopyrightAllIcons from "./CopyrightAllIcons.svelte" | ||||
|   import LanguagePicker from "../InputElement/LanguagePicker.svelte" | ||||
|  | @ -49,6 +48,7 @@ | |||
|   import Copyright from "../../assets/svg/Copyright.svelte" | ||||
|   import Pencil from "../../assets/svg/Pencil.svelte" | ||||
|   import Squares2x2 from "@babeard/svelte-heroicons/mini/Squares2x2" | ||||
|   import SidebarUnit from "../Base/SidebarUnit.svelte" | ||||
| 
 | ||||
|   export let state: ThemeViewState | ||||
|   let userdetails = state.osmConnection.userDetails | ||||
|  | @ -83,7 +83,7 @@ | |||
| 
 | ||||
| 
 | ||||
|   <!-- User related: avatar, settings, favourits, logout --> | ||||
|   <div class="sidebar-unit"> | ||||
|   <SidebarUnit> | ||||
|     <LoginToggle {state}> | ||||
|       <LoginButton osmConnection={state.osmConnection} slot="not-logged-in"></LoginButton> | ||||
|       <div class="flex gap-x-4 items-center"> | ||||
|  | @ -153,11 +153,11 @@ | |||
| 
 | ||||
|     <LanguagePicker /> | ||||
| 
 | ||||
|   </div> | ||||
|   </SidebarUnit> | ||||
| 
 | ||||
| 
 | ||||
|   <!-- Theme related: documentation links, download, ... --> | ||||
|   <div class="sidebar-unit"> | ||||
|   <SidebarUnit> | ||||
|     <h3> | ||||
|       <Tr t={t.aboutCurrentThemeTitle} /> | ||||
|     </h3> | ||||
|  | @ -218,11 +218,11 @@ | |||
|         <Tr t={Translations.t.general.attribution.openOsmcha.Subs({ theme: layout.title })} /> | ||||
|       </a> | ||||
|     {/if} | ||||
|   </div> | ||||
|   </SidebarUnit> | ||||
| 
 | ||||
| 
 | ||||
|   <!-- Other links and tools for the given location: open iD/JOSM; community index, ... --> | ||||
|   <div class="sidebar-unit"> | ||||
|   <SidebarUnit> | ||||
| 
 | ||||
|     <h3> | ||||
|       <Tr t={t.moreUtilsTitle} /> | ||||
|  | @ -244,11 +244,11 @@ | |||
|       <MapillaryLink large={false} mapProperties={state.mapProperties} /> | ||||
|     </If> | ||||
| 
 | ||||
|   </div> | ||||
|   </SidebarUnit> | ||||
| 
 | ||||
| 
 | ||||
|   <!-- About MC: various outward links, legal info, ... --> | ||||
|   <div class="sidebar-unit"> | ||||
|   <SidebarUnit> | ||||
| 
 | ||||
|     <h3> | ||||
|       <Tr t={Translations.t.general.menu.aboutMapComplete} /> | ||||
|  | @ -325,58 +325,8 @@ | |||
|     <div class="subtle self-end"> | ||||
|       {Constants.vNumber} | ||||
|     </div> | ||||
|   </div> | ||||
|   </SidebarUnit> | ||||
| </div> | ||||
| 
 | ||||
| 
 | ||||
| <style> | ||||
|     :global(.sidebar-unit) { | ||||
|         display: flex; | ||||
|         flex-direction: column; | ||||
|         row-gap: 0.25rem; | ||||
|         background: var(--background-color); | ||||
|         padding: 0.5rem; | ||||
|         border-radius: 0.5rem; | ||||
|     } | ||||
| 
 | ||||
|     :global(.sidebar-unit > h3) { | ||||
|         margin-top: 0; | ||||
|         margin-bottom: 0.5rem; | ||||
|         padding: 0.25rem; | ||||
|     } | ||||
| 
 | ||||
|     :global(.sidebar-button svg, .sidebar-button img) { | ||||
|         width: 1.5rem; | ||||
|         height: 1.5rem; | ||||
|         margin-right: 0.5rem; | ||||
|         flex-shrink: 0; | ||||
|     } | ||||
| 
 | ||||
|     :global(.sidebar-button .weblate-link > svg) { | ||||
|         width: 0.75rem; | ||||
|         height: 0.75rem; | ||||
|         flex-shrink: 0; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     :global(.sidebar-button, .sidebar-unit > a) { | ||||
|         display: flex; | ||||
|         align-items: center; | ||||
|         border-radius: 0.25rem !important; | ||||
|         padding: 0.4rem 0.75rem !important; | ||||
|         text-decoration: none !important; | ||||
|         width: 100%; | ||||
|         text-align: start; | ||||
|     } | ||||
| 
 | ||||
|     :global(.sidebar-button > svg , .sidebar-button > img, .sidebar-unit a > img, .sidebar-unit > a svg) { | ||||
|         margin-right: 0.5rem; | ||||
|         flex-shrink: 0; | ||||
|     } | ||||
| 
 | ||||
|     :global(.sidebar-button:hover, .sidebar-unit > a:hover) { | ||||
|         background: var(--low-interaction-background) !important; | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,59 +0,0 @@ | |||
| <script lang="ts"> | ||||
|   import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import Translations from "../i18n/Translations" | ||||
|   import Loading from "../Base/Loading.svelte" | ||||
|   import Hotkeys from "../Base/Hotkeys" | ||||
|   import { createEventDispatcher, onDestroy } from "svelte" | ||||
|   import { placeholder } from "../../Utils/placeholder" | ||||
|   import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid" | ||||
|   import { ariaLabel } from "../../Utils/ariaLabel" | ||||
|   import { Translation } from "../i18n/Translation" | ||||
| 
 | ||||
|   const dispatch = createEventDispatcher<{ search: string }>() | ||||
| 
 | ||||
|   export let searchValue: UIEventSource<string> | ||||
|   export let placeholderText: Translation = Translations.t.general.search.search | ||||
|   export let feedback = new UIEventSource<string>(undefined) | ||||
| 
 | ||||
|   let isRunning: boolean = false | ||||
| 
 | ||||
|   let inputElement: HTMLInputElement | ||||
| 
 | ||||
|   function _performSearch() { | ||||
|     dispatch("search", searchValue.data) | ||||
|   } | ||||
| </script> | ||||
| 
 | ||||
| <div class="normal-background flex justify-between rounded-full"> | ||||
|   <form class="flex w-full flex-wrap" on:submit|preventDefault={() => {}}> | ||||
|     {#if isRunning} | ||||
|       <Loading>{Translations.t.general.search.searching}</Loading> | ||||
|     {:else} | ||||
|       <div class="flex w-full rounded-full border border-gray-300"> | ||||
|         <input | ||||
|           type="search" | ||||
|           class="mx-2 w-full outline-none" | ||||
|           bind:this={inputElement} | ||||
|           on:keypress={(keypr) => { | ||||
|             feedback.set(undefined) | ||||
|             return keypr.key === "Enter" ? _performSearch() : undefined | ||||
|           }} | ||||
|           bind:value={$searchValue} | ||||
|           use:placeholder={placeholderText} | ||||
|           use:ariaLabel={Translations.t.general.search.search} | ||||
|         /> | ||||
|         <SearchIcon | ||||
|           aria-hidden="true" | ||||
|           class="h-6 w-6 self-end" | ||||
|           on:click={(event) => _performSearch()} | ||||
|         /> | ||||
|       </div> | ||||
|       {#if $feedback !== undefined} | ||||
|         <!-- The feedback is _always_ shown for screenreaders and to make sure that the searchfield can still be selected by tabbing--> | ||||
|         <div class="alert" role="alert" aria-live="assertive"> | ||||
|           {$feedback} | ||||
|         </div> | ||||
|       {/if} | ||||
|     {/if} | ||||
|   </form> | ||||
| </div> | ||||
|  | @ -8,11 +8,11 @@ | |||
|   import { ImmutableStore, Store, Stores, UIEventSource } from "../../../Logic/UIEventSource" | ||||
|   import Wikidata, { WikidataResponse } from "../../../Logic/Web/Wikidata" | ||||
|   import Locale from "../../i18n/Locale" | ||||
|   import SearchField from "../../BigComponents/SearchField.svelte" | ||||
|   import Loading from "../../Base/Loading.svelte" | ||||
|   import Wikidatapreview from "../../Wikipedia/Wikidatapreview.svelte" | ||||
|   import { Utils } from "../../../Utils" | ||||
|   import WikidataValidator from "../Validators/WikidataValidator" | ||||
|   import Searchbar from "../../Base/Searchbar.svelte" | ||||
| 
 | ||||
|   const t = Translations.t.general.wikipedia | ||||
| 
 | ||||
|  | @ -89,7 +89,7 @@ | |||
| </h3> | ||||
| 
 | ||||
| <form> | ||||
|   <SearchField {searchValue} placeholderText={placeholder} /> | ||||
|   <Searchbar value={searchValue} {placeholder} /> | ||||
| 
 | ||||
|   {#if $searchValue.trim().length === 0} | ||||
|     <Tr cls="w-full flex justify-center p-4" t={t.doSearch} /> | ||||
|  |  | |||
|  | @ -12,12 +12,10 @@ | |||
|   import { GeoOperations } from "../../Logic/GeoOperations" | ||||
|   import LocationInput from "../InputElement/Helpers/LocationInput.svelte" | ||||
|   import OpenBackgroundSelectorButton from "../BigComponents/OpenBackgroundSelectorButton.svelte" | ||||
|   import Geosearch from "../Search/Geosearch.svelte" | ||||
|   import If from "../Base/If.svelte" | ||||
|   import Constants from "../../Models/Constants" | ||||
|   import LoginToggle from "../Base/LoginToggle.svelte" | ||||
|   import AccordionSingle from "../Flowbite/AccordionSingle.svelte" | ||||
|   import BackButton from "../Base/BackButton.svelte" | ||||
|   import ChevronLeft from "@babeard/svelte-heroicons/solid/ChevronLeft" | ||||
|   import ThemeViewState from "../../Models/ThemeViewState" | ||||
|   import Icon from "../Map/Icon.svelte" | ||||
|  | @ -104,7 +102,7 @@ | |||
|             </div> | ||||
| 
 | ||||
|             {#if $reason.includeSearch} | ||||
|               <Geosearch {state}/> | ||||
|              <!-- TODO --> | ||||
|             {/if} | ||||
| 
 | ||||
|             <div class="flex flex-wrap"> | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ | |||
| {#if loading} | ||||
|   <Loading /> | ||||
| {:else } | ||||
|   <div class="badge"> | ||||
|   <div class="badge button-unstyled w-fit"> | ||||
|     <FilterOption option={$option} /> | ||||
|     <button on:click={() => clear()}> | ||||
|       <XMarkIcon class="w-5 h-5 pl-1" color="gray" /> | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
|   import { default as ActiveFilterSvelte } from "./ActiveFilter.svelte" | ||||
|   import type { ActiveFilter } from "../../Logic/State/LayerState" | ||||
|   import Loading from "../Base/Loading.svelte" | ||||
|   import SidebarUnit from "../Base/SidebarUnit.svelte" | ||||
| 
 | ||||
|   export let activeFilters: ActiveFilter[] | ||||
|   let loading = false | ||||
|  | @ -18,20 +19,25 @@ | |||
|   } | ||||
| </script> | ||||
| {#if activeFilters.length > 0} | ||||
|   <div class="flex flex-wrap gap-y-1 gap-x-1 button-unstyled"> | ||||
|   <SidebarUnit> | ||||
|     <h3>Active filters</h3> | ||||
| 
 | ||||
|     {#if loading} | ||||
|       <Loading /> | ||||
|     {:else} | ||||
|       {#each activeFilters as activeFilter (activeFilter)} | ||||
|         <ActiveFilterSvelte {activeFilter} /> | ||||
|       {/each} | ||||
|       <div class="flex flex-wrap gap-x-1 gap-y-2"> | ||||
| 
 | ||||
|       <button class="as-link subtle" on:click={() => clear()}> | ||||
|       {#each activeFilters as activeFilter (activeFilter)} | ||||
|         <div> | ||||
|         <ActiveFilterSvelte {activeFilter} /> | ||||
|         </div> | ||||
|       {/each} | ||||
|       </div> | ||||
| 
 | ||||
|       <button class="as-link subtle self-end" on:click={() => clear()} style="margin-right: 0.75rem"> | ||||
|         Clear filters | ||||
|       </button> | ||||
|     {/if} | ||||
|   </div> | ||||
|   </SidebarUnit> | ||||
| 
 | ||||
| {/if} | ||||
|  |  | |||
|  | @ -1,95 +0,0 @@ | |||
| <script lang="ts"> | ||||
|   import { UIEventSource } from "../../Logic/UIEventSource" | ||||
|   import Translations from "../i18n/Translations" | ||||
|   import Loading from "../Base/Loading.svelte" | ||||
|   import Hotkeys from "../Base/Hotkeys" | ||||
|   import { createEventDispatcher, onDestroy } from "svelte" | ||||
|   import { placeholder } from "../../Utils/placeholder" | ||||
|   import { SearchIcon } from "@rgossiaux/svelte-heroicons/solid" | ||||
|   import { ariaLabel } from "../../Utils/ariaLabel" | ||||
| 
 | ||||
|   import { focusWithArrows } from "../../Utils/focusWithArrows" | ||||
|   import ThemeViewState from "../../Models/ThemeViewState" | ||||
| 
 | ||||
|   export let state: ThemeViewState | ||||
|   export let searchContents: UIEventSource<string> = new UIEventSource<string>("") | ||||
| 
 | ||||
|   function performSearch() { | ||||
|     state.searchState.performSearch() | ||||
|   } | ||||
| 
 | ||||
|   let isRunning = state.searchState.isSearching | ||||
| 
 | ||||
|   let inputElement: HTMLInputElement | ||||
| 
 | ||||
|   export let isFocused = new UIEventSource(false) | ||||
| 
 | ||||
|   function focusOnSearch() { | ||||
|     requestAnimationFrame(() => { | ||||
|       inputElement?.focus() | ||||
|       inputElement?.select() | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   Hotkeys.RegisterHotkey({ ctrl: "F" }, Translations.t.hotkeyDocumentation.selectSearch, () => { | ||||
|     state.searchState.feedback.set(undefined) | ||||
|     focusOnSearch() | ||||
|   }) | ||||
| 
 | ||||
|   const dispatch = createEventDispatcher<{ searchCompleted; searchIsValid: boolean }>() | ||||
|   $: { | ||||
|     if (!$searchContents?.trim()) { | ||||
|       dispatch("searchIsValid", false) | ||||
|     } else { | ||||
|       dispatch("searchIsValid", true) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   let geosearch: HTMLDivElement | ||||
| 
 | ||||
|   function checkFocus() { | ||||
|     window.requestAnimationFrame(() => { | ||||
|       if (geosearch?.contains(document.activeElement)) { | ||||
|         return | ||||
|       } | ||||
|       isFocused.setData(false) | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   document.addEventListener("focus", () => { | ||||
|     checkFocus() | ||||
|   }, true /* use 'capturing' instead of bubbling, needed for focus-events*/) | ||||
| 
 | ||||
| 
 | ||||
| </script> | ||||
| 
 | ||||
| <div bind:this={geosearch} use:focusWithArrows={"searchresult"}> | ||||
| 
 | ||||
|   <div class="normal-background flex justify-between rounded-full pl-2 w-full"> | ||||
|     <form class="flex w-full flex-wrap"> | ||||
|       {#if $isRunning} | ||||
|         <Loading>{Translations.t.general.search.searching}</Loading> | ||||
|       {:else} | ||||
|         <input | ||||
|           type="search" | ||||
|           class="w-full outline-none" | ||||
|           bind:this={inputElement} | ||||
|           on:keypress={(keypr) => { | ||||
|             if(keypr.key === "Enter"){ | ||||
|               performSearch() | ||||
|               keypr.preventDefault() | ||||
|             } | ||||
|             return undefined | ||||
|         }} | ||||
|           on:focus={() => {isFocused.setData(true)}} | ||||
|           on:blur={() => {checkFocus()}} | ||||
|           bind:value={$searchContents} | ||||
|           use:placeholder={Translations.t.general.search.search} | ||||
|           use:ariaLabel={Translations.t.general.search.search} | ||||
|         /> | ||||
| 
 | ||||
|       {/if} | ||||
|     </form> | ||||
|     <SearchIcon aria-hidden="true" class="h-6 w-6 self-end" on:click={() => performSearch()} /> | ||||
|   </div> | ||||
| </div> | ||||
|  | @ -13,87 +13,103 @@ | |||
|   import ThemeViewState from "../../Models/ThemeViewState" | ||||
|   import FilterResult from "./FilterResult.svelte" | ||||
|   import ThemeResult from "./ThemeResult.svelte" | ||||
|   import SidebarUnit from "../Base/SidebarUnit.svelte" | ||||
| 
 | ||||
|   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.searchState.recentlySearched.seenThisSession | ||||
|   let recentThemes = state.userRelatedState.recentlyVisitedThemes.mapD(thms => thms.filter(th => th !== state.layout.id).slice(0, 3)) | ||||
|   let recentThemes = state.userRelatedState.recentlyVisitedThemes.mapD(thms => thms.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 themeResults = state.searchState.themeSuggestions | ||||
| 
 | ||||
| </script> | ||||
| <div class="p-4"> | ||||
| <div class="p-4 low-interaction flex gap-y-2 flex-col"> | ||||
| 
 | ||||
|   <h3>Search results</h3> | ||||
| 
 | ||||
|   <ActiveFilters activeFilters={$activeFilters} /> | ||||
| 
 | ||||
|   {#if $filterResults.length > 0} | ||||
|     <h3>Pick a filter below</h3> | ||||
|   {#if $searchTerm.length > 0 && $filterResults.length > 0} | ||||
|     <SidebarUnit> | ||||
| 
 | ||||
|     <div class="flex flex-wrap"> | ||||
|       {#each $filterResults as filterResult (filterResult)} | ||||
|         <FilterResult {state} entry={filterResult} /> | ||||
|       {/each} | ||||
|     </div> | ||||
|       <h3>Pick a filter below</h3> | ||||
| 
 | ||||
|       <div class="flex flex-wrap"> | ||||
|         {#each $filterResults as filterResult (filterResult)} | ||||
|           <FilterResult {state} entry={filterResult} /> | ||||
|         {/each} | ||||
|       </div> | ||||
|     </SidebarUnit> | ||||
|   {/if} | ||||
| 
 | ||||
|   <!-- Actual search results (or ""loading"", or ""no results"")--> | ||||
|   {#if $searchTerm.length > 0} | ||||
|     <h3>Locations</h3> | ||||
|   {/if} | ||||
|   {#if $searchTerm.length > 0 && $results === undefined} | ||||
|     <div class="flex justify-center m-4 my-8"> | ||||
|       <Loading /> | ||||
|     </div> | ||||
|   {:else if $results?.length > 0} | ||||
|     {#each $results as entry (entry)} | ||||
|       <SearchResultSvelte on:select {entry} {state} /> | ||||
|     {/each} | ||||
|   {:else if $searchTerm.length > 0 || $recentlySeen?.length > 0 || $recentThemes?.length > 0} | ||||
|     <div class="flex flex-col gap-y-8" | ||||
|          tabindex="-1"> | ||||
|       {#if $searchTerm.length > 0} | ||||
|     <SidebarUnit> | ||||
| 
 | ||||
|       <h3>Locations</h3> | ||||
| 
 | ||||
|       {#if $isSearching} | ||||
|         <div class="flex justify-center m-4 my-8"> | ||||
|           <Loading /> | ||||
|         </div> | ||||
|       {/if} | ||||
| 
 | ||||
|       {#if $results?.length > 0} | ||||
|         {#each $results as entry (entry)} | ||||
|           <SearchResultSvelte on:select {entry} {state} /> | ||||
|         {/each} | ||||
| 
 | ||||
|       {:else if !$isSearching} | ||||
|         <b class="flex justify-center p-4"> | ||||
|           <Tr t={Translations.t.general.search.nothingFor.Subs({term: $searchTerm})} /> | ||||
|         </b> | ||||
|       {/if} | ||||
|     </SidebarUnit> | ||||
| 
 | ||||
|       {#if $recentlySeen?.length > 0} | ||||
|         <div> | ||||
|           <h3 class="m-2"> | ||||
|             <Tr t={Translations.t.general.search.recents} /> | ||||
|           </h3> | ||||
|           {#each $recentlySeen as entry} | ||||
|             <SearchResultSvelte {entry} {state} on:select /> | ||||
|           {/each} | ||||
|         </div> | ||||
|       {/if} | ||||
| 
 | ||||
|       {#if $recentThemes?.length > 0 && $allowOtherThemes} | ||||
|         <div> | ||||
|           <h3 class="m-2"> | ||||
|             <Tr t={Translations.t.general.search.recentThemes} /> | ||||
|           </h3> | ||||
|           {#each $recentThemes as themeId (themeId)} | ||||
|             <SearchResultSvelte | ||||
|               entry={{payload: MoreScreen.officialThemesById.get(themeId), osm_id: themeId, category: "theme"}} | ||||
|               {state} | ||||
|               on:select /> | ||||
|           {/each} | ||||
|         </div> | ||||
|       {/if} | ||||
|     </div> | ||||
|   {/if} | ||||
| 
 | ||||
| 
 | ||||
|   <!-- Other maps which match the search term--> | ||||
|   {#if $themeResults.length > 0} | ||||
|     <h3> | ||||
|       Other maps | ||||
|     </h3> | ||||
|     {#each $themeResults as entry} | ||||
|       <ThemeResult {state} {entry} /> | ||||
|     {/each} | ||||
|     <SidebarUnit> | ||||
|       <h3> | ||||
|         Other maps | ||||
|       </h3> | ||||
|       {#each $themeResults as entry} | ||||
|         <ThemeResult {entry} /> | ||||
|       {/each} | ||||
|     </SidebarUnit> | ||||
|   {/if} | ||||
| 
 | ||||
| 
 | ||||
|   {#if $searchTerm.length == 0 && $recentlySeen?.length > 0} | ||||
|     <SidebarUnit> | ||||
|       <h3 class="m-2"> | ||||
|         <Tr t={Translations.t.general.search.recents} /> | ||||
|       </h3> | ||||
|       {#each $recentlySeen as entry} | ||||
|         <SearchResultSvelte {entry} {state} on:select /> | ||||
|       {/each} | ||||
|     </SidebarUnit> | ||||
|   {/if} | ||||
| 
 | ||||
|   {#if $searchTerm.length === 0 && $recentThemes?.length > 0 && $allowOtherThemes} | ||||
|     <SidebarUnit> | ||||
|       <h3 class="m-2"> | ||||
|         <Tr t={Translations.t.general.search.recentThemes} /> | ||||
|       </h3> | ||||
|       {#each $recentThemes as themeId (themeId)} | ||||
|         <SearchResultSvelte | ||||
|           entry={{payload: MoreScreen.officialThemesById.get(themeId), osm_id: themeId, category: "theme"}} | ||||
|           {state} | ||||
|           on:select /> | ||||
|       {/each} | ||||
|     </SidebarUnit> | ||||
|   {/if} | ||||
| 
 | ||||
| 
 | ||||
| </div> | ||||
|  |  | |||
|  | @ -11,7 +11,6 @@ | |||
|   import LayerConfig from "../Models/ThemeConfig/LayerConfig" | ||||
|   import ThemeViewState from "../Models/ThemeViewState" | ||||
|   import type { MapProperties } from "../Models/MapProperties" | ||||
|   import Geosearch from "./Search/Geosearch.svelte" | ||||
|   import Translations from "./i18n/Translations" | ||||
|   import { MenuIcon } from "@rgossiaux/svelte-heroicons/solid" | ||||
|   import Tr from "./Base/Tr.svelte" | ||||
|  | @ -47,6 +46,7 @@ | |||
|   import SearchResults from "./Search/SearchResults.svelte" | ||||
|   import { CloseButton } from "flowbite-svelte" | ||||
|   import Hash from "../Logic/Web/Hash" | ||||
|   import Searchbar from "./Base/Searchbar.svelte" | ||||
| 
 | ||||
|   export let state: ThemeViewState | ||||
|   let layout = state.layout | ||||
|  | @ -161,10 +161,25 @@ | |||
| </script> | ||||
| 
 | ||||
| <main> | ||||
|   <!-- Main map --> | ||||
|   <div class="absolute top-0 left-0 h-screen w-screen overflow-hidden"> | ||||
|     <MaplibreMap map={maplibremap} mapProperties={mapproperties} autorecovery={true} /> | ||||
|   </div> | ||||
| 
 | ||||
|   <LoginToggle ignoreLoading={true} {state}> | ||||
|     {#if ($showCrosshair === "yes" && $currentZoom >= 17) || $showCrosshair === "always" || $visualFeedback} | ||||
|       <!-- Don't use h-full: h-full does _not_ include the area under the URL-bar, which offsets the crosshair a bit --> | ||||
|       <div | ||||
|         class="pointer-events-none absolute top-0 left-0 flex w-full items-center justify-center" | ||||
|         style="height: 100vh" | ||||
|       > | ||||
|         <Cross class="h-4 w-4" /> | ||||
|       </div> | ||||
|     {/if} | ||||
|     <!-- Add in an empty container to remove error messages if login fails --> | ||||
|     <svelte:fragment slot="error" /> | ||||
|   </LoginToggle> | ||||
| 
 | ||||
|   {#if $visualFeedback} | ||||
|     <div | ||||
|       class="pointer-events-none absolute top-0 left-0 flex h-screen w-screen items-center justify-center overflow-hidden" | ||||
|  | @ -177,108 +192,6 @@ | |||
|     </div> | ||||
|   {/if} | ||||
| 
 | ||||
|   <div class="pointer-events-none absolute top-0 left-0 w-full"> | ||||
|     <!-- Top components --> | ||||
| 
 | ||||
|     <div | ||||
|       class="flex bg-black-light-transparent pointer-events-auto items-center justify-between px-4 py-1 flex-wrap-reverse"> | ||||
|       <!-- Top bar with tools --> | ||||
|       <div class="flex items-center"> | ||||
| 
 | ||||
|         <MapControlButton | ||||
|           cls="m-0.5 p-0.5 sm:p-1" | ||||
|           arialabel={Translations.t.general.labels.menu} | ||||
|           on:click={() => {console.log("Opening...."); state.guistate.pageStates.menu.setData(true)}} | ||||
|           on:keydown={forwardEventToMap} | ||||
|         > | ||||
|           <MenuIcon class="h-6 w-6 cursor-pointer" /> | ||||
|         </MapControlButton> | ||||
| 
 | ||||
|         <MapControlButton | ||||
|           on:click={() => state.guistate.pageStates.about_theme.set(true)} | ||||
|           on:keydown={forwardEventToMap} | ||||
|         > | ||||
|           <div | ||||
|             class="m-0.5 mx-1 flex cursor-pointer items-center max-[480px]:w-full sm:mx-1 mr-2" | ||||
|           > | ||||
|             <Marker icons={layout.icon} size="h-6 w-6 shrink-0 mr-0.5 sm:mr-1 md:mr-2" /> | ||||
|             <b class="mr-1"> | ||||
|               <Tr t={layout.title} /> | ||||
|             </b> | ||||
|           </div> | ||||
|         </MapControlButton> | ||||
|       </div> | ||||
| 
 | ||||
|       {#if $debug && $hash} | ||||
|         <div class="alert"> | ||||
|           {$hash} | ||||
|         </div> | ||||
|       {/if} | ||||
| 
 | ||||
|       <If condition={state.featureSwitches.featureSwitchSearch}> | ||||
|         <div class="w-full sm:w-64 my-2 sm:mt-0"> | ||||
| 
 | ||||
|           <Geosearch | ||||
|             bounds={state.mapProperties.bounds} | ||||
|             on:searchCompleted={() => { | ||||
|             state.map?.data?.getCanvas()?.focus() | ||||
|           }} | ||||
|             perLayer={state.perLayer} | ||||
|             selectedElement={state.selectedElement} | ||||
|             geolocationState={state.geolocation.geolocationState} | ||||
|           /> | ||||
|         </div> | ||||
|       </If> | ||||
| 
 | ||||
|     </div> | ||||
| 
 | ||||
|     <div class="pointer-events-auto float-right mt-1 flex flex-col px-1 max-[480px]:w-full sm:m-2"> | ||||
|       <If condition={state.visualFeedback}> | ||||
|         {#if $selectedElement === undefined} | ||||
|           <div class="w-fit"> | ||||
|             <VisualFeedbackPanel {state} /> | ||||
|           </div> | ||||
|         {/if} | ||||
|       </If> | ||||
| 
 | ||||
|     </div> | ||||
|     <div class="float-left m-1 flex flex-col sm:mt-2"> | ||||
|       <If condition={state.featureSwitches.featureSwitchWelcomeMessage}> | ||||
| 
 | ||||
| 
 | ||||
|       </If> | ||||
|       {#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()} | ||||
|         <MapControlButton | ||||
|           on:click={() => { | ||||
|             state.selectCurrentView() | ||||
|           }} | ||||
|           on:keydown={forwardEventToMap} | ||||
|         > | ||||
|           <div class="h-8 w-8 cursor-pointer"> | ||||
|             <ToSvelte construct={() => currentViewLayer.defaultIcon()} /> | ||||
|           </div> | ||||
|         </MapControlButton> | ||||
|       {/if} | ||||
|       <ExtraLinkButton {state} /> | ||||
|       <UploadingImageCounter featureId="*" showThankYou={false} {state} /> | ||||
|       <PendingChangesIndicator {state} /> | ||||
|       <If condition={state.featureSwitchIsTesting}> | ||||
|         <div class="alert w-fit">Testmode</div> | ||||
|       </If> | ||||
|       {#if state.osmConnection.Backend().startsWith("https://master.apis.dev.openstreetmap.org")} | ||||
|         <div class="thanks">Testserver</div> | ||||
|       {/if} | ||||
|       <If condition={state.featureSwitches.featureSwitchFakeUser}> | ||||
|         <div class="alert w-fit">Faking a user (Testmode)</div> | ||||
|       </If> | ||||
|     </div> | ||||
|     <div class="flex w-full flex-col items-center justify-center"> | ||||
|       <!-- Flex and w-full are needed for the positioning --> | ||||
|       <!-- Centermessage --> | ||||
|       <StateIndicator {state} /> | ||||
|       <ReverseGeocoding {state} /> | ||||
|     </div> | ||||
|   </div> | ||||
| 
 | ||||
|   <div class="pointer-events-none absolute bottom-0 left-0 mb-4 w-screen"> | ||||
|     <!-- bottom controls --> | ||||
|  | @ -389,7 +302,7 @@ | |||
|   </div> | ||||
| 
 | ||||
| 
 | ||||
|   <DrawerRight shown={state.searchState.showSearchDrawer} }> | ||||
|   <DrawerRight shown={state.searchState.showSearchDrawer}> | ||||
|     <div class="relative"> | ||||
|       <div class="absolute right-0 top-0 "> | ||||
|         <div class="mr-4 mt-4"> | ||||
|  | @ -401,8 +314,11 @@ | |||
|   </DrawerRight> | ||||
| 
 | ||||
| 
 | ||||
|   <div class="pointer-events-none absolute top-0 left-0 w-full"> | ||||
| 
 | ||||
| 
 | ||||
|     <!-- Top components --> | ||||
|   <div class="pointer-events-none absolute top-0 left-0 w-full"> | ||||
| 
 | ||||
|     <div | ||||
|       id="top-bar" | ||||
|       class="flex bg-black-light-transparent pointer-events-auto items-center justify-between px-4 py-1 flex-wrap-reverse"> | ||||
|  | @ -412,7 +328,7 @@ | |||
|         <MapControlButton | ||||
|           cls="m-0.5 p-0.5 sm:p-1" | ||||
|           arialabel={Translations.t.general.labels.menu} | ||||
|           on:click={() => {state.guistate.menuIsOpened.setData(true)}} | ||||
|           on:click={() => {console.log("Opening...."); state.guistate.pageStates.menu.setData(true)}} | ||||
|           on:keydown={forwardEventToMap} | ||||
|         > | ||||
|           <MenuIcon class="h-6 w-6 cursor-pointer" /> | ||||
|  | @ -433,11 +349,15 @@ | |||
|         </MapControlButton> | ||||
|       </div> | ||||
| 
 | ||||
|       {#if $debug && $hash} | ||||
|         <div class="alert"> | ||||
|           {$hash} | ||||
|         </div> | ||||
|       {/if} | ||||
| 
 | ||||
|       <If condition={state.featureSwitches.featureSwitchSearch}> | ||||
|         <div class="w-full sm:w-80 md:w-96 my-2 sm:mt-0"> | ||||
|           <Geosearch {state} isFocused={state.searchState.searchIsFocused} | ||||
|                      searchContents={state.searchState.searchTerm} /> | ||||
|         <div class="w-full sm:w-64"> | ||||
|           <Searchbar value={state.searchState.searchTerm} isFocused={state.searchState.searchIsFocused}/> | ||||
|         </div> | ||||
|       </If> | ||||
| 
 | ||||
|  | @ -453,8 +373,9 @@ | |||
|       </If> | ||||
| 
 | ||||
|     </div> | ||||
|     <div class="float-left m-1 flex flex-col sm:mt-2"> | ||||
| 
 | ||||
|     <div class="float-left m-1 flex flex-col sm:mt-2"> | ||||
|       <!-- Current view tools --> | ||||
|       {#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()} | ||||
|         <MapControlButton | ||||
|           on:click={() => { | ||||
|  | @ -467,6 +388,7 @@ | |||
|           </div> | ||||
|         </MapControlButton> | ||||
|       {/if} | ||||
| 
 | ||||
|       <ExtraLinkButton {state} /> | ||||
|       <UploadingImageCounter featureId="*" showThankYou={false} {state} /> | ||||
|       <PendingChangesIndicator {state} /> | ||||
|  | @ -480,7 +402,8 @@ | |||
|         <div class="alert w-fit">Faking a user (Testmode)</div> | ||||
|       </If> | ||||
|     </div> | ||||
|     <div class="flex w-full flex-col items-center justify-center" > | ||||
| 
 | ||||
|     <div class="flex w-full flex-col items-center justify-center"> | ||||
|       <!-- Flex and w-full are needed for the positioning --> | ||||
|       <!-- Centermessage --> | ||||
|       <StateIndicator {state} /> | ||||
|  | @ -489,20 +412,6 @@ | |||
|   </div> | ||||
| 
 | ||||
| 
 | ||||
|   <LoginToggle ignoreLoading={true} {state}> | ||||
|     {#if ($showCrosshair === "yes" && $currentZoom >= 17) || $showCrosshair === "always" || $visualFeedback} | ||||
|       <!-- Don't use h-full: h-full does _not_ include the area under the URL-bar, which offsets the crosshair a bit --> | ||||
|       <div | ||||
|         class="pointer-events-none absolute top-0 left-0 flex w-full items-center justify-center" | ||||
|         style="height: 100vh" | ||||
|       > | ||||
|         <Cross class="h-4 w-4" /> | ||||
|       </div> | ||||
|     {/if} | ||||
|     <!-- Add in an empty container to remove error messages if login fails --> | ||||
|     <svelte:fragment slot="error" /> | ||||
|   </LoginToggle> | ||||
| 
 | ||||
|   <DrawerLeft shown={state.guistate.pageStates.menu}> | ||||
|     <div class="h-screen overflow-y-auto"> | ||||
|       <MenuDrawer onlyLink={true} {state} /> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue