Merge develop

This commit is contained in:
Pieter Vander Vennet 2023-08-10 16:25:25 +02:00
commit 04ecdad1bb
61 changed files with 702 additions and 705 deletions

View file

@ -55,6 +55,28 @@
}
]
},
{
"id": "accepts_debit_cards",
"options": [
{
"osmTags": "payment:debit_cards=yes",
"question": {
"en": "Accepts debit cards"
}
}
]
},
{
"id": "accepts_credit_cards",
"options": [
{
"osmTags": "payment:credit_cards=yes",
"question": {
"en": "Accepts credit cards"
}
}
]
},
{
"id": "has_image",
"options": [

View file

@ -962,15 +962,41 @@
]
},
{
"id": "vegetarian",
"id": "food-category",
"options": [
{
"question": {
"en": "Has a vegetarian menu",
"nl": "Heeft een vegetarisch menu",
"de": "Vegetarische Gerichte im Angebot",
"es": "Tiene menú vegetariano",
"fr": "A un menu végétarien"
"fr": "A un menu végétarien",
"nl": "Heeft een vegetarisch menu"
}
},
{
"question": {
"en": "Only fastfood buisinesses"
},
"osmTags": "amenity=fast_food"
},
{
"question": {
"en": "Only restaurants"
},
"osmTags": "amenity=restaurant"
}
]
},
{
"id": "vegetarian",
"options": [
{
"question": {
"en": "Has a vegan menu",
"nl": "Heeft een veganistisch menu",
"de": "Vegane Gerichte im Angebot",
"es": "Tiene menú vegano",
"fr": "A un menu végétalien"
},
"osmTags": {
"or": [
@ -988,11 +1014,12 @@
"options": [
{
"question": {
"en": "Has a vegan menu",
"nl": "Heeft een veganistisch menu",
"de": "Vegane Gerichte im Angebot",
"es": "Tiene menú vegano",
"fr": "A un menu végétalien"
"en": "Has a halal menu",
"nl": "Heeft een halal menu",
"de": "Halal Gerichte im Angebot",
"es": "Tiene menú halah",
"fr": "A un menu halal",
"da": "Har en halalmenu"
},
"osmTags": {
"or": [

View file

@ -112,6 +112,7 @@
"website",
"email",
"phone",
"mastodon",
{
"builtin": "opening_hours_24_7",
"override": {

View file

@ -67,7 +67,19 @@
"defaults"
],
"render": "<a href='tel:{phone}'><img textmode='📞' alt='phone' src='./assets/layers/questions/phone.svg'/></a>",
"condition": "phone~*"
"mappings": [
{
"#": "ignore-image-in-then",
"if": "contact:phone~*",
"then": "<a href='tel:{contact:phone}'><img textmode='📞' alt='phone' src='./assets/layers/questions/phone.svg'/></a>"
}
],
"condition": {
"or": [
"phone~*",
"contact:phone~*"
]
}
},
{
"id": "emaillink",
@ -75,7 +87,19 @@
"defaults"
],
"render": "<a href='mailto:{email}'><img textmode='✉️' alt='email' src='./assets/layers/questions/send_email.svg'/></a>",
"condition": "email~*"
"mappings": [
{
"#": "ignore-image-in-then",
"if": "contact:email~*",
"then": "<a href='mailto:{contact:email}'><img textmode='✉️' alt='email' src='./assets/layers/questions/send_email.svg'/></a>"
}
],
"condition": {
"or": [
"email~*",
"contact:email~*"
]
}
},
{
"id": "websitelink",

View file

@ -79,24 +79,6 @@
"type": "pnat"
},
"mappings": [
{
"if": {
"and": [
"highway=living_street",
"_country!=be"
]
},
"then": {
"en": "This is a living street, which has a maxspeed of 20km/h",
"de": "Dies ist eine Wohnstraße, auf der eine Höchstgeschwindigkeit von 20 km/h gilt",
"nl": "Dit is een woonerf en heeft dus een maximale snelheid van 20km/h"
},
"icon": {
"path": "./assets/layers/maxspeed/living_street_be.svg",
"class": "large"
},
"hideInAnswer": true
},
{
"if": "highway=living_street",
"then": {
@ -108,6 +90,9 @@
"path": "./assets/layers/maxspeed/living_street_be.svg",
"class": "large"
},
"addExtraTags": [
"maxspeed=20"
],
"hideInAnswer": "_country!=be"
}
],

View file

@ -173,11 +173,13 @@
"render": {
"*": "<a href='tel:{phone}'>{phone}</a>"
},
"icon": "./assets/layers/questions/phone.svg",
"mappings": [
{
"if": "contact:phone~*",
"then": "<a href='tel:{contact:phone}'>{contact:phone}</a>",
"hideInAnswer": true
"hideInAnswer": true,
"icon": "./assets/layers/questions/phone.svg"
}
],
"freeform": {
@ -188,6 +190,21 @@
]
}
},
{
"id": "mastodon",
"description": "Shows and asks for the mastodon handle",
"question": {
"en": "What is the Mastodon-handle of {title()}?"
},
"freeform": {
"key": "contact:mastodon",
"type": "fediverse"
},
"render": {
"*": "{fediverse_link(contact:mastodon)}"
},
"icon": "./assets/svg/mastodon.svg"
},
{
"id": "osmlink",
"render": {
@ -205,6 +222,7 @@
"render": {
"*": "<a href='mailto:{email}' target='_blank'>{email}</a>"
},
"icon": "./assets/svg/envelope.svg",
"labels": [
"contact"
],
@ -236,6 +254,7 @@
"mappings": [
{
"if": "contact:email~*",
"icon": "./assets/svg/envelope.svg",
"then": "<a href='mailto:{contact:email}' target='_blank'>{contact:email}</a>",
"hideInAnswer": true
}
@ -253,6 +272,7 @@
"labels": [
"contact"
],
"icon": "./assets/layers/icons/website.svg",
"question": {
"en": "What is the website of {title()}?",
"nl": "Wat is de website van {title()}?",
@ -292,7 +312,8 @@
{
"if": "contact:website~*",
"then": "<a href='{contact:website}' rel='nofollow noopener noreferrer' target='_blank'>{contact:website}</a>",
"hideInAnswer": true
"hideInAnswer": true,
"icon": "./assets/layers/icons/website.svg"
}
]
},
@ -2174,6 +2195,33 @@
}
}
]
},
{
"id": "check_date",
"question": {
"en": "When was this object last checked?",
"de": "Wann wurde dieses Objekt zuletzt kontrolliert?",
"nl": "Wanneer is dit object voor het laatst gecontroleerd?"
},
"freeform": {
"key": "check_date",
"type": "date"
},
"render": {
"en": "This object was last checked on <b>{check_date}</b>",
"de": "Dieses Objekt wurde zuletzt kontrolliert am <b>{check_date}</b>",
"nl": "Dit object is voor het laatst gecontroleerd op <b>{check_date}</b>"
},
"mappings": [
{
"if": "check_date:={_now:date}",
"then": {
"en": "This object was last checked today",
"de": "Dieses Objekt wurde heute zuletzt kontrolliert",
"nl": "Dit object is vandaag voor het laatst gecontroleerd"
}
}
]
}
]
}
}

View file

@ -39,7 +39,8 @@
"point",
"centroid"
],
"icon": "circle:white;./assets/layers/waste_disposal/waste_disposal.svg"
"icon": "circle:white;./assets/layers/waste_disposal/waste_disposal.svg",
"iconSize": "20,20"
}
],
"presets": [

View file

@ -86,6 +86,7 @@
}
]
},
"payment-options-split",
{
"id": "coin",
"question": {
@ -205,7 +206,8 @@
}
]
},
"level"
"level",
"check_date"
],
"mapRendering": [
{
@ -235,8 +237,15 @@
]
}
],
"allowMove": {
"enableImproveAccuracy": true,
"enableRelocation": true
},
"deletion": true,
"filter": [
"open_now"
"open_now",
"accepts_debit_cards",
"accepts_credit_cards"
]
}
],

View file

@ -63,10 +63,10 @@
{
"builtin": "food",
"override": {
"minzoom": 19,
"minzoom": 18,
"filter": null,
"name": null
}
}
]
}
}

View file

