Add priority-search to the searchable element

This commit is contained in:
pietervdvn 2022-07-15 01:10:10 +02:00
parent c916d5fe66
commit 07973e37a6
14 changed files with 5349 additions and 244 deletions

View file

@ -107,10 +107,18 @@ export interface MappingConfigJson {
addExtraTags?: string[]
/**
* Searchterms (per language) to easily find an option if there are many options
* If there are many options, the mappings-radiobuttons will be replaced by an element with a searchfunction
*
* Searchterms (per language) allow to easily find an option if there are many options
*/
searchTerms?: Record<string, string[]>
/**
* If the searchable selector is picked, mappings with this item will have priority and show up even if the others are hidden
* Use this sparingly
*/
priorityIf?: string | AndOrTagConfigJson
}
/**

View file

@ -14,6 +14,7 @@ import List from "../../UI/Base/List";
import {MappingConfigJson, QuestionableTagRenderingConfigJson} from "./Json/QuestionableTagRenderingConfigJson";
import {FixedUiElement} from "../../UI/Base/FixedUiElement";
import {Paragraph} from "../../UI/Base/Paragraph";
import UserDetails from "../../Logic/Osm/OsmConnection";
export interface Mapping {
readonly if: TagsFilter,
@ -23,7 +24,8 @@ export interface Mapping {
readonly iconClass: string | "small" | "medium" | "large" | "small-height" | "medium-height" | "large-height",
readonly hideInAnswer: boolean | TagsFilter
readonly addExtraTags: Tag[],
readonly searchTerms?: Record<string, string[]>
readonly searchTerms?: Record<string, string[]>,
readonly priorityIf?: TagsFilter
}
/***
@ -287,6 +289,11 @@ export default class TagRenderingConfig {
}
}
/**
* const tr = TagRenderingConfig.ExtractMapping({if: "a=b", then: "x", priorityIf: "_country=be"}, 0, "test","test", false,true)
* tr.if // => new Tag("a","b")
* tr.priorityIf // => new Tag("_country","be")
*/
public static ExtractMapping(mapping: MappingConfigJson, i: number, translationKey: string,
context: string,
multiAnswer?: boolean, isQuestionable?: boolean, commonSize: string = "small") {
@ -337,6 +344,7 @@ export default class TagRenderingConfig {
iconClass = mapping.icon["class"] ?? iconClass
}
}
const prioritySearch = mapping.priorityIf !== undefined ? TagUtils.Tag(mapping.priorityIf) : undefined;
const mp = <Mapping>{
if: TagUtils.Tag(mapping.if, `${ctx}.if`),
ifnot: (mapping.ifnot !== undefined ? TagUtils.Tag(mapping.ifnot, `${ctx}.ifnot`) : undefined),
@ -345,7 +353,8 @@ export default class TagRenderingConfig {
icon,
iconClass,
addExtraTags,
searchTerms: mapping.searchTerms
searchTerms: mapping.searchTerms,
priorityIf: prioritySearch
};
if (isQuestionable) {
if (hideInAnswer !== true && mp.if !== undefined && !mp.if.isUsableAsAnswer()) {

View file

@ -57,7 +57,13 @@ class SelfHidingToggle extends UIElement implements InputElement<boolean> {
return true
}
s = s?.trim()?.toLowerCase()
return searchTerms[Locale.language.data]?.some(t => t.indexOf(s) >= 0) ?? false;
if(searchTerms[Locale.language.data]?.some(t => t.indexOf(s) >= 0)){
return true
}
if(searchTerms["*"]?.some(t => t.indexOf(s) >= 0)){
return true
}
return false;
}, [selected, Locale.language])
const self = this;
@ -142,13 +148,15 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
* Shows this if there are many (>200) possible mappings
*/
onManyElements?: BaseUIElement,
onManyElementsValue?: UIEventSource<T[]>,
selectIfSingle?: false | boolean,
searchAreaClass?: string
searchAreaClass?: string,
hideSearchBar?: false | boolean
}) {
const search = new TextField({value: options?.searchValue})
const searchBar = new Combine([Svg.search_svg().SetClass("w-8 normal-background"), search.SetClass("w-full")])
const searchBar = options?.hideSearchBar ? undefined : new Combine([Svg.search_svg().SetClass("w-8 normal-background"), search.SetClass("w-full")])
.SetClass("flex items-center border-2 border-black m-2")
const searchValue = search.GetValue().map(s => s?.trim()?.toLowerCase())
@ -228,19 +236,28 @@ export class SearchablePillsSelector<T> extends Combine implements InputElement<
totalShown = searchValue.map(_ => mappedValues.filter(mv => mv.show.isShown.data).length, mappedValues.map(mv => mv.show.GetValue()))
}
const tooMuchElementsCutoff = 200;
options?.onManyElementsValue?.map(value => {
console.log("Installing toMuchElementsValue", value)
if(tooMuchElementsCutoff <= totalShown.data){
selectedElements.setData(value)
selectedElements.ping()
}
}, [totalShown])
super([
searchBar,
new VariableUiElement(Locale.language.map(lng => {
if(totalShown.data >= 200){
return options?.onManyElements ?? Translations.t.general.useSearch;
}
if (options?.onNoSearchMade !== undefined && (searchValue.data === undefined || searchValue.data.length === 0)) {
return options?.onNoSearchMade
}
if (totalShown.data == 0) {
return onEmpty
}
if(totalShown.data >= 200){
return options?.onManyElements ?? Translations.t.general.useSearch;
}
mappedValues.sort((a, b) => a.mainTerm[lng] < b.mainTerm[lng] ? -1 : 1)
return new Combine(mappedValues.map(e => e.show))
.SetClass("flex flex-wrap w-full content-start")

View file

@ -11,7 +11,7 @@ import {SaveButton} from "./SaveButton";
import {VariableUiElement} from "../Base/VariableUIElement";
import Translations from "../i18n/Translations";
import {FixedUiElement} from "../Base/FixedUiElement";
import {Translation, TypedTranslation} from "../i18n/Translation";
import {Translation} from "../i18n/Translation";
import Constants from "../../Models/Constants";
import {SubstitutedTranslation} from "../SubstitutedTranslation";
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
@ -232,6 +232,29 @@ export default class TagRenderingQuestion extends Combine {
}
private static MappingToPillValue(applicableMappings: Mapping[], tagsSource: UIEventSource<OsmTags>, state: FeaturePipelineState): { show: BaseUIElement, value: number, mainTerm: Record<string, string>, searchTerms?: Record<string, string[]>, original: Mapping }[] {
const values: { show: BaseUIElement, value: number, mainTerm: Record<string, string>, searchTerms?: Record<string, string[]>, original: Mapping }[] = []
const addIcons = applicableMappings.some(m => m.icon !== undefined)
for (let i = 0; i < applicableMappings.length; i++) {
const mapping = applicableMappings[i];
const tr = mapping.then.Subs(tagsSource.data)
const patchedMapping = <Mapping>{
...mapping,
iconClass: `small-height`,
icon: mapping.icon ?? (addIcons ? "./assets/svg/none.svg" : undefined)
}
const fancy = TagRenderingQuestion.GenerateMappingContent(patchedMapping, tagsSource, state).SetClass("normal-background")
values.push({
show: fancy,
value: i,
mainTerm: tr.translations,
searchTerms: mapping.searchTerms,
original: mapping
})
}
return values
}
/**
*
* // Should return the search as freeform value
@ -305,24 +328,9 @@ export default class TagRenderingQuestion extends Combine {
options?: {
search: UIEventSource<string>
}): InputElement<TagsFilter> {
const values: { show: BaseUIElement, value: number, mainTerm: Record<string, string>, searchTerms?: Record<string, string[]> }[] = []
const addIcons = applicableMappings.some(m => m.icon !== undefined)
for (let i = 0; i < applicableMappings.length; i++) {
const mapping = applicableMappings[i];
const tr = mapping.then.Subs(tagsSource.data)
const patchedMapping = <Mapping>{
...mapping,
iconClass: `small-height`,
icon: mapping.icon ?? (addIcons ? "./assets/svg/none.svg": undefined)
}
const fancy = TagRenderingQuestion.GenerateMappingContent(patchedMapping, tagsSource, state).SetClass("normal-background")
values.push({
show: fancy,
value: i,
mainTerm: tr.translations,
searchTerms: mapping.searchTerms
})
}
const values = TagRenderingQuestion.MappingToPillValue(applicableMappings, tagsSource, state)
const searchValue: UIEventSource<string> = options?.search ?? new UIEventSource<string>(undefined)
const ff = configuration.freeform
@ -330,14 +338,39 @@ export default class TagRenderingQuestion extends Combine {
if (ff !== undefined) {
onEmpty = new VariableUiElement(searchValue.map(search => configuration.render.Subs({[ff.key]: search})))
}
const mode = configuration.multiAnswer ? "select-many" : "select-one";
const tooMuchElementsValue = new UIEventSource<number[]>([]);
let priorityPresets: BaseUIElement = undefined;
const classes = "h-64 overflow-scroll"
if (applicableMappings.some(m => m.priorityIf !== undefined)) {
const priorityValues = tagsSource.map(tags =>
TagRenderingQuestion.MappingToPillValue(applicableMappings, tagsSource, state)
.filter(v => v.original.priorityIf?.matchesProperties(tags)))
priorityPresets = new VariableUiElement(priorityValues.map(priority => {
if (priority.length === 0) {
return Translations.t.general.useSearch;
}
return new Combine([
Translations.t.general.useSearchForMore.Subs({total: applicableMappings.length}),
new SearchablePillsSelector(priority, {
selectedElements: tooMuchElementsValue,
hideSearchBar: true,
mode
})]).SetClass("flex flex-col items-center ").SetClass(classes);
}));
}
const presetSearch = new SearchablePillsSelector<number>(values, {
selectIfSingle: true,
mode: configuration.multiAnswer ? "select-many" : "select-one",
mode,
searchValue,
onNoMatches: onEmpty?.SetClass(classes).SetClass("flex justify-center items-center"),
searchAreaClass: classes,
onManyElementsValue: tooMuchElementsValue,
onManyElements: priorityPresets
})
const fallbackTag = searchValue.map(s => {
if (s === undefined || ff?.key === undefined) {

File diff suppressed because it is too large Load diff

View file

@ -1,13 +1,19 @@
{
"id": "mapcomplete-changes",
"title": {
"en": "Changes made with MapComplete"
"en": "Changes made with MapComplete",
"de": "Mit MapComplete vorgenommene Änderungen",
"nl": "Wijzigingen gemaakt met MapComplete"
},
"shortDescription": {
"en": "Shows changes made by MapComplete"
"en": "Shows changes made by MapComplete",
"de": "Zeigt die mit MapComplete vorgenommenen Änderungen",
"nl": "Toont wijzigingen gemaakt met MapComplete"
},
"description": {
"en": "This maps shows all the changes made with MapComplete"
"en": "This maps shows all the changes made with MapComplete",
"de": "Diese Karte zeigt alle mit MapComplete vorgenommenen Änderungen",
"nl": "Deze kaart toont alle wijzigingen die met MapComplete werden gemaakt"
},
"maintainer": "",
"icon": "./assets/svg/logo.svg",
@ -22,7 +28,8 @@
{
"id": "mapcomplete-changes",
"name": {
"en": "Changeset centers"
"en": "Changeset centers",
"de": "Zentrum der Änderungssätze"
},
"minzoom": 0,
"source": {
@ -36,35 +43,47 @@
],
"title": {
"render": {
"en": "Changeset for {theme}"
"en": "Changeset for {theme}",
"de": "Änderungssatz für {theme}",
"nl": "Wijzigingset voor {theme}"
}
},
"description": {
"en": "Shows all MapComplete changes"
"en": "Shows all MapComplete changes",
"de": "Zeigt alle MapComplete Änderungen",
"nl": "Toont alle wijzigingen met MapComplete"
},
"tagRenderings": [
{
"id": "render_id",
"render": {
"en": "Changeset <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>"
"en": "Changeset <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
"de": "Änderungssatz <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>",
"nl": "Wijzigingset <a href='https://openstreetmap.org/changeset/{id}' target='_blank'>{id}</a>"
}
},
{
"id": "contributor",
"render": {
"en": "Change made by <a href='https://openstreetmap.org/user/{_last_edit:contributor}' target='_blank'>{_last_edit:contributor}</a>"
"en": "Change made by <a href='https://openstreetmap.org/user/{_last_edit:contributor}' target='_blank'>{_last_edit:contributor}</a>",
"de": "Geändert von <a href='https://openstreetmap.org/user/{_last_edit:contributor}' target='_blank'>{_last_edit:contributor}</a>",
"nl": "Wijziging gemaakt door <a href='https://openstreetmap.org/user/{_last_edit:contributor}' target='_blank'>{_last_edit:contributor}</a>"
}
},
{
"id": "theme",
"render": {
"en": "Change with theme <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>"
"en": "Change with theme <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>",
"de": "Änderung mit Thema <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>",
"nl": "Wijziging met thema <a href='https://mapcomplete.osm.be/{theme}'>{theme}</a>"
},
"mappings": [
{
"if": "theme~http.*",
"then": {
"en": "Change with <b>unofficial</b> theme <a href='https://mapcomplete.osm.be/theme.html?userlayout={theme}'>{theme}</a>"
"en": "Change with <b>unofficial</b> theme <a href='https://mapcomplete.osm.be/theme.html?userlayout={theme}'>{theme}</a>",
"de": "Änderung mit <b>inoffiziellem</b> Thema <a href='https://mapcomplete.osm.be/theme.html?userlayout={theme}'>{theme}</a>",
"nl": "Wijziging met <b>officieus</b> thema <a href='https://mapcomplete.osm.be/theme.html?userlayout={theme}'>{theme}</a>"
}
}
]
@ -360,7 +379,9 @@
}
],
"question": {
"en": "Themename contains {search}"
"en": "Themename contains {search}",
"de": "Themenname enthält {search}",
"nl": "Themanaam bevat {search}"
}
}
]
@ -376,7 +397,9 @@
}
],
"question": {
"en": "Made by contributor {search}"
"en": "Made by contributor {search}",
"de": "Erstellt von {search}",
"nl": "Gemaakt door bijdrager {search}"
}
}
]
@ -392,7 +415,9 @@
}
],
"question": {
"en": "<b>Not</b> made by contributor {search}"
"en": "<b>Not</b> made by contributor {search}",
"de": "<b>Nicht</b> erstellt von {search}",
"nl": "<b>Niet</b> gemaakt door bijdrager {search}"
}
}
]
@ -407,7 +432,9 @@
{
"id": "link_to_more",
"render": {
"en": "More statistics can be found <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>here</a>"
"en": "More statistics can be found <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>here</a>",
"de": "Weitere Statistiken finden Sie <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>hier</a>",
"nl": "Meer statistieken kunnen <a href='https://github.com/pietervdvn/MapComplete/tree/develop/Docs/Tools/graphs' target='_blank'>hier</a> gevonden worden"
}
},
{

View file

@ -236,6 +236,7 @@
"skippedQuestions": "Some questions are skipped",
"testing": "Testing - changes won't be saved",
"useSearch": "Use the search above to see presets",
"useSearchForMore": "Use the search function to search within {total} more values....",
"weekdays": {
"abbreviations": {
"friday": "Fri",

View file

@ -5122,6 +5122,11 @@
}
},
"tagRenderings": {
"8": {
"override": {
"question": "Was ist die Hauptsprache dieser Schule?<div class='subtle'>Welche Sprache wird mit den Schülern in den nicht sprachbezogenen Kursen und mit der Verwaltung gesprochen?</div>"
}
},
"capacity": {
"question": "Wie viele Schüler können sich maximal an dieser Schule anmelden?",
"render": "Diese Schule kann höchstens {capacity} Schüler aufnehmen"
@ -5169,30 +5174,6 @@
},
"question": "Welche Geschlechter können sich an dieser Schule anmelden?"
},
"language": {
"freeform": {
"placeholder": "Sprache in Englisch in Kleinbuchstaben"
},
"mappings": {
"0": {
"then": "Die Hauptsprache dieser Schule ist unbekannt"
},
"1": {
"then": "Französisch ist die Hauptsprache von {name}"
},
"2": {
"then": "Niederländisch ist die Hauptsprache von {name}"
},
"3": {
"then": "Deutsch ist die Hauptsprache von {name}"
},
"4": {
"then": "Die Hauptsprache dieser Schule ist unbekannt"
}
},
"question": "Was ist die Hauptsprache dieser Schule?<div class='subtle'>Welche Sprache wird mit den Schülern in den nicht sprachbezogenen Kursen und mit der Verwaltung gesprochen?</div>",
"render": "{school:language} ist die Hauptsprache von {title()}"
},
"school-name": {
"question": "Wie lautet der Name dieser Schule?",
"render": "Diese Schule heißt {name}"

View file

@ -5303,6 +5303,16 @@
}
},
"tagRenderings": {
"8": {
"override": {
"+mappings": {
"0": {
"then": "The main language of this school is unknown"
}
},
"question": "What is the main language of this school?<div class='subtle'>What language is spoken with the students in non-language related courses and with the administration?</div>"
}
},
"capacity": {
"question": "How much students can at most enroll in this school?",
"render": "This school can enroll at most {capacity} students"
@ -5350,30 +5360,6 @@
},
"question": "Which genders can enroll at this school?"
},
"language": {
"freeform": {
"placeholder": "Language in lowercase English"
},
"mappings": {
"0": {
"then": "The main language of this school is unknown"
},
"1": {
"then": "French is the main language of {name}"
},
"2": {
"then": "Dutch is the main language of {name}"
},
"3": {
"then": "German is the main language of {name}"
},
"4": {
"then": "The main language of this school is unknown"
}
},
"question": "What is the main language of this school?<div class='subtle'>What language is spoken with the students in non-language related courses and with the administration?</div>",
"render": "{school:language} is the main language of {title()}"
},
"school-name": {
"question": "What is the name of this school?",
"render": "This school is named {name}"

View file

@ -2419,6 +2419,11 @@
}
},
"tagRenderings": {
"8": {
"override": {
"question": "Quelle est la langue principale de cette école ?<div class='subtle'>Quelle langue est parlée avec les élèves des cours non linguistiques et avec l'administration ?</div>"
}
},
"capacity": {
"question": "Combien d'élèves peuvent s'inscrire au maximum dans cette école ?",
"render": "Cette école peut accueillir au maximum {capacity} étudiants"
@ -2466,18 +2471,6 @@
},
"question": "Quels genres de personnes peuvent s'inscrire dans cette école ?"
},
"language": {
"mappings": {
"0": {
"then": "La langue principale de cette école est inconnue"
},
"4": {
"then": "La langue principale de cette école est inconnue"
}
},
"question": "Quelle est la langue principale de cette école ?<div class='subtle'>Quelle langue est parlée avec les élèves des cours non linguistiques et avec l'administration ?</div>",
"render": "{school:language} est la langue principale de {title()}"
},
"school-name": {
"question": "Quel est le nom de cet établissement scolaire?",
"render": "Cet établissement scolaire s'appelle {name}"

View file

@ -5112,6 +5112,16 @@
}
},
"tagRenderings": {
"8": {
"override": {
"+mappings": {
"0": {
"then": "De voertaal van deze school is niet gekend"
}
},
"question": "Wat is de voertaal van deze school?<div class='subtle'>Welke taal wordt met de studenten gesproken in niet-taal-gerelateerde vakken en met de administratie?</div>"
}
},
"capacity": {
"question": "Ten hoogste hoeveel studenten kunnen er les volgen in deze school?",
"render": "Deze school kan maximaal {capacity} studenten lesgeven"
@ -5159,30 +5169,6 @@
},
"question": "Mogen jongens en meisjes les volgen op deze school?"
},
"language": {
"freeform": {
"placeholder": "Taal in lowercase Engel"
},
"mappings": {
"0": {
"then": "De voertaal van deze school is niet gekend"
},
"1": {
"then": "Frans is de voertaal van {name}"
},
"2": {
"then": "Nederlands is de voertaal van {name}"
},
"3": {
"then": "Duits is de voertaal van {name}"
},
"4": {
"then": "De voertaal van deze school is niet gekend"
}
},
"question": "Wat is de voertaal van deze school?<div class='subtle'>Welke taal wordt met de studenten gesproken in niet-taal-gerelateerde vakken en met de administratie?</div>",
"render": "{school:language} is de voertaal van {title()}"
},
"school-name": {
"question": "Wat is de naam van deze school?",
"render": "Deze school heet <b>{name}</b>"

View file

@ -52,7 +52,7 @@
"weblate-fix-heavy": "git remote rm weblate-layers; git remote add weblate-layers https://hosted.weblate.org/git/mapcomplete/layers/; git remote update weblate-layers; git merge weblate-layers/master",
"housekeeping": "npm run generate && npm run generate:docs && npm run generate:contributor-list && git add Docs/* && git commit assets/ langs/ Docs/ -m 'Housekeeping...'",
"parseSchools": "ts-node scripts/schools/amendSchoolData.ts",
"steal": "ts-node scripts/readIdPresets.ts"
"steal": "ts-node scripts/thieves/stealLanguages.ts"
},
"keywords": [
"OpenStreetMap",

View file

@ -234,11 +234,11 @@ class LayerOverviewUtils {
this.checkAllSvgs()
if(AllKnownLayouts.getSharedLayersConfigs().size == 0){
throw "This was a bootstrapping-run. Run generate layeroverview again!"
console.error( "This was a bootstrapping-run. Run generate layeroverview again!")
}else{
const green = s => '\x1b[92m' + s + '\x1b[0m'
console.log(green("All done!"))
}
const green = s => '\x1b[92m' + s + '\x1b[0m'
console.log(green("All done!"))
}
private buildLayerIndex(doesImageExist: DoesImageExist, forceReload: boolean): Map<string, LayerConfigJson> {

View file

@ -7,14 +7,18 @@ import {existsSync, mkdirSync, readFileSync, writeFileSync} from "fs";
import {LayerConfigJson} from "../../Models/ThemeConfig/Json/LayerConfigJson";
import {MappingConfigJson} from "../../Models/ThemeConfig/Json/QuestionableTagRenderingConfigJson";
import LanguageUtils from "../../Utils/LanguageUtils";
import * as perCountry from "../../assets/language_in_country.json"
import {Utils} from "../../Utils";
function main(){
const sourcepath = "assets/generated/languages-wd.json";
console.log(`Converting language data file '${sourcepath}' into a tagMapping`)
const languages = WikidataUtils.extractLanguageData(JSON.parse(readFileSync(sourcepath, "utf8")), {})
const mappings : MappingConfigJson[] = []
const schoolmappings : MappingConfigJson[] = []
const countryToLanguage : Record<string, string[]> = perCountry
const officialLanguagesPerCountry = Utils.TransposeMap(countryToLanguage);
languages.forEach((l, code) => {
const then : Record<string, string>= {}
l.forEach((tr, lng) => {
@ -24,18 +28,26 @@ function main(){
}
then[languageCodeWeblate] = tr
})
const officialCountries = Utils.Dedup(officialLanguagesPerCountry[code]?.map(s => s.toLowerCase()) ?? [])
const prioritySearch = officialCountries.length > 0 ? "_country~" + officialCountries.map(c => "((^|;)"+c+"($|;))").join("|") : undefined
mappings.push(<MappingConfigJson>{
if: "language:" + code + "=yes",
ifnot: "language:" + code + "=",
searchTerms: {
"*": [code]
},
then
then,
priorityIf: prioritySearch
})
schoolmappings.push(<MappingConfigJson>{
if: "school:language=" + code,
then
then,
priorityIf: prioritySearch,
searchTerms: {
"*":[code]
}
})
})