From cdc1e05499ffc41d093503ccd24defa347eea50e Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 27 Aug 2024 21:33:47 +0200 Subject: [PATCH] Fix: actually search for keywords in theme view --- assets/layers/toilet/toilet.json | 25 +++ scripts/generateLayerOverview.ts | 190 +++++++++++------- .../ThemeConfig/Json/LayerConfigJson.ts | 10 +- src/Models/ThemeConfig/LayerConfig.ts | 3 +- src/Models/ThemeConfig/LayoutConfig.ts | 2 +- src/UI/BigComponents/MoreScreen.ts | 46 +++-- src/UI/BigComponents/ThemeButton.svelte | 4 +- src/UI/BigComponents/ThemesList.svelte | 4 +- src/Utils.ts | 4 +- 9 files changed, 193 insertions(+), 95 deletions(-) diff --git a/assets/layers/toilet/toilet.json b/assets/layers/toilet/toilet.json index 3e386b052..21a585d8c 100644 --- a/assets/layers/toilet/toilet.json +++ b/assets/layers/toilet/toilet.json @@ -25,6 +25,31 @@ "cs": "Vrstva zobrazující (veřejné) toalety", "sl": "Prikaz (javnih) stranišč" }, + "searchTerms": { + "en": [ + "Toilets", + "Bathroom", + "Lavatory", + "Water Closet", + "outhouse", + "privy", + "head", + "latrine", + "WC", + "W.C." + ], + "nl": [ + "WC", + "WCs", + "plee", + "gemak", + "opschik", + "kabinet", + "latrine", + "retirade", + "piesemopsantee" + ] + }, "source": { "osmTags": "amenity=toilets" }, diff --git a/scripts/generateLayerOverview.ts b/scripts/generateLayerOverview.ts index 3b57b6f42..7b52805e4 100644 --- a/scripts/generateLayerOverview.ts +++ b/scripts/generateLayerOverview.ts @@ -34,6 +34,10 @@ import Translations from "../src/UI/i18n/Translations" import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable" import { ValidateThemeAndLayers } from "../src/Models/ThemeConfig/Conversion/ValidateThemeAndLayers" import { ExtractImages } from "../src/Models/ThemeConfig/Conversion/FixImages" +import { + MinimalTagRenderingConfigJson, + TagRenderingConfigJson, +} from "../src/Models/ThemeConfig/Json/TagRenderingConfigJson" // This scripts scans 'src/assets/layers/*.json' for layer definition files and 'src/assets/themes/*.json' for theme definition files. // It spits out an overview of those to be used to load them @@ -56,7 +60,7 @@ class ParseLayer extends Conversion< convert( path: string, - context: ConversionContext + context: ConversionContext, ): { parsed: LayerConfig raw: LayerConfigJson @@ -85,7 +89,7 @@ class ParseLayer extends Conversion< context .enter("source") .err( - "No source is configured. (Tags might be automatically derived if presets are given)" + "No source is configured. (Tags might be automatically derived if presets are given)", ) return undefined } @@ -116,7 +120,7 @@ class AddIconSummary extends DesugaringStep<{ raw: LayerConfigJson; parsed: Laye const fixed = json.raw const layerConfig = json.parsed const pointRendering: PointRenderingConfig = layerConfig.mapRendering.find((pr) => - pr.location.has("point") + pr.location.has("point"), ) const defaultTags = layerConfig.GetBaseTags() fixed["_layerIcon"] = Utils.NoNull( @@ -131,7 +135,7 @@ class AddIconSummary extends DesugaringStep<{ raw: LayerConfigJson; parsed: Laye result["color"] = c } return result - }) + }), ) return { raw: fixed, parsed: layerConfig } } @@ -153,7 +157,7 @@ class LayerOverviewUtils extends Script { private static extractLayerIdsFrom( themeFile: LayoutConfigJson, - includeInlineLayers = true + includeInlineLayers = true, ): string[] { const publicLayerIds: string[] = [] if (!Array.isArray(themeFile.layers)) { @@ -220,19 +224,63 @@ class LayerOverviewUtils extends Script { | LayerConfigJson | string | { - builtin - } - )[] - }[] + builtin + } + )[] + }[], ) { const perId = new Map() for (const theme of themes) { - const keywords: {}[] = [] + const keywords: Record = {} + + function addWord(language: string, word: string | string[]) { + if(Array.isArray(word)){ + word.forEach(w => addWord(language, w)) + return + } + + word = Utils.SubstituteKeys(word, {}).trim() + if(!word){ + return + } + console.log(language, "--->", word) + if (!keywords[language]) { + keywords[language] = [] + } + keywords[language].push(word) + } + + function addWords(tr: string | Record | Record | TagRenderingConfigJson) { + if(!tr){ + return + } + if (typeof tr === "string") { + addWord("*", tr) + return + } + if (tr["render"] !== undefined || tr["mappings"] !== undefined) { + tr = tr + addWords(tr.render) + for (let mapping of tr.mappings ?? []) { + if (typeof mapping === "string") { + addWords(mapping) + continue + } + addWords(mapping.then) + } + return + } + for (const lang in tr) { + addWord(lang, tr[lang]) + } + } + for (const layer of theme.layers ?? []) { const l = layer - keywords.push({ "*": l.id }) - keywords.push(l.title) - keywords.push(l.description) + addWord("*", l.id) + addWords(l.title) + addWords(l.description) + addWords(l.searchTerms) } const data = { @@ -242,7 +290,7 @@ class LayerOverviewUtils extends Script { icon: theme.icon, hideFromOverview: theme.hideFromOverview, mustHaveLanguage: theme.mustHaveLanguage, - keywords: Utils.NoNull(keywords), + keywords, } perId.set(theme.id, data) } @@ -264,7 +312,7 @@ class LayerOverviewUtils extends Script { writeFileSync( "./src/assets/generated/theme_overview.json", JSON.stringify(sorted, null, " "), - { encoding: "utf8" } + { encoding: "utf8" }, ) } @@ -276,7 +324,7 @@ class LayerOverviewUtils extends Script { writeFileSync( `${LayerOverviewUtils.themePath}${theme.id}.json`, JSON.stringify(theme, null, " "), - { encoding: "utf8" } + { encoding: "utf8" }, ) } @@ -287,12 +335,12 @@ class LayerOverviewUtils extends Script { writeFileSync( `${LayerOverviewUtils.layerPath}${layer.id}.json`, JSON.stringify(layer, null, " "), - { encoding: "utf8" } + { encoding: "utf8" }, ) } static asDict( - trs: QuestionableTagRenderingConfigJson[] + trs: QuestionableTagRenderingConfigJson[], ): Map { const d = new Map() for (const tr of trs) { @@ -305,12 +353,12 @@ class LayerOverviewUtils extends Script { getSharedTagRenderings( doesImageExist: DoesImageExist, bootstrapTagRenderings: Map, - bootstrapTagRenderingsOrder: string[] + bootstrapTagRenderingsOrder: string[], ): QuestionableTagRenderingConfigJson[] getSharedTagRenderings( doesImageExist: DoesImageExist, bootstrapTagRenderings: Map = null, - bootstrapTagRenderingsOrder: string[] = [] + bootstrapTagRenderingsOrder: string[] = [], ): QuestionableTagRenderingConfigJson[] { const prepareLayer = new PrepareLayer( { @@ -321,7 +369,7 @@ class LayerOverviewUtils extends Script { }, { addTagRenderingsToContext: true, - } + }, ) const path = "assets/layers/questions/questions.json" @@ -341,7 +389,7 @@ class LayerOverviewUtils extends Script { return this.getSharedTagRenderings( doesImageExist, dict, - sharedQuestions.tagRenderings.map((tr) => tr["id"]) + sharedQuestions.tagRenderings.map((tr) => tr["id"]), ) } @@ -381,8 +429,8 @@ class LayerOverviewUtils extends Script { if (contents.indexOf(" 0) { console.warn( "The SVG at " + - path + - " contains a `text`-tag. This is highly discouraged. Every machine viewing your theme has their own font libary, and the font you choose might not be present, resulting in a different font being rendered. Solution: open your .svg in inkscape (or another program), select the text and convert it to a path" + path + + " contains a `text`-tag. This is highly discouraged. Every machine viewing your theme has their own font libary, and the font you choose might not be present, resulting in a different font being rendered. Solution: open your .svg in inkscape (or another program), select the text and convert it to a path", ) errCount++ } @@ -398,14 +446,14 @@ class LayerOverviewUtils extends Script { args .find((a) => a.startsWith("--themes=")) ?.substring("--themes=".length) - ?.split(",") ?? [] + ?.split(",") ?? [], ) const layerWhitelist = new Set( args .find((a) => a.startsWith("--layers=")) ?.substring("--layers=".length) - ?.split(",") ?? [] + ?.split(",") ?? [], ) const forceReload = args.some((a) => a == "--force") @@ -440,11 +488,11 @@ class LayerOverviewUtils extends Script { sharedLayers, recompiledThemes, forceReload, - themeWhitelist + themeWhitelist, ) new ValidateThemeEnsemble().convertStrict( - Array.from(sharedThemes.values()).map((th) => new LayoutConfig(th, true)) + Array.from(sharedThemes.values()).map((th) => new LayoutConfig(th, true)), ) if (recompiledThemes.length > 0) { @@ -452,7 +500,7 @@ class LayerOverviewUtils extends Script { "./src/assets/generated/known_layers.json", JSON.stringify({ layers: Array.from(sharedLayers.values()).filter((l) => l.id !== "favourite"), - }) + }), ) } @@ -473,7 +521,7 @@ class LayerOverviewUtils extends Script { const proto: LayoutConfigJson = JSON.parse( readFileSync("./assets/themes/mapcomplete-changes/mapcomplete-changes.proto.json", { encoding: "utf8", - }) + }), ) const protolayer = ( proto.layers.filter((l) => l["id"] === "mapcomplete-changes")[0] @@ -490,12 +538,12 @@ class LayerOverviewUtils extends Script { layers: ScriptUtils.getLayerFiles().map((f) => f.parsed), themes: ScriptUtils.getThemeFiles().map((f) => f.parsed), }, - ConversionContext.construct([], []) + ConversionContext.construct([], []), ) for (const [_, theme] of sharedThemes) { theme.layers = theme.layers.filter( - (l) => Constants.added_by_default.indexOf(l["id"]) < 0 + (l) => Constants.added_by_default.indexOf(l["id"]) < 0, ) } @@ -504,7 +552,7 @@ class LayerOverviewUtils extends Script { "./src/assets/generated/known_themes.json", JSON.stringify({ themes: Array.from(sharedThemes.values()), - }) + }), ) } @@ -516,7 +564,7 @@ class LayerOverviewUtils extends Script { private parseLayer( doesImageExist: DoesImageExist, prepLayer: PrepareLayer, - sharedLayerPath: string + sharedLayerPath: string, ): { raw: LayerConfigJson parsed: LayerConfig @@ -527,7 +575,7 @@ class LayerOverviewUtils extends Script { const parsed = parser.convertStrict(sharedLayerPath, context) const result = AddIconSummary.singleton.convertStrict( parsed, - context.inOperation("AddIconSummary") + context.inOperation("AddIconSummary"), ) return { ...result, context } } @@ -535,7 +583,7 @@ class LayerOverviewUtils extends Script { private buildLayerIndex( doesImageExist: DoesImageExist, forceReload: boolean, - whitelist: Set + whitelist: Set, ): Map { // First, we expand and validate all builtin layers. These are written to src/assets/generated/layers // At the same time, an index of available layers is built. @@ -590,17 +638,17 @@ class LayerOverviewUtils extends Script { console.log( "Recompiled layers " + - recompiledLayers.join(", ") + - " and skipped " + - skippedLayers.length + - " layers. Detected " + - warningCount + - " warnings" + recompiledLayers.join(", ") + + " and skipped " + + skippedLayers.length + + " layers. Detected " + + warningCount + + " warnings", ) // We always need the calculated tags of 'usersettings', so we export them separately this.extractJavascriptCodeForLayer( state.sharedLayers.get("usersettings"), - "./src/Logic/State/UserSettingsMetaTagging.ts" + "./src/Logic/State/UserSettingsMetaTagging.ts", ) return sharedLayers @@ -617,8 +665,8 @@ class LayerOverviewUtils extends Script { private extractJavascriptCode(themeFile: LayoutConfigJson) { const allCode = [ "import {Feature} from 'geojson'", - 'import { ExtraFuncType } from "../../../Logic/ExtraFunctions";', - 'import { Utils } from "../../../Utils"', + "import { ExtraFuncType } from \"../../../Logic/ExtraFunctions\";", + "import { Utils } from \"../../../Utils\"", "export class ThemeMetaTagging {", " public static readonly themeName = " + JSON.stringify(themeFile.id), "", @@ -630,8 +678,8 @@ class LayerOverviewUtils extends Script { allCode.push( " public metaTaggging_for_" + - id + - "(feat: Feature, helperFunctions: Record Function>) {" + id + + "(feat: Feature, helperFunctions: Record Function>) {", ) allCode.push(" const {" + ExtraFunctions.types.join(", ") + "} = helperFunctions") for (const line of code) { @@ -642,10 +690,10 @@ class LayerOverviewUtils extends Script { if (!isStrict) { allCode.push( " Utils.AddLazyProperty(feat.properties, '" + - attributeName + - "', () => " + - expression + - " ) " + attributeName + + "', () => " + + expression + + " ) ", ) } else { attributeName = attributeName.substring(0, attributeName.length - 1).trim() @@ -690,7 +738,7 @@ class LayerOverviewUtils extends Script { const code = l.calculatedTags ?? [] allCode.push( - " public metaTaggging_for_" + l.id + "(feat: {properties: Record}) {" + " public metaTaggging_for_" + l.id + "(feat: {properties: Record}) {", ) for (const line of code) { const firstEq = line.indexOf("=") @@ -700,10 +748,10 @@ class LayerOverviewUtils extends Script { if (!isStrict) { allCode.push( " Utils.AddLazyProperty(feat.properties, '" + - attributeName + - "', () => " + - expression + - " ) " + attributeName + + "', () => " + + expression + + " ) ", ) } else { attributeName = attributeName.substring(0, attributeName.length - 2).trim() @@ -728,14 +776,14 @@ class LayerOverviewUtils extends Script { sharedLayers: Map, recompiledThemes: string[], forceReload: boolean, - whitelist: Set + whitelist: Set, ): Map { console.log(" ---------- VALIDATING BUILTIN THEMES ---------") const themeFiles = ScriptUtils.getThemeFiles() const fixed = new Map() const publicLayers = LayerOverviewUtils.publicLayerIdsFrom( - themeFiles.map((th) => th.parsed) + themeFiles.map((th) => th.parsed), ) const trs = this.getSharedTagRenderings(new DoesImageExist(licensePaths, existsSync)) @@ -775,15 +823,15 @@ class LayerOverviewUtils extends Script { LayerOverviewUtils.themePath + "/" + themePath.substring(themePath.lastIndexOf("/")) const usedLayers = Array.from( - LayerOverviewUtils.extractLayerIdsFrom(themeFile, false) + LayerOverviewUtils.extractLayerIdsFrom(themeFile, false), ).map((id) => LayerOverviewUtils.layerPath + id + ".json") if (!forceReload && !this.shouldBeUpdated([themePath, ...usedLayers], targetPath)) { fixed.set( themeFile.id, JSON.parse( - readFileSync(LayerOverviewUtils.themePath + themeFile.id + ".json", "utf8") - ) + readFileSync(LayerOverviewUtils.themePath + themeFile.id + ".json", "utf8"), + ), ) ScriptUtils.erasableLog("Skipping", themeFile.id) skippedThemes.push(themeFile.id) @@ -794,23 +842,23 @@ class LayerOverviewUtils extends Script { new PrevalidateTheme().convertStrict( themeFile, - ConversionContext.construct([themePath], ["PrepareLayer"]) + ConversionContext.construct([themePath], ["PrepareLayer"]), ) try { themeFile = new PrepareTheme(convertState, { skipDefaultLayers: true, }).convertStrict( themeFile, - ConversionContext.construct([themePath], ["PrepareLayer"]) + ConversionContext.construct([themePath], ["PrepareLayer"]), ) new ValidateThemeAndLayers( new DoesImageExist(licensePaths, existsSync, knownTagRenderings), themePath, true, - knownTagRenderings + knownTagRenderings, ).convertStrict( themeFile, - ConversionContext.construct([themePath], ["PrepareLayer"]) + ConversionContext.construct([themePath], ["PrepareLayer"]), ) if (themeFile.icon.endsWith(".svg")) { @@ -860,7 +908,7 @@ class LayerOverviewUtils extends Script { const usedImages = Utils.Dedup( new ExtractImages(true, knownTagRenderings) .convertStrict(themeFile) - .map((x) => x.path) + .map((x) => x.path), ) usedImages.sort() @@ -886,16 +934,16 @@ class LayerOverviewUtils extends Script { t.shortDescription ?? new Translation(t.description).FirstSentence(), mustHaveLanguage: t.mustHaveLanguage?.length > 0, } - }) + }), ) } console.log( "Recompiled themes " + - recompiledThemes.join(", ") + - " and skipped " + - skippedThemes.length + - " themes" + recompiledThemes.join(", ") + + " and skipped " + + skippedThemes.length + + " themes", ) return fixed diff --git a/src/Models/ThemeConfig/Json/LayerConfigJson.ts b/src/Models/ThemeConfig/Json/LayerConfigJson.ts index e7df9a562..3ddd140c4 100644 --- a/src/Models/ThemeConfig/Json/LayerConfigJson.ts +++ b/src/Models/ThemeConfig/Json/LayerConfigJson.ts @@ -41,14 +41,22 @@ export interface LayerConfigJson { name?: Translatable /** + * question: How would you describe the features that are shown on this layer? + * * A description for the features shown in this layer. * This often resembles the introduction of the wiki.osm.org-page for this feature. * * group: Basic - * question: How would you describe the features that are shown on this layer? */ description?: Translatable + /** + * question: What are some other terms used to describe these objects? + * + * This is used in the search functionality + */ + searchTerms?: Record + /** * Question: Where should the data be fetched from? * title: Data Source diff --git a/src/Models/ThemeConfig/LayerConfig.ts b/src/Models/ThemeConfig/LayerConfig.ts index 5624184c2..f6529d834 100644 --- a/src/Models/ThemeConfig/LayerConfig.ts +++ b/src/Models/ThemeConfig/LayerConfig.ts @@ -29,6 +29,7 @@ export default class LayerConfig extends WithContextLoader { public readonly id: string public readonly name: Translation public readonly description: Translation + public readonly searchTerms: Record /** * Only 'null' for special, privileged layers */ @@ -113,8 +114,8 @@ export default class LayerConfig extends WithContextLoader { json.description = undefined } } - this.description = Translations.T(json.description, translationContext + ".description") + this.searchTerms = json.searchTerms ?? {} this.calculatedTags = undefined if (json.calculatedTags !== undefined) { diff --git a/src/Models/ThemeConfig/LayoutConfig.ts b/src/Models/ThemeConfig/LayoutConfig.ts index 7c1ec9104..dccee99f2 100644 --- a/src/Models/ThemeConfig/LayoutConfig.ts +++ b/src/Models/ThemeConfig/LayoutConfig.ts @@ -25,7 +25,7 @@ export class MinimalLayoutInformation { definition?: Translatable mustHaveLanguage?: boolean hideFromOverview?: boolean - keywords?: (Translatable | TagRenderingConfigJson)[] + keywords?: Record } /** * Minimal information about a theme diff --git a/src/UI/BigComponents/MoreScreen.ts b/src/UI/BigComponents/MoreScreen.ts index 793240192..3101346b8 100644 --- a/src/UI/BigComponents/MoreScreen.ts +++ b/src/UI/BigComponents/MoreScreen.ts @@ -15,6 +15,7 @@ export default class MoreScreen { MoreScreen.officialThemesById.set(th.id, th) } } + public static applySearch(searchTerm: string) { searchTerm = searchTerm.toLowerCase() if (!searchTerm) { @@ -43,13 +44,13 @@ export default class MoreScreen { (th) => th.hideFromOverview == false && th.id !== "personal" && - MoreScreen.MatchesLayout(th, searchTerm) + MoreScreen.MatchesLayout(th, searchTerm), ) if (publicTheme !== undefined) { window.location.href = MoreScreen.createUrlFor(publicTheme, false) } const hiddenTheme = MoreScreen.officialThemes.find( - (th) => th.id !== "personal" && MoreScreen.MatchesLayout(th, searchTerm) + (th) => th.id !== "personal" && MoreScreen.MatchesLayout(th, searchTerm), ) if (hiddenTheme !== undefined) { window.location.href = MoreScreen.createUrlFor(hiddenTheme, false) @@ -57,34 +58,47 @@ export default class MoreScreen { } public static MatchesLayout( - layout: { - id: string - title: Translatable - shortDescription: Translatable - keywords?: (Translatable | TagRenderingConfigJson)[] - }, - search: string + layout: MinimalLayoutInformation, + search: string, + language?: string, ): boolean { if (search === undefined) { return true } - search = Utils.RemoveDiacritics(search.toLocaleLowerCase()) // See #1729 + search = Utils.simplifyStringForSearch(search.toLocaleLowerCase()) // See #1729 if (search.length > 3 && layout.id.toLowerCase().indexOf(search) >= 0) { return true } if (layout.id === "personal") { return false } - if(Utils.simplifyStringForSearch(layout.id) === Utils.simplifyStringForSearch(search)){ + if (Utils.simplifyStringForSearch(layout.id) === Utils.simplifyStringForSearch(search)) { return true } - const entitiesToSearch = [layout.shortDescription, layout.title, ...(layout.keywords ?? [])] + language ??= Locale.language.data + + const entitiesToSearch: (string | Record | Record)[] = [layout.shortDescription, layout.title, layout.keywords] for (const entity of entitiesToSearch) { if (entity === undefined) { continue } - const term: string = entity["*"] ?? entity[Locale.language.data] - if (Utils.RemoveDiacritics(term?.toLowerCase())?.indexOf(search) >= 0) { + + let term: string[] + if (typeof entity === "string") { + term = [entity] + } else { + const terms = [].concat(entity["*"], entity[language]) + if (Array.isArray(terms)) { + term = terms + } else { + term = [terms] + } + } + + const minLevehnstein = Math.min(...Utils.NoNull(term).map(t => Utils.levenshteinDistance(search, + Utils.simplifyStringForSearch(t).slice(0, search.length)))) + + if (minLevehnstein < 1 || minLevehnstein / search.length < 0.2) { return true } } @@ -95,7 +109,7 @@ export default class MoreScreen { public static createUrlFor( layout: { id: string }, isCustom: boolean, - state?: { layoutToUse?: { id } } + state?: { layoutToUse?: { id } }, ): string { if (layout === undefined) { return undefined @@ -141,7 +155,7 @@ export default class MoreScreen { new Set( Object.keys(preferences) .filter((key) => key.startsWith(prefix)) - .map((key) => key.substring(prefix.length, key.length - "-enabled".length)) + .map((key) => key.substring(prefix.length, key.length - "-enabled".length)), )) } } diff --git a/src/UI/BigComponents/ThemeButton.svelte b/src/UI/BigComponents/ThemeButton.svelte index 598a89176..2a71fcc1d 100644 --- a/src/UI/BigComponents/ThemeButton.svelte +++ b/src/UI/BigComponents/ThemeButton.svelte @@ -3,13 +3,13 @@ import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" import UserDetails, { OsmConnection } from "../../Logic/Osm/OsmConnection" import Constants from "../../Models/Constants" - import type { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig" + import type { MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig" import Tr from "../Base/Tr.svelte" import Translations from "../i18n/Translations" import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource" import Marker from "../Map/Marker.svelte" - export let theme: LayoutInformation + export let theme: MinimalLayoutInformation export let isCustom: boolean = false export let userDetails: UIEventSource export let state: { layoutToUse?: { id: string }; osmConnection: OsmConnection } diff --git a/src/UI/BigComponents/ThemesList.svelte b/src/UI/BigComponents/ThemesList.svelte index 29ba02ef6..24dc4552b 100644 --- a/src/UI/BigComponents/ThemesList.svelte +++ b/src/UI/BigComponents/ThemesList.svelte @@ -4,12 +4,12 @@ import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { UIEventSource } from "../../Logic/UIEventSource" import ThemeButton from "./ThemeButton.svelte" - import { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig" + import { LayoutInformation, MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig" import MoreScreen from "./MoreScreen" import themeOverview from "../../assets/generated/theme_overview.json" export let search: UIEventSource - export let themes: LayoutInformation[] + export let themes: MinimalLayoutInformation[] export let state: { osmConnection: OsmConnection } export let isCustom: boolean = false export let hideThemes: boolean = true diff --git a/src/Utils.ts b/src/Utils.ts index f853a6619..6e360fa07 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1602,6 +1602,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be * @constructor * * Utils.RemoveDiacritics("bâtiments") // => "batiments" + * Utils.RemoveDiacritics(undefined) // => undefined */ public static RemoveDiacritics(str?: string): string { // See #1729 @@ -1616,9 +1617,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be * @param str * Utils.simplifyStringForSearch("abc def; ghi 564") // => "abcdefghi564" * Utils.simplifyStringForSearch("âbc déf; ghi 564") // => "abcdefghi564" + * Utils.simplifyStringForSearch(undefined) // => undefined */ public static simplifyStringForSearch(str: string): string { - return Utils.RemoveDiacritics(str).toLowerCase().replace(/[^a-z0-9]/g, "") + return Utils.RemoveDiacritics(str)?.toLowerCase()?.replace(/[^a-z0-9]/g, "") } public static randomString(length: number): string {