Fix: actually search for keywords in theme view

This commit is contained in:
Pieter Vander Vennet 2024-08-27 21:33:47 +02:00
parent 821c1fabd7
commit cdc1e05499
9 changed files with 193 additions and 95 deletions

View file

@ -25,6 +25,31 @@
"cs": "Vrstva zobrazující (veřejné) toalety", "cs": "Vrstva zobrazující (veřejné) toalety",
"sl": "Prikaz (javnih) stranišč" "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": { "source": {
"osmTags": "amenity=toilets" "osmTags": "amenity=toilets"
}, },

View file

@ -34,6 +34,10 @@ import Translations from "../src/UI/i18n/Translations"
import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable" import { Translatable } from "../src/Models/ThemeConfig/Json/Translatable"
import { ValidateThemeAndLayers } from "../src/Models/ThemeConfig/Conversion/ValidateThemeAndLayers" import { ValidateThemeAndLayers } from "../src/Models/ThemeConfig/Conversion/ValidateThemeAndLayers"
import { ExtractImages } from "../src/Models/ThemeConfig/Conversion/FixImages" 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. // 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 // It spits out an overview of those to be used to load them
@ -56,7 +60,7 @@ class ParseLayer extends Conversion<
convert( convert(
path: string, path: string,
context: ConversionContext context: ConversionContext,
): { ): {
parsed: LayerConfig parsed: LayerConfig
raw: LayerConfigJson raw: LayerConfigJson
@ -85,7 +89,7 @@ class ParseLayer extends Conversion<
context context
.enter("source") .enter("source")
.err( .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 return undefined
} }
@ -116,7 +120,7 @@ class AddIconSummary extends DesugaringStep<{ raw: LayerConfigJson; parsed: Laye
const fixed = json.raw const fixed = json.raw
const layerConfig = json.parsed const layerConfig = json.parsed
const pointRendering: PointRenderingConfig = layerConfig.mapRendering.find((pr) => const pointRendering: PointRenderingConfig = layerConfig.mapRendering.find((pr) =>
pr.location.has("point") pr.location.has("point"),
) )
const defaultTags = layerConfig.GetBaseTags() const defaultTags = layerConfig.GetBaseTags()
fixed["_layerIcon"] = Utils.NoNull( fixed["_layerIcon"] = Utils.NoNull(
@ -131,7 +135,7 @@ class AddIconSummary extends DesugaringStep<{ raw: LayerConfigJson; parsed: Laye
result["color"] = c result["color"] = c
} }
return result return result
}) }),
) )
return { raw: fixed, parsed: layerConfig } return { raw: fixed, parsed: layerConfig }
} }
@ -153,7 +157,7 @@ class LayerOverviewUtils extends Script {
private static extractLayerIdsFrom( private static extractLayerIdsFrom(
themeFile: LayoutConfigJson, themeFile: LayoutConfigJson,
includeInlineLayers = true includeInlineLayers = true,
): string[] { ): string[] {
const publicLayerIds: string[] = [] const publicLayerIds: string[] = []
if (!Array.isArray(themeFile.layers)) { if (!Array.isArray(themeFile.layers)) {
@ -220,19 +224,63 @@ class LayerOverviewUtils extends Script {
| LayerConfigJson | LayerConfigJson
| string | string
| { | {
builtin builtin
} }
)[] )[]
}[] }[],
) { ) {
const perId = new Map<string, any>() const perId = new Map<string, any>()
for (const theme of themes) { for (const theme of themes) {
const keywords: {}[] = [] const keywords: Record<string, string[]> = {}
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<string, string> | Record<string, string[]> | TagRenderingConfigJson) {
if(!tr){
return
}
if (typeof tr === "string") {
addWord("*", tr)
return
}
if (tr["render"] !== undefined || tr["mappings"] !== undefined) {
tr = <TagRenderingConfigJson>tr
addWords(<Translatable>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 ?? []) { for (const layer of theme.layers ?? []) {
const l = <LayerConfigJson>layer const l = <LayerConfigJson>layer
keywords.push({ "*": l.id }) addWord("*", l.id)
keywords.push(l.title) addWords(l.title)
keywords.push(l.description) addWords(l.description)
addWords(l.searchTerms)
} }
const data = { const data = {
@ -242,7 +290,7 @@ class LayerOverviewUtils extends Script {
icon: theme.icon, icon: theme.icon,
hideFromOverview: theme.hideFromOverview, hideFromOverview: theme.hideFromOverview,
mustHaveLanguage: theme.mustHaveLanguage, mustHaveLanguage: theme.mustHaveLanguage,
keywords: Utils.NoNull(keywords), keywords,
} }
perId.set(theme.id, data) perId.set(theme.id, data)
} }
@ -264,7 +312,7 @@ class LayerOverviewUtils extends Script {
writeFileSync( writeFileSync(
"./src/assets/generated/theme_overview.json", "./src/assets/generated/theme_overview.json",
JSON.stringify(sorted, null, " "), JSON.stringify(sorted, null, " "),
{ encoding: "utf8" } { encoding: "utf8" },
) )
} }
@ -276,7 +324,7 @@ class LayerOverviewUtils extends Script {
writeFileSync( writeFileSync(
`${LayerOverviewUtils.themePath}${theme.id}.json`, `${LayerOverviewUtils.themePath}${theme.id}.json`,
JSON.stringify(theme, null, " "), JSON.stringify(theme, null, " "),
{ encoding: "utf8" } { encoding: "utf8" },
) )
} }
@ -287,12 +335,12 @@ class LayerOverviewUtils extends Script {
writeFileSync( writeFileSync(
`${LayerOverviewUtils.layerPath}${layer.id}.json`, `${LayerOverviewUtils.layerPath}${layer.id}.json`,
JSON.stringify(layer, null, " "), JSON.stringify(layer, null, " "),
{ encoding: "utf8" } { encoding: "utf8" },
) )
} }
static asDict( static asDict(
trs: QuestionableTagRenderingConfigJson[] trs: QuestionableTagRenderingConfigJson[],
): Map<string, QuestionableTagRenderingConfigJson> { ): Map<string, QuestionableTagRenderingConfigJson> {
const d = new Map<string, QuestionableTagRenderingConfigJson>() const d = new Map<string, QuestionableTagRenderingConfigJson>()
for (const tr of trs) { for (const tr of trs) {
@ -305,12 +353,12 @@ class LayerOverviewUtils extends Script {
getSharedTagRenderings( getSharedTagRenderings(
doesImageExist: DoesImageExist, doesImageExist: DoesImageExist,
bootstrapTagRenderings: Map<string, QuestionableTagRenderingConfigJson>, bootstrapTagRenderings: Map<string, QuestionableTagRenderingConfigJson>,
bootstrapTagRenderingsOrder: string[] bootstrapTagRenderingsOrder: string[],
): QuestionableTagRenderingConfigJson[] ): QuestionableTagRenderingConfigJson[]
getSharedTagRenderings( getSharedTagRenderings(
doesImageExist: DoesImageExist, doesImageExist: DoesImageExist,
bootstrapTagRenderings: Map<string, QuestionableTagRenderingConfigJson> = null, bootstrapTagRenderings: Map<string, QuestionableTagRenderingConfigJson> = null,
bootstrapTagRenderingsOrder: string[] = [] bootstrapTagRenderingsOrder: string[] = [],
): QuestionableTagRenderingConfigJson[] { ): QuestionableTagRenderingConfigJson[] {
const prepareLayer = new PrepareLayer( const prepareLayer = new PrepareLayer(
{ {
@ -321,7 +369,7 @@ class LayerOverviewUtils extends Script {
}, },
{ {
addTagRenderingsToContext: true, addTagRenderingsToContext: true,
} },
) )
const path = "assets/layers/questions/questions.json" const path = "assets/layers/questions/questions.json"
@ -341,7 +389,7 @@ class LayerOverviewUtils extends Script {
return this.getSharedTagRenderings( return this.getSharedTagRenderings(
doesImageExist, doesImageExist,
dict, dict,
sharedQuestions.tagRenderings.map((tr) => tr["id"]) sharedQuestions.tagRenderings.map((tr) => tr["id"]),
) )
} }
@ -381,8 +429,8 @@ class LayerOverviewUtils extends Script {
if (contents.indexOf("<text") > 0) { if (contents.indexOf("<text") > 0) {
console.warn( console.warn(
"The SVG at " + "The SVG at " +
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" " 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++ errCount++
} }
@ -398,14 +446,14 @@ class LayerOverviewUtils extends Script {
args args
.find((a) => a.startsWith("--themes=")) .find((a) => a.startsWith("--themes="))
?.substring("--themes=".length) ?.substring("--themes=".length)
?.split(",") ?? [] ?.split(",") ?? [],
) )
const layerWhitelist = new Set( const layerWhitelist = new Set(
args args
.find((a) => a.startsWith("--layers=")) .find((a) => a.startsWith("--layers="))
?.substring("--layers=".length) ?.substring("--layers=".length)
?.split(",") ?? [] ?.split(",") ?? [],
) )
const forceReload = args.some((a) => a == "--force") const forceReload = args.some((a) => a == "--force")
@ -440,11 +488,11 @@ class LayerOverviewUtils extends Script {
sharedLayers, sharedLayers,
recompiledThemes, recompiledThemes,
forceReload, forceReload,
themeWhitelist themeWhitelist,
) )
new ValidateThemeEnsemble().convertStrict( 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) { if (recompiledThemes.length > 0) {
@ -452,7 +500,7 @@ class LayerOverviewUtils extends Script {
"./src/assets/generated/known_layers.json", "./src/assets/generated/known_layers.json",
JSON.stringify({ JSON.stringify({
layers: Array.from(sharedLayers.values()).filter((l) => l.id !== "favourite"), layers: Array.from(sharedLayers.values()).filter((l) => l.id !== "favourite"),
}) }),
) )
} }
@ -473,7 +521,7 @@ class LayerOverviewUtils extends Script {
const proto: LayoutConfigJson = JSON.parse( const proto: LayoutConfigJson = JSON.parse(
readFileSync("./assets/themes/mapcomplete-changes/mapcomplete-changes.proto.json", { readFileSync("./assets/themes/mapcomplete-changes/mapcomplete-changes.proto.json", {
encoding: "utf8", encoding: "utf8",
}) }),
) )
const protolayer = <LayerConfigJson>( const protolayer = <LayerConfigJson>(
proto.layers.filter((l) => l["id"] === "mapcomplete-changes")[0] proto.layers.filter((l) => l["id"] === "mapcomplete-changes")[0]
@ -490,12 +538,12 @@ class LayerOverviewUtils extends Script {
layers: ScriptUtils.getLayerFiles().map((f) => f.parsed), layers: ScriptUtils.getLayerFiles().map((f) => f.parsed),
themes: ScriptUtils.getThemeFiles().map((f) => f.parsed), themes: ScriptUtils.getThemeFiles().map((f) => f.parsed),
}, },
ConversionContext.construct([], []) ConversionContext.construct([], []),
) )
for (const [_, theme] of sharedThemes) { for (const [_, theme] of sharedThemes) {
theme.layers = theme.layers.filter( 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", "./src/assets/generated/known_themes.json",
JSON.stringify({ JSON.stringify({
themes: Array.from(sharedThemes.values()), themes: Array.from(sharedThemes.values()),
}) }),
) )
} }
@ -516,7 +564,7 @@ class LayerOverviewUtils extends Script {
private parseLayer( private parseLayer(
doesImageExist: DoesImageExist, doesImageExist: DoesImageExist,
prepLayer: PrepareLayer, prepLayer: PrepareLayer,
sharedLayerPath: string sharedLayerPath: string,
): { ): {
raw: LayerConfigJson raw: LayerConfigJson
parsed: LayerConfig parsed: LayerConfig
@ -527,7 +575,7 @@ class LayerOverviewUtils extends Script {
const parsed = parser.convertStrict(sharedLayerPath, context) const parsed = parser.convertStrict(sharedLayerPath, context)
const result = AddIconSummary.singleton.convertStrict( const result = AddIconSummary.singleton.convertStrict(
parsed, parsed,
context.inOperation("AddIconSummary") context.inOperation("AddIconSummary"),
) )
return { ...result, context } return { ...result, context }
} }
@ -535,7 +583,7 @@ class LayerOverviewUtils extends Script {
private buildLayerIndex( private buildLayerIndex(
doesImageExist: DoesImageExist, doesImageExist: DoesImageExist,
forceReload: boolean, forceReload: boolean,
whitelist: Set<string> whitelist: Set<string>,
): Map<string, LayerConfigJson> { ): Map<string, LayerConfigJson> {
// First, we expand and validate all builtin layers. These are written to src/assets/generated/layers // 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. // At the same time, an index of available layers is built.
@ -590,17 +638,17 @@ class LayerOverviewUtils extends Script {
console.log( console.log(
"Recompiled layers " + "Recompiled layers " +
recompiledLayers.join(", ") + recompiledLayers.join(", ") +
" and skipped " + " and skipped " +
skippedLayers.length + skippedLayers.length +
" layers. Detected " + " layers. Detected " +
warningCount + warningCount +
" warnings" " warnings",
) )
// We always need the calculated tags of 'usersettings', so we export them separately // We always need the calculated tags of 'usersettings', so we export them separately
this.extractJavascriptCodeForLayer( this.extractJavascriptCodeForLayer(
state.sharedLayers.get("usersettings"), state.sharedLayers.get("usersettings"),
"./src/Logic/State/UserSettingsMetaTagging.ts" "./src/Logic/State/UserSettingsMetaTagging.ts",
) )
return sharedLayers return sharedLayers
@ -617,8 +665,8 @@ class LayerOverviewUtils extends Script {
private extractJavascriptCode(themeFile: LayoutConfigJson) { private extractJavascriptCode(themeFile: LayoutConfigJson) {
const allCode = [ const allCode = [
"import {Feature} from 'geojson'", "import {Feature} from 'geojson'",
'import { ExtraFuncType } from "../../../Logic/ExtraFunctions";', "import { ExtraFuncType } from \"../../../Logic/ExtraFunctions\";",
'import { Utils } from "../../../Utils"', "import { Utils } from \"../../../Utils\"",
"export class ThemeMetaTagging {", "export class ThemeMetaTagging {",
" public static readonly themeName = " + JSON.stringify(themeFile.id), " public static readonly themeName = " + JSON.stringify(themeFile.id),
"", "",
@ -630,8 +678,8 @@ class LayerOverviewUtils extends Script {
allCode.push( allCode.push(
" public metaTaggging_for_" + " public metaTaggging_for_" +
id + id +
"(feat: Feature, helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>) {" "(feat: Feature, helperFunctions: Record<ExtraFuncType, (feature: Feature) => Function>) {",
) )
allCode.push(" const {" + ExtraFunctions.types.join(", ") + "} = helperFunctions") allCode.push(" const {" + ExtraFunctions.types.join(", ") + "} = helperFunctions")
for (const line of code) { for (const line of code) {
@ -642,10 +690,10 @@ class LayerOverviewUtils extends Script {
if (!isStrict) { if (!isStrict) {
allCode.push( allCode.push(
" Utils.AddLazyProperty(feat.properties, '" + " Utils.AddLazyProperty(feat.properties, '" +
attributeName + attributeName +
"', () => " + "', () => " +
expression + expression +
" ) " " ) ",
) )
} else { } else {
attributeName = attributeName.substring(0, attributeName.length - 1).trim() attributeName = attributeName.substring(0, attributeName.length - 1).trim()
@ -690,7 +738,7 @@ class LayerOverviewUtils extends Script {
const code = l.calculatedTags ?? [] const code = l.calculatedTags ?? []
allCode.push( allCode.push(
" public metaTaggging_for_" + l.id + "(feat: {properties: Record<string, string>}) {" " public metaTaggging_for_" + l.id + "(feat: {properties: Record<string, string>}) {",
) )
for (const line of code) { for (const line of code) {
const firstEq = line.indexOf("=") const firstEq = line.indexOf("=")
@ -700,10 +748,10 @@ class LayerOverviewUtils extends Script {
if (!isStrict) { if (!isStrict) {
allCode.push( allCode.push(
" Utils.AddLazyProperty(feat.properties, '" + " Utils.AddLazyProperty(feat.properties, '" +
attributeName + attributeName +
"', () => " + "', () => " +
expression + expression +
" ) " " ) ",
) )
} else { } else {
attributeName = attributeName.substring(0, attributeName.length - 2).trim() attributeName = attributeName.substring(0, attributeName.length - 2).trim()
@ -728,14 +776,14 @@ class LayerOverviewUtils extends Script {
sharedLayers: Map<string, LayerConfigJson>, sharedLayers: Map<string, LayerConfigJson>,
recompiledThemes: string[], recompiledThemes: string[],
forceReload: boolean, forceReload: boolean,
whitelist: Set<string> whitelist: Set<string>,
): Map<string, LayoutConfigJson> { ): Map<string, LayoutConfigJson> {
console.log(" ---------- VALIDATING BUILTIN THEMES ---------") console.log(" ---------- VALIDATING BUILTIN THEMES ---------")
const themeFiles = ScriptUtils.getThemeFiles() const themeFiles = ScriptUtils.getThemeFiles()
const fixed = new Map<string, LayoutConfigJson>() const fixed = new Map<string, LayoutConfigJson>()
const publicLayers = LayerOverviewUtils.publicLayerIdsFrom( const publicLayers = LayerOverviewUtils.publicLayerIdsFrom(
themeFiles.map((th) => th.parsed) themeFiles.map((th) => th.parsed),
) )
const trs = this.getSharedTagRenderings(new DoesImageExist(licensePaths, existsSync)) const trs = this.getSharedTagRenderings(new DoesImageExist(licensePaths, existsSync))
@ -775,15 +823,15 @@ class LayerOverviewUtils extends Script {
LayerOverviewUtils.themePath + "/" + themePath.substring(themePath.lastIndexOf("/")) LayerOverviewUtils.themePath + "/" + themePath.substring(themePath.lastIndexOf("/"))
const usedLayers = Array.from( const usedLayers = Array.from(
LayerOverviewUtils.extractLayerIdsFrom(themeFile, false) LayerOverviewUtils.extractLayerIdsFrom(themeFile, false),
).map((id) => LayerOverviewUtils.layerPath + id + ".json") ).map((id) => LayerOverviewUtils.layerPath + id + ".json")
if (!forceReload && !this.shouldBeUpdated([themePath, ...usedLayers], targetPath)) { if (!forceReload && !this.shouldBeUpdated([themePath, ...usedLayers], targetPath)) {
fixed.set( fixed.set(
themeFile.id, themeFile.id,
JSON.parse( JSON.parse(
readFileSync(LayerOverviewUtils.themePath + themeFile.id + ".json", "utf8") readFileSync(LayerOverviewUtils.themePath + themeFile.id + ".json", "utf8"),
) ),
) )
ScriptUtils.erasableLog("Skipping", themeFile.id) ScriptUtils.erasableLog("Skipping", themeFile.id)
skippedThemes.push(themeFile.id) skippedThemes.push(themeFile.id)
@ -794,23 +842,23 @@ class LayerOverviewUtils extends Script {
new PrevalidateTheme().convertStrict( new PrevalidateTheme().convertStrict(
themeFile, themeFile,
ConversionContext.construct([themePath], ["PrepareLayer"]) ConversionContext.construct([themePath], ["PrepareLayer"]),
) )
try { try {
themeFile = new PrepareTheme(convertState, { themeFile = new PrepareTheme(convertState, {
skipDefaultLayers: true, skipDefaultLayers: true,
}).convertStrict( }).convertStrict(
themeFile, themeFile,
ConversionContext.construct([themePath], ["PrepareLayer"]) ConversionContext.construct([themePath], ["PrepareLayer"]),
) )
new ValidateThemeAndLayers( new ValidateThemeAndLayers(
new DoesImageExist(licensePaths, existsSync, knownTagRenderings), new DoesImageExist(licensePaths, existsSync, knownTagRenderings),
themePath, themePath,
true, true,
knownTagRenderings knownTagRenderings,
).convertStrict( ).convertStrict(
themeFile, themeFile,
ConversionContext.construct([themePath], ["PrepareLayer"]) ConversionContext.construct([themePath], ["PrepareLayer"]),
) )
if (themeFile.icon.endsWith(".svg")) { if (themeFile.icon.endsWith(".svg")) {
@ -860,7 +908,7 @@ class LayerOverviewUtils extends Script {
const usedImages = Utils.Dedup( const usedImages = Utils.Dedup(
new ExtractImages(true, knownTagRenderings) new ExtractImages(true, knownTagRenderings)
.convertStrict(themeFile) .convertStrict(themeFile)
.map((x) => x.path) .map((x) => x.path),
) )
usedImages.sort() usedImages.sort()
@ -886,16 +934,16 @@ class LayerOverviewUtils extends Script {
t.shortDescription ?? new Translation(t.description).FirstSentence(), t.shortDescription ?? new Translation(t.description).FirstSentence(),
mustHaveLanguage: t.mustHaveLanguage?.length > 0, mustHaveLanguage: t.mustHaveLanguage?.length > 0,
} }
}) }),
) )
} }
console.log( console.log(
"Recompiled themes " + "Recompiled themes " +
recompiledThemes.join(", ") + recompiledThemes.join(", ") +
" and skipped " + " and skipped " +
skippedThemes.length + skippedThemes.length +
" themes" " themes",
) )
return fixed return fixed

View file

@ -41,14 +41,22 @@ export interface LayerConfigJson {
name?: Translatable name?: Translatable
/** /**
* question: How would you describe the features that are shown on this layer?
*
* A description for the features shown in 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. * This often resembles the introduction of the wiki.osm.org-page for this feature.
* *
* group: Basic * group: Basic
* question: How would you describe the features that are shown on this layer?
*/ */
description?: Translatable description?: Translatable
/**
* question: What are some other terms used to describe these objects?
*
* This is used in the search functionality
*/
searchTerms?: Record<string, string[]>
/** /**
* Question: Where should the data be fetched from? * Question: Where should the data be fetched from?
* title: Data Source * title: Data Source

View file

@ -29,6 +29,7 @@ export default class LayerConfig extends WithContextLoader {
public readonly id: string public readonly id: string
public readonly name: Translation public readonly name: Translation
public readonly description: Translation public readonly description: Translation
public readonly searchTerms: Record<string, string[]>
/** /**
* Only 'null' for special, privileged layers * Only 'null' for special, privileged layers
*/ */
@ -113,8 +114,8 @@ export default class LayerConfig extends WithContextLoader {
json.description = undefined json.description = undefined
} }
} }
this.description = Translations.T(json.description, translationContext + ".description") this.description = Translations.T(json.description, translationContext + ".description")
this.searchTerms = json.searchTerms ?? {}
this.calculatedTags = undefined this.calculatedTags = undefined
if (json.calculatedTags !== undefined) { if (json.calculatedTags !== undefined) {

View file

@ -25,7 +25,7 @@ export class MinimalLayoutInformation {
definition?: Translatable definition?: Translatable
mustHaveLanguage?: boolean mustHaveLanguage?: boolean
hideFromOverview?: boolean hideFromOverview?: boolean
keywords?: (Translatable | TagRenderingConfigJson)[] keywords?: Record<string, string[]>
} }
/** /**
* Minimal information about a theme * Minimal information about a theme

View file

@ -15,6 +15,7 @@ export default class MoreScreen {
MoreScreen.officialThemesById.set(th.id, th) MoreScreen.officialThemesById.set(th.id, th)
} }
} }
public static applySearch(searchTerm: string) { public static applySearch(searchTerm: string) {
searchTerm = searchTerm.toLowerCase() searchTerm = searchTerm.toLowerCase()
if (!searchTerm) { if (!searchTerm) {
@ -43,13 +44,13 @@ export default class MoreScreen {
(th) => (th) =>
th.hideFromOverview == false && th.hideFromOverview == false &&
th.id !== "personal" && th.id !== "personal" &&
MoreScreen.MatchesLayout(th, searchTerm) MoreScreen.MatchesLayout(th, searchTerm),
) )
if (publicTheme !== undefined) { if (publicTheme !== undefined) {
window.location.href = MoreScreen.createUrlFor(publicTheme, false) window.location.href = MoreScreen.createUrlFor(publicTheme, false)
} }
const hiddenTheme = MoreScreen.officialThemes.find( const hiddenTheme = MoreScreen.officialThemes.find(
(th) => th.id !== "personal" && MoreScreen.MatchesLayout(th, searchTerm) (th) => th.id !== "personal" && MoreScreen.MatchesLayout(th, searchTerm),
) )
if (hiddenTheme !== undefined) { if (hiddenTheme !== undefined) {
window.location.href = MoreScreen.createUrlFor(hiddenTheme, false) window.location.href = MoreScreen.createUrlFor(hiddenTheme, false)
@ -57,34 +58,47 @@ export default class MoreScreen {
} }
public static MatchesLayout( public static MatchesLayout(
layout: { layout: MinimalLayoutInformation,
id: string search: string,
title: Translatable language?: string,
shortDescription: Translatable
keywords?: (Translatable | TagRenderingConfigJson)[]
},
search: string
): boolean { ): boolean {
if (search === undefined) { if (search === undefined) {
return true 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) { if (search.length > 3 && layout.id.toLowerCase().indexOf(search) >= 0) {
return true return true
} }
if (layout.id === "personal") { if (layout.id === "personal") {
return false return false
} }
if(Utils.simplifyStringForSearch(layout.id) === Utils.simplifyStringForSearch(search)){ if (Utils.simplifyStringForSearch(layout.id) === Utils.simplifyStringForSearch(search)) {
return true return true
} }
const entitiesToSearch = [layout.shortDescription, layout.title, ...(layout.keywords ?? [])] language ??= Locale.language.data
const entitiesToSearch: (string | Record<string, string> | Record<string, string[]>)[] = [layout.shortDescription, layout.title, layout.keywords]
for (const entity of entitiesToSearch) { for (const entity of entitiesToSearch) {
if (entity === undefined) { if (entity === undefined) {
continue 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 return true
} }
} }
@ -95,7 +109,7 @@ export default class MoreScreen {
public static createUrlFor( public static createUrlFor(
layout: { id: string }, layout: { id: string },
isCustom: boolean, isCustom: boolean,
state?: { layoutToUse?: { id } } state?: { layoutToUse?: { id } },
): string { ): string {
if (layout === undefined) { if (layout === undefined) {
return undefined return undefined
@ -141,7 +155,7 @@ export default class MoreScreen {
new Set<string>( new Set<string>(
Object.keys(preferences) Object.keys(preferences)
.filter((key) => key.startsWith(prefix)) .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)),
)) ))
} }
} }

View file

@ -3,13 +3,13 @@
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource" import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
import UserDetails, { OsmConnection } from "../../Logic/Osm/OsmConnection" import UserDetails, { OsmConnection } from "../../Logic/Osm/OsmConnection"
import Constants from "../../Models/Constants" 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 Tr from "../Base/Tr.svelte"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource" import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource"
import Marker from "../Map/Marker.svelte" import Marker from "../Map/Marker.svelte"
export let theme: LayoutInformation export let theme: MinimalLayoutInformation
export let isCustom: boolean = false export let isCustom: boolean = false
export let userDetails: UIEventSource<UserDetails> export let userDetails: UIEventSource<UserDetails>
export let state: { layoutToUse?: { id: string }; osmConnection: OsmConnection } export let state: { layoutToUse?: { id: string }; osmConnection: OsmConnection }

View file

@ -4,12 +4,12 @@
import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { UIEventSource } from "../../Logic/UIEventSource" import { UIEventSource } from "../../Logic/UIEventSource"
import ThemeButton from "./ThemeButton.svelte" import ThemeButton from "./ThemeButton.svelte"
import { LayoutInformation } from "../../Models/ThemeConfig/LayoutConfig" import { LayoutInformation, MinimalLayoutInformation } from "../../Models/ThemeConfig/LayoutConfig"
import MoreScreen from "./MoreScreen" import MoreScreen from "./MoreScreen"
import themeOverview from "../../assets/generated/theme_overview.json" import themeOverview from "../../assets/generated/theme_overview.json"
export let search: UIEventSource<string> export let search: UIEventSource<string>
export let themes: LayoutInformation[] export let themes: MinimalLayoutInformation[]
export let state: { osmConnection: OsmConnection } export let state: { osmConnection: OsmConnection }
export let isCustom: boolean = false export let isCustom: boolean = false
export let hideThemes: boolean = true export let hideThemes: boolean = true

View file

@ -1602,6 +1602,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
* @constructor * @constructor
* *
* Utils.RemoveDiacritics("bâtiments") // => "batiments" * Utils.RemoveDiacritics("bâtiments") // => "batiments"
* Utils.RemoveDiacritics(undefined) // => undefined
*/ */
public static RemoveDiacritics(str?: string): string { public static RemoveDiacritics(str?: string): string {
// See #1729 // See #1729
@ -1616,9 +1617,10 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
* @param str * @param str
* Utils.simplifyStringForSearch("abc def; ghi 564") // => "abcdefghi564" * Utils.simplifyStringForSearch("abc def; ghi 564") // => "abcdefghi564"
* Utils.simplifyStringForSearch("âbc déf; ghi 564") // => "abcdefghi564" * Utils.simplifyStringForSearch("âbc déf; ghi 564") // => "abcdefghi564"
* Utils.simplifyStringForSearch(undefined) // => undefined
*/ */
public static simplifyStringForSearch(str: string): string { 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 { public static randomString(length: number): string {