Refactoring: allow to reuse units, move all units into central file
This commit is contained in:
parent
067fb549c1
commit
94e07d5b13
30 changed files with 1495 additions and 1307 deletions
|
@ -349,48 +349,27 @@
|
|||
],
|
||||
"units": [
|
||||
{
|
||||
"appliesToKey": [
|
||||
"door:width",
|
||||
"elevator:width",
|
||||
"elevator:depth"
|
||||
],
|
||||
"defaultInput": "cm",
|
||||
"applicableUnits": [
|
||||
{
|
||||
"canonicalDenomination": "m",
|
||||
"alternativeDenomination": [
|
||||
"meter"
|
||||
],
|
||||
"useIfNoUnitGiven": true,
|
||||
"human": {
|
||||
"en": "meter",
|
||||
"fr": "mètre",
|
||||
"de": "Meter",
|
||||
"nl": "meter",
|
||||
"pa_PK": "میٹر",
|
||||
"pl": "metr",
|
||||
"ca": "metre",
|
||||
"cs": "metr"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "cm",
|
||||
"alternativeDenomination": [
|
||||
"centimeter",
|
||||
"cms"
|
||||
],
|
||||
"human": {
|
||||
"en": "centimeter",
|
||||
"fr": "centimètre",
|
||||
"de": "Zentimeter",
|
||||
"nl": "centimeter",
|
||||
"pa_PK": "سینٹیمیٹر",
|
||||
"pl": "centymetr",
|
||||
"ca": "centímetre",
|
||||
"cs": "centimetr"
|
||||
}
|
||||
}
|
||||
]
|
||||
"door:width": {
|
||||
"quantity": "distance",
|
||||
"canonical": "m",
|
||||
"denominations": [
|
||||
"cm"
|
||||
]
|
||||
},
|
||||
"elevator:width": {
|
||||
"quantity": "distance",
|
||||
"canonical": "m",
|
||||
"denominations": [
|
||||
"cm"
|
||||
]
|
||||
},
|
||||
"elevator:depth": {
|
||||
"quantity": "distance",
|
||||
"canonical": "m",
|
||||
"denominations": [
|
||||
"cm"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -567,47 +567,20 @@
|
|||
],
|
||||
"units": [
|
||||
{
|
||||
"appliesToKey": [
|
||||
"kerb:height",
|
||||
"width"
|
||||
],
|
||||
"defaultInput": "cm",
|
||||
"applicableUnits": [
|
||||
{
|
||||
"useIfNoUnitGiven": true,
|
||||
"canonicalDenomination": "m",
|
||||
"alternativeDenomination": [
|
||||
"meter"
|
||||
],
|
||||
"human": {
|
||||
"en": "meter",
|
||||
"fr": "mètre",
|
||||
"de": "Meter",
|
||||
"nl": "meter",
|
||||
"pa_PK": "میٹر",
|
||||
"pl": "metr",
|
||||
"ca": "metre",
|
||||
"cs": "metr"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "cm",
|
||||
"alternativeDenomination": [
|
||||
"centimeter",
|
||||
"cms"
|
||||
],
|
||||
"human": {
|
||||
"en": "centimeter",
|
||||
"fr": "centimètre",
|
||||
"de": "Zentimeter",
|
||||
"nl": "centimeter",
|
||||
"pa_PK": "سینٹیمیٹر",
|
||||
"pl": "centrymetr",
|
||||
"ca": "centimetre",
|
||||
"cs": "centimetr"
|
||||
}
|
||||
}
|
||||
]
|
||||
"kerb:height": {
|
||||
"quantity": "distance",
|
||||
"canonical": "m",
|
||||
"denominations": [
|
||||
"cm"
|
||||
]
|
||||
},
|
||||
"width": {
|
||||
"quantity": "distance",
|
||||
"canonical": "m",
|
||||
"denominations": [
|
||||
"cm"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -549,37 +549,12 @@
|
|||
],
|
||||
"units": [
|
||||
{
|
||||
"applicableUnits": [
|
||||
{
|
||||
"canonicalDenomination": "",
|
||||
"alternativeDenomination": [
|
||||
"mm",
|
||||
"millimeter",
|
||||
"millimeters"
|
||||
],
|
||||
"human": {
|
||||
"en": "millimeters",
|
||||
"nl": "millimeter",
|
||||
"de": "Millimeter",
|
||||
"pa_PK": "ملیمیٹر",
|
||||
"ru": "миллиметры",
|
||||
"ca": "mil·límetres",
|
||||
"cs": "milimetry"
|
||||
},
|
||||
"humanSingular": {
|
||||
"en": "millimeter",
|
||||
"nl": "millimeter",
|
||||
"de": "Millimeter",
|
||||
"pa_PK": "ملیمیٹر",
|
||||
"ru": "миллиметр",
|
||||
"ca": "mil·límetre",
|
||||
"cs": "milimetr"
|
||||
}
|
||||
}
|
||||
],
|
||||
"appliesToKey": [
|
||||
"fire_hydrant:diameter"
|
||||
]
|
||||
"fire_hydrant:diameter": {
|
||||
"quantity": "distance",
|
||||
"denominations": [
|
||||
"mm"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -394,69 +394,13 @@
|
|||
],
|
||||
"units": [
|
||||
{
|
||||
"applicableUnits": [
|
||||
{
|
||||
"canonicalDenomination": "cm",
|
||||
"alternativeDenomination": [
|
||||
"centimeter",
|
||||
"centimeters"
|
||||
],
|
||||
"human": {
|
||||
"en": "centimeters",
|
||||
"nl": "centimeter",
|
||||
"de": "Zentimeter",
|
||||
"fr": "centimètres",
|
||||
"pa_PK": "سینٹیمیٹر",
|
||||
"ru": "сантиметры",
|
||||
"ca": "centímetres",
|
||||
"pl": "centymetry",
|
||||
"cs": "centimetry"
|
||||
},
|
||||
"humanSingular": {
|
||||
"en": "centimeter",
|
||||
"nl": "centimeter",
|
||||
"de": "Zentimeter",
|
||||
"fr": "centimètre",
|
||||
"pa_PK": "سینٹیمیٹر",
|
||||
"ru": "сантиметр",
|
||||
"ca": "centímetre",
|
||||
"pl": "centymetr",
|
||||
"cs": "centimetr"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "m",
|
||||
"alternativeDenomination": [
|
||||
"meter",
|
||||
"meters"
|
||||
],
|
||||
"human": {
|
||||
"en": "meters",
|
||||
"nl": "meter",
|
||||
"de": "Meter",
|
||||
"fr": "mètres",
|
||||
"pa_PK": "میٹر",
|
||||
"ru": "метры",
|
||||
"ca": "metres",
|
||||
"pl": "metry",
|
||||
"cs": "metry"
|
||||
},
|
||||
"humanSingular": {
|
||||
"en": "meter",
|
||||
"nl": "meter",
|
||||
"de": "Meter",
|
||||
"fr": "mètre",
|
||||
"pa_PK": "میٹر",
|
||||
"ru": "метр",
|
||||
"ca": "metre",
|
||||
"pl": "metr",
|
||||
"cs": "metr"
|
||||
}
|
||||
}
|
||||
],
|
||||
"appliesToKey": [
|
||||
"kerb:height"
|
||||
]
|
||||
"kerb:height": {
|
||||
"quantity": "distance",
|
||||
"denominations": [
|
||||
"cm",
|
||||
"m"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -155,73 +155,13 @@
|
|||
"allowSplit": true,
|
||||
"units": [
|
||||
{
|
||||
"applicableUnits": [
|
||||
{
|
||||
"#": "km/h is the default for a maxspeed; should be empty string",
|
||||
"canonicalDenomination": "",
|
||||
"alternativeDenomination": [
|
||||
"km/u",
|
||||
"kmh",
|
||||
"kph"
|
||||
],
|
||||
"human": {
|
||||
"en": "kilometers/hour",
|
||||
"ca": "quilòmetres/hora",
|
||||
"es": "kilómetros/hora",
|
||||
"nl": "kilometers/uur",
|
||||
"de": "Kilometer/Stunde",
|
||||
"pa_PK": "ہر گھنٹہ وچ کیلومیٹر",
|
||||
"fr": "kilomètres/heure",
|
||||
"cs": "km/hod"
|
||||
},
|
||||
"humanShort": {
|
||||
"en": "km/h",
|
||||
"ca": "km/h",
|
||||
"es": "km/h",
|
||||
"nl": "km/u",
|
||||
"de": "km/h",
|
||||
"pa_PK": "ہر گھنٹے وچ کیلومیٹر",
|
||||
"ru": "км/ч",
|
||||
"fr": "km/h",
|
||||
"cs": "km/h"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "mph",
|
||||
"useIfNoUnitGiven": [
|
||||
"gb",
|
||||
"us"
|
||||
],
|
||||
"alternativeDenomination": [
|
||||
"m/u",
|
||||
"mh",
|
||||
"m/ph"
|
||||
],
|
||||
"human": {
|
||||
"en": "miles/hour",
|
||||
"ca": "milles/hora",
|
||||
"es": "millas/hora",
|
||||
"nl": "miles/uur",
|
||||
"de": "Meilen/Stunde",
|
||||
"pa_PK": "ہر گھنٹہ وچ میل",
|
||||
"fr": "miles/heure",
|
||||
"cs": "míle/hod"
|
||||
},
|
||||
"humanShort": {
|
||||
"en": "mph",
|
||||
"ca": "mph",
|
||||
"es": "mph",
|
||||
"nl": "mph",
|
||||
"de": "mph",
|
||||
"pa_PK": "ہر گھنٹہ وچ میل",
|
||||
"fr": "mph",
|
||||
"cs": "mph"
|
||||
}
|
||||
}
|
||||
],
|
||||
"appliesToKey": [
|
||||
"maxspeed"
|
||||
]
|
||||
"maxspeed": {
|
||||
"quantity": "speed",
|
||||
"canonical": "kmh",
|
||||
"denominations": [
|
||||
"mph"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -362,29 +362,12 @@
|
|||
},
|
||||
"units": [
|
||||
{
|
||||
"appliesToKey": [
|
||||
"height"
|
||||
],
|
||||
"applicableUnits": [
|
||||
{
|
||||
"canonicalDenomination": "m",
|
||||
"alternativeDenomination": [
|
||||
"meter",
|
||||
"mtr"
|
||||
],
|
||||
"human": {
|
||||
"nl": " meter",
|
||||
"en": " meter",
|
||||
"ru": " метр",
|
||||
"de": " Meter",
|
||||
"ca": " metre",
|
||||
"es": " metros",
|
||||
"pl": " metr",
|
||||
"cs": " metr"
|
||||
}
|
||||
}
|
||||
],
|
||||
"eraseInvalidValues": true
|
||||
"height": {
|
||||
"quantity": "distance",
|
||||
"denominations": [
|
||||
"m"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -98,44 +98,13 @@
|
|||
],
|
||||
"units": [
|
||||
{
|
||||
"appliesToKey": [
|
||||
"desk:height"
|
||||
],
|
||||
"applicableUnits": [
|
||||
{
|
||||
"canonicalDenomination": "m",
|
||||
"alternativeDenomination": [
|
||||
"meter"
|
||||
],
|
||||
"human": {
|
||||
"en": "meter",
|
||||
"fr": "mètre",
|
||||
"de": "Meter",
|
||||
"nl": "meter",
|
||||
"pa_PK": "میٹر",
|
||||
"ca": "metre",
|
||||
"pl": "metr",
|
||||
"cs": "metr"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "cm",
|
||||
"alternativeDenomination": [
|
||||
"centimeter",
|
||||
"cms"
|
||||
],
|
||||
"human": {
|
||||
"en": "centimeter",
|
||||
"fr": "centimètre",
|
||||
"de": "Zentimeter",
|
||||
"nl": "centimeter",
|
||||
"pa_PK": "سینٹیمیٹر",
|
||||
"ca": "centímetre",
|
||||
"pl": "centymetr",
|
||||
"cs": "centimetr"
|
||||
}
|
||||
}
|
||||
]
|
||||
"desk:height": {
|
||||
"quantity": "distance",
|
||||
"denominations": [
|
||||
"m",
|
||||
"cm"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -116,64 +116,13 @@
|
|||
],
|
||||
"units": [
|
||||
{
|
||||
"appliesToKey": [
|
||||
"maxspeed"
|
||||
],
|
||||
"applicableUnits": [
|
||||
{
|
||||
"#": "km/h is the default for a maxspeed; should be empty string",
|
||||
"canonicalDenomination": "",
|
||||
"alternativeDenomination": [
|
||||
"km/u",
|
||||
"kmh",
|
||||
"kph"
|
||||
],
|
||||
"human": {
|
||||
"en": "kilometers/hour",
|
||||
"ca": "quilòmetres/hora",
|
||||
"es": "kilómetros/hora",
|
||||
"nl": "kilometers/uur",
|
||||
"de": "Kilometer/Stunde",
|
||||
"cs": "kilometry/hodinu"
|
||||
},
|
||||
"humanShort": {
|
||||
"en": "km/h",
|
||||
"ca": "km/h",
|
||||
"es": "km/h",
|
||||
"nl": "km/u",
|
||||
"de": "km/h",
|
||||
"cs": "km/h"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "mph",
|
||||
"useIfNoUnitGiven": [
|
||||
"gb",
|
||||
"us"
|
||||
],
|
||||
"alternativeDenomination": [
|
||||
"m/u",
|
||||
"mh",
|
||||
"m/ph"
|
||||
],
|
||||
"human": {
|
||||
"en": "miles/hour",
|
||||
"ca": "milles/hora",
|
||||
"es": "millas/hora",
|
||||
"nl": "miles/uur",
|
||||
"de": "Meilen/Stunde",
|
||||
"cs": "míle/hodinu"
|
||||
},
|
||||
"humanShort": {
|
||||
"en": "mph",
|
||||
"ca": "mph",
|
||||
"es": "mph",
|
||||
"nl": "mph",
|
||||
"de": "mph",
|
||||
"cs": "mph"
|
||||
}
|
||||
}
|
||||
]
|
||||
"maxspeed": {
|
||||
"quantity": "speed",
|
||||
"denominations": [
|
||||
"kmh",
|
||||
"mph"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -115,64 +115,13 @@
|
|||
],
|
||||
"units": [
|
||||
{
|
||||
"appliesToKey": [
|
||||
"maxspeed"
|
||||
],
|
||||
"applicableUnits": [
|
||||
{
|
||||
"#": "km/h is the default for a maxspeed; should be empty string",
|
||||
"canonicalDenomination": "",
|
||||
"alternativeDenomination": [
|
||||
"km/u",
|
||||
"kmh",
|
||||
"kph"
|
||||
],
|
||||
"human": {
|
||||
"en": "kilometers/hour",
|
||||
"ca": "quilòmetres/hora",
|
||||
"es": "kilómetros/hora",
|
||||
"nl": "kilometers/uur",
|
||||
"de": "Kilometer/Stunde",
|
||||
"cs": "kilometry/hodinu"
|
||||
},
|
||||
"humanShort": {
|
||||
"en": "km/h",
|
||||
"ca": "km/h",
|
||||
"es": "km/h",
|
||||
"nl": "km/u",
|
||||
"de": "km/h",
|
||||
"cs": "km/h"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "mph",
|
||||
"useIfNoUnitGiven": [
|
||||
"gb",
|
||||
"us"
|
||||
],
|
||||
"alternativeDenomination": [
|
||||
"m/u",
|
||||
"mh",
|
||||
"m/ph"
|
||||
],
|
||||
"human": {
|
||||
"en": "miles/hour",
|
||||
"ca": "milles/hora",
|
||||
"es": "millas/hora",
|
||||
"nl": "miles/uur",
|
||||
"de": "Meilen/Stunde",
|
||||
"cs": "míle/hodinu"
|
||||
},
|
||||
"humanShort": {
|
||||
"en": "mph",
|
||||
"ca": "mph",
|
||||
"es": "mph",
|
||||
"nl": "mph",
|
||||
"de": "mph",
|
||||
"cs": "mph"
|
||||
}
|
||||
}
|
||||
]
|
||||
"maxspeed": {
|
||||
"quantity": "speed",
|
||||
"canonical": "kmh",
|
||||
"denominations": [
|
||||
"mph"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -460,7 +460,8 @@
|
|||
"if": "surface=fine_gravel",
|
||||
"then": {
|
||||
"en": "The surface is <b>fine gravel</b>",
|
||||
"nl": "De ondergrond bestaat uit <b>grind</b>"
|
||||
"nl": "De ondergrond bestaat uit <b>grind</b>",
|
||||
"de": "Die Oberfläche ist <b>feiner Kies</b>"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
@ -841,44 +841,13 @@
|
|||
},
|
||||
"units": [
|
||||
{
|
||||
"appliesToKey": [
|
||||
"door:width"
|
||||
],
|
||||
"applicableUnits": [
|
||||
{
|
||||
"canonicalDenomination": "m",
|
||||
"alternativeDenomination": [
|
||||
"meter"
|
||||
],
|
||||
"human": {
|
||||
"en": "meter",
|
||||
"nl": "meter",
|
||||
"fr": "mètre",
|
||||
"de": "Meter",
|
||||
"da": "meter",
|
||||
"pa_PK": "میٹر",
|
||||
"ca": "metre",
|
||||
"cs": "metr"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "cm",
|
||||
"alternativeDenomination": [
|
||||
"centimeter",
|
||||
"cms"
|
||||
],
|
||||
"human": {
|
||||
"en": "centimeter",
|
||||
"nl": "centimeter",
|
||||
"fr": "centimètre",
|
||||
"de": "Zentimeter",
|
||||
"da": "centimeter",
|
||||
"pa_PK": "سینٹیمیٹر",
|
||||
"ca": "centimetre",
|
||||
"cs": "centimetr"
|
||||
}
|
||||
}
|
||||
]
|
||||
"door:width": {
|
||||
"quantity": "distance",
|
||||
"denominations": [
|
||||
"m",
|
||||
"cm"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -480,42 +480,13 @@
|
|||
},
|
||||
"units": [
|
||||
{
|
||||
"appliesToKey": [
|
||||
"toilets:door:width"
|
||||
],
|
||||
"applicableUnits": [
|
||||
{
|
||||
"canonicalDenomination": "m",
|
||||
"alternativeDenomination": [
|
||||
"meter"
|
||||
],
|
||||
"human": {
|
||||
"en": "meter",
|
||||
"nl": "meter",
|
||||
"fr": "mètre",
|
||||
"de": "Meter",
|
||||
"da": "meter",
|
||||
"ca": "metre",
|
||||
"cs": "metr"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "cm",
|
||||
"alternativeDenomination": [
|
||||
"centimeter",
|
||||
"cms"
|
||||
],
|
||||
"human": {
|
||||
"en": "centimeter",
|
||||
"nl": "centimeter",
|
||||
"fr": "centimètre",
|
||||
"de": "Zentimeter",
|
||||
"da": "centimeter",
|
||||
"ca": "centimetre",
|
||||
"cs": "centimetr"
|
||||
}
|
||||
}
|
||||
]
|
||||
"toilets:door:width": {
|
||||
"quantity": "distance",
|
||||
"denominations": [
|
||||
"m",
|
||||
"cm"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
384
assets/layers/unit/unit.json
Normal file
384
assets/layers/unit/unit.json
Normal file
|
@ -0,0 +1,384 @@
|
|||
{
|
||||
"id": "unit",
|
||||
"description": {
|
||||
"en": "Library layer with all (common) units. Units can _only_ be imported from this file"
|
||||
},
|
||||
"source": "special:library",
|
||||
"units": [
|
||||
{
|
||||
"quantity": "power",
|
||||
"applicableUnits": [
|
||||
{
|
||||
"canonicalDenomination": "MW",
|
||||
"alternativeDenomination": [
|
||||
"megawatts",
|
||||
"megawatt"
|
||||
],
|
||||
"human": {
|
||||
"en": "{quantity} megawatts",
|
||||
"nl": "{quantity} megawatt",
|
||||
"fr": "{quantity} megawatts",
|
||||
"de": "{quantity} Megawatt",
|
||||
"eo": "{quantity} megavatoj",
|
||||
"it": "{quantity} megawatt",
|
||||
"ru": "{quantity} мегаватт",
|
||||
"zh_Hant": "{quantity} 百萬瓦",
|
||||
"id": "{quantity} megawat",
|
||||
"hu": "{quantity} megawatt",
|
||||
"ca": "{quantity} megavats",
|
||||
"da": "{quantity} Megawatt",
|
||||
"cs": "{quantity} megawatty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "kW",
|
||||
"alternativeDenomination": [
|
||||
"kilowatts",
|
||||
"kilowatt"
|
||||
],
|
||||
"human": {
|
||||
"en": "{quantity} kilowatt",
|
||||
"nl": "{quantity} kilowatt",
|
||||
"fr": "{quantity} kilowatts",
|
||||
"de": "{quantity} Kilowatt",
|
||||
"eo": "{quantity} kilovatoj",
|
||||
"it": "{quantity} kilowatt",
|
||||
"nb_NO": "{quantity} kilowatt",
|
||||
"ru": "{quantity} киловатт",
|
||||
"zh_Hant": "{quantity} 千瓦",
|
||||
"id": "{quantity} kilowat",
|
||||
"hu": "{quantity} kilowatt",
|
||||
"ca": "{quantity} quilovats",
|
||||
"da": "{quantity} Kilowatt",
|
||||
"cs": "{quantity} kilowatty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "W",
|
||||
"alternativeDenomination": [
|
||||
"watts",
|
||||
"watt"
|
||||
],
|
||||
"human": {
|
||||
"en": "{quantity} watts",
|
||||
"nl": "{quantity} watt",
|
||||
"fr": "{quantity} watts",
|
||||
"de": "{quantity} Watt",
|
||||
"eo": "{quantity} vatoj",
|
||||
"it": "{quantity} watt",
|
||||
"ru": "{quantity} ватт",
|
||||
"id": "{quantity} watt",
|
||||
"hu": "{quantity} watt",
|
||||
"ca": "{quantity} vats",
|
||||
"da": "{quantity} Watt",
|
||||
"cs": "{quantity} watty",
|
||||
"zh_Hant": "{quantity} 瓦"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "GW",
|
||||
"alternativeDenomination": [
|
||||
"gigawatts",
|
||||
"gigawatt"
|
||||
],
|
||||
"human": {
|
||||
"en": "{quantity} gigawatts",
|
||||
"nl": "{quantity} gigawatt",
|
||||
"fr": "{quantity} gigawatts",
|
||||
"de": "{quantity} Gigawatt",
|
||||
"eo": "{quantity} gigavatoj",
|
||||
"it": "{quantity} gigawatt",
|
||||
"ru": "{quantity} гигаватт",
|
||||
"id": "{quantity} gigawatt",
|
||||
"hu": "{quantity} gigawatt",
|
||||
"ca": "{quantity} gigavats",
|
||||
"da": "{quantity} Gigawatt",
|
||||
"cs": "{quantity} gigawatty",
|
||||
"zh_Hant": "{quantity} 千兆瓦"
|
||||
}
|
||||
}
|
||||
],
|
||||
"eraseInvalidValues": true
|
||||
},
|
||||
{
|
||||
"quantity": "voltage",
|
||||
"applicableUnits": [
|
||||
{
|
||||
"canonicalDenomination": "V",
|
||||
"alternativeDenomination": [
|
||||
"v",
|
||||
"volt",
|
||||
"voltage",
|
||||
"Volt"
|
||||
],
|
||||
"human": {
|
||||
"en": "{quantity} Volt",
|
||||
"nl": "{quantity} volt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"eraseInvalidValues": true
|
||||
},
|
||||
{
|
||||
"quantity": "current",
|
||||
"applicableUnits": [
|
||||
{
|
||||
"canonicalDenomination": "A",
|
||||
"alternativeDenomination": [
|
||||
"a",
|
||||
"amp",
|
||||
"amperage",
|
||||
"A"
|
||||
],
|
||||
"human": {
|
||||
"en": "{quantity} A",
|
||||
"nl": "{quantity} A"
|
||||
}
|
||||
}
|
||||
],
|
||||
"eraseInvalidValues": true
|
||||
},
|
||||
{
|
||||
"quantity": "distance",
|
||||
"eraseInvalidValue": true,
|
||||
"applicableUnits": [
|
||||
{
|
||||
"canonicalDenomination": "m",
|
||||
"useIfNoUnitGiven": true,
|
||||
"alternativeDenomination": [
|
||||
"meter",
|
||||
"meters"
|
||||
],
|
||||
"human": {
|
||||
"en": "{quantity} meter",
|
||||
"nl": "{quantity} meter",
|
||||
"fr": "{quantity} mètres",
|
||||
"de": "{quantity} Meter",
|
||||
"eo": "{quantity} metro",
|
||||
"it": "{quantity} metri",
|
||||
"ru": "{quantity} метр",
|
||||
"id": "{quantity} meter",
|
||||
"hu": "{quantity} méter",
|
||||
"ca": "{quantity} metre",
|
||||
"da": "{quantity} meter",
|
||||
"cs": "{quantity} metr",
|
||||
"es": "{quantity} metros",
|
||||
"pl": "{quantity} metr",
|
||||
"pa_PK": "{quantity}میٹر",
|
||||
"zh_Hant": "{quantity} 公尺",
|
||||
"nb_NO": "{quantity} meter",
|
||||
"eu": "{quantity} ·metro"
|
||||
},
|
||||
"humanSingular": {
|
||||
"en": "one meter",
|
||||
"fr": "un mètre",
|
||||
"nl": "één meter",
|
||||
"de": "ein Meter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "cm",
|
||||
"alternativeDenomination": [
|
||||
"centimeter",
|
||||
"centimeters",
|
||||
"cms"
|
||||
],
|
||||
"human": {
|
||||
"en": "{quantity} centimeter",
|
||||
"fr": "{quantity} centimètres",
|
||||
"de": "{quantity} Zentimeter",
|
||||
"da": "{quantity} centimeter",
|
||||
"nl": "{quantity} centimeter",
|
||||
"ca": "{quantity} centimetre",
|
||||
"cs": "{quantity} centimetr",
|
||||
"pl": "{quantity} centymetr",
|
||||
"ru": "{quantity} сантиметры",
|
||||
"pa_PK": " {quantity}سینٹیمیٹر"
|
||||
},
|
||||
"humanSingular": {
|
||||
"en": "one centimeter",
|
||||
"nl": "één centimeter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "mm",
|
||||
"alternativeDenomination": [
|
||||
"millimeter",
|
||||
"millimeters"
|
||||
],
|
||||
"human": {
|
||||
"en": "{quantity} millimeters",
|
||||
"nl": "{quantity} millimeter",
|
||||
"de": "{quantity} Millimeter",
|
||||
"ru": "{quantity} миллиметры",
|
||||
"ca": "{quantity} mil·límetres",
|
||||
"cs": "{quantity} milimetry",
|
||||
"pa_PK": "{quantity} ملیمیٹر"
|
||||
},
|
||||
"humanSingular": {
|
||||
"en": "one millimeter",
|
||||
"nl": "één millimeter",
|
||||
"de": "ein Millimeter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "ft",
|
||||
"alternativeDenomination": [
|
||||
"feet",
|
||||
"voet"
|
||||
],
|
||||
"human": {
|
||||
"en": "{quantity} feet",
|
||||
"nl": "{quantity} voet",
|
||||
"fr": "{quantity} pieds",
|
||||
"de": "{quantity} Fuß",
|
||||
"eo": "{quantity} futo",
|
||||
"it": "{quantity} piedi",
|
||||
"ca": "{quantity} peus",
|
||||
"es": "{quantity} pies",
|
||||
"da": "{quantity} fod",
|
||||
"cs": "{quantity} stopa",
|
||||
"eu": "{quantity} ·hanka",
|
||||
"pl": "{quantity} stopy",
|
||||
"nb_NO": "{quantity} fot",
|
||||
"pa_PK": "{quantity} فوٹ"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"quantity": "speed",
|
||||
"applicableUnits": [
|
||||
{
|
||||
"#": "km/h is the default for a maxspeed; should be empty string",
|
||||
"canonicalDenomination": "kmh",
|
||||
"alternativeDenomination": [
|
||||
"km/u",
|
||||
"km/h",
|
||||
"kph"
|
||||
],
|
||||
"human": {
|
||||
"en": "{quantity} kilometers/hour",
|
||||
"ca": "{quantity} quilòmetres/hora",
|
||||
"es": "{quantity} kilómetros/hora",
|
||||
"nl": "{quantity} kilometers/uur",
|
||||
"de": "{quantity} Kilometer/Stunde",
|
||||
"cs": "{quantity} kilometry/hodinu",
|
||||
"pa_PK": "{quantity}ہر گھنٹہ وچ کیلومیٹر",
|
||||
"fr": "{quantity} kilomètres/heure"
|
||||
},
|
||||
"humanShort": {
|
||||
"en": "{quantity} km/h",
|
||||
"ca": "{quantity} km/h",
|
||||
"es": "{quantity} km/h",
|
||||
"nl": "{quantity} km/u",
|
||||
"de": "{quantity} km/h",
|
||||
"cs": "{quantity} km/h",
|
||||
"pa_PK": "{quantity}ہر گھنٹے وچ کیلومیٹر",
|
||||
"ru": "{quantity} км/ч",
|
||||
"fr": "{quantity} km/h"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "mph",
|
||||
"addSpace": true,
|
||||
"useIfNoUnitGiven": [
|
||||
"gb",
|
||||
"us"
|
||||
],
|
||||
"alternativeDenomination": [
|
||||
"m/u",
|
||||
"mh",
|
||||
"m/ph"
|
||||
],
|
||||
"human": {
|
||||
"en": "{quantity} miles/hour",
|
||||
"ca": "{quantity} milles/hora",
|
||||
"es": "{quantity} millas/hora",
|
||||
"nl": "{quantity} miles/uur",
|
||||
"de": "{quantity} Meilen/Stunde",
|
||||
"cs": "{quantity} míle/hodinu",
|
||||
"fr": "{quantity} miles/heure",
|
||||
"pa_PK": "{quantity} ہر گھنٹہ وچ میل"
|
||||
},
|
||||
"humanShort": {
|
||||
"en": "{quantity} mph",
|
||||
"ca": "{quantity} mph",
|
||||
"es": "{quantity} mph",
|
||||
"nl": "{quantity} mph",
|
||||
"de": "{quantity} mph",
|
||||
"cs": "{quantity} mph",
|
||||
"pa_PK": "{quantity}ہر گھنٹہ وچ میل",
|
||||
"fr": "{quantity} mph"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"quantity": "duration",
|
||||
"applicableUnits": [
|
||||
{
|
||||
"canonicalDenomination": "minutes",
|
||||
"addSpace": true,
|
||||
"canonicalDenominationSingular": "minute",
|
||||
"alternativeDenomination": [
|
||||
"m",
|
||||
"min",
|
||||
"mins",
|
||||
"minuten",
|
||||
"mns"
|
||||
],
|
||||
"human": {
|
||||
"en": "{quantity} minutes",
|
||||
"nl": "{quantity} minuten"
|
||||
},
|
||||
"humanSingular": {
|
||||
"en": "one minute",
|
||||
"nl": "één minuut"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "hours",
|
||||
"addSpace": true,
|
||||
"canonicalDenominationSingular": "hour",
|
||||
"alternativeDenomination": [
|
||||
"h",
|
||||
"hrs",
|
||||
"hours",
|
||||
"u",
|
||||
"uur",
|
||||
"uren"
|
||||
],
|
||||
"human": {
|
||||
"en": "{quantity} hours",
|
||||
"nl": "{quantity} uren"
|
||||
},
|
||||
"humanSingular": {
|
||||
"en": "one hour",
|
||||
"nl": "één uur"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "days",
|
||||
"addSpace": true,
|
||||
"canonicalDenominationSingular": "day",
|
||||
"alternativeDenomination": [
|
||||
"dys",
|
||||
"dagen",
|
||||
"dag"
|
||||
],
|
||||
"human": {
|
||||
"en": "{quantity} days",
|
||||
"nl": "{quantity} day"
|
||||
},
|
||||
"humanSingular": {
|
||||
"en": "one day",
|
||||
"nl": "één dag"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"pointRendering": null,
|
||||
"lineRendering": null
|
||||
}
|
|
@ -123,45 +123,20 @@
|
|||
],
|
||||
"units": [
|
||||
{
|
||||
"appliesToKey": [
|
||||
"width",
|
||||
"_biggest_width"
|
||||
],
|
||||
"defaultUnit": "cm",
|
||||
"applicableUnits": [
|
||||
{
|
||||
"useIfNoUnitGiven": true,
|
||||
"canonicalDenomination": "m",
|
||||
"alternativeDenomination": [
|
||||
"meter"
|
||||
],
|
||||
"human": {
|
||||
"en": "meter",
|
||||
"fr": "mètre",
|
||||
"de": "Meter",
|
||||
"da": "meter",
|
||||
"nl": "meter",
|
||||
"ca": "metre",
|
||||
"cs": "metr"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "cm",
|
||||
"alternativeDenomination": [
|
||||
"centimeter",
|
||||
"cms"
|
||||
],
|
||||
"human": {
|
||||
"en": "centimeter",
|
||||
"fr": "centimètre",
|
||||
"de": "Zentimeter",
|
||||
"da": "centimeter",
|
||||
"nl": "centimeter",
|
||||
"ca": "centimetre",
|
||||
"cs": "centimetr"
|
||||
}
|
||||
}
|
||||
]
|
||||
"width": {
|
||||
"quantity": "distance",
|
||||
"denominations": [
|
||||
"m",
|
||||
"cm"
|
||||
]
|
||||
},
|
||||
"_biggest_width": {
|
||||
"quantity": "distance",
|
||||
"denominations": [
|
||||
"m",
|
||||
"cm"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -300,130 +300,19 @@
|
|||
],
|
||||
"units": [
|
||||
{
|
||||
"appliesToKey": [
|
||||
"generator:output:electricity"
|
||||
],
|
||||
"applicableUnits": [
|
||||
{
|
||||
"canonicalDenomination": "MW",
|
||||
"alternativeDenomination": [
|
||||
"megawatts",
|
||||
"megawatt"
|
||||
],
|
||||
"human": {
|
||||
"en": " megawatts",
|
||||
"nl": " megawatt",
|
||||
"fr": " megawatts",
|
||||
"de": " Megawatt",
|
||||
"eo": " megavatoj",
|
||||
"it": " megawatt",
|
||||
"ru": " мегаватт",
|
||||
"zh_Hant": " 百萬瓦",
|
||||
"id": " megawat",
|
||||
"hu": " megawatt",
|
||||
"ca": " megavats",
|
||||
"da": " Megawatt",
|
||||
"cs": " megawatty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "kW",
|
||||
"alternativeDenomination": [
|
||||
"kilowatts",
|
||||
"kilowatt"
|
||||
],
|
||||
"human": {
|
||||
"en": " kilowatts",
|
||||
"nl": " kilowatt",
|
||||
"fr": " kilowatts",
|
||||
"de": " Kilowatt",
|
||||
"eo": " kilovatoj",
|
||||
"it": " kilowatt",
|
||||
"nb_NO": " kilowatt",
|
||||
"ru": " киловатт",
|
||||
"zh_Hant": " 千瓦",
|
||||
"id": " kilowat",
|
||||
"hu": " kilowatt",
|
||||
"ca": " quilovats",
|
||||
"da": " Kilowatt",
|
||||
"cs": " kilowatty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "W",
|
||||
"alternativeDenomination": [
|
||||
"watts",
|
||||
"watt"
|
||||
],
|
||||
"human": {
|
||||
"en": " watts",
|
||||
"nl": " watt",
|
||||
"fr": " watts",
|
||||
"de": " Watt",
|
||||
"eo": " vatoj",
|
||||
"it": " watt",
|
||||
"ru": " ватт",
|
||||
"zh_Hant": " 瓦",
|
||||
"id": " watt",
|
||||
"hu": " watt",
|
||||
"ca": " vats",
|
||||
"da": " Watt",
|
||||
"cs": " watty"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "GW",
|
||||
"alternativeDenomination": [
|
||||
"gigawatts",
|
||||
"gigawatt"
|
||||
],
|
||||
"human": {
|
||||
"en": " gigawatts",
|
||||
"nl": " gigawatt",
|
||||
"fr": " gigawatts",
|
||||
"de": " Gigawatt",
|
||||
"eo": " gigavatoj",
|
||||
"it": " gigawatt",
|
||||
"ru": " гигаватт",
|
||||
"zh_Hant": " 千兆瓦",
|
||||
"id": " gigawatt",
|
||||
"hu": " gigawatt",
|
||||
"ca": " gigavats",
|
||||
"da": " Gigawatt",
|
||||
"cs": " gigawatty"
|
||||
}
|
||||
}
|
||||
],
|
||||
"eraseInvalidValues": true
|
||||
},
|
||||
{
|
||||
"appliesToKey": [
|
||||
"height",
|
||||
"rotor:diameter"
|
||||
],
|
||||
"applicableUnits": [
|
||||
{
|
||||
"canonicalDenomination": "m",
|
||||
"alternativeDenomination": [
|
||||
"meter"
|
||||
],
|
||||
"human": {
|
||||
"en": " meter",
|
||||
"nl": " meter",
|
||||
"fr": " mètres",
|
||||
"de": " Meter",
|
||||
"eo": " metro",
|
||||
"it": " metri",
|
||||
"ru": " метр",
|
||||
"zh_Hant": " 公尺",
|
||||
"id": " meter",
|
||||
"hu": " méter",
|
||||
"ca": " metre",
|
||||
"da": " meter",
|
||||
"cs": " metr"
|
||||
}
|
||||
}
|
||||
]
|
||||
"generator:output:electricity": "power",
|
||||
"height": {
|
||||
"quantity": "distance",
|
||||
"denominations": [
|
||||
"m"
|
||||
]
|
||||
},
|
||||
"rotor:diamter": {
|
||||
"quantity": "distance",
|
||||
"denominations": [
|
||||
"m"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -120,61 +120,27 @@
|
|||
],
|
||||
"units+": [
|
||||
{
|
||||
"appliesToKey": [
|
||||
"climbing:length",
|
||||
"climbing:length:min",
|
||||
"climbing:length:max"
|
||||
],
|
||||
"applicableUnits": [
|
||||
{
|
||||
"canonicalDenomination": "",
|
||||
"alternativeDenomination": [
|
||||
"m",
|
||||
"meter",
|
||||
"meters"
|
||||
],
|
||||
"human": {
|
||||
"en": " meter",
|
||||
"nl": " meter",
|
||||
"fr": " mètres",
|
||||
"de": " Meter",
|
||||
"eo": " metro",
|
||||
"it": " metri",
|
||||
"ru": " метр",
|
||||
"ca": " metre",
|
||||
"nb_NO": " meter",
|
||||
"es": " metro",
|
||||
"da": " meter",
|
||||
"pa_PK": " میٹر",
|
||||
"cs": " metr",
|
||||
"eu": " ·metro",
|
||||
"pl": " metry"
|
||||
}
|
||||
},
|
||||
{
|
||||
"canonicalDenomination": "ft",
|
||||
"alternativeDenomination": [
|
||||
"feet",
|
||||
"voet"
|
||||
],
|
||||
"human": {
|
||||
"en": " feet",
|
||||
"nl": " voet",
|
||||
"fr": " pieds",
|
||||
"de": " Fuß",
|
||||
"eo": " futo",
|
||||
"it": " piedi",
|
||||
"ca": " peus",
|
||||
"nb_NO": " fot",
|
||||
"es": " pies",
|
||||
"da": " fod",
|
||||
"pa_PK": " فوٹ",
|
||||
"cs": " stopa",
|
||||
"eu": " ·hanka",
|
||||
"pl": " stopy"
|
||||
}
|
||||
}
|
||||
]
|
||||
"climbing:length": {
|
||||
"quantity": "distance",
|
||||
"canonical": "m",
|
||||
"denominations": [
|
||||
"ft"
|
||||
]
|
||||
},
|
||||
"climbing:length:min": {
|
||||
"quantity": "distance",
|
||||
"canonical": "m",
|
||||
"denominations": [
|
||||
"ft"
|
||||
]
|
||||
},
|
||||
"climbing:length:max": {
|
||||
"quantity": "distance",
|
||||
"canonical": "m",
|
||||
"denominations": [
|
||||
"ft"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"tagRenderings+": [
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import Combine from "../src/UI/Base/Combine"
|
||||
import BaseUIElement from "../src/UI/BaseUIElement"
|
||||
import { existsSync, mkdirSync, readFileSync, writeFile, writeFileSync } from "fs"
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"
|
||||
import { AllKnownLayouts } from "../src/Customizations/AllKnownLayouts"
|
||||
import TableOfContents from "../src/UI/Base/TableOfContents"
|
||||
import SimpleMetaTaggers from "../src/Logic/SimpleMetaTagger"
|
||||
|
@ -15,7 +15,7 @@ import themeOverview from "../src/assets/generated/theme_overview.json"
|
|||
import LayoutConfig from "../src/Models/ThemeConfig/LayoutConfig"
|
||||
import bookcases from "../src/assets/generated/themes/bookcases.json"
|
||||
import fakedom from "fake-dom"
|
||||
|
||||
import unit from "../src/assets/generated/layers/unit.json"
|
||||
import Hotkeys from "../src/UI/Base/Hotkeys"
|
||||
import { QueryParameters } from "../src/Logic/Web/QueryParameters"
|
||||
import Link from "../src/UI/Base/Link"
|
||||
|
@ -29,242 +29,80 @@ import questions from "../src/assets/generated/layers/questions.json"
|
|||
import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson"
|
||||
import { Utils } from "../src/Utils"
|
||||
import { TagUtils } from "../src/Logic/Tags/TagUtils"
|
||||
function WriteFile(
|
||||
filename,
|
||||
html: string | BaseUIElement,
|
||||
autogenSource: string[],
|
||||
options?: {
|
||||
noTableOfContents: boolean
|
||||
}
|
||||
): void {
|
||||
if (!html) {
|
||||
return
|
||||
}
|
||||
for (const source of autogenSource) {
|
||||
if (source.indexOf("*") > 0) {
|
||||
continue
|
||||
}
|
||||
if (!existsSync(source)) {
|
||||
throw (
|
||||
"While creating a documentation file and checking that the generation sources are properly linked: source file " +
|
||||
source +
|
||||
" was not found. Typo?"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (html instanceof Combine && !options?.noTableOfContents) {
|
||||
const toc = new TableOfContents(html)
|
||||
const els = html.getElements()
|
||||
html = new Combine([els.shift(), toc, ...els]).SetClass("flex flex-col")
|
||||
}
|
||||
|
||||
let md = new Combine([
|
||||
Translations.W(html),
|
||||
"\n\nThis document is autogenerated from " +
|
||||
autogenSource
|
||||
.map(
|
||||
(file) =>
|
||||
`[${file}](https://github.com/pietervdvn/MapComplete/blob/develop/${file})`
|
||||
)
|
||||
.join(", "),
|
||||
]).AsMarkdown()
|
||||
|
||||
md.replace(/\n\n\n+/g, "\n\n")
|
||||
|
||||
if (!md.endsWith("\n")) {
|
||||
md += "\n"
|
||||
}
|
||||
|
||||
const warnAutomated =
|
||||
"[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources)"
|
||||
|
||||
writeFileSync(filename, warnAutomated + md)
|
||||
}
|
||||
|
||||
function GenerateDocumentationForTheme(theme: LayoutConfig): BaseUIElement {
|
||||
return new Combine([
|
||||
new Title(
|
||||
new Combine([
|
||||
theme.title,
|
||||
"(",
|
||||
new Link(theme.id, "https://mapcomplete.org/" + theme.id),
|
||||
")",
|
||||
]),
|
||||
2
|
||||
),
|
||||
theme.description,
|
||||
"This theme contains the following layers:",
|
||||
new List(
|
||||
theme.layers
|
||||
.filter((l) => !l.id.startsWith("note_import_"))
|
||||
.map((l) => new Link(l.id, "../Layers/" + l.id + ".md"))
|
||||
),
|
||||
"Available languages:",
|
||||
new List(theme.language.filter((ln) => ln !== "_context")),
|
||||
]).SetClass("flex flex-col")
|
||||
}
|
||||
import Script from "./Script"
|
||||
|
||||
/**
|
||||
* Generates the documentation for the layers overview page
|
||||
* @constructor
|
||||
* Converts a markdown-file into a .json file, which a walkthrough/slideshow element can use
|
||||
*
|
||||
* These are used in the studio
|
||||
*/
|
||||
function GenLayerOverviewText(): BaseUIElement {
|
||||
for (const id of Constants.priviliged_layers) {
|
||||
if (!AllSharedLayers.sharedLayers.has(id)) {
|
||||
console.error("Priviliged layer definition not found: " + id)
|
||||
return undefined
|
||||
}
|
||||
class ToSlideshowJson {
|
||||
private readonly _source: string
|
||||
private readonly _target: string
|
||||
|
||||
constructor(source: string, target: string) {
|
||||
this._source = source
|
||||
this._target = target
|
||||
}
|
||||
|
||||
const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter(
|
||||
(layer) => layer["source"] === null
|
||||
)
|
||||
public convert() {
|
||||
const lines = readFileSync(this._source, "utf8").split("\n")
|
||||
|
||||
const builtinLayerIds: Set<string> = new Set<string>()
|
||||
allLayers.forEach((l) => builtinLayerIds.add(l.id))
|
||||
|
||||
const themesPerLayer = new Map<string, string[]>()
|
||||
|
||||
for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) {
|
||||
for (const layer of layout.layers) {
|
||||
if (!builtinLayerIds.has(layer.id)) {
|
||||
continue
|
||||
const sections: string[][] = []
|
||||
let currentSection: string[] = []
|
||||
for (let line of lines) {
|
||||
if (line.trim().startsWith("# ")) {
|
||||
sections.push(currentSection)
|
||||
currentSection = []
|
||||
}
|
||||
if (!themesPerLayer.has(layer.id)) {
|
||||
themesPerLayer.set(layer.id, [])
|
||||
}
|
||||
themesPerLayer.get(layer.id).push(layout.id)
|
||||
line = line.replace('src="../../public/', 'src="./')
|
||||
line = line.replace('src="../../', 'src="./')
|
||||
currentSection.push(line)
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the cross-dependencies
|
||||
const layerIsNeededBy: Map<string, string[]> = new Map<string, string[]>()
|
||||
|
||||
for (const layer of allLayers) {
|
||||
for (const dep of DependencyCalculator.getLayerDependencies(layer)) {
|
||||
const dependency = dep.neededLayer
|
||||
if (!layerIsNeededBy.has(dependency)) {
|
||||
layerIsNeededBy.set(dependency, [])
|
||||
}
|
||||
layerIsNeededBy.get(dependency).push(layer.id)
|
||||
}
|
||||
}
|
||||
|
||||
return new Combine([
|
||||
new Title("Special and other useful layers", 1),
|
||||
"MapComplete has a few data layers available in the theme which have special properties through builtin-hooks. Furthermore, there are some normal layers (which are built from normal Theme-config files) but are so general that they get a mention here.",
|
||||
new Title("Priviliged layers", 1),
|
||||
new List(Constants.priviliged_layers.map((id) => "[" + id + "](#" + id + ")")),
|
||||
...Utils.NoNull(
|
||||
Constants.priviliged_layers.map((id) => AllSharedLayers.sharedLayers.get(id))
|
||||
).map((l) =>
|
||||
l.GenerateDocumentation(
|
||||
themesPerLayer.get(l.id),
|
||||
layerIsNeededBy,
|
||||
DependencyCalculator.getLayerDependencies(l),
|
||||
Constants.added_by_default.indexOf(<any>l.id) >= 0,
|
||||
Constants.no_include.indexOf(<any>l.id) < 0
|
||||
)
|
||||
),
|
||||
new Title("Normal layers", 1),
|
||||
"The following layers are included in MapComplete:",
|
||||
new List(
|
||||
Array.from(AllSharedLayers.sharedLayers.keys()).map(
|
||||
(id) => new Link(id, "./Layers/" + id + ".md")
|
||||
)
|
||||
),
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates documentation for the layers.
|
||||
* Inline layers are included (if the theme is public)
|
||||
* @param callback
|
||||
* @constructor
|
||||
*/
|
||||
function GenOverviewsForSingleLayer(
|
||||
callback: (layer: LayerConfig, element: BaseUIElement, inlineSource: string) => void
|
||||
): void {
|
||||
const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter(
|
||||
(layer) => layer["source"] !== null
|
||||
)
|
||||
const builtinLayerIds: Set<string> = new Set<string>()
|
||||
allLayers.forEach((l) => builtinLayerIds.add(l.id))
|
||||
const inlineLayers = new Map<string, string>()
|
||||
|
||||
for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) {
|
||||
if (layout.hideFromOverview) {
|
||||
continue
|
||||
}
|
||||
|
||||
for (const layer of layout.layers) {
|
||||
if (layer.source === null) {
|
||||
continue
|
||||
}
|
||||
if (builtinLayerIds.has(layer.id)) {
|
||||
continue
|
||||
}
|
||||
if (layer.source.geojsonSource !== undefined) {
|
||||
// Not an OSM-source
|
||||
continue
|
||||
}
|
||||
allLayers.push(layer)
|
||||
builtinLayerIds.add(layer.id)
|
||||
inlineLayers.set(layer.id, layout.id)
|
||||
}
|
||||
}
|
||||
|
||||
const themesPerLayer = new Map<string, string[]>()
|
||||
|
||||
for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) {
|
||||
if (layout.hideFromOverview) {
|
||||
continue
|
||||
}
|
||||
for (const layer of layout.layers) {
|
||||
if (!builtinLayerIds.has(layer.id)) {
|
||||
// This is an inline layer
|
||||
continue
|
||||
}
|
||||
if (!themesPerLayer.has(layer.id)) {
|
||||
themesPerLayer.set(layer.id, [])
|
||||
}
|
||||
themesPerLayer.get(layer.id).push(layout.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the cross-dependencies
|
||||
const layerIsNeededBy: Map<string, string[]> = new Map<string, string[]>()
|
||||
|
||||
for (const layer of allLayers) {
|
||||
for (const dep of DependencyCalculator.getLayerDependencies(layer)) {
|
||||
const dependency = dep.neededLayer
|
||||
if (!layerIsNeededBy.has(dependency)) {
|
||||
layerIsNeededBy.set(dependency, [])
|
||||
}
|
||||
layerIsNeededBy.get(dependency).push(layer.id)
|
||||
}
|
||||
}
|
||||
|
||||
allLayers.forEach((layer) => {
|
||||
const element = layer.GenerateDocumentation(
|
||||
themesPerLayer.get(layer.id),
|
||||
layerIsNeededBy,
|
||||
DependencyCalculator.getLayerDependencies(layer)
|
||||
sections.push(currentSection)
|
||||
writeFileSync(
|
||||
this._target,
|
||||
JSON.stringify({
|
||||
sections: sections.map((s) => s.join("\n")).filter((s) => s.length > 0),
|
||||
})
|
||||
)
|
||||
callback(layer, element, inlineLayers.get(layer.id))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The wikitable is updated as some tools show an overview of apps based on the wiki.
|
||||
* Generates a wiki page with the theme overview
|
||||
* The wikitable should be updated regularly as some tools show an overview of apps based on the wiki.
|
||||
*/
|
||||
function generateWikipage() {
|
||||
function generateWikiEntry(layout: {
|
||||
class WikiPageGenerator {
|
||||
private readonly _target: string
|
||||
|
||||
constructor(target: string = "Docs/wikiIndex.txt") {
|
||||
this._target = target
|
||||
}
|
||||
|
||||
generate() {
|
||||
let wikiPage =
|
||||
'{|class="wikitable sortable"\n' +
|
||||
"! Name, link !! Genre !! Covered region !! Language !! Description !! Free materials !! Image\n" +
|
||||
"|-"
|
||||
|
||||
for (const layout of themeOverview) {
|
||||
if (layout.hideFromOverview) {
|
||||
continue
|
||||
}
|
||||
wikiPage += "\n" + this.generateWikiEntryFor(layout)
|
||||
}
|
||||
|
||||
wikiPage += "\n|}"
|
||||
|
||||
writeFileSync(this._target, wikiPage)
|
||||
}
|
||||
|
||||
private generateWikiEntryFor(layout: {
|
||||
hideFromOverview: boolean
|
||||
id: string
|
||||
shortDescription: any
|
||||
}) {
|
||||
}): string {
|
||||
if (layout.hideFromOverview) {
|
||||
return ""
|
||||
}
|
||||
|
@ -287,174 +125,421 @@ function generateWikipage() {
|
|||
|genre= POI, editor, ${layout.id}
|
||||
}}`
|
||||
}
|
||||
|
||||
let wikiPage =
|
||||
'{|class="wikitable sortable"\n' +
|
||||
"! Name, link !! Genre !! Covered region !! Language !! Description !! Free materials !! Image\n" +
|
||||
"|-"
|
||||
|
||||
for (const layout of themeOverview) {
|
||||
if (layout.hideFromOverview) {
|
||||
continue
|
||||
}
|
||||
wikiPage += "\n" + generateWikiEntry(layout)
|
||||
}
|
||||
|
||||
wikiPage += "\n|}"
|
||||
|
||||
writeFile("Docs/wikiIndex.txt", wikiPage, (err) => {
|
||||
if (err !== null) {
|
||||
console.log("Could not save wikiindex", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function studioDocsFor(source: string, target: string) {
|
||||
const lines = readFileSync(source, "utf8").split("\n")
|
||||
|
||||
const sections: string[][] = []
|
||||
let currentSection: string[] = []
|
||||
for (let line of lines) {
|
||||
if (line.trim().startsWith("# ")) {
|
||||
sections.push(currentSection)
|
||||
currentSection = []
|
||||
}
|
||||
line = line.replace('src="../../public/', 'src="./')
|
||||
line = line.replace('src="../../', 'src="./')
|
||||
currentSection.push(line)
|
||||
export class GenerateDocs extends Script {
|
||||
constructor() {
|
||||
super("Generates various documentation files")
|
||||
}
|
||||
sections.push(currentSection)
|
||||
writeFileSync(
|
||||
target,
|
||||
JSON.stringify({
|
||||
sections: sections.map((s) => s.join("\n")).filter((s) => s.length > 0),
|
||||
|
||||
async main(args: string[]) {
|
||||
console.log("Starting documentation generation...")
|
||||
ScriptUtils.fixUtils()
|
||||
if (!existsSync("./Docs/Themes")) {
|
||||
mkdirSync("./Docs/Themes")
|
||||
}
|
||||
|
||||
this.WriteFile("./Docs/Tags_format.md", TagUtils.generateDocs(), [
|
||||
"src/Logic/Tags/TagUtils.ts",
|
||||
])
|
||||
|
||||
new ToSlideshowJson(
|
||||
"./Docs/Studio/Introduction.md",
|
||||
"./src/assets/studio_introduction.json"
|
||||
).convert()
|
||||
new ToSlideshowJson(
|
||||
"./Docs/Studio/TagRenderingIntro.md",
|
||||
"./src/assets/studio_tagrenderings_intro.json"
|
||||
).convert()
|
||||
|
||||
this.generateHotkeyDocs()
|
||||
this.generateBuiltinIndex()
|
||||
this.generateQueryParameterDocs()
|
||||
this.generateBuiltinQuestions()
|
||||
this.generateOverviewsForAllSingleLayer()
|
||||
this.generateLayerOverviewText()
|
||||
this.generateBuiltinUnits()
|
||||
|
||||
Array.from(AllKnownLayouts.allKnownLayouts.values()).map((theme) => {
|
||||
this.generateForTheme(theme)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function studioDocs() {
|
||||
studioDocsFor("./Docs/Studio/Introduction.md", "./src/assets/studio_introduction.json")
|
||||
studioDocsFor(
|
||||
"./Docs/Studio/TagRenderingIntro.md",
|
||||
"./src/assets/studio_tagrenderings_intro.json"
|
||||
)
|
||||
}
|
||||
this.WriteFile("./Docs/SpecialRenderings.md", SpecialVisualizations.HelpMessage(), [
|
||||
"src/UI/SpecialVisualizations.ts",
|
||||
])
|
||||
this.WriteFile(
|
||||
"./Docs/CalculatedTags.md",
|
||||
new Combine([
|
||||
new Title("Metatags", 1),
|
||||
SimpleMetaTaggers.HelpText(),
|
||||
ExtraFunctions.HelpText(),
|
||||
]).SetClass("flex-col"),
|
||||
["src/Logic/SimpleMetaTagger.ts", "src/Logic/ExtraFunctions.ts"]
|
||||
)
|
||||
this.WriteFile("./Docs/SpecialInputElements.md", Validators.HelpText(), [
|
||||
"src/UI/InputElement/Validators.ts",
|
||||
])
|
||||
|
||||
console.log("Starting documentation generation...")
|
||||
ScriptUtils.fixUtils()
|
||||
studioDocs()
|
||||
generateWikipage()
|
||||
GenOverviewsForSingleLayer((layer, element, inlineSource) => {
|
||||
ScriptUtils.erasableLog("Exporting layer documentation for", layer.id)
|
||||
if (!existsSync("./Docs/Layers")) {
|
||||
mkdirSync("./Docs/Layers")
|
||||
new WikiPageGenerator().generate()
|
||||
|
||||
console.log("Generated docs")
|
||||
}
|
||||
let source: string = `assets/layers/${layer.id}/${layer.id}.json`
|
||||
if (inlineSource !== undefined) {
|
||||
source = `assets/themes/${inlineSource}/${inlineSource}.json`
|
||||
}
|
||||
WriteFile("./Docs/Layers/" + layer.id + ".md", element, [source], { noTableOfContents: true })
|
||||
})
|
||||
|
||||
Array.from(AllKnownLayouts.allKnownLayouts.values()).map((theme) => {
|
||||
if (!existsSync("./Docs/Themes")) {
|
||||
mkdirSync("./Docs/Themes")
|
||||
}
|
||||
const docs = GenerateDocumentationForTheme(theme)
|
||||
WriteFile(
|
||||
"./Docs/Themes/" + theme.id + ".md",
|
||||
docs,
|
||||
[`assets/themes/${theme.id}/${theme.id}.json`],
|
||||
{ noTableOfContents: true }
|
||||
)
|
||||
})
|
||||
WriteFile("./Docs/SpecialRenderings.md", SpecialVisualizations.HelpMessage(), [
|
||||
"src/UI/SpecialVisualizations.ts",
|
||||
])
|
||||
WriteFile(
|
||||
"./Docs/CalculatedTags.md",
|
||||
new Combine([
|
||||
new Title("Metatags", 1),
|
||||
SimpleMetaTaggers.HelpText(),
|
||||
ExtraFunctions.HelpText(),
|
||||
]).SetClass("flex-col"),
|
||||
["src/Logic/SimpleMetaTagger.ts", "src/Logic/ExtraFunctions.ts"]
|
||||
)
|
||||
WriteFile("./Docs/SpecialInputElements.md", Validators.HelpText(), [
|
||||
"src/UI/InputElement/Validators.ts",
|
||||
])
|
||||
WriteFile("./Docs/BuiltinLayers.md", GenLayerOverviewText(), [
|
||||
"src/Customizations/AllKnownLayouts.ts",
|
||||
])
|
||||
|
||||
const qLayer = new LayerConfig(<LayerConfigJson>questions, "questions.json", true)
|
||||
WriteFile("./Docs/BuiltinQuestions.md", qLayer.GenerateDocumentation([], new Map(), []), [
|
||||
"assets/layers/questions/questions.json",
|
||||
])
|
||||
WriteFile("./Docs/Tags_format.md", TagUtils.generateDocs(), ["src/Logic/Tags/TagUtils.ts"])
|
||||
|
||||
{
|
||||
// Generate the builtinIndex which shows interlayer dependencies
|
||||
var layers = ScriptUtils.getLayerFiles().map((f) => f.parsed)
|
||||
var builtinsPerLayer = new Map<string, string[]>()
|
||||
var layersUsingBuiltin = new Map<string /* Builtin */, string[]>()
|
||||
for (const layer of layers) {
|
||||
if (layer.tagRenderings === undefined) {
|
||||
continue
|
||||
private WriteFile(
|
||||
filename,
|
||||
html: string | BaseUIElement,
|
||||
autogenSource: string[],
|
||||
options?: {
|
||||
noTableOfContents: boolean
|
||||
}
|
||||
const usedBuiltins: string[] = []
|
||||
for (const tagRendering of layer.tagRenderings) {
|
||||
if (typeof tagRendering === "string") {
|
||||
usedBuiltins.push(tagRendering)
|
||||
): void {
|
||||
if (!html) {
|
||||
return
|
||||
}
|
||||
for (const source of autogenSource) {
|
||||
if (source.indexOf("*") > 0) {
|
||||
continue
|
||||
}
|
||||
if (tagRendering["builtin"] !== undefined) {
|
||||
const builtins = tagRendering["builtin"]
|
||||
if (typeof builtins === "string") {
|
||||
usedBuiltins.push(builtins)
|
||||
} else {
|
||||
usedBuiltins.push(...builtins)
|
||||
if (!existsSync(source)) {
|
||||
throw (
|
||||
"While creating a documentation file and checking that the generation sources are properly linked: source file " +
|
||||
source +
|
||||
" was not found. Typo?"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (html instanceof Combine && !options?.noTableOfContents) {
|
||||
const toc = new TableOfContents(html)
|
||||
const els = html.getElements()
|
||||
html = new Combine([els.shift(), toc, ...els]).SetClass("flex flex-col")
|
||||
}
|
||||
|
||||
let md = new Combine([
|
||||
Translations.W(html),
|
||||
"\n\nThis document is autogenerated from " +
|
||||
autogenSource
|
||||
.map(
|
||||
(file) =>
|
||||
`[${file}](https://github.com/pietervdvn/MapComplete/blob/develop/${file})`
|
||||
)
|
||||
.join(", "),
|
||||
]).AsMarkdown()
|
||||
|
||||
md.replace(/\n\n\n+/g, "\n\n")
|
||||
|
||||
if (!md.endsWith("\n")) {
|
||||
md += "\n"
|
||||
}
|
||||
|
||||
const warnAutomated =
|
||||
"[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources)"
|
||||
|
||||
writeFileSync(filename, warnAutomated + md)
|
||||
}
|
||||
|
||||
private generateHotkeyDocs() {
|
||||
new ThemeViewState(new LayoutConfig(<any>bookcases))
|
||||
this.WriteFile("./Docs/Hotkeys.md", Hotkeys.generateDocumentation(), [])
|
||||
}
|
||||
|
||||
private generateBuiltinUnits() {
|
||||
const layer = new LayerConfig(<LayerConfigJson>unit, "units", true)
|
||||
const els: (BaseUIElement | string)[] = [new Title(layer.id, 2)]
|
||||
|
||||
for (const unit of layer.units) {
|
||||
els.push(new Title(unit.quantity))
|
||||
for (const denomination of unit.denominations) {
|
||||
els.push(new Title(denomination.canonical, 4))
|
||||
if (denomination.useIfNoUnitGiven === true) {
|
||||
els.push("*Default denomination*")
|
||||
} else if (
|
||||
denomination.useIfNoUnitGiven &&
|
||||
denomination.useIfNoUnitGiven.length > 0
|
||||
) {
|
||||
els.push("Default denomination in the following countries:")
|
||||
els.push(new List(denomination.useIfNoUnitGiven))
|
||||
}
|
||||
if (denomination.prefix) {
|
||||
els.push("Prefixed")
|
||||
}
|
||||
if (denomination.alternativeDenominations.length > 0) {
|
||||
els.push(
|
||||
"Alternative denominations:",
|
||||
new List(denomination.alternativeDenominations)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const usedBuiltin of usedBuiltins) {
|
||||
const usingLayers = layersUsingBuiltin.get(usedBuiltin)
|
||||
if (usingLayers === undefined) {
|
||||
layersUsingBuiltin.set(usedBuiltin, [layer.id])
|
||||
} else {
|
||||
usingLayers.push(layer.id)
|
||||
|
||||
this.WriteFile("./Docs/builtin_units.md", new Combine([new Title("Units", 1), ...els]), [
|
||||
`assets/layers/unit/unit.json`,
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates documentation for the all the individual layers.
|
||||
* Inline layers are included (if the theme is public)
|
||||
*/
|
||||
private generateOverviewsForAllSingleLayer(): void {
|
||||
const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter(
|
||||
(layer) => layer["source"] !== null
|
||||
)
|
||||
const builtinLayerIds: Set<string> = new Set<string>()
|
||||
allLayers.forEach((l) => builtinLayerIds.add(l.id))
|
||||
const inlineLayers = new Map<string, string>()
|
||||
|
||||
for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) {
|
||||
if (layout.hideFromOverview) {
|
||||
continue
|
||||
}
|
||||
|
||||
for (const layer of layout.layers) {
|
||||
if (layer.source === null) {
|
||||
continue
|
||||
}
|
||||
if (builtinLayerIds.has(layer.id)) {
|
||||
continue
|
||||
}
|
||||
if (layer.source.geojsonSource !== undefined) {
|
||||
// Not an OSM-source
|
||||
continue
|
||||
}
|
||||
allLayers.push(layer)
|
||||
builtinLayerIds.add(layer.id)
|
||||
inlineLayers.set(layer.id, layout.id)
|
||||
}
|
||||
}
|
||||
|
||||
builtinsPerLayer.set(layer.id, usedBuiltins)
|
||||
const themesPerLayer = new Map<string, string[]>()
|
||||
|
||||
for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) {
|
||||
if (layout.hideFromOverview) {
|
||||
continue
|
||||
}
|
||||
for (const layer of layout.layers) {
|
||||
if (!builtinLayerIds.has(layer.id)) {
|
||||
// This is an inline layer
|
||||
continue
|
||||
}
|
||||
if (!themesPerLayer.has(layer.id)) {
|
||||
themesPerLayer.set(layer.id, [])
|
||||
}
|
||||
themesPerLayer.get(layer.id).push(layout.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the cross-dependencies
|
||||
const layerIsNeededBy: Map<string, string[]> = new Map<string, string[]>()
|
||||
|
||||
for (const layer of allLayers) {
|
||||
for (const dep of DependencyCalculator.getLayerDependencies(layer)) {
|
||||
const dependency = dep.neededLayer
|
||||
if (!layerIsNeededBy.has(dependency)) {
|
||||
layerIsNeededBy.set(dependency, [])
|
||||
}
|
||||
layerIsNeededBy.get(dependency).push(layer.id)
|
||||
}
|
||||
}
|
||||
|
||||
allLayers.forEach((layer) => {
|
||||
const element = layer.GenerateDocumentation(
|
||||
themesPerLayer.get(layer.id),
|
||||
layerIsNeededBy,
|
||||
DependencyCalculator.getLayerDependencies(layer)
|
||||
)
|
||||
const inlineSource = inlineLayers.get(layer.id)
|
||||
ScriptUtils.erasableLog("Exporting layer documentation for", layer.id)
|
||||
if (!existsSync("./Docs/Layers")) {
|
||||
mkdirSync("./Docs/Layers")
|
||||
}
|
||||
let source: string = `assets/layers/${layer.id}/${layer.id}.json`
|
||||
if (inlineSource !== undefined) {
|
||||
source = `assets/themes/${inlineSource}/${inlineSource}.json`
|
||||
}
|
||||
this.WriteFile("./Docs/Layers/" + layer.id + ".md", element, [source], {
|
||||
noTableOfContents: true,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const docs = new Combine([
|
||||
new Title("Index of builtin TagRendering", 1),
|
||||
new Title("Existing builtin tagrenderings", 2),
|
||||
...Array.from(layersUsingBuiltin.entries()).map(([builtin, usedByLayers]) =>
|
||||
new Combine([new Title(builtin), new List(usedByLayers)]).SetClass("flex flex-col")
|
||||
),
|
||||
]).SetClass("flex flex-col")
|
||||
WriteFile("./Docs/BuiltinIndex.md", docs, ["assets/layers/*.json"])
|
||||
/**
|
||||
* Generate the builtinIndex which shows interlayer dependencies
|
||||
* @private
|
||||
*/
|
||||
|
||||
private generateBuiltinIndex() {
|
||||
const layers = ScriptUtils.getLayerFiles().map((f) => f.parsed)
|
||||
const builtinsPerLayer = new Map<string, string[]>()
|
||||
const layersUsingBuiltin = new Map<string /* Builtin */, string[]>()
|
||||
for (const layer of layers) {
|
||||
if (layer.tagRenderings === undefined) {
|
||||
continue
|
||||
}
|
||||
const usedBuiltins: string[] = []
|
||||
for (const tagRendering of layer.tagRenderings) {
|
||||
if (typeof tagRendering === "string") {
|
||||
usedBuiltins.push(tagRendering)
|
||||
continue
|
||||
}
|
||||
if (tagRendering["builtin"] !== undefined) {
|
||||
const builtins = tagRendering["builtin"]
|
||||
if (typeof builtins === "string") {
|
||||
usedBuiltins.push(builtins)
|
||||
} else {
|
||||
usedBuiltins.push(...builtins)
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const usedBuiltin of usedBuiltins) {
|
||||
const usingLayers = layersUsingBuiltin.get(usedBuiltin)
|
||||
if (usingLayers === undefined) {
|
||||
layersUsingBuiltin.set(usedBuiltin, [layer.id])
|
||||
} else {
|
||||
usingLayers.push(layer.id)
|
||||
}
|
||||
}
|
||||
|
||||
builtinsPerLayer.set(layer.id, usedBuiltins)
|
||||
}
|
||||
|
||||
const docs = new Combine([
|
||||
new Title("Index of builtin TagRendering", 1),
|
||||
new Title("Existing builtin tagrenderings", 2),
|
||||
...Array.from(layersUsingBuiltin.entries()).map(([builtin, usedByLayers]) =>
|
||||
new Combine([new Title(builtin), new List(usedByLayers)]).SetClass("flex flex-col")
|
||||
),
|
||||
]).SetClass("flex flex-col")
|
||||
this.WriteFile("./Docs/BuiltinIndex.md", docs, ["assets/layers/*.json"])
|
||||
}
|
||||
|
||||
private generateQueryParameterDocs() {
|
||||
if (fakedom === undefined) {
|
||||
throw "FakeDom not initialized"
|
||||
}
|
||||
QueryParameters.GetQueryParameter(
|
||||
"mode",
|
||||
"map",
|
||||
"The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics'"
|
||||
)
|
||||
|
||||
this.WriteFile(
|
||||
"./Docs/URL_Parameters.md",
|
||||
QueryParameterDocumentation.GenerateQueryParameterDocs(),
|
||||
["src/Logic/Web/QueryParameters.ts", "src/UI/QueryParameterDocumentation.ts"]
|
||||
)
|
||||
}
|
||||
|
||||
private generateBuiltinQuestions() {
|
||||
const qLayer = new LayerConfig(<LayerConfigJson>questions, "questions.json", true)
|
||||
this.WriteFile(
|
||||
"./Docs/BuiltinQuestions.md",
|
||||
qLayer.GenerateDocumentation([], new Map(), []),
|
||||
["assets/layers/questions/questions.json"]
|
||||
)
|
||||
}
|
||||
|
||||
private generateForTheme(theme: LayoutConfig): void {
|
||||
const el = new Combine([
|
||||
new Title(
|
||||
new Combine([
|
||||
theme.title,
|
||||
"(",
|
||||
new Link(theme.id, "https://mapcomplete.org/" + theme.id),
|
||||
")",
|
||||
]),
|
||||
2
|
||||
),
|
||||
theme.description,
|
||||
"This theme contains the following layers:",
|
||||
new List(
|
||||
theme.layers
|
||||
.filter((l) => !l.id.startsWith("note_import_"))
|
||||
.map((l) => new Link(l.id, "../Layers/" + l.id + ".md"))
|
||||
),
|
||||
"Available languages:",
|
||||
new List(theme.language.filter((ln) => ln !== "_context")),
|
||||
]).SetClass("flex flex-col")
|
||||
this.WriteFile(
|
||||
"./Docs/Themes/" + theme.id + ".md",
|
||||
el,
|
||||
[`assets/themes/${theme.id}/${theme.id}.json`],
|
||||
{ noTableOfContents: true }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the documentation for the layers overview page
|
||||
* @constructor
|
||||
*/
|
||||
private generateLayerOverviewText(): BaseUIElement {
|
||||
for (const id of Constants.priviliged_layers) {
|
||||
if (!AllSharedLayers.sharedLayers.has(id)) {
|
||||
console.error("Priviliged layer definition not found: " + id)
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter(
|
||||
(layer) => layer["source"] === null
|
||||
)
|
||||
|
||||
const builtinLayerIds: Set<string> = new Set<string>()
|
||||
allLayers.forEach((l) => builtinLayerIds.add(l.id))
|
||||
|
||||
const themesPerLayer = new Map<string, string[]>()
|
||||
|
||||
for (const layout of Array.from(AllKnownLayouts.allKnownLayouts.values())) {
|
||||
for (const layer of layout.layers) {
|
||||
if (!builtinLayerIds.has(layer.id)) {
|
||||
continue
|
||||
}
|
||||
if (!themesPerLayer.has(layer.id)) {
|
||||
themesPerLayer.set(layer.id, [])
|
||||
}
|
||||
themesPerLayer.get(layer.id).push(layout.id)
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the cross-dependencies
|
||||
const layerIsNeededBy: Map<string, string[]> = new Map<string, string[]>()
|
||||
|
||||
for (const layer of allLayers) {
|
||||
for (const dep of DependencyCalculator.getLayerDependencies(layer)) {
|
||||
const dependency = dep.neededLayer
|
||||
if (!layerIsNeededBy.has(dependency)) {
|
||||
layerIsNeededBy.set(dependency, [])
|
||||
}
|
||||
layerIsNeededBy.get(dependency).push(layer.id)
|
||||
}
|
||||
}
|
||||
|
||||
const el = new Combine([
|
||||
new Title("Special and other useful layers", 1),
|
||||
"MapComplete has a few data layers available in the theme which have special properties through builtin-hooks. Furthermore, there are some normal layers (which are built from normal Theme-config files) but are so general that they get a mention here.",
|
||||
new Title("Priviliged layers", 1),
|
||||
new List(Constants.priviliged_layers.map((id) => "[" + id + "](#" + id + ")")),
|
||||
...Utils.NoNull(
|
||||
Constants.priviliged_layers.map((id) => AllSharedLayers.sharedLayers.get(id))
|
||||
).map((l) =>
|
||||
l.GenerateDocumentation(
|
||||
themesPerLayer.get(l.id),
|
||||
layerIsNeededBy,
|
||||
DependencyCalculator.getLayerDependencies(l),
|
||||
Constants.added_by_default.indexOf(<any>l.id) >= 0,
|
||||
Constants.no_include.indexOf(<any>l.id) < 0
|
||||
)
|
||||
),
|
||||
new Title("Normal layers", 1),
|
||||
"The following layers are included in MapComplete:",
|
||||
new List(
|
||||
Array.from(AllSharedLayers.sharedLayers.keys()).map(
|
||||
(id) => new Link(id, "./Layers/" + id + ".md")
|
||||
)
|
||||
),
|
||||
])
|
||||
this.WriteFile("./Docs/BuiltinLayers.md", el, ["src/Customizations/AllKnownLayouts.ts"])
|
||||
}
|
||||
}
|
||||
|
||||
WriteFile("./Docs/URL_Parameters.md", QueryParameterDocumentation.GenerateQueryParameterDocs(), [
|
||||
"src/Logic/Web/QueryParameters.ts",
|
||||
"src/UI/QueryParameterDocumentation.ts",
|
||||
])
|
||||
if (fakedom === undefined) {
|
||||
throw "FakeDom not initialized"
|
||||
}
|
||||
QueryParameters.GetQueryParameter(
|
||||
"mode",
|
||||
"map",
|
||||
"The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics'"
|
||||
)
|
||||
|
||||
{
|
||||
new ThemeViewState(new LayoutConfig(<any>bookcases))
|
||||
WriteFile("./Docs/Hotkeys.md", Hotkeys.generateDocumentation(), [])
|
||||
}
|
||||
|
||||
console.log("Generated docs")
|
||||
new GenerateDocs().run()
|
||||
|
|
|
@ -137,11 +137,12 @@ export default class DetermineLayout {
|
|||
if (json.layers === undefined && json.tagRenderings !== undefined) {
|
||||
// We got fed a layer instead of a theme
|
||||
const layerConfig = <LayerConfigJson>json
|
||||
const iconTr: string | TagRenderingConfigJson = <any>(
|
||||
layerConfig.pointRendering
|
||||
.map((mr) => mr?.marker?.find((icon) => icon.icon !== undefined)?.icon)
|
||||
.find((i) => i !== undefined)
|
||||
) ?? "bug"
|
||||
const iconTr: string | TagRenderingConfigJson =
|
||||
<any>(
|
||||
layerConfig.pointRendering
|
||||
.map((mr) => mr?.marker?.find((icon) => icon.icon !== undefined)?.icon)
|
||||
.find((i) => i !== undefined)
|
||||
) ?? "bug"
|
||||
const icon = new TagRenderingConfig(iconTr).render.txt
|
||||
json = {
|
||||
id: json.id,
|
||||
|
@ -156,8 +157,8 @@ export default class DetermineLayout {
|
|||
}
|
||||
|
||||
const knownLayersDict = new Map<string, LayerConfigJson>()
|
||||
for (const key in known_layers.layers) {
|
||||
const layer = known_layers.layers[key]
|
||||
for (const key in known_layers["layers"]) {
|
||||
const layer = known_layers["layers"][key]
|
||||
knownLayersDict.set(layer.id, <LayerConfigJson>layer)
|
||||
}
|
||||
const convertState: DesugaringContext = {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Translation } from "../UI/i18n/Translation"
|
||||
import { Translation, TypedTranslation } from "../UI/i18n/Translation"
|
||||
import { DenominationConfigJson } from "./ThemeConfig/Json/UnitConfigJson"
|
||||
import Translations from "../UI/i18n/Translations"
|
||||
|
||||
|
@ -9,20 +9,39 @@ import Translations from "../UI/i18n/Translations"
|
|||
export class Denomination {
|
||||
public readonly canonical: string
|
||||
public readonly _canonicalSingular: string
|
||||
public readonly useAsDefaultInput: boolean | string[]
|
||||
public readonly useIfNoUnitGiven: boolean | string[]
|
||||
public readonly prefix: boolean
|
||||
public readonly addSpace: boolean
|
||||
public readonly alternativeDenominations: string[]
|
||||
private readonly _human: Translation
|
||||
private readonly _humanSingular?: Translation
|
||||
public readonly human: TypedTranslation<{ quantity: string }>
|
||||
public readonly humanSingular?: Translation
|
||||
|
||||
constructor(json: DenominationConfigJson, useAsDefaultInput: boolean, context: string) {
|
||||
private constructor(
|
||||
canonical: string,
|
||||
_canonicalSingular: string,
|
||||
useIfNoUnitGiven: boolean | string[],
|
||||
prefix: boolean,
|
||||
addSpace: boolean,
|
||||
alternativeDenominations: string[],
|
||||
_human: TypedTranslation<{ quantity: string }>,
|
||||
_humanSingular?: Translation
|
||||
) {
|
||||
this.canonical = canonical
|
||||
this._canonicalSingular = _canonicalSingular
|
||||
this.useIfNoUnitGiven = useIfNoUnitGiven
|
||||
this.prefix = prefix
|
||||
this.addSpace = addSpace
|
||||
this.alternativeDenominations = alternativeDenominations
|
||||
this.human = _human
|
||||
this.humanSingular = _humanSingular
|
||||
}
|
||||
|
||||
public static fromJson(json: DenominationConfigJson, context: string) {
|
||||
context = `${context}.unit(${json.canonicalDenomination})`
|
||||
this.canonical = json.canonicalDenomination.trim()
|
||||
if (this.canonical === undefined) {
|
||||
const canonical = json.canonicalDenomination.trim()
|
||||
if (canonical === undefined) {
|
||||
throw `${context}: this unit has no decent canonical value defined`
|
||||
}
|
||||
this._canonicalSingular = json.canonicalDenominationSingular?.trim()
|
||||
|
||||
json.alternativeDenomination?.forEach((v, i) => {
|
||||
if ((v?.trim() ?? "") === "") {
|
||||
|
@ -30,40 +49,67 @@ export class Denomination {
|
|||
}
|
||||
})
|
||||
|
||||
this.alternativeDenominations = json.alternativeDenomination?.map((v) => v.trim()) ?? []
|
||||
|
||||
if (json["default" /* @code-quality: ignore*/] !== undefined) {
|
||||
throw `${context} uses the old 'default'-key. Use "useIfNoUnitGiven" or "useAsDefaultInput" instead`
|
||||
}
|
||||
this.useIfNoUnitGiven = json.useIfNoUnitGiven
|
||||
this.useAsDefaultInput = useAsDefaultInput ?? json.useIfNoUnitGiven
|
||||
|
||||
this._human = Translations.T(json.human, context + "human")
|
||||
this._humanSingular = Translations.T(json.humanSingular, context + "humanSingular")
|
||||
|
||||
this.prefix = json.prefix ?? false
|
||||
const humanTexts = Translations.T(json.human, context + "human")
|
||||
humanTexts.OnEveryLanguage((text, language) => {
|
||||
if (text.indexOf("{quantity}") < 0) {
|
||||
throw `In denomination: a human text should contain {quantity} (at ${context}.human.${language})`
|
||||
}
|
||||
return text
|
||||
})
|
||||
return new Denomination(
|
||||
canonical,
|
||||
json.canonicalDenominationSingular?.trim(),
|
||||
json.useIfNoUnitGiven,
|
||||
json.prefix ?? false,
|
||||
json.addSpace ?? false,
|
||||
json.alternativeDenomination?.map((v) => v.trim()) ?? [],
|
||||
humanTexts,
|
||||
Translations.T(json.humanSingular, context + "humanSingular")
|
||||
)
|
||||
}
|
||||
|
||||
get human(): Translation {
|
||||
return this._human.Clone()
|
||||
public clone() {
|
||||
return new Denomination(
|
||||
this.canonical,
|
||||
this._canonicalSingular,
|
||||
this.useIfNoUnitGiven,
|
||||
this.prefix,
|
||||
this.addSpace,
|
||||
this.alternativeDenominations,
|
||||
this.human,
|
||||
this.humanSingular
|
||||
)
|
||||
}
|
||||
|
||||
get humanSingular(): Translation {
|
||||
return (this._humanSingular ?? this._human).Clone()
|
||||
public withBlankCanonical() {
|
||||
return new Denomination(
|
||||
"",
|
||||
this._canonicalSingular,
|
||||
this.useIfNoUnitGiven,
|
||||
this.prefix,
|
||||
this.addSpace,
|
||||
[this.canonical, ...this.alternativeDenominations],
|
||||
this.human,
|
||||
this.humanSingular
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a representation of the given value
|
||||
* Create the canonical, human representation of the given value
|
||||
* @param value the value from OSM
|
||||
* @param actAsDefault if set and the value can be parsed as number, will be parsed and trimmed
|
||||
*
|
||||
* const unit = new Denomination({
|
||||
* const unit = Denomination.fromJson({
|
||||
* canonicalDenomination: "m",
|
||||
* alternativeDenomination: ["meter"],
|
||||
* human: {
|
||||
* en: "meter"
|
||||
* en: "{quantity} meter"
|
||||
* }
|
||||
* }, false, "test")
|
||||
* }, "test")
|
||||
* unit.canonicalValue("42m", true) // =>"42 m"
|
||||
* unit.canonicalValue("42", true) // =>"42 m"
|
||||
* unit.canonicalValue("42 m", true) // =>"42 m"
|
||||
|
@ -72,13 +118,13 @@ export class Denomination {
|
|||
* unit.canonicalValue("42", true) // =>"42 m"
|
||||
*
|
||||
* // Should be trimmed if canonical is empty
|
||||
* const unit = new Denomination({
|
||||
* const unit = Denomination.fromJson({
|
||||
* canonicalDenomination: "",
|
||||
* alternativeDenomination: ["meter","m"],
|
||||
* human: {
|
||||
* en: "meter"
|
||||
* en: "{quantity} meter"
|
||||
* }
|
||||
* }, false, "test")
|
||||
* }, "test")
|
||||
* unit.canonicalValue("42m", true) // =>"42"
|
||||
* unit.canonicalValue("42", true) // =>"42"
|
||||
* unit.canonicalValue("42 m", true) // =>"42"
|
||||
|
@ -160,14 +206,4 @@ export class Denomination {
|
|||
|
||||
return null
|
||||
}
|
||||
|
||||
isDefaultDenomination(country: () => string) {
|
||||
if (this.useIfNoUnitGiven === true) {
|
||||
return true
|
||||
}
|
||||
if (this.useIfNoUnitGiven === false) {
|
||||
return false
|
||||
}
|
||||
return this.useIfNoUnitGiven.indexOf(country()) >= 0
|
||||
}
|
||||
}
|
||||
|
|
|
@ -517,7 +517,10 @@ export interface LayerConfigJson {
|
|||
*
|
||||
* group: editing
|
||||
*/
|
||||
units?: UnitConfigJson[]
|
||||
units?: (
|
||||
| UnitConfigJson
|
||||
| Record<string, string | { quantity: string; denominations: string[]; canonical?: string }>
|
||||
)[]
|
||||
|
||||
/**
|
||||
* If set, synchronizes whether or not this layer is enabled.
|
||||
|
|
|
@ -57,12 +57,16 @@
|
|||
*
|
||||
*/
|
||||
export default interface UnitConfigJson {
|
||||
/**
|
||||
* What is quantified? E.g. 'speed', 'length' (including width, diameter, ...), 'electric tension', 'electric current', 'duration'
|
||||
*/
|
||||
quantity?: string
|
||||
/**
|
||||
* Every key from this list will be normalized.
|
||||
*
|
||||
* To render the value properly (with a human readable denomination), use `{canonical(<key>)}`
|
||||
*/
|
||||
appliesToKey: string[]
|
||||
appliesToKey?: string[]
|
||||
/**
|
||||
* If set, invalid values will be erased in the MC application (but not in OSM of course!)
|
||||
* Be careful with setting this
|
||||
|
@ -143,4 +147,11 @@ export interface DenominationConfigJson {
|
|||
* Note that if all values use 'prefix', the dropdown might move to before the text field
|
||||
*/
|
||||
prefix?: boolean
|
||||
|
||||
/**
|
||||
* If set, add a space between the quantity and the denomination.
|
||||
*
|
||||
* E.g.: `50 mph` instad of `50mph`
|
||||
*/
|
||||
addSpace?: boolean
|
||||
}
|
||||
|
|
|
@ -105,8 +105,10 @@ export default class LayerConfig extends WithContextLoader {
|
|||
".units: the 'units'-section should be a list; you probably have an object there"
|
||||
)
|
||||
}
|
||||
this.units = (json.units ?? []).map((unitJson, i) =>
|
||||
Unit.fromJson(unitJson, `${context}.unit[${i}]`)
|
||||
this.units = [].concat(
|
||||
...(json.units ?? []).map((unitJson, i) =>
|
||||
Unit.fromJson(unitJson, `${context}.unit[${i}]`)
|
||||
)
|
||||
)
|
||||
|
||||
if (json.description !== undefined) {
|
||||
|
|
|
@ -3,18 +3,23 @@ import { FixedUiElement } from "../UI/Base/FixedUiElement"
|
|||
import Combine from "../UI/Base/Combine"
|
||||
import { Denomination } from "./Denomination"
|
||||
import UnitConfigJson from "./ThemeConfig/Json/UnitConfigJson"
|
||||
import unit from "../../assets/layers/unit/unit.json"
|
||||
|
||||
export class Unit {
|
||||
private static allUnits = this.initUnits()
|
||||
public readonly appliesToKeys: Set<string>
|
||||
public readonly denominations: Denomination[]
|
||||
public readonly denominationsSorted: Denomination[]
|
||||
public readonly eraseInvalid: boolean
|
||||
public readonly quantity: string
|
||||
|
||||
constructor(
|
||||
quantity: string,
|
||||
appliesToKeys: string[],
|
||||
applicableDenominations: Denomination[],
|
||||
eraseInvalid: boolean
|
||||
) {
|
||||
this.quantity = quantity
|
||||
this.appliesToKeys = new Set(appliesToKeys)
|
||||
this.denominations = applicableDenominations
|
||||
this.eraseInvalid = eraseInvalid
|
||||
|
@ -60,12 +65,24 @@ export class Unit {
|
|||
}
|
||||
}
|
||||
|
||||
static fromJson(
|
||||
json:
|
||||
| UnitConfigJson
|
||||
| Record<string, string | { quantity: string; denominations: string[] }>,
|
||||
ctx: string
|
||||
): Unit[] {
|
||||
if (!json.appliesToKey && !json.quantity) {
|
||||
return this.loadFromLibrary(<any>json, ctx)
|
||||
}
|
||||
return [this.parse(<UnitConfigJson>json, ctx)]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* // Should detect invalid defaultInput
|
||||
* let threwError = false
|
||||
* try{
|
||||
* Unit.fromJson({
|
||||
* Unit.parse({
|
||||
* appliesToKey: ["length"],
|
||||
* defaultInput: "xcm",
|
||||
* applicableUnits: [
|
||||
|
@ -82,7 +99,7 @@ export class Unit {
|
|||
* threwError // => true
|
||||
*
|
||||
* // Should work
|
||||
* Unit.fromJson({
|
||||
* Unit.parse({
|
||||
* appliesToKey: ["length"],
|
||||
* defaultInput: "xcm",
|
||||
* applicableUnits: [
|
||||
|
@ -98,9 +115,9 @@ export class Unit {
|
|||
* ]
|
||||
* }, "test")
|
||||
*/
|
||||
static fromJson(json: UnitConfigJson, ctx: string) {
|
||||
private static parse(json: UnitConfigJson, ctx: string): Unit {
|
||||
const appliesTo = json.appliesToKey
|
||||
for (let i = 0; i < appliesTo.length; i++) {
|
||||
for (let i = 0; i < (appliesTo ?? []).length; i++) {
|
||||
let key = appliesTo[i]
|
||||
if (key.trim() !== key) {
|
||||
throw `${ctx}.appliesToKey[${i}] is invalid: it starts or ends with whitespace`
|
||||
|
@ -112,15 +129,8 @@ export class Unit {
|
|||
}
|
||||
// Some keys do have unit handling
|
||||
|
||||
const applicable = json.applicableUnits.map(
|
||||
(u, i) =>
|
||||
new Denomination(
|
||||
u,
|
||||
u.canonicalDenomination === undefined
|
||||
? undefined
|
||||
: u.canonicalDenomination.trim() === json.defaultInput,
|
||||
`${ctx}.units[${i}]`
|
||||
)
|
||||
const applicable = json.applicableUnits.map((u, i) =>
|
||||
Denomination.fromJson(u, `${ctx}.units[${i}]`)
|
||||
)
|
||||
|
||||
if (
|
||||
|
@ -133,7 +143,85 @@ export class Unit {
|
|||
.map((denom) => denom.canonical)
|
||||
.join(", ")}`
|
||||
}
|
||||
return new Unit(appliesTo, applicable, json.eraseInvalidValues ?? false)
|
||||
return new Unit(
|
||||
json.quantity ?? "",
|
||||
appliesTo,
|
||||
applicable,
|
||||
json.eraseInvalidValues ?? false
|
||||
)
|
||||
}
|
||||
|
||||
private static initUnits(): Map<string, Unit> {
|
||||
const m = new Map<string, Unit>()
|
||||
const units = (<UnitConfigJson[]>unit.units).map((json, i) =>
|
||||
this.parse(json, "unit.json.units." + i)
|
||||
)
|
||||
|
||||
for (const unit of units) {
|
||||
m.set(unit.quantity, unit)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
private static getFromLibrary(name: string, ctx: string): Unit {
|
||||
const loaded = this.allUnits.get(name)
|
||||
if (loaded === undefined) {
|
||||
throw (
|
||||
"No unit with quantity name " +
|
||||
name +
|
||||
" found (at " +
|
||||
ctx +
|
||||
"). Try one of: " +
|
||||
Array.from(this.allUnits.keys()).join(", ")
|
||||
)
|
||||
}
|
||||
return loaded
|
||||
}
|
||||
|
||||
private static loadFromLibrary(
|
||||
spec: Record<
|
||||
string,
|
||||
string | { quantity: string; denominations: string[]; canonical?: string }
|
||||
>,
|
||||
ctx: string
|
||||
): Unit[] {
|
||||
const units: Unit[] = []
|
||||
for (const key in spec) {
|
||||
const toLoad = spec[key]
|
||||
if (typeof toLoad === "string") {
|
||||
const loaded = this.getFromLibrary(toLoad, ctx)
|
||||
units.push(
|
||||
new Unit(loaded.quantity, [key], loaded.denominations, loaded.eraseInvalid)
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
const loaded = this.getFromLibrary(toLoad.quantity, ctx)
|
||||
const quantity = toLoad.quantity
|
||||
function fetchDenom(d: string): Denomination {
|
||||
const found = loaded.denominations.find(
|
||||
(denom) => denom.canonical.toLowerCase() === d
|
||||
)
|
||||
if (!found) {
|
||||
throw (
|
||||
`Could not find a denomination \`${d}\`for quantity ${quantity} at ${ctx}. Perhaps you meant to use on of ` +
|
||||
loaded.denominations.map((d) => d.canonical).join(", ")
|
||||
)
|
||||
}
|
||||
return found
|
||||
}
|
||||
|
||||
const denoms = toLoad.denominations
|
||||
.map((d) => d.toLowerCase())
|
||||
.map((d) => fetchDenom(d))
|
||||
|
||||
if (toLoad.canonical) {
|
||||
const canonical = fetchDenom(toLoad.canonical)
|
||||
denoms.unshift(canonical.withBlankCanonical())
|
||||
}
|
||||
units.push(new Unit(loaded.quantity, [key], denoms, loaded.eraseInvalid))
|
||||
}
|
||||
return units
|
||||
}
|
||||
|
||||
isApplicableToKey(key: string | undefined): boolean {
|
||||
|
@ -161,47 +249,34 @@ export class Unit {
|
|||
return [undefined, undefined]
|
||||
}
|
||||
|
||||
asHumanLongValue(value: string, country: () => string): BaseUIElement {
|
||||
asHumanLongValue(value: string, country: () => string): BaseUIElement | string {
|
||||
if (value === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const [stripped, denom] = this.findDenomination(value, country)
|
||||
const human = stripped === "1" ? denom?.humanSingular : denom?.human
|
||||
if (stripped === "1") {
|
||||
return denom?.humanSingular ?? stripped
|
||||
}
|
||||
const human = denom?.human
|
||||
if (human === undefined) {
|
||||
return new FixedUiElement(stripped ?? value)
|
||||
return stripped ?? value
|
||||
}
|
||||
|
||||
const elems = denom.prefix ? [human, stripped] : [stripped, human]
|
||||
return new Combine(elems)
|
||||
return human.Subs({ quantity: value })
|
||||
}
|
||||
|
||||
public getDefaultInput(country: () => string | string[]) {
|
||||
console.log("Searching the default denomination for input", country)
|
||||
for (const denomination of this.denominations) {
|
||||
if (denomination.useAsDefaultInput === true) {
|
||||
return denomination
|
||||
}
|
||||
if (
|
||||
denomination.useAsDefaultInput === undefined ||
|
||||
denomination.useAsDefaultInput === false
|
||||
) {
|
||||
continue
|
||||
}
|
||||
let countries: string | string[] = country()
|
||||
if (typeof countries === "string") {
|
||||
countries = countries.split(",")
|
||||
}
|
||||
const denominationCountries: string[] = denomination.useAsDefaultInput
|
||||
if (countries.some((country) => denominationCountries.indexOf(country) >= 0)) {
|
||||
return denomination
|
||||
}
|
||||
public toOsm(value: string, denomination: string) {
|
||||
const denom = this.denominations.find((d) => d.canonical === denomination)
|
||||
const space = denom.addSpace ? " " : ""
|
||||
if (denom.prefix) {
|
||||
return denom.canonical + space + value
|
||||
}
|
||||
return this.denominations[0]
|
||||
return value + space + denom.canonical
|
||||
}
|
||||
|
||||
public getDefaultDenomination(country: () => string) {
|
||||
for (const denomination of this.denominations) {
|
||||
if (denomination.useIfNoUnitGiven === true || denomination.canonical === "") {
|
||||
if (denomination.useIfNoUnitGiven === true) {
|
||||
return denomination
|
||||
}
|
||||
if (
|
||||
|
@ -219,6 +294,11 @@ export class Unit {
|
|||
return denomination
|
||||
}
|
||||
}
|
||||
for (const denomination of this.denominations) {
|
||||
if (denomination.canonical === "") {
|
||||
return denomination
|
||||
}
|
||||
}
|
||||
return this.denominations[0]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,95 +1,102 @@
|
|||
<script lang="ts">
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
import type { ValidatorType } from "./Validators"
|
||||
import Validators from "./Validators"
|
||||
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import { createEventDispatcher, onDestroy } from "svelte"
|
||||
import { Validator } from "./Validator"
|
||||
import { Unit } from "../../Models/Unit"
|
||||
import UnitInput from "../Popup/UnitInput.svelte"
|
||||
import { Utils } from "../../Utils"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||
import type { ValidatorType } from "./Validators";
|
||||
import Validators from "./Validators";
|
||||
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||
import { Translation } from "../i18n/Translation";
|
||||
import { createEventDispatcher, onDestroy } from "svelte";
|
||||
import { Validator } from "./Validator";
|
||||
import { Unit } from "../../Models/Unit";
|
||||
import UnitInput from "../Popup/UnitInput.svelte";
|
||||
import { Utils } from "../../Utils";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export let type: ValidatorType
|
||||
export let feedback: UIEventSource<Translation> | undefined = undefined
|
||||
export let cls: string = undefined
|
||||
export let getCountry: () => string | undefined
|
||||
export let placeholder: string | Translation | undefined
|
||||
export let unit: Unit = undefined
|
||||
export let value: UIEventSource<string>
|
||||
export let type: ValidatorType;
|
||||
export let feedback: UIEventSource<Translation> | undefined = undefined;
|
||||
export let cls: string = undefined;
|
||||
export let getCountry: () => string | undefined;
|
||||
export let placeholder: string | Translation | undefined;
|
||||
export let unit: Unit = undefined;
|
||||
/**
|
||||
* Valid state, exported to the calling component
|
||||
*/
|
||||
export let value: UIEventSource<string | undefined>;
|
||||
/**
|
||||
* Internal state bound to the input element.
|
||||
*
|
||||
* This is only copied to 'value' when appropriate so that no invalid values leak outside;
|
||||
* Additionally, the unit is added when copying
|
||||
*/
|
||||
let _value = new UIEventSource(value.data ?? "")
|
||||
let _value = new UIEventSource(value.data ?? "");
|
||||
|
||||
let validator: Validator = Validators.get(type ?? "string")
|
||||
let validator: Validator = Validators.get(type ?? "string");
|
||||
if (validator === undefined) {
|
||||
console.warn("Didn't find a validator for type", type)
|
||||
console.warn("Didn't find a validator for type", type);
|
||||
}
|
||||
let selectedUnit: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||
let _placeholder = placeholder ?? validator?.getPlaceholder() ?? type
|
||||
let selectedUnit: UIEventSource<string> = new UIEventSource<string>(undefined);
|
||||
let _placeholder = placeholder ?? validator?.getPlaceholder() ?? type;
|
||||
|
||||
function initValueAndDenom() {
|
||||
if (unit && value.data) {
|
||||
const [v, denom] = unit?.findDenomination(value.data, getCountry)
|
||||
const [v, denom] = unit?.findDenomination(value.data, getCountry);
|
||||
if (denom) {
|
||||
_value.setData(v)
|
||||
selectedUnit.setData(denom.canonical)
|
||||
_value.setData(v);
|
||||
selectedUnit.setData(denom.canonical);
|
||||
} else {
|
||||
_value.setData(value.data ?? "")
|
||||
_value.setData(value.data ?? "");
|
||||
}
|
||||
} else {
|
||||
_value.setData(value.data ?? "")
|
||||
_value.setData(value.data ?? "");
|
||||
}
|
||||
}
|
||||
|
||||
initValueAndDenom()
|
||||
initValueAndDenom();
|
||||
|
||||
$: {
|
||||
// The type changed -> reset some values
|
||||
validator = Validators.get(type ?? "string")
|
||||
validator = Validators.get(type ?? "string");
|
||||
|
||||
_placeholder = placeholder ?? validator?.getPlaceholder() ?? type
|
||||
feedback?.setData(validator?.getFeedback(_value.data, getCountry))
|
||||
_placeholder = placeholder ?? validator?.getPlaceholder() ?? type;
|
||||
feedback?.setData(validator?.getFeedback(_value.data, getCountry));
|
||||
|
||||
initValueAndDenom()
|
||||
initValueAndDenom();
|
||||
}
|
||||
|
||||
function setValues() {
|
||||
// Update the value stores
|
||||
const v = _value.data
|
||||
const v = _value.data;
|
||||
if (!validator?.isValid(v, getCountry) || v === "") {
|
||||
feedback?.setData(validator?.getFeedback(v, getCountry))
|
||||
value.setData("")
|
||||
return
|
||||
feedback?.setData(validator?.getFeedback(v, getCountry));
|
||||
value.setData("");
|
||||
return;
|
||||
}
|
||||
|
||||
if (unit !== undefined && isNaN(Number(v))) {
|
||||
value.setData(undefined)
|
||||
return
|
||||
value.setData(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
feedback?.setData(undefined)
|
||||
feedback?.setData(undefined);
|
||||
if (selectedUnit.data) {
|
||||
value.setData(v + selectedUnit.data)
|
||||
value.setData(unit.toOsm(v, selectedUnit.data))
|
||||
} else {
|
||||
value.setData(v)
|
||||
value.setData(v);
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(_value.addCallbackAndRun((_) => setValues()))
|
||||
onDestroy(
|
||||
value.addCallbackAndRunD((fromUpstream) => {
|
||||
if (_value.data !== fromUpstream && fromUpstream !== "") {
|
||||
_value.setData(fromUpstream)
|
||||
}
|
||||
})
|
||||
)
|
||||
onDestroy(selectedUnit.addCallback((_) => setValues()))
|
||||
onDestroy(_value.addCallbackAndRun((_) => setValues()));
|
||||
if (unit === undefined) {
|
||||
onDestroy(
|
||||
value.addCallbackAndRunD((fromUpstream) => {
|
||||
if (_value.data !== fromUpstream && fromUpstream !== "") {
|
||||
_value.setData(fromUpstream);
|
||||
}
|
||||
})
|
||||
);
|
||||
}else{
|
||||
// Handled by the UnitInput
|
||||
}
|
||||
onDestroy(selectedUnit.addCallback((_) => setValues()));
|
||||
if (validator === undefined) {
|
||||
throw (
|
||||
"Not a valid type (no validator found) for type '" +
|
||||
|
@ -102,17 +109,17 @@
|
|||
)
|
||||
.slice(0, 5)
|
||||
.join(", ")
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const isValid = _value.map((v) => validator?.isValid(v, getCountry) ?? true)
|
||||
const isValid = _value.map((v) => validator?.isValid(v, getCountry) ?? true);
|
||||
|
||||
let htmlElem: HTMLInputElement
|
||||
let htmlElem: HTMLInputElement;
|
||||
|
||||
let dispatch = createEventDispatcher<{ selected; submit }>()
|
||||
let dispatch = createEventDispatcher<{ selected; submit }>();
|
||||
$: {
|
||||
if (htmlElem !== undefined) {
|
||||
htmlElem.onfocus = () => dispatch("selected")
|
||||
htmlElem.onfocus = () => dispatch("selected");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,9 +128,9 @@
|
|||
*/
|
||||
function sendSubmit() {
|
||||
if (feedback?.data) {
|
||||
console.log("Not sending a submit as there is feedback")
|
||||
console.log("Not sending a submit as there is feedback");
|
||||
}
|
||||
dispatch("submit")
|
||||
dispatch("submit");
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -150,7 +157,7 @@
|
|||
{/if}
|
||||
|
||||
{#if unit !== undefined}
|
||||
<UnitInput {unit} {selectedUnit} textValue={_value} upstreamValue={value} />
|
||||
<UnitInput {unit} {selectedUnit} textValue={_value} upstreamValue={value} {getCountry} />
|
||||
{/if}
|
||||
</form>
|
||||
{/if}
|
||||
|
|
|
@ -1,56 +1,67 @@
|
|||
<script lang="ts">
|
||||
import { Unit } from "../../Models/Unit"
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import { onDestroy } from "svelte"
|
||||
import { Unit } from "../../Models/Unit";
|
||||
import { Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||
import Tr from "../Base/Tr.svelte";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import { Denomination } from "../../Models/Denomination";
|
||||
|
||||
export let unit: Unit
|
||||
export let unit: Unit;
|
||||
|
||||
/**
|
||||
* The current value of the input field
|
||||
* Not necessarily a correct number
|
||||
* Not necessarily a correct number, should not contain the denomination
|
||||
*/
|
||||
export let textValue: UIEventSource<string>
|
||||
export let textValue: UIEventSource<string>;
|
||||
/**
|
||||
* The actual _valid_ value that is upstreamed
|
||||
* The actual _valid_ value that is upstreamed, including the denomination
|
||||
*/
|
||||
export let upstreamValue: Store<string>
|
||||
export let upstreamValue: Store<string>;
|
||||
|
||||
let isSingle: Store<boolean> = textValue.map((v) => Number(v) === 1)
|
||||
let isSingle: Store<boolean> = textValue.map((v) => Number(v) === 1);
|
||||
|
||||
export let selectedUnit: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||
export let getCountry = () => "be"
|
||||
console.log("Unit", unit)
|
||||
export let selectedUnit: UIEventSource<string> = new UIEventSource<string>(undefined);
|
||||
export let getCountry = () => "?";
|
||||
|
||||
onMount(() => {
|
||||
console.log("Setting selected unit based on country", getCountry(), upstreamValue.data)
|
||||
if(upstreamValue.data === undefined || upstreamValue.data === ""){
|
||||
// Init the selected unit
|
||||
let denomination: Denomination = unit.getDefaultDenomination(getCountry);
|
||||
console.log("Found denom", denomination.canonical)
|
||||
selectedUnit.setData(denomination.canonical)
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(
|
||||
upstreamValue.addCallbackAndRun((v) => {
|
||||
if (v === undefined) {
|
||||
if (!selectedUnit.data) {
|
||||
selectedUnit.setData(unit.getDefaultDenomination(getCountry).canonical)
|
||||
}
|
||||
if(v === undefined || v === ""){
|
||||
return
|
||||
}
|
||||
const selected = unit.findDenomination(v, getCountry)
|
||||
if (selected === undefined) {
|
||||
selectedUnit.setData(unit.getDefaultDenomination(getCountry).canonical)
|
||||
return
|
||||
let denomination: Denomination = unit.getDefaultDenomination(getCountry);
|
||||
const selected = unit.findDenomination(v, getCountry);
|
||||
if(selected){
|
||||
denomination = selected[1];
|
||||
}
|
||||
const [value, denomination] = selected
|
||||
selectedUnit.setData(denomination.canonical)
|
||||
return
|
||||
selectedUnit.setData(denomination.canonical);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
onDestroy(
|
||||
textValue.addCallbackAndRunD((v) => {
|
||||
// Fallback in case that the user manually types a denomination
|
||||
const [value, denomination] = unit.findDenomination(v, getCountry)
|
||||
const [value, denomination] = unit.findDenomination(v, getCountry);
|
||||
if (value === undefined || denomination === undefined) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
textValue.setData(value)
|
||||
selectedUnit.setData(denomination.canonical)
|
||||
if(value === v){
|
||||
// The input value actually didn't have a denomination typed out - so lets ignore this one
|
||||
// If a denomination is given, it is the default value anyway
|
||||
return;
|
||||
}
|
||||
textValue.setData(value);
|
||||
selectedUnit.setData(denomination.canonical);
|
||||
})
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
<select bind:value={$selectedUnit}>
|
||||
|
@ -59,7 +70,7 @@
|
|||
{#if $isSingle}
|
||||
<Tr t={denom.humanSingular} />
|
||||
{:else}
|
||||
<Tr t={denom.human} />
|
||||
<Tr t={denom.human.Subs({quantity: ""})} />
|
||||
{/if}
|
||||
</option>
|
||||
{/each}
|
||||
|
|
|
@ -83,6 +83,7 @@ import NearbyImages from "./Image/NearbyImages.svelte"
|
|||
import NearbyImagesCollapsed from "./Image/NearbyImagesCollapsed.svelte"
|
||||
import { svelte } from "@sveltejs/vite-plugin-svelte"
|
||||
import MoveWizard from "./Popup/MoveWizard.svelte"
|
||||
import { Unit } from "../Models/Unit"
|
||||
|
||||
class NearbyImageVis implements SpecialVisualization {
|
||||
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
||||
|
@ -890,7 +891,7 @@ export default class SpecialVisualizations {
|
|||
if (value === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const allUnits = [].concat(
|
||||
const allUnits: Unit[] = [].concat(
|
||||
...(state?.layout?.layers?.map((lyr) => lyr.units) ?? [])
|
||||
)
|
||||
const unit = allUnits.filter((unit) =>
|
||||
|
@ -899,7 +900,9 @@ export default class SpecialVisualizations {
|
|||
if (unit === undefined) {
|
||||
return value
|
||||
}
|
||||
return unit.asHumanLongValue(value)
|
||||
const getCountry = () => tagSource.data._country
|
||||
const [v, denom] = unit.findDenomination(value, getCountry)
|
||||
return unit.asHumanLongValue(v, getCountry)
|
||||
})
|
||||
)
|
||||
},
|
||||
|
|
|
@ -131,7 +131,7 @@ export default class Translations {
|
|||
}
|
||||
|
||||
static isProbablyATranslation(transl: any) {
|
||||
if (typeof transl !== "object") {
|
||||
if (!transl || typeof transl !== "object") {
|
||||
return false
|
||||
}
|
||||
if (Object.keys(transl).length == 0) {
|
||||
|
|
|
@ -1081,7 +1081,19 @@
|
|||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TagRenderingConfigJson"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TagRenderingConfigJson"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
|
@ -1103,7 +1115,16 @@
|
|||
"maxItems": 1
|
||||
}
|
||||
],
|
||||
"description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)"
|
||||
"description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)\nUse `auto:<tagrenderingId>` to automatically create an icon based on a tagRendering which has icons"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"titleIcons"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"type": "object",
|
||||
"description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -10380,6 +10401,10 @@
|
|||
"if": "value=tree_node",
|
||||
"then": "tree_node - A layer showing trees"
|
||||
},
|
||||
{
|
||||
"if": "value=unit",
|
||||
"then": "unit - Library layer with all common units"
|
||||
},
|
||||
{
|
||||
"if": "value=usersettings",
|
||||
"then": "usersettings - A special layer which is not meant to be shown on a map, but which is used to set user settings"
|
||||
|
@ -10447,7 +10472,7 @@
|
|||
"hints": {
|
||||
"typehint": "tagrendering[]",
|
||||
"group": "tagrenderings",
|
||||
"question": "Edit this attribute showing piece/question"
|
||||
"question": "Edit this way this attributed is displayed or queried"
|
||||
},
|
||||
"type": [
|
||||
{
|
||||
|
@ -15969,21 +15994,20 @@
|
|||
},
|
||||
{
|
||||
"path": [
|
||||
"units"
|
||||
"units",
|
||||
"quantity"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {
|
||||
"default": "ult: true,"
|
||||
},
|
||||
"type": "object",
|
||||
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\nNot only do we want to write consistent data to OSM, we also want to present this consistently to the user.\nThis is handled by defining units.\n# Rendering\nTo render a value with long (human) denomination, use {canonical(key)}\n# Usage\nFirst of all, you define which keys have units applied, for example:\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n human: {\n en: \"kilometer/hour\",\n nl: \"kilometer/uur\"\n },\n humanShort: {\n en: \"km/h\",\n nl: \"km/u\"\n }\n},\n{\n canoncialDenomination: \"mph\",\n ... similar for miles an hour ...\n}\n]\n```\nIf this is defined, then every key which the denominations apply to (`maxspeed`, `maxspeed:hgv` and `maxspeed:bus`) will be rewritten at the metatagging stage:\nevery value will be parsed and the canonical extension will be added add presented to the other parts of the code.\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given"
|
||||
"hints": {},
|
||||
"type": "string",
|
||||
"description": "What is quantified? E.g. 'speed', 'length' (including width, diameter, ...), 'electric tension', 'electric current', 'duration'"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"units",
|
||||
"appliesToKey"
|
||||
],
|
||||
"required": true,
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"type": "array",
|
||||
"description": "Every key from this list will be normalized.\nTo render the value properly (with a human readable denomination), use `{canonical(<key>)}`"
|
||||
|
@ -16109,6 +16133,17 @@
|
|||
"type": "boolean",
|
||||
"description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"units",
|
||||
"applicableUnits",
|
||||
"addSpace"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"type": "boolean",
|
||||
"description": "If set, add a space between the quantity and the denomination.\nE.g.: `50 mph` instad of `50mph`"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"units",
|
||||
|
|
|
@ -832,6 +832,10 @@
|
|||
"if": "value=tree_node",
|
||||
"then": "tree_node - A layer showing trees"
|
||||
},
|
||||
{
|
||||
"if": "value=unit",
|
||||
"then": "unit - Library layer with all common units"
|
||||
},
|
||||
{
|
||||
"if": "value=usersettings",
|
||||
"then": "usersettings - A special layer which is not meant to be shown on a map, but which is used to set user settings"
|
||||
|
@ -1183,14 +1187,26 @@
|
|||
"type": "boolean"
|
||||
},
|
||||
"titleIcons": {
|
||||
"description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)\n\nType: icon[]\ngroup: infobox",
|
||||
"description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)\n\nUse `auto:<tagrenderingId>` to automatically create an icon based on a tagRendering which has icons\n\nType: icon[]\ngroup: infobox",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TagRenderingConfigJson"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TagRenderingConfigJson"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
|
@ -1300,7 +1316,7 @@
|
|||
}
|
||||
},
|
||||
"tagRenderings": {
|
||||
"description": "question: Edit this attribute showing piece/question\n\nA tag rendering is a block that either shows the known value or asks a question.\n\nRefer to the class `TagRenderingConfigJson` to see the possibilities.\n\nNote that we can also use a string here - where the string refers to a tag rendering defined in `assets/questions/questions.json`,\nwhere a few very general questions are defined e.g. website, phone number, ...\nFurthermore, _all_ the questions of another layer can be reused with `otherlayer.*`\nIf you need only a single of the tagRenderings, use `otherlayer.tagrenderingId`\nIf one or more questions have a 'group' or 'label' set, select all the entries with the corresponding group or label with `otherlayer.*group`\nRemark: if a tagRendering is 'lent' from another layer, the 'source'-tags are copied and added as condition.\nIf they are not wanted, remove them with an override\n\nA special value is 'questions', which indicates the location of the questions box. If not specified, it'll be appended to the bottom of the featureInfobox.\n\nAt last, one can define a group of renderings where parts of all strings will be replaced by multiple other strings.\nThis is mainly create questions for a 'left' and a 'right' side of the road.\nThese will be grouped and questions will be asked together\n\ntype: tagrendering[]\ngroup: tagrenderings",
|
||||
"description": "question: Edit this way this attributed is displayed or queried\n\nA tag rendering is a block that either shows the known value or asks a question.\n\nRefer to the class `TagRenderingConfigJson` to see the possibilities.\n\nNote that we can also use a string here - where the string refers to a tag rendering defined in `assets/questions/questions.json`,\nwhere a few very general questions are defined e.g. website, phone number, ...\nFurthermore, _all_ the questions of another layer can be reused with `otherlayer.*`\nIf you need only a single of the tagRenderings, use `otherlayer.tagrenderingId`\nIf one or more questions have a 'group' or 'label' set, select all the entries with the corresponding group or label with `otherlayer.*group`\nRemark: if a tagRendering is 'lent' from another layer, the 'source'-tags are copied and added as condition.\nIf they are not wanted, remove them with an override\n\nA special value is 'questions', which indicates the location of the questions box. If not specified, it'll be appended to the bottom of the featureInfobox.\n\nAt last, one can define a group of renderings where parts of all strings will be replaced by multiple other strings.\nThis is mainly create questions for a 'left' and a 'right' side of the road.\nThese will be grouped and questions will be asked together\n\ntype: tagrendering[]\ngroup: tagrenderings",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
|
@ -1706,7 +1722,14 @@
|
|||
"units": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/default_2"
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/default_2"
|
||||
},
|
||||
{
|
||||
"$ref": "#/definitions/Record<string,string|{quantity:string;denominations:string[];}>"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"syncSelection": {
|
||||
|
@ -2889,7 +2912,19 @@
|
|||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TagRenderingConfigJson"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TagRenderingConfigJson"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
|
@ -2911,7 +2946,17 @@
|
|||
"maxItems": 1
|
||||
}
|
||||
],
|
||||
"description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)"
|
||||
"description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)\nUse `auto:<tagrenderingId>` to automatically create an icon based on a tagRendering which has icons"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"layers",
|
||||
"titleIcons"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"type": "object",
|
||||
"description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -12476,6 +12521,10 @@
|
|||
"if": "value=tree_node",
|
||||
"then": "tree_node - A layer showing trees"
|
||||
},
|
||||
{
|
||||
"if": "value=unit",
|
||||
"then": "unit - Library layer with all common units"
|
||||
},
|
||||
{
|
||||
"if": "value=usersettings",
|
||||
"then": "usersettings - A special layer which is not meant to be shown on a map, but which is used to set user settings"
|
||||
|
@ -12545,7 +12594,7 @@
|
|||
"hints": {
|
||||
"typehint": "tagrendering[]",
|
||||
"group": "tagrenderings",
|
||||
"question": "Edit this attribute showing piece/question"
|
||||
"question": "Edit this way this attributed is displayed or queried"
|
||||
},
|
||||
"type": [
|
||||
{
|
||||
|
@ -18273,14 +18322,13 @@
|
|||
{
|
||||
"path": [
|
||||
"layers",
|
||||
"units"
|
||||
"units",
|
||||
"quantity"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {
|
||||
"default": "ult: true,"
|
||||
},
|
||||
"type": "object",
|
||||
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\nNot only do we want to write consistent data to OSM, we also want to present this consistently to the user.\nThis is handled by defining units.\n# Rendering\nTo render a value with long (human) denomination, use {canonical(key)}\n# Usage\nFirst of all, you define which keys have units applied, for example:\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n human: {\n en: \"kilometer/hour\",\n nl: \"kilometer/uur\"\n },\n humanShort: {\n en: \"km/h\",\n nl: \"km/u\"\n }\n},\n{\n canoncialDenomination: \"mph\",\n ... similar for miles an hour ...\n}\n]\n```\nIf this is defined, then every key which the denominations apply to (`maxspeed`, `maxspeed:hgv` and `maxspeed:bus`) will be rewritten at the metatagging stage:\nevery value will be parsed and the canonical extension will be added add presented to the other parts of the code.\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given"
|
||||
"hints": {},
|
||||
"type": "string",
|
||||
"description": "What is quantified? E.g. 'speed', 'length' (including width, diameter, ...), 'electric tension', 'electric current', 'duration'"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -18288,7 +18336,7 @@
|
|||
"units",
|
||||
"appliesToKey"
|
||||
],
|
||||
"required": true,
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"type": "array",
|
||||
"description": "Every key from this list will be normalized.\nTo render the value properly (with a human readable denomination), use `{canonical(<key>)}`"
|
||||
|
@ -18423,6 +18471,18 @@
|
|||
"type": "boolean",
|
||||
"description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"layers",
|
||||
"units",
|
||||
"applicableUnits",
|
||||
"addSpace"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"type": "boolean",
|
||||
"description": "If set, add a space between the quantity and the denomination.\nE.g.: `50 mph` instad of `50mph`"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"layers",
|
||||
|
@ -19646,7 +19706,19 @@
|
|||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TagRenderingConfigJson"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/TagRenderingConfigJson"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
|
@ -19668,7 +19740,18 @@
|
|||
"maxItems": 1
|
||||
}
|
||||
],
|
||||
"description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)"
|
||||
"description": "Small icons shown next to the title.\nIf not specified, the OsmLink and wikipedia links will be used by default.\nUse an empty array to hide them.\nNote that \"defaults\" will insert all the default titleIcons (which are added automatically)\nUse `auto:<tagrenderingId>` to automatically create an icon based on a tagRendering which has icons"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"layers",
|
||||
"override",
|
||||
"titleIcons"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"type": "object",
|
||||
"description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -29521,6 +29604,10 @@
|
|||
"if": "value=tree_node",
|
||||
"then": "tree_node - A layer showing trees"
|
||||
},
|
||||
{
|
||||
"if": "value=unit",
|
||||
"then": "unit - Library layer with all common units"
|
||||
},
|
||||
{
|
||||
"if": "value=usersettings",
|
||||
"then": "usersettings - A special layer which is not meant to be shown on a map, but which is used to set user settings"
|
||||
|
@ -29592,7 +29679,7 @@
|
|||
"hints": {
|
||||
"typehint": "tagrendering[]",
|
||||
"group": "tagrenderings",
|
||||
"question": "Edit this attribute showing piece/question"
|
||||
"question": "Edit this way this attributed is displayed or queried"
|
||||
},
|
||||
"type": [
|
||||
{
|
||||
|
@ -35526,14 +35613,13 @@
|
|||
"path": [
|
||||
"layers",
|
||||
"override",
|
||||
"units"
|
||||
"units",
|
||||
"quantity"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {
|
||||
"default": "ult: true,"
|
||||
},
|
||||
"type": "object",
|
||||
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\nNot only do we want to write consistent data to OSM, we also want to present this consistently to the user.\nThis is handled by defining units.\n# Rendering\nTo render a value with long (human) denomination, use {canonical(key)}\n# Usage\nFirst of all, you define which keys have units applied, for example:\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n human: {\n en: \"kilometer/hour\",\n nl: \"kilometer/uur\"\n },\n humanShort: {\n en: \"km/h\",\n nl: \"km/u\"\n }\n},\n{\n canoncialDenomination: \"mph\",\n ... similar for miles an hour ...\n}\n]\n```\nIf this is defined, then every key which the denominations apply to (`maxspeed`, `maxspeed:hgv` and `maxspeed:bus`) will be rewritten at the metatagging stage:\nevery value will be parsed and the canonical extension will be added add presented to the other parts of the code.\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given"
|
||||
"hints": {},
|
||||
"type": "string",
|
||||
"description": "What is quantified? E.g. 'speed', 'length' (including width, diameter, ...), 'electric tension', 'electric current', 'duration'"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
|
@ -35542,7 +35628,7 @@
|
|||
"units",
|
||||
"appliesToKey"
|
||||
],
|
||||
"required": true,
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"type": "array",
|
||||
"description": "Every key from this list will be normalized.\nTo render the value properly (with a human readable denomination), use `{canonical(<key>)}`"
|
||||
|
@ -35686,6 +35772,19 @@
|
|||
"type": "boolean",
|
||||
"description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"layers",
|
||||
"override",
|
||||
"units",
|
||||
"applicableUnits",
|
||||
"addSpace"
|
||||
],
|
||||
"required": false,
|
||||
"hints": {},
|
||||
"type": "boolean",
|
||||
"description": "If set, add a space between the quantity and the denomination.\nE.g.: `50 mph` instad of `50mph`"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"layers",
|
||||
|
|
|
@ -4,22 +4,21 @@ import { describe, expect, it } from "vitest"
|
|||
|
||||
describe("Unit", () => {
|
||||
it("should convert a value back and forth", () => {
|
||||
const denomintion = new Denomination(
|
||||
const denomintion = Denomination.fromJson(
|
||||
{
|
||||
canonicalDenomination: "MW",
|
||||
alternativeDenomination: ["megawatts", "megawatt"],
|
||||
human: {
|
||||
en: " megawatts",
|
||||
nl: " megawatt",
|
||||
en: "{quantity} megawatts",
|
||||
nl: "{quantity} megawatt",
|
||||
},
|
||||
},
|
||||
false,
|
||||
"test"
|
||||
)
|
||||
|
||||
const canonical = denomintion.canonicalValue("5", true)
|
||||
expect(canonical).toBe("5 MW")
|
||||
const units = new Unit(["key"], [denomintion], false)
|
||||
const units = new Unit("quantity", ["key"], [denomintion], false)
|
||||
const [detected, detectedDenom] = units.findDenomination("5 MW", () => "be")
|
||||
expect(detected).toBe("5")
|
||||
expect(detectedDenom).toBe(denomintion)
|
||||
|
|
Loading…
Reference in a new issue