From 1c6d21b065e5dafc79de05a3c6183fc1e2fc5aa7 Mon Sep 17 00:00:00 2001 From: kjon Date: Fri, 19 Jan 2024 20:44:15 +0000 Subject: [PATCH 01/18] Translated using Weblate (German) Currently translated at 100.0% (602 of 602 strings) Translation: MapComplete/Core Translate-URL: https://hosted.weblate.org/projects/mapcomplete/core/de/ --- langs/de.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/langs/de.json b/langs/de.json index f340d4546..20a49f1e0 100644 --- a/langs/de.json +++ b/langs/de.json @@ -500,7 +500,8 @@ "selectOsmbasedmap": "OpenStreetMap-basierte Karte als Hintergrund auswählen (oder Hintergrundebene deaktivieren)", "selectSearch": "Suchleiste auswählen, um nach Orten zu suchen", "shakePhone": "Telefon schütteln", - "title": "Tastaturbefehle" + "title": "Tastaturbefehle", + "translationMode": "Übersetzungsmodus ein- oder ausschalten" }, "image": { "addPicture": "Bild hinzufügen", @@ -683,7 +684,7 @@ "hasBeenSplit": "Dieser Weg wurde geteilt", "inviteToSplit": "Die Straße in Abschnitte teilen. Dadurch können je Abschnitt unterschiedliche Eigenschaften angegeben werden.", "loginToSplit": "Anmeldung erforderlich, um Straßen zu teilen", - "split": "Teilen", + "split": "Aufteilen", "splitAgain": "Diese Straße erneut teilen", "splitTitle": "Wähle auf der Karte aus, wo sich die Eigenschaften dieser Straße ändern" }, From 7f8f25bd6c40f3d63341222a9a3f6c58550ce003 Mon Sep 17 00:00:00 2001 From: paunofu Date: Sun, 21 Jan 2024 22:34:08 +0000 Subject: [PATCH 02/18] Translated using Weblate (Catalan) Currently translated at 86.7% (2759 of 3182 strings) Translation: MapComplete/Layer translations Translate-URL: https://hosted.weblate.org/projects/mapcomplete/layers/ca/ --- langs/layers/ca.json | 505 ++++++++++++++++++++++--------------------- 1 file changed, 256 insertions(+), 249 deletions(-) diff --git a/langs/layers/ca.json b/langs/layers/ca.json index f89d20f61..4653df83b 100644 --- a/langs/layers/ca.json +++ b/langs/layers/ca.json @@ -35,6 +35,16 @@ "1": { "title": "un mupi" }, + "10": { + "description": "S'utilitza per a cartells publicitaris, rètols de neó, logotips i cartells en entrades institucionals", + "title": "un lletrer" + }, + "11": { + "title": "una escupltura" + }, + "12": { + "title": "una paret pintada" + }, "2": { "title": "un mupi sobre la paret" }, @@ -61,16 +71,6 @@ }, "9": { "title": "un tòtem" - }, - "10": { - "description": "S'utilitza per a cartells publicitaris, rètols de neó, logotips i cartells en entrades institucionals", - "title": "un lletrer" - }, - "11": { - "title": "una escupltura" - }, - "12": { - "title": "una paret pintada" } }, "tagRenderings": { @@ -165,6 +165,9 @@ "1": { "then": "Açò és un tauló d'anunis" }, + "10": { + "then": "Açò és una paret pintada" + }, "2": { "then": "Açò és una columna" }, @@ -188,9 +191,6 @@ }, "9": { "then": "Açò és un tòtem" - }, - "10": { - "then": "Açò és una paret pintada" } }, "question": "Quin tipus d'element publicitari és aquest?", @@ -205,6 +205,9 @@ "1": { "then": "Tauló d'anuncis" }, + "10": { + "then": "Paret Pintada" + }, "2": { "then": "Mupi" }, @@ -228,9 +231,6 @@ }, "9": { "then": "Tòtem" - }, - "10": { - "then": "Paret Pintada" } } } @@ -351,6 +351,15 @@ "1": { "then": "Mural" }, + "10": { + "then": "Azulejo (Rajoles decoratives espanyoles i portugueses)" + }, + "11": { + "then": "Enrajolat" + }, + "12": { + "then": "Tallat a la fusta" + }, "2": { "then": "Pintura" }, @@ -374,15 +383,6 @@ }, "9": { "then": "Relleu" - }, - "10": { - "then": "Azulejo (Rajoles decoratives espanyoles i portugueses)" - }, - "11": { - "then": "Enrajolat" - }, - "12": { - "then": "Tallat a la fusta" } }, "question": "Quin tipus d'obra és aquesta peça?", @@ -1920,30 +1920,6 @@ "1": { "then": "Endoll de paret Schuko sense pin a terra (CEE7/4 tipus F)" }, - "2": { - "then": "Endoll de paret Europeu amb pin a terra (CEE7/4 tipus E)" - }, - "3": { - "then": "Endoll de paret Europeu amb pin a terra (CEE7/4 tipus E)" - }, - "4": { - "then": "CHAdeMo" - }, - "5": { - "then": "CHAdeMo" - }, - "6": { - "then": "Tipus 1 amb cable" - }, - "7": { - "then": "Tipus 1 amb cable" - }, - "8": { - "then": "Tipus 1 sense cable" - }, - "9": { - "then": "Tipus 1 sense cable(J1772)" - }, "10": { "then": "CSS Tipus 1 (també conegut com a Tipus 1 Combo)" }, @@ -1967,6 +1943,30 @@ }, "17": { "then": "CSS Tipus 2 (mennekes)" + }, + "2": { + "then": "Endoll de paret Europeu amb pin a terra (CEE7/4 tipus E)" + }, + "3": { + "then": "Endoll de paret Europeu amb pin a terra (CEE7/4 tipus E)" + }, + "4": { + "then": "CHAdeMo" + }, + "5": { + "then": "CHAdeMo" + }, + "6": { + "then": "Tipus 1 amb cable" + }, + "7": { + "then": "Tipus 1 amb cable" + }, + "8": { + "then": "Tipus 1 sense cable" + }, + "9": { + "then": "Tipus 1 sense cable(J1772)" } } }, @@ -2112,6 +2112,13 @@ } } } + }, + "title": { + "mappings": { + "1": { + "then": "Estació de càrrega per a cotxes" + } + } } }, "climbing": { @@ -2636,6 +2643,9 @@ "1": { "then": "Aquesta via ciclista està pavimentada" }, + "10": { + "then": "Aquesta via ciclista està feta de grava fina" + }, "2": { "then": "Aquesta via ciclista està feta d'asfalt" }, @@ -2659,9 +2669,6 @@ }, "9": { "then": "Aquesta via ciclista està feta de grava" - }, - "10": { - "then": "Aquesta via ciclista està feta de grava fina" } }, "question": "De quina superfície està fet aquesta via ciclista?", @@ -2707,6 +2714,9 @@ "1": { "then": "Aquest carril bici està pavimentat" }, + "10": { + "then": "Aquesta via ciclista està feta de gravilla" + }, "2": { "then": "Aquest carril bici està fet d'asfalt" }, @@ -2718,9 +2728,6 @@ }, "9": { "then": "Aquesta via ciclista està feta de grava" - }, - "10": { - "then": "Aquesta via ciclista està feta de gravilla" } }, "question": "De què està feta la superfície d'aquest carrer?", @@ -3739,6 +3746,21 @@ "1": { "then": "Aquesta estació de fitness té un cartell amb instruccions per a un exercici concret." }, + "10": { + "then": "Aquesta estació de gimnàs té esglaons." + }, + "11": { + "then": "Aquesta estació de fitness disposa de cons per fer salts de granota." + }, + "12": { + "then": "Aquesta estació de fitness té bigues per saltar." + }, + "13": { + "then": "Aquesta estació de fitness té obstacles per a travesar." + }, + "14": { + "then": "Aquesta estació de fitness té una paret per enfilar-se." + }, "2": { "then": "Aquesta estació de fitness té una instal·lació per fer abdominals." }, @@ -3762,21 +3784,6 @@ }, "9": { "then": "Aquesta estació de fitness té llocs per fer exercicis d'eslàlom." - }, - "10": { - "then": "Aquesta estació de gimnàs té esglaons." - }, - "11": { - "then": "Aquesta estació de fitness disposa de cons per fer salts de granota." - }, - "12": { - "then": "Aquesta estació de fitness té bigues per saltar." - }, - "13": { - "then": "Aquesta estació de fitness té obstacles per a travesar." - }, - "14": { - "then": "Aquesta estació de fitness té una paret per enfilar-se." } } } @@ -3895,6 +3902,21 @@ "1": { "then": "Això és una fregiduria" }, + "10": { + "then": "Aquí es serveixen plats xinesos" + }, + "11": { + "then": "Aquí es serveixen plats grecs" + }, + "12": { + "then": "Aquí es serveixen plats indis" + }, + "13": { + "then": "Aquí es serveixen plats turcs" + }, + "14": { + "then": "Aquí es serveixen plats tailandesos" + }, "2": { "then": "Principalment serveix pasta" }, @@ -3918,21 +3940,6 @@ }, "9": { "then": "Aquí es serveixen plats francesos" - }, - "10": { - "then": "Aquí es serveixen plats xinesos" - }, - "11": { - "then": "Aquí es serveixen plats grecs" - }, - "12": { - "then": "Aquí es serveixen plats indis" - }, - "13": { - "then": "Aquí es serveixen plats turcs" - }, - "14": { - "then": "Aquí es serveixen plats tailandesos" } }, "question": "Quin tipus de menjar es serveix aquí?", @@ -4485,9 +4492,24 @@ "1": { "then": "Açò és un auditori" }, + "10": { + "then": "Açò és un laboratori" + }, + "14": { + "then": "Açò és una oficina" + }, + "16": { + "then": "Açò és un restaurant" + }, + "19": { + "then": "Açò és un magatzem" + }, "2": { "then": "Açò és un dormitori" }, + "22": { + "then": "Açò és una sala d'espera" + }, "3": { "then": "Açò és una capella" }, @@ -4505,21 +4527,6 @@ }, "9": { "then": "Açò és una cuina" - }, - "10": { - "then": "Açò és un laboratori" - }, - "14": { - "then": "Açò és una oficina" - }, - "16": { - "then": "Açò és un restaurant" - }, - "19": { - "then": "Açò és un magatzem" - }, - "22": { - "then": "Açò és una sala d'espera" } }, "question": "Quin tipus d'habitació és aquesta?" @@ -5121,6 +5128,19 @@ } } }, + "10": { + "options": { + "0": { + "question": "Totes les notes" + }, + "1": { + "question": "Oculta les notes d'importació" + }, + "2": { + "question": "Mostrar només les notes d'importació" + } + } + }, "2": { "options": { "0": { @@ -5176,19 +5196,6 @@ "question": "Sols mostra les notes obertes" } } - }, - "10": { - "options": { - "0": { - "question": "Totes les notes" - }, - "1": { - "question": "Oculta les notes d'importació" - }, - "2": { - "question": "Mostrar només les notes d'importació" - } - } } }, "name": "Notes d'OpenStreetMap", @@ -5488,6 +5495,15 @@ "1": { "then": "Aquesta és una plaça d'aparcament normal." }, + "10": { + "then": "Es tracta d'una plaça d'aparcament reservada per a pares amb fills." + }, + "11": { + "then": "Es tracta d'una plaça d'aparcament reservada al personal." + }, + "12": { + "then": "Aquest espai d'aparcament està reservat per a taxi." + }, "2": { "then": "Aquesta és una plaça d'aparcament per a minusvàlids." }, @@ -5505,15 +5521,6 @@ }, "9": { "then": "Es tracta d'una plaça d'aparcament reservada per a motos." - }, - "10": { - "then": "Es tracta d'una plaça d'aparcament reservada per a pares amb fills." - }, - "11": { - "then": "Es tracta d'una plaça d'aparcament reservada al personal." - }, - "12": { - "then": "Aquest espai d'aparcament està reservat per a taxi." } }, "question": "Quin tipus d'espai d'aparcament és aquest?" @@ -6088,6 +6095,21 @@ "1": { "then": "S'accepten monedes de 2 cèntims" }, + "10": { + "then": "S'accepten monedes de 20 cèntims" + }, + "11": { + "then": "S'accepten monedes de ½ franc" + }, + "12": { + "then": "S'accepten monedes d'1 franc" + }, + "13": { + "then": "S'accepten monedes de 2 francs" + }, + "14": { + "then": "S'accepten monedes de 5 francs" + }, "2": { "then": "S'accepten monedes de 5 cèntims" }, @@ -6111,21 +6133,6 @@ }, "9": { "then": "S'accepten monedes de 10 cèntims" - }, - "10": { - "then": "S'accepten monedes de 20 cèntims" - }, - "11": { - "then": "S'accepten monedes de ½ franc" - }, - "12": { - "then": "S'accepten monedes d'1 franc" - }, - "13": { - "then": "S'accepten monedes de 2 francs" - }, - "14": { - "then": "S'accepten monedes de 5 francs" } }, "question": "Quines monedes es poden utilitzar per a pagar aquí?" @@ -6138,6 +6145,15 @@ "1": { "then": "S'accepten bitllets de 10 euros" }, + "10": { + "then": "S'accepten bitllets de 100 francs" + }, + "11": { + "then": "S'accepten bitllets de 200 francs" + }, + "12": { + "then": "S'accepten bitllets de 1000 francs" + }, "2": { "then": "S'accepten bitllets de 20 euros" }, @@ -6161,15 +6177,6 @@ }, "9": { "then": "S'accepten bitllets de 50 francs" - }, - "10": { - "then": "S'accepten bitllets de 100 francs" - }, - "11": { - "then": "S'accepten bitllets de 200 francs" - }, - "12": { - "then": "S'accepten bitllets de 1000 francs" } }, "question": "Amb quins bitllets pot pagar aquí?" @@ -6524,30 +6531,6 @@ "1": { "question": "Reciclatge de piles" }, - "2": { - "question": "Reciclatge de cartrons de begudes" - }, - "3": { - "question": "Reciclatge de llaunes" - }, - "4": { - "question": "Reciclatge de roba" - }, - "5": { - "question": "Reciclatge d'oli de cuina" - }, - "6": { - "question": "Reciclatge d'oli de motor" - }, - "7": { - "question": "Reciclatge de tubs fluorescents" - }, - "8": { - "question": "Reciclatge de residus verds" - }, - "9": { - "question": "Reciclatge d'ampolles de vidre" - }, "10": { "question": "Reciclatge de vidre" }, @@ -6578,11 +6561,35 @@ "19": { "question": "Reciclatge del rebuig" }, + "2": { + "question": "Reciclatge de cartrons de begudes" + }, "20": { "question": "Reciclatge de cartutxos d'impressora" }, "21": { "question": "Reciclatge de bicicletes" + }, + "3": { + "question": "Reciclatge de llaunes" + }, + "4": { + "question": "Reciclatge de roba" + }, + "5": { + "question": "Reciclatge d'oli de cuina" + }, + "6": { + "question": "Reciclatge d'oli de motor" + }, + "7": { + "question": "Reciclatge de tubs fluorescents" + }, + "8": { + "question": "Reciclatge de residus verds" + }, + "9": { + "question": "Reciclatge d'ampolles de vidre" } } }, @@ -6650,30 +6657,6 @@ "1": { "then": "Aquí es poden reciclar els cartons de begudes" }, - "2": { - "then": "Aquí es poden reciclar llaunes" - }, - "3": { - "then": "Aquí es pot reciclar roba" - }, - "4": { - "then": "Aquí es pot reciclar oli de cuina" - }, - "5": { - "then": "Aquí es pot reciclar oli de motor" - }, - "6": { - "then": "Aquí es poden reciclar tub fluroescents" - }, - "7": { - "then": "Aquí es poden reciclar residus verds" - }, - "8": { - "then": "Ací es poden reciclar residus orgànics" - }, - "9": { - "then": "Aquí es poden reciclar ampolles de vidre" - }, "10": { "then": "Aquí es pot reciclar vidre" }, @@ -6704,6 +6687,9 @@ "19": { "then": "Aquí es poden reciclar sabates" }, + "2": { + "then": "Aquí es poden reciclar llaunes" + }, "20": { "then": "Aquí es poden reciclar petits electrodomèstics" }, @@ -6718,6 +6704,27 @@ }, "24": { "then": "Aquí es poden reciclar bicicletes" + }, + "3": { + "then": "Aquí es pot reciclar roba" + }, + "4": { + "then": "Aquí es pot reciclar oli de cuina" + }, + "5": { + "then": "Aquí es pot reciclar oli de motor" + }, + "6": { + "then": "Aquí es poden reciclar tub fluroescents" + }, + "7": { + "then": "Aquí es poden reciclar residus verds" + }, + "8": { + "then": "Ací es poden reciclar residus orgànics" + }, + "9": { + "then": "Aquí es poden reciclar ampolles de vidre" } }, "question": "Què es pot reciclar aquí?" @@ -7465,6 +7472,12 @@ "1": { "then": "Aquest fanal utilitza LED" }, + "10": { + "then": "Aquest fanal utilitza làmpades de sodi d'alta pressió (taronja amb blanc)" + }, + "11": { + "then": "Aquest fanal s'il·lumina amb gas" + }, "2": { "then": "Aquest fanal utilitza il·luminació incandescent" }, @@ -7488,12 +7501,6 @@ }, "9": { "then": "Aquest fanal utilitza làmpades de sodi de baixa pressió (taronja monocroma)" - }, - "10": { - "then": "Aquest fanal utilitza làmpades de sodi d'alta pressió (taronja amb blanc)" - }, - "11": { - "then": "Aquest fanal s'il·lumina amb gas" } }, "question": "Quin tipus d'il·luminació utilitza aquest fanal?" @@ -8698,30 +8705,6 @@ "1": { "question": "Venda de begudes" }, - "2": { - "question": "Venda de llaminadures" - }, - "3": { - "question": "Venda de menjar" - }, - "4": { - "question": "Venda de tabaco" - }, - "5": { - "question": "Venda de preservatius" - }, - "6": { - "question": "Venda de cafè" - }, - "7": { - "question": "Venda d'aigua" - }, - "8": { - "question": "Venda de diaris" - }, - "9": { - "question": "Venda de càmeres interiors de bicicletes" - }, "10": { "question": "Venda de llet" }, @@ -8752,6 +8735,9 @@ "19": { "question": "Venda de flors" }, + "2": { + "question": "Venda de llaminadures" + }, "20": { "question": "Venda de tiquets d'aparcament" }, @@ -8775,6 +8761,27 @@ }, "27": { "question": "Venda de cadenat per a bicicletes" + }, + "3": { + "question": "Venda de menjar" + }, + "4": { + "question": "Venda de tabaco" + }, + "5": { + "question": "Venda de preservatius" + }, + "6": { + "question": "Venda de cafè" + }, + "7": { + "question": "Venda d'aigua" + }, + "8": { + "question": "Venda de diaris" + }, + "9": { + "question": "Venda de càmeres interiors de bicicletes" } } } @@ -8821,30 +8828,6 @@ "1": { "then": "Es venen llaminadures" }, - "2": { - "then": "Es ven menjar" - }, - "3": { - "then": "Es ven tabaco" - }, - "4": { - "then": "Es venen preservatius" - }, - "5": { - "then": "Es ven cafè" - }, - "6": { - "then": "Es ven aigua" - }, - "7": { - "then": "Es venen diaris" - }, - "8": { - "then": "Es venen càmeres interiors de bicicletes" - }, - "9": { - "then": "Es ven llet" - }, "10": { "then": "Es ven pa" }, @@ -8875,6 +8858,9 @@ "19": { "then": "Es venen tiquets d'aparcament" }, + "2": { + "then": "Es ven menjar" + }, "20": { "then": "Es venen cèntims premsats" }, @@ -8895,6 +8881,27 @@ }, "26": { "then": "Es venen cadenats per a bicicletes" + }, + "3": { + "then": "Es ven tabaco" + }, + "4": { + "then": "Es venen preservatius" + }, + "5": { + "then": "Es ven cafè" + }, + "6": { + "then": "Es ven aigua" + }, + "7": { + "then": "Es venen diaris" + }, + "8": { + "then": "Es venen càmeres interiors de bicicletes" + }, + "9": { + "then": "Es ven llet" } }, "question": "Que ven aquesta màquina expenedora?", @@ -9186,4 +9193,4 @@ "render": "Turbina eòlica" } } -} \ No newline at end of file +} From cfa58b6387d87bc4e86077a30dd77691d110c9d3 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 19 Jan 2024 17:31:35 +0100 Subject: [PATCH 03/18] Studio: improve error handling, fix renumbering --- .../ThemeConfig/Conversion/Conversion.ts | 15 ++-- .../Conversion/ConversionContext.ts | 26 +++++++ .../ThemeConfig/Conversion/Validation.ts | 68 +++++++++++++------ src/UI/Studio/EditLayer.svelte | 2 +- src/UI/Studio/EditLayerState.ts | 67 ++++++++++++++++-- src/UI/Studio/ErrorIndicatorForRegion.svelte | 2 +- src/UI/Studio/MappingInput.svelte | 9 ++- src/UI/Studio/StudioServer.ts | 5 +- src/UI/Studio/TagRenderingInput.svelte | 15 ++-- src/UI/StudioGUI.svelte | 40 ++++++----- 10 files changed, 187 insertions(+), 62 deletions(-) diff --git a/src/Models/ThemeConfig/Conversion/Conversion.ts b/src/Models/ThemeConfig/Conversion/Conversion.ts index d74028731..867a3d8f8 100644 --- a/src/Models/ThemeConfig/Conversion/Conversion.ts +++ b/src/Models/ThemeConfig/Conversion/Conversion.ts @@ -2,7 +2,6 @@ import { LayerConfigJson } from "../Json/LayerConfigJson" import { Utils } from "../../../Utils" import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" import { ConversionContext } from "./ConversionContext" -import { T } from "vitest/dist/types-aac763a5" export interface DesugaringContext { tagRenderings: Map @@ -11,10 +10,11 @@ export interface DesugaringContext { } export type ConversionMsgLevel = "debug" | "information" | "warning" | "error" + export interface ConversionMessage { - context: ConversionContext - message: string - level: ConversionMsgLevel + readonly context: ConversionContext + readonly message: string + readonly level: ConversionMsgLevel } export abstract class Conversion { @@ -85,6 +85,7 @@ export class Pure extends Conversion { export class Bypass extends DesugaringStep { private readonly _applyIf: (t: T) => boolean private readonly _step: DesugaringStep + constructor(applyIf: (t: T) => boolean, step: DesugaringStep) { super("Applies the step on the object, if the object satisfies the predicate", [], "Bypass") this._applyIf = applyIf @@ -102,7 +103,6 @@ export class Bypass extends DesugaringStep { export class Each extends Conversion { private readonly _step: Conversion private readonly _msg: string - private readonly _filter: (x: X) => boolean constructor(step: Conversion, options?: { msg?: string }) { super( @@ -224,6 +224,7 @@ export class FirstOf extends Conversion { export class Cached extends Conversion { private _step: Conversion private readonly key: string + constructor(step: Conversion) { super("Secretly caches the output for the given input", [], "cached") this._step = step @@ -242,9 +243,11 @@ export class Cached extends Conversion { return converted } } + export class Fuse extends DesugaringStep { - private readonly steps: DesugaringStep[] protected debug = false + private readonly steps: DesugaringStep[] + constructor(doc: string, ...steps: DesugaringStep[]) { super( (doc ?? "") + diff --git a/src/Models/ThemeConfig/Conversion/ConversionContext.ts b/src/Models/ThemeConfig/Conversion/ConversionContext.ts index 2a0e5e848..db4bed4fd 100644 --- a/src/Models/ThemeConfig/Conversion/ConversionContext.ts +++ b/src/Models/ThemeConfig/Conversion/ConversionContext.ts @@ -1,4 +1,5 @@ import { ConversionMessage, ConversionMsgLevel } from "./Conversion" +import { Context } from "maplibre-gl" export class ConversionContext { /** @@ -42,6 +43,31 @@ export class ConversionContext { return new ConversionContext([], msg ? [msg] : [], ["test"]) } + /** + * Does an inline edit of the messages for which a new path is defined + * This is a slight hack + * @param rewritePath + */ + public rewriteMessages( + rewritePath: ( + p: ReadonlyArray + ) => undefined | ReadonlyArray + ): void { + for (let i = 0; i < this.messages.length; i++) { + const m = this.messages[i] + const newPath = rewritePath(m.context.path) + if (!newPath) { + continue + } + const rewrittenContext = new ConversionContext( + this.messages, + newPath, + m.context.operation + ) + this.messages[i] = { ...m, context: rewrittenContext } + } + } + static print(msg: ConversionMessage) { const noString = msg.context.path.filter( (p) => typeof p !== "string" && typeof p !== "number" diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index ed4c79098..2adf0f023 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -13,7 +13,10 @@ import { And } from "../../../Logic/Tags/And" import Translations from "../../../UI/i18n/Translations" import FilterConfigJson from "../Json/FilterConfigJson" import DeleteConfig from "../DeleteConfig" -import { QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" +import { + MappingConfigJson, + QuestionableTagRenderingConfigJson, +} from "../Json/QuestionableTagRenderingConfigJson" import Validators from "../../../UI/InputElement/Validators" import TagRenderingConfig from "../TagRenderingConfig" import { parse as parse_html } from "node-html-parser" @@ -21,9 +24,7 @@ import PresetConfig from "../PresetConfig" import { TagsFilter } from "../../../Logic/Tags/TagsFilter" import { Translatable } from "../Json/Translatable" import { ConversionContext } from "./ConversionContext" -import * as eli from "../../../assets/editor-layer-index.json" import { AvailableRasterLayers } from "../../RasterLayers" -import Back from "../../../assets/svg/Back.svelte" import PointRenderingConfigJson from "../Json/PointRenderingConfigJson" class ValidateLanguageCompleteness extends DesugaringStep { @@ -178,7 +179,7 @@ export class ValidateTheme extends DesugaringStep { if (!json.title) { context.enter("title").err(`The theme ${json.id} does not have a title defined.`) } - if(!json.icon){ + if (!json.icon) { context.enter("icon").err("A theme should have an icon") } if (this._isBuiltin && this._extractImages !== undefined) { @@ -831,6 +832,7 @@ class MiscTagRenderingChecks extends DesugaringStep { json: TagRenderingConfigJson | QuestionableTagRenderingConfigJson, context: ConversionContext ): TagRenderingConfigJson { + console.log(">>> Validating TR", context.path.join("."), json) if (json["special"] !== undefined) { context.err( 'Detected `special` on the top level. Did you mean `{"render":{ "special": ... }}`' @@ -848,13 +850,32 @@ class MiscTagRenderingChecks extends DesugaringStep { CheckTranslation.allowUndefined.convert(json[key], context.enter(key)) } for (let i = 0; i < json.mappings?.length ?? 0; i++) { - const mapping = json.mappings[i] + const mapping: MappingConfigJson = json.mappings[i] CheckTranslation.noUndefined.convert( mapping.then, context.enters("mappings", i, "then") ) if (!mapping.if) { - context.enters("mappings", i).err("No `if` is defined") + console.log( + "Checking mappings", + i, + "if", + mapping.if, + context.path.join("."), + mapping.then + ) + context.enters("mappings", i, "if").err("No `if` is defined") + } + if (mapping.addExtraTags) { + for (let j = 0; j < mapping.addExtraTags.length; j++) { + if (!mapping.addExtraTags[j]) { + context + .enters("mappings", i, "addExtraTags", j) + .err( + "Detected a 'null' or 'undefined' value. Either specify a tag or delete this item" + ) + } + } } const en = mapping?.then?.["en"] if (en && this.detectYesOrNo(en)) { @@ -977,6 +998,9 @@ class MiscTagRenderingChecks extends DesugaringStep { } } + if (context.hasErrors()) { + return undefined + } return json } @@ -996,6 +1020,7 @@ export class ValidateTagRenderings extends Fuse { constructor(layerConfig?: LayerConfigJson, doesImageExist?: DoesImageExist) { super( "Various validation on tagRenderingConfigs", + new MiscTagRenderingChecks(), new DetectShadowedMappings(layerConfig), new DetectConflictingAddExtraTags(), // TODO enable new DetectNonErasedKeysInMappings(), @@ -1003,8 +1028,7 @@ export class ValidateTagRenderings extends Fuse { new On("render", new ValidatePossibleLinks()), new On("question", new ValidatePossibleLinks()), new On("questionHint", new ValidatePossibleLinks()), - new On("mappings", new Each(new On("then", new ValidatePossibleLinks()))), - new MiscTagRenderingChecks() + new On("mappings", new Each(new On("then", new ValidatePossibleLinks()))) ) } } @@ -1107,7 +1131,9 @@ export class PrevalidateLayer extends DesugaringStep { context.enter("pointRendering").err("There are no pointRenderings at all...") } - json.pointRendering?.forEach((pr,i) => this._validatePointRendering.convert(pr, context.enters("pointeRendering", i))) + json.pointRendering?.forEach((pr, i) => + this._validatePointRendering.convert(pr, context.enters("pointeRendering", i)) + ) if (json["mapRendering"]) { context.enter("mapRendering").err("This layer has a legacy 'mapRendering'") @@ -1134,7 +1160,7 @@ export class PrevalidateLayer extends DesugaringStep { } if (json.tagRenderings !== undefined && json.tagRenderings.length > 0) { - new On("tagRendering", new Each(new ValidateTagRenderings(json))) + new On("tagRenderings", new Each(new ValidateTagRenderings(json))) if (json.title === undefined && json.source !== "special:library") { context .enter("title") @@ -1424,29 +1450,33 @@ class ValidatePointRendering extends DesugaringStep { } if (json["markers"]) { - context.enter("markers").err(`Detected a field 'markerS' in pointRendering. It is written as a singular case`) + context + .enter("markers") + .err( + `Detected a field 'markerS' in pointRendering. It is written as a singular case` + ) } if (json.marker && !Array.isArray(json.marker)) { - context.enter("marker").err( - "The marker in a pointRendering should be an array" - ) + context.enter("marker").err("The marker in a pointRendering should be an array") } if (json.location.length == 0) { - context.enter("location").err ( - "A pointRendering should have at least one 'location' to defined where it should be rendered. " - ) + context + .enter("location") + .err( + "A pointRendering should have at least one 'location' to defined where it should be rendered. " + ) } return json - - } } + export class ValidateLayer extends Conversion< LayerConfigJson, { parsed: LayerConfig; raw: LayerConfigJson } > { private readonly _skipDefaultLayers: boolean private readonly _prevalidation: PrevalidateLayer + constructor( path: string, isBuiltin: boolean, diff --git a/src/UI/Studio/EditLayer.svelte b/src/UI/Studio/EditLayer.svelte index 5d9aedbaf..fc74eb3ab 100644 --- a/src/UI/Studio/EditLayer.svelte +++ b/src/UI/Studio/EditLayer.svelte @@ -180,7 +180,7 @@
Advanced functionality - +
diff --git a/src/UI/Studio/EditLayerState.ts b/src/UI/Studio/EditLayerState.ts index 557ec2109..b52e6e85a 100644 --- a/src/UI/Studio/EditLayerState.ts +++ b/src/UI/Studio/EditLayerState.ts @@ -22,6 +22,7 @@ import { LayoutConfigJson } from "../../Models/ThemeConfig/Json/LayoutConfigJson import { PrepareTheme } from "../../Models/ThemeConfig/Conversion/PrepareTheme" import { ConversionContext } from "../../Models/ThemeConfig/Conversion/ConversionContext" import { LocalStorageSource } from "../../Logic/Web/LocalStorageSource" +import { TagRenderingConfigJson } from "../../Models/ThemeConfig/Json/TagRenderingConfigJson" export interface HighlightedTagRendering { path: ReadonlyArray @@ -66,7 +67,6 @@ export abstract class EditJsonState { this.messages = this.setupErrorsForLayers() const layerId = this.getId() - this.highlightedItem.addCallbackD((hl) => console.log("Highlighted item is", hl)) this.configuration .mapD((config) => { if (!this.sendingUpdates) { @@ -110,6 +110,7 @@ export abstract class EditJsonState { public async delete() { await this.server.delete(this.getId().data, this.category) } + public getStoreFor(path: ReadonlyArray): UIEventSource { const key = path.join(".") @@ -172,7 +173,6 @@ export abstract class EditJsonState { public setValueAt(path: ReadonlyArray, v: any) { let entry = this.configuration.data - console.trace("Setting value at", path,"to",v) const isUndefined = v === undefined || v === null || @@ -249,6 +249,62 @@ export abstract class EditJsonState { } } +class ContextRewritingStep extends Conversion { + private readonly _step: Conversion + private readonly _state: DesugaringContext + private readonly _getTagRenderings: (t: T) => TagRenderingConfigJson[] + + constructor( + state: DesugaringContext, + step: Conversion, + getTagRenderings: (t: T) => TagRenderingConfigJson[] + ) { + super( + "When validating a layer, the tagRenderings are first expanded. Some builtin tagRendering-calls (e.g. `contact`) will introduce _multiple_ tagRenderings, causing the count to be off. This class rewrites the error messages to fix this", + [], + "ContextRewritingStep" + ) + this._state = state + this._step = step + this._getTagRenderings = getTagRenderings + } + + convert(json: LayerConfigJson, context: ConversionContext): T { + const converted = this._step.convert(json, context) + const originalIds = json.tagRenderings?.map( + (tr) => (tr)["id"] + ) + if (!originalIds) { + return converted + } + + let newTagRenderings: TagRenderingConfigJson[] + if (converted === undefined) { + const prepared = new PrepareLayer(this._state) + newTagRenderings = ( + prepared.convert(json, context).tagRenderings + ) + } else { + newTagRenderings = this._getTagRenderings(converted) + } + context.rewriteMessages((path) => { + if (path[0] !== "tagRenderings") { + return undefined + } + const newPath = [...path] + const idToSearch = newTagRenderings[newPath[1]].id + const oldIndex = originalIds.indexOf(idToSearch) + if (oldIndex < 0) { + console.warn("Original ID was not found: ", idToSearch) + return undefined // We don't modify the message + } + newPath[1] = oldIndex + return newPath + }) + return converted + } +} + export default class EditLayerState extends EditJsonState { // Needed for the special visualisations public readonly osmConnection: OsmConnection @@ -334,9 +390,10 @@ export default class EditLayerState extends EditJsonState { } protected buildValidation(state: DesugaringContext) { - return new Pipe( - new PrepareLayer(state), - new ValidateLayer("dynamic", false, undefined, true) + return new ContextRewritingStep( + state, + new Pipe(new PrepareLayer(state), new ValidateLayer("dynamic", false, undefined, true)), + (t) => t.raw.tagRenderings ) } diff --git a/src/UI/Studio/ErrorIndicatorForRegion.svelte b/src/UI/Studio/ErrorIndicatorForRegion.svelte index e3fd4c084..b1983db5d 100644 --- a/src/UI/Studio/ErrorIndicatorForRegion.svelte +++ b/src/UI/Studio/ErrorIndicatorForRegion.svelte @@ -2,7 +2,7 @@ import EditLayerState from "./EditLayerState" import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid" - export let firstPaths: Set + export let firstPaths: Set export let state: EditLayerState let messagesCount = state.messages.map( (msgs) => diff --git a/src/UI/Studio/MappingInput.svelte b/src/UI/Studio/MappingInput.svelte index f36984085..d210811d4 100644 --- a/src/UI/Studio/MappingInput.svelte +++ b/src/UI/Studio/MappingInput.svelte @@ -11,9 +11,11 @@ import { Utils } from "../../Utils" import ToSvelte from "../Base/ToSvelte.svelte" import { VariableUiElement } from "../Base/VariableUIElement" + import { ExclamationTriangle } from "@babeard/svelte-heroicons/solid/ExclamationTriangle" export let state: EditLayerState export let path: (string | number)[] + let messages = state.messagesFor(path) let tag: UIEventSource = state.getStoreFor([...path, "if"]) let parsedTag = tag.map((t) => (t ? TagUtils.Tag(t) : undefined)) let exampleTags = parsedTag.map((pt) => { @@ -27,7 +29,6 @@ } return o }) - let uploadableOnly: boolean = true let thenText: UIEventSource> = state.getStoreFor([...path, "then"]) let thenTextEn = thenText.mapD((translation) => @@ -71,5 +72,11 @@ No then is set {/if} + {#if $messages.length > 0} +
+ + {$messages.length} errors +
+ {/if}
{/if} diff --git a/src/UI/Studio/StudioServer.ts b/src/UI/Studio/StudioServer.ts index 2c438f01b..1256e0895 100644 --- a/src/UI/Studio/StudioServer.ts +++ b/src/UI/Studio/StudioServer.ts @@ -2,6 +2,7 @@ import { Utils } from "../../Utils" import Constants from "../../Models/Constants" import { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" import { Store } from "../../Logic/UIEventSource" +import { LayoutConfigJson } from "../../Models/ThemeConfig/Json/LayoutConfigJson" export default class StudioServer { private readonly url: string @@ -47,11 +48,13 @@ export default class StudioServer { return layerOverview } + async fetch(layerId: string, category: "layers", uid?: number): Promise + async fetch(layerId: string, category: "themes", uid?: number): Promise async fetch( layerId: string, category: "layers" | "themes", uid?: number - ): Promise { + ): Promise { try { return await Utils.downloadJson(this.urlFor(layerId, category, uid)) } catch (e) { diff --git a/src/UI/Studio/TagRenderingInput.svelte b/src/UI/Studio/TagRenderingInput.svelte index 41e01a220..ee43f20d1 100644 --- a/src/UI/Studio/TagRenderingInput.svelte +++ b/src/UI/Studio/TagRenderingInput.svelte @@ -24,13 +24,13 @@ import { onMount } from "svelte" export let state: EditLayerState - export let schema: ConfigMeta - export let path: (string | number)[] + export let path: ReadonlyArray + let messages = state.messagesFor(path) let expertMode = state.expertMode const store = state.getStoreFor(path) let value = store.data let hasSeenIntro = UIEventSource.asBoolean( - LocalStorageSource.Get("studio-seen-tagrendering-tutorial", "false") + LocalStorageSource.Get("studio-seen-tagrendering-tutorial", "false"), ) onMount(() => { if (!hasSeenIntro.data) { @@ -43,7 +43,7 @@ * Should only be enabled for 'tagrenderings' in the theme, if the source is OSM */ let allowQuestions: Store = state.configuration.mapD( - (config) => path.at(0) === "tagRenderings" && config.source?.geoJson === undefined + (config) => path.at(0) === "tagRenderings" && config.source?.["geoJson"] === undefined, ) let mappingsBuiltin: MappingConfigJson[] = [] @@ -119,7 +119,7 @@ const freeformSchemaAll = ( questionableTagRenderingSchemaRaw.filter( - (schema) => schema.path.length == 2 && schema.path[0] === "freeform" && $allowQuestions + (schema) => schema.path.length == 2 && schema.path[0] === "freeform" && $allowQuestions, ) ) let freeformSchema = $expertMode @@ -128,7 +128,7 @@ const missing: string[] = questionableTagRenderingSchemaRaw .filter( (schema) => - schema.path.length >= 1 && !items.has(schema.path[0]) && !ignored.has(schema.path[0]) + schema.path.length >= 1 && !items.has(schema.path[0]) && !ignored.has(schema.path[0]), ) .map((schema) => schema.path.join(".")) console.log({ state }) @@ -164,7 +164,7 @@ {/if} {#each $mappings ?? [] as mapping, i (mapping)}
- +
+ {/each}