chore: automated housekeeping...

This commit is contained in:
Pieter Vander Vennet 2025-10-11 14:03:42 +02:00
parent d3b6c090f3
commit 332f960f86
57 changed files with 2884 additions and 1972 deletions

View file

@ -1092,9 +1092,9 @@
"id": "bench-view",
"render": {
"special": {
"type": "group",
"header": "bench-view-title",
"labels": "bench-view-content"
"labels": "bench-view-content",
"type": "group"
}
}
},
@ -1116,12 +1116,13 @@
],
"render": {
"special": {
"type": "image_carousel",
"image_key": "image:view;panoramax:view;mapillary:view"
"image_key": "image:view;panoramax:view;mapillary:view",
"type": "image_carousel"
}
}
},
{ "id": "bench-view-upload",
{
"id": "bench-view-upload",
"labels": [
"hidden",
"bench-view-content"

View file

@ -430,10 +430,10 @@
}
},
{
"condition": "_favourite=yes",
"id": "favourite_icon",
"description": "Only for rendering",
"icon": "circle:white;heart:red",
"id": "favourite_icon",
"condition": "_favourite=yes",
"metacondition": "__showTimeSensitiveIcons!=no"
},
{

View file

@ -217,8 +217,8 @@
},
{
"id": "debug",
"metacondition": "__featureSwitchIsDebugging=true",
"render": "{all_tags()}"
"render": "{all_tags()}",
"metacondition": "__featureSwitchIsDebugging=true"
}
],
"filter": [

View file

@ -114,9 +114,9 @@
"lineRendering": [],
"tagRenderings": [
{
"classes": "p-0",
"id": "conversation",
"render": "{visualize_note_comments()}"
"render": "{visualize_note_comments()}",
"classes": "p-0"
},
{
"id": "add_image",

View file

@ -66,16 +66,16 @@
],
"tagRenderings": [
{
"condition": "level=country",
"description": "The name of the country",
"id": "country_name",
"render": "{nameEn} {emojiFlag}"
"description": "The name of the country",
"render": "{nameEn} {emojiFlag}",
"condition": "level=country"
},
{
"condition": "_community_links~*",
"description": "Community Links (Discord, meetups, Slack groups, IRC channels, mailing lists etc...)",
"id": "community_links",
"render": "{_community_links}"
"description": "Community Links (Discord, meetups, Slack groups, IRC channels, mailing lists etc...)",
"render": "{_community_links}",
"condition": "_community_links~*"
}
],
"filter": [

View file

@ -3234,7 +3234,7 @@
"freeform": {
"key": "maxstay",
"type": "pfloat",
"unit": {
"unit": {
"quantity": "duration",
"denominations": [
"minutes",

View file

@ -93,7 +93,6 @@
"geoJsonZoomLevel": 12,
"#": "The 'staging'-url is a test maproulette",
"#geoJson": "https://staging.maproulette.org/api/v2/challenge/view/70?bbox={x_min},{y_max},{x_max},{y_min}",
"geoJson": "https://maproulette.org/api/v2/challenge/view/53358?bbox={x_min},{y_max},{x_max},{y_min}"
},
"calculatedTags": [
@ -140,9 +139,9 @@
"condition": "_osm_poi_with_this_ref!=[]",
"render": {
"special": {
"type": "multi",
"key": "_osm_poi_with_this_ref",
"tagrendering": "The OSM-bench <a href='#{id}'>{id}</a> is linked to this OpenBenches-datapoint"
"tagrendering": "The OSM-bench <a href='#{id}'>{id}</a> is linked to this OpenBenches-datapoint",
"type": "multi"
}
}
},

View file

@ -38,9 +38,6 @@
"zh_Hant": "顯示由MapComplete進行的變動"
},
"icon": "./assets/svg/logo.svg",
"startZoom": 1,
"startLat": 0,
"startLon": 0,
"hideFromOverview": true,
"layers": [
{
@ -429,6 +426,10 @@
"if": "theme=facadegardens",
"then": "./assets/themes/facadegardens/geveltuin.svg"
},
{
"if": "theme=farming_equipment_cooperatives",
"then": "./assets/layers/farming_equipment_cooperative/tractor.svg"
},
{
"if": "theme=fireplace",
"then": "./assets/layers/assembly_point/fire.svg"

View file

@ -1339,6 +1339,16 @@
},
"question": "When was this bench last surveyed?",
"render": "This bench was last surveyed on {survey:date}"
},
"bench-view-title": {
"render": "View from the bench"
},
"bench-view-upload": {
"render": {
"special": {
"label": "Add a picture of the view when sitting on the bench"
}
}
}
},
"title": {
@ -3466,6 +3476,37 @@
},
"climbing_gym": {
"description": "A climbing gym",
"filter": {
"0": {
"options": {
"0": {
"question": "climbing style"
},
"1": {
"question": "Bouldering"
},
"2": {
"question": "Top rope climbing"
},
"3": {
"question": "Sport climbing"
}
}
},
"1": {
"options": {
"0": {
"question": "auto belays"
},
"1": {
"question": "auto belays for top roping"
},
"2": {
"question": "auto belays for lead climbing"
}
}
}
},
"name": "Climbing gyms",
"presets": {
"0": {
@ -5688,6 +5729,315 @@
"render": "Extinguishers"
}
},
"farming_equipment_cooperative": {
"description": "This layer is to map cooperative for the use of agricultural equipment",
"name": "Cooperatives for the use of agricultural equipment",
"presets": {
"0": {
"title": "a farming equipment cooperative"
}
},
"tagRenderings": {
"cuma_building": {
"mappings": {
"0": {
"then": "This cooperative owns an office building"
},
"1": {
"then": "This cooperative owns a building"
},
"2": {
"then": "This cooperative does not own a building"
}
},
"question": "Has this cooperative a building?"
},
"cuma_equipment": {
"mappings": {
"0": {
"then": "Plastic basins for various uses, e.g. as containers while sorting produce, holding fluids, ..."
},
"1": {
"then": "Cooking kettle"
},
"10": {
"then": "Motor driven equipment"
},
"11": {
"then": "Peelers"
},
"12": {
"then": "Plough"
},
"13": {
"then": "Presses"
},
"14": {
"then": "Mobile pumps"
},
"15": {
"then": "Rice parboiling kit"
},
"16": {
"then": "Roller"
},
"17": {
"then": "Seed drill"
},
"18": {
"then": "Sewing machine "
},
"19": {
"then": "Shea churn"
},
"2": {
"then": "Cultivator (or rotavator)"
},
"20": {
"then": "Shea complex"
},
"21": {
"then": "Small, manually powered equipment"
},
"22": {
"then": "Mobile solar panel(s) (which is moved around as necessary)"
},
"23": {
"then": "Mower"
},
"24": {
"then": "Soybean processing unit"
},
"25": {
"then": "Manure spreader"
},
"26": {
"then": "Storage store"
},
"27": {
"then": "Tarpaulin"
},
"28": {
"then": "Threshing machine"
},
"29": {
"then": "Tiller"
},
"3": {
"then": "Dethatcher"
},
"30": {
"then": "Various pieces of tilling equipment"
},
"31": {
"then": "Tractor"
},
"32": {
"then": "Trailer"
},
"33": {
"then": "Tricycle"
},
"34": {
"then": "Manages a shared, unmovable water tank - possibly at a different location"
},
"35": {
"then": "Manages a shared water well - possibly at a different location"
},
"4": {
"then": "Electric rice incubator"
},
"5": {
"then": "A horse drawn cart"
},
"6": {
"then": "Huller machine"
},
"7": {
"then": "Millet mill (to grind flour)"
},
"8": {
"then": "Millet huller"
},
"9": {
"then": "Moisture meter"
}
},
"multiTitle": "This cooperative manages the following equipment:",
"question": "What equipment does this cooperative manage that a member of the cooperative can loan to use at their farm?",
"render": "{equipment}"
},
"cuma_type": {
"mappings": {
"0": {
"then": "This cooperation has equipment for <b>processing</b>"
},
"1": {
"then": "This cooperation has equipment for <b>horticulture</b>"
},
"2": {
"then": "Thils cooperation has equipment for <b>tillage</b>"
},
"3": {
"then": "This cooperation has equipment for <b>breeding</b>"
}
},
"question": "What category of agricultural cooperative is this?",
"render": "has equipment for {cooperative:agricultural}"
},
"produces": {
"mappings": {
"0": {
"then": "Amaranth"
},
"1": {
"then": "Arachide"
},
"10": {
"then": "Corn"
},
"11": {
"then": "Cotton"
},
"12": {
"then": "Cowpea"
},
"13": {
"then": "Crincrin"
},
"14": {
"then": "Cucumber"
},
"15": {
"then": "Eggplant"
},
"16": {
"then": "Fonio"
},
"17": {
"then": "Fruits"
},
"18": {
"then": "Gboma"
},
"19": {
"then": "Green bean"
},
"2": {
"then": "Banana"
},
"20": {
"then": "Hibiscus"
},
"21": {
"then": "Lemon"
},
"22": {
"then": "Lemongrass"
},
"23": {
"then": "Lettuce"
},
"24": {
"then": "Mango"
},
"25": {
"then": "Nightshade"
},
"26": {
"then": "Okra"
},
"27": {
"then": "Onion"
},
"28": {
"then": "Orange"
},
"29": {
"then": "Papaya"
},
"3": {
"then": "Beans"
},
"30": {
"then": "Palm nut"
},
"31": {
"then": "Palm oil"
},
"32": {
"then": "Parsley"
},
"33": {
"then": "Peanut"
},
"34": {
"then": "Peas"
},
"35": {
"then": "Pawpaw"
},
"36": {
"then": "Rice"
},
"37": {
"then": "Sesame"
},
"38": {
"then": "Shea"
},
"39": {
"then": "Sorghum"
},
"4": {
"then": "Bell peppers"
},
"40": {
"then": "Soya"
},
"41": {
"then": "Tomatoes"
},
"42": {
"then": "Vegetables"
},
"43": {
"then": "Watermelon"
},
"44": {
"then": "Yam"
},
"5": {
"then": "Cabbage"
},
"6": {
"then": "Carrots"
},
"7": {
"then": "Cassava"
},
"8": {
"then": "Cashew nut"
},
"9": {
"then": "Chili"
}
},
"multiTitle": "This cooperative has equipment for crops as:",
"question": "What kinds of crops does this cooperative provide equipment for?"
},
"short_name": {
"render": "The short name is {short_name}"
}
},
"title": {
"mappings": {
"0": {
"then": "Cooperative for the use of agricultural equipment (CUMA)"
}
}
}
},
"filters": {
"filter": {
"0": {
@ -7683,6 +8033,13 @@
}
}
},
"mark_not_too_hard": {
"render": {
"special": {
"message": "This challenge was marked as too hard. Try anyway?"
}
}
},
"mark_too_hard": {
"render": {
"special": {
@ -8573,6 +8930,29 @@
}
},
"tagRenderings": {
"access": {
"mappings": {
"0": {
"then": "This parking can be used by anyone"
},
"1": {
"then": "This parking can't be used by anyone"
},
"2": {
"then": "This parking is private"
},
"3": {
"then": "This parking can be used by anyone, but the owner can revoke access at any time"
},
"4": {
"then": "This parking can only be used by customers"
},
"5": {
"then": "This parking can only be used by authorized persons"
}
},
"question": "Who is allowed to use this parking?"
},
"capacity": {
"freeform": {
"placeholder": "Amount of parking spots"
@ -8598,6 +8978,18 @@
"question": "How many disabled parking spots are there at this parking?",
"render": "There are {capacity:disabled} disabled parking spots"
},
"fee": {
"mappings": {
"0": {
"then": "The parking is free of charge"
},
"1": {
"then": "There is a fee"
}
},
"question": "Is there a fee?",
"render": "A fee of {charge} should be paid for parking here"
},
"parking-type": {
"mappings": {
"0": {

View file

@ -3734,6 +3734,271 @@
"render": "Exctincteurs"
}
},
"farming_equipment_cooperative": {
"description": "Cette couche est destinée à cartographier des coopératives d'utilisation de matériel agricole.",
"name": "Coopératives d'utilisation de matériel agricole (CUMA)",
"tagRenderings": {
"cuma_equipment": {
"mappings": {
"0": {
"then": "Bassines manuelles"
},
"1": {
"then": "Chaudière de cuisson"
},
"10": {
"then": "équipement motorisé"
},
"11": {
"then": "éplucheuses"
},
"12": {
"then": "Charrue"
},
"13": {
"then": "Presses"
},
"14": {
"then": "Pompes"
},
"15": {
"then": "Précuisson du riz"
},
"16": {
"then": "Rouleau"
},
"18": {
"then": "Machine à coudre"
},
"19": {
"then": "Baratte de karité"
},
"2": {
"then": "Cultivateur"
},
"20": {
"then": "Complexe karité"
},
"21": {
"then": "manuel petit format"
},
"22": {
"then": "Panneau solaire"
},
"24": {
"then": "Unité de transformation de soja"
},
"25": {
"then": "épandage"
},
"26": {
"then": "Entrepôt de stockage"
},
"27": {
"then": "Bâche"
},
"28": {
"then": "Machine à battre"
},
"29": {
"then": "Motoculteur"
},
"3": {
"then": "Scarificateur"
},
"30": {
"then": "Autres outils de labour"
},
"31": {
"then": "Tracteur"
},
"32": {
"then": "Remorque"
},
"33": {
"then": "Tricycle"
},
"34": {
"then": "Réservoir d'eau"
},
"35": {
"then": "puits"
},
"4": {
"then": "Incubateur électrique"
},
"6": {
"then": "Décortiqueuse"
},
"9": {
"then": "Humidimètre"
}
},
"question": "Équipements de la CUMA"
},
"cuma_type": {
"mappings": {
"0": {
"then": "<b>Type de CUMA : transformation</b>"
},
"1": {
"then": "<b>Type de CUMA : maraîchage</b>"
},
"2": {
"then": "<b>Type de CUMA : travail du sol</b>"
},
"3": {
"then": "<b> Type de CUMA : élevage</b> "
}
},
"question": "Type de CUMA",
"render": "Type de CUMA : {cooperative:agricultural}"
},
"produces": {
"mappings": {
"0": {
"then": "Amarante"
},
"1": {
"then": "Arachide"
},
"10": {
"then": "Maïs"
},
"11": {
"then": "Coton"
},
"12": {
"then": "Niébé"
},
"13": {
"then": "Crincrin"
},
"14": {
"then": "Concombre"
},
"15": {
"then": "Aubergine"
},
"16": {
"then": "Fonio"
},
"17": {
"then": "Fruits"
},
"18": {
"then": "Gboma"
},
"19": {
"then": "Haricot vert"
},
"2": {
"then": "Banane"
},
"20": {
"then": "Hibiscus"
},
"21": {
"then": "Citron"
},
"22": {
"then": "Citronnelle"
},
"23": {
"then": "Laitue"
},
"24": {
"then": "Mangue"
},
"25": {
"then": "Morelle"
},
"26": {
"then": "Gombo"
},
"27": {
"then": "Oignon"
},
"28": {
"then": "Orange"
},
"29": {
"then": "Papaye"
},
"3": {
"then": "Haricots"
},
"30": {
"then": "Noix de palme"
},
"31": {
"then": "Huile de palme"
},
"32": {
"then": "Persil"
},
"33": {
"then": "Arachide"
},
"34": {
"then": "Pois"
},
"35": {
"then": "Papaye"
},
"36": {
"then": "Riz"
},
"37": {
"then": "Sésame"
},
"38": {
"then": "Karité"
},
"39": {
"then": "Sorgho"
},
"4": {
"then": "Poivron"
},
"40": {
"then": "Soja"
},
"41": {
"then": "Tomates"
},
"42": {
"then": "Légumes"
},
"43": {
"then": "Pastèque"
},
"44": {
"then": "Igname"
},
"5": {
"then": "Chou"
},
"6": {
"then": "Carottes"
},
"7": {
"then": "Manioc"
},
"8": {
"then": "Noix de cajou"
},
"9": {
"then": "Piment"
}
},
"question": "Produits de la CUMA"
},
"short_name": {
"render": "Le nom abrégé est {short_name}"
}
}
},
"filters": {
"filter": {
"0": {

View file

@ -1281,6 +1281,16 @@
},
"question": "Wanneer is deze laatste bank laatst gesurveyed?",
"render": "Deze bank is laatst gesurveyd op {survey:date}"
},
"bench-view-title": {
"render": "Zicht vanop de bank"
},
"bench-view-upload": {
"render": {
"special": {
"label": "Voeg een afbeelding van het zicht vanaf de bank toe"
}
}
}
},
"title": {
@ -5373,6 +5383,23 @@
"render": "Brandblussers"
}
},
"farming_equipment_cooperative": {
"tagRenderings": {
"cuma_equipment": {
"mappings": {
"28": {
"then": "Dorsmachine"
},
"3": {
"then": "Verticuleermachine"
}
}
},
"produces": {
"question": "Voor wat soort oogstproducten heeft deze cooperatie materiaal?"
}
}
},
"filters": {
"filter": {
"0": {

View file

@ -101,6 +101,40 @@
},
"benches": {
"description": "This map shows all benches that are recorded in OpenStreetMap: Individual benches, and benches belonging to public transport stops or shelters.",
"layers": {
"3": {
"override": {
"=tagRenderings": {
"0": {
"render": "This is a bench that is known in Openbenches.org but might not exist in OpenStreetMap. If this bench still exists in the real world, you can add or link this information to OpenStreetMap with the tools below"
},
"2": {
"render": {
"special": {
"text": "See {_idN} on openbenches.org"
}
}
},
"3": {
"render": "OpenStreetMap knows about <a href='#{_closest_osm_poi}'>a bench which is {_closest_osm_poi_distance} meter away.</a> "
},
"5": {
"render": {
"before": "Choose below which bench you want to link."
}
},
"6": {
"render": {
"special": {
"text": "Create a bench in OSM with the properties of openBenches.org"
}
}
}
},
"name=": "Data from OpenBenches.org"
}
}
},
"shortDescription": "A map of benches",
"title": "Benches"
},

View file

@ -93,6 +93,26 @@
},
"benches": {
"description": "Deze kaart toont alle zitbanken die zijn opgenomen in OpenStreetMap: individuele banken en banken bij bushaltes.",
"layers": {
"3": {
"override": {
"=tagRenderings": {
"5": {
"render": {
"before": "Kies hieronder welke bank je wilt linken."
}
},
"6": {
"render": {
"special": {
"text": "Maak een bank aan in OSM met de attributen van openBenches.org"
}
}
}
}
}
}
},
"shortDescription": "Een kaart van zitbanken",
"title": "Zitbanken"
},

View file

@ -285,13 +285,13 @@ export default class ScriptUtils {
*/
static detectVariablePaths(path: string) {
const splitPath = path.match(/(.*)\{(.+)\}(.*)/)
if(!splitPath){
return [path]
if (!splitPath) {
return [path]
}
const [_, head] = splitPath
const headPath = head.slice(0, head.lastIndexOf("/"))
const allPossibleMatchingFiles = ScriptUtils.readDirRecSync(headPath)
const pathAsRegex = path.replaceAll(/\{.+}/g, ".+")
return allPossibleMatchingFiles.filter(pth => pth.match(pathAsRegex))
return allPossibleMatchingFiles.filter((pth) => pth.match(pathAsRegex))
}
}

View file

@ -46,8 +46,7 @@ class ToSlideshowJson {
}
public convert() {
const lines = readFileSync(this._source, "utf8")
.split("\n")
const lines = readFileSync(this._source, "utf8").split("\n")
const sections: string[][] = []
let currentSection: string[] = []
@ -159,10 +158,10 @@ export class GenerateDocs extends Script {
this.generateEliDocs()
this.generateBuiltinUnits()
this.writeMarkdownFile("./Docs/Studio/SpecialInputElements.md", Validators.HelpText(), [
"src/UI/InputElement/Validators.ts"
"src/UI/InputElement/Validators.ts",
])
this.writeMarkdownFile("./Docs/Studio/Tags_format.md", TagUtils.generateDocs(), [
"src/Logic/Tags/TagUtils.ts"
"src/Logic/Tags/TagUtils.ts",
])
this.writeMarkdownFile(
@ -170,7 +169,7 @@ export class GenerateDocs extends Script {
SpecialVisualizations.HelpMessage(),
["src/UI/SpecialVisualizations.ts"],
{
tocMaxDepth: 3
tocMaxDepth: 3,
}
)
this.writeMarkdownFile(
@ -181,7 +180,6 @@ export class GenerateDocs extends Script {
)
}
// For dev
{
this.generateQueryParameterDocs()
@ -208,13 +206,12 @@ export class GenerateDocs extends Script {
ScriptUtils.erasableLog("Written docs for theme", theme.id)
})
this.generateOverviewsForAllSingleLayer("./Docs/nl/Layers", "nl")
}
this.writeMarkdownFile("./Docs/ChangesetMeta.md", Changes.getDocs(), [
"src/Logic/Osm/Changes.ts",
"src/Logic/Osm/ChangesetHandler.ts"
"src/Logic/Osm/ChangesetHandler.ts",
])
new WikiPageGenerator().generate()
@ -224,9 +221,11 @@ export class GenerateDocs extends Script {
this.generateSidebar("nl")
this.generatedPaths.push(".gitignore")
writeFileSync("./Docs/.gitignore", this.generatedPaths
.map(p => p.replace("./Docs/", ""))
.join("\n"), "utf-8")
writeFileSync(
"./Docs/.gitignore",
this.generatedPaths.map((p) => p.replace("./Docs/", "")).join("\n"),
"utf-8"
)
console.log("Generated docs")
}
@ -238,7 +237,7 @@ export class GenerateDocs extends Script {
options?: {
noTableOfContents?: boolean
tocMaxDepth?: number
lang?: string,
lang?: string
noWarn?: boolean
}
): void {
@ -269,25 +268,31 @@ export class GenerateDocs extends Script {
md += "\n"
}
const warnAutomated = options?.noWarn ? "" :
"[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources)\n\n"
const warnAutomated = options?.noWarn
? ""
: "[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources)\n\n"
const sources = autogenSource
.map(
(s) =>
`[${s}](https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/${s})`
)
.join(", ")
const sources = autogenSource.map(
(s) => `[${s}](https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/${s})`).join(", ")
const generatedFrom = new TypedTranslation<{ sources; date }>({
en: "This document is autogenerated from {sources} on {date}",
nl: "Dit document werd gegenereerd op basis van {sources} op {date}",
})
.Subs({ sources, date: new Date().toDateString() })
.textFor(lang)
const generatedFrom =
new TypedTranslation<{ sources, date }>({
en: "This document is autogenerated from {sources} on {date}",
nl: "Dit document werd gegenereerd op basis van {sources} op {date}"
}).Subs({ sources, date: new Date().toDateString() }).textFor(lang)
writeFileSync(filename, warnAutomated + md + (options?.noWarn ? "" : "\n\n" + generatedFrom + "\n"))
writeFileSync(
filename,
warnAutomated + md + (options?.noWarn ? "" : "\n\n" + generatedFrom + "\n")
)
this.generatedPaths.push(filename)
}
private generateEliDocs() {
const eli = AvailableRasterLayers.editorLayerIndex()
this.writeMarkdownFile(
@ -295,8 +300,8 @@ export class GenerateDocs extends Script {
[
"# Layers in the Editor Layer Index",
"This table gives a summary of ids, names and other metainformation of background imagery that is available in MapComplete and that can be used as (default) map background." +
"These are sourced from [the Editor Layer Index](https://github.com/osmlab/editor-layer-index)", +
"\n[See the online, interactive map here](https://osmlab.github.io/editor-layer-index/)",
"These are sourced from [the Editor Layer Index](https://github.com/osmlab/editor-layer-index)",
+"\n[See the online, interactive map here](https://osmlab.github.io/editor-layer-index/)",
MarkdownUtils.table(
["id", "name", "category", "Best", "attribution"],
eli.map((f) => [
@ -305,10 +310,10 @@ export class GenerateDocs extends Script {
f.properties.category,
f.properties.best ? "⭐" : "",
f.properties.attribution?.html ?? f.properties.attribution?.text,
]),
])
),
].join("\n\n"),
["./public/assets/data/editor-layer-index.json"],
["./public/assets/data/editor-layer-index.json"]
)
}
@ -367,7 +372,10 @@ export class GenerateDocs extends Script {
* Generates documentation for the all the individual layers.
* Inline layers are included (if the theme is public)
*/
private generateOverviewsForAllSingleLayer(targetDirectory: string = "./Docs/Layers", lang: string = "en"): void {
private generateOverviewsForAllSingleLayer(
targetDirectory: string = "./Docs/Layers",
lang: string = "en"
): void {
const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter(
(layer) => layer["source"] !== null
)
@ -434,19 +442,23 @@ export class GenerateDocs extends Script {
mkdirSync(targetDirectory)
}
allLayers.forEach((layer) => {
const element = layer.generateDocumentation({
usedInThemes: themesPerLayer.get(layer.id),
layerIsNeededBy: layerIsNeededBy,
dependencies: DependencyCalculator.getLayerDependencies(layer),
lang
}).replaceAll("./Docs/Layers", targetDirectory)
const element = layer
.generateDocumentation({
usedInThemes: themesPerLayer.get(layer.id),
layerIsNeededBy: layerIsNeededBy,
dependencies: DependencyCalculator.getLayerDependencies(layer),
lang,
})
.replaceAll("./Docs/Layers", targetDirectory)
const inlineSource = inlineLayers.get(layer.id)
ScriptUtils.erasableLog("Exporting layer documentation for", layer.id)
let source: string = `assets/layers/${layer.id}/${layer.id}.json`
if (inlineSource !== undefined) {
source = `assets/themes/${inlineSource}/${inlineSource}.json`
}
this.writeMarkdownFile(targetDirectory + "/" + layer.id + ".md", element, [source], { lang })
this.writeMarkdownFile(targetDirectory + "/" + layer.id + ".md", element, [source], {
lang,
})
})
}
@ -491,17 +503,24 @@ export class GenerateDocs extends Script {
}
const docs: string[] = [
"# Which tagrendering is used where?", "",
"This document details where a tagRendering from one layer is reused in another layer, either by directly using it or by using a `{\"builtin\": id, \"override\": ...}` syntax",
"# Which tagrendering is used where?",
"",
'This document details where a tagRendering from one layer is reused in another layer, either by directly using it or by using a `{"builtin": id, "override": ...}` syntax',
"Having this overview supports e.g. refactoring efforts",
"## Existing builtin tagrenderings", ""]
"## Existing builtin tagrenderings",
"",
]
for (const [builtin, usedByLayers] of Array.from(layersUsingBuiltin.entries())) {
docs.push(`### ${builtin}\n`)
docs.push(usedByLayers.length + " usages")
docs.push(`${usedByLayers.map((item) => ` - [${item}](./Docs/Layers/${item}.md)`).join("\n")}`)
docs.push(
`${usedByLayers.map((item) => ` - [${item}](./Docs/Layers/${item}.md)`).join("\n")}`
)
}
this.writeMarkdownFile("./Docs/Studio/TagRendering_reuse_overview.md", docs.join("\n"), ["assets/layers/*.json"])
this.writeMarkdownFile("./Docs/Studio/TagRendering_reuse_overview.md", docs.join("\n"), [
"assets/layers/*.json",
])
}
private generateQueryParameterDocs() {
@ -537,7 +556,7 @@ export class GenerateDocs extends Script {
])
}
private generateForTheme(theme: ThemeConfig, options?: { path?: string, lang?: string }): void {
private generateForTheme(theme: ThemeConfig, options?: { path?: string; lang?: string }): void {
const allLayers = AllSharedLayers.sharedLayers
const layersToShow = theme.layers.filter(
(l) => l.id !== "favourite" && Constants.added_by_default.indexOf(<any>l.id) < 0
@ -563,38 +582,59 @@ export class GenerateDocs extends Script {
if (allLayers.has(l.id)) {
return `[${l.id}](../Layers/${l.id}.md)`
}
return `[${l.id} (${l.name?.textFor(lang)})](#${l.id.trim().replace(/ /g, "-")})`
return `[${l.id} (${l.name?.textFor(lang)})](#${l.id
.trim()
.replace(/ /g, "-")})`
})
),
new Translation(
{
en: "This theme is available in the following languages:",
nl: "Deze kaart is beschikbaar in de volgende talen:",
},
).textFor(lang),
MarkdownUtils.list(theme.language.filter((ln) => ln !== "_context").map(ln => {
if (language_translations[ln]) {
return ln + " (" + new Translation(language_translations[ln]).textFor(lang) + ")"
} else {
return ln
}
},
)),
new Translation({
en: "This theme is available in the following languages:",
nl: "Deze kaart is beschikbaar in de volgende talen:",
}).textFor(lang),
MarkdownUtils.list(
theme.language
.filter((ln) => ln !== "_context")
.map((ln) => {
if (language_translations[ln]) {
return (
ln +
" (" +
new Translation(language_translations[ln]).textFor(lang) +
")"
)
} else {
return ln
}
})
),
]
if (layersToInline.length > 0) {
el.push(MarkdownUtils.title(1, new Translation({
en: "Layers defined in this theme configuration file",
nl: "Lagen gedefinieerd in dit kaartthema-bestand",
})).textFor(lang))
el.push(MarkdownUtils.list(layersToInline.map(l => `[${l.name?.textFor(lang) ?? ""} (\`${l.id}\`)](#${l.id})`)))
el.push(new Translation({
en: "These layers can not be reused in different themes.",
nl: "Deze lagen kunnen niet in andere kaartthemas hergebruikt worden",
}).textFor(lang))
el.push(
...layersToInline.map((l) => l.generateDocumentation({ usedInThemes: null, lang })),
MarkdownUtils.title(
1,
new Translation({
en: "Layers defined in this theme configuration file",
nl: "Lagen gedefinieerd in dit kaartthema-bestand",
})
).textFor(lang)
)
el.push(
MarkdownUtils.list(
layersToInline.map(
(l) => `[${l.name?.textFor(lang) ?? ""} (\`${l.id}\`)](#${l.id})`
)
)
)
el.push(
new Translation({
en: "These layers can not be reused in different themes.",
nl: "Deze lagen kunnen niet in andere kaartthemas hergebruikt worden",
}).textFor(lang)
)
el.push(
...layersToInline.map((l) => l.generateDocumentation({ usedInThemes: null, lang }))
)
}
@ -606,7 +646,7 @@ export class GenerateDocs extends Script {
path + "/" + theme.id + ".md",
el.join("\n"),
[`assets/themes/${theme.id}/${theme.id}.json`],
{ noTableOfContents: true, lang },
{ noTableOfContents: true, lang }
)
}
@ -742,14 +782,16 @@ export class GenerateDocs extends Script {
*/
private generateSidebar(subdirectory = ""): string[] {
const tr = Translations.t.app.back.textFor(subdirectory)
const sidebar: string[] = [
`<a href='https://mapcomplete.org' class='back-to-mc'>${tr}</a>`
]
const sidebar: string[] = [`<a href='https://mapcomplete.org' class='back-to-mc'>${tr}</a>`]
const allFiles = ScriptUtils.readDirRecSync("./Docs/" + subdirectory)
.filter(path => path.endsWith(".md"))
.filter(path => !path.startsWith("_"))
.filter(path => !path.startsWith("./Docs/nl/") || (path.startsWith("./Docs/" + subdirectory) && subdirectory !== ""))
.map(path => path.substring("./Docs/".length + subdirectory.length + 1))
.filter((path) => path.endsWith(".md"))
.filter((path) => !path.startsWith("_"))
.filter(
(path) =>
!path.startsWith("./Docs/nl/") ||
(path.startsWith("./Docs/" + subdirectory) && subdirectory !== "")
)
.map((path) => path.substring("./Docs/".length + subdirectory.length + 1))
const perDirectory = new Map<string, string[]>()
function addFile(dir: string, path: string) {
@ -779,13 +821,15 @@ export class GenerateDocs extends Script {
const directories: [string, Translation | string][] = [
["", ""],
["Layers", new Translation({ en: "Overview of layers", nl: "Overzicht van de lagen" })],
["Themes", new Translation({ en: "Overview of map themes", nl: "Overzicht van de themas" })],
[
"Themes",
new Translation({ en: "Overview of map themes", nl: "Overzicht van de themas" }),
],
["UserTests", "Usability tests with users"],
["Studio", "For theme builders"],
["Dev", "For developers"],
]
for (const [dir, title] of directories) {
if (title === null) {
continue
@ -830,10 +874,9 @@ export class GenerateDocs extends Script {
}
private generateNormalLayerOverview(type: "Layers" | "Themes", subdir = "") {
const layerinfo: [string, string, string][] = []
const source: ReadonlyMap<string, LayerConfig> | AllKnownLayoutsLazy
= type === "Layers" ? AllSharedLayers.sharedLayers : AllKnownLayouts.allKnownLayouts
const source: ReadonlyMap<string, LayerConfig> | AllKnownLayoutsLazy =
type === "Layers" ? AllSharedLayers.sharedLayers : AllKnownLayouts.allKnownLayouts
const keys = Array.from(source.keys())
keys.sort()
@ -841,7 +884,7 @@ export class GenerateDocs extends Script {
const layer = source.get(id)
let name: Translation
if (type === "Layers") {
const layer_ = (<LayerConfig>layer)
const layer_ = <LayerConfig>layer
if (!layer_.isNormal()) {
continue
}
@ -849,36 +892,41 @@ export class GenerateDocs extends Script {
} else {
name = (<ThemeConfig>layer).title
}
layerinfo.push([`[${id}](./${type}/${id})`, name.textFor(subdir), (layer["shortDescription"] ?? layer.description)?.textFor(subdir)])
layerinfo.push([
`[${id}](./${type}/${id})`,
name.textFor(subdir),
(layer["shortDescription"] ?? layer.description)?.textFor(subdir),
])
}
const titles = {
"Layers": new Translation({ en: "Layers", nl: "Lagen" }),
"Themes": new Translation({ en: "Themes", nl: "Kaartthema's" })
Layers: new Translation({ en: "Layers", nl: "Lagen" }),
Themes: new Translation({ en: "Themes", nl: "Kaartthema's" }),
}
const intro: Record<string, TypedTranslation<{ version }>> = {
"Layers": new TypedTranslation<{ version }>({
Layers: new TypedTranslation<{ version }>({
en: "The following layers are available in MapComplete {version}:",
nl: "De volgende lagen zijn beschikbaar in MapComplete {version}:"
nl: "De volgende lagen zijn beschikbaar in MapComplete {version}:",
}),
"Themes": new TypedTranslation<{ version }>({
Themes: new TypedTranslation<{ version }>({
en: "The following themes are available in MapComplete {version}:",
nl: "De volgende kaartthemas zijn beschikbaar in MapComplete {version}:"
})
nl: "De volgende kaartthemas zijn beschikbaar in MapComplete {version}:",
}),
}
const doc = ["# " + titles[type].textFor(subdir),
intro[type].Subs({ version: Constants.vNumber }).textFor(subdir)
, MarkdownUtils.table(
["id", "name", "description"],
layerinfo)
const doc = [
"# " + titles[type].textFor(subdir),
intro[type].Subs({ version: Constants.vNumber }).textFor(subdir),
MarkdownUtils.table(["id", "name", "description"], layerinfo),
]
const path = `./Docs/${subdir}/${type}`
if (!existsSync(path)) {
mkdirSync(path)
}
this.writeMarkdownFile(`${path}/README.md`, doc.join("\n\n"), [`./assets/${type.toLowerCase()}/*.json`])
this.writeMarkdownFile(`${path}/README.md`, doc.join("\n\n"), [
`./assets/${type.toLowerCase()}/*.json`,
])
}
/**
@ -893,8 +941,7 @@ export class GenerateDocs extends Script {
continue
}
if (!AllSharedLayers.sharedLayers.has(id)) {
throw ("Privileged layer definition not found: " + id)
throw "Privileged layer definition not found: " + id
}
}
@ -933,7 +980,8 @@ export class GenerateDocs extends Script {
}
const el = [
"# Special and priviliged layers", "",
"# Special and priviliged layers",
"",
"MapComplete has a few data layers available which have special properties through builtin-hooks.",
"They perform various tasks, such as showing the GPS-location and track on the screen or help in special elements such as the 'cut way'-element.",
"As a theme builder, you can influence the behaviour of those layers by overriding them in your .json file",
@ -942,14 +990,15 @@ export class GenerateDocs extends Script {
),
...Lists.noNull(
Constants.priviliged_layers.map((id) => AllSharedLayers.sharedLayers.get(id))
).map((l) =>
l.generateDocumentation({
usedInThemes: themesPerLayer.get(l.id),
layerIsNeededBy: layerIsNeededBy,
dependencies: DependencyCalculator.getLayerDependencies(l),
addedByDefault: Constants.added_by_default.indexOf(<any>l.id) >= 0,
canBeIncluded: Constants.no_include.indexOf(<any>l.id) < 0,
}) + "\n"
).map(
(l) =>
l.generateDocumentation({
usedInThemes: themesPerLayer.get(l.id),
layerIsNeededBy: layerIsNeededBy,
dependencies: DependencyCalculator.getLayerDependencies(l),
addedByDefault: Constants.added_by_default.indexOf(<any>l.id) >= 0,
canBeIncluded: Constants.no_include.indexOf(<any>l.id) < 0,
}) + "\n"
),
].join("\n")
this.writeMarkdownFile("./Docs/Studio/SpecialLayers.md", el, [

View file

@ -898,7 +898,7 @@ class LayerOverviewUtils extends Script {
console.log("Creating needed_assets-file")
const images = Lists.dedup(
Array.from(sharedThemes.values()).flatMap((th) => th._usedImages ?? [])
).flatMap(path => ScriptUtils.detectVariablePaths(path))
).flatMap((path) => ScriptUtils.detectVariablePaths(path))
writeFileSync("needed_assets.csv", images.join("\n"))
console.log("Written needed_assets.csv")

View file

@ -13,8 +13,8 @@ class GeneratePmTilesExtracts extends Script {
constructor() {
super(
"Generates many pmtiles-archive from planet-latest.pmtiles. Expects the `pmtiles`-executable to be at `/data/pmtiles`." +
"\n\n" +
"The first argument should be the 'targetDirectory', where many subdirectories will be made containing the subarchives. A second argument (defaulting to [targetDir]/planet-latest.pmtiles) is the source data"
"\n\n" +
"The first argument should be the 'targetDirectory', where many subdirectories will be made containing the subarchives. A second argument (defaulting to [targetDir]/planet-latest.pmtiles) is the source data"
)
}
@ -31,15 +31,7 @@ class GeneratePmTilesExtracts extends Script {
this.skipped += boundary
return
}
console.log(
"Starting column",
x,
"at zoom",
z,
"as",
lastFileForColumn,
"does not exist"
)
console.log("Starting column", x, "at zoom", z, "as", lastFileForColumn, "does not exist")
for (let y = 0; y < boundary; y++) {
yield this.extractsGenerator.generateArchive(z, x, y, maxzoom)
@ -58,8 +50,6 @@ class GeneratePmTilesExtracts extends Script {
}
}
private *generateAll(): Generator<Promise<void>> {
const zoomlevels: Record<number, number> = OfflineBasemapManager.zoomelevels
for (const key in zoomlevels) {
@ -88,7 +78,7 @@ class GeneratePmTilesExtracts extends Script {
async main(args: string[]): Promise<void> {
this.targetDir = args[0]
const sourceFile = this.targetDir+"/planet-latest.pmtiles"
const sourceFile = this.targetDir + "/planet-latest.pmtiles"
if (!this.targetDir) {
console.log("Please specify a target directory. Did you forget '--' in vite-node?")
return
@ -126,11 +116,14 @@ class GeneratePmTilesExtracts extends Script {
)}`
)
} while (batch.length > 0)
writeFileSync(this.targetDir+"/Last_pm_tile_extracts.txt",
[new Date().getTime() + "",
writeFileSync(
this.targetDir + "/Last_pm_tile_extracts.txt",
[
new Date().getTime() + "",
new Date().toISOString(),
"# The script converting the planet-latest.pmtiles into sub-archives has been successfully executed at the stated time",
].join("\n"), "utf-8",
].join("\n"),
"utf-8"
)
}
}

View file

@ -235,12 +235,7 @@ function generateProjectsOverview(files: string[]) {
const projectList = readFileSync(tagInfoList, { encoding: "utf8" })
.split("\n")
.filter((entry) => entry.indexOf("mapcomplete_") < 0)
.concat(
files.map(
(f) =>
`${f} https://docs.mapcomplete.org/TagInfo/${f}.json`
)
)
.concat(files.map((f) => `${f} https://docs.mapcomplete.org/TagInfo/${f}.json`))
.sort()
.filter((entry) => entry != "")

View file

@ -6,7 +6,11 @@ export class PmTilesExtractGenerator {
private readonly _sourceFile: string
private readonly _executeableLocation: string
constructor(sourceFile: string, targetDir: string, executeableLocation: string = "/data/pmtiles") {
constructor(
sourceFile: string,
targetDir: string,
executeableLocation: string = "/data/pmtiles"
) {
this._sourceFile = sourceFile
this._targetDir = targetDir
this._executeableLocation = executeableLocation
@ -20,7 +24,7 @@ export class PmTilesExtractGenerator {
})
if (captureStdioChunks !== undefined) {
child.stdout.on("data", data => {
child.stdout.on("data", (data) => {
captureStdioChunks(data)
})
}
@ -50,15 +54,15 @@ export class PmTilesExtractGenerator {
}
const outputFileName = this.getFilename(z, x, y)
await this.startProcess(
`extract ${this._sourceFile} --download-threads=1 --minzoom=${z}${maxzoomflag} --bbox=${[
`extract ${
this._sourceFile
} --download-threads=1 --minzoom=${z}${maxzoomflag} --bbox=${[
min_lon,
min_lat + 0.0001,
max_lon,
max_lat,
].join(",")} ${outputFileName}`,
].join(",")} ${outputFileName}`
)
return outputFileName
}
}

View file

@ -65,9 +65,9 @@ export class Server {
},
})
this.handlers = handle
http.createServer((req: http.IncomingMessage, res) =>
this.answerRequest(req, res)
).listen(port)
http.createServer((req: http.IncomingMessage, res) => this.answerRequest(req, res)).listen(
port
)
console.log(
"Server is running on http://127.0.0.1:" + port,
". Supported endpoints are: " + handle.map((h) => h.mustMatch).join(", ")
@ -86,7 +86,7 @@ export class Server {
"from:",
req.headers.origin,
new Date().toISOString(),
path,
path
)
if (this.options?.ignorePathPrefix) {
for (const toIgnore of this.options.ignorePathPrefix) {
@ -114,14 +114,11 @@ export class Server {
res.setHeader(
"Access-Control-Allow-Headers",
"Origin, X-Requested-With, Content-Type, Accept",
"Origin, X-Requested-With, Content-Type, Accept"
)
res.setHeader("Access-Control-Allow-Origin", req.headers.origin ?? "*")
if (req.method === "OPTIONS") {
res.setHeader(
"Access-Control-Allow-Methods",
"POST, GET, OPTIONS, DELETE, UPDATE",
)
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, UPDATE")
res.writeHead(204, { "Content-Type": handler.mimetype })
res.end()
return
@ -154,7 +151,7 @@ export class Server {
"resulted in a ",
typeof result,
" instead of a string:",
result,
result
)
}
@ -162,8 +159,6 @@ export class Server {
res.writeHead(200, { "Content-Type": handler.mimetype, ...extraHeaders })
res.write("" + result)
res.end()
} catch (e) {
console.error("Could not handle request:", e)
res.writeHead(500)
@ -183,8 +178,7 @@ export class Server {
* @param path
* @param res
*/
public static sendFile(path: string, res: ServerResponse){
createReadStream(path).pipe(res);
public static sendFile(path: string, res: ServerResponse) {
createReadStream(path).pipe(res)
}
}

View file

@ -11,14 +11,16 @@ class ServerPmTileExtracts extends Script {
private static FILE_IS_STALE_AFTER_SEC = 30 * 24 * 60 * 60
constructor() {
super("Starts a server that serves PMtiles. Usage:\n" +
"sourceFile cachedir [portnumber??2346]")
super(
"Starts a server that serves PMtiles. Usage:\n" +
"sourceFile cachedir [portnumber??2346]"
)
}
async main(args: string[]): Promise<void> {
if(args.length < 2){
if (args.length < 2) {
this.printHelp()
throw ("!!! Please, specify a source- and cachedir !!!")
throw "!!! Please, specify a source- and cachedir !!!"
}
const sourcefile = args[0]
const targetDir = args[1]
@ -29,52 +31,65 @@ class ServerPmTileExtracts extends Script {
const creationDates = new Map<string, Date>()
new Server(port, {},
[
{
mustMatch: /\d+\/\d+\/\d+.pmtiles/,
unmanaged: true,
mimetype: "application/octet-stream",
handle: async (path: string,
queryParams: URLSearchParams,
req: http.IncomingMessage,
body: string,
res: ServerResponse) => {
const [z,x,y] = path.split(".")[0].split("/").map(x => Number(x))
if(!(z in zoomlevels)){
throw `Invalid zoomlevel ${z} (x: ${x}, y: ${y}, must be one of ${Array.from(Object.keys(zoomlevels)).join(", ")}`
}
new Server(port, {}, [
{
mustMatch: /\d+\/\d+\/\d+.pmtiles/,
unmanaged: true,
mimetype: "application/octet-stream",
handle: async (
path: string,
queryParams: URLSearchParams,
req: http.IncomingMessage,
body: string,
res: ServerResponse
) => {
const [z, x, y] = path
.split(".")[0]
.split("/")
.map((x) => Number(x))
if (!(z in zoomlevels)) {
throw `Invalid zoomlevel ${z} (x: ${x}, y: ${y}, must be one of ${Array.from(
Object.keys(zoomlevels)
).join(", ")}`
}
const targetFile = generator.getFilename(z, x, y)
const targetFile = generator.getFilename(z, x, y)
let targetCreationDate = creationDates.get(targetFile)
let isStale = targetCreationDate === undefined ? false : new Date().getTime() - targetCreationDate.getTime() > 1000 * ServerPmTileExtracts.FILE_IS_STALE_AFTER_SEC
let targetCreationDate = creationDates.get(targetFile)
let isStale =
targetCreationDate === undefined
? false
: new Date().getTime() - targetCreationDate.getTime() >
1000 * ServerPmTileExtracts.FILE_IS_STALE_AFTER_SEC
if (isStale || !existsSync(targetFile)) {
ScriptUtils.createParentDir(targetFile)
console.log("Creating", targetFile)
const start = new Date()
await generator.generateArchive(z, x, y)
const stop = new Date()
console.log("Creating ", targetFile, "took", (stop.getTime() - start.getTime()) + "ms")
} else if (targetCreationDate === undefined) {
const stats = statSync(targetFile)
creationDates.set(targetFile, stats.mtime)
}
if(req.destroyed){
return null
}
res.writeHead(200, { "Content-Type": "application/octet-stream"})
Server.sendFile(targetFile, res)
if (isStale || !existsSync(targetFile)) {
ScriptUtils.createParentDir(targetFile)
console.log("Creating", targetFile)
const start = new Date()
await generator.generateArchive(z, x, y)
const stop = new Date()
console.log(
"Creating ",
targetFile,
"took",
stop.getTime() - start.getTime() + "ms"
)
} else if (targetCreationDate === undefined) {
const stats = statSync(targetFile)
creationDates.set(targetFile, stats.mtime)
}
if (req.destroyed) {
return null
},
},
],
)
}
}
res.writeHead(200, { "Content-Type": "application/octet-stream" })
Server.sendFile(targetFile, res)
return null
},
},
])
}
}
new ServerPmTileExtracts().run()

View file

@ -255,18 +255,20 @@ export class RegexTag extends TagsFilter {
asHumanString(linkToWiki: boolean) {
if (typeof this.key === "string") {
const oper = typeof this.value === "string" ? "=" : "~"
if(linkToWiki){
return `[${this.key}](https://wiki.osm.org/wiki/Key:${this.key})\`${this.invert ? "!" : ""}${oper}${RegexTag.source(this.value)}\``
if (linkToWiki) {
return `[${this.key}](https://wiki.osm.org/wiki/Key:${this.key})\`${
this.invert ? "!" : ""
}${oper}${RegexTag.source(this.value)}\``
}
const v= `${this.key}${this.invert ? "!" : ""}${oper}${RegexTag.source(this.value)}`
if(linkToWiki){
const v = `${this.key}${this.invert ? "!" : ""}${oper}${RegexTag.source(this.value)}`
if (linkToWiki) {
return `\`${v}\``
}
return v
}
const v = `${this.key.source}${this.invert ? "!" : ""}~~${RegexTag.source(this.value)}`
if(linkToWiki){
if (linkToWiki) {
return `\`${v}\``
}
return v

View file

@ -1,6 +1,18 @@
import { Concat, DesugaringContext, DesugaringStep, Each, FirstOf, Fuse, On, SetDefault } from "./Conversion"
import {
Concat,
DesugaringContext,
DesugaringStep,
Each,
FirstOf,
Fuse,
On,
SetDefault,
} from "./Conversion"
import { LayerConfigJson } from "../Json/LayerConfigJson"
import { MinimalTagRenderingConfigJson, TagRenderingConfigJson } from "../Json/TagRenderingConfigJson"
import {
MinimalTagRenderingConfigJson,
TagRenderingConfigJson,
} from "../Json/TagRenderingConfigJson"
import { Utils } from "../../../Utils"
import SpecialVisualizations from "../../../UI/SpecialVisualizations"
import Translations from "../../../UI/i18n/Translations"
@ -11,7 +23,10 @@ import { TagConfigJson } from "../Json/TagConfigJson"
import PointRenderingConfigJson, { IconConfigJson } from "../Json/PointRenderingConfigJson"
import ValidationUtils from "./ValidationUtils"
import { RenderingSpecification } from "../../../UI/SpecialVisualization"
import { MappingConfigJson, QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson"
import {
MappingConfigJson,
QuestionableTagRenderingConfigJson,
} from "../Json/QuestionableTagRenderingConfigJson"
import { ConfigMeta } from "../../../UI/Studio/configMeta"
import { ConversionContext } from "./ConversionContext"
import { ExpandRewrite } from "./ExpandRewrite"
@ -457,7 +472,10 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
private static escapeStr(v: string, context: ConversionContext): string {
if (typeof v !== "string") {
context.err("Detected a non-string value where one expected a string (while rewriting a special): " + JSON.stringify(v))
context.err(
"Detected a non-string value where one expected a string (while rewriting a special): " +
JSON.stringify(v)
)
return RewriteSpecial.escapeStr("" + v, context)
}
return v
@ -593,9 +611,13 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
argNamesList,
(x) => x
)
return `Unexpected argument in special block of type ${vis.funcName} with name '${wrongArg}'. Did you mean ${
return `Unexpected argument in special block of type ${
vis.funcName
} with name '${wrongArg}'. Did you mean ${
byDistance[0]
}?\n\tAll known arguments are: ${vis.args.map(arg => arg.name + ": \t" + arg.type + "\t" + arg.doc).join("\n ")}`
}?\n\tAll known arguments are: ${vis.args
.map((arg) => arg.name + ": \t" + arg.type + "\t" + arg.doc)
.join("\n ")}`
})
.forEach((e) => context.err(e))
@ -756,7 +778,7 @@ class ExpandIconBadges extends DesugaringStep<PointRenderingConfigJson> {
for (const tr of expanded) {
const condition = tr.condition
for (const trElement of <MappingConfigJson[]> tr.mappings) {
for (const trElement of <MappingConfigJson[]>tr.mappings) {
const showIf = TagUtils.optimzeJson({
and: Lists.noNull([
condition,
@ -1129,10 +1151,9 @@ export class OrderLayer extends DesugaringStep<string | LayerConfigJson> {
const orderTag = new OrderTagRendering()
let json: LayerConfigJson = jsonOrString
json = <LayerConfigJson>new On(
"tagRenderings",
new Each(orderTag)
).convert(<any>json, context)
json = <LayerConfigJson>(
new On("tagRenderings", new Each(orderTag)).convert(<any>json, context)
)
json = <LayerConfigJson>new On("title", orderTag).convert(<any>json, context)
@ -1174,29 +1195,46 @@ export class PrepareLayer extends Fuse<LayerConfigJson> {
return Lists.flatten(list)
})
),
<any>new On(
"pointRendering",
(layer) =>
new Each(new On("marker", new Each(new ExpandMarkerRenderings(state, <any> layer))))
<any>(
new On(
"pointRendering",
(layer) =>
new Each(
new On(
"marker",
new Each(new ExpandMarkerRenderings(state, <any>layer))
)
)
)
),
<any>new On(
"pointRendering",
(layer) => new Each(new PreparePointRendering(state, <any> layer))
<any>(
new On(
"pointRendering",
(layer) => new Each(new PreparePointRendering(state, <any>layer))
)
),
new SetDefault("titleIcons", ["icons.defaults"]),
new AddRatingBadge(),
new AddFavouriteBadges(),
new AutoTitleIcon(),
<any>new On(
"titleIcons",
(layer) =>
new Concat(<any> new ExpandTagRendering(state, <any> layer, { noHardcodedStrings: true }))
<any>(
new On(
"titleIcons",
(layer) =>
new Concat(
<any>(
new ExpandTagRendering(state, <any>layer, {
noHardcodedStrings: true,
})
)
)
)
),
new AddFiltersFromTagRenderings(),
new ExpandFilter(state),
new MoveUnitConfigs(),
new PruneFilters(),
<any> new OrderLayer()
<any>new OrderLayer()
)
}

View file

@ -156,8 +156,10 @@ export class PrevalidateLayer extends DesugaringStep<LayerConfigJson> {
context.err("Layer " + json.id + " does not have an explicit 'allowMove'")
}
}
if(json.source["geojsonZoomLevel"]){
context.enter("source").err("Use geoJsonZoomLevel (with capital J) instead of geojsonZoomLevel")
if (json.source["geojsonZoomLevel"]) {
context
.enter("source")
.err("Use geoJsonZoomLevel (with capital J) instead of geojsonZoomLevel")
}
if (context.hasErrors()) {

View file

@ -224,13 +224,17 @@ export default class FilterConfig {
public generateDocs(options?: { lang?: string }): string {
const lang = options?.lang ?? "en"
const header = ["id", { en: "Question", nl: "Vraag" }, {
en: "Attributes",
nl: "Attributen",
},
const header = [
"id",
{ en: "Question", nl: "Vraag" },
{
en: "Attributes",
nl: "Attributen",
},
{ en: "Fields", nl: "Velden" },
].map(x => typeof x === "string" ? new Translation({ "*": x }) : new Translation(x))
.map(tr => tr.textFor(lang))
]
.map((x) => (typeof x === "string" ? new Translation({ "*": x }) : new Translation(x)))
.map((tr) => tr.textFor(lang))
const content: string[][] = []
for (let i = 0; i < this.options.length; i++) {
@ -239,7 +243,10 @@ export default class FilterConfig {
content.push([
this.id + "." + i,
opt.question.textFor(lang),
opt.osmTags?.asHumanString(true) ?? (new Translation({en: "_None - show all_", nl: "_Geen - toon alles_"})).textFor(lang),
opt.osmTags?.asHumanString(true) ??
new Translation({ en: "_None - show all_", nl: "_Geen - toon alles_" }).textFor(
lang
),
opt.fields?.map((f) => f.name + " (" + f.type + ")").join(" "),
])
}

View file

@ -422,13 +422,11 @@ export default class LayerConfig extends WithContextLoader {
reusedTagRenderings?: Map<string, { layer: string }[]>
lang?: string
}): string {
const paragraphs: (string | Translation)[] = ["# " + this.id, this.description]
const paragraphs: (string | Translation)[] = [
"# " + this.id,
this.description,
]
function add(item: Translation | string | Record<string, string> & { en: string, nl: string }) {
function add(
item: Translation | string | (Record<string, string> & { en: string; nl: string })
) {
if (item instanceof Translation || typeof item === "string") {
paragraphs.push(item)
} else if (item["en"] !== undefined) {
@ -437,12 +435,12 @@ export default class LayerConfig extends WithContextLoader {
}
if (this._basedOn) {
add(new TypedTranslation<{ basedOn }>(
{
add(
new TypedTranslation<{ basedOn }>({
en: `This layer is based on [{basedOn}](../Layers/{basedOn}.md)`,
nl: `Deze laag is gebaseerd op [{basedOn}](../Layers/{basedOn}.md)`,
},
).Subs({ basedOn: this._basedOn }))
}).Subs({ basedOn: this._basedOn })
)
}
// Various extra properties, added in a list below the description
@ -452,18 +450,16 @@ export default class LayerConfig extends WithContextLoader {
new TypedTranslation<{ minzoom }>({
en: "This layer is shown at zoomlevel **{minzoom}** and higher",
nl: "Deze laag wordt getoond vanaf zoomlevel **{minzoom}**",
}).Subs(this))
}).Subs(this)
)
if (canBeIncluded) {
if (addedByDefault) {
extraProps.push(
new Translation(
{
en:
"**This layer is included automatically in every theme. This layer might contain no points**",
nl: "**Deze laag wordt automatisch toegevoegd aan ieder kaartthema. Deze laag bevat mogelijks geen punten",
},
),
new Translation({
en: "**This layer is included automatically in every theme. This layer might contain no points**",
nl: "**Deze laag wordt automatisch toegevoegd aan ieder kaartthema. Deze laag bevat mogelijks geen punten",
})
)
}
if (this.shownByDefault === false) {
@ -471,34 +467,31 @@ export default class LayerConfig extends WithContextLoader {
new Translation({
en: "This layer is not visible by default and must be enabled in the filter by the user. ",
nl: "Deze laag is standaard niet zichtbaar en moet door de gebruiker aangezet worden in het 'filter'-menu ",
}),
})
)
}
if (this.title === undefined) {
extraProps.push(
new Translation({
en:
"This layers doesn't have a title set. As such, elements will appear on the map but cannot be clicked. If you import this layer in your theme, override `title` to make sure elements can be opened."
,
en: "This layers doesn't have a title set. As such, elements will appear on the map but cannot be clicked. If you import this layer in your theme, override `title` to make sure elements can be opened.",
nl: "Deze laag heeft geen 'title' ingesteld. Elementen zullen op de kaart tonen, maar kunnen niet opengeklikt worden door de gebruiker. Hergebruik je deze laag? Voeg een `title` toe zodat element wel opengeklikt kunnen worden.",
}),
})
)
}
if (this.name === undefined && this.shownByDefault === false) {
if (this.shownByDefault === false) {
extraProps.push(
new TypedTranslation<{ id }>({
en: "This layer is not visible by default and the visibility cannot be toggled, effectively resulting in a fully hidden layer. This can be useful, e.g. to calculate some metatags. If you want to render this layer (e.g. for debugging), enable it by adding the URL-parameter `layer-{id}=true`",
nl: "Deze laag is standaard niet zichtbaar én heeft geen `name`. Dit betekent dat de zichtbaarheid niet door de gebruiker ingesteld kan worden, wat in een volledig verborgen laag resulteert. Dit kan nuttig zij, bv. om metatags uit te rekenen. Wil je deze laag toch tonen (bv om te debuggen?)? Voeg de URL-parameter `layer-{id}=true` toe",
}).Subs(this),
}).Subs(this)
)
} else {
extraProps.push(
new Translation({
en: "Not visible in the layer selection by default. If you want to make this layer toggable, override `name`",
nl: "Deze laag kan niet onzichtbaar gemaakt worden in het filtermenu. Overschrijf `name` indien je dit wel wilt.",
}),
})
)
}
}
@ -507,40 +500,35 @@ export default class LayerConfig extends WithContextLoader {
new Translation({
en: "Not rendered on the map by default.",
nl: "Deze laag wordt standaard niet weergegeven op de kaart.",
}),
})
)
}
} else {
extraProps.push(
new Translation({
en: "This layer can **not** be included in a theme. It is solely used by [special renderings](SpecialRenderings.md) showing a minimap with custom data.",
nl: "Deze laag kan **niet** aan een kaartthema toegevoegd worden. Deze laag wordt enkel en alleen gebruikt om een [speciale rendering](SpecialRenderings.md) te ondersteunen. ",
}),
})
)
}
for (const dep of dependencies) {
extraProps.push(
new TypedTranslation<{ neededLayer, reason, context? }>({
new TypedTranslation<{ neededLayer; reason; context? }>({
en: "This layer will automatically load [`{neededLayer}`](./{neededLayer}.md) into the theme as it depends on it: {reason} ({context??no context given})",
nl: "Deze laag laadt automatisch de laag [`{neededLayer}`](./{neededLayer}.md) in het kaartthema want deze laag steunt hierop: {reason} ({context??geen context gekend})",
}).Subs(dep),
}).Subs(dep)
)
}
for (const revDep of Lists.dedup(layerIsNeededBy?.get(this.id) ?? [])) {
extraProps.push(
new TypedTranslation<{ revDep }>({
en: "This layer is needed as dependency for layer [`{revDep}`](#{revDep})",
}).Subs({ revDep }),
}).Subs({ revDep })
)
}
add(MarkdownUtils.list(extraProps.map(tr => tr.textFor(lang))))
add(MarkdownUtils.list(extraProps.map((tr) => tr.textFor(lang))))
}
// Overview of what themes use this layer
@ -548,16 +536,20 @@ export default class LayerConfig extends WithContextLoader {
if (!addedByDefault) {
if (usedInThemes?.length > 0) {
add(
MarkdownUtils.title(2,
MarkdownUtils.title(
2,
new Translation({
en: "Themes using this layer",
nl: "Kaartthemas die deze laag gebruiken",
})),
})
)
)
add(
MarkdownUtils.list(
(usedInThemes ?? []).map((id) => `[\`${id}\`](https://mapcomplete.org/${id})`),
),
(usedInThemes ?? []).map(
(id) => `[\`${id}\`](https://mapcomplete.org/${id})`
)
)
)
} else if (this.source !== null) {
add({
@ -570,85 +562,75 @@ export default class LayerConfig extends WithContextLoader {
// Data source information
{
paragraphs.push(MarkdownUtils.title(2, new Translation({ en: "Data source", nl: "Databron" })))
paragraphs.push(
MarkdownUtils.title(2, new Translation({ en: "Data source", nl: "Databron" }))
)
if (this.source?.geojsonSource) {
paragraphs.push(
new TypedTranslation<{ geojsonSource? }>({
en: "⚠️ This layer is loaded from an external source, namely `{geojsonSource}`",
nl: "⚠️ Deze laag wordt van een externe bron geladen, namelijk `{geojsonSource}`",
},
).Subs(this.source),
en: "⚠️ This layer is loaded from an external source, namely `{geojsonSource}`",
nl: "⚠️ Deze laag wordt van een externe bron geladen, namelijk `{geojsonSource}`",
}).Subs(this.source)
)
} else if (!this.source) {
add({
en: "This is a special layer, probably a library layer or support layer for MapComplete",
nl: "Dit is een speciale laag, waarschijnlijk een bibliotheeklaag of een ondersteunende laag",
})
} else {
const neededTags = <TagsFilter>this.source.osmTags.optimize()
if (neededTags["and"]) {
add(
{
en: "Elements on this layer match **all** of the following expressions:",
nl: "Elementen in deze laag hebben **alle** van de volgende kenmerken:",
},
)
add({
en: "Elements on this layer match **all** of the following expressions:",
nl: "Elementen in deze laag hebben **alle** van de volgende kenmerken:",
})
const parts = neededTags["and"]
add(
parts.map((p, i) => i + ". " + p.asHumanString(true, false, {})).join("\n"),
)
add(parts.map((p, i) => i + ". " + p.asHumanString(true, false, {})).join("\n"))
} else if (neededTags["or"]) {
const parts = neededTags["or"]
add(
{
en: "Elements on this layer match **any** of the following expressions:",
nl: "Elementen in deze laag hebben **minstens één** van de volgende kenmerken:",
})
add(
parts.map((p) => " - " + p.asHumanString(true, false, {})).join("\n"),
)
add({
en: "Elements on this layer match **any** of the following expressions:",
nl: "Elementen in deze laag hebben **minstens één** van de volgende kenmerken:",
})
add(parts.map((p) => " - " + p.asHumanString(true, false, {})).join("\n"))
} else {
add({
en: "Elements on this layer match the following expression:",
nl: "Elementen in deze laag hebben de volgende kenmerken:",
})
add(
neededTags.asHumanString(true, false, {}),
)
add(neededTags.asHumanString(true, false, {}))
}
const link = Overpass.AsOverpassTurboLink(<TagsFilter>this.source.osmTags.optimize())
const link = Overpass.AsOverpassTurboLink(
<TagsFilter>this.source.osmTags.optimize()
)
.replaceAll("(", "%28")
.replaceAll(")", "%29")
const txt = new TypedTranslation<{ link }>(
{
en: "Execute on overpass-turbo.eu",
nl: "Uitvoeren op overpass-turbo.eu",
},
).textFor(lang)
const txt = new TypedTranslation<{ link }>({
en: "Execute on overpass-turbo.eu",
nl: "Uitvoeren op overpass-turbo.eu",
}).textFor(lang)
paragraphs.push(`[🗺️ ${txt}](${link})`)
}
}
// Presets
{
if (this.presets.length > 0) {
add(new Translation({
en: "## Presets",
nl: "## Nieuwe punten toevoegen",
}))
add(
new Translation({
en: "## Presets",
nl: "## Nieuwe punten toevoegen",
})
)
add(new Translation({
en: "The following options to create new points are included:",
nl: "De volgende opties bestaan om nieuwe punten toe te voegen:",
}))
add(
new Translation({
en: "The following options to create new points are included:",
nl: "De volgende opties bestaan om nieuwe punten toe te voegen:",
})
)
const hTags = new Translation({
en: "Used tags",
@ -667,28 +649,24 @@ export default class LayerConfig extends WithContextLoader {
nl: "Beschrijving",
})
add(MarkdownUtils.table(
[
hTags,
hTitle,
hSnaps,
hDescription,
].map(t => t.textFor(lang)),
this.presets.map(preset => {
let category = preset.title.textFor(lang)
category = "**" + category + "**"
return [
new And(preset.tags).asHumanString(true),
Translations.t.general.add.addNew.Subs({ category }).textFor(lang),
preset.preciseInput?.snapToLayers
?.map((id) => `[\`${id}\`](./Layers/${id}.md)`)
?.join(", "),
preset.description?.textFor(lang),
]
}),
{ dropEmptyColumns: true },
))
add(
MarkdownUtils.table(
[hTags, hTitle, hSnaps, hDescription].map((t) => t.textFor(lang)),
this.presets.map((preset) => {
let category = preset.title.textFor(lang)
category = "**" + category + "**"
return [
new And(preset.tags).asHumanString(true),
Translations.t.general.add.addNew.Subs({ category }).textFor(lang),
preset.preciseInput?.snapToLayers
?.map((id) => `[\`${id}\`](./Layers/${id}.md)`)
?.join(", "),
preset.description?.textFor(lang),
]
}),
{ dropEmptyColumns: true }
)
)
} else {
add({
en: "This layer does not allowing adding new points",
@ -699,26 +677,33 @@ export default class LayerConfig extends WithContextLoader {
// Supported attributes table
{
const keyValues = (Lists.noNull(this.tagRenderings
.map((tr) => tr.freeformValues())
.filter((values) => values !== undefined)
.filter((values) => values.key !== "id")))
keyValues.sort((a, b) => a.key < b.key ? -1 : 1)
const keyValues = Lists.noNull(
this.tagRenderings
.map((tr) => tr.freeformValues())
.filter((values) => values !== undefined)
.filter((values) => values.key !== "id")
)
keyValues.sort((a, b) => (a.key < b.key ? -1 : 1))
if (keyValues.length > 0) {
add(MarkdownUtils.title(2, new Translation({
en: "Attribute overview",
nl: "Attributenoverzicht",
})))
add(
MarkdownUtils.title(
2,
new Translation({
en: "Attribute overview",
nl: "Attributenoverzicht",
})
)
)
add({
en: "This table gives an overview of most OpenStreetMap [keys](https://wiki.openstreetmap.org/wiki/Tags) that this layer shows and/or edits",
nl: "Deze tabel geeft een overzicht van de meeste OpenStreetMap [sleutels](https://wiki.openstreetmap.org/wiki/Tags) die deze laag toont en/of aanpast",
})
const tableRows: string[][] = keyValues.map((values) => {
const embedded: string[] = values.values?.map((v) =>
OsmWiki.constructLinkMd(values.key, v),
OsmWiki.constructLinkMd(values.key, v)
) ?? ["_no preset options defined, or no values in them_"]
const statistics = `https://taghistory.raifer.tech/?#***/${encodeURIComponent(
values.key,
values.key
)}/`
const tagInfo = `https://taginfo.openstreetmap.org/keys/${values.key}#values`
return [
@ -734,55 +719,71 @@ export default class LayerConfig extends WithContextLoader {
]
})
const header: { en: string, nl: string }[] = [
{ en: "Key", nl: "OSM-sleutel" }, {
const header: { en: string; nl: string }[] = [
{ en: "Key", nl: "OSM-sleutel" },
{
en: "Type (for freeform input)",
nl: "Inputtype",
}, { en: "Predefined, supported options", nl: "Voorgedefinieerde, ondersteunde opties" },
},
{
en: "Predefined, supported options",
nl: "Voorgedefinieerde, ondersteunde opties",
},
]
add(
MarkdownUtils.table(
header.map(tr => new Translation(tr).textFor(lang)),
tableRows
)
MarkdownUtils.table(
header.map((tr) => new Translation(tr).textFor(lang)),
tableRows
)
)
}
}
}
// Elements in the popup
{
add(MarkdownUtils.title(2, new Translation({
en: "Overview of questions (and other elements) in the popup",
nl: "Overzicht van vragen (en andere elementen) in de popup",
})))
add(
MarkdownUtils.title(
2,
new Translation({
en: "Overview of questions (and other elements) in the popup",
nl: "Overzicht van vragen (en andere elementen) in de popup",
})
)
)
for (const tagRendering of this.tagRenderings) {
if (tagRendering.labels.indexOf("ignore_docs") >= 0) {
continue
}
add(tagRendering.generateDocumentation(
this.id,
lang,
reusedTagRenderings?.get(tagRendering.id)?.map((l) => l.layer),
))
add(
tagRendering.generateDocumentation(
this.id,
lang,
reusedTagRenderings?.get(tagRendering.id)?.map((l) => l.layer)
)
)
}
}
// Filters
if (this.filters.length > 0) {
add(MarkdownUtils.title(2, new Translation({
en: "Filters",
nl: "Filters"
})))
add(
MarkdownUtils.title(
2,
new Translation({
en: "Filters",
nl: "Filters",
})
)
)
for (const filter of this.filters) {
add(filter.generateDocs({lang}))
add(filter.generateDocs({ lang }))
}
}
return (Lists.noEmpty(Lists.noNull(paragraphs).map(p => typeof p === "string" ? p : p.textFor(lang)))).join("\n\n")
return Lists.noEmpty(
Lists.noNull(paragraphs).map((p) => (typeof p === "string" ? p : p.textFor(lang)))
).join("\n\n")
}
public CustomCodeSnippets(): string[] {

View file

@ -678,7 +678,7 @@ export default class TagRenderingConfig {
} catch (e) {
return undefined
}
}) ?? [],
}) ?? []
)
if (values.length === 0) {
return
@ -967,15 +967,22 @@ export default class TagRenderingConfig {
}
}
generateDocumentation(currentLayerId: string, lang: string = "en", usedInLayers?: string[]): string {
let paragraphs: (Translation | string) [] = ["### " + this.id,
this.description]
generateDocumentation(
currentLayerId: string,
lang: string = "en",
usedInLayers?: string[]
): string {
let paragraphs: (Translation | string)[] = ["### " + this.id, this.description]
if (this.question === undefined) {
paragraphs.push(MarkdownUtils.quote(new Translation({
en: "_This tagrendering has no question and is thus read-only_",
nl: "_Deze tagRendering heeft geen vraag en wordt dus enkel weergegeven_",
})))
paragraphs.push(
MarkdownUtils.quote(
new Translation({
en: "_This tagrendering has no question and is thus read-only_",
nl: "_Deze tagRendering heeft geen vraag en wordt dus enkel weergegeven_",
})
)
)
} else {
paragraphs.push(MarkdownUtils.quote(this.question))
}
@ -983,33 +990,39 @@ export default class TagRenderingConfig {
if (this.render) {
if (this.freeform?.key) {
const render = "*" + this.render.textFor(lang) + "*"
paragraphs.push(new TypedTranslation<{ render, key }>({
en: "*{render}* is shown if `{key}` is exists in the object",
nl: "*{render}* wordt getoond indien het attribuut `{key}` bestaat",
}).Subs({ render, key: this.freeform.key }))
paragraphs.push(
new TypedTranslation<{ render; key }>({
en: "*{render}* is shown if `{key}` is exists in the object",
nl: "*{render}* wordt getoond indien het attribuut `{key}` bestaat",
}).Subs({ render, key: this.freeform.key })
)
} else {
paragraphs.push(this.render)
}
if (this.question && this.freeform.range) {
let paragraph: (string)[] = [
let paragraph: string[] = [
new TypedTranslation<{ type?: string }>({
en: "The allowed input is of type {type??string}",
nl: "Input moet van het type {type} zijn",
}).Subs(this.freeform).textFor(lang),
})
.Subs(this.freeform)
.textFor(lang),
]
if (this.freeform.range) {
paragraph.push(
new TypedTranslation(
{
en: "Values must be between {min??negative infinity} and {max??infinity} (both inclusive)",
nl: "Waardes moeten tussen {min??min oneindig} en {max??oneindig} vallen (beiden inclusief)",
},
).Subs(this.freeform.range).textFor(lang),
new TypedTranslation({
en: "Values must be between {min??negative infinity} and {max??infinity} (both inclusive)",
nl: "Waardes moeten tussen {min??min oneindig} en {max??oneindig} vallen (beiden inclusief)",
})
.Subs(this.freeform.range)
.textFor(lang)
)
const r = this.freeform.range
if (r.warnAbove && r.warnBelow) {
paragraph.push(`A warning will appear if the value is outside of ${r.warnBelow} and ${r.warnAbove}.`)
paragraph.push(
`A warning will appear if the value is outside of ${r.warnBelow} and ${r.warnAbove}.`
)
} else if (r.warnBelow) {
paragraph.push(`A warning will appear below ${r.warnBelow}.`)
} else if (r.warnAbove) {
@ -1021,44 +1034,51 @@ export default class TagRenderingConfig {
}
if (this.mappings !== undefined) {
paragraphs.push(MarkdownUtils.list(
this.mappings.map((m) => {
let icon = ""
if (m.icon?.indexOf(";") < 0) {
if (Strings.isEmoji(m.icon)) {
icon = m.icon
} else {
icon = "<img width='38px' height='38px' src='https://dev.mapcomplete.org/" +
m.icon +
"'>"
paragraphs.push(
MarkdownUtils.list(
this.mappings.map((m) => {
let icon = ""
if (m.icon?.indexOf(";") < 0) {
if (Strings.isEmoji(m.icon)) {
icon = m.icon
} else {
icon =
"<img width='38px' height='38px' src='https://dev.mapcomplete.org/" +
m.icon +
"'>"
}
}
}
const msgs: Translation[] = [
new TypedTranslation<{ icon, then, cond }>({
en: "{icon} *{then}* is shown if {cond}",
nl: "{icon} *{then}* wordt getoond als {cond}",
}).Subs({ icon, then: m.then.textFor(lang), cond: m.if.asHumanString(true) }),
]
const msgs: Translation[] = [
new TypedTranslation<{ icon; then; cond }>({
en: "{icon} *{then}* is shown if {cond}",
nl: "{icon} *{then}* wordt getoond als {cond}",
}).Subs({
icon,
then: m.then.textFor(lang),
cond: m.if.asHumanString(true),
}),
]
if (m.hideInAnswer === true) {
msgs.push(new Translation(
{
en: "_This option cannot be chosen as answer_",
nl: "_Deze optie kan niet als antwoord gekozen worden_",
}))
}
if (m.ifnot !== undefined) {
msgs.push(
new TypedTranslation<{ ifnot }>({
en: "If _not_ selected when answering, {ifnot} will be added",
nl: "Indien _niet_ geselecteerd bij het antwoorden, zal {ifnot} toegevoegd worden",
}).Subs({ ifnot: m.ifnot.asHumanString(true) }),
)
}
return msgs.map(tr => tr.textFor(lang)).join(". ")
})
))
if (m.hideInAnswer === true) {
msgs.push(
new Translation({
en: "_This option cannot be chosen as answer_",
nl: "_Deze optie kan niet als antwoord gekozen worden_",
})
)
}
if (m.ifnot !== undefined) {
msgs.push(
new TypedTranslation<{ ifnot }>({
en: "If _not_ selected when answering, {ifnot} will be added",
nl: "Indien _niet_ geselecteerd bij het antwoorden, zal {ifnot} toegevoegd worden",
}).Subs({ ifnot: m.ifnot.asHumanString(true) })
)
}
return msgs.map((tr) => tr.textFor(lang)).join(". ")
})
)
)
}
if (this.condition !== undefined && !this.condition?.matchesProperties({})) {
@ -1067,12 +1087,12 @@ export default class TagRenderingConfig {
false,
{}
)
paragraphs.push(new TypedTranslation<{ conditionAsLink }>(
{
paragraphs.push(
new TypedTranslation<{ conditionAsLink }>({
en: "This tagRendering is only visible in the information panel if the condition *{conditionAsLink}* is met",
nl: "Deze tagRendering is enkel zichtbaar in het informatiepaneel indien de voorwaarde *{conditionAsLink}* vervuld is",
},
).Subs({ conditionAsLink }))
}).Subs({ conditionAsLink })
)
}
if (this.invalidValues) {
@ -1080,18 +1100,18 @@ export default class TagRenderingConfig {
new Translation({
en: "This tagRendering has some values that are not valid and cannot be entered. The following values are _not_ accepted:",
nl: "Deze tagRendering heeft waardes die als ongeldig beschouwd worden en _niet_ ingevoerd kunnen worden:",
}),
)
paragraphs.push(
"❌ " + this.invalidValues.asHumanString(true),
})
)
paragraphs.push("❌ " + this.invalidValues.asHumanString(true))
}
if (this.labels?.length > 0) {
paragraphs.push(new Translation({
en: "This tagRendering has the following labels:",
nl: "Deze tagRendering heeft de volgende labels:",
}))
paragraphs.push(
new Translation({
en: "This tagRendering has the following labels:",
nl: "Deze tagRendering heeft de volgende labels:",
})
)
paragraphs.push(MarkdownUtils.list(this.labels.map((label) => "`" + label + "`")))
}
if (usedInLayers?.length > 0) {
@ -1099,32 +1119,31 @@ export default class TagRenderingConfig {
new TypedTranslation<{ length }>({
en: "This tagrendering is reused in {length} other layers:",
nl: "Deze tagRendering wordt ook hergebruikt in {length} andere lagen:",
}).Subs(usedInLayers),
}).Subs(usedInLayers)
)
paragraphs.push(
MarkdownUtils.list(usedInLayers.map((l) => `[${l}](../Docs/Layers/${l}.md)`)),
MarkdownUtils.list(usedInLayers.map((l) => `[${l}](../Docs/Layers/${l}.md)`))
)
}
if (this._definedIn) {
const [layer, id] = this._definedIn
if (this.id === id && currentLayerId === id) {
// pass
// It is defined right here
} else {
paragraphs.push(new TypedTranslation(
{
paragraphs.push(
new TypedTranslation({
en: "Originally defined in [{layer}](../Docs/Layers/{layer}.md#{id})",
nl: "Oorspronkelijk gedefinieerd in [`{layer}`](../Docs/Layers/{layer}.md#{id})",
},
).Subs({ layer, id }))
}).Subs({ layer, id })
)
}
}
return Lists.noNull(paragraphs).map(tr => typeof tr === "string" ? tr : tr.textFor(lang)).join("\n\n")
return Lists.noNull(paragraphs)
.map((tr) => (typeof tr === "string" ? tr : tr.textFor(lang)))
.join("\n\n")
}
public usedTags(): TagsFilter[] {

View file

@ -21,10 +21,10 @@
let tags = state.featureProperties.getStore(selected.properties.id)
export let absolute = true
let layerCorrect = tags.map(properties => state.getMatchingLayer(properties))
let layerCorrect = tags.map((properties) => state.getMatchingLayer(properties))
let layer: LayerConfig
layerCorrect.addCallbackAndRun(l => {
layerCorrect.addCallbackAndRun((l) => {
if (layer) {
layer = undefined
window.setTimeout(() => {
@ -33,19 +33,29 @@
} else {
layer = l
}
})
</script>
{#if !layer || $tags._deleted === "yes"}
<div class="normal-background flex h-full w-full flex-col justify-center items-center " class:absolute>
<div
class="normal-background flex h-full w-full flex-col items-center justify-center"
class:absolute
>
{#if $layerCorrect === undefined || $tags._deleted === "yes"}
<div aria-live="assertive" class="alert flex justify-center self-stretch gap-x-4 items-center">
<div
aria-live="assertive"
class="alert flex items-center justify-center gap-x-4 self-stretch"
>
<Delete_icon class="m-2 h-8 w-8" />
<Tr t={Translations.t.delete.isDeleted} />
</div>
<a href={`${$tags._backend ?? "https://openstreetmap.org"}/${$tags.id}`} target="_blank">{$tags.id}</a>
<BackButton clss="self-stretch mt-4" on:click={() => state.selectedElement.setData(undefined)}>
<a href={`${$tags._backend ?? "https://openstreetmap.org"}/${$tags.id}`} target="_blank">
{$tags.id}
</a>
<BackButton
clss="self-stretch mt-4"
on:click={() => state.selectedElement.setData(undefined)}
>
<Tr t={Translations.t.general.returnToTheMap} />
</BackButton>
{:else}
@ -55,7 +65,7 @@
{:else}
<div class="normal-background flex h-full w-full flex-col" class:absolute>
<InsetSpacer clss="low-interaction" height={AndroidPolyfill.getInsetSizes().top} />
<SelectedElementTitle {state} layer={layer} selectedElement={selected} />
<SelectedElementView {state} layer={layer} selectedElement={selected} />
<SelectedElementTitle {state} {layer} selectedElement={selected} />
<SelectedElementView {state} {layer} selectedElement={selected} />
</div>
{/if}

View file

@ -2,7 +2,7 @@
export let clss = ""
</script>
<div class={"sidebar-unit "+clss}>
<div class={"sidebar-unit " + clss}>
<slot />
</div>

View file

@ -66,9 +66,15 @@ export default class TableOfContents {
}
const heading = Utils.times(() => "#", firstTitle.depth)
toc = heading +" " + new Translation({
en: "Table of contents",nl: "Inhoudsopgave"
}).textFor(lang) + "\n\n" + toc
toc =
heading +
" " +
new Translation({
en: "Table of contents",
nl: "Inhoudsopgave",
}).textFor(lang) +
"\n\n" +
toc
const firstTitleIndex = md.indexOf(firstTitle.title)

View file

@ -24,7 +24,7 @@
})
let mapRotation = mapProperties.rotation
let showHint = new UIEventSource(false)
showHint.stabilized(4000).addCallback(isShown => {
showHint.stabilized(4000).addCallback((isShown) => {
if (isShown) {
showHint.set(false)
}
@ -51,19 +51,23 @@
}
}
let orientationText = mapProperties.rotation.mapD(r => {
let orientationText = mapProperties.rotation.mapD((r) => {
const key = GeoOperations.bearingToHuman(r)
return Translations.t.compass[key]
})
let deviceOrientation = Orientation.singleton.alpha
</script>
{#if $allowRotation || $gotNonZero}
<button class={"as-link pointer-events-auto relative " + size} on:click={() => clicked()}
on:mouseenter={() => hovered.set(true)} on:mouseleave={() => hovered.set(false)}
on:focus={() => focused.set(true)} on:blur={() => focused.set(false)}>
<button
class={"as-link pointer-events-auto relative " + size}
on:click={() => clicked()}
on:mouseenter={() => hovered.set(true)}
on:mouseleave={() => hovered.set(false)}
on:focus={() => focused.set(true)}
on:blur={() => focused.set(false)}
>
{#if $allowRotation && !$compassLoaded && !$gotNonZero}
<div
class={"rounded-full border-2 border-dotted border-gray-500 " + wrapperClass}

View file

@ -59,8 +59,6 @@
<div class:h-0={!onlyLink} class:h-full={onlyLink} class="overflow-hidden">
<MenuDrawerIndex {state} {onlyLink}>
<svelte:fragment slot="offline-management">
<Page {onlyLink} shown={pg.manageOffline} fullscreen>
<svelte:fragment slot="header">
@ -118,9 +116,7 @@
{#if theme.official}
<a
class="flex"
href={"https://docs.mapcomplete.org/#/Themes/" +
theme.id +
".md"}
href={"https://docs.mapcomplete.org/#/Themes/" + theme.id + ".md"}
target="_blank"
>
<DocumentMagnifyingGlass class="h-6 w-6" />

View file

@ -205,7 +205,7 @@
{/if}
</LoginToggle>
<slot name="offline-management"/>
<slot name="offline-management" />
<LanguagePicker
preferredLanguages={state.userRelatedState.osmConnection.userDetails.mapD(

View file

@ -31,7 +31,7 @@
let focusZ = Math.max(...Object.keys(OfflineBasemapManager.zoomelevels).map(Number))
let map: UIEventSource<MlMap> = new UIEventSource(undefined)
let mapProperties: MapProperties = new MapLibreAdaptor(map)
if(state?.showCurrentLocationOn){
if (state?.showCurrentLocationOn) {
state?.showCurrentLocationOn(map)
}
mapProperties.maxzoom.set(focusZ - 1)

View file

@ -64,27 +64,27 @@
)
</script>
<div
class="selected-element-view flex h-full w-full flex-col gap-y-1 overflow-y-auto"
class:p1={!$isAddNew}
class:px-4={!$isAddNew}
tabindex="-1"
>
{#each $knownTagRenderings as config (config.id)}
<TagRenderingEditableDynamic
{tags}
{config}
{state}
{selectedElement}
{layer}
{highlightedRendering}
clss={($knownTagRenderings.length === 1
? "h-full"
: "tr-length-" + $knownTagRenderings.length) +
" " +
config.classes.join(" ")}
/>
{/each}
<div
class="selected-element-view flex h-full w-full flex-col gap-y-1 overflow-y-auto"
class:p1={!$isAddNew}
class:px-4={!$isAddNew}
tabindex="-1"
>
{#each $knownTagRenderings as config (config.id)}
<TagRenderingEditableDynamic
{tags}
{config}
{state}
{selectedElement}
{layer}
{highlightedRendering}
clss={($knownTagRenderings.length === 1
? "h-full"
: "tr-length-" + $knownTagRenderings.length) +
" " +
config.classes.join(" ")}
/>
{/each}
<InsetSpacer height={AndroidPolyfill.getInsetSizes().bottom} />
</div>
<InsetSpacer height={AndroidPolyfill.getInsetSizes().bottom} />
</div>

View file

@ -23,18 +23,17 @@
wd.thursday,
wd.friday,
wd.saturday,
wd.sunday
wd.sunday,
]
function addTime(startMinutesDiff: number, endMinutesDiff: number) {
const oldOh: OpeningHour[] = value.data?.filter(r => r !== range) ?? []
const oldOh: OpeningHour[] = value.data?.filter((r) => r !== range) ?? []
const newOh: OpeningHour = OpeningHours.canonicalize(
{
...range,
startMinutes: range.startMinutes + startMinutesDiff,
endMinutes: range.endMinutes + endMinutesDiff
})
const newOh: OpeningHour = OpeningHours.canonicalize({
...range,
startMinutes: range.startMinutes + startMinutesDiff,
endMinutes: range.endMinutes + endMinutesDiff,
})
value.set(OpeningHours.MergeTimes([...oldOh, newOh]))
}
@ -48,22 +47,23 @@
{#if $editMode}
<div
style="min-height: fit-content; height: 100%; z-index: 100"
class="pointer-events-auto border-interactive low-interaction flex items-center flex-col justify-between rounded-xl w-fit">
<div class="flex justify-between interactive">
class="border-interactive low-interaction pointer-events-auto flex w-fit flex-col items-center justify-between rounded-xl"
>
<div class="interactive flex justify-between">
{#if range.startHour !== 0 || range.startMinutes !== 0}
<button class="text-sm "
style="padding: 0.15rem; margin: 0"
on:click={() => addTime( - 15, 0)}>
<button
class="text-sm"
style="padding: 0.15rem; margin: 0"
on:click={() => addTime(-15, 0)}
>
-15
</button>
{/if}
<span class="font-bold">
{OpeningHours.hhmm(range.startHour, range.startMinutes)}
{OpeningHours.hhmm(range.startHour, range.startMinutes)}
</span>
{#if range.endHour - range.startHour > 1}
<button class="text-sm "
style="padding: 0.15rem; margin: 0"
on:click={() => addTime(15, 0)}>
<button class="text-sm" style="padding: 0.15rem; margin: 0" on:click={() => addTime(15, 0)}>
+15
</button>
{/if}
@ -76,43 +76,47 @@
<span class="font-bold">
<Tr t={days[range.weekday]} />
</span>
<button class=" w-fit p-1" on:click={() =>onDelete()}>
<button class=" w-fit p-1" on:click={() => onDelete()}>
<TrashIcon class="h-6 w-6" style="color: red" />
</button>
<div class="flex justify-between font-normal items-end interactive">
<div class="interactive flex items-end justify-between font-normal">
{#if range.endHour - range.startHour > 1}
<button class="text-sm "
style="padding: 0.15rem; margin: 0"
on:click={() => addTime(0, -15)}>
<button
class="text-sm"
style="padding: 0.15rem; margin: 0"
on:click={() => addTime(0, -15)}
>
-15
</button>
{/if}
<span class="font-bold">
{OpeningHours.hhmm(range.endHour, range.endMinutes)}
{OpeningHours.hhmm(range.endHour, range.endMinutes)}
</span>
{#if range.endHour != 24}
<button class="text-sm "
style="padding: 0.15rem; margin: 0"
on:click={() => addTime(0, + 15)}>
<button
class="text-sm"
style="padding: 0.15rem; margin: 0"
on:click={() => addTime(0, +15)}
>
+15
</button>
{/if}
</div>
</div>
{:else}
<div class="border-interactive low-interaction flex items-center h-full flex-col justify-between rounded-xl">
<div
class="border-interactive low-interaction flex h-full flex-col items-center justify-between rounded-xl"
>
{#if range.endHour - range.startHour >= 3}
{OpeningHours.hhmm(range.startHour, range.startMinutes)}
{/if}
<button
class=" w-fit self-center p-1 pointer-events-auto"
on:click={() => {editMode.set(true)}}
class=" pointer-events-auto w-fit self-center p-1"
on:click={() => {
editMode.set(true)
}}
>
<PencilIcon class="h-6 w-6" />
</button>
@ -121,5 +125,4 @@
{OpeningHours.hhmm(range.endHour, range.endMinutes)}
{/if}
</div>
{/if}

View file

@ -131,7 +131,6 @@
clearSelection()
}
let lasttouched: [number, number] = undefined
function moved(weekday: number, hour: number) {
@ -212,7 +211,7 @@
<!-- Virtual row to add the ranges to-->
<td style="width: 9%" />
{#each range(7) as wd}
<td style={"width: 13%; position: relative; z-index: "+(7 - wd)}>
<td style={"width: 13%; position: relative; z-index: " + (7 - wd)}>
<div class="pointer-events-none h-0" style="z-index: 10">
{#each ($value ?? [])
.filter((oh) => oh.weekday === wd)

View file

@ -38,14 +38,14 @@
async function apply() {
const maproulette_id = tags.data[maproulette_id_key] ?? tags.data.mr_taskId ?? tags.data.id
try {
const statusIndex: number = Maproulette.codeToIndex(""+statusToSet) ?? Number(statusToSet)
if(statusIndex !== 0){
const statusIndex: number = Maproulette.codeToIndex("" + statusToSet) ?? Number(statusToSet)
if (statusIndex !== 0) {
await Maproulette.singleton.closeTask(Number(maproulette_id), statusIndex, state, {
comment: feedback,
})
}
tags.data["mr_taskStatus"] = maprouletteStatus[statusIndex]
tags.data.status = ""+statusToSet
tags.data.status = "" + statusToSet
tags.ping()
} catch (e) {
console.error(e)
@ -61,7 +61,7 @@
<Loading>
<Tr t={Translations.t.general.loading} />
</Loading>
{:else if $status !== statusToSet && ($status === Maproulette.STATUS_OPEN || statusToSet === Maproulette.STATUS_OPEN)}
{:else if $status !== statusToSet && ($status === Maproulette.STATUS_OPEN || statusToSet === Maproulette.STATUS_OPEN)}
{#if askFeedback !== "" && askFeedback !== undefined}
<div class="interactive flex flex-col gap-y-1 border border-dashed border-gray-500 p-1">
<h3>{askFeedback}</h3>

View file

@ -1089,7 +1089,7 @@ changes // => [[36000,61200], ["10:00", "17:00"]]
startHour: startHour + Math.floor(startMinutes / 60),
startMinutes: startMinutes % 60,
endHour: endHour + Math.floor(endMinutes / 60),
endMinutes: endMinutes % 60
endMinutes: endMinutes % 60,
}
}
}

View file

@ -12,7 +12,6 @@
import Translations from "../i18n/Translations"
import WikidatapreviewWithLoading from "../Wikipedia/WikidatapreviewWithLoading.svelte"
const dispatch = createEventDispatcher<{ selected: string /* wikidata-id*/ }>()
const t = Translations.t.plantDetection
@ -26,11 +25,11 @@
let wikidata = UIEventSource.fromPromise(
Wikidata.Sparql<{ species }>(
["?species", "?speciesLabel"],
["?species wdt:P846 \"" + species.gbif.id + "\""],
),
['?species wdt:P846 "' + species.gbif.id + '"']
)
)
let fallback = wikidata.bindD(wd => {
let fallback = wikidata.bindD((wd) => {
const normalQuery = wd[0]?.species?.value
if (normalQuery) {
return undefined
@ -38,26 +37,26 @@
return UIEventSource.fromPromise(
Wikidata.Sparql<{ species }>(
["?species", "?speciesLabel"],
["?species wdt:P225 \"" + species.species.scientificNameWithoutAuthor + "\""],
),
['?species wdt:P225 "' + species.species.scientificNameWithoutAuthor + '"']
)
)
})
/**
* This will contain the id
*/
let wikidataDirectId: Store<string> = wikidata.mapD((wd) => wd[0]?.species?.value)
let fallbackId = fallback.mapD(wd => wd[0]?.species?.value)
let fallbackId = fallback.mapD((wd) => wd[0]?.species?.value)
let wikidataId = wikidataDirectId.map(id => id ?? fallbackId.data, [fallbackId])
let wikidataId = wikidataDirectId.map((id) => id ?? fallbackId.data, [fallbackId])
</script>
{#if $wikidataId === undefined && $wikidata !== undefined && $fallback !== undefined}
<!-- We got a result from wikidata, but that result contained nothing -> there is no matching species found -->
<div class="subtle interactive p-4 m-2">
No wikidata-match found for {species.species.scientificNameWithoutAuthor} (GBIF {species.gbif.id}
<div class="subtle interactive m-2 p-4">
No wikidata-match found for {species.species.scientificNameWithoutAuthor} (GBIF {species.gbif
.id}
, {Math.round(species.score * 100)}% match)
</div>
{:else}
@ -66,8 +65,8 @@
<Loading>
<Tr
t={t.loadingWikidata.Subs({
species: species.species.scientificNameWithoutAuthor,
})}
species: species.species.scientificNameWithoutAuthor,
})}
/>
</Loading>
{:else}

View file

@ -182,7 +182,6 @@
<Tr t={t.confirm} />
</NextButton>
</div>
</div>
{/if}
</Popup>

View file

@ -46,8 +46,7 @@
}
}
let removesFromLayer: Store<false | true | "layer-change"> = tags.mapD(tags => {
let removesFromLayer: Store<false | true | "layer-change"> = tags.mapD((tags) => {
const tagsIfApplied = new And(Lists.noNull([mapping.if, ...(mapping?.addExtraTags ?? [])]))
const newTags = tagsIfApplied.applyOn(tags)
if (layer.source.osmTags.matchesProperties(newTags)) {
@ -57,7 +56,6 @@
return "layer-change"
}
return true
})
let showDelete = false
@ -115,19 +113,18 @@
{#if $matchesTerm && !$mappingIsHidden}
<label class:checked={mappingIsSelected} class={"flex gap-x-1"}>
<slot />
<div class="flex justify-between items-center w-full">
<div class="flex w-full items-center justify-between">
<TagRenderingMapping {mapping} {tags} {state} {selectedElement} {layer} />
{#if $removesFromLayer === true}
<TrashIcon class="w-6 shrink-0 ml-2" />
<TrashIcon class="ml-2 w-6 shrink-0" />
<Popover bind:open={showDelete}>
<div class="w-80">
<Tr t={Translations.t.delete.mappingDeleteExplanation} />
</div>
</Popover>
{:else if $removesFromLayer === "layer-change"}
<Square3Stack3dIcon class="w-6 subtle" />
<Square3Stack3dIcon class="subtle w-6" />
<Popover bind:open={showDelete}>
<div class="w-80">
<Tr t={Translations.t.delete.mappingChangeLayer} />

View file

@ -324,35 +324,33 @@
let disabledMappings: boolean[] = []
$: {
if(config.multiAnswer){
for (let i = 0; i < config.mappings.length; i++){
if(disabledMappings[i] === undefined){
if (config.multiAnswer) {
for (let i = 0; i < config.mappings.length; i++) {
if (disabledMappings[i] === undefined) {
disabledMappings.push(false)
}
disabledMappings[i] = false
if(checkedMappings[i]){
if (checkedMappings[i]) {
continue
}
const fakeCheckedMappings = [ ...checkedMappings]
const fakeCheckedMappings = [...checkedMappings]
fakeCheckedMappings[i] = true
try{
const ifMappingSelected = config.constructChangeSpecification(
$freeformInput,
selectedMapping,
fakeCheckedMappings,
$tags,
unit
)
if(ifMappingSelected === undefined){
try {
const ifMappingSelected = config.constructChangeSpecification(
$freeformInput,
selectedMapping,
fakeCheckedMappings,
$tags,
unit
)
if (ifMappingSelected === undefined) {
disabledMappings[i] = true
}
}catch (e) {
} catch (e) {
disabledMappings[i] = true
}
}
}
}
let featureSwitchIsTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false)

View file

@ -49,7 +49,11 @@
<div class="low-interaction flex flex-col gap-y-2 p-4">
{#if $allowFilters}
<ActiveFilters clss={"flex-shrink "+ ($searchTerm.length > 0 ? "max-h-[30vh]" : "")} {state} activeFilters={$activeFilters} />
<ActiveFilters
clss={"flex-shrink " + ($searchTerm.length > 0 ? "max-h-[30vh]" : "")}
{state}
activeFilters={$activeFilters}
/>
{/if}
{#if $searchTerm.length === 0 && $activeFilters.length === 0}
<div class="items-center p-8 text-center">

View file

@ -88,7 +88,7 @@ class MaprouletteSetStatusVis extends SpecialVisualizationSvelte {
if (maproulette_id_key === "" || maproulette_id_key === undefined) {
maproulette_id_key = "mr_taskId"
}
if(statusToSet === ""){
if (statusToSet === "") {
statusToSet = "1"
}
return new SvelteUIElement(MaprouletteSetStatus, {

View file

@ -24,10 +24,10 @@
const t = Translations.t.general.apply_button
export let maprouletteIdKey: string
// THis button might be shown on MapRoulette-items, which might already have been applied
// This will default to 'false' for non-maproulette challenges
let isMaprouletteAndApplied = tags?.data?.["mr_taskStatus"] !== undefined && tags?.data?.["mr_taskStatus"] !== "Created"
let isMaprouletteAndApplied =
tags?.data?.["mr_taskStatus"] !== undefined && tags?.data?.["mr_taskStatus"] !== "Created"
let currentState: UIEventSource<"init" | "applying" | "applied"> = new UIEventSource(
isMaprouletteAndApplied ? "applied" : "init"
@ -35,10 +35,13 @@
async function apply() {
currentState.set("applying")
window.requestIdleCallback(async () => {
await onApply()
currentState.set("applied")
}, {timeout: 2000})
window.requestIdleCallback(
async () => {
await onApply()
currentState.set("applied")
},
{ timeout: 2000 }
)
}
</script>

View file

@ -68,7 +68,7 @@ export default class TagApplyViz extends SpecialVisualization implements AutoAct
const properties = JSON.parse(spec)
tgsSpec = []
for (const key of Object.keys(properties)) {
tgsSpec.push([key, ""+ properties[key]])
tgsSpec.push([key, "" + properties[key]])
}
} else {
tgsSpec = TagApplyViz.parseTagSpec(spec)
@ -200,7 +200,7 @@ export default class TagApplyViz extends SpecialVisualization implements AutoAct
image,
targetIdKey,
onApply,
maprouletteIdKey: maprouletteId
maprouletteIdKey: maprouletteId,
})
}
}

View file

@ -157,9 +157,9 @@ class Multi extends SpecialVisualization {
const elements = []
for (const properties of propertiess) {
const propertiesWithOriginal = {...properties}
const propertiesWithOriginal = { ...properties }
for (const k in tags) {
propertiesWithOriginal["_original:"+k]=tags[k]
propertiesWithOriginal["_original:" + k] = tags[k]
}
const subsTr = new SvelteUIElement(SpecialTranslation, {
t: translation,

View file

@ -228,12 +228,12 @@
})
}
{
// https://cache.mapcomplete.org/0-0-0.pmtiles
const s = Constants.pmtiles_host+"/0-0-0.pmtiles"
// https://cache.mapcomplete.org/0-0-0.pmtiles
const s = Constants.pmtiles_host + "/0-0-0.pmtiles"
const status = testDownload(s, true)
services.push({
name: s+" (PMTiles clusters)",
name: s + " (PMTiles clusters)",
status: status.mapD((s) => {
if (s["error"]) {
return "offline"

View file

@ -46,11 +46,11 @@ export default class MarkdownUtils {
}
static quote(translation: Translation) {
return translation.OnEveryLanguage(s => "> "+s)
return translation.OnEveryLanguage((s) => "> " + s)
}
static title(number: number, translation: Translation) {
const t = Utils.times(() => "#", number)+" "
return translation.OnEveryLanguage(l => t+ l)
const t = Utils.times(() => "#", number) + " "
return translation.OnEveryLanguage((l) => t + l)
}
}

View file

@ -1,7 +1,7 @@
{
"contributors": [
{
"commits": 10552,
"commits": 10661,
"contributor": "Pieter Vander Vennet"
},
{
@ -9,12 +9,12 @@
"contributor": "Robin van der Linde"
},
{
"commits": 55,
"contributor": "Tobias"
"commits": 57,
"contributor": "Osmwithspace"
},
{
"commits": 45,
"contributor": "Osmwithspace"
"commits": 55,
"contributor": "Tobias"
},
{
"commits": 45,

View file

@ -276,8 +276,8 @@
"he"
],
"IN": [
"en",
"hi"
"hi",
"en"
],
"IQ": [
"ar",

File diff suppressed because it is too large Load diff

View file

@ -10950,6 +10950,10 @@
"if": "value=extinguisher",
"then": "extinguisher - Map layer to show fire extinguishers."
},
{
"if": "value=farming_equipment_cooperative",
"then": "farming_equipment_cooperative - This layer is to map cooperative for the use of agricultural equipment"
},
{
"if": "value=fire_station",
"then": "fire_station - Map layer to show fire stations."

View file

@ -829,6 +829,10 @@
"if": "value=extinguisher",
"then": "<b>extinguisher</b> (builtin) - Map layer to show fire extinguishers."
},
{
"if": "value=farming_equipment_cooperative",
"then": "<b>farming_equipment_cooperative</b> (builtin) - This layer is to map cooperative for the use of agricultural equipment"
},
{
"if": "value=fire_station",
"then": "<b>fire_station</b> (builtin) - Map layer to show fire stations."
@ -13695,6 +13699,10 @@
"if": "value=extinguisher",
"then": "extinguisher - Map layer to show fire extinguishers."
},
{
"if": "value=farming_equipment_cooperative",
"then": "farming_equipment_cooperative - This layer is to map cooperative for the use of agricultural equipment"
},
{
"if": "value=fire_station",
"then": "fire_station - Map layer to show fire stations."
@ -35645,6 +35653,10 @@
"if": "value=extinguisher",
"then": "extinguisher - Map layer to show fire extinguishers."
},
{
"if": "value=farming_equipment_cooperative",
"then": "farming_equipment_cooperative - This layer is to map cooperative for the use of agricultural equipment"
},
{
"if": "value=fire_station",
"then": "fire_station - Map layer to show fire stations."

View file

@ -17,11 +17,11 @@
"contributor": "paunofu"
},
{
"commits": 134,
"commits": 135,
"contributor": "mcliquid"
},
{
"commits": 103,
"commits": 105,
"contributor": "mike140"
},
{
@ -61,7 +61,7 @@
"contributor": "Babos Gábor"
},
{
"commits": 43,
"commits": 44,
"contributor": "Lukáš Jelínek"
},
{
@ -76,6 +76,10 @@
"commits": 34,
"contributor": "oxisol"
},
{
"commits": 32,
"contributor": "Weblate Admin"
},
{
"commits": 29,
"contributor": "Artem"
@ -84,10 +88,6 @@
"commits": 26,
"contributor": "Reza Almanda"
},
{
"commits": 25,
"contributor": "Weblate Admin"
},
{
"commits": 25,
"contributor": "SC"
@ -160,6 +160,10 @@
"commits": 13,
"contributor": "Joost"
},
{
"commits": 12,
"contributor": "Severin Menard"
},
{
"commits": 12,
"contributor": "hugoalh"
@ -168,10 +172,6 @@
"commits": 12,
"contributor": "Piotr Strebski"
},
{
"commits": 11,
"contributor": "Severin Menard"
},
{
"commits": 11,
"contributor": "Manuel Tassi"
@ -212,6 +212,10 @@
"commits": 10,
"contributor": "Irina"
},
{
"commits": 9,
"contributor": "opensourceit"
},
{
"commits": 9,
"contributor": "Krzysztof Chorzempa"
@ -268,6 +272,10 @@
"commits": 7,
"contributor": "Niels Elgaard Larsen"
},
{
"commits": 6,
"contributor": "Phoenix"
},
{
"commits": 6,
"contributor": "Juele juele"
@ -364,6 +372,10 @@
"commits": 5,
"contributor": "Alexey Shabanov"
},
{
"commits": 4,
"contributor": "Patchanka"
},
{
"commits": 4,
"contributor": "ceirios"
@ -486,11 +498,7 @@
},
{
"commits": 2,
"contributor": "Patchanka"
},
{
"commits": 2,
"contributor": "Phoenix"
"contributor": "Smarzaro"
},
{
"commits": 2,
@ -636,6 +644,14 @@
"commits": 2,
"contributor": "Leo Alcaraz"
},
{
"commits": 1,
"contributor": "tuxayo"
},
{
"commits": 1,
"contributor": "Pauwel02"
},
{
"commits": 1,
"contributor": "POG"

View file

@ -1,5 +1,5 @@
export class SWGenerated {
// generated by scripts/prepareServiceWorker.ts
static vNumber = "0.56.1"
static buildTime = 1758362649147
static vNumber = "0.56.3"
static buildTime = 1760184129032
}