Themes: improve note theme, fix #2088

This commit is contained in:
Pieter Vander Vennet 2024-09-13 02:25:06 +02:00
parent 70117ac687
commit e8099b9081
5 changed files with 266 additions and 189 deletions

View file

@ -18,13 +18,16 @@
"calculatedTags": [
"_total_comments:=get(feat)('comments').length",
"_first_comment:=get(feat)('comments')[0].text",
"_all_comments:=get(feat)('comments').map(c => c.text ?? '').join('\\n')",
"_all_usernames:=get(feat)('comments').map(c => c.user ?? 'Anonymous').join('\\n')",
"_opened_by_anonymous_user:=get(feat)('comments')[0].user === undefined",
"_first_user:=get(feat)('comments')[0].user",
"_last_user:=(() => {const comms = get(feat)('comments'); return comms[comms.length - 1].user})()",
"_last_change_date:=(() => {const comms = get(feat)('comments'); return comms[comms.length - 1].date})()",
"_first_user_id:=get(feat)('comments')[0].uid",
"_is_import_note:=(() => {const lines = feat.properties['_first_comment'].split('\\n'); const matchesMapCompleteURL = lines.map(l => l.match(\".*https://mapcomplete.\\(osm.be|org\\)/\\([a-zA-Z_-]+\\)\\(.html\\).*#import\")); const matchedIndexes = matchesMapCompleteURL.map((doesMatch, i) => [doesMatch !== null, i]).filter(v => v[0]).map(v => v[1]); return matchedIndexes[0] })()"
],
"minzoom": 10,
"minzoom": 7,
"title": {
"render": {
"en": "Note",
@ -158,183 +161,6 @@
}
],
"filter": [
{
"id": "search",
"options": [
{
"osmTags": "_first_comment~i~.*{search}.*",
"fields": [
{
"name": "search"
}
],
"question": {
"en": "Should mention {search} in the first comment",
"nl": "Moet in de eerste opmerking \"{search}\" bevatten",
"de": "Sollte {search} im ersten Kommentar erwähnen",
"es": "Debe mencionar {search} en el primer comentario",
"ca": "Has de mencionar {search} en el primer comentari",
"cs": "Měl by se zmínit {search} v prvním komentáři"
}
}
]
},
{
"id": "not",
"options": [
{
"osmTags": "_first_comment!~i~.*{search}.*",
"fields": [
{
"name": "search"
}
],
"question": {
"en": "Should <b>not</b> mention {search} in the first comment",
"nl": "Mag in de eerste opmerking <b>niet</b> \"{search}\" bevatten",
"de": "Sollte <b>nicht</b> {search} im ersten Kommentar erwähnen",
"es": "<b>No</b> debe mencionar {search} en el primer comentario",
"ca": "<b>No</b> s'ha de mencionar {search} al primer comentari",
"cs": "V prvním komentáři by jste <b>neměli</b> zmiňovat {search}"
}
}
]
},
{
"id": "opened_by",
"options": [
{
"osmTags": "_first_user~i~.*{search}.*",
"fields": [
{
"name": "search"
}
],
"question": {
"en": "Opened by contributor {search}",
"nl": "Geopend door bijdrager {search}",
"de": "Erstellt von {search}",
"es": "Abierto por el contributor {search}",
"fr": "Ouverte par {search}",
"ca": "Obert pel contribuïdor {search}",
"cs": "Otevřeno přispěvatelem {search}"
}
}
]
},
{
"id": "not_opened_by",
"options": [
{
"osmTags": "_first_user!~i~.*{search}.*",
"fields": [
{
"name": "search"
}
],
"question": {
"en": "<b>Not</b> opened by contributor {search}",
"nl": "<b>Niet</b> geopend door bijdrager {search}",
"de": "<b>Nicht</b> erstellt von {search}",
"es": "<b>No</b> abierto por el contributor {search}",
"ca": "<b>No</b> obert pel contribuïdor {search}",
"cs": "<b>Není</b> otevřeno přispěvatelem {search}",
"fr": "<b>Exclure</b>les notes ouvertes par {search}"
}
}
]
},
{
"id": "edited_by",
"options": [
{
"osmTags": "_last_user~i~.*{search}.*",
"fields": [
{
"name": "search"
}
],
"question": {
"en": "Last edited by contributor {search}",
"nl": "Laatst bewerkt door bijdrager {search}",
"de": "Zuletzt bearbeitet von {search}",
"es": "Editada por última vez por el contributor {search}",
"ca": "Editat per última vega pel contribuïdor {search}",
"cs": "Naposledy upravil přispěvatel {search}",
"da": "Senest redigeret af bidragsyder {search}",
"fr": "Dernière modification par {search}"
}
}
]
},
{
"id": "not_edited_by",
"options": [
{
"osmTags": "_last_user!~i~.*{search}.*",
"fields": [
{
"name": "search"
}
],
"question": {
"en": "Opened after {search}",
"nl": "Geopend na {search}",
"de": "Zuletzt bearbeitet nach dem {search}",
"es": "Abierta después de {search}",
"ca": "Oberta després de {search}",
"cs": "Otevřeno po {search}",
"fr": "Ouverte après le {search}"
}
}
]
},
{
"id": "opened_before",
"options": [
{
"osmTags": "date_created<{search}",
"fields": [
{
"name": "search",
"type": "date"
}
],
"question": {
"en": "Created before {search}",
"nl": "Aangemaakt voor {search}",
"de": "Erstellt vor dem {search}",
"es": "Creada antes de {search}",
"ca": "Creada abans de {search}",
"cs": "Vytvořeno před {search}",
"fr": "Créée avant le {search}"
}
}
]
},
{
"id": "opened_after",
"options": [
{
"osmTags": "date_created>{search}",
"fields": [
{
"name": "search",
"type": "date"
}
],
"question": {
"en": "Created after {search}",
"nl": "Aangemaakt na {search}",
"de": "Erstellt nach dem {search}",
"es": "Creada después de {search}",
"ca": "Creada després de {search}",
"cs": "Vytvořeno po {search}",
"fr": "Créée après le {search}"
}
}
]
},
{
"id": "anonymous",
"options": [
@ -406,6 +232,244 @@
}
}
]
},
{
"id": "search",
"options": [
{
"osmTags": "_first_comment~i~.*{search}.*",
"fields": [
{
"name": "search"
}
],
"question": {
"en": "Should mention {search} in the first comment",
"nl": "Moet in de eerste opmerking \"{search}\" bevatten",
"de": "Sollte {search} im ersten Kommentar erwähnen",
"es": "Debe mencionar {search} en el primer comentario",
"ca": "Has de mencionar {search} en el primer comentari",
"cs": "Měl by se zmínit {search} v prvním komentáři"
}
}
]
},
{
"id": "search_any",
"options": [
{
"osmTags": "_all_comments~i~.*{search}.*",
"fields": [
{
"name": "search"
}
],
"question": {
"en": "Should mention {search} in any comment"
}
}
]
},
{
"id": "not",
"options": [
{
"osmTags": "_first_comment!~i~.*{search}.*",
"fields": [
{
"name": "search"
}
],
"question": {
"en": "Should <b>not</b> mention {search} in the first comment",
"nl": "Mag in de eerste opmerking <b>niet</b> \"{search}\" bevatten",
"de": "Sollte <b>nicht</b> {search} im ersten Kommentar erwähnen",
"es": "<b>No</b> debe mencionar {search} en el primer comentario",
"ca": "<b>No</b> s'ha de mencionar {search} al primer comentari",
"cs": "V prvním komentáři by jste <b>neměli</b> zmiňovat {search}"
}
}
]
},
{
"id": "opened_by",
"options": [
{
"osmTags": "_first_user~i~.*{search}.*",
"fields": [
{
"name": "search"
}
],
"question": {
"en": "Opened by contributor {search}",
"nl": "Geopend door bijdrager {search}",
"de": "Erstellt von {search}",
"es": "Abierto por el contributor {search}",
"fr": "Ouverte par {search}",
"ca": "Obert pel contribuïdor {search}",
"cs": "Otevřeno přispěvatelem {search}"
}
}
]
},
{
"id": "not_opened_by",
"options": [
{
"osmTags": "_first_user!~i~.*{search}.*",
"fields": [
{
"name": "search"
}
],
"question": {
"en": "<b>Not</b> opened by contributor {search}",
"nl": "<b>Niet</b> geopend door bijdrager {search}",
"de": "<b>Nicht</b> erstellt von {search}",
"es": "<b>No</b> abierto por el contributor {search}",
"ca": "<b>No</b> obert pel contribuïdor {search}",
"cs": "<b>Není</b> otevřeno přispěvatelem {search}",
"fr": "<b>Exclure</b>les notes ouvertes par {search}"
}
}
]
},
{
"id": "edited_by_any",
"options": [
{
"osmTags": "_all_usernames~i~.*{search}.*",
"fields": [
{
"name": "search"
}
],
"question": {
"en": "Edited or commented on by any user with name {search}"
}
}
]
},
{
"id": "last_edited_by",
"options": [
{
"osmTags": "_last_user~i~.*{search}.*",
"fields": [
{
"name": "search"
}
],
"question": {
"en": "Last edited by contributor {search}",
"nl": "Laatst bewerkt door bijdrager {search}",
"de": "Zuletzt bearbeitet von {search}",
"es": "Editada por última vez por el contributor {search}",
"ca": "Editat per última vega pel contribuïdor {search}",
"cs": "Naposledy upravil přispěvatel {search}",
"da": "Senest redigeret af bidragsyder {search}",
"fr": "Dernière modification par {search}"
}
}
]
},
{
"id": "not_last_edited_by",
"options": [
{
"osmTags": "_last_user!~i~.*{search}.*",
"fields": [
{
"name": "search"
}
],
"question": {
"en": "Not edited as last by {search}"
}
}
]
},
{
"id": "opened_before",
"options": [
{
"osmTags": "date_created<{search}",
"fields": [
{
"name": "search",
"type": "date"
}
],
"question": {
"en": "Created before {search}",
"nl": "Aangemaakt voor {search}",
"de": "Erstellt vor dem {search}",
"es": "Creada antes de {search}",
"ca": "Creada abans de {search}",
"cs": "Vytvořeno před {search}",
"fr": "Créée avant le {search}"
}
}
]
},
{
"id": "opened_after",
"options": [
{
"osmTags": "date_created>{search}",
"fields": [
{
"name": "search",
"type": "date"
}
],
"question": {
"en": "Created after {search}",
"nl": "Aangemaakt na {search}",
"de": "Erstellt nach dem {search}",
"es": "Creada después de {search}",
"ca": "Creada després de {search}",
"cs": "Vytvořeno po {search}",
"fr": "Créée après le {search}"
}
}
]
},
{
"id": "last_edited_before",
"options": [
{
"osmTags": "_last_change_date<{search}",
"fields": [
{
"name": "search",
"type": "date"
}
],
"question": {
"en": "Last edited before {search}"
}
}
]
},
{
"id": "last_edited_after",
"options": [
{
"osmTags": "_last_change_date>{search}",
"fields": [
{
"name": "search",
"type": "date"
}
],
"question": {
"en": "Last edited after {search}"
}
}
]
}
],
"allowMove": false