@ -262,21 +262,8 @@
"searching": "Cercant…"
},
"sharescreen": {
"addToHomeScreen": "<h3>Afegir-lo a la pantalla d'inici</h3>Pots afegir aquesta web a la pantalla d'inici del teu smartphone per a que es vegi més nadiu. Apreta al botó 'Afegir a l'inici' a la barra d'adreces URL per fer-ho.",
"copiedToClipboard": "Enllaç copiat al portapapers",
"downloadCustomTheme": "Descarrega la configuració per a aquest tema",
"downloadCustomThemeHelp": "Un contribuïdor amb experiència pot utilitzar un arxiu per a millorar el vostre tema",
"editThemeDescription": "Afegir o canviar preguntes d'aquesta petició",
"editThisTheme": "Editar aquest repte",
"embedIntro": "<h3>Inclou-ho a la teva pàgina web</h3>Per favor, inclou aquest mapa dins de la teva pàgina web. <br/>T'animem a que ho facis, no cal que demanis permís. <br/> És gratuït, i sempre ho serà. A més gent que ho faci servir més valuós serà.",
"fsAddNew": "Activar el botó d'afegir nou PDI'",
"fsGeolocation": "Activar el botó de 'geolocalitza'm' (només mòbil)",
"fsIncludeCurrentBackgroundMap": "Incloure l'opció de fons actual <b>{name}</b>",
"fsIncludeCurrentLayers": "Incloure les opcions de capa actual",
"fsIncludeCurrentLocation": "Incloure localització actual",
"fsLayerControlToggle": "Iniciar el control de capes avançat",
"fsLayers": "Activar el control de capes",
"fsSearch": "Activar la barra de cerca",
"fsUserbadge": "Activar el botó d'entrada",
"fsWelcomeMessage": "Mostra el missatge emergent de benvinguda i pestanyes associades",
"intro": "<h3>Comparteix aquest mapa</h3> Comparteix aquest mapa copiant l'enllaç de sota i enviant-lo a amics i família:",

View file

@ -262,21 +262,8 @@
"searching": "Hledání…"
},
"sharescreen": {
"addToHomeScreen": "<h3>Přidejte stránku na domovskou obrazovku</h3>Tuto webovou stránku můžete snadno přidat na domovskou obrazovku vašeho smartphonu, aby působila nativně. Klikněte na tlačítko „Přidat na domovskou obrazovku“ na panelu s adresou URL.",
"copiedToClipboard": "Odkaz zkopírovaný do schránky",
"downloadCustomTheme": "Stáhnout konfiguraci tohoto tématu",
"downloadCustomThemeHelp": "Zkušený přispěvatel může tento soubor použít k vylepšení vašeho tématu",
"editThemeDescription": "Přidejte nebo změňte otázky k tomuto tématu mapy",
"editThisTheme": "Upravit toto téma",
"embedIntro": "<h3>Vložte mapu na své webové stránky</h3>Prosíme, vložte tuto mapu na své webové stránky. <br/>Doporučujeme vám to udělat - nemusíte ani žádat o povolení. <br/> Je a vždy to bude zdarma. Čím více lidí bude projekt používat, tím bude cennější.",
"fsAddNew": "Povolit tlačítko „přidat nový bod zájmu“",
"fsGeolocation": "Povolit tlačítko „geolokovat mě“ (pouze pro mobilní zařízení)",
"fsIncludeCurrentBackgroundMap": "Zahrnout aktuální volbu pozadí <b>{name}</b>",
"fsIncludeCurrentLayers": "Zahrnout aktuální volby vrstvy",
"fsIncludeCurrentLocation": "Zahrnout aktuální polohu",
"fsLayerControlToggle": "Začněte s rozšířeným ovládáním vrstvy",
"fsLayers": "Povolit ovládání vrstev",
"fsSearch": "Povolit vyhledávací pole",
"fsUserbadge": "Povolit tlačítko přihlášení",
"fsWelcomeMessage": "Zobrazit vyskakovací okno s uvítací zprávou a související karty",
"intro": "<h3>Sdílejte tuto mapu</h3> Sdílejte tuto mapu zkopírováním níže uvedeného odkazu a jeho zasláním přátelům a rodině:",

View file

@ -203,21 +203,8 @@
"searching": "Søger…"
},
"sharescreen": {
"addToHomeScreen": "<h3>Tilføj til din hjemmeskærm</h3>Du kan let tilføje denne hjemmeside til din smartphones hjemmeskærm for at få den integreret. Klik på 'Tilføj til hjemmeskærm' knappen i URL-feltet for at gøre det.",
"copiedToClipboard": "Link kopierer til udklipsholder",
"downloadCustomTheme": "Download opsætningen for dette tema",
"downloadCustomThemeHelp": "En erfaren bidragyder kan bruge denne fil til at forbedre dit tema",
"editThemeDescription": "Tilføj eller ret spørgsmål til dette korttema",
"editThisTheme": "Ret dette tema",
"embedIntro": "<h3>Indlejr på din hjemmeside</h3>Indlejr venligst dette kort på din hjemmeside. <br>Vi tilskynder dig til det - du behøver ikke engang at spørge om tilladelse. <br> Det det frit og gratis og vil altid være det. Jo flere der bruger det, jo mere værdifuldt bliver det.",
"fsAddNew": "Slå 'tilføj nyt POI' knappen til",
"fsGeolocation": "Slå 'geolocate-me' knappen til (kun mobil)",
"fsIncludeCurrentBackgroundMap": "Inkluder det aktuelle baggrundsvalg <b>{name}</b>",
"fsIncludeCurrentLayers": "Inkluder det aktuelle valg af lag",
"fsIncludeCurrentLocation": "Inkludere det aktuelle sted",
"fsLayerControlToggle": "Start med lagkontrollen udvidet",
"fsLayers": "Slå lagkontrollen til",
"fsSearch": "Slå søgefeltet til",
"fsUserbadge": "Slå loginknappen til",
"fsWelcomeMessage": "Vis velkomstbeskeden og tilknyttede faner",
"intro": "<h3>Del dette kort</h3>Del dette kort ved at kopiere linket nedenunder og send det til venner og familie:",

View file

@ -304,21 +304,8 @@
"searching": "Suchen …"
},
"sharescreen": {
"addToHomeScreen": "<h3>Karte zum Startbildschirm hinzufügen</h3> Fügen Sie diese Webseite zum Startbildschirm Ihres Smartphones hinzu, um ein natives Gefühl zu erhalten. Klicken Sie dazu in der Adressleiste auf die Schaltfläche 'Zum Startbildschirm hinzufügen'.",
"copiedToClipboard": "Verknüpfung in Zwischenablage kopiert",
"downloadCustomTheme": "Konfiguration für diese Karte herunterladen",
"downloadCustomThemeHelp": "Ein erfahrener Mitwirkender kann diese Datei verwenden, um Ihr Thema zu verbessern",
"editThemeDescription": "Fragen zu diesem Thema hinzufügen oder ändern",
"editThisTheme": "Dieses Thema bearbeiten",
"embedIntro": "<h3>Karte in Webseiten einbetten</h3>Betten Sie diese Karte in Ihre Webseite ein. <br/>Wir ermutigen Sie gern dazu - Sie müssen nicht mal um Erlaubnis fragen.<br/> Die Karte ist kostenlos und wird es immer sein. Je mehr Leute die Karte benutzen, desto wertvoller wird sie.",
"fsAddNew": "Schaltfläche 'neuen POI hinzufügen' aktivieren",
"fsGeolocation": "Schaltfläche 'Mich geolokalisieren' aktivieren (nur mobil)",
"fsIncludeCurrentBackgroundMap": "Aktuellen Hintergrund übernehmen <b>({name})</b>",
"fsIncludeCurrentLayers": "Aktuelle Ebenenauswahl übernehmen",
"fsIncludeCurrentLocation": "Aktuelle Position übernehmen",
"fsLayerControlToggle": "Ausgeklappte Ebenenauswahl anzeigen",
"fsLayers": "Ebenensteuerung aktivieren",
"fsSearch": "Suchleiste aktivieren",
"fsUserbadge": "Anmeldefeld aktivieren",
"fsWelcomeMessage": "Begrüßung und Registerkarten anzeigen",
"intro": "<h3>Karte teilen</h3> Mit dem folgenden Link können Sie diese Karte mit Freunden und Familie teilen:",

View file

@ -304,21 +304,9 @@
"searching": "Searching…"
},
"sharescreen": {
"addToHomeScreen": "<h3>Add to your home screen</h3>You can easily add this website to your smartphone home screen for a native feel. Click the 'Add to home screen' button in the URL bar to do this.",
"copiedToClipboard": "Link copied to clipboard",
"downloadCustomTheme": "Download the configuration for this theme",
"downloadCustomThemeHelp": "An experienced contributor can use this file to improve your theme",
"editThemeDescription": "Add or change questions to this map theme",
"editThisTheme": "Edit this theme",
"documentation": "For more information on available URL-parameters, <a href='https://github.com/pietervdvn/MapComplete/blob/develop/Docs/URL_Parameters.md' target='_blank'>consult the documentation</a>",
"embedIntro": "<h3>Embed on your website</h3>Please, embed this map into your website. <br/>We encourage you to do it - you don't even have to ask permission. <br/> It is free, and always will be. The more people are using this, the more valuable it becomes.",
"fsAddNew": "Enable the 'add new POI' button",
"fsGeolocation": "Enable the 'geolocate-me' button (mobile only)",
"fsIncludeCurrentBackgroundMap": "Include the current background choice <b>{name}</b>",
"fsIncludeCurrentLayers": "Include the current layer choices",
"fsIncludeCurrentLocation": "Include current location",
"fsLayerControlToggle": "Start with the layer control expanded",
"fsLayers": "Enable the layer control",
"fsSearch": "Enable the search bar",
"fsUserbadge": "Enable the login button",
"fsWelcomeMessage": "Show the welcome message popup and associated tabs",
"intro": "<h3>Share this map</h3> Share this map by copying the link below and sending it to friends and family:",
@ -610,6 +598,11 @@
"feedback": "This is not a valid email address",
"noAt": "An e-mail address must contain an @"
},
"fediverse": {
"description": "A fediverse handle, often @username@server.tld",
"feedback": "A fediverse handle consists of @username@server.tld or is a link to a profile",
"invalidHost": "{host} is not a valid hostname"
},
"float": {
"description": "a number",
"feedback": "This is not a number"

View file

@ -61,8 +61,6 @@
"searching": "Serĉante…"
},
"sharescreen": {
"editThisTheme": "Modifi ĉi tiun etoson",
"fsSearch": "Ŝalti la serĉbreton",
"fsUserbadge": "Ŝalti la salutbutonon"
},
"skip": "Preterpasi ĉi tiun demandon",

View file

