From c94393e825c65c23b7bb4746c073ae45526faf2a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 26 Aug 2024 17:24:12 +0200 Subject: [PATCH] Add search for filters --- assets/layers/filters/filters.json | 12 +- assets/layers/food/food.json | 51 ++- assets/layers/questions/questions.json | 2 +- .../mapcomplete-changes.json | 9 +- langs/layers/nl.json | 13 + public/css/index-tailwind-output.css | 11 + src/Logic/Geocoding/FilterSearch.ts | 26 +- src/Logic/Geocoding/RecentSearch.ts | 3 - .../ThemeConfig/Conversion/PrepareLayer.ts | 295 ++++++++++-------- src/Models/ThemeConfig/FilterConfig.ts | 4 +- .../ThemeConfig/Json/FilterConfigJson.ts | 31 +- .../Json/TagRenderingConfigJson.ts | 2 +- src/Models/ThemeViewState.ts | 9 +- src/UI/BigComponents/Filterview.svelte | 5 +- src/UI/Search/ActiveFilter.svelte | 24 +- src/UI/Search/ActiveFilters.svelte | 31 +- src/UI/Search/FilterOption.svelte | 10 + src/UI/Search/FilterResult.svelte | 43 +-- src/UI/Search/Geosearch.svelte | 8 +- src/UI/Search/SearchResult.svelte | 7 +- src/UI/Search/SearchResultUtils.ts | 25 ++ src/UI/Search/SearchResults.svelte | 10 +- src/Utils.ts | 17 +- src/index.css | 11 + 24 files changed, 405 insertions(+), 254 deletions(-) create mode 100644 src/UI/Search/FilterOption.svelte create mode 100644 src/UI/Search/SearchResultUtils.ts diff --git a/assets/layers/filters/filters.json b/assets/layers/filters/filters.json index fafcd6dba..dca5ec172 100644 --- a/assets/layers/filters/filters.json +++ b/assets/layers/filters/filters.json @@ -9,6 +9,7 @@ "id": "open_now", "options": [ { + "emoji": "⏰", "question": { "en": "Open now", "nl": "Nu open", @@ -268,6 +269,7 @@ { "question": { "en": "No preference towards dogs", + "nl": "Geen voorkeur voor honden", "de": "Keine Bevorzugung von Hunden", "cs": "Bez preference psů" } @@ -275,22 +277,27 @@ { "question": { "en": "Dogs allowed", + "nl": "Honden toegelaten", "de": "Hunde erlaubt", "cs": "Psi povoleny" }, + "emoji": "🐕", "osmTags": { "or": [ "dog=unleashed", "dog=yes" ] - } + }, + "icon": "./assets/layers/questions/dogs_allowed.svg" }, { "question": { "en": "No dogs allowed", + "nl": "Geen honden toegelaten", "de": "Keine Hunde erlaubt", "cs": "Psi nejsou povoleni" }, + "icon": "./assets/layers/questions/no_dogs.svg", "osmTags": "dog=no" } ] @@ -304,6 +311,7 @@ "de": "Internetzugang vorhanden", "cs": "Nabízí internet" }, + "icon": "wifi", "osmTags": { "or": [ "internet_access=wlan", @@ -355,6 +363,7 @@ "cs": "Má bezlepkovou nabídku", "de": "Hat glutenfreie Angebote" }, + "icon": "./assets/layers/questions/glutenfree.svg", "osmTags": { "or": [ "diet:gluten_free=yes", @@ -374,6 +383,7 @@ "cs": "Má nabídku bez laktózy", "de": "Hat laktosefreie Angebote" }, + "icon": "./assets/layers/questions/lactose_free.svg", "osmTags": { "or": [ "diet:lactose_free=yes", diff --git a/assets/layers/food/food.json b/assets/layers/food/food.json index b9f9e3ef4..83d9f3b24 100644 --- a/assets/layers/food/food.json +++ b/assets/layers/food/food.json @@ -341,6 +341,7 @@ "mappings": [ { "if": "cuisine=pizza", + "icon": "🍕", "then": { "en": "This is a pizzeria", "nl": "Dit is een pizzeria", @@ -354,6 +355,7 @@ }, { "if": "cuisine=friture", + "icon": "🍟", "then": { "en": "This is a friture", "nl": "Dit is een frituur", @@ -365,6 +367,7 @@ }, { "if": "cuisine=pasta", + "icon": "🍝", "then": { "en": "Mainly serves pasta", "nl": "Dit is een pastazaak", @@ -378,6 +381,7 @@ }, { "if": "cuisine=kebab", + "icon": "🥙", "then": { "en": "This is kebab shop", "nl": "Dit is een kebabzaak", @@ -391,6 +395,7 @@ }, { "if": "cuisine=sandwich", + "icon": "🥪", "then": { "en": "This is a sandwich shop", "nl": "Dit is een broodjeszaak", @@ -402,6 +407,7 @@ }, { "if": "cuisine=burger", + "icon": "🍔", "then": { "en": "Burgers are served here", "nl": "Dit is een hamburgerrestaurant", @@ -415,6 +421,7 @@ }, { "if": "cuisine=sushi", + "icon": "\uD83C\uDF63", "then": { "en": "Sushi is served here", "nl": "Dit is een sushirestaurant", @@ -427,6 +434,7 @@ }, { "if": "cuisine=coffee", + "icon": "☕", "then": { "en": "Coffee is served here", "nl": "Dit is een koffiezaak", @@ -439,6 +447,7 @@ }, { "if": "cuisine=italian", + "icon": "🇮🇹", "then": { "en": "This is an Italian restaurant (which serves more than pasta and pizza)", "nl": "Dit is een Italiaans restaurant (dat meer dan enkel pasta of pizza verkoopt)", @@ -451,6 +460,7 @@ }, { "if": "cuisine=french", + "icon": "🇫🇷", "then": { "en": "French dishes are served here", "nl": "Dit is een Frans restaurant", @@ -463,6 +473,7 @@ }, { "if": "cuisine=chinese", + "icon":"🇨🇳", "then": { "en": "Chinese dishes are served here", "nl": "Dit is een Chinees restaurant", @@ -475,6 +486,7 @@ }, { "if": "cuisine=greek", + "icon": "🇬🇷", "then": { "en": "Greek dishes are served here", "nl": "Dit is een Grieks restaurant", @@ -487,6 +499,7 @@ }, { "if": "cuisine=indian", + "icon": "🇮🇳", "then": { "en": "Indian dishes are served here", "nl": "Dit is een Indisch restaurant", @@ -499,6 +512,7 @@ }, { "if": "cuisine=turkish", + "icon": "🇹🇷", "then": { "en": "Turkish dishes are served here", "nl": "Dit is een Turks restaurant (dat meer dan enkel kebab verkoopt)", @@ -511,6 +525,7 @@ }, { "if": "cuisine=thai", + "icon": "🇹🇭", "then": { "en": "Thai dishes are served here", "nl": "Dit is een Thaïs restaurant", @@ -519,9 +534,42 @@ "ca": "Aquí es serveixen plats tailandesos", "cs": "Podávají se zde thajské pokrmy" } + }, + { + "if": "cuisine=mexican ", + "icon": "\uD83D\uDC14", + "then": { + "en": "Mexican dishes are served here", + "nl": "Dit is een mexicaans restaurant" + } + }, + { + "if": "cuisine=japanese ", + "icon": "🇯🇵", + "then": { + "en": "Japanese dishes are served here", + "nl": "Dit is een japans restaurant" + } + }, + { + "if": "cuisine=chicken ", + "icon": "\uD83D\uDC14", + "then": { + "en": "Chicken based dishes are served here", + "nl": "Dit is een kiprestaurant" + } + }, + { + "if": "cuisine=seafood ", + "icon": "\uD83D\uDC1F", + "then": { + "en": "Seafood dishes are served here", + "nl": "Dit is een vis- en zeerestaurant" + } } ], - "id": "Cuisine" + "id": "Cuisine", + "filter": true }, { "id": "show-menu-image", @@ -1291,6 +1339,7 @@ "es": "Tiene menú vegetariano", "fr": "A un menu végétarien" }, + "icon": "./assets/layers/food/Vegetarian-mark.svg", "osmTags": { "or": [ "diet:vegetarian=yes", diff --git a/assets/layers/questions/questions.json b/assets/layers/questions/questions.json index ae1289d55..d5d4551f7 100644 --- a/assets/layers/questions/questions.json +++ b/assets/layers/questions/questions.json @@ -569,7 +569,7 @@ }, { "if": "dog=no", - "icon": "\uD83D\uDC15 ⃠", + "icon": "./assets/layers/questions/no_dogs.svg", "then": { "en": "Dogs are not allowed", "nl": "honden zijn niet toegelaten", diff --git a/assets/themes/mapcomplete-changes/mapcomplete-changes.json b/assets/themes/mapcomplete-changes/mapcomplete-changes.json index afc62363b..7873f6b66 100644 --- a/assets/themes/mapcomplete-changes/mapcomplete-changes.json +++ b/assets/themes/mapcomplete-changes/mapcomplete-changes.json @@ -3,14 +3,17 @@ "title": { "en": "Changes made with MapComplete" }, - "description": { - "en": "This maps shows all the changes made with MapComplete" - }, "shortDescription": { "en": "Shows changes made by MapComplete" }, + "description": { + "en": "This maps shows all the changes made with MapComplete" + }, "icon": "./assets/svg/logo.svg", "hideFromOverview": true, + "startLat": 0, + "startLon": 0, + "startZoom": 1, "layers": [ { "id": "mapcomplete-changes", diff --git a/langs/layers/nl.json b/langs/layers/nl.json index 04882ce7e..e5a33caac 100644 --- a/langs/layers/nl.json +++ b/langs/layers/nl.json @@ -3883,6 +3883,19 @@ "question": "Gratis toegankelijk" } } + }, + "10": { + "options": { + "0": { + "question": "Geen voorkeur voor honden" + }, + "1": { + "question": "Honden toegelaten" + }, + "2": { + "question": "Geen honden toegelaten" + } + } } } }, diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index 2546818a4..e6a1f97f1 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -4724,6 +4724,17 @@ h2.group { background-color: #58cd2722; } +.badge { + display: flex; + align-items: center; + white-space: nowrap; + border-radius: 999rem; + padding-left: 0.5rem; + padding-right: 0.5rem; + border: 1px solid var(--subtle-detail-color-light-contrast); + background-color: var(--low-interaction-background); +} + .alert { /* The class to convey important information, e.g. 'invalid', 'something went wrong', 'warning: testmode', ... */ background-color: var(--alert-color); diff --git a/src/Logic/Geocoding/FilterSearch.ts b/src/Logic/Geocoding/FilterSearch.ts index c116052f7..f7255b855 100644 --- a/src/Logic/Geocoding/FilterSearch.ts +++ b/src/Logic/Geocoding/FilterSearch.ts @@ -13,11 +13,17 @@ export default class FilterSearch implements GeocodingProvider { } async search(query: string, options?: GeocodingOptions): Promise { - return [] + return this.searchDirectly(query) } private searchDirectly(query: string): SearchResult[] { const possibleFilters: SearchResult[] = [] + if (query.length === 0) { + return [] + } + if(!Utils.isEmoji(query)){ + query = Utils.simplifyStringForSearch(query) + } for (const layer of this._state.layout.layers) { if (!Array.isArray(layer.filters)) { continue @@ -26,16 +32,16 @@ export default class FilterSearch implements GeocodingProvider { for (let i = 0; i < filter.options.length; i++) { const option = filter.options[i] if (option === undefined) { - console.log("No options for", filter) continue } - const terms = [option.question.txt, - ...(option.searchTerms?.[Locale.language.data] ?? option.searchTerms?.["en"] ?? [])].flatMap(term => term.split(" ")) + let terms = ([option.question.txt, + ...(option.searchTerms?.[Locale.language.data] ?? option.searchTerms?.["en"] ?? [])] + .flatMap(term => [term, ...term?.split(" ")])) + terms = terms.map(t => Utils.simplifyStringForSearch(t)) + terms.push(option.emoji) + Utils.NoNullInplace(terms) const levehnsteinD = Math.min(... - terms.map(entry => { - const simplified = Utils.simplifyStringForSearch(entry) - return Utils.levenshteinDistance(query, simplified.slice(0, query.length)) - })) + terms.map(entry => Utils.levenshteinDistance(query, entry.slice(0, query.length)))) if (levehnsteinD / query.length > 0.25) { continue } @@ -51,8 +57,10 @@ export default class FilterSearch implements GeocodingProvider { } suggest(query: string, options?: GeocodingOptions): Store { + if (Utils.isEmoji(query)) { + return new ImmutableStore(this.searchDirectly(query)) + } query = Utils.simplifyStringForSearch(query) - return new ImmutableStore(this.searchDirectly(query)) } diff --git a/src/Logic/Geocoding/RecentSearch.ts b/src/Logic/Geocoding/RecentSearch.ts index dce6133c4..6abf99749 100644 --- a/src/Logic/Geocoding/RecentSearch.ts +++ b/src/Logic/Geocoding/RecentSearch.ts @@ -48,7 +48,6 @@ export class RecentSearch { } results.push(simple) } - console.log("Setting", results) prefs.setData(JSON.stringify(results)) }) @@ -59,7 +58,6 @@ export class RecentSearch { if (!osm_id) { return } - console.log("Selected element is", selected) if (["node", "way", "relation"].indexOf(osm_type) < 0) { return } @@ -86,7 +84,6 @@ export class RecentSearch { seenIds.add(id) } } - console.log(">>>", arr) this._seenThisSession.set(arr) } } diff --git a/src/Models/ThemeConfig/Conversion/PrepareLayer.ts b/src/Models/ThemeConfig/Conversion/PrepareLayer.ts index 76b0d46f6..4f28c3af9 100644 --- a/src/Models/ThemeConfig/Conversion/PrepareLayer.ts +++ b/src/Models/ThemeConfig/Conversion/PrepareLayer.ts @@ -10,10 +10,7 @@ import { SetDefault, } from "./Conversion" import { LayerConfigJson } from "../Json/LayerConfigJson" -import { - MinimalTagRenderingConfigJson, - TagRenderingConfigJson, -} from "../Json/TagRenderingConfigJson" +import { MinimalTagRenderingConfigJson, TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" import { Utils } from "../../../Utils" import RewritableConfigJson from "../Json/RewritableConfigJson" import SpecialVisualizations from "../../../UI/SpecialVisualizations" @@ -21,7 +18,7 @@ import Translations from "../../../UI/i18n/Translations" import { Translation } from "../../../UI/i18n/Translation" import tagrenderingconfigmeta from "../../../../src/assets/schemas/tagrenderingconfigmeta.json" import { AddContextToTranslations } from "./AddContextToTranslations" -import FilterConfigJson from "../Json/FilterConfigJson" +import FilterConfigJson, { FilterConfigOptionJson } from "../Json/FilterConfigJson" import predifined_filters from "../../../../assets/layers/filters/filters.json" import { TagConfigJson } from "../Json/TagConfigJson" import PointRenderingConfigJson, { IconConfigJson } from "../Json/PointRenderingConfigJson" @@ -33,7 +30,7 @@ import LineRenderingConfigJson from "../Json/LineRenderingConfigJson" import { ConversionContext } from "./ConversionContext" import { ExpandRewrite } from "./ExpandRewrite" import { TagUtils } from "../../../Logic/Tags/TagUtils" -import { Translatable } from "../Json/Translatable" +import FilterConfig, { FilterConfigOption } from "../FilterConfig" class ExpandFilter extends DesugaringStep { private static readonly predefinedFilters = ExpandFilter.load_filters() @@ -41,9 +38,11 @@ class ExpandFilter extends DesugaringStep { constructor(state: DesugaringContext) { super( - "Expands filters: replaces a shorthand by the value found in 'filters.json'. If the string is formatted 'layername.filtername, it will be looked up into that layer instead. If a tagRendering sets 'filter', this filter will also be included", + ["Expands filters: replaces a shorthand by the value found in 'filters.json'.", + "If the string is formatted 'layername.filtername, it will be looked up into that layer instead. If a tagRendering sets 'filter', this filter will also be included", + ""].join(" "), ["filter"], - "ExpandFilter" + "ExpandFilter", ) this._state = state } @@ -56,6 +55,38 @@ class ExpandFilter extends DesugaringStep { return filters } + private static buildFilterFromTagRendering(tr: TagRenderingConfigJson, context: ConversionContext): FilterConfigJson { + if (!(tr.mappings?.length >= 1)) { + context.err( + "Found a matching tagRendering to base a filter on, but this tagRendering does not contain any mappings", + ) + } + const options = (tr).mappings.map((mapping) => { + let icon : string= mapping.icon?.["path"] ?? mapping.icon + let emoji: string = undefined + if(Utils.isEmoji(icon)){ + emoji = icon + icon = undefined + } + return ({ + question: mapping.then, + osmTags: mapping.if, + searchTerms: mapping.searchTerms, + icon, emoji + }) + }) + // Add default option + options.unshift({ + question: tr["question"] ?? Translations.t.general.filterPanel.allTypes, + osmTags: undefined, + searchTerms: undefined, + }) + return ({ + id: tr["id"], + options, + }) + } + convert(json: LayerConfigJson, context: ConversionContext): LayerConfigJson { if (json?.filter === undefined || json?.filter === null) { return json // Nothing to change here @@ -68,6 +99,16 @@ class ExpandFilter extends DesugaringStep { const newFilters: FilterConfigJson[] = [] const filters = <(FilterConfigJson | string)[]>json.filter + function filterExists(filterName: string): boolean{ + return filters.some((existing) => { + const id: string = existing["id"] ?? existing + return ( + filterName === id || + (filterName.startsWith("filters.") && filterName.endsWith("." + id)) + ) + }) + } + /** * Checks all tagRendering. If a tagrendering has 'filter' set, add this filter to the layer config */ @@ -76,18 +117,18 @@ class ExpandFilter extends DesugaringStep { if (!tagRendering?.filter) { continue } + if (tagRendering.filter === true) { + if(filterExists(tagRendering["id"])){ + continue + } + filters.push(ExpandFilter.buildFilterFromTagRendering(tagRendering, context.enters("tagRenderings", i, "filter"))) + continue + } for (const filterName of tagRendering.filter ?? []) { if (typeof filterName !== "string") { context.enters("tagRenderings", i, "filter").err("Not a string: " + filterName) } - const exists = filters.some((existing) => { - const id: string = existing["id"] ?? existing - return ( - filterName === id || - (filterName.startsWith("filters.") && filterName.endsWith("." + id)) - ) - }) - if (exists) { + if (filterExists(filterName)) { continue } if (!filterName) { @@ -99,7 +140,7 @@ class ExpandFilter extends DesugaringStep { } /** - * Create filters based on builtin filters + * Create filters based on builtin filters or create them based on the tagRendering */ for (let i = 0; i < filters.length; i++) { const filter = filters[i] @@ -115,28 +156,8 @@ class ExpandFilter extends DesugaringStep { json.tagRenderings.find((tr) => !!tr && tr["id"] === filter) ) if (matchingTr) { - if (!(matchingTr.mappings?.length >= 1)) { - context - .enters("filter", i) - .err( - "Found a matching tagRendering to base a filter on, but this tagRendering does not contain any mappings" - ) - } - const options = ( matchingTr).mappings.map((mapping) => ({ - question: mapping.then, - osmTags: mapping.if, - searchTerms: mapping.searchTerms - - })) - options.unshift({ - question: matchingTr["question"] ?? Translations.t.general.filterPanel.allTypes, - osmTags: undefined, - searchTerms: undefined - }) - newFilters.push({ - id: filter, - options, - }) + const filter = ExpandFilter.buildFilterFromTagRendering(matchingTr, context.enters("filter", i)) + newFilters.push(filter) continue } @@ -145,7 +166,7 @@ class ExpandFilter extends DesugaringStep { const split = filter.split(".") if (split.length > 2) { context.err( - "invalid filter name: " + filter + ", expected `layername.filterid`" + "invalid filter name: " + filter + ", expected `layername.filterid`", ) } const layer = this._state.sharedLayers.get(split[0]) @@ -154,7 +175,7 @@ class ExpandFilter extends DesugaringStep { } const expectedId = split[1] const expandedFilter = (<(FilterConfigJson | string)[]>layer.filter).find( - (f) => typeof f !== "string" && f.id === expectedId + (f) => typeof f !== "string" && f.id === expectedId, ) if (expandedFilter === undefined) { context.err("Did not find filter with name " + filter) @@ -172,15 +193,15 @@ class ExpandFilter extends DesugaringStep { const suggestions = Utils.sortedByLevenshteinDistance( filter, Array.from(ExpandFilter.predefinedFilters.keys()), - (t) => t + (t) => t, ) context .enter(filter) .err( "While searching for predefined filter " + - filter + - ": this filter is not found. Perhaps you meant one of: " + - suggestions + filter + + ": this filter is not found. Perhaps you meant one of: " + + suggestions, ) } newFilters.push(found) @@ -193,9 +214,9 @@ class ExpandTagRendering extends Conversion< | string | TagRenderingConfigJson | { - builtin: string | string[] - override: any - }, + builtin: string | string[] + override: any +}, TagRenderingConfigJson[] > { private readonly _state: DesugaringContext @@ -217,12 +238,12 @@ class ExpandTagRendering extends Conversion< noHardcodedStrings?: false | boolean // If set, a question will be added to the 'sharedTagRenderings'. Should only be used for 'questions.json' addToContext?: false | boolean - } + }, ) { super( "Converts a tagRenderingSpec into the full tagRendering, e.g. by substituting the tagRendering by the shared-question and reusing the builtins", [], - "ExpandTagRendering" + "ExpandTagRendering", ) this._state = state this._self = self @@ -242,7 +263,7 @@ class ExpandTagRendering extends Conversion< public convert( spec: string | any, - ctx: ConversionContext + ctx: ConversionContext, ): QuestionableTagRenderingConfigJson[] { const trs = this.convertOnce(spec, ctx) @@ -355,8 +376,8 @@ class ExpandTagRendering extends Conversion< found, ConversionContext.construct( [layer.id, "tagRenderings", found["id"]], - ["AddContextToTranslations"] - ) + ["AddContextToTranslations"], + ), ) matchingTrs[i] = found } @@ -384,17 +405,17 @@ class ExpandTagRendering extends Conversion< ctx.warn( `A literal rendering was detected: ${tr} Did you perhaps forgot to add a layer name as 'layername.${tr}'? ` + - Array.from(state.sharedLayers.keys()).join(", ") + Array.from(state.sharedLayers.keys()).join(", "), ) } if (this._options?.noHardcodedStrings && this._state?.sharedLayers?.size > 0) { ctx.err( "Detected an invocation to a builtin tagRendering, but this tagrendering was not found: " + - tr + - " \n Did you perhaps forget to add the layer as prefix, such as `icons." + - tr + - "`? " + tr + + " \n Did you perhaps forget to add the layer as prefix, such as `icons." + + tr + + "`? ", ) } @@ -429,9 +450,9 @@ class ExpandTagRendering extends Conversion< } ctx.err( "An object calling a builtin can only have keys `builtin` or `override`, but a key with name `" + - key + - "` was found. This won't be picked up! The full object is: " + - JSON.stringify(tr) + key + + "` was found. This won't be picked up! The full object is: " + + JSON.stringify(tr), ) } @@ -450,39 +471,39 @@ class ExpandTagRendering extends Conversion< const candidates = Utils.sortedByLevenshteinDistance( layerName, Array.from(state.sharedLayers.keys()), - (s) => s + (s) => s, ) if (state.sharedLayers.size === 0) { ctx.warn( "BOOTSTRAPPING. Rerun generate layeroverview. While reusing tagrendering: " + - name + - ": layer " + - layerName + - " not found for now, but ignoring as this is a bootstrapping run. " + name + + ": layer " + + layerName + + " not found for now, but ignoring as this is a bootstrapping run. ", ) } else { ctx.err( ": While reusing tagrendering: " + - name + - ": layer " + - layerName + - " not found. Maybe you meant one of " + - candidates.slice(0, 3).join(", ") + name + + ": layer " + + layerName + + " not found. Maybe you meant one of " + + candidates.slice(0, 3).join(", "), ) } continue } candidates = Utils.NoNull(layer.tagRenderings.map((tr) => tr["id"])).map( - (id) => layerName + "." + id + (id) => layerName + "." + id, ) } candidates = Utils.sortedByLevenshteinDistance(name, candidates, (i) => i) ctx.err( "The tagRendering with identifier " + - name + - " was not found.\n\tDid you mean one of " + - candidates.join(", ") + - "?\n(Hint: did you add a new label and are you trying to use this label at the same time? Run 'reset:layeroverview' first" + name + + " was not found.\n\tDid you mean one of " + + candidates.join(", ") + + "?\n(Hint: did you add a new label and are you trying to use this label at the same time? Run 'reset:layeroverview' first", ) continue } @@ -507,13 +528,13 @@ class DetectInline extends DesugaringStep { super( "If no 'inline' is set on the freeform key, it will be automatically added. If no special renderings are used, it'll be set to true", ["freeform.inline"], - "DetectInline" + "DetectInline", ) } convert( json: QuestionableTagRenderingConfigJson, - context: ConversionContext + context: ConversionContext, ): QuestionableTagRenderingConfigJson { if (json.freeform === undefined) { return json @@ -536,7 +557,7 @@ class DetectInline extends DesugaringStep { if (json.freeform.inline === true) { context.err( "'inline' is set, but the rendering contains a special visualisation...\n " + - spec[key] + spec[key], ) } json = JSON.parse(JSON.stringify(json)) @@ -559,7 +580,7 @@ export class AddQuestionBox extends DesugaringStep { super( "Adds a 'questions'-object if no question element is added yet", ["tagRenderings"], - "AddQuestionBox" + "AddQuestionBox", ) } @@ -583,18 +604,18 @@ export class AddQuestionBox extends DesugaringStep { json.tagRenderings = [...json.tagRenderings] const allSpecials: Exclude[] = ( ValidationUtils.getAllSpecialVisualisations( - json.tagRenderings + json.tagRenderings, ).filter((spec) => typeof spec !== "string") ) const questionSpecials = allSpecials.filter((sp) => sp.func.funcName === "questions") const noLabels = questionSpecials.filter( - (sp) => sp.args.length === 0 || sp.args[0].trim() === "" + (sp) => sp.args.length === 0 || sp.args[0].trim() === "", ) if (noLabels.length > 1) { context.err( - "Multiple 'questions'-visualisations found which would show _all_ questions. Don't do this" + "Multiple 'questions'-visualisations found which would show _all_ questions. Don't do this", ) } @@ -602,9 +623,9 @@ export class AddQuestionBox extends DesugaringStep { const allLabels = new Set( [].concat( ...json.tagRenderings.map( - (tr) => (tr).labels ?? [] - ) - ) + (tr) => (tr).labels ?? [], + ), + ), ) const seen: Set = new Set() for (const questionSpecial of questionSpecials) { @@ -622,20 +643,20 @@ export class AddQuestionBox extends DesugaringStep { if (blacklisted?.length > 0 && used?.length > 0) { context.err( "The {questions()}-special rendering only supports either a blacklist OR a whitelist, but not both." + - "\n Whitelisted: " + - used.join(", ") + - "\n Blacklisted: " + - blacklisted.join(", ") + "\n Whitelisted: " + + used.join(", ") + + "\n Blacklisted: " + + blacklisted.join(", "), ) } for (const usedLabel of used) { if (!allLabels.has(usedLabel)) { context.err( "This layers specifies a special question element for label `" + - usedLabel + - "`, but this label doesn't exist.\n" + - " Available labels are " + - Array.from(allLabels).join(", ") + usedLabel + + "`, but this label doesn't exist.\n" + + " Available labels are " + + Array.from(allLabels).join(", "), ) } seen.add(usedLabel) @@ -668,7 +689,7 @@ export class AddEditingElements extends DesugaringStep { super( "Add some editing elements, such as the delete button or the move button if they are configured. These used to be handled by the feature info box, but this has been replaced by special visualisation elements", [], - "AddEditingElements" + "AddEditingElements", ) this._desugaring = desugaring this.builtinQuestions = Array.from(this._desugaring.tagRenderings?.values() ?? []) @@ -698,13 +719,13 @@ export class AddEditingElements extends DesugaringStep { json.tagRenderings = [...(json.tagRenderings ?? [])] const allIds = new Set(json.tagRenderings.map((tr) => tr["id"])) const specialVisualisations = ValidationUtils.getAllSpecialVisualisations( - json.tagRenderings + json.tagRenderings, ) const usedSpecialFunctions = new Set( specialVisualisations.map((sv) => - typeof sv === "string" ? undefined : sv.func.funcName - ) + typeof sv === "string" ? undefined : sv.func.funcName, + ), ) /***** ADD TO TOP ****/ @@ -772,7 +793,7 @@ export class RewriteSpecial extends DesugaringStep { super( "Converts a 'special' translation into a regular translation which uses parameters", ["special"], - "RewriteSpecial" + "RewriteSpecial", ) } @@ -863,12 +884,12 @@ export class RewriteSpecial extends DesugaringStep { private static convertIfNeeded( input: | (object & { - special: { - type: string - } - }) + special: { + type: string + } + }) | any, - context: ConversionContext + context: ConversionContext, ): any { const special = input["special"] if (special === undefined) { @@ -878,7 +899,7 @@ export class RewriteSpecial extends DesugaringStep { const type = special["type"] if (type === undefined) { context.err( - "A 'special'-block should define 'type' to indicate which visualisation should be used" + "A 'special'-block should define 'type' to indicate which visualisation should be used", ) return undefined } @@ -888,10 +909,10 @@ export class RewriteSpecial extends DesugaringStep { const options = Utils.sortedByLevenshteinDistance( type, SpecialVisualizations.specialVisualizations, - (sp) => sp.funcName + (sp) => sp.funcName, ) context.err( - `Special visualisation '${type}' not found. Did you perhaps mean ${options[0].funcName}, ${options[1].funcName} or ${options[2].funcName}?\n\tFor all known special visualisations, please see https://github.com/pietervdvn/MapComplete/blob/develop/Docs/SpecialRenderings.md` + `Special visualisation '${type}' not found. Did you perhaps mean ${options[0].funcName}, ${options[1].funcName} or ${options[2].funcName}?\n\tFor all known special visualisations, please see https://github.com/pietervdvn/MapComplete/blob/develop/Docs/SpecialRenderings.md`, ) return undefined } @@ -912,7 +933,7 @@ export class RewriteSpecial extends DesugaringStep { const byDistance = Utils.sortedByLevenshteinDistance( wrongArg, argNamesList, - (x) => x + (x) => x, ) return `Unexpected argument in special block at ${context} with name '${wrongArg}'. Did you mean ${ byDistance[0] @@ -931,8 +952,8 @@ export class RewriteSpecial extends DesugaringStep { `Obligated parameter '${arg.name}' in special rendering of type ${ vis.funcName } not found.\n The full special rendering specification is: '${JSON.stringify( - input - )}'\n ${arg.name}: ${arg.doc}` + input, + )}'\n ${arg.name}: ${arg.doc}`, ) } } @@ -1034,7 +1055,7 @@ export class RewriteSpecial extends DesugaringStep { continue } Utils.WalkPath(path.path, json, (leaf, travelled) => - RewriteSpecial.convertIfNeeded(leaf, context.enter(travelled)) + RewriteSpecial.convertIfNeeded(leaf, context.enter(travelled)), ) } @@ -1068,7 +1089,7 @@ class ExpandIconBadges extends DesugaringStep { } = badgesJson[i] const expanded = this._expand.convert( iconBadge.then, - context.enters("iconBadges", i) + context.enters("iconBadges", i), ) if (expanded === undefined) { iconBadges.push(iconBadge) @@ -1079,7 +1100,7 @@ class ExpandIconBadges extends DesugaringStep { ...expanded.map((resolved) => ({ if: iconBadge.if, then: resolved, - })) + })), ) } @@ -1096,11 +1117,11 @@ class PreparePointRendering extends Fuse { new Each( new On( "icon", - new FirstOf(new ExpandTagRendering(state, layer, { applyCondition: false })) - ) - ) + new FirstOf(new ExpandTagRendering(state, layer, { applyCondition: false })), + ), + ), ), - new ExpandIconBadges(state, layer) + new ExpandIconBadges(state, layer), ) } } @@ -1110,7 +1131,7 @@ class SetFullNodeDatabase extends DesugaringStep { super( "sets the fullNodeDatabase-bit if needed", ["fullNodeDatabase"], - "SetFullNodeDatabase" + "SetFullNodeDatabase", ) } @@ -1139,7 +1160,7 @@ class ExpandMarkerRenderings extends DesugaringStep { super( "Expands tagRenderings in the icons, if needed", ["icon", "color"], - "ExpandMarkerRenderings" + "ExpandMarkerRenderings", ) this._layer = layer this._state = state @@ -1171,7 +1192,7 @@ class AddFavouriteBadges extends DesugaringStep { super( "Adds the favourite heart to the title and the rendering badges", [], - "AddFavouriteBadges" + "AddFavouriteBadges", ) } @@ -1196,7 +1217,7 @@ export class AddRatingBadge extends DesugaringStep { super( "Adds the 'rating'-element if a reviews-element is used in the tagRenderings", ["titleIcons"], - "AddRatingBadge" + "AddRatingBadge", ) } @@ -1215,8 +1236,8 @@ export class AddRatingBadge extends DesugaringStep { const specialVis: Exclude[] = < Exclude[] - >ValidationUtils.getAllSpecialVisualisations(json.tagRenderings).filter( - (rs) => typeof rs !== "string" + >ValidationUtils.getAllSpecialVisualisations(json.tagRenderings).filter( + (rs) => typeof rs !== "string", ) const funcs = new Set(specialVis.map((rs) => rs.func.funcName)) @@ -1232,12 +1253,12 @@ export class AutoTitleIcon extends DesugaringStep { super( "The auto-icon creates a (non-clickable) title icon based on a tagRendering which has icons", ["titleIcons"], - "AutoTitleIcon" + "AutoTitleIcon", ) } private createTitleIconsBasedOn( - tr: QuestionableTagRenderingConfigJson + tr: QuestionableTagRenderingConfigJson, ): TagRenderingConfigJson | undefined { const mappings: { if: TagConfigJson; then: string }[] = tr.mappings ?.filter((m) => m.icon !== undefined) @@ -1267,7 +1288,7 @@ export class AutoTitleIcon extends DesugaringStep { return undefined } return this.createTitleIconsBasedOn(tr) - }) + }), ) json.titleIcons.splice(allAutoIndex, 1, ...generated) return json @@ -1296,8 +1317,8 @@ export class AutoTitleIcon extends DesugaringStep { .enters("titleIcons", i) .warn( "TagRendering with id " + - trId + - " does not have any icons, not generating an icon for this" + trId + + " does not have any icons, not generating an icon for this", ) continue } @@ -1312,7 +1333,7 @@ class DeriveSource extends DesugaringStep { super( "If no source is given, automatically derives the osmTags by 'or'-ing all the preset tags", ["source"], - "DeriveSource" + "DeriveSource", ) } @@ -1322,7 +1343,7 @@ class DeriveSource extends DesugaringStep { } if (!json.presets) { context.err( - "No source tags given. Trying to derive the source-tags based on the presets, but no presets are given" + "No source tags given. Trying to derive the source-tags based on the presets, but no presets are given", ) return json } @@ -1348,7 +1369,7 @@ class DeriveSource extends DesugaringStep { export class PrepareLayer extends Fuse { constructor( state: DesugaringContext, - options?: { addTagRenderingsToContext?: false | boolean } + options?: { addTagRenderingsToContext?: false | boolean }, ) { super( "Fully prepares and expands a layer for the LayerConfig.", @@ -1361,8 +1382,8 @@ export class PrepareLayer extends Fuse { new Concat( new ExpandTagRendering(state, layer, { addToContext: options?.addTagRenderingsToContext ?? false, - }) - ) + }), + ), ), new On("tagRenderings", new Each(new DetectInline())), new AddQuestionBox(), @@ -1375,11 +1396,11 @@ export class PrepareLayer extends Fuse { new On( "pointRendering", (layer) => - new Each(new On("marker", new Each(new ExpandMarkerRenderings(state, layer)))) + new Each(new On("marker", new Each(new ExpandMarkerRenderings(state, layer)))), ), new On( "pointRendering", - (layer) => new Each(new PreparePointRendering(state, layer)) + (layer) => new Each(new PreparePointRendering(state, layer)), ), new SetDefault("titleIcons", ["icons.defaults"]), new AddRatingBadge(), @@ -1388,9 +1409,9 @@ export class PrepareLayer extends Fuse { new On( "titleIcons", (layer) => - new Concat(new ExpandTagRendering(state, layer, { noHardcodedStrings: true })) + new Concat(new ExpandTagRendering(state, layer, { noHardcodedStrings: true })), ), - new ExpandFilter(state) + new ExpandFilter(state), ) } } diff --git a/src/Models/ThemeConfig/FilterConfig.ts b/src/Models/ThemeConfig/FilterConfig.ts index deb67fc03..306bd50de 100644 --- a/src/Models/ThemeConfig/FilterConfig.ts +++ b/src/Models/ThemeConfig/FilterConfig.ts @@ -14,6 +14,7 @@ export type FilterConfigOption = { question: Translation searchTerms: Record icon?: string + emoji?: string osmTags: TagsFilter | undefined /* Only set if fields are present. Used to create `osmTags` (which are used to _actually_ filter) when the field is written*/ readonly originalTagsSpec: TagConfigJson @@ -108,7 +109,8 @@ export default class FilterConfig { searchTerms: option.searchTerms, fields, originalTagsSpec: option.osmTags, - icon: option.icon + icon: option.icon, + emoji: option.emoji, } }) diff --git a/src/Models/ThemeConfig/Json/FilterConfigJson.ts b/src/Models/ThemeConfig/Json/FilterConfigJson.ts index 04a800edd..c12ff126e 100644 --- a/src/Models/ThemeConfig/Json/FilterConfigJson.ts +++ b/src/Models/ThemeConfig/Json/FilterConfigJson.ts @@ -1,6 +1,20 @@ import { TagConfigJson } from "./TagConfigJson" import { Translatable } from "./Translatable" - +export interface FilterConfigOptionJson { + question: Translatable + searchTerms?: Record + emoji?: string + icon?: string + osmTags?: TagConfigJson + default?: boolean + fields?: { + /** + * If name is `search`, use "_first_comment~.*{search}.*" as osmTags + */ + name: string + type?: string | "string" + }[] +} export default interface FilterConfigJson { /** * An id/name for this filter, used to set the URL parameters @@ -34,20 +48,7 @@ export default interface FilterConfigJson { * } * ``` */ - options: { - question: Translatable - searchTerms?: Record - icon?: string - osmTags?: TagConfigJson - default?: boolean - fields?: { - /** - * If name is `search`, use "_first_comment~.*{search}.*" as osmTags - */ - name: string - type?: string | "string" - }[] - }[] + options: FilterConfigOptionJson[] /** * Used for comments or to disable a check diff --git a/src/Models/ThemeConfig/Json/TagRenderingConfigJson.ts b/src/Models/ThemeConfig/Json/TagRenderingConfigJson.ts index 645b0eaca..22cfc1f11 100644 --- a/src/Models/ThemeConfig/Json/TagRenderingConfigJson.ts +++ b/src/Models/ThemeConfig/Json/TagRenderingConfigJson.ts @@ -226,5 +226,5 @@ export interface TagRenderingConfigJson { /** * This tagRendering can introduce this builtin filter */ - filter?: string[] + filter?: string[] | true } diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index ff4d9c47c..9f7179ea7 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -385,11 +385,11 @@ export default class ThemeViewState implements SpecialVisualizationState { this.toCacheSavers = layout.enableCache ? this.initSaveToLocalStorage() : undefined this.geosearch = new CombinedSearcher( - new CoordinateSearch(), new FilterSearch(this), - //new LocalElementSearch(this, 5), - //new OpenStreetMapIdSearch(this), - // new PhotonSearch(), // new NominatimGeocoding(), + new LocalElementSearch(this, 5), + new CoordinateSearch(), + new OpenStreetMapIdSearch(this), + new PhotonSearch(), // new NominatimGeocoding(), this.featureSwitches.featureSwitchBackToThemeOverview.data ? new ThemeSearch(this) : undefined ) @@ -652,7 +652,6 @@ export default class ThemeViewState implements SpecialVisualizationState { }, Translations.t.hotkeyDocumentation.openFilterPanel, () => { - console.log("S pressed") if (this.featureSwitches.featureSwitchFilter.data) { this.guistate.openFilterView() } diff --git a/src/UI/BigComponents/Filterview.svelte b/src/UI/BigComponents/Filterview.svelte index 098e72007..6ff175d61 100644 --- a/src/UI/BigComponents/Filterview.svelte +++ b/src/UI/BigComponents/Filterview.svelte @@ -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 = new ImmutableStore(undefined) @@ -76,8 +77,8 @@ {#each filter.options as option, i} diff --git a/src/UI/Search/ActiveFilter.svelte b/src/UI/Search/ActiveFilter.svelte index 4787974b4..88540bc5d 100644 --- a/src/UI/Search/ActiveFilter.svelte +++ b/src/UI/Search/ActiveFilter.svelte @@ -1,20 +1,20 @@ -{ console.log( "dismiss"); return control.setData(undefined) }}> - - +
+ + +
diff --git a/src/UI/Search/ActiveFilters.svelte b/src/UI/Search/ActiveFilters.svelte index 57a95e4c4..f463c11fd 100644 --- a/src/UI/Search/ActiveFilters.svelte +++ b/src/UI/Search/ActiveFilters.svelte @@ -1,17 +1,24 @@ -
+{#if activeFilters.length > 0} +
+ {#each activeFilters as activeFilter (activeFilter)} + + {/each} - {#each $activeFilters as activeFilter (activeFilter)} - - {/each} -
+ +
+ +{/if} diff --git a/src/UI/Search/FilterOption.svelte b/src/UI/Search/FilterOption.svelte new file mode 100644 index 000000000..498dba26c --- /dev/null +++ b/src/UI/Search/FilterOption.svelte @@ -0,0 +1,10 @@ + + + + diff --git a/src/UI/Search/FilterResult.svelte b/src/UI/Search/FilterResult.svelte index 8696a0db4..df521909c 100644 --- a/src/UI/Search/FilterResult.svelte +++ b/src/UI/Search/FilterResult.svelte @@ -1,14 +1,10 @@ + diff --git a/src/UI/Search/Geosearch.svelte b/src/UI/Search/Geosearch.svelte index e1199cd28..fac7b8134 100644 --- a/src/UI/Search/Geosearch.svelte +++ b/src/UI/Search/Geosearch.svelte @@ -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 | undefined = undefined export let bounds: UIEventSource @@ -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 = 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) diff --git a/src/UI/Search/SearchResult.svelte b/src/UI/Search/SearchResult.svelte index db14788e7..24869e8ad 100644 --- a/src/UI/Search/SearchResult.svelte +++ b/src/UI/Search/SearchResult.svelte @@ -11,10 +11,9 @@ {#if entry.category === "theme"} - + {:else if entry.category === "filter"} - + {:else} - - + {/if} diff --git a/src/UI/Search/SearchResultUtils.ts b/src/UI/Search/SearchResultUtils.ts new file mode 100644 index 000000000..242238a2a --- /dev/null +++ b/src/UI/Search/SearchResultUtils.ts @@ -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) + } + } +} diff --git a/src/UI/Search/SearchResults.svelte b/src/UI/Search/SearchResults.svelte index 2a71ca11a..dbf05a888 100644 --- a/src/UI/Search/SearchResults.svelte +++ b/src/UI/Search/SearchResults.svelte @@ -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 export let isFocused: UIEventSource - let hasActiveFilters = state.layerState.activeFilters.map(af => af.length > 0) + let activeFilters: Store = state.layerState.activeFilters.map(fs => fs.filter(f => Constants.priviliged_layers.indexOf(f.layer.id) < 0)) - console.log("Results are", results) + let hasActiveFilters = activeFilters.map(afs => afs.length > 0) let recentlySeen: Store = state.recentlySearched.seenThisSession let recentThemes = state.userRelatedState.recentlyVisitedThemes.mapD(thms => thms.filter(th => th !== state.layout.id).slice(0, 3)) @@ -24,7 +26,7 @@