View file

@ -12,12 +12,14 @@ import BaseUIElement from "../../UI/BaseUIElement"
import Table from "../../UI/Base/Table"
import Combine from "../../UI/Base/Combine"
import MarkdownUtils from "../../Utils/MarkdownUtils"
import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
export type FilterConfigOption = {
question: Translation
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
fields: { name: string; type: string }[]
fields: { name: string; type: ValidatorType }[]
}
export default class FilterConfig {
public readonly id: string
@ -57,8 +59,11 @@ export default class FilterConfig {
throw `Invalid filter: no question given at ${ctx}`
}
const fields: { name: string; type: string }[] = (option.fields ?? []).map((f, i) => {
const type = f.type ?? "string"
const fields: { name: string; type: ValidatorType }[] = (option.fields ?? []).map((f, i) => {
const type = <ValidatorType> f.type ?? "string"
if(Validators.availableTypes.indexOf(type) < 0){
throw `Invalid filter: type is not a valid validator. Did you mean one of ${Utils.sortedByLevenshteinDistance(type, <any>Validators.availableTypes, x => x).slice(0, 3)}`
}
// Type is validated against 'ValidatedTextField' in Validation.ts, in ValidateFilterConfig
if (f.name === undefined || f.name === "" || f.name.match(/[a-z0-9_-]+/) == null) {
throw `Invalid filter: a variable name should match [a-z0-9_-]+ at ${ctx}.fields[${i}]`

View file

@ -6,7 +6,8 @@
import { UIEventSource } from "../../Logic/UIEventSource"
import { onDestroy } from "svelte"
import { Utils } from "../../Utils"
import Tr from "../Base/Tr.svelte"
import type { ValidatorType } from "../InputElement/Validators"
import InputHelper from "../InputElement/InputHelper.svelte"
export let filteredLayer: FilteredLayer
export let option: FilterConfigOption
@ -18,7 +19,7 @@
parts = Utils.splitIntoSubstitutionParts(template)
}
let fieldValues: Record<string, UIEventSource<string>> = {}
let fieldTypes: Record<string, string> = {}
let fieldTypes: Record<string, ValidatorType> = {}
let appliedFilter = <UIEventSource<string>>filteredLayer.appliedFilters.get(id)
let initialState: Record<string, string> = JSON.parse(appliedFilter?.data ?? "{}")
@ -35,25 +36,30 @@
appliedFilter?.setData(FilteredLayer.fieldsToString(properties))
}
let firstValue : UIEventSource<string>
for (const field of option.fields) {
// A bit of cheating: the 'parts' will have '}' suffixed for fields
const src = new UIEventSource<string>(initialState[field.name] ?? "")
firstValue ??= src
fieldTypes[field.name] = field.type
console.log(field.name, "-->", field.type)
fieldValues[field.name] = src
onDestroy(
src.stabilized(200).addCallback(() => {
setFields()
})
}),
)
}
</script>
<div>
<div class="low-interaction p-1 rounded-2xl px-3" class:interactive={$firstValue?.length > 0}>
{#each parts as part, i}
{#if part["subs"]}
<!-- This is a field! -->
<span class="mx-1">
<ValidatedInput value={fieldValues[part["subs"]]} type={fieldTypes[part["subs"]]} />
<InputHelper value={fieldValues[part["subs"]]} type={fieldTypes[part["subs"]]}>
<ValidatedInput slot="fallback" value={fieldValues[part["subs"]]} type={fieldTypes[part["subs"]]} />
</InputHelper>
</span>
{:else}
{@html part["message"]}

View file

@ -23,9 +23,9 @@
export let type: ValidatorType
export let value: UIEventSource<string | object>
export let feature: Feature
export let feature: Feature = undefined
export let args: (string | number | boolean)[] = undefined
export let state: SpecialVisualizationState
export let state: SpecialVisualizationState = undefined
</script>
{#if type === "translation"}
@ -51,4 +51,6 @@
<SlopeInput {value} {feature} {state} />
{:else if type === "wikidata"}
<WikidataInputHelper {value} {feature} {state} {args} />
{:else}
<slot name="fallback" />
{/if}

View file

@ -1272,7 +1272,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
public static sortedByLevenshteinDistance<T>(
reference: string,
ts: T[],
ts: ReadonlyArray<T>,
getName: (t: T) => string
): T[] {
const withDistance: [T, number][] = ts.map((t) => [