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", "id": "bench-view",
"render": { "render": {
"special": { "special": {
"type": "group",
"header": "bench-view-title", "header": "bench-view-title",
"labels": "bench-view-content" "labels": "bench-view-content",
"type": "group"
} }
} }
}, },
@ -1116,12 +1116,13 @@
], ],
"render": { "render": {
"special": { "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": [ "labels": [
"hidden", "hidden",
"bench-view-content" "bench-view-content"

View file

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

View file

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

View file

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

View file

@ -66,16 +66,16 @@
], ],
"tagRenderings": [ "tagRenderings": [
{ {
"condition": "level=country",
"description": "The name of the country",
"id": "country_name", "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", "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": [ "filter": [

View file

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

View file

@ -93,7 +93,6 @@
"geoJsonZoomLevel": 12, "geoJsonZoomLevel": 12,
"#": "The 'staging'-url is a test maproulette", "#": "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://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}" "geoJson": "https://maproulette.org/api/v2/challenge/view/53358?bbox={x_min},{y_max},{x_max},{y_min}"
}, },
"calculatedTags": [ "calculatedTags": [
@ -140,9 +139,9 @@
"condition": "_osm_poi_with_this_ref!=[]", "condition": "_osm_poi_with_this_ref!=[]",
"render": { "render": {
"special": { "special": {
"type": "multi",
"key": "_osm_poi_with_this_ref", "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進行的變動" "zh_Hant": "顯示由MapComplete進行的變動"
}, },
"icon": "./assets/svg/logo.svg", "icon": "./assets/svg/logo.svg",
"startZoom": 1,
"startLat": 0,
"startLon": 0,
"hideFromOverview": true, "hideFromOverview": true,
"layers": [ "layers": [
{ {
@ -429,6 +426,10 @@
"if": "theme=facadegardens", "if": "theme=facadegardens",
"then": "./assets/themes/facadegardens/geveltuin.svg" "then": "./assets/themes/facadegardens/geveltuin.svg"
}, },
{
"if": "theme=farming_equipment_cooperatives",
"then": "./assets/layers/farming_equipment_cooperative/tractor.svg"
},
{ {
"if": "theme=fireplace", "if": "theme=fireplace",
"then": "./assets/layers/assembly_point/fire.svg" "then": "./assets/layers/assembly_point/fire.svg"

View file

@ -1339,6 +1339,16 @@
}, },
"question": "When was this bench last surveyed?", "question": "When was this bench last surveyed?",
"render": "This bench was last surveyed on {survey:date}" "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": { "title": {
@ -3466,6 +3476,37 @@
}, },
"climbing_gym": { "climbing_gym": {
"description": "A 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", "name": "Climbing gyms",
"presets": { "presets": {
"0": { "0": {
@ -5688,6 +5729,315 @@
"render": "Extinguishers" "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": { "filters": {
"filter": { "filter": {
"0": { "0": {
@ -7683,6 +8033,13 @@
} }
} }
}, },
"mark_not_too_hard": {
"render": {
"special": {
"message": "This challenge was marked as too hard. Try anyway?"
}
}
},
"mark_too_hard": { "mark_too_hard": {
"render": { "render": {
"special": { "special": {
@ -8573,6 +8930,29 @@
} }
}, },
"tagRenderings": { "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": { "capacity": {
"freeform": { "freeform": {
"placeholder": "Amount of parking spots" "placeholder": "Amount of parking spots"
@ -8598,6 +8978,18 @@
"question": "How many disabled parking spots are there at this parking?", "question": "How many disabled parking spots are there at this parking?",
"render": "There are {capacity:disabled} disabled parking spots" "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": { "parking-type": {
"mappings": { "mappings": {
"0": { "0": {

View file

@ -3734,6 +3734,271 @@
"render": "Exctincteurs" "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": { "filters": {
"filter": { "filter": {
"0": { "0": {

View file

@ -1281,6 +1281,16 @@
}, },
"question": "Wanneer is deze laatste bank laatst gesurveyed?", "question": "Wanneer is deze laatste bank laatst gesurveyed?",
"render": "Deze bank is laatst gesurveyd op {survey:date}" "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": { "title": {
@ -5373,6 +5383,23 @@
"render": "Brandblussers" "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": { "filters": {
"filter": { "filter": {
"0": { "0": {

View file

@ -101,6 +101,40 @@
}, },
"benches": { "benches": {
"description": "This map shows all benches that are recorded in OpenStreetMap: Individual benches, and benches belonging to public transport stops or shelters.", "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", "shortDescription": "A map of benches",
"title": "Benches" "title": "Benches"
}, },

View file

@ -93,6 +93,26 @@
}, },
"benches": { "benches": {
"description": "Deze kaart toont alle zitbanken die zijn opgenomen in OpenStreetMap: individuele banken en banken bij bushaltes.", "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", "shortDescription": "Een kaart van zitbanken",
"title": "Zitbanken" "title": "Zitbanken"
}, },

View file

@ -285,13 +285,13 @@ export default class ScriptUtils {
*/ */
static detectVariablePaths(path: string) { static detectVariablePaths(path: string) {
const splitPath = path.match(/(.*)\{(.+)\}(.*)/) const splitPath = path.match(/(.*)\{(.+)\}(.*)/)
if(!splitPath){ if (!splitPath) {
return [path] return [path]
} }
const [_, head] = splitPath const [_, head] = splitPath
const headPath = head.slice(0, head.lastIndexOf("/")) const headPath = head.slice(0, head.lastIndexOf("/"))
const allPossibleMatchingFiles = ScriptUtils.readDirRecSync(headPath) const allPossibleMatchingFiles = ScriptUtils.readDirRecSync(headPath)
const pathAsRegex = path.replaceAll(/\{.+}/g, ".+") 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() { public convert() {
const lines = readFileSync(this._source, "utf8") const lines = readFileSync(this._source, "utf8").split("\n")
.split("\n")
const sections: string[][] = [] const sections: string[][] = []
let currentSection: string[] = [] let currentSection: string[] = []
@ -159,10 +158,10 @@ export class GenerateDocs extends Script {
this.generateEliDocs() this.generateEliDocs()
this.generateBuiltinUnits() this.generateBuiltinUnits()
this.writeMarkdownFile("./Docs/Studio/SpecialInputElements.md", Validators.HelpText(), [ 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(), [ this.writeMarkdownFile("./Docs/Studio/Tags_format.md", TagUtils.generateDocs(), [
"src/Logic/Tags/TagUtils.ts" "src/Logic/Tags/TagUtils.ts",
]) ])
this.writeMarkdownFile( this.writeMarkdownFile(
@ -170,7 +169,7 @@ export class GenerateDocs extends Script {
SpecialVisualizations.HelpMessage(), SpecialVisualizations.HelpMessage(),
["src/UI/SpecialVisualizations.ts"], ["src/UI/SpecialVisualizations.ts"],
{ {
tocMaxDepth: 3 tocMaxDepth: 3,
} }
) )
this.writeMarkdownFile( this.writeMarkdownFile(
@ -181,7 +180,6 @@ export class GenerateDocs extends Script {
) )
} }
// For dev // For dev
{ {
this.generateQueryParameterDocs() this.generateQueryParameterDocs()
@ -208,13 +206,12 @@ export class GenerateDocs extends Script {
ScriptUtils.erasableLog("Written docs for theme", theme.id) ScriptUtils.erasableLog("Written docs for theme", theme.id)
}) })
this.generateOverviewsForAllSingleLayer("./Docs/nl/Layers", "nl") this.generateOverviewsForAllSingleLayer("./Docs/nl/Layers", "nl")
} }
this.writeMarkdownFile("./Docs/ChangesetMeta.md", Changes.getDocs(), [ this.writeMarkdownFile("./Docs/ChangesetMeta.md", Changes.getDocs(), [
"src/Logic/Osm/Changes.ts", "src/Logic/Osm/Changes.ts",
"src/Logic/Osm/ChangesetHandler.ts" "src/Logic/Osm/ChangesetHandler.ts",
]) ])
new WikiPageGenerator().generate() new WikiPageGenerator().generate()
@ -224,9 +221,11 @@ export class GenerateDocs extends Script {
this.generateSidebar("nl") this.generateSidebar("nl")
this.generatedPaths.push(".gitignore") this.generatedPaths.push(".gitignore")
writeFileSync("./Docs/.gitignore", this.generatedPaths writeFileSync(
.map(p => p.replace("./Docs/", "")) "./Docs/.gitignore",
.join("\n"), "utf-8") this.generatedPaths.map((p) => p.replace("./Docs/", "")).join("\n"),
"utf-8"
)
console.log("Generated docs") console.log("Generated docs")
} }
@ -238,7 +237,7 @@ export class GenerateDocs extends Script {
options?: { options?: {
noTableOfContents?: boolean noTableOfContents?: boolean
tocMaxDepth?: number tocMaxDepth?: number
lang?: string, lang?: string
noWarn?: boolean noWarn?: boolean
} }
): void { ): void {
@ -269,25 +268,31 @@ export class GenerateDocs extends Script {
md += "\n" md += "\n"
} }
const warnAutomated = options?.noWarn ? "" : const warnAutomated = options?.noWarn
"[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources)\n\n" ? ""
: "[//]: # (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( const generatedFrom = new TypedTranslation<{ sources; date }>({
(s) => `[${s}](https://source.mapcomplete.org/MapComplete/MapComplete/src/branch/develop/${s})`).join(", ") 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 = writeFileSync(
new TypedTranslation<{ sources, date }>({ filename,
en: "This document is autogenerated from {sources} on {date}", warnAutomated + md + (options?.noWarn ? "" : "\n\n" + generatedFrom + "\n")
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"))
this.generatedPaths.push(filename) this.generatedPaths.push(filename)
} }
private generateEliDocs() { private generateEliDocs() {
const eli = AvailableRasterLayers.editorLayerIndex() const eli = AvailableRasterLayers.editorLayerIndex()
this.writeMarkdownFile( this.writeMarkdownFile(
@ -295,8 +300,8 @@ export class GenerateDocs extends Script {
[ [
"# Layers in the Editor Layer Index", "# 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." + "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)", + "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/)", +"\n[See the online, interactive map here](https://osmlab.github.io/editor-layer-index/)",
MarkdownUtils.table( MarkdownUtils.table(
["id", "name", "category", "Best", "attribution"], ["id", "name", "category", "Best", "attribution"],
eli.map((f) => [ eli.map((f) => [
@ -305,10 +310,10 @@ export class GenerateDocs extends Script {
f.properties.category, f.properties.category,
f.properties.best ? "⭐" : "", f.properties.best ? "⭐" : "",
f.properties.attribution?.html ?? f.properties.attribution?.text, f.properties.attribution?.html ?? f.properties.attribution?.text,
]), ])
), ),
].join("\n\n"), ].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. * Generates documentation for the all the individual layers.
* Inline layers are included (if the theme is public) * 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( const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter(
(layer) => layer["source"] !== null (layer) => layer["source"] !== null
) )
@ -434,19 +442,23 @@ export class GenerateDocs extends Script {
mkdirSync(targetDirectory) mkdirSync(targetDirectory)
} }
allLayers.forEach((layer) => { allLayers.forEach((layer) => {
const element = layer.generateDocumentation({ const element = layer
usedInThemes: themesPerLayer.get(layer.id), .generateDocumentation({
layerIsNeededBy: layerIsNeededBy, usedInThemes: themesPerLayer.get(layer.id),
dependencies: DependencyCalculator.getLayerDependencies(layer), layerIsNeededBy: layerIsNeededBy,
lang dependencies: DependencyCalculator.getLayerDependencies(layer),
}).replaceAll("./Docs/Layers", targetDirectory) lang,
})
.replaceAll("./Docs/Layers", targetDirectory)
const inlineSource = inlineLayers.get(layer.id) const inlineSource = inlineLayers.get(layer.id)
ScriptUtils.erasableLog("Exporting layer documentation for", layer.id) ScriptUtils.erasableLog("Exporting layer documentation for", layer.id)
let source: string = `assets/layers/${layer.id}/${layer.id}.json` let source: string = `assets/layers/${layer.id}/${layer.id}.json`
if (inlineSource !== undefined) { if (inlineSource !== undefined) {
source = `assets/themes/${inlineSource}/${inlineSource}.json` 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[] = [ const docs: string[] = [
"# Which tagrendering is used where?", "", "# 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", "",
'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", "Having this overview supports e.g. refactoring efforts",
"## Existing builtin tagrenderings", ""] "## Existing builtin tagrenderings",
"",
]
for (const [builtin, usedByLayers] of Array.from(layersUsingBuiltin.entries())) { for (const [builtin, usedByLayers] of Array.from(layersUsingBuiltin.entries())) {
docs.push(`### ${builtin}\n`) docs.push(`### ${builtin}\n`)
docs.push(usedByLayers.length + " usages") 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() { 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 allLayers = AllSharedLayers.sharedLayers
const layersToShow = theme.layers.filter( const layersToShow = theme.layers.filter(
(l) => l.id !== "favourite" && Constants.added_by_default.indexOf(<any>l.id) < 0 (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)) { if (allLayers.has(l.id)) {
return `[${l.id}](../Layers/${l.id}.md)` 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( new Translation({
{ en: "This theme is available in the following languages:",
en: "This theme is available in the following languages:", nl: "Deze kaart is beschikbaar in de volgende talen:",
nl: "Deze kaart is beschikbaar in de volgende talen:", }).textFor(lang),
}, MarkdownUtils.list(
).textFor(lang), theme.language
MarkdownUtils.list(theme.language.filter((ln) => ln !== "_context").map(ln => { .filter((ln) => ln !== "_context")
if (language_translations[ln]) { .map((ln) => {
return ln + " (" + new Translation(language_translations[ln]).textFor(lang) + ")" if (language_translations[ln]) {
} else { return (
return ln ln +
} " (" +
}, new Translation(language_translations[ln]).textFor(lang) +
)), ")"
)
} else {
return ln
}
})
),
] ]
if (layersToInline.length > 0) { 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( 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", path + "/" + theme.id + ".md",
el.join("\n"), el.join("\n"),
[`assets/themes/${theme.id}/${theme.id}.json`], [`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[] { private generateSidebar(subdirectory = ""): string[] {
const tr = Translations.t.app.back.textFor(subdirectory) const tr = Translations.t.app.back.textFor(subdirectory)
const sidebar: string[] = [ const sidebar: string[] = [`<a href='https://mapcomplete.org' class='back-to-mc'>${tr}</a>`]
`<a href='https://mapcomplete.org' class='back-to-mc'>${tr}</a>`
]
const allFiles = ScriptUtils.readDirRecSync("./Docs/" + subdirectory) const allFiles = ScriptUtils.readDirRecSync("./Docs/" + subdirectory)
.filter(path => path.endsWith(".md")) .filter((path) => path.endsWith(".md"))
.filter(path => !path.startsWith("_")) .filter((path) => !path.startsWith("_"))
.filter(path => !path.startsWith("./Docs/nl/") || (path.startsWith("./Docs/" + subdirectory) && subdirectory !== "")) .filter(
.map(path => path.substring("./Docs/".length + subdirectory.length + 1)) (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[]>() const perDirectory = new Map<string, string[]>()
function addFile(dir: string, path: string) { function addFile(dir: string, path: string) {
@ -779,13 +821,15 @@ export class GenerateDocs extends Script {
const directories: [string, Translation | string][] = [ const directories: [string, Translation | string][] = [
["", ""], ["", ""],
["Layers", new Translation({ en: "Overview of layers", nl: "Overzicht van de lagen" })], ["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"], ["UserTests", "Usability tests with users"],
["Studio", "For theme builders"], ["Studio", "For theme builders"],
["Dev", "For developers"], ["Dev", "For developers"],
] ]
for (const [dir, title] of directories) { for (const [dir, title] of directories) {
if (title === null) { if (title === null) {
continue continue
@ -830,10 +874,9 @@ export class GenerateDocs extends Script {
} }
private generateNormalLayerOverview(type: "Layers" | "Themes", subdir = "") { private generateNormalLayerOverview(type: "Layers" | "Themes", subdir = "") {
const layerinfo: [string, string, string][] = [] const layerinfo: [string, string, string][] = []
const source: ReadonlyMap<string, LayerConfig> | AllKnownLayoutsLazy const source: ReadonlyMap<string, LayerConfig> | AllKnownLayoutsLazy =
= type === "Layers" ? AllSharedLayers.sharedLayers : AllKnownLayouts.allKnownLayouts type === "Layers" ? AllSharedLayers.sharedLayers : AllKnownLayouts.allKnownLayouts
const keys = Array.from(source.keys()) const keys = Array.from(source.keys())
keys.sort() keys.sort()
@ -841,7 +884,7 @@ export class GenerateDocs extends Script {
const layer = source.get(id) const layer = source.get(id)
let name: Translation let name: Translation
if (type === "Layers") { if (type === "Layers") {
const layer_ = (<LayerConfig>layer) const layer_ = <LayerConfig>layer
if (!layer_.isNormal()) { if (!layer_.isNormal()) {
continue continue
} }
@ -849,36 +892,41 @@ export class GenerateDocs extends Script {
} else { } else {
name = (<ThemeConfig>layer).title 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 = { const titles = {
"Layers": new Translation({ en: "Layers", nl: "Lagen" }), Layers: new Translation({ en: "Layers", nl: "Lagen" }),
"Themes": new Translation({ en: "Themes", nl: "Kaartthema's" }) Themes: new Translation({ en: "Themes", nl: "Kaartthema's" }),
} }
const intro: Record<string, TypedTranslation<{ version }>> = { const intro: Record<string, TypedTranslation<{ version }>> = {
"Layers": new TypedTranslation<{ version }>({ Layers: new TypedTranslation<{ version }>({
en: "The following layers are available in MapComplete {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}:", 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), const doc = [
intro[type].Subs({ version: Constants.vNumber }).textFor(subdir) "# " + titles[type].textFor(subdir),
, MarkdownUtils.table( intro[type].Subs({ version: Constants.vNumber }).textFor(subdir),
["id", "name", "description"], MarkdownUtils.table(["id", "name", "description"], layerinfo),
layerinfo)
] ]
const path = `./Docs/${subdir}/${type}` const path = `./Docs/${subdir}/${type}`
if (!existsSync(path)) { if (!existsSync(path)) {
mkdirSync(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 continue
} }
if (!AllSharedLayers.sharedLayers.has(id)) { 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 = [ const el = [
"# Special and priviliged layers", "", "# Special and priviliged layers",
"",
"MapComplete has a few data layers available which have special properties through builtin-hooks.", "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.", "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", "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( ...Lists.noNull(
Constants.priviliged_layers.map((id) => AllSharedLayers.sharedLayers.get(id)) Constants.priviliged_layers.map((id) => AllSharedLayers.sharedLayers.get(id))
).map((l) => ).map(
l.generateDocumentation({ (l) =>
usedInThemes: themesPerLayer.get(l.id), l.generateDocumentation({
layerIsNeededBy: layerIsNeededBy, usedInThemes: themesPerLayer.get(l.id),
dependencies: DependencyCalculator.getLayerDependencies(l), layerIsNeededBy: layerIsNeededBy,
addedByDefault: Constants.added_by_default.indexOf(<any>l.id) >= 0, dependencies: DependencyCalculator.getLayerDependencies(l),
canBeIncluded: Constants.no_include.indexOf(<any>l.id) < 0, addedByDefault: Constants.added_by_default.indexOf(<any>l.id) >= 0,
}) + "\n" canBeIncluded: Constants.no_include.indexOf(<any>l.id) < 0,
}) + "\n"
), ),
].join("\n") ].join("\n")
this.writeMarkdownFile("./Docs/Studio/SpecialLayers.md", el, [ this.writeMarkdownFile("./Docs/Studio/SpecialLayers.md", el, [

View file

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

View file

@ -13,8 +13,8 @@ class GeneratePmTilesExtracts extends Script {
constructor() { constructor() {
super( super(
"Generates many pmtiles-archive from planet-latest.pmtiles. Expects the `pmtiles`-executable to be at `/data/pmtiles`." + "Generates many pmtiles-archive from planet-latest.pmtiles. Expects the `pmtiles`-executable to be at `/data/pmtiles`." +
"\n\n" + "\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" "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 this.skipped += boundary
return return
} }
console.log( console.log("Starting column", x, "at zoom", z, "as", lastFileForColumn, "does not exist")
"Starting column",
x,
"at zoom",
z,
"as",
lastFileForColumn,
"does not exist"
)
for (let y = 0; y < boundary; y++) { for (let y = 0; y < boundary; y++) {
yield this.extractsGenerator.generateArchive(z, x, y, maxzoom) yield this.extractsGenerator.generateArchive(z, x, y, maxzoom)
@ -58,8 +50,6 @@ class GeneratePmTilesExtracts extends Script {
} }
} }
private *generateAll(): Generator<Promise<void>> { private *generateAll(): Generator<Promise<void>> {
const zoomlevels: Record<number, number> = OfflineBasemapManager.zoomelevels const zoomlevels: Record<number, number> = OfflineBasemapManager.zoomelevels
for (const key in zoomlevels) { for (const key in zoomlevels) {
@ -88,7 +78,7 @@ class GeneratePmTilesExtracts extends Script {
async main(args: string[]): Promise<void> { async main(args: string[]): Promise<void> {
this.targetDir = args[0] this.targetDir = args[0]
const sourceFile = this.targetDir+"/planet-latest.pmtiles" const sourceFile = this.targetDir + "/planet-latest.pmtiles"
if (!this.targetDir) { if (!this.targetDir) {
console.log("Please specify a target directory. Did you forget '--' in vite-node?") console.log("Please specify a target directory. Did you forget '--' in vite-node?")
return return
@ -126,11 +116,14 @@ class GeneratePmTilesExtracts extends Script {
)}` )}`
) )
} while (batch.length > 0) } while (batch.length > 0)
writeFileSync(this.targetDir+"/Last_pm_tile_extracts.txt", writeFileSync(
[new Date().getTime() + "", this.targetDir + "/Last_pm_tile_extracts.txt",
[
new Date().getTime() + "",
new Date().toISOString(), new Date().toISOString(),
"# The script converting the planet-latest.pmtiles into sub-archives has been successfully executed at the stated time", "# 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" }) const projectList = readFileSync(tagInfoList, { encoding: "utf8" })
.split("\n") .split("\n")
.filter((entry) => entry.indexOf("mapcomplete_") < 0) .filter((entry) => entry.indexOf("mapcomplete_") < 0)
.concat( .concat(files.map((f) => `${f} https://docs.mapcomplete.org/TagInfo/${f}.json`))
files.map(
(f) =>
`${f} https://docs.mapcomplete.org/TagInfo/${f}.json`
)
)
.sort() .sort()
.filter((entry) => entry != "") .filter((entry) => entry != "")

View file

@ -6,7 +6,11 @@ export class PmTilesExtractGenerator {
private readonly _sourceFile: string private readonly _sourceFile: string
private readonly _executeableLocation: 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._sourceFile = sourceFile
this._targetDir = targetDir this._targetDir = targetDir
this._executeableLocation = executeableLocation this._executeableLocation = executeableLocation
@ -20,7 +24,7 @@ export class PmTilesExtractGenerator {
}) })
if (captureStdioChunks !== undefined) { if (captureStdioChunks !== undefined) {
child.stdout.on("data", data => { child.stdout.on("data", (data) => {
captureStdioChunks(data) captureStdioChunks(data)
}) })
} }
@ -50,15 +54,15 @@ export class PmTilesExtractGenerator {
} }
const outputFileName = this.getFilename(z, x, y) const outputFileName = this.getFilename(z, x, y)
await this.startProcess( 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_lon,
min_lat + 0.0001, min_lat + 0.0001,
max_lon, max_lon,
max_lat, max_lat,
].join(",")} ${outputFileName}`, ].join(",")} ${outputFileName}`
) )
return outputFileName return outputFileName
} }
} }

View file

@ -65,9 +65,9 @@ export class Server {
}, },
}) })
this.handlers = handle this.handlers = handle
http.createServer((req: http.IncomingMessage, res) => http.createServer((req: http.IncomingMessage, res) => this.answerRequest(req, res)).listen(
this.answerRequest(req, res) port
).listen(port) )
console.log( console.log(
"Server is running on http://127.0.0.1:" + port, "Server is running on http://127.0.0.1:" + port,
". Supported endpoints are: " + handle.map((h) => h.mustMatch).join(", ") ". Supported endpoints are: " + handle.map((h) => h.mustMatch).join(", ")
@ -86,7 +86,7 @@ export class Server {
"from:", "from:",
req.headers.origin, req.headers.origin,
new Date().toISOString(), new Date().toISOString(),
path, path
) )
if (this.options?.ignorePathPrefix) { if (this.options?.ignorePathPrefix) {
for (const toIgnore of this.options.ignorePathPrefix) { for (const toIgnore of this.options.ignorePathPrefix) {
@ -114,14 +114,11 @@ export class Server {
res.setHeader( res.setHeader(
"Access-Control-Allow-Headers", "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 ?? "*") res.setHeader("Access-Control-Allow-Origin", req.headers.origin ?? "*")
if (req.method === "OPTIONS") { if (req.method === "OPTIONS") {
res.setHeader( res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, UPDATE")
"Access-Control-Allow-Methods",
"POST, GET, OPTIONS, DELETE, UPDATE",
)
res.writeHead(204, { "Content-Type": handler.mimetype }) res.writeHead(204, { "Content-Type": handler.mimetype })
res.end() res.end()
return return
@ -154,7 +151,7 @@ export class Server {
"resulted in a ", "resulted in a ",
typeof result, typeof result,
" instead of a string:", " instead of a string:",
result, result
) )
} }
@ -162,8 +159,6 @@ export class Server {
res.writeHead(200, { "Content-Type": handler.mimetype, ...extraHeaders }) res.writeHead(200, { "Content-Type": handler.mimetype, ...extraHeaders })
res.write("" + result) res.write("" + result)
res.end() res.end()
} catch (e) { } catch (e) {
console.error("Could not handle request:", e) console.error("Could not handle request:", e)
res.writeHead(500) res.writeHead(500)
@ -183,8 +178,7 @@ export class Server {
* @param path * @param path
* @param res * @param res
*/ */
public static sendFile(path: string, res: ServerResponse){ public static sendFile(path: string, res: ServerResponse) {
createReadStream(path).pipe(res); 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 private static FILE_IS_STALE_AFTER_SEC = 30 * 24 * 60 * 60
constructor() { constructor() {
super("Starts a server that serves PMtiles. Usage:\n" + super(
"sourceFile cachedir [portnumber??2346]") "Starts a server that serves PMtiles. Usage:\n" +
"sourceFile cachedir [portnumber??2346]"
)
} }
async main(args: string[]): Promise<void> { async main(args: string[]): Promise<void> {
if(args.length < 2){ if (args.length < 2) {
this.printHelp() this.printHelp()
throw ("!!! Please, specify a source- and cachedir !!!") throw "!!! Please, specify a source- and cachedir !!!"
} }
const sourcefile = args[0] const sourcefile = args[0]
const targetDir = args[1] const targetDir = args[1]
@ -29,52 +31,65 @@ class ServerPmTileExtracts extends Script {
const creationDates = new Map<string, Date>() const creationDates = new Map<string, Date>()
new Server(port, {}, new Server(port, {}, [
[ {
{ mustMatch: /\d+\/\d+\/\d+.pmtiles/,
mustMatch: /\d+\/\d+\/\d+.pmtiles/, unmanaged: true,
unmanaged: true, mimetype: "application/octet-stream",
mimetype: "application/octet-stream", handle: async (
handle: async (path: string, path: string,
queryParams: URLSearchParams, queryParams: URLSearchParams,
req: http.IncomingMessage, req: http.IncomingMessage,
body: string, body: string,
res: ServerResponse) => { res: ServerResponse
const [z,x,y] = path.split(".")[0].split("/").map(x => Number(x)) ) => {
if(!(z in zoomlevels)){ const [z, x, y] = path
throw `Invalid zoomlevel ${z} (x: ${x}, y: ${y}, must be one of ${Array.from(Object.keys(zoomlevels)).join(", ")}` .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 targetCreationDate = creationDates.get(targetFile)
let isStale = targetCreationDate === undefined ? false : new Date().getTime() - targetCreationDate.getTime() > 1000 * ServerPmTileExtracts.FILE_IS_STALE_AFTER_SEC let isStale =
targetCreationDate === undefined
? false
: new Date().getTime() - targetCreationDate.getTime() >
1000 * ServerPmTileExtracts.FILE_IS_STALE_AFTER_SEC
if (isStale || !existsSync(targetFile)) { if (isStale || !existsSync(targetFile)) {
ScriptUtils.createParentDir(targetFile) ScriptUtils.createParentDir(targetFile)
console.log("Creating", targetFile) console.log("Creating", targetFile)
const start = new Date() const start = new Date()
await generator.generateArchive(z, x, y) await generator.generateArchive(z, x, y)
const stop = new Date() const stop = new Date()
console.log("Creating ", targetFile, "took", (stop.getTime() - start.getTime()) + "ms") console.log(
} else if (targetCreationDate === undefined) { "Creating ",
const stats = statSync(targetFile) targetFile,
creationDates.set(targetFile, stats.mtime) "took",
} stop.getTime() - start.getTime() + "ms"
)
if(req.destroyed){ } else if (targetCreationDate === undefined) {
return null const stats = statSync(targetFile)
} creationDates.set(targetFile, stats.mtime)
res.writeHead(200, { "Content-Type": "application/octet-stream"}) }
Server.sendFile(targetFile, res)
if (req.destroyed) {
return null return null
}, }
}, res.writeHead(200, { "Content-Type": "application/octet-stream" })
], Server.sendFile(targetFile, res)
)
}
return null
},
},
])
}
} }
new ServerPmTileExtracts().run() new ServerPmTileExtracts().run()

View file

@ -255,18 +255,20 @@ export class RegexTag extends TagsFilter {
asHumanString(linkToWiki: boolean) { asHumanString(linkToWiki: boolean) {
if (typeof this.key === "string") { if (typeof this.key === "string") {
const oper = typeof this.value === "string" ? "=" : "~" const oper = typeof this.value === "string" ? "=" : "~"
if(linkToWiki){ if (linkToWiki) {
return `[${this.key}](https://wiki.osm.org/wiki/Key:${this.key})\`${this.invert ? "!" : ""}${oper}${RegexTag.source(this.value)}\`` 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)}` const v = `${this.key}${this.invert ? "!" : ""}${oper}${RegexTag.source(this.value)}`
if(linkToWiki){ if (linkToWiki) {
return `\`${v}\`` return `\`${v}\``
} }
return v return v
} }
const v = `${this.key.source}${this.invert ? "!" : ""}~~${RegexTag.source(this.value)}` const v = `${this.key.source}${this.invert ? "!" : ""}~~${RegexTag.source(this.value)}`
if(linkToWiki){ if (linkToWiki) {
return `\`${v}\`` return `\`${v}\``
} }
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 { LayerConfigJson } from "../Json/LayerConfigJson"
import { MinimalTagRenderingConfigJson, TagRenderingConfigJson } from "../Json/TagRenderingConfigJson" import {
MinimalTagRenderingConfigJson,
TagRenderingConfigJson,
} from "../Json/TagRenderingConfigJson"
import { Utils } from "../../../Utils" import { Utils } from "../../../Utils"
import SpecialVisualizations from "../../../UI/SpecialVisualizations" import SpecialVisualizations from "../../../UI/SpecialVisualizations"
import Translations from "../../../UI/i18n/Translations" import Translations from "../../../UI/i18n/Translations"
@ -11,7 +23,10 @@ import { TagConfigJson } from "../Json/TagConfigJson"
import PointRenderingConfigJson, { IconConfigJson } from "../Json/PointRenderingConfigJson" import PointRenderingConfigJson, { IconConfigJson } from "../Json/PointRenderingConfigJson"
import ValidationUtils from "./ValidationUtils" import ValidationUtils from "./ValidationUtils"
import { RenderingSpecification } from "../../../UI/SpecialVisualization" import { RenderingSpecification } from "../../../UI/SpecialVisualization"
import { MappingConfigJson, QuestionableTagRenderingConfigJson } from "../Json/QuestionableTagRenderingConfigJson" import {
MappingConfigJson,
QuestionableTagRenderingConfigJson,
} from "../Json/QuestionableTagRenderingConfigJson"
import { ConfigMeta } from "../../../UI/Studio/configMeta" import { ConfigMeta } from "../../../UI/Studio/configMeta"
import { ConversionContext } from "./ConversionContext" import { ConversionContext } from "./ConversionContext"
import { ExpandRewrite } from "./ExpandRewrite" import { ExpandRewrite } from "./ExpandRewrite"
@ -457,7 +472,10 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
private static escapeStr(v: string, context: ConversionContext): string { private static escapeStr(v: string, context: ConversionContext): string {
if (typeof v !== "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 RewriteSpecial.escapeStr("" + v, context)
} }
return v return v
@ -593,9 +611,13 @@ export class RewriteSpecial extends DesugaringStep<TagRenderingConfigJson> {
argNamesList, argNamesList,
(x) => x (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] 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)) .forEach((e) => context.err(e))
@ -756,7 +778,7 @@ class ExpandIconBadges extends DesugaringStep<PointRenderingConfigJson> {
for (const tr of expanded) { for (const tr of expanded) {
const condition = tr.condition const condition = tr.condition
for (const trElement of <MappingConfigJson[]> tr.mappings) { for (const trElement of <MappingConfigJson[]>tr.mappings) {
const showIf = TagUtils.optimzeJson({ const showIf = TagUtils.optimzeJson({
and: Lists.noNull([ and: Lists.noNull([
condition, condition,
@ -1129,10 +1151,9 @@ export class OrderLayer extends DesugaringStep<string | LayerConfigJson> {
const orderTag = new OrderTagRendering() const orderTag = new OrderTagRendering()
let json: LayerConfigJson = jsonOrString let json: LayerConfigJson = jsonOrString
json = <LayerConfigJson>new On( json = <LayerConfigJson>(
"tagRenderings", new On("tagRenderings", new Each(orderTag)).convert(<any>json, context)
new Each(orderTag) )
).convert(<any>json, context)
json = <LayerConfigJson>new On("title", 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) return Lists.flatten(list)
}) })
), ),
<any>new On( <any>(
"pointRendering", new On(
(layer) => "pointRendering",
new Each(new On("marker", new Each(new ExpandMarkerRenderings(state, <any> layer)))) (layer) =>
new Each(
new On(
"marker",
new Each(new ExpandMarkerRenderings(state, <any>layer))
)
)
)
), ),
<any>new On( <any>(
"pointRendering", new On(
(layer) => new Each(new PreparePointRendering(state, <any> layer)) "pointRendering",
(layer) => new Each(new PreparePointRendering(state, <any>layer))
)
), ),
new SetDefault("titleIcons", ["icons.defaults"]), new SetDefault("titleIcons", ["icons.defaults"]),
new AddRatingBadge(), new AddRatingBadge(),
new AddFavouriteBadges(), new AddFavouriteBadges(),
new AutoTitleIcon(), new AutoTitleIcon(),
<any>new On( <any>(
"titleIcons", new On(
(layer) => "titleIcons",
new Concat(<any> new ExpandTagRendering(state, <any> layer, { noHardcodedStrings: true })) (layer) =>
new Concat(
<any>(
new ExpandTagRendering(state, <any>layer, {
noHardcodedStrings: true,
})
)
)
)
), ),
new AddFiltersFromTagRenderings(), new AddFiltersFromTagRenderings(),
new ExpandFilter(state), new ExpandFilter(state),
new MoveUnitConfigs(), new MoveUnitConfigs(),
new PruneFilters(), 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'") context.err("Layer " + json.id + " does not have an explicit 'allowMove'")
} }
} }
if(json.source["geojsonZoomLevel"]){ if (json.source["geojsonZoomLevel"]) {
context.enter("source").err("Use geoJsonZoomLevel (with capital J) instead of geojsonZoomLevel") context
.enter("source")
.err("Use geoJsonZoomLevel (with capital J) instead of geojsonZoomLevel")
} }
if (context.hasErrors()) { if (context.hasErrors()) {

View file

@ -224,13 +224,17 @@ export default class FilterConfig {
public generateDocs(options?: { lang?: string }): string { public generateDocs(options?: { lang?: string }): string {
const lang = options?.lang ?? "en" const lang = options?.lang ?? "en"
const header = ["id", { en: "Question", nl: "Vraag" }, { const header = [
en: "Attributes", "id",
nl: "Attributen", { en: "Question", nl: "Vraag" },
}, {
en: "Attributes",
nl: "Attributen",
},
{ en: "Fields", nl: "Velden" }, { 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[][] = [] const content: string[][] = []
for (let i = 0; i < this.options.length; i++) { for (let i = 0; i < this.options.length; i++) {
@ -239,7 +243,10 @@ export default class FilterConfig {
content.push([ content.push([
this.id + "." + i, this.id + "." + i,
opt.question.textFor(lang), 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(" "), 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 }[]> reusedTagRenderings?: Map<string, { layer: string }[]>
lang?: string lang?: string
}): string { }): string {
const paragraphs: (string | Translation)[] = ["# " + this.id, this.description]
const paragraphs: (string | Translation)[] = [ function add(
"# " + this.id, item: Translation | string | (Record<string, string> & { en: string; nl: string })
this.description, ) {
]
function add(item: Translation | string | Record<string, string> & { en: string, nl: string }) {
if (item instanceof Translation || typeof item === "string") { if (item instanceof Translation || typeof item === "string") {
paragraphs.push(item) paragraphs.push(item)
} else if (item["en"] !== undefined) { } else if (item["en"] !== undefined) {
@ -437,12 +435,12 @@ export default class LayerConfig extends WithContextLoader {
} }
if (this._basedOn) { if (this._basedOn) {
add(new TypedTranslation<{ basedOn }>( add(
{ new TypedTranslation<{ basedOn }>({
en: `This layer is based on [{basedOn}](../Layers/{basedOn}.md)`, en: `This layer is based on [{basedOn}](../Layers/{basedOn}.md)`,
nl: `Deze laag is gebaseerd op [{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 // Various extra properties, added in a list below the description
@ -452,18 +450,16 @@ export default class LayerConfig extends WithContextLoader {
new TypedTranslation<{ minzoom }>({ new TypedTranslation<{ minzoom }>({
en: "This layer is shown at zoomlevel **{minzoom}** and higher", en: "This layer is shown at zoomlevel **{minzoom}** and higher",
nl: "Deze laag wordt getoond vanaf zoomlevel **{minzoom}**", nl: "Deze laag wordt getoond vanaf zoomlevel **{minzoom}**",
}).Subs(this)) }).Subs(this)
)
if (canBeIncluded) { if (canBeIncluded) {
if (addedByDefault) { if (addedByDefault) {
extraProps.push( extraProps.push(
new Translation( new Translation({
{ en: "**This layer is included automatically in every theme. This layer might contain no points**",
en: nl: "**Deze laag wordt automatisch toegevoegd aan ieder kaartthema. Deze laag bevat mogelijks geen punten",
"**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) { if (this.shownByDefault === false) {
@ -471,34 +467,31 @@ export default class LayerConfig extends WithContextLoader {
new Translation({ new Translation({
en: "This layer is not visible by default and must be enabled in the filter by the user. ", 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 ", nl: "Deze laag is standaard niet zichtbaar en moet door de gebruiker aangezet worden in het 'filter'-menu ",
}), })
) )
} }
if (this.title === undefined) { if (this.title === undefined) {
extraProps.push( extraProps.push(
new Translation({ new Translation({
en: 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.",
"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.", 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.name === undefined && this.shownByDefault === false) {
if (this.shownByDefault === false) { if (this.shownByDefault === false) {
extraProps.push( extraProps.push(
new TypedTranslation<{ id }>({ 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`", 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", 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 { } else {
extraProps.push( extraProps.push(
new Translation({ new Translation({
en: "Not visible in the layer selection by default. If you want to make this layer toggable, override `name`", 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.", 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({ new Translation({
en: "Not rendered on the map by default.", en: "Not rendered on the map by default.",
nl: "Deze laag wordt standaard niet weergegeven op de kaart.", nl: "Deze laag wordt standaard niet weergegeven op de kaart.",
}), })
) )
} }
} else { } else {
extraProps.push( extraProps.push(
new Translation({ 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.", 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. ", 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) { for (const dep of dependencies) {
extraProps.push( 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})", 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})", 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) ?? [])) { for (const revDep of Lists.dedup(layerIsNeededBy?.get(this.id) ?? [])) {
extraProps.push( extraProps.push(
new TypedTranslation<{ revDep }>({ new TypedTranslation<{ revDep }>({
en: "This layer is needed as dependency for layer [`{revDep}`](#{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 // Overview of what themes use this layer
@ -548,16 +536,20 @@ export default class LayerConfig extends WithContextLoader {
if (!addedByDefault) { if (!addedByDefault) {
if (usedInThemes?.length > 0) { if (usedInThemes?.length > 0) {
add( add(
MarkdownUtils.title(2, MarkdownUtils.title(
2,
new Translation({ new Translation({
en: "Themes using this layer", en: "Themes using this layer",
nl: "Kaartthemas die deze laag gebruiken", nl: "Kaartthemas die deze laag gebruiken",
})), })
)
) )
add( add(
MarkdownUtils.list( MarkdownUtils.list(
(usedInThemes ?? []).map((id) => `[\`${id}\`](https://mapcomplete.org/${id})`), (usedInThemes ?? []).map(
), (id) => `[\`${id}\`](https://mapcomplete.org/${id})`
)
)
) )
} else if (this.source !== null) { } else if (this.source !== null) {
add({ add({
@ -570,85 +562,75 @@ export default class LayerConfig extends WithContextLoader {
// Data source information // 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) { if (this.source?.geojsonSource) {
paragraphs.push( paragraphs.push(
new TypedTranslation<{ geojsonSource? }>({ new TypedTranslation<{ geojsonSource? }>({
en: "⚠️ This layer is loaded from an external source, namely `{geojsonSource}`", en: "⚠️ This layer is loaded from an external source, namely `{geojsonSource}`",
nl: "⚠️ Deze laag wordt van een externe bron geladen, namelijk `{geojsonSource}`", nl: "⚠️ Deze laag wordt van een externe bron geladen, namelijk `{geojsonSource}`",
}, }).Subs(this.source)
).Subs(this.source),
) )
} else if (!this.source) { } else if (!this.source) {
add({ add({
en: "This is a special layer, probably a library layer or support layer for MapComplete", 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", nl: "Dit is een speciale laag, waarschijnlijk een bibliotheeklaag of een ondersteunende laag",
}) })
} else { } else {
const neededTags = <TagsFilter>this.source.osmTags.optimize() const neededTags = <TagsFilter>this.source.osmTags.optimize()
if (neededTags["and"]) { if (neededTags["and"]) {
add( add({
{ en: "Elements on this layer match **all** of the following expressions:",
en: "Elements on this layer match **all** of the following expressions:", nl: "Elementen in deze laag hebben **alle** van de volgende kenmerken:",
nl: "Elementen in deze laag hebben **alle** van de volgende kenmerken:", })
},
)
const parts = neededTags["and"] const parts = neededTags["and"]
add( add(parts.map((p, i) => i + ". " + p.asHumanString(true, false, {})).join("\n"))
parts.map((p, i) => i + ". " + p.asHumanString(true, false, {})).join("\n"),
)
} else if (neededTags["or"]) { } else if (neededTags["or"]) {
const parts = neededTags["or"] const parts = neededTags["or"]
add( add({
{ en: "Elements on this layer match **any** of the following expressions:",
en: "Elements on this layer match **any** of the following expressions:", nl: "Elementen in deze laag hebben **minstens één** van de volgende kenmerken:",
nl: "Elementen in deze laag hebben **minstens één** van de volgende kenmerken:", })
add(parts.map((p) => " - " + p.asHumanString(true, false, {})).join("\n"))
})
add(
parts.map((p) => " - " + p.asHumanString(true, false, {})).join("\n"),
)
} else { } else {
add({ add({
en: "Elements on this layer match the following expression:", en: "Elements on this layer match the following expression:",
nl: "Elementen in deze laag hebben de volgende kenmerken:", nl: "Elementen in deze laag hebben de volgende kenmerken:",
}) })
add( add(neededTags.asHumanString(true, false, {}))
neededTags.asHumanString(true, false, {}),
)
} }
const link = Overpass.AsOverpassTurboLink(
const link = Overpass.AsOverpassTurboLink(<TagsFilter>this.source.osmTags.optimize()) <TagsFilter>this.source.osmTags.optimize()
)
.replaceAll("(", "%28") .replaceAll("(", "%28")
.replaceAll(")", "%29") .replaceAll(")", "%29")
const txt = new TypedTranslation<{ link }>( const txt = new TypedTranslation<{ link }>({
{ en: "Execute on overpass-turbo.eu",
en: "Execute on overpass-turbo.eu", nl: "Uitvoeren op overpass-turbo.eu",
nl: "Uitvoeren op overpass-turbo.eu", }).textFor(lang)
},
).textFor(lang)
paragraphs.push(`[🗺️ ${txt}](${link})`) paragraphs.push(`[🗺️ ${txt}](${link})`)
} }
} }
// Presets // Presets
{ {
if (this.presets.length > 0) { if (this.presets.length > 0) {
add(new Translation({ add(
en: "## Presets", new Translation({
nl: "## Nieuwe punten toevoegen", en: "## Presets",
})) nl: "## Nieuwe punten toevoegen",
})
)
add(new Translation({ add(
en: "The following options to create new points are included:", new Translation({
nl: "De volgende opties bestaan om nieuwe punten toe te voegen:", 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({ const hTags = new Translation({
en: "Used tags", en: "Used tags",
@ -667,28 +649,24 @@ export default class LayerConfig extends WithContextLoader {
nl: "Beschrijving", nl: "Beschrijving",
}) })
add(MarkdownUtils.table( add(
[ MarkdownUtils.table(
hTags, [hTags, hTitle, hSnaps, hDescription].map((t) => t.textFor(lang)),
hTitle, this.presets.map((preset) => {
hSnaps, let category = preset.title.textFor(lang)
hDescription, category = "**" + category + "**"
].map(t => t.textFor(lang)), return [
this.presets.map(preset => { new And(preset.tags).asHumanString(true),
let category = preset.title.textFor(lang) Translations.t.general.add.addNew.Subs({ category }).textFor(lang),
category = "**" + category + "**" preset.preciseInput?.snapToLayers
return [ ?.map((id) => `[\`${id}\`](./Layers/${id}.md)`)
new And(preset.tags).asHumanString(true), ?.join(", "),
Translations.t.general.add.addNew.Subs({ category }).textFor(lang), preset.description?.textFor(lang),
preset.preciseInput?.snapToLayers ]
?.map((id) => `[\`${id}\`](./Layers/${id}.md)`) }),
?.join(", "), { dropEmptyColumns: true }
preset.description?.textFor(lang), )
] )
}),
{ dropEmptyColumns: true },
))
} else { } else {
add({ add({
en: "This layer does not allowing adding new points", en: "This layer does not allowing adding new points",
@ -699,26 +677,33 @@ export default class LayerConfig extends WithContextLoader {
// Supported attributes table // Supported attributes table
{ {
const keyValues = (Lists.noNull(this.tagRenderings const keyValues = Lists.noNull(
.map((tr) => tr.freeformValues()) this.tagRenderings
.filter((values) => values !== undefined) .map((tr) => tr.freeformValues())
.filter((values) => values.key !== "id"))) .filter((values) => values !== undefined)
keyValues.sort((a, b) => a.key < b.key ? -1 : 1) .filter((values) => values.key !== "id")
)
keyValues.sort((a, b) => (a.key < b.key ? -1 : 1))
if (keyValues.length > 0) { if (keyValues.length > 0) {
add(MarkdownUtils.title(2, new Translation({ add(
en: "Attribute overview", MarkdownUtils.title(
nl: "Attributenoverzicht", 2,
}))) new Translation({
en: "Attribute overview",
nl: "Attributenoverzicht",
})
)
)
add({ add({
en: "This table gives an overview of most OpenStreetMap [keys](https://wiki.openstreetmap.org/wiki/Tags) that this layer shows and/or edits", 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", 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 tableRows: string[][] = keyValues.map((values) => {
const embedded: string[] = values.values?.map((v) => 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_"] ) ?? ["_no preset options defined, or no values in them_"]
const statistics = `https://taghistory.raifer.tech/?#***/${encodeURIComponent( const statistics = `https://taghistory.raifer.tech/?#***/${encodeURIComponent(
values.key, values.key
)}/` )}/`
const tagInfo = `https://taginfo.openstreetmap.org/keys/${values.key}#values` const tagInfo = `https://taginfo.openstreetmap.org/keys/${values.key}#values`
return [ return [
@ -734,55 +719,71 @@ export default class LayerConfig extends WithContextLoader {
] ]
}) })
const header: { en: string, nl: string }[] = [ const header: { en: string; nl: string }[] = [
{ en: "Key", nl: "OSM-sleutel" }, { { en: "Key", nl: "OSM-sleutel" },
{
en: "Type (for freeform input)", en: "Type (for freeform input)",
nl: "Inputtype", nl: "Inputtype",
}, { en: "Predefined, supported options", nl: "Voorgedefinieerde, ondersteunde opties" }, },
{
en: "Predefined, supported options",
nl: "Voorgedefinieerde, ondersteunde opties",
},
] ]
add( add(
MarkdownUtils.table( MarkdownUtils.table(
header.map(tr => new Translation(tr).textFor(lang)), header.map((tr) => new Translation(tr).textFor(lang)),
tableRows tableRows
) )
) )
}
} }
}
// Elements in the popup // Elements in the popup
{ {
add(MarkdownUtils.title(2, new Translation({ add(
en: "Overview of questions (and other elements) in the popup", MarkdownUtils.title(
nl: "Overzicht van vragen (en andere elementen) in de popup", 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) { for (const tagRendering of this.tagRenderings) {
if (tagRendering.labels.indexOf("ignore_docs") >= 0) { if (tagRendering.labels.indexOf("ignore_docs") >= 0) {
continue continue
} }
add(tagRendering.generateDocumentation( add(
this.id, tagRendering.generateDocumentation(
lang, this.id,
reusedTagRenderings?.get(tagRendering.id)?.map((l) => l.layer), lang,
)) reusedTagRenderings?.get(tagRendering.id)?.map((l) => l.layer)
)
)
} }
} }
// Filters // Filters
if (this.filters.length > 0) { if (this.filters.length > 0) {
add(MarkdownUtils.title(2, new Translation({ add(
en: "Filters", MarkdownUtils.title(
nl: "Filters" 2,
}))) new Translation({
en: "Filters",
nl: "Filters",
})
)
)
for (const filter of this.filters) { for (const filter of this.filters) {
add(filter.generateDocs({lang})) add(filter.generateDocs({ lang }))
} }
} }
return Lists.noEmpty(
return (Lists.noEmpty(Lists.noNull(paragraphs).map(p => typeof p === "string" ? p : p.textFor(lang)))).join("\n\n") Lists.noNull(paragraphs).map((p) => (typeof p === "string" ? p : p.textFor(lang)))
).join("\n\n")
} }
public CustomCodeSnippets(): string[] { public CustomCodeSnippets(): string[] {

View file

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

View file

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

View file

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

View file

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

View file

@ -24,7 +24,7 @@
}) })
let mapRotation = mapProperties.rotation let mapRotation = mapProperties.rotation
let showHint = new UIEventSource(false) let showHint = new UIEventSource(false)
showHint.stabilized(4000).addCallback(isShown => { showHint.stabilized(4000).addCallback((isShown) => {
if (isShown) { if (isShown) {
showHint.set(false) showHint.set(false)
} }
@ -51,19 +51,23 @@
} }
} }
let orientationText = mapProperties.rotation.mapD(r => { let orientationText = mapProperties.rotation.mapD((r) => {
const key = GeoOperations.bearingToHuman(r) const key = GeoOperations.bearingToHuman(r)
return Translations.t.compass[key] return Translations.t.compass[key]
}) })
let deviceOrientation = Orientation.singleton.alpha let deviceOrientation = Orientation.singleton.alpha
</script> </script>
{#if $allowRotation || $gotNonZero} {#if $allowRotation || $gotNonZero}
<button class={"as-link pointer-events-auto relative " + size} on:click={() => clicked()} <button
on:mouseenter={() => hovered.set(true)} on:mouseleave={() => hovered.set(false)} class={"as-link pointer-events-auto relative " + size}
on:focus={() => focused.set(true)} on:blur={() => focused.set(false)}> 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} {#if $allowRotation && !$compassLoaded && !$gotNonZero}
<div <div
class={"rounded-full border-2 border-dotted border-gray-500 " + wrapperClass} 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"> <div class:h-0={!onlyLink} class:h-full={onlyLink} class="overflow-hidden">
<MenuDrawerIndex {state} {onlyLink}> <MenuDrawerIndex {state} {onlyLink}>
<svelte:fragment slot="offline-management"> <svelte:fragment slot="offline-management">
<Page {onlyLink} shown={pg.manageOffline} fullscreen> <Page {onlyLink} shown={pg.manageOffline} fullscreen>
<svelte:fragment slot="header"> <svelte:fragment slot="header">
@ -118,9 +116,7 @@
{#if theme.official} {#if theme.official}
<a <a
class="flex" class="flex"
href={"https://docs.mapcomplete.org/#/Themes/" + href={"https://docs.mapcomplete.org/#/Themes/" + theme.id + ".md"}
theme.id +
".md"}
target="_blank" target="_blank"
> >
<DocumentMagnifyingGlass class="h-6 w-6" /> <DocumentMagnifyingGlass class="h-6 w-6" />

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -324,35 +324,33 @@
let disabledMappings: boolean[] = [] let disabledMappings: boolean[] = []
$: { $: {
if(config.multiAnswer){ if (config.multiAnswer) {
for (let i = 0; i < config.mappings.length; i++){ for (let i = 0; i < config.mappings.length; i++) {
if(disabledMappings[i] === undefined){ if (disabledMappings[i] === undefined) {
disabledMappings.push(false) disabledMappings.push(false)
} }
disabledMappings[i] = false disabledMappings[i] = false
if(checkedMappings[i]){ if (checkedMappings[i]) {
continue continue
} }
const fakeCheckedMappings = [ ...checkedMappings] const fakeCheckedMappings = [...checkedMappings]
fakeCheckedMappings[i] = true fakeCheckedMappings[i] = true
try{ try {
const ifMappingSelected = config.constructChangeSpecification(
const ifMappingSelected = config.constructChangeSpecification( $freeformInput,
$freeformInput, selectedMapping,
selectedMapping, fakeCheckedMappings,
fakeCheckedMappings, $tags,
$tags, unit
unit )
) if (ifMappingSelected === undefined) {
if(ifMappingSelected === undefined){
disabledMappings[i] = true disabledMappings[i] = true
} }
}catch (e) { } catch (e) {
disabledMappings[i] = true disabledMappings[i] = true
} }
} }
} }
} }
let featureSwitchIsTesting = state?.featureSwitchIsTesting ?? new ImmutableStore(false) 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"> <div class="low-interaction flex flex-col gap-y-2 p-4">
{#if $allowFilters} {#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}
{#if $searchTerm.length === 0 && $activeFilters.length === 0} {#if $searchTerm.length === 0 && $activeFilters.length === 0}
<div class="items-center p-8 text-center"> <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) { if (maproulette_id_key === "" || maproulette_id_key === undefined) {
maproulette_id_key = "mr_taskId" maproulette_id_key = "mr_taskId"
} }
if(statusToSet === ""){ if (statusToSet === "") {
statusToSet = "1" statusToSet = "1"
} }
return new SvelteUIElement(MaprouletteSetStatus, { return new SvelteUIElement(MaprouletteSetStatus, {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -10950,6 +10950,10 @@
"if": "value=extinguisher", "if": "value=extinguisher",
"then": "extinguisher - Map layer to show fire extinguishers." "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", "if": "value=fire_station",
"then": "fire_station - Map layer to show fire stations." "then": "fire_station - Map layer to show fire stations."

View file

@ -829,6 +829,10 @@
"if": "value=extinguisher", "if": "value=extinguisher",
"then": "<b>extinguisher</b> (builtin) - Map layer to show fire extinguishers." "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", "if": "value=fire_station",
"then": "<b>fire_station</b> (builtin) - Map layer to show fire stations." "then": "<b>fire_station</b> (builtin) - Map layer to show fire stations."
@ -13695,6 +13699,10 @@
"if": "value=extinguisher", "if": "value=extinguisher",
"then": "extinguisher - Map layer to show fire extinguishers." "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", "if": "value=fire_station",
"then": "fire_station - Map layer to show fire stations." "then": "fire_station - Map layer to show fire stations."
@ -35645,6 +35653,10 @@
"if": "value=extinguisher", "if": "value=extinguisher",
"then": "extinguisher - Map layer to show fire extinguishers." "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", "if": "value=fire_station",
"then": "fire_station - Map layer to show fire stations." "then": "fire_station - Map layer to show fire stations."

View file

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

View file

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