@ -209,21 +209,8 @@
"searching": "Buscando…"
},
"sharescreen": {
"addToHomeScreen": "<h3>Añadir a la pantalla de inicio</h3>Puedes añadir esta web en la pantalla de inicio de tu smartphone para que se vea más nativo. Aprieta el botón 'añadir a inicio' en la barra de direcciones URL para hacerlo.",
"copiedToClipboard": "Enlace copiado en el portapapeles",
"downloadCustomTheme": "Descargar la configuración para este tema",
"downloadCustomThemeHelp": "Un contributor con experiencia puede utilizar este archivo para mejorar tu tema",
"editThemeDescription": "Añadir o cambiar preguntas de este reto",
"editThisTheme": "Editar este reto",
"embedIntro": "<h3>Inclúyelo en tu página web</h3>Incluye este mapa en tu página web. <br/> Te animamos a que lo hagas, no hace falta que pidas permiso. <br/> Es gratis, y siempre lo será. A más gente que lo use más valioso será.",
"fsAddNew": "Activar el botón de añadir nuevo PDI'",
"fsGeolocation": "Activar el botón de 'geolocalízame' (només mòbil)",
"fsIncludeCurrentBackgroundMap": "Incluir la opción de fondo actual <b>{name}</b>",
"fsIncludeCurrentLayers": "Incluir las opciones de capa actual",
"fsIncludeCurrentLocation": "Incluir localización actual",
"fsLayerControlToggle": "Iniciar el control de capas avanzado",
"fsLayers": "Activar el control de capas",
"fsSearch": "Activar la barra de búsqueda",
"fsUserbadge": "Activar el botón de entrada",
"fsWelcomeMessage": "Muestra el mensaje emergente de bienvenida y pestañas asociadas",
"intro": "<h3>Comparte este mapa</h3> Comparte este mapa copiando el enlace de debajo y enviándolo a amigos y familia:",

View file

@ -242,21 +242,8 @@
"searching": "Chargement…"
},
"sharescreen": {
"addToHomeScreen": "<h3>Ajouter à votre page d'accueil</h3> Vous pouvez facilement ajouter la carte à votre écran d'accueil de téléphone. Cliquer sur le bouton 'ajouter à l'écran d'accueil' dans la barre d'adresse pour effectuer cette tâche.",
"copiedToClipboard": "Lien copié dans le presse-papier",
"downloadCustomTheme": "Téléchargez la configuration de ce thème",
"downloadCustomThemeHelp": "Un contributeur expérimenté peut utiliser ce fichier pour améliorer votre thème",
"editThemeDescription": "Ajouter ou modifier des questions à ce thème",
"editThisTheme": "Editer ce thème",
"embedIntro": "<h3>Incorporer à votre site Web</h3>Ajouter la carte à votre site Web. <br>Nous vous y encourageons pas besoin de permission.<br> C'est gratuit et pour toujours. Plus de personnes l'utilisent, mieux c'est.",
"fsAddNew": "Activer le bouton 'ajouter un POI'",
"fsGeolocation": "Activer le bouton 'Localisez-moi' (seulement sur mobile)",
"fsIncludeCurrentBackgroundMap": "Include le choix actuel d'arrière plan <b>{name}</b>",
"fsIncludeCurrentLayers": "Inclure la couche selectionnée",
"fsIncludeCurrentLocation": "Inclure l'emplacement actuel",
"fsLayerControlToggle": "Démarrer avec le contrôle des couches ouvert",
"fsLayers": "Activer le contrôle des couches",
"fsSearch": "Activer la barre de recherche",
"fsUserbadge": "Activer le bouton de connexion",
"fsWelcomeMessage": "Afficher le message de bienvenue et les onglets associés",
"intro": "<h3>Partager cette carte</h3> Partagez cette carte en copiant le lien suivant et envoyez-le à vos amis:",

View file

@ -101,19 +101,8 @@
"searching": "Procurando..."
},
"sharescreen": {
"addToHomeScreen": "<h3>Engadir á pantalla de inicio</h3>Podes engadir esta web na pantalla de inicio do teu smartphone para que se vexa máis nativo. Preme o botón 'engadir ó inicio' na barra de enderezos URL para facelo.",
"copiedToClipboard": "Ligazón copiada ó portapapeis",
"editThemeDescription": "Engadir ou mudar preguntas a este tema do mapa",
"editThisTheme": "Editar este tema",
"embedIntro": "<h3>Inclúeo na túa páxina web</h3>Inclúe este mapa na túa páxina web. <br/> Animámoche a que o fagas, non fai falla que pidas permiso. <br/> É de balde, e sempre será. Canta máis xente que o empregue máis valioso será.",
"fsAddNew": "Activar o botón de 'engadir novo PDI'",
"fsGeolocation": "Activar o botón de 'xeolocalizarme' (só móbil)",
"fsIncludeCurrentBackgroundMap": "Incluír a opción de fondo actual <b>{name}</b>",
"fsIncludeCurrentLayers": "Incluír as opcións de capa actual",
"fsIncludeCurrentLocation": "Incluír localización actual",
"fsLayerControlToggle": "Comenza co control de capas expandido",
"fsLayers": "Activar o control de capas",
"fsSearch": "Activar a barra de procura",
"fsUserbadge": "Activar botón de inicio de sesión",
"fsWelcomeMessage": "Amosar a xanela emerxente da mensaxe de benvida e as lapelas asociadas",
"intro": "<h3>Comparte este mapa</h3> Comparte este mapa copiando a ligazón de embaixo e enviándoa ás amizades e familia:",

View file

@ -196,19 +196,8 @@
"searching": "Keresés…"
},
"sharescreen": {
"addToHomeScreen": "<h3>Hozzáadás a kezdőképernyőhöz</h3> Könnyedén hozzáadhatod ezt a weboldalt az okostelefon kezdőképernyőjéhez a natív hangulat érdekében. Ehhez kattints az URL-sávban a „Hozzáadás a kezdőképernyőhöz” gombra.",
"copiedToClipboard": "Link a vágólapra másolva",
"editThemeDescription": "Térképtémához tartozó kérdések hozzáadása vagy módosítása",
"editThisTheme": "Téma szerkesztése",
"embedIntro": "<h3>Beágyazás egy weboldalon</h3>Kérjük, illeszd be ezt a térképet a weboldalba. <br>Biztatunk, tedd meg még engedélyt sem kell kérned. <br> Ingyenes, és az is marad. Minél többen használják, annál értékesebbé válik. A pipákra kattintva módosíthatod a paramétereket:",
"fsAddNew": "„Új érdekes pont (POI) hozzáadása” gomb engedélyezése",
"fsGeolocation": "„Saját helyem megjelenítése” gomb engedélyezése (csak mobileszközön)",
"fsIncludeCurrentBackgroundMap": "Tartalmazza a jelenleg kiválasztott hátteret (<b>{name}</b>)",
"fsIncludeCurrentLayers": "Tartalmazza a jelenleg kiválasztott rétegeket",
"fsIncludeCurrentLocation": "Tartalmazza az aktuális helyet",
"fsLayerControlToggle": "Kezdés kibontott rétegvezérlővel",
"fsLayers": "Rétegvezérlő engedélyezése",
"fsSearch": "Keresősáv engedélyezése",
"fsUserbadge": "Bejelentkezési gomb engedélyezése",
"fsWelcomeMessage": "Felugró üdvözlő üzenet és kapcsolódó fülek megjelenítése",
"intro": "<h3>Térkép megosztása</h3> Oszd meg ezt a térképet! Másold ki az alábbi linket, és küldd el a barátaidnak és a családodnak:",

View file

@ -106,15 +106,6 @@
},
"sharescreen": {
"copiedToClipboard": "Tautan disalin ke papan klip",
"editThemeDescription": "Tambahkan atau ubah pertanyaan ke tema peta ini",
"editThisTheme": "Sunting tema ini",
"fsAddNew": "Aktifkan tombol 'tambah POI baru'",
"fsGeolocation": "Aktifkan tombol 'geolocate-me' (hanya seluler)",
"fsIncludeCurrentBackgroundMap": "Sertakan pilihan latar belakang saat ini <b>{name}</b>",
"fsIncludeCurrentLayers": "Sertakan pilihan lapisan saat ini",
"fsIncludeCurrentLocation": "Sertakan lokasi saat ini",
"fsLayers": "Aktifkan kontrol lapisan",
"fsSearch": "Aktifkan bilah pencarian",
"fsUserbadge": "Aktifkan tombol masuk",
"fsWelcomeMessage": "Tampilkan popup pesan selamat datang dan tab terkait",
"thanksForSharing": "Terima kasih telah berbagi!"

View file

@ -204,21 +204,8 @@
"searching": "Ricerca…"
},
"sharescreen": {
"addToHomeScreen": "<h3>Aggiungi alla tua schermata Home</h3>Puoi aggiungere facilmente questo sito web alla schermata Home del tuo smartphone. Per farlo, clicca sul pulsante Aggiungi a schermata Home nella barra degli indirizzi.",
"copiedToClipboard": "Collegamento copiato negli appunti",
"downloadCustomTheme": "Scarica la configurazione di questo argomento",
"downloadCustomThemeHelp": "Una persona esperta può utilizzare questo file per migliorare il tuo argomento",
"editThemeDescription": "Aggiungi o modifica le domande a questo tema della mappa",
"editThisTheme": "Modifica questo tema",
"embedIntro": "<h3>Incorpora nel tuo sito web</h3>Siamo lieti se vorrai includere questa cartina nel tuo sito web.<br>Ti incoraggiamo a farlo (non devi neanche chieder il permesso).<br>È gratuito e lo sarà per sempre. Più persone lo useranno e più valore acquisirà.",
"fsAddNew": "Abilita il pulsante aggiungi nuovo PDI",
"fsGeolocation": "Abilita il pusante geo-localizzami (solo da mobile)",
"fsIncludeCurrentBackgroundMap": "Includi lo sfondo attualmente selezionato <b>{name}</b>",
"fsIncludeCurrentLayers": "Includi i livelli correntemente selezionati",
"fsIncludeCurrentLocation": "Includi la posizione attuale",
"fsLayerControlToggle": "Inizia con il pannello dei livelli aperto",
"fsLayers": "Abilita il controllo dei livelli",
"fsSearch": "Abilita la barra di ricerca",
"fsUserbadge": "Abilita il pulsante di accesso",
"fsWelcomeMessage": "Mostra il messaggio di benvenuto e le schede associate",
"intro": "<h3>Condividi questa mappa</h3>Condividi questa mappa copiando il collegamento qua sotto e inviandolo ad amici o parenti:",

View file

@ -101,19 +101,8 @@
"searching": "検索中…"
},
"sharescreen": {
"addToHomeScreen": "<h3>ホーム画面に追加する</h3>このサイトはスマートフォンのホーム画面に簡単に追加でき、ネイティブな雰囲気になります。これを行うには、URLバーの「ホーム画面に追加ボタン」をクリックします。",
"copiedToClipboard": "クリップボードにコピーされたリンク",
"editThemeDescription": "このマップテーマに質問を追加または変更する",
"editThisTheme": "このテーマを編集",
"embedIntro": "<h3>お客様のWebサイトに埋め込む</h3> この地図をお客様のWebサイトに埋め込みます。<br>許可を得る必要もありませんので、ぜひご利用ください。<br>無料であり、常に利用できます。使う人が増えれば増えるほど、価値が増大します。",
"fsAddNew": "[新しいPOIの追加]ボタンを有効にする",
"fsGeolocation": "[geolocate-me]ボタンを有効にする(モバイルのみ)",
"fsIncludeCurrentBackgroundMap": "現在の背景の選択肢<b>{name}</b>を含める",
"fsIncludeCurrentLayers": "現在のレイヤの選択肢を含める",
"fsIncludeCurrentLocation": "現在の場所を含める",
"fsLayerControlToggle": "レイヤコントロールを展開して開始",
"fsLayers": "レイヤコントロールを有効にする",
"fsSearch": "検索バーを有効にする",
"fsUserbadge": "ログインボタンを有効にする",
"fsWelcomeMessage": "ウェルカムメッセージのポップアップと関連するタブを表示します",
"intro": "<h3>このマップを共有</h3>このマップを共有するには、次のリンクをコピーして、友人や家族に送信します。",

View file

@ -2065,6 +2065,13 @@
"question": "Har en halalmenu"
}
}
},
"5": {
"options": {
"0": {
"question": "Har en halalmenu"
}
}
}
},
"name": "Restauranter og fastfood"

View file

@ -4754,6 +4754,13 @@
"question": "Halal Gerichte im Angebot"
}
}
},
"5": {
"options": {
"0": {
"question": "Halal Gerichte im Angebot"
}
}
}
},
"name": "Restaurants und Imbisse",
@ -5855,9 +5862,6 @@
"mappings": {
"0": {
"then": "Dies ist eine Wohnstraße, auf der eine Höchstgeschwindigkeit von 20 km/h gilt"
},
"1": {
"then": "Dies ist eine Wohnstraße, auf der eine Höchstgeschwindigkeit von 20 km/h gilt"
}
},
"question": "Wie hoch ist die zulässige Höchstgeschwindigkeit, die man auf dieser Straße fahren darf?",

View file

@ -4739,6 +4739,12 @@
"options": {
"0": {
"question": "Has a vegetarian menu"
},
"1": {
"question": "Only fastfood buisinesses"
},
"2": {
"question": "Only restaurants"
}
}
},
@ -4755,6 +4761,13 @@
"question": "Has a halal menu"
}
}
},
"5": {
"options": {
"0": {
"question": "Has a halal menu"
}
}
}
},
"name": "Restaurants and fast food",
@ -5856,9 +5869,6 @@
"mappings": {
"0": {
"then": "This is a living street, which has a maxspeed of 20km/h"
},
"1": {
"then": "This is a living street, which has a maxspeed of 20km/h"
}
},
"question": "What is the legal maximum speed one is allowed to drive on this road?",

View file

@ -2610,6 +2610,13 @@
"question": "Tiene menú halah"
}
}
},
"5": {
"options": {
"0": {
"question": "Tiene menú halah"
}
}
}
},
"name": "Restaurantes y comida rápida",

View file

@ -3278,6 +3278,13 @@
"question": "A un menu halal"
}
}
},
"5": {
"options": {
"0": {
"question": "A un menu halal"
}
}
}
},
"name": "Restaurants et nourriture rapide",

View file

@ -4530,6 +4530,13 @@
"question": "Heeft een halal menu"
}
}
},
"5": {
"options": {
"0": {
"question": "Heeft een halal menu"
}
}
}
},
"name": "Eetgelegenheden",
@ -5553,9 +5560,6 @@
"mappings": {
"0": {
"then": "Dit is een woonerf en heeft dus een maximale snelheid van 20km/h"
},
"1": {
"then": "Dit is een woonerf en heeft dus een maximale snelheid van 20km/h"
}
},
"question": "Wat is de legale maximumsnelheid voor deze weg?",

View file

