diff --git a/assets/layers/note/note.json b/assets/layers/note/note.json index 146bbc17f..8b4d9392d 100644 --- a/assets/layers/note/note.json +++ b/assets/layers/note/note.json @@ -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 not mention {search} in the first comment", - "nl": "Mag in de eerste opmerking niet \"{search}\" bevatten", - "de": "Sollte nicht {search} im ersten Kommentar erwähnen", - "es": "No debe mencionar {search} en el primer comentario", - "ca": "No s'ha de mencionar {search} al primer comentari", - "cs": "V prvním komentáři by jste neměli 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": "Not opened by contributor {search}", - "nl": "Niet geopend door bijdrager {search}", - "de": "Nicht erstellt von {search}", - "es": "No abierto por el contributor {search}", - "ca": "No obert pel contribuïdor {search}", - "cs": "Není otevřeno přispěvatelem {search}", - "fr": "Exclureles 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 not mention {search} in the first comment", + "nl": "Mag in de eerste opmerking niet \"{search}\" bevatten", + "de": "Sollte nicht {search} im ersten Kommentar erwähnen", + "es": "No debe mencionar {search} en el primer comentario", + "ca": "No s'ha de mencionar {search} al primer comentari", + "cs": "V prvním komentáři by jste neměli 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": "Not opened by contributor {search}", + "nl": "Niet geopend door bijdrager {search}", + "de": "Nicht erstellt von {search}", + "es": "No abierto por el contributor {search}", + "ca": "No obert pel contribuïdor {search}", + "cs": "Není otevřeno přispěvatelem {search}", + "fr": "Exclureles 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 diff --git a/src/Models/ThemeConfig/FilterConfig.ts b/src/Models/ThemeConfig/FilterConfig.ts index eff90cb2c..8e80b581b 100644 --- a/src/Models/ThemeConfig/FilterConfig.ts +++ b/src/Models/ThemeConfig/FilterConfig.ts @@ -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 = 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, 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}]` diff --git a/src/UI/BigComponents/FilterviewWithFields.svelte b/src/UI/BigComponents/FilterviewWithFields.svelte index e3a903106..9d9a52e43 100644 --- a/src/UI/BigComponents/FilterviewWithFields.svelte +++ b/src/UI/BigComponents/FilterviewWithFields.svelte @@ -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> = {} - let fieldTypes: Record = {} + let fieldTypes: Record = {} let appliedFilter = >filteredLayer.appliedFilters.get(id) let initialState: Record = JSON.parse(appliedFilter?.data ?? "{}") @@ -35,25 +36,30 @@ appliedFilter?.setData(FilteredLayer.fieldsToString(properties)) } + let firstValue : UIEventSource for (const field of option.fields) { // A bit of cheating: the 'parts' will have '}' suffixed for fields const src = new UIEventSource(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() - }) + }), ) } -
+
0}> {#each parts as part, i} {#if part["subs"]} - + + + {:else} {@html part["message"]} diff --git a/src/UI/InputElement/InputHelper.svelte b/src/UI/InputElement/InputHelper.svelte index 8a8f84466..3bbb4e904 100644 --- a/src/UI/InputElement/InputHelper.svelte +++ b/src/UI/InputElement/InputHelper.svelte @@ -23,9 +23,9 @@ export let type: ValidatorType export let value: UIEventSource - 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 {#if type === "translation"} @@ -51,4 +51,6 @@ {:else if type === "wikidata"} +{:else} + {/if} diff --git a/src/Utils.ts b/src/Utils.ts index 5db0113a8..99674d926 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1272,7 +1272,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be public static sortedByLevenshteinDistance( reference: string, - ts: T[], + ts: ReadonlyArray, getName: (t: T) => string ): T[] { const withDistance: [T, number][] = ts.map((t) => [