From 9b8c300e77705e4fdafd74509afcb52d0ab6ff9c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 3 Sep 2024 01:14:08 +0200 Subject: [PATCH] Search: use 'searchbar' where applicable, refactoring --- assets/themes/postal_codes/postal_codes.json | 1 - .../street_lighting/street_lighting.json | 1 - assets/themes/uk_addresses/uk_addresses.json | 1 - langs/layers/en.json | 41 ++--- langs/layers/nl.json | 67 +++----- package.json | 2 +- public/css/index-tailwind-output.css | 41 ++--- src/Logic/Geocoding/CombinedSearcher.ts | 21 ++- src/Logic/Geocoding/CoordinateSearch.ts | 14 +- src/Logic/Geocoding/FilterSearch.ts | 42 ++++- src/Logic/Geocoding/GeocodingProvider.ts | 6 +- src/Logic/Geocoding/OpenStreetMapIdSearch.ts | 10 +- src/Logic/State/SearchState.ts | 41 +++-- src/Logic/UIEventSource.ts | 35 ++-- src/Models/ThemeViewState.ts | 31 ++-- src/UI/Base/DrawerRight.svelte | 2 +- src/UI/Base/Searchbar.svelte | 27 ++- src/UI/Base/SidebarUnit.svelte | 55 ++++++ src/UI/BigComponents/MenuDrawer.svelte | 70 ++------ src/UI/BigComponents/SearchField.svelte | 59 ------- .../InputElement/Helpers/WikidataInput.svelte | 4 +- src/UI/Popup/MoveWizard.svelte | 4 +- src/UI/Search/ActiveFilter.svelte | 2 +- src/UI/Search/ActiveFilters.svelte | 18 +- src/UI/Search/Geosearch.svelte | 95 ----------- src/UI/Search/SearchResults.svelte | 124 ++++++++------ src/UI/ThemeViewGUI.svelte | 159 ++++-------------- src/Utils.ts | 12 ++ 28 files changed, 403 insertions(+), 582 deletions(-) create mode 100644 src/UI/Base/SidebarUnit.svelte delete mode 100644 src/UI/BigComponents/SearchField.svelte delete mode 100644 src/UI/Search/Geosearch.svelte diff --git a/assets/themes/postal_codes/postal_codes.json b/assets/themes/postal_codes/postal_codes.json index 2cfbeda0f..5d61e4b37 100644 --- a/assets/themes/postal_codes/postal_codes.json +++ b/assets/themes/postal_codes/postal_codes.json @@ -166,7 +166,6 @@ } ], "allowMove": false - }, { "id": "town_hall", diff --git a/assets/themes/street_lighting/street_lighting.json b/assets/themes/street_lighting/street_lighting.json index 970246e67..b75266575 100644 --- a/assets/themes/street_lighting/street_lighting.json +++ b/assets/themes/street_lighting/street_lighting.json @@ -370,7 +370,6 @@ } ], "allowMove": false - } ] } diff --git a/assets/themes/uk_addresses/uk_addresses.json b/assets/themes/uk_addresses/uk_addresses.json index 52ea31313..85959a8e2 100644 --- a/assets/themes/uk_addresses/uk_addresses.json +++ b/assets/themes/uk_addresses/uk_addresses.json @@ -686,7 +686,6 @@ "enableImproveAccuraccy": true, "enableRelocation": false } - }, "named_streets" ], diff --git a/langs/layers/en.json b/langs/layers/en.json index 2776e87df..6a8af4c42 100644 --- a/langs/layers/en.json +++ b/langs/layers/en.json @@ -5161,6 +5161,18 @@ "14": { "then": "Thai dishes are served here" }, + "15": { + "then": "Mexican dishes are served here" + }, + "16": { + "then": "Japanese dishes are served here" + }, + "17": { + "then": "Chicken based dishes are served here" + }, + "18": { + "then": "Seafood dishes are served here" + }, "2": { "then": "Mainly serves pasta" }, @@ -5184,33 +5196,6 @@ }, "9": { "then": "French dishes are served here" - }, - "10": { - "then": "Chinese dishes are served here" - }, - "11": { - "then": "Greek dishes are served here" - }, - "12": { - "then": "Indian dishes are served here" - }, - "13": { - "then": "Turkish dishes are served here" - }, - "14": { - "then": "Thai dishes are served here" - }, - "15": { - "then": "Mexican dishes are served here" - }, - "16": { - "then": "Japanese dishes are served here" - }, - "17": { - "then": "Chicken based dishes are served here" - }, - "18": { - "then": "Seafood dishes are served here" } }, "question": "What kind of food is served here?", @@ -12260,4 +12245,4 @@ "render": "wind turbine" } } -} +} \ No newline at end of file diff --git a/langs/layers/nl.json b/langs/layers/nl.json index cf681f4db..f44308006 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -4028,6 +4028,19 @@ } } }, + "10": { + "options": { + "0": { + "question": "Geen voorkeur voor honden" + }, + "1": { + "question": "Honden toegelaten" + }, + "2": { + "question": "Geen honden toegelaten" + } + } + }, "2": { "options": { "0": { @@ -4084,19 +4097,6 @@ "question": "Gratis toegankelijk" } } - }, - "10": { - "options": { - "0": { - "question": "Geen voorkeur voor honden" - }, - "1": { - "question": "Honden toegelaten" - }, - "2": { - "question": "Geen honden toegelaten" - } - } } } }, @@ -4305,6 +4305,18 @@ "14": { "then": "Dit is een Thaïs restaurant" }, + "15": { + "then": "Dit is een mexicaans restaurant" + }, + "16": { + "then": "Dit is een japans restaurant" + }, + "17": { + "then": "Dit is een kiprestaurant" + }, + "18": { + "then": "Dit is een vis- en zeerestaurant" + }, "2": { "then": "Dit is een pastazaak" }, @@ -4328,33 +4340,6 @@ }, "9": { "then": "Dit is een Frans restaurant" - }, - "10": { - "then": "Dit is een Chinees restaurant" - }, - "11": { - "then": "Dit is een Grieks restaurant" - }, - "12": { - "then": "Dit is een Indisch restaurant" - }, - "13": { - "then": "Dit is een Turks restaurant (dat meer dan enkel kebab verkoopt)" - }, - "14": { - "then": "Dit is een Thaïs restaurant" - }, - "15": { - "then": "Dit is een mexicaans restaurant" - }, - "16": { - "then": "Dit is een japans restaurant" - }, - "17": { - "then": "Dit is een kiprestaurant" - }, - "18": { - "then": "Dit is een vis- en zeerestaurant" } }, "question": "Welk soort gerechten worden hier geserveerd?", @@ -9921,4 +9906,4 @@ "render": "windturbine" } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 4463da124..7961dd633 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.46.0", + "version": "0.47.0", "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index d66046233..360ad4a65 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -1160,14 +1160,14 @@ input[type="range"].range-lg::-moz-range-thumb { left: 0px; } -.right-1\/3 { - right: 33.333333%; -} - .right-0 { right: 0px; } +.right-1\/3 { + right: 33.333333%; +} + .right-10 { right: 2.5rem; } @@ -1413,6 +1413,11 @@ input[type="range"].range-lg::-moz-range-thumb { margin-right: auto; } +.mx-3 { + margin-left: 0.75rem; + margin-right: 0.75rem; +} + .my-4 { margin-top: 1rem; margin-bottom: 1rem; @@ -1474,6 +1479,14 @@ input[type="range"].range-lg::-moz-range-thumb { margin-bottom: 4rem; } +.mb-4 { + margin-bottom: 1rem; +} + +.ml-1 { + margin-left: 0.25rem; +} + .mr-0\.5 { margin-right: 0.125rem; } @@ -1490,14 +1503,6 @@ input[type="range"].range-lg::-moz-range-thumb { margin-top: 0.25rem; } -.mb-4 { - margin-bottom: 1rem; -} - -.ml-1 { - margin-left: 0.25rem; -} - .mt-2 { margin-top: 0.5rem; } @@ -1913,10 +1918,6 @@ input[type="range"].range-lg::-moz-range-thumb { max-height: 3rem; } -.max-h-screen { - max-height: 100vh; -} - .max-h-full { max-height: 100%; } @@ -8090,14 +8091,14 @@ svg.apply-fill path { order: 9999; } - .sm\:m-2 { - margin: 0.5rem; - } - .sm\:m-1 { margin: 0.25rem; } + .sm\:m-2 { + margin: 0.5rem; + } + .sm\:mx-1 { margin-left: 0.25rem; margin-right: 0.25rem; diff --git a/src/Logic/Geocoding/CombinedSearcher.ts b/src/Logic/Geocoding/CombinedSearcher.ts index 5bc364f51..9b6a422f1 100644 --- a/src/Logic/Geocoding/CombinedSearcher.ts +++ b/src/Logic/Geocoding/CombinedSearcher.ts @@ -1,12 +1,12 @@ -import GeocodingProvider, { SearchResult, GeocodingOptions } from "./GeocodingProvider" +import GeocodingProvider, { SearchResult, GeocodingOptions, GeocodeResult } from "./GeocodingProvider" import { Utils } from "../../Utils" import { Store, Stores } from "../UIEventSource" -export default class CombinedSearcher implements GeocodingProvider { - private _providers: ReadonlyArray - private _providersWithSuggest: ReadonlyArray +export default class CombinedSearcher implements GeocodingProvider { + private _providers: ReadonlyArray> + private _providersWithSuggest: ReadonlyArray> - constructor(...providers: ReadonlyArray) { + constructor(...providers: ReadonlyArray>) { this._providers = Utils.NoNull(providers) this._providersWithSuggest = this._providers.filter(pr => pr.suggest !== undefined) } @@ -17,10 +17,13 @@ export default class CombinedSearcher implements GeocodingProvider { * @param geocoded * @private */ - private merge(geocoded: SearchResult[][]): SearchResult[] { - const results: SearchResult[] = [] + public static merge(geocoded: GeocodeResult[][]): GeocodeResult[] { + const results: GeocodeResult[] = [] const seenIds = new Set() for (const geocodedElement of geocoded) { + if(geocodedElement === undefined){ + continue + } for (const entry of geocodedElement) { @@ -40,13 +43,13 @@ export default class CombinedSearcher implements GeocodingProvider { async search(query: string, options?: GeocodingOptions): Promise { const results = (await Promise.all(this._providers.map(pr => pr.search(query, options)))) - return this.merge(results) + return CombinedSearcher.merge(results) } suggest(query: string, options?: GeocodingOptions): Store { return Stores.concat( this._providersWithSuggest.map(pr => pr.suggest(query, options))) - .map(gcrss => this.merge(gcrss)) + .map(gcrss => CombinedSearcher.merge(gcrss)) } } diff --git a/src/Logic/Geocoding/CoordinateSearch.ts b/src/Logic/Geocoding/CoordinateSearch.ts index 256a56c36..a9c4127de 100644 --- a/src/Logic/Geocoding/CoordinateSearch.ts +++ b/src/Logic/Geocoding/CoordinateSearch.ts @@ -1,11 +1,11 @@ -import GeocodingProvider, { SearchResult } from "./GeocodingProvider" +import GeocodingProvider, { GeocodeResult } from "./GeocodingProvider" import { Utils } from "../../Utils" import { ImmutableStore, Store } from "../UIEventSource" /** * A simple search-class which interprets possible locations */ -export default class CoordinateSearch implements GeocodingProvider { +export default class CoordinateSearch implements GeocodingProvider { private static readonly latLonRegexes: ReadonlyArray = [ /^(-?[0-9]+\.[0-9]+)[ ,;/\\]+(-?[0-9]+\.[0-9]+)/, /lat[:=]? *['"]?(-?[0-9]+\.[0-9]+)['"]?[ ,;&]+lon[:=]? *['"]?(-?[0-9]+\.[0-9]+)['"]?/, @@ -59,9 +59,9 @@ export default class CoordinateSearch implements GeocodingProvider { * results.length // => 1 * results[0] // => {lat: -57.5802905, lon: -12.7202538, display_name: "lon: -12.7202538, lat: -57.5802905", "category": "coordinate", "source": "coordinate:latlon"} */ - private directSearch(query: string): SearchResult[] { + private directSearch(query: string): GeocodeResult[] { - const matches = Utils.NoNull(CoordinateSearch.latLonRegexes.map(r => query.match(r))).map(m => { + const matches = Utils.NoNull(CoordinateSearch.latLonRegexes.map(r => query.match(r))).map(m => { lat: Number(m[1]), lon: Number(m[2]), display_name: "lon: " + m[2] + ", lat: " + m[1], @@ -71,7 +71,7 @@ export default class CoordinateSearch implements GeocodingProvider { const matchesLonLat = Utils.NoNull(CoordinateSearch.lonLatRegexes.map(r => query.match(r))) - .map(m => { + .map(m => { lat: Number(m[2]), lon: Number(m[1]), display_name: "lon: " + m[1] + ", lat: " + m[2], @@ -81,11 +81,11 @@ export default class CoordinateSearch implements GeocodingProvider { return matches.concat(matchesLonLat) } - suggest(query: string): Store { + suggest(query: string): Store { return new ImmutableStore(this.directSearch(query)) } - async search (query: string): Promise { + async search (query: string): Promise { return this.directSearch(query) } diff --git a/src/Logic/Geocoding/FilterSearch.ts b/src/Logic/Geocoding/FilterSearch.ts index 76ec83389..35b312017 100644 --- a/src/Logic/Geocoding/FilterSearch.ts +++ b/src/Logic/Geocoding/FilterSearch.ts @@ -3,13 +3,15 @@ import GeocodingProvider, { FilterPayload, FilterResult, GeocodingOptions, Searc import { SpecialVisualizationState } from "../../UI/SpecialVisualization" import { Utils } from "../../Utils" import Locale from "../../UI/i18n/Locale" +import Constants from "../../Models/Constants" export default class FilterSearch implements GeocodingProvider { private readonly _state: SpecialVisualizationState + private readonly suggestions constructor(state: SpecialVisualizationState) { this._state = state - + this.suggestions = this.getSuggestions() } async search(query: string): Promise { @@ -34,7 +36,6 @@ export default class FilterSearch implements GeocodingProvider { } return query }).filter(q => q.length > 0) - console.log("Queries:",queries) const possibleFilters: FilterPayload[] = [] for (const layer of this._state.layout.layers) { if (!Array.isArray(layer.filters)) { @@ -55,9 +56,9 @@ export default class FilterSearch implements GeocodingProvider { terms = terms.map(t => Utils.simplifyStringForSearch(t)) terms.push(option.emoji) Utils.NoNullInplace(terms) - const distances = queries.flatMap(query => terms.map(entry => { + const distances = queries.flatMap(query => terms.map(entry => { const d = Utils.levenshteinDistance(query, entry.slice(0, query.length)) - console.log(query,"? +",terms, "=",d) + console.log(query, "? +", terms, "=", d) const dRelative = d / query.length return dRelative })) @@ -78,4 +79,37 @@ export default class FilterSearch implements GeocodingProvider { } + getSuggestions(): FilterPayload[] { + if (this.suggestions) { + // return this.suggestions + } + const result: FilterPayload[] = [] + for (const [id, filteredLayer] of this._state.layerState.filteredLayers) { + if (!Array.isArray(filteredLayer.layerDef.filters)) { + continue + } + if (Constants.priviliged_layers.indexOf(id) >= 0) { + continue + } + for (const filter of filteredLayer.layerDef.filters) { + const singleFilterResults: FilterPayload[] = [] + for (let i = 0; i < Math.min(filter.options.length, 5); i++) { + const option = filter.options[i] + if (option.osmTags === undefined) { + continue + } + singleFilterResults.push({ + option, + filter, + index: i, + layer: filteredLayer.layerDef + }) + } + Utils.shuffle(singleFilterResults) + result.push(...singleFilterResults.slice(0,3)) + } + } + Utils.shuffle(result) + return result.slice(0,6) + } } diff --git a/src/Logic/Geocoding/GeocodingProvider.ts b/src/Logic/Geocoding/GeocodingProvider.ts index 60aa84f95..819b28a90 100644 --- a/src/Logic/Geocoding/GeocodingProvider.ts +++ b/src/Logic/Geocoding/GeocodingProvider.ts @@ -56,16 +56,16 @@ export interface GeocodingOptions { } -export default interface GeocodingProvider { +export default interface GeocodingProvider { - search(query: string, options?: GeocodingOptions): Promise + search(query: string, options?: GeocodingOptions): Promise /** * @param query * @param options */ - suggest?(query: string, options?: GeocodingOptions): Store + suggest?(query: string, options?: GeocodingOptions): Store } export type ReverseGeocodingResult = Feature { + private static readonly regex = /((https?:\/\/)?(www.)?(osm|openstreetmap).org\/)?(n|node|w|way|r|relation)[/ ]?([0-9]+)/ private static readonly types: Readonly> = { "n":"node", @@ -45,7 +45,7 @@ export default class OpenStreetMapIdSearch implements GeocodingProvider { return undefined } - async search(query: string, options?: GeocodingOptions): Promise { + async search(query: string, options?: GeocodingOptions): Promise { const id = OpenStreetMapIdSearch.extractId(query) if (!id) { return [] @@ -74,7 +74,7 @@ export default class OpenStreetMapIdSearch implements GeocodingProvider { }] } - suggest?(query: string, options?: GeocodingOptions): Store { + suggest?(query: string, options?: GeocodingOptions): Store { return UIEventSource.FromPromise(this.search(query, options)) } diff --git a/src/Logic/State/SearchState.ts b/src/Logic/State/SearchState.ts index 4e7b4de5a..cac9ee5c1 100644 --- a/src/Logic/State/SearchState.ts +++ b/src/Logic/State/SearchState.ts @@ -1,6 +1,11 @@ -import GeocodingProvider, { FilterPayload, GeocodingUtils, type SearchResult } from "../Geocoding/GeocodingProvider" +import GeocodingProvider, { + FilterPayload, + GeocodeResult, + GeocodingUtils, + type SearchResult +} from "../Geocoding/GeocodingProvider" import { RecentSearch } from "../Geocoding/RecentSearch" -import { Store, Stores, UIEventSource } from "../UIEventSource" +import { ImmutableStore, Store, Stores, UIEventSource } from "../UIEventSource" import CombinedSearcher from "../Geocoding/CombinedSearcher" import FilterSearch from "../Geocoding/FilterSearch" import LocalElementSearch from "../Geocoding/LocalElementSearch" @@ -20,7 +25,6 @@ import ShowDataLayer from "../../UI/Map/ShowDataLayer" export default class SearchState { public readonly isSearching = new UIEventSource(false) - public readonly geosearch: GeocodingProvider public readonly recentlySearched: RecentSearch public readonly feedback: UIEventSource = new UIEventSource(undefined) public readonly searchTerm: UIEventSource = new UIEventSource("") @@ -28,28 +32,40 @@ export default class SearchState { public readonly suggestions: Store public readonly filterSuggestions: Store public readonly themeSuggestions: Store + public readonly locationSearchers: ReadonlyArray> private readonly state: ThemeViewState public readonly showSearchDrawer: UIEventSource + public readonly suggestionsSearchRunning: Store constructor(state: ThemeViewState) { this.state = state - this.geosearch = new CombinedSearcher( - // new LocalElementSearch(state, 5), + this.locationSearchers = [ + // new LocalElementSearch(state, 5), new CoordinateSearch(), new OpenStreetMapIdSearch(state), new PhotonSearch() // new NominatimGeocoding(), - ) + ] this.recentlySearched = new RecentSearch(state) const bounds = state.mapProperties.bounds - this.suggestions = this.searchTerm.stabilized(250).bindD(search => { + const suggestionsList = this.searchTerm.stabilized(250).mapD(search => { if (search.length === 0) { return undefined } - return Stores.holdDefined(bounds.bindD(bbox => this.geosearch.suggest(search, { bbox }))) + return this.locationSearchers.map(ls => ls.suggest(search, { bbox: bounds.data })) + + }, [bounds] + ) + this.suggestionsSearchRunning = suggestionsList.bind(suggestions => { + 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)) ) const themeSearch = new ThemeSearch(state, 3) @@ -57,8 +73,7 @@ export default class SearchState { const filterSearch = new FilterSearch(state) - this.filterSuggestions = this.searchTerm.stabilized(50).mapD(query => - filterSearch.searchDirectly(query) + 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 }) => { @@ -81,11 +96,7 @@ export default class SearchState { ) this.showSearchDrawer = new UIEventSource(false) - this.suggestions.addCallbackAndRunD(sugg => { - if (sugg.length > 0) { - this.showSearchDrawer.set(true) - } - }) + this.searchIsFocused.addCallbackAndRunD(sugg => { if (sugg) { this.showSearchDrawer.set(true) diff --git a/src/Logic/UIEventSource.ts b/src/Logic/UIEventSource.ts index 66187153a..6e481629f 100644 --- a/src/Logic/UIEventSource.ts +++ b/src/Logic/UIEventSource.ts @@ -41,17 +41,16 @@ export class Stores { return src } - public static concat(stores: Store[]): Store { - const newStore = new UIEventSource([]) - function update(){ - if(newStore._callbacks.isDestroyed){ + public static concat(stores: Store[]): Store<(T[] | undefined)[]> { + const newStore = new UIEventSource<(T[] | undefined)[]>([]) + + function update() { + if (newStore._callbacks.isDestroyed) { return true // unregister } - const results: T[][] = [] + const results: (T[] | undefined)[] = [] for (const store of stores) { - if(store.data){ - results.push(store.data) - } + results.push(store.data) } newStore.setData(results) } @@ -261,7 +260,7 @@ export abstract class Store implements Readable { if (mapped.data === newEventSource) { sink.setData(resultData) } - if(sink._callbacks.isDestroyed){ + if (sink._callbacks.isDestroyed) { return true // unregister } }) @@ -270,7 +269,7 @@ export abstract class Store implements Readable { return sink } - public bindD(f: (t: Exclude) => Store, extraSources: UIEventSource[] =[]): Store { + public bindD(f: (t: Exclude) => Store, extraSources: UIEventSource[] = []): Store { return this.bind((t) => { if (t === null) { return null @@ -408,7 +407,8 @@ export class ImmutableStore extends Store { class ListenerTracker { public pingCount = 0 private readonly _callbacks: ((t: T) => boolean | void | any)[] = [] -public isDestroyed = false + public isDestroyed = false + /** * Adds a callback which can be called; a function to unregister is returned */ @@ -469,8 +469,8 @@ public isDestroyed = false return this._callbacks.length } - public destroy(){ - this.isDestroyed= true + public destroy() { + this.isDestroyed = true this._callbacks.splice(0, this._callbacks.length) } } @@ -635,7 +635,8 @@ class MappedStore extends Store { } export class UIEventSource extends Store implements Writable { - private static readonly pass: (() => void) = () => {}; + private static readonly pass: (() => void) = () => { + } public data: T _callbacks: ListenerTracker = new ListenerTracker() @@ -644,7 +645,7 @@ export class UIEventSource extends Store implements Writable { this.data = data } - public destroy(){ + public destroy() { this._callbacks.destroy() } @@ -782,9 +783,9 @@ export class UIEventSource extends Store implements Writable { return defaultV } try { - return JSON.parse(str) + return JSON.parse(str) } catch (e) { - console.error("Could not parse value", str,"due to",e) + console.error("Could not parse value", str, "due to", e) return defaultV } }, diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index d4dacd828..5e910c85a 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -2,11 +2,7 @@ import LayoutConfig from "./ThemeConfig/LayoutConfig" import { SpecialVisualizationState } from "../UI/SpecialVisualization" import { Changes } from "../Logic/Osm/Changes" import { Store, UIEventSource } from "../Logic/UIEventSource" -import { - FeatureSource, - IndexedFeatureSource, - WritableFeatureSource -} from "../Logic/FeatureSource/FeatureSource" +import { FeatureSource, IndexedFeatureSource, WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource" import { OsmConnection } from "../Logic/Osm/OsmConnection" import { ExportableMap, MapProperties } from "./MapProperties" import LayerState from "../Logic/State/LayerState" @@ -50,9 +46,7 @@ import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter" import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor" -import NoElementsInViewDetector, { - FeatureViewState -} from "../Logic/Actors/NoElementsInViewDetector" +import NoElementsInViewDetector, { FeatureViewState } from "../Logic/Actors/NoElementsInViewDetector" import FilteredLayer from "./FilteredLayer" import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector" import { ImageUploadManager } from "../Logic/ImageProviders/ImageUploadManager" @@ -70,19 +64,10 @@ import summaryLayer from "../assets/generated/layers/summary.json" import last_click_layerconfig from "../assets/generated/layers/last_click.json" import { LayerConfigJson } from "./ThemeConfig/Json/LayerConfigJson" -import Locale from "../UI/i18n/Locale" import Hash from "../Logic/Web/Hash" import { GeoOperations } from "../Logic/GeoOperations" import { CombinedFetcher } from "../Logic/Web/NearbyImagesSearch" -import GeocodingProvider, { GeocodingUtils } from "../Logic/Geocoding/GeocodingProvider" -import CombinedSearcher from "../Logic/Geocoding/CombinedSearcher" -import CoordinateSearch from "../Logic/Geocoding/CoordinateSearch" -import LocalElementSearch from "../Logic/Geocoding/LocalElementSearch" -import { RecentSearch } from "../Logic/Geocoding/RecentSearch" -import PhotonSearch from "../Logic/Geocoding/PhotonSearch" -import ThemeSearch from "../Logic/Geocoding/ThemeSearch" -import OpenStreetMapIdSearch from "../Logic/Geocoding/OpenStreetMapIdSearch" -import FilterSearch from "../Logic/Geocoding/FilterSearch" +import { GeocodingUtils } from "../Logic/Geocoding/GeocodingProvider" import SearchState from "../Logic/State/SearchState" /** @@ -569,6 +554,10 @@ export default class ThemeViewState implements SpecialVisualizationState { this.previewedImage.setData(undefined) return } + if(this.searchState.showSearchDrawer.data){ + this.searchState.showSearchDrawer.set(false) + return + } if(this.guistate.closeAll()){ return } @@ -623,6 +612,12 @@ export default class ThemeViewState implements SpecialVisualizationState { ) } + + Hotkeys.RegisterHotkey({ ctrl: "F" }, Translations.t.hotkeyDocumentation.selectSearch, () => { + this.searchState.feedback.set(undefined) + this.searchState.searchIsFocused.set(true) + }) + this.featureSwitches.featureSwitchBackgroundSelection.addCallbackAndRun((enable) => { if (!enable) { return diff --git a/src/UI/Base/DrawerRight.svelte b/src/UI/Base/DrawerRight.svelte index 20343e8c6..06120768e 100644 --- a/src/UI/Base/DrawerRight.svelte +++ b/src/UI/Base/DrawerRight.svelte @@ -33,7 +33,7 @@ rightOffset="inset-y-0 right-0" bind:hidden={hidden}> -
+
diff --git a/src/UI/Base/Searchbar.svelte b/src/UI/Base/Searchbar.svelte index 4cbb35102..b74e148c3 100644 --- a/src/UI/Base/Searchbar.svelte +++ b/src/UI/Base/Searchbar.svelte @@ -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 = undefined + let inputElement: HTMLInputElement + + isFocused?.addCallback(focussed => { + if (focussed) { + requestAnimationFrame(() => { + if (document.activeElement !== inputElement) { + inputElement.focus() + inputElement.select() + } + }) + } + }) +
dispatch("search")} >
diff --git a/src/UI/Base/SidebarUnit.svelte b/src/UI/Base/SidebarUnit.svelte new file mode 100644 index 000000000..97f43e19b --- /dev/null +++ b/src/UI/Base/SidebarUnit.svelte @@ -0,0 +1,55 @@ + + + diff --git a/src/UI/BigComponents/MenuDrawer.svelte b/src/UI/BigComponents/MenuDrawer.svelte index a65c7f69e..105c00e8e 100644 --- a/src/UI/BigComponents/MenuDrawer.svelte +++ b/src/UI/BigComponents/MenuDrawer.svelte @@ -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 @@ - - diff --git a/src/UI/BigComponents/SearchField.svelte b/src/UI/BigComponents/SearchField.svelte deleted file mode 100644 index b0d2accec..000000000 --- a/src/UI/BigComponents/SearchField.svelte +++ /dev/null @@ -1,59 +0,0 @@ - - -
-
{}}> - {#if isRunning} - {Translations.t.general.search.searching} - {:else} -
- { - feedback.set(undefined) - return keypr.key === "Enter" ? _performSearch() : undefined - }} - bind:value={$searchValue} - use:placeholder={placeholderText} - use:ariaLabel={Translations.t.general.search.search} - /> -
- {#if $feedback !== undefined} - - - {/if} - {/if} -
-
diff --git a/src/UI/InputElement/Helpers/WikidataInput.svelte b/src/UI/InputElement/Helpers/WikidataInput.svelte index 96479f2d2..431c91eed 100644 --- a/src/UI/InputElement/Helpers/WikidataInput.svelte +++ b/src/UI/InputElement/Helpers/WikidataInput.svelte @@ -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 @@
- + {#if $searchValue.trim().length === 0} diff --git a/src/UI/Popup/MoveWizard.svelte b/src/UI/Popup/MoveWizard.svelte index 7a8a0512a..cbb7407de 100644 --- a/src/UI/Popup/MoveWizard.svelte +++ b/src/UI/Popup/MoveWizard.svelte @@ -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 @@
{#if $reason.includeSearch} - + {/if}
diff --git a/src/UI/Search/ActiveFilter.svelte b/src/UI/Search/ActiveFilter.svelte index 66d9d7a48..fd43da9a0 100644 --- a/src/UI/Search/ActiveFilter.svelte +++ b/src/UI/Search/ActiveFilter.svelte @@ -21,7 +21,7 @@ {#if loading} {:else } -
+
+ + {/if} -
+ {/if} diff --git a/src/UI/Search/Geosearch.svelte b/src/UI/Search/Geosearch.svelte deleted file mode 100644 index 029e8c9bf..000000000 --- a/src/UI/Search/Geosearch.svelte +++ /dev/null @@ -1,95 +0,0 @@ - - -
- -
- - {#if $isRunning} - {Translations.t.general.search.searching} - {:else} - { - 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} - -
-
diff --git a/src/UI/Search/SearchResults.svelte b/src/UI/Search/SearchResults.svelte index d036b4944..7f8b6eb36 100644 --- a/src/UI/Search/SearchResults.svelte +++ b/src/UI/Search/SearchResults.svelte @@ -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 = state.layerState.activeFilters.map(fs => fs.filter(f => Constants.priviliged_layers.indexOf(f.layer.id) < 0)) let recentlySeen: Store = 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 -
+
+ +

Search results

- {#if $filterResults.length > 0} -

Pick a filter below

+ {#if $searchTerm.length > 0 && $filterResults.length > 0} + -
- {#each $filterResults as filterResult (filterResult)} - - {/each} -
+

Pick a filter below

+ +
+ {#each $filterResults as filterResult (filterResult)} + + {/each} +
+
{/if} + {#if $searchTerm.length > 0} -

Locations

- {/if} - {#if $searchTerm.length > 0 && $results === undefined} -
- -
- {:else if $results?.length > 0} - {#each $results as entry (entry)} - - {/each} - {:else if $searchTerm.length > 0 || $recentlySeen?.length > 0 || $recentThemes?.length > 0} -
- {#if $searchTerm.length > 0} + + +

Locations

+ + {#if $isSearching} +
+ +
+ {/if} + + {#if $results?.length > 0} + {#each $results as entry (entry)} + + {/each} + + {:else if !$isSearching} {/if} +
- {#if $recentlySeen?.length > 0} -
-

- -

- {#each $recentlySeen as entry} - - {/each} -
- {/if} - - {#if $recentThemes?.length > 0 && $allowOtherThemes} -
-

- -

- {#each $recentThemes as themeId (themeId)} - - {/each} -
- {/if} -
{/if} + {#if $themeResults.length > 0} -

- Other maps -

- {#each $themeResults as entry} - - {/each} + +

+ Other maps +

+ {#each $themeResults as entry} + + {/each} +
{/if} + + {#if $searchTerm.length == 0 && $recentlySeen?.length > 0} + +

+ +

+ {#each $recentlySeen as entry} + + {/each} +
+ {/if} + + {#if $searchTerm.length === 0 && $recentThemes?.length > 0 && $allowOtherThemes} + +

+ +

+ {#each $recentThemes as themeId (themeId)} + + {/each} +
+ {/if} + +
diff --git a/src/UI/ThemeViewGUI.svelte b/src/UI/ThemeViewGUI.svelte index dde4538b8..7750c1aa5 100644 --- a/src/UI/ThemeViewGUI.svelte +++ b/src/UI/ThemeViewGUI.svelte @@ -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 @@
+
+ + {#if ($showCrosshair === "yes" && $currentZoom >= 17) || $showCrosshair === "always" || $visualFeedback} + +
+ +
+ {/if} + + +
+ {#if $visualFeedback}
{/if} -
- - -
- -
- - {console.log("Opening...."); state.guistate.pageStates.menu.setData(true)}} - on:keydown={forwardEventToMap} - > - - - - state.guistate.pageStates.about_theme.set(true)} - on:keydown={forwardEventToMap} - > -
- - - - -
-
-
- - {#if $debug && $hash} -
- {$hash} -
- {/if} - - -
- - { - state.map?.data?.getCanvas()?.focus() - }} - perLayer={state.perLayer} - selectedElement={state.selectedElement} - geolocationState={state.geolocation.geolocationState} - /> -
-
- -
- -
- - {#if $selectedElement === undefined} -
- -
- {/if} -
- -
-
- - - - - {#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()} - { - state.selectCurrentView() - }} - on:keydown={forwardEventToMap} - > -
- currentViewLayer.defaultIcon()} /> -
-
- {/if} - - - - -
Testmode
-
- {#if state.osmConnection.Backend().startsWith("https://master.apis.dev.openstreetmap.org")} -
Testserver
- {/if} - -
Faking a user (Testmode)
-
-
-
- - - - -
-
@@ -389,7 +302,7 @@
- +
@@ -401,8 +314,11 @@ -
+ + +
+
@@ -412,7 +328,7 @@ {state.guistate.menuIsOpened.setData(true)}} + on:click={() => {console.log("Opening...."); state.guistate.pageStates.menu.setData(true)}} on:keydown={forwardEventToMap} > @@ -433,11 +349,15 @@
+ {#if $debug && $hash} +
+ {$hash} +
+ {/if} -
- +
+
@@ -453,8 +373,9 @@
-
+
+ {#if currentViewLayer?.tagRenderings && currentViewLayer.defaultIcon()} { @@ -467,6 +388,7 @@
{/if} + @@ -480,7 +402,8 @@
Faking a user (Testmode)
-
+ +
@@ -489,20 +412,6 @@
- - {#if ($showCrosshair === "yes" && $currentZoom >= 17) || $showCrosshair === "always" || $visualFeedback} - -
- -
- {/if} - - -
-
diff --git a/src/Utils.ts b/src/Utils.ts index c3a2d1182..9708515f7 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1808,6 +1808,18 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be return href } + /** Randomize array in-place using Durstenfeld shuffle algorithm + * Source: https://stackoverflow.com/questions/2450954/how-to-randomize-shuffle-a-javascript-array + * */ + static shuffle(array: any[]) { + for (let i = array.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + const temp = array[i] + array[i] = array[j] + array[j] = temp + } + } + private static emojiRegex = /[\p{Extended_Pictographic}🛰️]/u