@ -233,20 +233,8 @@
"searching": "Søker …"
},
"sharescreen": {
"addToHomeScreen": "<h3>Legg til på hjemmeskjermen din</h3>Du kan enkelt legge til denne nettsiden på din smarttelefon-hjemmeskjerm for å få det hele integrert. Klikk på «Legg til på hjemmeskjerm»-knappen i nettadressefeltet for å gjøre dette.",
"copiedToClipboard": "Lenke kopiert til utklippstavle",
"downloadCustomTheme": "Last ned oppsettet for dette temaet",
"downloadCustomThemeHelp": "En dreven bidragsyter kan bruke denne filen for å forbedre temaet ditt",
"editThemeDescription": "Legg til eller endre spørsmål for dette karttemaet",
"editThisTheme": "Rediger dette temaet",
"embedIntro": "<h3>Bygg inn på nettsiden din</h3>Legg til dette kartet på nettsiden din. <br/>Du oppfordres til å gjøre dette, og trenger ikke å spørre om tillatelse. <br/> Det er fritt, og vil alltid være det. Desto flere som bruker dette, desto mer verdifullt blir det.",
"fsGeolocation": "Skru på «Geolokaliser meg»-knappen (kun for mobil)",
"fsIncludeCurrentBackgroundMap": "Inkluder nåværende bakgrunnsvalg <b>{name}</b>",
"fsIncludeCurrentLayers": "Inkluder nåværende lagvalg",
"fsIncludeCurrentLocation": "Inkluder nåværende posisjon",
"fsLayerControlToggle": "Start med lagkontrollen utvidet",
"fsLayers": "Skru på lagkontrollen",
"fsSearch": "Skru på søkefeltet",
"fsUserbadge": "Skru på innloggingsknappen",
"fsWelcomeMessage": "Vis velkomst-oppsprettsmeldinger og tilknyttede faner",
"intro": "<h3>Del dette kartet</h3> Del dette kartet ved å kopiere lenken nedenfor og sende den til venner og familie:",

View file

@ -304,21 +304,8 @@
"searching": "Aan het zoeken…"
},
"sharescreen": {
"addToHomeScreen": "<h3>Voeg toe aan je thuisscherm</h3>Je kan eenvoudigweg deze website aan het thuisscherm van je smartphone toevoegen voor een \"native feel\"",
"copiedToClipboard": "Link gekopieerd naar klembord",
"downloadCustomTheme": "Download de instellingen van dit thema",
"downloadCustomThemeHelp": "Een ervaren bijdrager kan op basis van dit bestand je thema verder verbeteren",
"editThemeDescription": "Pas vragen aan of voeg vragen toe aan dit kaartthema",
"editThisTheme": "Pas dit thema aan",
"embedIntro": "<h3>Plaats dit op je website</h3>Voeg dit kaartje toe op je eigen website.<br/>We moedigen dit zelfs aan - je hoeft geen toestemming te vragen.<br/> Het is gratis en zal dat altijd blijven. Hoe meer het gebruikt wordt, hoe waardevoller",
"fsAddNew": "Activeer het toevoegen van nieuwe POI",
"fsGeolocation": "Toon het knopje voor geolocalisatie (enkel op mobiel)",
"fsIncludeCurrentBackgroundMap": "Gebruik de huidige achtergrond <b>{name}</b>",
"fsIncludeCurrentLayers": "Toon enkel de huidig getoonde lagen",
"fsIncludeCurrentLocation": "Start op de huidige locatie",
"fsLayerControlToggle": "Toon de laagbediening meteen volledig",
"fsLayers": "Toon de knop voor laagbediening",
"fsSearch": "Activeer de zoekbalk",
"fsUserbadge": "Activeer de login-knop",
"fsWelcomeMessage": "Toon het welkomstbericht en de bijhorende tabbladen",
"intro": "<h3>Deel deze kaart</h3> Kopieer onderstaande link om deze kaart naar vrienden en familie door te sturen:",

View file

@ -55,7 +55,6 @@
"searching": "کھوجیا جا رہا اے۔ ۔ ۔"
},
"sharescreen": {
"editThisTheme": "ایہہ تھیم سودھو",
"thanksForSharing": "ٹھیک اے، مہربانی۔"
},
"weekdays": {

View file

@ -98,19 +98,8 @@
"searching": "Szukanie…"
},
"sharescreen": {
"addToHomeScreen": "<h3> Dodaj do ekranu głównego</h3>Możesz łatwo dodać tę stronę do ekranu głównego smartfona, aby poczuć się jak w domu. Kliknij przycisk \"Dodaj do ekranu głównego\" na pasku adresu URL, aby to zrobić.",
"copiedToClipboard": "Link został skopiowany do schowka",
"editThemeDescription": "Dodaj lub zmień pytania do tego motywu mapy",
"editThisTheme": "Edytuj ten motyw",
"embedIntro": "<h3>Umieść na swojej stronie internetowej</h3>Proszę, umieść tę mapę na swojej stronie internetowej. <br>Zachęcamy cię do tego - nie musisz nawet pytać o zgodę. <br>Jest ona darmowa i zawsze będzie. Im więcej osób jej używa, tym bardziej staje się wartościowa.",
"fsAddNew": "Włącz przycisk \"Dodaj nowe POI\"",
"fsGeolocation": "Włącz przycisk „Zlokalizuj mnie” (tylko na urządzeniach mobilnych)",
"fsIncludeCurrentBackgroundMap": "Dołącz bieżący wybór tła <b>{name}</b>",
"fsIncludeCurrentLayers": "Uwzględnij wybór bieżącej warstwy",
"fsIncludeCurrentLocation": "Uwzględnij bieżącą lokalizację",
"fsLayerControlToggle": "Zacznij od rozwiniętej kontroli warstw",
"fsLayers": "Włącz kontrolę warstw",
"fsSearch": "Włącz pasek wyszukiwania",
"fsUserbadge": "Włącz przycisk logowania",
"fsWelcomeMessage": "Pokaż wyskakujące okienko wiadomości powitalnej i powiązane zakładki",
"intro": "<h3> Udostępnij tę mapę</h3> Udostępnij tę mapę, kopiując poniższy link i wysyłając ją do przyjaciół i rodziny:",

View file

@ -242,21 +242,8 @@
"searching": "A procurar…"
},
"sharescreen": {
"addToHomeScreen": "<h3>Adicionar ao seu ecrã inicial</h3> Pode adicionar facilmente este site ao ecrã inicial do seu telemóvel. Para isso clique no botão 'Adicionar ao ecrã inicial' na barra de URL.",
"copiedToClipboard": "Hiperligação copiada para a área de transferência",
"downloadCustomTheme": "Descarregar a configuração para este tema",
"downloadCustomThemeHelp": "Um colaborador experiente pode utilizar este ficheiro para melhorar o seu tema",
"editThemeDescription": "Adicionar ou alterar perguntas deste tema",
"editThisTheme": "Editar este tema",
"embedIntro": "<h3>Incorporar no seu site</h3>Por favor, insira este mapa no seu site. <br>Encorajamos a fazê-lo - nem precisa de pedir permissão. <br> É grátis e sempre será. Quanto mais pessoas estiverem a usar isto, mais valioso se torna.",
"fsAddNew": "Ativar o botão 'adicionar novo POI'",
"fsGeolocation": "Ativar o botão 'localizar-me geograficamente' (apenas telemóvel)",
"fsIncludeCurrentBackgroundMap": "Incluir a escolha de fundo atual <b>{name}</b>",
"fsIncludeCurrentLayers": "Incluir as opções de camada atuais",
"fsIncludeCurrentLocation": "Incluir localização atual",
"fsLayerControlToggle": "Começar com o controlo de camadas expandido",
"fsLayers": "Ativar o controlo das camadas",
"fsSearch": "Ativar a barra de pesquisa",
"fsUserbadge": "Ativar o botão de iniciar sessão",
"fsWelcomeMessage": "Mostrar a janela com a mensagem de boas-vindas e separadores associados",
"intro": "<h3>Partilhar este mapa</h3> Partilhe este mapa copiando a hiperligação abaixo e enviando-a a amigos e familiares:",

View file

@ -98,19 +98,8 @@
"searching": "Procurando…"
},
"sharescreen": {
"addToHomeScreen": "<h3>Adicionar à sua tela inicial</h3>Você pode adicionar facilmente este site à tela inicial do smartphone para uma sensação nativa. Clique no botão 'adicionar à tela inicial' na barra de URL para fazer isso.",
"copiedToClipboard": "Link copiado para a área de transferência",
"editThemeDescription": "Adicione ou altere perguntas a este tema do mapa",
"editThisTheme": "Editar neste tema",
"embedIntro": "<h3>Incorpore em seu site</h3>Por favor, incorpore este mapa em seu site.<br>Nós o encorajamos a fazer isso - você nem precisa pedir permissão.<br>É gratuito e sempre será. Quanto mais pessoas usarem isso, mais valioso se tornará.",
"fsAddNew": "Habilite o botão 'adicionar novo POI'",
"fsGeolocation": "Ative o botão 'localizar-me geograficamente' (apenas para celular)",
"fsIncludeCurrentBackgroundMap": "Incluir a escolha de fundo atual <b>{name}</b>",
"fsIncludeCurrentLayers": "Incluir as opções de camada atuais",
"fsIncludeCurrentLocation": "Incluir localização atual",
"fsLayerControlToggle": "Iniciar com o controle de camada expandido",
"fsLayers": "Ativar o controle de camada",
"fsSearch": "Ativar a barra de pesquisa",
"fsUserbadge": "Habilite o botão de login",
"fsWelcomeMessage": "Mostra o pop-up da mensagem de boas-vindas e as guias associadas",
"intro": "<h3>Compartilhe este mapa</h3> Compartilhe este mapa copiando o link abaixo e enviando-o para amigos e familiares:",

View file

@ -114,19 +114,8 @@
"searching": "Поиск…"
},
"sharescreen": {
"addToHomeScreen": "<h3>Добавить на домашний экран</h3>Вы можете легко добавить этот сайт на домашний экран вашего смартфона. Для этого нажмите кнопку \"Добавить на главный экран\" в строке URL.",
"copiedToClipboard": "Ссылка скопирована в буфер обмена",
"editThemeDescription": "Добавить или изменить вопросы к этой теме карты",
"editThisTheme": "Редактировать эту тему",
"embedIntro": "<h3>Встроить на свой сайт</h3>Пожалуйста, вставьте эту карту на свой сайт.<br>Мы призываем вас сделать это - вам даже не нужно спрашивать разрешения.<br>Карта бесплатна и всегда будет бесплатной. Чем больше людей пользуются ею, тем более ценной она становится.",
"fsAddNew": "Включить кнопку \"добавить новую точку интереса\"",
"fsGeolocation": "Включить кнопку \"найди меня\" (только в мобильной версии)",
"fsIncludeCurrentBackgroundMap": "Включить текущий фоновый слой <b>{name}</b>",
"fsIncludeCurrentLayers": "Включить текущие выбранные слои",
"fsIncludeCurrentLocation": "Включить текущее местоположение карты",
"fsLayerControlToggle": "Открыть панель выбора слоя",
"fsLayers": "Включить выбор слоя карты",
"fsSearch": "Включить строку поиска",
"fsUserbadge": "Включить кнопку входа в систему",
"fsWelcomeMessage": "Показать всплывающее окно с приветствием и соответствующие вкладки",
"intro": "<h3>Поделиться этой картой</h3> Поделитесь этой картой, скопировав ссылку ниже и отправив её друзьям и близким:",

View file

@ -226,21 +226,8 @@
"searching": "搜尋中…"
},
"sharescreen": {
"addToHomeScreen": "<h3>新增到你主頁畫面</h3>你可以輕易將這網站加到你智慧型手機的主頁畫面,在網址列點選 '新增到主頁按鈕'來做這件事情。",
"copiedToClipboard": "複製連結到簡貼簿",
"downloadCustomTheme": "下載這個主題的設定",
"downloadCustomThemeHelp": "有經驗的貢獻者能使用這個檔案來改善你的主題",
"editThemeDescription": "新增或改變這個地圖主題的問題",
"editThisTheme": "編輯這個主題",
"embedIntro": "<h3>嵌入到你的網站</h3>請考慮將這份地圖嵌入您的網站。<br>地圖毋須額外授權,非常歡迎您多加利用。<br>一切都是免費的,而且之後也是免費的,越有更多人使用,則越顯得它的價值。",
"fsAddNew": "啟用'新增新的興趣點'按鈕",
"fsGeolocation": "啟用'地理定位自身'按鈕 (只有行動版本)",
"fsIncludeCurrentBackgroundMap": "包含目前背景選擇<b>{name}</b>",
"fsIncludeCurrentLayers": "包含目前選擇圖層",
"fsIncludeCurrentLocation": "包含目前位置",
"fsLayerControlToggle": "開始時擴展圖層控制",
"fsLayers": "啟用圖層控制",
"fsSearch": "啟用搜尋列",
"fsUserbadge": "啟用登入按鈕",
"fsWelcomeMessage": "顯示歡迎訊息以及相關頁籤",
"intro": "<h3>分享這地圖</h3>複製下面的連結來向朋友與家人分享這份地圖:",

View file

@ -1,6 +1,6 @@
{
"name": "mapcomplete",
"version": "0.31.2",
"version": "0.31.3",
"repository": "https://github.com/pietervdvn/MapComplete",
"description": "A small website to edit OSM easily",
"bugs": "https://github.com/pietervdvn/MapComplete/issues",

View file

@ -388,11 +388,11 @@ WriteFile("./Docs/Tags_format.md", TagUtils.generateDocs(), ["src/Logic/Tags/Tag
}
}
for (const usedBuiltin of usedBuiltins) {
var using = layersUsingBuiltin.get(usedBuiltin)
if (using === undefined) {
const usingLayers = layersUsingBuiltin.get(usedBuiltin)
if (usingLayers === undefined) {
layersUsingBuiltin.set(usedBuiltin, [layer.id])
} else {
using.push(layer.id)
usingLayers.push(layer.id)
}
}
@ -413,7 +413,7 @@ WriteFile("./Docs/URL_Parameters.md", QueryParameterDocumentation.GenerateQueryP
"src/Logic/Web/QueryParameters.ts",
"src/UI/QueryParameterDocumentation.ts",
])
if (fakedom === undefined || window === undefined) {
if (fakedom === undefined) {
throw "FakeDom not initialized"
}
QueryParameters.GetQueryParameter(

View file

@ -1,5 +1,5 @@
import ScriptUtils from "./ScriptUtils"
import { Utils } from "../Utils"
import { Utils } from "../src/Utils"
import * as fs from "fs"
async function main(args: string[]) {

View file

@ -72,6 +72,11 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
return
}
if (neededTiles.total > 100) {
console.error("Too much tiles to download!")
return
}
this.isRunning.setData(true)
try {
const tileNumbers = Tiles.MapRange(neededTiles, (x, y) => {
@ -133,7 +138,6 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
}
private async LoadTile(z: number, x: number, y: number): Promise<void> {
console.log("OsmFeatureSource: loading ", z, x, y, "from", this._backend)
if (z >= 22) {
throw "This is an absurd high zoom level"
}
@ -145,6 +149,7 @@ export default class OsmFeatureSource extends FeatureSourceMerger {
if (this._downloadedTiles.has(index)) {
return
}
console.log("OsmFeatureSource: loading ", z, x, y, "from", this._backend)
this._downloadedTiles.add(index)
const bbox = BBox.fromTile(z, x, y)

View file

@ -51,10 +51,9 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
*/
public readonly layoutToUse: LayoutConfig
public readonly featureSwitchUserbadge: UIEventSource<boolean>
public readonly featureSwitchEnableLogin: UIEventSource<boolean>
public readonly featureSwitchSearch: UIEventSource<boolean>
public readonly featureSwitchBackgroundSelection: UIEventSource<boolean>
public readonly featureSwitchAddNew: UIEventSource<boolean>
public readonly featureSwitchWelcomeMessage: UIEventSource<boolean>
public readonly featureSwitchCommunityIndex: UIEventSource<boolean>
public readonly featureSwitchExtraLinkEnabled: UIEventSource<boolean>
@ -78,10 +77,10 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
// Helper function to initialize feature switches
this.featureSwitchUserbadge = FeatureSwitchUtils.initSwitch(
"fs-userbadge",
this.featureSwitchEnableLogin = FeatureSwitchUtils.initSwitch(
"fs-enable-login",
layoutToUse?.enableUserBadge ?? true,
"Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode."
"Disables/Enables logging in and thus disables editing all together. This effectively puts MapComplete into read-only mode."
)
this.featureSwitchSearch = FeatureSwitchUtils.initSwitch(
"fs-search",
@ -99,11 +98,7 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
layoutToUse?.enableLayers ?? true,
"Disables/Enables the filter view"
)
this.featureSwitchAddNew = FeatureSwitchUtils.initSwitch(
"fs-add-new",
layoutToUse?.enableAddNewPoints ?? true,
"Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)"
)
this.featureSwitchWelcomeMessage = FeatureSwitchUtils.initSwitch(
"fs-welcome-message",
true,
@ -201,12 +196,6 @@ export default class FeatureSwitchState extends OsmConnectionFeatureSwitches {
)
)
this.featureSwitchUserbadge.addCallbackAndRun((userbadge) => {
if (!userbadge) {
this.featureSwitchAddNew.setData(false)
}
})
this.backgroundLayerId = QueryParameters.GetQueryParameter(
"background",
layoutToUse?.defaultBackgroundId ?? "osm",

View file

@ -4,14 +4,13 @@
import { UIEventSource } from "../UIEventSource"
import Hash from "./Hash"
import { Utils } from "../../Utils"
import doc = Mocha.reporters.doc
export class QueryParameters {
static defaults: Record<string, string> = {}
static documentation: Map<string, string> = new Map<string, string>()
private static order: string[] = ["layout", "test", "z", "lat", "lon"]
protected static readonly _wasInitialized: Set<string> = new Set()
protected static readonly knownSources: Record<string, UIEventSource<string>> = {}
private static order: string[] = ["layout", "test", "z", "lat", "lon"]
private static initialized = false
public static GetQueryParameter(
@ -74,6 +73,7 @@ export class QueryParameters {
this.init()
return QueryParameters._wasInitialized.has(key)
}
public static initializedParameters(): ReadonlyArray<string> {
return Array.from(QueryParameters._wasInitialized.keys())
}
@ -108,14 +108,12 @@ export class QueryParameters {
}
}
/**
* Set the query parameters of the page location
* @constructor
* @private
*/
private static Serialize() {
const parts = []
public static GetParts(exclude?: Set<string>) {
const parts: string[] = []
for (const key of QueryParameters.order) {
if (exclude?.has(key)) {
continue
}
if (QueryParameters.knownSources[key]?.data === undefined) {
continue
}
@ -134,6 +132,16 @@ export class QueryParameters {
encodeURIComponent(QueryParameters.knownSources[key].data)
)
}
return parts
}
/**
* Set the query parameters of the page location
* @constructor
* @private
*/
private static Serialize() {
const parts = QueryParameters.GetParts()
if (!Utils.runningFromConsole) {
// Don't pollute the history every time a parameter changes
try {
@ -151,4 +159,8 @@ export class QueryParameters {
QueryParameters._wasInitialized.clear()
QueryParameters.order = []
}
static GetDefaultFor(key: string): string {
return QueryParameters.defaults[key]
}
}

View file

@ -1,9 +1,25 @@
import ThemeViewState from "../../Models/ThemeViewState"
import Hash from "./Hash"
import { MenuState } from "../../Models/MenuState"
export default class ThemeViewStateHashActor {
private readonly _state: ThemeViewState
public static readonly documentation = [
"The URL-hash can contain multiple values:",
"",
"- The id of the currently selected object, e.g. `node/1234`",
"- The currently opened menu view",
"- The base64-encoded JSON-file specifying a custom theme (only when loading)",
"",
"### Possible hashes to open a menu",
"",
"The possible hashes are:",
"",
MenuState._menuviewTabs.map((tab) => "`menu:" + tab + "`").join(","),
MenuState._themeviewTabs.map((tab) => "`theme-menu:" + tab + "`").join(","),
]
/**
* Converts the hash to the appropriate themeview state and, vice versa, sets the hash.
*
@ -100,7 +116,7 @@ export default class ThemeViewStateHashActor {
private loadStateFromHash(hash: string) {
const state = this._state
const parts = hash.split(";")
const parts = hash.split(":")
outer: for (const { toggle, name, showOverOthers, submenu } of state.guistate.allToggles) {
for (const part of parts) {
if (part === name) {

View file

@ -50,11 +50,15 @@ export class MenuState {
)
public highlightedUserSetting: UIEventSource<string> = new UIEventSource<string>(undefined)
constructor(themeid: string = "") {
constructor(shouldOpenWelcomeMessage: boolean, themeid: string = "") {
// Note: this class is _not_ responsible to update the Hash, @see ThemeViewStateHashActor for this
if (themeid) {
themeid += "-"
}
this.themeIsOpened = LocalStorageSource.GetParsed(themeid + "thememenuisopened", true)
this.themeIsOpened = LocalStorageSource.GetParsed(
themeid + "thememenuisopened",
shouldOpenWelcomeMessage
)
this.themeViewTabIndex = LocalStorageSource.GetParsed(themeid + "themeviewtabindex", 0)
this.themeViewTab = this.themeViewTabIndex.sync(
(i) => MenuState._themeviewTabs[i],

View file

@ -23,6 +23,27 @@ export interface TagRenderingConfigJson {
| Translatable
| { special: Record<string, string | Record<string, string>> & { type: string } }
/**
* question: what icon should be shown next to the 'render' value?
* An icon shown next to the rendering; typically shown pretty small
* This is only shown next to the "render" value
* Type: icon
*/
icon?:
| string
| {
/**
* The path to the icon
* Type: icon
*/
path: string
/**
* A hint to mapcomplete on how to render this icon within the mapping.
* This is translated to 'mapping-icon-<classtype>', so defining your own in combination with a custom CSS is possible (but discouraged)
*/
class?: "small" | "medium" | "large" | string
}
/**
*
* question: When should this item be shown?

View file

@ -19,6 +19,8 @@ import { Paragraph } from "../../UI/Base/Paragraph"
import Svg from "../../Svg"
import Validators, { ValidatorType } from "../../UI/InputElement/Validators"
export interface Icon {}
export interface Mapping {
readonly if: UploadableTag
readonly ifnot?: UploadableTag
@ -45,6 +47,8 @@ export interface Mapping {
export default class TagRenderingConfig {
public readonly id: string
public readonly render?: TypedTranslation<object>
public readonly renderIcon?: string
public readonly renderIconClass?: string
public readonly question?: TypedTranslation<object>
public readonly questionhint?: TypedTranslation<object>
public readonly condition?: TagsFilter
@ -58,7 +62,7 @@ export default class TagRenderingConfig {
public readonly freeform?: {
readonly key: string
readonly type: string
readonly type: ValidatorType
readonly placeholder: Translation
readonly addExtraTags: UploadableTag[]
readonly inline: boolean
@ -124,6 +128,13 @@ export default class TagRenderingConfig {
this.questionhint = Translations.T(json.questionHint, translationKey + ".questionHint")
this.description = Translations.T(json.description, translationKey + ".description")
this.condition = TagUtils.Tag(json.condition ?? { and: [] }, `${context}.condition`)
if (typeof json.icon === "string") {
this.renderIcon = json.icon
this.renderIconClass = "small"
} else if (typeof json.icon === "object") {
this.renderIcon = json.icon.path
this.renderIconClass = json.icon.class
}
this.metacondition = TagUtils.Tag(
json.metacondition ?? { and: [] },
`${context}.metacondition`
@ -135,7 +146,17 @@ export default class TagRenderingConfig {
) {
throw `Freeform.addExtraTags should be a list of strings - not a single string (at ${context})`
}
const type = json.freeform.type ?? "string"
if (
json.freeform.type &&
Validators.availableTypes.indexOf(<any>json.freeform.type) < 0
) {
throw `At ${context}: invalid type, perhaps you meant ${Utils.sortedByLevenshteinDistance(
json.freeform.key,
<any>Validators.availableTypes,
(s) => <any>s
)}`
}
const type: ValidatorType = <any>json.freeform.type ?? "string"
let placeholder: Translation = Translations.T(json.freeform.placeholder)
if (placeholder === undefined) {
@ -230,19 +251,21 @@ export default class TagRenderingConfig {
if (txt.indexOf("{" + this.freeform.key + ":") >= 0) {
continue
}
if (txt.indexOf("{canonical(" + this.freeform.key + ")") >= 0) {
continue
}
if (txt.indexOf("{translated(" + this.freeform.key + ")") >= 0) {
continue
}
if (
this.freeform.type === "opening_hours" &&
txt.indexOf("{opening_hours_table(") >= 0
) {
continue
}
const keyFirstArg = ["canonical", "fediverse_link", "translated"]
if (
keyFirstArg.some(
(funcName) => txt.indexOf(`{${funcName}(${this.freeform.key}`) >= 0
)
) {
continue
}
if (
this.freeform.type === "wikidata" &&
txt.indexOf("{wikipedia(" + this.freeform.key) >= 0
@ -528,7 +551,7 @@ export default class TagRenderingConfig {
*/
public GetRenderValueWithImage(
tags: Record<string, string>
): { then: TypedTranslation<any>; icon?: string } | undefined {
): { then: TypedTranslation<any>; icon?: string; iconClass?: string } | undefined {
if (this.condition !== undefined) {
if (!this.condition.matchesProperties(tags)) {
return undefined
@ -547,7 +570,7 @@ export default class TagRenderingConfig {
}
if (this.freeform?.key === undefined || tags[this.freeform.key] !== undefined) {
return { then: this.render }
return { then: this.render, icon: this.renderIcon, iconClass: this.renderIconClass }
}
return undefined
@ -628,7 +651,7 @@ export default class TagRenderingConfig {
*
* @param singleSelectedMapping (Only used if multiAnswer == false): the single mapping to apply. Use (mappings.length) for the freeform
* @param multiSelectedMapping (Only used if multiAnswer == true): all the mappings that must be applied. Set multiSelectedMapping[mappings.length] to use the freeform as well
* @param currentProperties: The current properties of the object for which the question should be answered
* @param currentProperties The current properties of the object for which the question should be answered
*/
public constructChangeSpecification(
freeformValue: string | undefined,
@ -691,38 +714,42 @@ export default class TagRenderingConfig {
return undefined
}
return and
} else {
// Is at least one mapping shown in the answer?
const someMappingIsShown = this.mappings.some((m) => {
if (typeof m.hideInAnswer === "boolean") {
return !m.hideInAnswer
}
const isHidden = m.hideInAnswer.matchesProperties(currentProperties)
return !isHidden
})
// If all mappings are hidden for the current tags, we can safely assume that we should use the freeform key
const useFreeform =
freeformValue !== undefined &&
(singleSelectedMapping === this.mappings.length || !someMappingIsShown)
if (useFreeform) {
return new And([
new Tag(this.freeform.key, freeformValue),
...(this.freeform.addExtraTags ?? []),
])
} else if (singleSelectedMapping !== undefined) {
return new And([
this.mappings[singleSelectedMapping].if,
...(this.mappings[singleSelectedMapping].addExtraTags ?? []),
])
} else {
console.warn("TagRenderingConfig.ConstructSpecification has a weird fallback for", {
freeformValue,
singleSelectedMapping,
multiSelectedMapping,
currentProperties,
})
return undefined
}
// Is at least one mapping shown in the answer?
const someMappingIsShown = this.mappings.some((m) => {
if (typeof m.hideInAnswer === "boolean") {
return !m.hideInAnswer
}
const isHidden = m.hideInAnswer.matchesProperties(currentProperties)
return !isHidden
})
// If all mappings are hidden for the current tags, we can safely assume that we should use the freeform key
const useFreeform =
freeformValue !== undefined &&
(singleSelectedMapping === this.mappings.length ||
!someMappingIsShown ||
singleSelectedMapping === undefined)
if (useFreeform) {
return new And([
new Tag(this.freeform.key, freeformValue),
...(this.freeform.addExtraTags ?? []),
])
} else if (singleSelectedMapping !== undefined) {
return new And([
this.mappings[singleSelectedMapping].if,
...(this.mappings[singleSelectedMapping].addExtraTags ?? []),
])
} else {
console.error("TagRenderingConfig.ConstructSpecification has a weird fallback for", {
freeformValue,
singleSelectedMapping,
multiSelectedMapping,
currentProperties,
useFreeform,
})
return undefined
}
}

View file

@ -110,15 +110,18 @@ export default class ThemeViewState implements SpecialVisualizationState {
constructor(layout: LayoutConfig) {
this.layout = layout
this.guistate = new MenuState(layout.id)
this.featureSwitches = new FeatureSwitchState(layout)
this.guistate = new MenuState(
this.featureSwitches.featureSwitchWelcomeMessage.data,
layout.id
)
this.map = new UIEventSource<MlMap>(undefined)
const initial = new InitialMapPositioning(layout)
this.mapProperties = new MapLibreAdaptor(this.map, initial)
const geolocationState = new GeoLocationState()
this.featureSwitches = new FeatureSwitchState(layout)
this.featureSwitchIsTesting = this.featureSwitches.featureSwitchIsTesting
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchUserbadge
this.featureSwitchUserbadge = this.featureSwitches.featureSwitchEnableLogin
this.osmConnection = new OsmConnection({
dryRun: this.featureSwitches.featureSwitchIsTesting,
@ -465,7 +468,7 @@ export default class ThemeViewState implements SpecialVisualizationState {
new ShowDataLayer(this.map, {
features: new FilteringFeatureSource(last_click_layer, last_click),
doShowLayer: new ImmutableStore(true),
doShowLayer: this.featureSwitches.featureSwitchEnableLogin,
layer: last_click_layer.layerDef,
selectedElement: this.selectedElement,
selectedLayer: this.selectedLayer,

View file

@ -0,0 +1,121 @@
<script lang="ts">/**
* A screen showing:
* - A link to share the current view
* - Some query parameters that can be enabled/disabled
* - The code to embed MC as IFrame
*/
import ThemeViewState from "../../Models/ThemeViewState";
import { QueryParameters } from "../../Logic/Web/QueryParameters";
import Tr from "../Base/Tr.svelte";
import Translations from "../i18n/Translations";
import { Utils } from "../../Utils";
import Svg from "../../Svg";
import ToSvelte from "../Base/ToSvelte.svelte";
import { DocumentDuplicateIcon } from "@rgossiaux/svelte-heroicons/outline";
export let state: ThemeViewState;
const tr = Translations.t.general.sharescreen;
let url = window.location;
let linkToShare: string = undefined;
/**
* In some cases (local deploys, custom themes), we need to set the URL to `/theme.html?layout=xyz` instead of `/xyz?...`
*/
let needsThemeRedirect = url.port !== "" || url.hostname.match(/^[0-9]/) || !state.layout.official;
let layoutId = state.layout.id;
let baseLink = url.protocol + "//" + url.host + "/" + (needsThemeRedirect ? "theme.html?layout=" + layoutId + "&" : layoutId + "?");
let showWelcomeMessage = true;
let enableLogin = true;
$: {
const layout = state.layout;
let excluded = Utils.NoNull([
showWelcomeMessage ? undefined : "fs-welcome-message",
enableLogin ? undefined : "fs-enable-login"
]);
linkToShare = baseLink + QueryParameters.GetParts(new Set(excluded))
.concat(excluded.map(k => k + "=" + false))
.join("&");
if (layout.definitionRaw !== undefined) {
linkToShare += "&userlayout=" + (layout.definedAtUrl ?? layout.id);
}
}
async function shareCurrentLink() {
await navigator.share({
title: Translations.W(state.layout.title)?.ConstructElement().textContent ?? "MapComplete",
text: Translations.W(state.layout.description)?.ConstructElement().textContent ?? "",
url: linkToShare
});
}
let isCopied = false;
async function copyCurrentLink() {
await navigator.clipboard.writeText(linkToShare);
isCopied = true;
await Utils.waitFor(5000);
isCopied = false;
}
</script>
<div>
<Tr t={tr.intro} />
<div class="flex">
{#if typeof navigator?.share === "function"}
<button class="w-8 h-8 p-1 shrink-0" on:click={shareCurrentLink}>
<ToSvelte construct={Svg.share_svg()} />
</button>
{/if}
{#if navigator.clipboard !== undefined}
<button class="w-8 h-8 p-1 shrink-0 no-image-background" on:click={copyCurrentLink}>
<DocumentDuplicateIcon />
</button>
{/if}
<div class="literal-code" on:click={e => Utils.selectTextIn(e.target)}>
{linkToShare}
</div>
</div>
<div class="flex justify-center">
{#if isCopied}
<Tr t={tr.copiedToClipboard} cls="thanks m-2" />
{/if}
</div>
<Tr t={ tr.embedIntro} />
<div class="flex flex-col my-1 link-underline">
<label>
<input bind:checked={showWelcomeMessage} type="checkbox" />
<Tr t={tr.fsWelcomeMessage} />
</label>
<label>
<input bind:checked={enableLogin} type="checkbox" />
<Tr t={tr.fsUserbadge} />
</label>
</div>
<div class="literal-code m-1">
&lt;span class="literal-code iframe-code-block"&gt; <br />
&lt;iframe src="${url}" <br />
allow="geolocation" width="100%" height="100%" style="min-width: 250px; min-height: 250px" <br />
title="${state.layout.title?.txt ?? "MapComplete" } with MapComplete"&gt; <br />
&lt;/iframe&gt; <br />
&lt;/span&gt;
</div>
<Tr t={tr.documentation} cls="link-underline"/>
</div>

View file

@ -1,256 +0,0 @@
import { VariableUiElement } from "../Base/VariableUIElement"
import { Translation } from "../i18n/Translation"
import Svg from "../../Svg"
import Combine from "../Base/Combine"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { Utils } from "../../Utils"
import Translations from "../i18n/Translations"
import BaseUIElement from "../BaseUIElement"
import LayerConfig from "../../Models/ThemeConfig/LayerConfig"
import { InputElement } from "../Input/InputElement"
import { CheckBox } from "../Input/Checkboxes"
import { SubtleButton } from "../Base/SubtleButton"
import LZString from "lz-string"
import { SpecialVisualizationState } from "../SpecialVisualization"
export class ShareScreen extends Combine {
constructor(state: SpecialVisualizationState) {
const layout = state?.layout
const tr = Translations.t.general.sharescreen
const optionCheckboxes: InputElement<boolean>[] = []
const optionParts: Store<string>[] = []
const includeLocation = new CheckBox(tr.fsIncludeCurrentLocation, true)
optionCheckboxes.push(includeLocation)
const currentLocation = state.mapProperties.location
const zoom = state.mapProperties.zoom
optionParts.push(
includeLocation.GetValue().map(
(includeL) => {
if (currentLocation === undefined) {
return null
}
if (includeL) {
return [
["z", zoom.data],
["lat", currentLocation.data?.lat],
["lon", currentLocation.data?.lon],
]
.filter((p) => p[1] !== undefined)
.map((p) => p[0] + "=" + p[1])
.join("&")
} else {
return null
}
},
[currentLocation, zoom]
)
)
function fLayerToParam(flayer: {
isDisplayed: UIEventSource<boolean>
layerDef: LayerConfig
}) {
if (flayer.isDisplayed.data) {
return null // Being displayed is the default
}
return "layer-" + flayer.layerDef.id + "=" + flayer.isDisplayed.data
}
const currentLayer: Store<
{ id: string; name: string | Record<string, string> } | undefined
> = state.mapProperties.rasterLayer.map((l) => l?.properties)
const currentBackground = new VariableUiElement(
currentLayer.map((layer) => {
return tr.fsIncludeCurrentBackgroundMap.Subs({ name: layer?.name ?? "" })
})
)
const includeCurrentBackground = new CheckBox(currentBackground, true)
optionCheckboxes.push(includeCurrentBackground)
optionParts.push(
includeCurrentBackground.GetValue().map(
(includeBG) => {
if (includeBG) {
return "background=" + currentLayer.data?.id
} else {
return null
}
},
[currentLayer]
)
)
const includeLayerChoices = new CheckBox(tr.fsIncludeCurrentLayers, true)
optionCheckboxes.push(includeLayerChoices)
optionParts.push(
includeLayerChoices.GetValue().map(
(includeLayerSelection) => {
if (includeLayerSelection) {
return Utils.NoNull(
Array.from(state.layerState.filteredLayers.values()).map(fLayerToParam)
).join("&")
} else {
return null
}
},
Array.from(state.layerState.filteredLayers.values()).map(
(flayer) => flayer.isDisplayed
)
)
)
const switches = [
{ urlName: "fs-userbadge", human: tr.fsUserbadge },
{ urlName: "fs-search", human: tr.fsSearch },
{ urlName: "fs-welcome-message", human: tr.fsWelcomeMessage },
{ urlName: "fs-layers", human: tr.fsLayers },
{ urlName: "fs-add-new", human: tr.fsAddNew },
{ urlName: "fs-geolocation", human: tr.fsGeolocation },
]
for (const swtch of switches) {
const checkbox = new CheckBox(Translations.W(swtch.human))
optionCheckboxes.push(checkbox)
optionParts.push(
checkbox.GetValue().map((isEn) => {
if (isEn) {
return null
} else {
return `${swtch.urlName}=false`
}
})
)
}
if (layout.definitionRaw !== undefined) {
optionParts.push(new UIEventSource("userlayout=" + (layout.definedAtUrl ?? layout.id)))
}
const options = new Combine(optionCheckboxes).SetClass("flex flex-col")
const url = (currentLocation ?? new UIEventSource(undefined)).map(() => {
const host = window.location.host
let path = window.location.pathname
path = path.substr(0, path.lastIndexOf("/"))
let id = layout.id.toLowerCase()
if (layout.definitionRaw !== undefined) {
id = "theme.html"
}
let literalText = `https://${host}${path}/${id}`
let hash = ""
if (layout.definedAtUrl === undefined && layout.definitionRaw !== undefined) {
hash = "#" + LZString.compressToBase64(Utils.MinifyJSON(layout.definitionRaw))
}
const parts = Utils.NoEmpty(
Utils.NoNull(optionParts.map((eventSource) => eventSource.data))
)
if (parts.length === 0) {
return literalText + hash
}
return literalText + "?" + parts.join("&") + hash
}, optionParts)
const iframeCode = new VariableUiElement(
url.map((url) => {
return `<span class='literal-code iframe-code-block'>
&lt;iframe src="${url}" allow="geolocation" width="100%" height="100%" style="min-width: 250px; min-height: 250px" title="${
layout.title?.txt ?? "MapComplete"
} with MapComplete"&gt;&lt;/iframe&gt
</span>`
})
)
const linkStatus = new UIEventSource<string | Translation>("")
const link = new VariableUiElement(
url.map(
(url) =>
`<input type="text" value=" ${url}" id="code-link--copyable" style="width:90%">`
)
).onClick(async () => {
const shareData = {
title: Translations.W(layout.title)?.ConstructElement().textContent ?? "",
text: Translations.W(layout.description)?.ConstructElement().textContent ?? "",
url: url.data,
}
function rejected() {
const copyText = document.getElementById("code-link--copyable")
// @ts-ignore
copyText.select()
// @ts-ignore
copyText.setSelectionRange(0, 99999) /*For mobile devices*/
document.execCommand("copy")
const copied = tr.copiedToClipboard.Clone()
copied.SetClass("thanks")
linkStatus.setData(copied)
}
try {
navigator
.share(shareData)
.then(() => {
const thx = tr.thanksForSharing.Clone()
thx.SetClass("thanks")
linkStatus.setData(thx)
}, rejected)
.catch(rejected)
} catch (err) {
rejected()
}
})
let downloadThemeConfig: BaseUIElement = undefined
if (layout.definitionRaw !== undefined) {
const downloadThemeConfigAsJson = new SubtleButton(
Svg.download_svg(),
new Combine([tr.downloadCustomTheme, tr.downloadCustomThemeHelp.SetClass("subtle")])
.onClick(() => {
Utils.offerContentsAsDownloadableFile(
layout.definitionRaw,
layout.id + ".mapcomplete-theme-definition.json",
{
mimetype: "application/json",
}
)
})
.SetClass("flex flex-col")
)
let editThemeConfig: BaseUIElement = undefined
if (layout.definedAtUrl === undefined) {
const patchedDefinition = JSON.parse(layout.definitionRaw)
patchedDefinition["language"] = Object.keys(patchedDefinition.title)
editThemeConfig = new SubtleButton(
Svg.pencil_svg(),
"Edit this theme on the custom theme generator",
{
url: `https://pietervdvn.github.io/mc/legacy/070/customGenerator.html#${btoa(
JSON.stringify(patchedDefinition)
)}`,
}
)
}
downloadThemeConfig = new Combine([
downloadThemeConfigAsJson,
editThemeConfig,
]).SetClass("flex flex-col")
}
super([
tr.intro,
link,
new VariableUiElement(linkStatus),
downloadThemeConfig,
tr.addToHomeScreen,
tr.embedIntro,
options,
iframeCode,
])
this.SetClass("flex flex-col link-underline")
}
}

View file

@ -45,9 +45,7 @@
<Tr t={layout.description} />
<Tr t={Translations.t.general.welcomeExplanation.general} />
{#if layout.layers.some((l) => l.presets?.length > 0)}
<If condition={state.featureSwitches.featureSwitchAddNew}>
<Tr t={Translations.t.general.welcomeExplanation.addNew} />
</If>
{/if}
<Tr t={layout.descriptionTail} />

View file

@ -67,7 +67,7 @@
return
}
if (unit && isNaN(Number(v))) {
if (unit !== undefined && isNaN(Number(v))) {
value.setData(undefined)
return
}
@ -75,6 +75,7 @@
feedback?.setData(undefined)
value.setData(v + (selectedUnit.data ?? ""))
}
onDestroy(_value.addCallbackAndRun((_) => setValues()))
onDestroy(value.addCallbackAndRunD(fromUpstream => {

View file

@ -1,6 +1,6 @@
import BaseUIElement from "../BaseUIElement"
import { Translation } from "../i18n/Translation"
import Translations from "../i18n/Translations"
import BaseUIElement from "../BaseUIElement";
import { Translation } from "../i18n/Translation";
import Translations from "../i18n/Translations";
/**
* A 'TextFieldValidator' contains various methods to check and cleanup an entered value or to give feedback.
@ -16,13 +16,13 @@ export abstract class Validator {
/**
* What HTML-inputmode to use
*/
public readonly inputmode?: string
public readonly inputmode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search'
public readonly textArea: boolean
constructor(
name: string,
explanation: string | BaseUIElement,
inputmode?: string,
inputmode?: 'none' | 'text' | 'tel' | 'url' | 'email' | 'numeric' | 'decimal' | 'search',
textArea?: false | boolean
) {
this.name = name

View file

@ -22,6 +22,7 @@ import SimpleTagValidator from "./Validators/SimpleTagValidator"
import ImageUrlValidator from "./Validators/ImageUrlValidator"
import TagKeyValidator from "./Validators/TagKeyValidator"
import TranslationValidator from "./Validators/TranslationValidator"
import FediverseValidator from "./Validators/FediverseValidator"
export type ValidatorType = (typeof Validators.availableTypes)[number]
@ -47,6 +48,7 @@ export default class Validators {
"simple_tag",
"key",
"translation",
"fediverse",
] as const
public static readonly AllValidators: ReadonlyArray<Validator> = [
@ -70,6 +72,7 @@ export default class Validators {
new SimpleTagValidator(),
new TagKeyValidator(),
new TranslationValidator(),
new FediverseValidator(),
]
private static _byType = Validators._byTypeConstructor()

View file

@ -0,0 +1,63 @@
import {Validator} from "../Validator"
import {Translation} from "../../i18n/Translation";
import Translations from "../../i18n/Translations";
export default class FediverseValidator extends Validator {
public static readonly usernameAtServer: RegExp = /^@?(\w+)@((\w|\.)+)$/
constructor() {
super("fediverse", "Validates fediverse addresses and normalizes them into `@username@server`-format");
}
/**
* Returns an `@username@host`
* @param s
*/
reformat(s: string): string {
if(!s.startsWith("@")){
s = "@"+s
}
if (s.match(FediverseValidator.usernameAtServer)) {
return s
}
try {
const url = new URL(s)
const path = url.pathname
if (path.match(/^\/\w+$/)) {
return `@${path.substring(1)}@${url.hostname}`;
}
} catch (e) {
// Nothing to do here
}
return undefined
}
getFeedback(s: string): Translation | undefined {
const match = s.match(FediverseValidator.usernameAtServer)
console.log("Match:", match)
if (match) {
const host = match[2]
try {
const url = new URL("https://" + host)
return undefined
} catch (e) {
return Translations.t.validation.fediverse.invalidHost.Subs({host})
}
}
try {
const url = new URL(s)
const path = url.pathname
if (path.match(/^\/\w+$/)) {
return undefined
}
} catch (e) {
// Nothing to do here
}
return Translations.t.validation.fediverse.feedback
}
isValid(s): boolean {
return this.getFeedback(s) === undefined
}
}

View file

@ -1,11 +1,12 @@
import { Translation } from "../../i18n/Translation"
import Translations from "../../i18n/Translations"
import { Validator } from "../Validator"
import { ValidatorType } from "../Validators";
export default class FloatValidator extends Validator {
inputmode = "decimal"
inputmode: "decimal" = "decimal"
constructor(name?: string, explanation?: string) {
constructor(name?: ValidatorType, explanation?: string) {
super(name ?? "float", explanation ?? "A decimal number", "decimal")
}

View file

@ -1,24 +1,24 @@
<script lang="ts">
import LoginToggle from "../../Base/LoginToggle.svelte"
import type { SpecialVisualizationState } from "../../SpecialVisualization"
import Translations from "../../i18n/Translations"
import Tr from "../../Base/Tr.svelte"
import { InformationCircleIcon, TrashIcon } from "@babeard/svelte-heroicons/mini"
import type { OsmId, OsmTags } from "../../../Models/OsmFeature"
import DeleteConfig from "../../../Models/ThemeConfig/DeleteConfig"
import TagRenderingQuestion from "../TagRendering/TagRenderingQuestion.svelte"
import type { Feature } from "geojson"
import { UIEventSource } from "../../../Logic/UIEventSource"
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig"
import { TagsFilter } from "../../../Logic/Tags/TagsFilter"
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import { TagUtils } from "../../../Logic/Tags/TagUtils"
import OsmChangeAction from "../../../Logic/Osm/Actions/OsmChangeAction"
import DeleteAction from "../../../Logic/Osm/Actions/DeleteAction"
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction"
import Loading from "../../Base/Loading.svelte"
import { DeleteFlowState } from "./DeleteFlowState"
import { twJoin } from "tailwind-merge"
import LoginToggle from "../../Base/LoginToggle.svelte";
import type { SpecialVisualizationState } from "../../SpecialVisualization";
import Translations from "../../i18n/Translations";
import Tr from "../../Base/Tr.svelte";
import { TrashIcon } from "@babeard/svelte-heroicons/mini";
import type { OsmId, OsmTags } from "../../../Models/OsmFeature";
import DeleteConfig from "../../../Models/ThemeConfig/DeleteConfig";
import TagRenderingQuestion from "../TagRendering/TagRenderingQuestion.svelte";
import type { Feature } from "geojson";
import { UIEventSource } from "../../../Logic/UIEventSource";
import LayerConfig from "../../../Models/ThemeConfig/LayerConfig";
import { TagsFilter } from "../../../Logic/Tags/TagsFilter";
import { XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import { TagUtils } from "../../../Logic/Tags/TagUtils";
import OsmChangeAction from "../../../Logic/Osm/Actions/OsmChangeAction";
import DeleteAction from "../../../Logic/Osm/Actions/DeleteAction";
import ChangeTagAction from "../../../Logic/Osm/Actions/ChangeTagAction";
import Loading from "../../Base/Loading.svelte";
import { DeleteFlowState } from "./DeleteFlowState";
import { twJoin } from "tailwind-merge";
export let state: SpecialVisualizationState
export let deleteConfig: DeleteConfig
@ -83,10 +83,9 @@
</script>
{#if $canBeDeleted === false && !hasSoftDeletion}
<div class="low-interaction flex">
<InformationCircleIcon class="h-6 w-6" />
<div class="low-interaction flex flex-col">
<Tr t={$canBeDeletedReason} />
<Tr class="subtle" t={t.useSomethingElse} />
<Tr cls="subtle" t={t.useSomethingElse} />
</div>
{:else}
<LoginToggle ignoreLoading={true} {state}>

View file

@ -38,11 +38,12 @@
let selectedMapping: number = undefined
let checkedMappings: boolean[]
$: {
let tgs = $tags
mappings = config.mappings?.filter((m) => {
if (typeof m.hideInAnswer === "boolean") {
return !m.hideInAnswer
}
return m.hideInAnswer.matchesProperties(tags.data)
return !m.hideInAnswer.matchesProperties(tgs)
})
// We received a new config -> reinit
unit = layer?.units?.find((unit) => unit.appliesToKeys.has(config.freeform?.key))
@ -59,7 +60,7 @@
if (config.freeform?.key) {
if (!config.multiAnswer) {
// Somehow, setting multianswer freeform values is broken if this is not set
freeformInput.setData(tags.data[config.freeform.key])
freeformInput.setData(tgs[config.freeform.key])
}
} else {
freeformInput.setData(undefined)
@ -69,7 +70,7 @@
export let selectedTags: TagsFilter = undefined
let mappings: Mapping[] = config?.mappings
let searchTerm: Store<string> = new UIEventSource("")
let searchTerm: UIEventSource<string> = new UIEventSource("")
$: {
try {

View file

@ -6,6 +6,7 @@ import Translations from "./i18n/Translations"
import { QueryParameters } from "../Logic/Web/QueryParameters"
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"
import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor"
export default class QueryParameterDocumentation {
private static QueryParamDocsIntro = [
@ -60,6 +61,7 @@ export default class QueryParameterDocumentation {
public static GenerateQueryParameterDocs(): BaseUIElement {
const docs: (string | BaseUIElement)[] = [
...QueryParameterDocumentation.QueryParamDocsIntro,
...ThemeViewStateHashActor.documentation,
]
this.UrlParamDocs().forEach((value, key) => {
const c = new Combine([

View file

@ -82,6 +82,7 @@ import ConflateImportButtonViz from "./Popup/ImportButtons/ConflateImportButtonV
import DeleteWizard from "./Popup/DeleteFlow/DeleteWizard.svelte"
import { OpenJosm } from "./BigComponents/OpenJosm"
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
import FediverseValidator from "./InputElement/Validators/FediverseValidator"
class NearbyImageVis implements SpecialVisualization {
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
@ -1374,6 +1375,43 @@ export default class SpecialVisualizations {
)
},
},
{
funcName: "fediverse_link",
docs: "Converts a fediverse username or link into a clickable link",
args: [
{
name: "key",
doc: "The attribute-name containing the link",
required: true,
},
],
constr(
state: SpecialVisualizationState,
tagSource: UIEventSource<Record<string, string>>,
argument: string[],
feature: Feature,
layer: LayerConfig
): BaseUIElement {
const key = argument[0]
const validator = new FediverseValidator()
return new VariableUiElement(
tagSource
.map((tags) => tags[key])
.map((fediAccount) => {
fediAccount = validator.reformat(fediAccount)
const [_, username, host] = fediAccount.match(
FediverseValidator.usernameAtServer
)
return new Link(
fediAccount,
"https://" + host + "/@" + username,
true
)
})
)
},
},
]
specialVisualizations.push(new AutoApplyButton(specialVisualizations))

View file

@ -1,57 +1,57 @@
<script lang="ts">
import { Store, UIEventSource } from "../Logic/UIEventSource"
import { Map as MlMap } from "maplibre-gl"
import MaplibreMap from "./Map/MaplibreMap.svelte"
import FeatureSwitchState from "../Logic/State/FeatureSwitchState"
import MapControlButton from "./Base/MapControlButton.svelte"
import ToSvelte from "./Base/ToSvelte.svelte"
import If from "./Base/If.svelte"
import { GeolocationControl } from "./BigComponents/GeolocationControl"
import type { Feature } from "geojson"
import SelectedElementView from "./BigComponents/SelectedElementView.svelte"
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
import Filterview from "./BigComponents/Filterview.svelte"
import ThemeViewState from "../Models/ThemeViewState"
import type { MapProperties } from "../Models/MapProperties"
import Geosearch from "./BigComponents/Geosearch.svelte"
import Translations from "./i18n/Translations"
import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid"
import { Store, UIEventSource } from "../Logic/UIEventSource";
import { Map as MlMap } from "maplibre-gl";
import MaplibreMap from "./Map/MaplibreMap.svelte";
import FeatureSwitchState from "../Logic/State/FeatureSwitchState";
import MapControlButton from "./Base/MapControlButton.svelte";
import ToSvelte from "./Base/ToSvelte.svelte";
import If from "./Base/If.svelte";
import { GeolocationControl } from "./BigComponents/GeolocationControl";
import type { Feature } from "geojson";
import SelectedElementView from "./BigComponents/SelectedElementView.svelte";
import LayerConfig from "../Models/ThemeConfig/LayerConfig";
import Filterview from "./BigComponents/Filterview.svelte";
import ThemeViewState from "../Models/ThemeViewState";
import type { MapProperties } from "../Models/MapProperties";
import Geosearch from "./BigComponents/Geosearch.svelte";
import Translations from "./i18n/Translations";
import { CogIcon, EyeIcon, MenuIcon, XCircleIcon } from "@rgossiaux/svelte-heroicons/solid";
import Tr from "./Base/Tr.svelte"
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
import FloatOver from "./Base/FloatOver.svelte"
import PrivacyPolicy from "./BigComponents/PrivacyPolicy"
import Constants from "../Models/Constants"
import TabbedGroup from "./Base/TabbedGroup.svelte"
import UserRelatedState from "../Logic/State/UserRelatedState"
import LoginToggle from "./Base/LoginToggle.svelte"
import LoginButton from "./Base/LoginButton.svelte"
import CopyrightPanel from "./BigComponents/CopyrightPanel"
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"
import ModalRight from "./Base/ModalRight.svelte"
import { Utils } from "../Utils"
import Hotkeys from "./Base/Hotkeys"
import { VariableUiElement } from "./Base/VariableUIElement"
import SvelteUIElement from "./Base/SvelteUIElement"
import OverlayToggle from "./BigComponents/OverlayToggle.svelte"
import LevelSelector from "./BigComponents/LevelSelector.svelte"
import ExtraLinkButton from "./BigComponents/ExtraLinkButton"
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte"
import Svg from "../Svg"
import { ShareScreen } from "./BigComponents/ShareScreen"
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"
import type { RasterLayerPolygon } from "../Models/RasterLayers"
import { AvailableRasterLayers } from "../Models/RasterLayers"
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"
import IfHidden from "./Base/IfHidden.svelte"
import { onDestroy } from "svelte"
import { OpenJosm } from "./BigComponents/OpenJosm"
import MapillaryLink from "./BigComponents/MapillaryLink.svelte"
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte"
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"
import StateIndicator from "./BigComponents/StateIndicator.svelte"
import LanguagePicker from "./LanguagePicker"
import Locale from "./i18n/Locale"
import Tr from "./Base/Tr.svelte";
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte";
import FloatOver from "./Base/FloatOver.svelte";
import PrivacyPolicy from "./BigComponents/PrivacyPolicy";
import Constants from "../Models/Constants";
import TabbedGroup from "./Base/TabbedGroup.svelte";
import UserRelatedState from "../Logic/State/UserRelatedState";
import LoginToggle from "./Base/LoginToggle.svelte";
import LoginButton from "./Base/LoginButton.svelte";
import CopyrightPanel from "./BigComponents/CopyrightPanel";
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte";
import ModalRight from "./Base/ModalRight.svelte";
import { Utils } from "../Utils";
import Hotkeys from "./Base/Hotkeys";
import { VariableUiElement } from "./Base/VariableUIElement";
import SvelteUIElement from "./Base/SvelteUIElement";
import OverlayToggle from "./BigComponents/OverlayToggle.svelte";
import LevelSelector from "./BigComponents/LevelSelector.svelte";
import ExtraLinkButton from "./BigComponents/ExtraLinkButton";
import SelectedElementTitle from "./BigComponents/SelectedElementTitle.svelte";
import Svg from "../Svg";
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte";
import type { RasterLayerPolygon } from "../Models/RasterLayers";
import { AvailableRasterLayers } from "../Models/RasterLayers";
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte";
import IfHidden from "./Base/IfHidden.svelte";
import { onDestroy } from "svelte";
import { OpenJosm } from "./BigComponents/OpenJosm";
import MapillaryLink from "./BigComponents/MapillaryLink.svelte";
import OpenIdEditor from "./BigComponents/OpenIdEditor.svelte";
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte";
import StateIndicator from "./BigComponents/StateIndicator.svelte";
import LanguagePicker from "./LanguagePicker";
import Locale from "./i18n/Locale";
import ShareScreen from "./BigComponents/ShareScreen.svelte";
export let state: ThemeViewState
let layout = state.layout
@ -314,11 +314,12 @@
<ToSvelte construct={() => new CopyrightPanel(state)} slot="content3" />
<div slot="title4">
<div slot="title4" class="flex">
<ToSvelte construct={Svg.share_svg().SetClass("w-4 h-4")} />
<Tr t={Translations.t.general.sharescreen.title} />
</div>
<div class="m-2" slot="content4">
<ToSvelte construct={() => new ShareScreen(state)} />
<ShareScreen {state}/>
</div>
</TabbedGroup>
</FloatOver>

View file

@ -221,6 +221,9 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
* Utils.Round7(12.123456789) // => 12.1234568
*/
public static Round7(i: number): number {
if (i == undefined) {
return undefined
}
return Math.round(i * 10000000) / 10000000
}
@ -1211,6 +1214,22 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
return new Date(str)
}
public static selectTextIn(node) {
if (document.body["createTextRange"]) {
const range = document.body["createTextRange"]()
range.moveToElementText(node)
range.select()
} else if (window.getSelection) {
const selection = window.getSelection()
const range = document.createRange()
range.selectNodeContents(node)
selection.removeAllRanges()
selection.addRange(range)
} else {
console.warn("Could not select text in node: Unsupported browser.")
}
}
public static sortedByLevenshteinDistance<T>(
reference: string,
ts: T[],