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": [
|
"units": [
|
||||||
{
|
{
|
||||||
"appliesToKey": [
|
"door:width": {
|
||||||
"door:width",
|
"quantity": "distance",
|
||||||
"elevator:width",
|
"canonical": "m",
|
||||||
"elevator:depth"
|
"denominations": [
|
||||||
],
|
"cm"
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"elevator:width": {
|
||||||
|
"quantity": "distance",
|
||||||
|
"canonical": "m",
|
||||||
|
"denominations": [
|
||||||
|
"cm"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"elevator:depth": {
|
||||||
|
"quantity": "distance",
|
||||||
|
"canonical": "m",
|
||||||
|
"denominations": [
|
||||||
|
"cm"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -567,47 +567,20 @@
|
||||||
],
|
],
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"appliesToKey": [
|
"kerb:height": {
|
||||||
"kerb:height",
|
"quantity": "distance",
|
||||||
"width"
|
"canonical": "m",
|
||||||
],
|
"denominations": [
|
||||||
"defaultInput": "cm",
|
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"quantity": "distance",
|
||||||
|
"canonical": "m",
|
||||||
|
"denominations": [
|
||||||
|
"cm"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -549,37 +549,12 @@
|
||||||
],
|
],
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"applicableUnits": [
|
"fire_hydrant:diameter": {
|
||||||
{
|
"quantity": "distance",
|
||||||
"canonicalDenomination": "",
|
"denominations": [
|
||||||
"alternativeDenomination": [
|
"mm"
|
||||||
"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"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -394,69 +394,13 @@
|
||||||
],
|
],
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"applicableUnits": [
|
"kerb:height": {
|
||||||
{
|
"quantity": "distance",
|
||||||
"canonicalDenomination": "cm",
|
"denominations": [
|
||||||
"alternativeDenomination": [
|
"cm",
|
||||||
"centimeter",
|
"m"
|
||||||
"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"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -155,73 +155,13 @@
|
||||||
"allowSplit": true,
|
"allowSplit": true,
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"applicableUnits": [
|
"maxspeed": {
|
||||||
{
|
"quantity": "speed",
|
||||||
"#": "km/h is the default for a maxspeed; should be empty string",
|
"canonical": "kmh",
|
||||||
"canonicalDenomination": "",
|
"denominations": [
|
||||||
"alternativeDenomination": [
|
"mph"
|
||||||
"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"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -362,29 +362,12 @@
|
||||||
},
|
},
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"appliesToKey": [
|
"height": {
|
||||||
"height"
|
"quantity": "distance",
|
||||||
],
|
"denominations": [
|
||||||
"applicableUnits": [
|
"m"
|
||||||
{
|
]
|
||||||
"canonicalDenomination": "m",
|
|
||||||
"alternativeDenomination": [
|
|
||||||
"meter",
|
|
||||||
"mtr"
|
|
||||||
],
|
|
||||||
"human": {
|
|
||||||
"nl": " meter",
|
|
||||||
"en": " meter",
|
|
||||||
"ru": " метр",
|
|
||||||
"de": " Meter",
|
|
||||||
"ca": " metre",
|
|
||||||
"es": " metros",
|
|
||||||
"pl": " metr",
|
|
||||||
"cs": " metr"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
|
||||||
"eraseInvalidValues": true
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -98,44 +98,13 @@
|
||||||
],
|
],
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"appliesToKey": [
|
"desk:height": {
|
||||||
"desk:height"
|
"quantity": "distance",
|
||||||
],
|
"denominations": [
|
||||||
"applicableUnits": [
|
"m",
|
||||||
{
|
"cm"
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,64 +116,13 @@
|
||||||
],
|
],
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"appliesToKey": [
|
"maxspeed": {
|
||||||
"maxspeed"
|
"quantity": "speed",
|
||||||
],
|
"denominations": [
|
||||||
"applicableUnits": [
|
|
||||||
{
|
|
||||||
"#": "km/h is the default for a maxspeed; should be empty string",
|
|
||||||
"canonicalDenomination": "",
|
|
||||||
"alternativeDenomination": [
|
|
||||||
"km/u",
|
|
||||||
"kmh",
|
"kmh",
|
||||||
"kph"
|
"mph"
|
||||||
],
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,64 +115,13 @@
|
||||||
],
|
],
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"appliesToKey": [
|
"maxspeed": {
|
||||||
"maxspeed"
|
"quantity": "speed",
|
||||||
],
|
"canonical": "kmh",
|
||||||
"applicableUnits": [
|
"denominations": [
|
||||||
{
|
"mph"
|
||||||
"#": "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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -460,7 +460,8 @@
|
||||||
"if": "surface=fine_gravel",
|
"if": "surface=fine_gravel",
|
||||||
"then": {
|
"then": {
|
||||||
"en": "The surface is <b>fine gravel</b>",
|
"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": [
|
"units": [
|
||||||
{
|
{
|
||||||
"appliesToKey": [
|
"door:width": {
|
||||||
"door:width"
|
"quantity": "distance",
|
||||||
],
|
"denominations": [
|
||||||
"applicableUnits": [
|
"m",
|
||||||
{
|
"cm"
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -480,42 +480,13 @@
|
||||||
},
|
},
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"appliesToKey": [
|
"toilets:door:width": {
|
||||||
"toilets:door:width"
|
"quantity": "distance",
|
||||||
],
|
"denominations": [
|
||||||
"applicableUnits": [
|
"m",
|
||||||
{
|
"cm"
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
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": [
|
"units": [
|
||||||
{
|
{
|
||||||
"appliesToKey": [
|
"width": {
|
||||||
"width",
|
"quantity": "distance",
|
||||||
"_biggest_width"
|
"denominations": [
|
||||||
],
|
"m",
|
||||||
"defaultUnit": "cm",
|
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"_biggest_width": {
|
||||||
|
"quantity": "distance",
|
||||||
|
"denominations": [
|
||||||
|
"m",
|
||||||
|
"cm"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -300,130 +300,19 @@
|
||||||
],
|
],
|
||||||
"units": [
|
"units": [
|
||||||
{
|
{
|
||||||
"appliesToKey": [
|
"generator:output:electricity": "power",
|
||||||
"generator:output:electricity"
|
"height": {
|
||||||
],
|
"quantity": "distance",
|
||||||
"applicableUnits": [
|
"denominations": [
|
||||||
{
|
"m"
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"rotor:diamter": {
|
||||||
|
"quantity": "distance",
|
||||||
|
"denominations": [
|
||||||
|
"m"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -120,61 +120,27 @@
|
||||||
],
|
],
|
||||||
"units+": [
|
"units+": [
|
||||||
{
|
{
|
||||||
"appliesToKey": [
|
"climbing:length": {
|
||||||
"climbing:length",
|
"quantity": "distance",
|
||||||
"climbing:length:min",
|
"canonical": "m",
|
||||||
"climbing:length:max"
|
"denominations": [
|
||||||
],
|
"ft"
|
||||||
"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:min": {
|
||||||
|
"quantity": "distance",
|
||||||
|
"canonical": "m",
|
||||||
|
"denominations": [
|
||||||
|
"ft"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"climbing:length:max": {
|
||||||
|
"quantity": "distance",
|
||||||
|
"canonical": "m",
|
||||||
|
"denominations": [
|
||||||
|
"ft"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"tagRenderings+": [
|
"tagRenderings+": [
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Combine from "../src/UI/Base/Combine"
|
import Combine from "../src/UI/Base/Combine"
|
||||||
import BaseUIElement from "../src/UI/BaseUIElement"
|
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 { AllKnownLayouts } from "../src/Customizations/AllKnownLayouts"
|
||||||
import TableOfContents from "../src/UI/Base/TableOfContents"
|
import TableOfContents from "../src/UI/Base/TableOfContents"
|
||||||
import SimpleMetaTaggers from "../src/Logic/SimpleMetaTagger"
|
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 LayoutConfig from "../src/Models/ThemeConfig/LayoutConfig"
|
||||||
import bookcases from "../src/assets/generated/themes/bookcases.json"
|
import bookcases from "../src/assets/generated/themes/bookcases.json"
|
||||||
import fakedom from "fake-dom"
|
import fakedom from "fake-dom"
|
||||||
|
import unit from "../src/assets/generated/layers/unit.json"
|
||||||
import Hotkeys from "../src/UI/Base/Hotkeys"
|
import Hotkeys from "../src/UI/Base/Hotkeys"
|
||||||
import { QueryParameters } from "../src/Logic/Web/QueryParameters"
|
import { QueryParameters } from "../src/Logic/Web/QueryParameters"
|
||||||
import Link from "../src/UI/Base/Link"
|
import Link from "../src/UI/Base/Link"
|
||||||
|
@ -29,14 +29,170 @@ import questions from "../src/assets/generated/layers/questions.json"
|
||||||
import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson"
|
import { LayerConfigJson } from "../src/Models/ThemeConfig/Json/LayerConfigJson"
|
||||||
import { Utils } from "../src/Utils"
|
import { Utils } from "../src/Utils"
|
||||||
import { TagUtils } from "../src/Logic/Tags/TagUtils"
|
import { TagUtils } from "../src/Logic/Tags/TagUtils"
|
||||||
function WriteFile(
|
import Script from "./Script"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a markdown-file into a .json file, which a walkthrough/slideshow element can use
|
||||||
|
*
|
||||||
|
* These are used in the studio
|
||||||
|
*/
|
||||||
|
class ToSlideshowJson {
|
||||||
|
private readonly _source: string
|
||||||
|
private readonly _target: string
|
||||||
|
|
||||||
|
constructor(source: string, target: string) {
|
||||||
|
this._source = source
|
||||||
|
this._target = target
|
||||||
|
}
|
||||||
|
|
||||||
|
public convert() {
|
||||||
|
const lines = readFileSync(this._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)
|
||||||
|
}
|
||||||
|
sections.push(currentSection)
|
||||||
|
writeFileSync(
|
||||||
|
this._target,
|
||||||
|
JSON.stringify({
|
||||||
|
sections: sections.map((s) => s.join("\n")).filter((s) => s.length > 0),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
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 ""
|
||||||
|
}
|
||||||
|
|
||||||
|
const languagesInDescr = Array.from(Object.keys(layout.shortDescription)).filter(
|
||||||
|
(k) => k !== "_context"
|
||||||
|
)
|
||||||
|
const languages = languagesInDescr.map((ln) => `{{#language:${ln}|en}}`).join(", ")
|
||||||
|
let auth = "Yes"
|
||||||
|
return `{{service_item
|
||||||
|
|name= [https://mapcomplete.org/${layout.id} ${layout.id}]
|
||||||
|
|region= Worldwide
|
||||||
|
|lang= ${languages}
|
||||||
|
|descr= A MapComplete theme: ${Translations.T(layout.shortDescription)
|
||||||
|
.textFor("en")
|
||||||
|
.replace("<a href='", "[[")
|
||||||
|
.replace(/'>.*<\/a>/, "]]")}
|
||||||
|
|material= {{yes|[https://mapcomplete.org/ ${auth}]}}
|
||||||
|
|image= MapComplete_Screenshot.png
|
||||||
|
|genre= POI, editor, ${layout.id}
|
||||||
|
}}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GenerateDocs extends Script {
|
||||||
|
constructor() {
|
||||||
|
super("Generates various documentation files")
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
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",
|
||||||
|
])
|
||||||
|
|
||||||
|
new WikiPageGenerator().generate()
|
||||||
|
|
||||||
|
console.log("Generated docs")
|
||||||
|
}
|
||||||
|
|
||||||
|
private WriteFile(
|
||||||
filename,
|
filename,
|
||||||
html: string | BaseUIElement,
|
html: string | BaseUIElement,
|
||||||
autogenSource: string[],
|
autogenSource: string[],
|
||||||
options?: {
|
options?: {
|
||||||
noTableOfContents: boolean
|
noTableOfContents: boolean
|
||||||
}
|
}
|
||||||
): void {
|
): void {
|
||||||
if (!html) {
|
if (!html) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -80,112 +236,52 @@ function WriteFile(
|
||||||
"[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources)"
|
"[//]: # (WARNING: this file is automatically generated. Please find the sources at the bottom and edit those sources)"
|
||||||
|
|
||||||
writeFileSync(filename, warnAutomated + md)
|
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")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates the documentation for the layers overview page
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter(
|
private generateHotkeyDocs() {
|
||||||
(layer) => layer["source"] === null
|
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)
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
this.WriteFile("./Docs/builtin_units.md", new Combine([new Title("Units", 1), ...els]), [
|
||||||
const layerIsNeededBy: Map<string, string[]> = new Map<string, string[]>()
|
`assets/layers/unit/unit.json`,
|
||||||
|
|
||||||
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.
|
* Generates documentation for the all the individual layers.
|
||||||
* Inline layers are included (if the theme is public)
|
* Inline layers are included (if the theme is public)
|
||||||
* @param callback
|
|
||||||
* @constructor
|
|
||||||
*/
|
*/
|
||||||
function GenOverviewsForSingleLayer(
|
private generateOverviewsForAllSingleLayer(): void {
|
||||||
callback: (layer: LayerConfig, element: BaseUIElement, inlineSource: string) => void
|
|
||||||
): void {
|
|
||||||
const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter(
|
const allLayers: LayerConfig[] = Array.from(AllSharedLayers.sharedLayers.values()).filter(
|
||||||
(layer) => layer["source"] !== null
|
(layer) => layer["source"] !== null
|
||||||
)
|
)
|
||||||
|
@ -252,99 +348,7 @@ function GenOverviewsForSingleLayer(
|
||||||
layerIsNeededBy,
|
layerIsNeededBy,
|
||||||
DependencyCalculator.getLayerDependencies(layer)
|
DependencyCalculator.getLayerDependencies(layer)
|
||||||
)
|
)
|
||||||
callback(layer, element, inlineLayers.get(layer.id))
|
const inlineSource = inlineLayers.get(layer.id)
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The wikitable is updated as some tools show an overview of apps based on the wiki.
|
|
||||||
*/
|
|
||||||
function generateWikipage() {
|
|
||||||
function generateWikiEntry(layout: {
|
|
||||||
hideFromOverview: boolean
|
|
||||||
id: string
|
|
||||||
shortDescription: any
|
|
||||||
}) {
|
|
||||||
if (layout.hideFromOverview) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
const languagesInDescr = Array.from(Object.keys(layout.shortDescription)).filter(
|
|
||||||
(k) => k !== "_context"
|
|
||||||
)
|
|
||||||
const languages = languagesInDescr.map((ln) => `{{#language:${ln}|en}}`).join(", ")
|
|
||||||
let auth = "Yes"
|
|
||||||
return `{{service_item
|
|
||||||
|name= [https://mapcomplete.org/${layout.id} ${layout.id}]
|
|
||||||
|region= Worldwide
|
|
||||||
|lang= ${languages}
|
|
||||||
|descr= A MapComplete theme: ${Translations.T(layout.shortDescription)
|
|
||||||
.textFor("en")
|
|
||||||
.replace("<a href='", "[[")
|
|
||||||
.replace(/'>.*<\/a>/, "]]")}
|
|
||||||
|material= {{yes|[https://mapcomplete.org/ ${auth}]}}
|
|
||||||
|image= MapComplete_Screenshot.png
|
|
||||||
|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)
|
|
||||||
}
|
|
||||||
sections.push(currentSection)
|
|
||||||
writeFileSync(
|
|
||||||
target,
|
|
||||||
JSON.stringify({
|
|
||||||
sections: sections.map((s) => s.join("\n")).filter((s) => s.length > 0),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function studioDocs() {
|
|
||||||
studioDocsFor("./Docs/Studio/Introduction.md", "./src/assets/studio_introduction.json")
|
|
||||||
studioDocsFor(
|
|
||||||
"./Docs/Studio/TagRenderingIntro.md",
|
|
||||||
"./src/assets/studio_tagrenderings_intro.json"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Starting documentation generation...")
|
|
||||||
ScriptUtils.fixUtils()
|
|
||||||
studioDocs()
|
|
||||||
generateWikipage()
|
|
||||||
GenOverviewsForSingleLayer((layer, element, inlineSource) => {
|
|
||||||
ScriptUtils.erasableLog("Exporting layer documentation for", layer.id)
|
ScriptUtils.erasableLog("Exporting layer documentation for", layer.id)
|
||||||
if (!existsSync("./Docs/Layers")) {
|
if (!existsSync("./Docs/Layers")) {
|
||||||
mkdirSync("./Docs/Layers")
|
mkdirSync("./Docs/Layers")
|
||||||
|
@ -353,51 +357,21 @@ GenOverviewsForSingleLayer((layer, element, inlineSource) => {
|
||||||
if (inlineSource !== undefined) {
|
if (inlineSource !== undefined) {
|
||||||
source = `assets/themes/${inlineSource}/${inlineSource}.json`
|
source = `assets/themes/${inlineSource}/${inlineSource}.json`
|
||||||
}
|
}
|
||||||
WriteFile("./Docs/Layers/" + layer.id + ".md", element, [source], { noTableOfContents: true })
|
this.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(), []), [
|
* Generate the builtinIndex which shows interlayer dependencies
|
||||||
"assets/layers/questions/questions.json",
|
* @private
|
||||||
])
|
*/
|
||||||
WriteFile("./Docs/Tags_format.md", TagUtils.generateDocs(), ["src/Logic/Tags/TagUtils.ts"])
|
|
||||||
|
|
||||||
{
|
private generateBuiltinIndex() {
|
||||||
// Generate the builtinIndex which shows interlayer dependencies
|
const layers = ScriptUtils.getLayerFiles().map((f) => f.parsed)
|
||||||
var layers = ScriptUtils.getLayerFiles().map((f) => f.parsed)
|
const builtinsPerLayer = new Map<string, string[]>()
|
||||||
var builtinsPerLayer = new Map<string, string[]>()
|
const layersUsingBuiltin = new Map<string /* Builtin */, string[]>()
|
||||||
var layersUsingBuiltin = new Map<string /* Builtin */, string[]>()
|
|
||||||
for (const layer of layers) {
|
for (const layer of layers) {
|
||||||
if (layer.tagRenderings === undefined) {
|
if (layer.tagRenderings === undefined) {
|
||||||
continue
|
continue
|
||||||
|
@ -436,25 +410,136 @@ WriteFile("./Docs/Tags_format.md", TagUtils.generateDocs(), ["src/Logic/Tags/Tag
|
||||||
new Combine([new Title(builtin), new List(usedByLayers)]).SetClass("flex flex-col")
|
new Combine([new Title(builtin), new List(usedByLayers)]).SetClass("flex flex-col")
|
||||||
),
|
),
|
||||||
]).SetClass("flex flex-col")
|
]).SetClass("flex flex-col")
|
||||||
WriteFile("./Docs/BuiltinIndex.md", docs, ["assets/layers/*.json"])
|
this.WriteFile("./Docs/BuiltinIndex.md", docs, ["assets/layers/*.json"])
|
||||||
}
|
}
|
||||||
|
|
||||||
WriteFile("./Docs/URL_Parameters.md", QueryParameterDocumentation.GenerateQueryParameterDocs(), [
|
private generateQueryParameterDocs() {
|
||||||
"src/Logic/Web/QueryParameters.ts",
|
if (fakedom === undefined) {
|
||||||
"src/UI/QueryParameterDocumentation.ts",
|
|
||||||
])
|
|
||||||
if (fakedom === undefined) {
|
|
||||||
throw "FakeDom not initialized"
|
throw "FakeDom not initialized"
|
||||||
}
|
}
|
||||||
QueryParameters.GetQueryParameter(
|
QueryParameters.GetQueryParameter(
|
||||||
"mode",
|
"mode",
|
||||||
"map",
|
"map",
|
||||||
"The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics'"
|
"The mode the application starts in, e.g. 'map', 'dashboard' or 'statistics'"
|
||||||
)
|
)
|
||||||
|
|
||||||
{
|
this.WriteFile(
|
||||||
new ThemeViewState(new LayoutConfig(<any>bookcases))
|
"./Docs/URL_Parameters.md",
|
||||||
WriteFile("./Docs/Hotkeys.md", Hotkeys.generateDocumentation(), [])
|
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"])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Generated docs")
|
new GenerateDocs().run()
|
||||||
|
|
|
@ -137,7 +137,8 @@ export default class DetermineLayout {
|
||||||
if (json.layers === undefined && json.tagRenderings !== undefined) {
|
if (json.layers === undefined && json.tagRenderings !== undefined) {
|
||||||
// We got fed a layer instead of a theme
|
// We got fed a layer instead of a theme
|
||||||
const layerConfig = <LayerConfigJson>json
|
const layerConfig = <LayerConfigJson>json
|
||||||
const iconTr: string | TagRenderingConfigJson = <any>(
|
const iconTr: string | TagRenderingConfigJson =
|
||||||
|
<any>(
|
||||||
layerConfig.pointRendering
|
layerConfig.pointRendering
|
||||||
.map((mr) => mr?.marker?.find((icon) => icon.icon !== undefined)?.icon)
|
.map((mr) => mr?.marker?.find((icon) => icon.icon !== undefined)?.icon)
|
||||||
.find((i) => i !== undefined)
|
.find((i) => i !== undefined)
|
||||||
|
@ -156,8 +157,8 @@ export default class DetermineLayout {
|
||||||
}
|
}
|
||||||
|
|
||||||
const knownLayersDict = new Map<string, LayerConfigJson>()
|
const knownLayersDict = new Map<string, LayerConfigJson>()
|
||||||
for (const key in known_layers.layers) {
|
for (const key in known_layers["layers"]) {
|
||||||
const layer = known_layers.layers[key]
|
const layer = known_layers["layers"][key]
|
||||||
knownLayersDict.set(layer.id, <LayerConfigJson>layer)
|
knownLayersDict.set(layer.id, <LayerConfigJson>layer)
|
||||||
}
|
}
|
||||||
const convertState: DesugaringContext = {
|
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 { DenominationConfigJson } from "./ThemeConfig/Json/UnitConfigJson"
|
||||||
import Translations from "../UI/i18n/Translations"
|
import Translations from "../UI/i18n/Translations"
|
||||||
|
|
||||||
|
@ -9,20 +9,39 @@ import Translations from "../UI/i18n/Translations"
|
||||||
export class Denomination {
|
export class Denomination {
|
||||||
public readonly canonical: string
|
public readonly canonical: string
|
||||||
public readonly _canonicalSingular: string
|
public readonly _canonicalSingular: string
|
||||||
public readonly useAsDefaultInput: boolean | string[]
|
|
||||||
public readonly useIfNoUnitGiven: boolean | string[]
|
public readonly useIfNoUnitGiven: boolean | string[]
|
||||||
public readonly prefix: boolean
|
public readonly prefix: boolean
|
||||||
|
public readonly addSpace: boolean
|
||||||
public readonly alternativeDenominations: string[]
|
public readonly alternativeDenominations: string[]
|
||||||
private readonly _human: Translation
|
public readonly human: TypedTranslation<{ quantity: string }>
|
||||||
private readonly _humanSingular?: Translation
|
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})`
|
context = `${context}.unit(${json.canonicalDenomination})`
|
||||||
this.canonical = json.canonicalDenomination.trim()
|
const canonical = json.canonicalDenomination.trim()
|
||||||
if (this.canonical === undefined) {
|
if (canonical === undefined) {
|
||||||
throw `${context}: this unit has no decent canonical value defined`
|
throw `${context}: this unit has no decent canonical value defined`
|
||||||
}
|
}
|
||||||
this._canonicalSingular = json.canonicalDenominationSingular?.trim()
|
|
||||||
|
|
||||||
json.alternativeDenomination?.forEach((v, i) => {
|
json.alternativeDenomination?.forEach((v, i) => {
|
||||||
if ((v?.trim() ?? "") === "") {
|
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) {
|
if (json["default" /* @code-quality: ignore*/] !== undefined) {
|
||||||
throw `${context} uses the old 'default'-key. Use "useIfNoUnitGiven" or "useAsDefaultInput" instead`
|
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")
|
const humanTexts = Translations.T(json.human, context + "human")
|
||||||
this._humanSingular = Translations.T(json.humanSingular, context + "humanSingular")
|
humanTexts.OnEveryLanguage((text, language) => {
|
||||||
|
if (text.indexOf("{quantity}") < 0) {
|
||||||
this.prefix = json.prefix ?? false
|
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 {
|
public clone() {
|
||||||
return this._human.Clone()
|
return new Denomination(
|
||||||
|
this.canonical,
|
||||||
|
this._canonicalSingular,
|
||||||
|
this.useIfNoUnitGiven,
|
||||||
|
this.prefix,
|
||||||
|
this.addSpace,
|
||||||
|
this.alternativeDenominations,
|
||||||
|
this.human,
|
||||||
|
this.humanSingular
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
get humanSingular(): Translation {
|
public withBlankCanonical() {
|
||||||
return (this._humanSingular ?? this._human).Clone()
|
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 value the value from OSM
|
||||||
* @param actAsDefault if set and the value can be parsed as number, will be parsed and trimmed
|
* @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",
|
* canonicalDenomination: "m",
|
||||||
* alternativeDenomination: ["meter"],
|
* alternativeDenomination: ["meter"],
|
||||||
* human: {
|
* human: {
|
||||||
* en: "meter"
|
* en: "{quantity} meter"
|
||||||
* }
|
* }
|
||||||
* }, false, "test")
|
* }, "test")
|
||||||
* unit.canonicalValue("42m", true) // =>"42 m"
|
* unit.canonicalValue("42m", true) // =>"42 m"
|
||||||
* unit.canonicalValue("42", true) // =>"42 m"
|
* unit.canonicalValue("42", true) // =>"42 m"
|
||||||
* unit.canonicalValue("42 m", true) // =>"42 m"
|
* unit.canonicalValue("42 m", true) // =>"42 m"
|
||||||
|
@ -72,13 +118,13 @@ export class Denomination {
|
||||||
* unit.canonicalValue("42", true) // =>"42 m"
|
* unit.canonicalValue("42", true) // =>"42 m"
|
||||||
*
|
*
|
||||||
* // Should be trimmed if canonical is empty
|
* // Should be trimmed if canonical is empty
|
||||||
* const unit = new Denomination({
|
* const unit = Denomination.fromJson({
|
||||||
* canonicalDenomination: "",
|
* canonicalDenomination: "",
|
||||||
* alternativeDenomination: ["meter","m"],
|
* alternativeDenomination: ["meter","m"],
|
||||||
* human: {
|
* human: {
|
||||||
* en: "meter"
|
* en: "{quantity} meter"
|
||||||
* }
|
* }
|
||||||
* }, false, "test")
|
* }, "test")
|
||||||
* unit.canonicalValue("42m", true) // =>"42"
|
* unit.canonicalValue("42m", true) // =>"42"
|
||||||
* unit.canonicalValue("42", true) // =>"42"
|
* unit.canonicalValue("42", true) // =>"42"
|
||||||
* unit.canonicalValue("42 m", true) // =>"42"
|
* unit.canonicalValue("42 m", true) // =>"42"
|
||||||
|
@ -160,14 +206,4 @@ export class Denomination {
|
||||||
|
|
||||||
return null
|
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
|
* 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.
|
* If set, synchronizes whether or not this layer is enabled.
|
||||||
|
|
|
@ -57,12 +57,16 @@
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export default interface UnitConfigJson {
|
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.
|
* Every key from this list will be normalized.
|
||||||
*
|
*
|
||||||
* To render the value properly (with a human readable denomination), use `{canonical(<key>)}`
|
* 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!)
|
* If set, invalid values will be erased in the MC application (but not in OSM of course!)
|
||||||
* Be careful with setting this
|
* 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
|
* Note that if all values use 'prefix', the dropdown might move to before the text field
|
||||||
*/
|
*/
|
||||||
prefix?: boolean
|
prefix?: boolean
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set, add a space between the quantity and the denomination.
|
||||||
|
*
|
||||||
|
* E.g.: `50 mph` instad of `50mph`
|
||||||
|
*/
|
||||||
|
addSpace?: boolean
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,9 +105,11 @@ export default class LayerConfig extends WithContextLoader {
|
||||||
".units: the 'units'-section should be a list; you probably have an object there"
|
".units: the 'units'-section should be a list; you probably have an object there"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
this.units = (json.units ?? []).map((unitJson, i) =>
|
this.units = [].concat(
|
||||||
|
...(json.units ?? []).map((unitJson, i) =>
|
||||||
Unit.fromJson(unitJson, `${context}.unit[${i}]`)
|
Unit.fromJson(unitJson, `${context}.unit[${i}]`)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if (json.description !== undefined) {
|
if (json.description !== undefined) {
|
||||||
if (Object.keys(json.description).length === 0) {
|
if (Object.keys(json.description).length === 0) {
|
||||||
|
|
|
@ -3,18 +3,23 @@ import { FixedUiElement } from "../UI/Base/FixedUiElement"
|
||||||
import Combine from "../UI/Base/Combine"
|
import Combine from "../UI/Base/Combine"
|
||||||
import { Denomination } from "./Denomination"
|
import { Denomination } from "./Denomination"
|
||||||
import UnitConfigJson from "./ThemeConfig/Json/UnitConfigJson"
|
import UnitConfigJson from "./ThemeConfig/Json/UnitConfigJson"
|
||||||
|
import unit from "../../assets/layers/unit/unit.json"
|
||||||
|
|
||||||
export class Unit {
|
export class Unit {
|
||||||
|
private static allUnits = this.initUnits()
|
||||||
public readonly appliesToKeys: Set<string>
|
public readonly appliesToKeys: Set<string>
|
||||||
public readonly denominations: Denomination[]
|
public readonly denominations: Denomination[]
|
||||||
public readonly denominationsSorted: Denomination[]
|
public readonly denominationsSorted: Denomination[]
|
||||||
public readonly eraseInvalid: boolean
|
public readonly eraseInvalid: boolean
|
||||||
|
public readonly quantity: string
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
quantity: string,
|
||||||
appliesToKeys: string[],
|
appliesToKeys: string[],
|
||||||
applicableDenominations: Denomination[],
|
applicableDenominations: Denomination[],
|
||||||
eraseInvalid: boolean
|
eraseInvalid: boolean
|
||||||
) {
|
) {
|
||||||
|
this.quantity = quantity
|
||||||
this.appliesToKeys = new Set(appliesToKeys)
|
this.appliesToKeys = new Set(appliesToKeys)
|
||||||
this.denominations = applicableDenominations
|
this.denominations = applicableDenominations
|
||||||
this.eraseInvalid = eraseInvalid
|
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
|
* // Should detect invalid defaultInput
|
||||||
* let threwError = false
|
* let threwError = false
|
||||||
* try{
|
* try{
|
||||||
* Unit.fromJson({
|
* Unit.parse({
|
||||||
* appliesToKey: ["length"],
|
* appliesToKey: ["length"],
|
||||||
* defaultInput: "xcm",
|
* defaultInput: "xcm",
|
||||||
* applicableUnits: [
|
* applicableUnits: [
|
||||||
|
@ -82,7 +99,7 @@ export class Unit {
|
||||||
* threwError // => true
|
* threwError // => true
|
||||||
*
|
*
|
||||||
* // Should work
|
* // Should work
|
||||||
* Unit.fromJson({
|
* Unit.parse({
|
||||||
* appliesToKey: ["length"],
|
* appliesToKey: ["length"],
|
||||||
* defaultInput: "xcm",
|
* defaultInput: "xcm",
|
||||||
* applicableUnits: [
|
* applicableUnits: [
|
||||||
|
@ -98,9 +115,9 @@ export class Unit {
|
||||||
* ]
|
* ]
|
||||||
* }, "test")
|
* }, "test")
|
||||||
*/
|
*/
|
||||||
static fromJson(json: UnitConfigJson, ctx: string) {
|
private static parse(json: UnitConfigJson, ctx: string): Unit {
|
||||||
const appliesTo = json.appliesToKey
|
const appliesTo = json.appliesToKey
|
||||||
for (let i = 0; i < appliesTo.length; i++) {
|
for (let i = 0; i < (appliesTo ?? []).length; i++) {
|
||||||
let key = appliesTo[i]
|
let key = appliesTo[i]
|
||||||
if (key.trim() !== key) {
|
if (key.trim() !== key) {
|
||||||
throw `${ctx}.appliesToKey[${i}] is invalid: it starts or ends with whitespace`
|
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
|
// Some keys do have unit handling
|
||||||
|
|
||||||
const applicable = json.applicableUnits.map(
|
const applicable = json.applicableUnits.map((u, i) =>
|
||||||
(u, i) =>
|
Denomination.fromJson(u, `${ctx}.units[${i}]`)
|
||||||
new Denomination(
|
|
||||||
u,
|
|
||||||
u.canonicalDenomination === undefined
|
|
||||||
? undefined
|
|
||||||
: u.canonicalDenomination.trim() === json.defaultInput,
|
|
||||||
`${ctx}.units[${i}]`
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
@ -133,7 +143,85 @@ export class Unit {
|
||||||
.map((denom) => denom.canonical)
|
.map((denom) => denom.canonical)
|
||||||
.join(", ")}`
|
.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 {
|
isApplicableToKey(key: string | undefined): boolean {
|
||||||
|
@ -161,47 +249,34 @@ export class Unit {
|
||||||
return [undefined, undefined]
|
return [undefined, undefined]
|
||||||
}
|
}
|
||||||
|
|
||||||
asHumanLongValue(value: string, country: () => string): BaseUIElement {
|
asHumanLongValue(value: string, country: () => string): BaseUIElement | string {
|
||||||
if (value === undefined) {
|
if (value === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const [stripped, denom] = this.findDenomination(value, country)
|
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) {
|
if (human === undefined) {
|
||||||
return new FixedUiElement(stripped ?? value)
|
return stripped ?? value
|
||||||
}
|
}
|
||||||
|
|
||||||
const elems = denom.prefix ? [human, stripped] : [stripped, human]
|
return human.Subs({ quantity: value })
|
||||||
return new Combine(elems)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDefaultInput(country: () => string | string[]) {
|
public toOsm(value: string, denomination: string) {
|
||||||
console.log("Searching the default denomination for input", country)
|
const denom = this.denominations.find((d) => d.canonical === denomination)
|
||||||
for (const denomination of this.denominations) {
|
const space = denom.addSpace ? " " : ""
|
||||||
if (denomination.useAsDefaultInput === true) {
|
if (denom.prefix) {
|
||||||
return denomination
|
return denom.canonical + space + value
|
||||||
}
|
}
|
||||||
if (
|
return value + space + denom.canonical
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.denominations[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getDefaultDenomination(country: () => string) {
|
public getDefaultDenomination(country: () => string) {
|
||||||
for (const denomination of this.denominations) {
|
for (const denomination of this.denominations) {
|
||||||
if (denomination.useIfNoUnitGiven === true || denomination.canonical === "") {
|
if (denomination.useIfNoUnitGiven === true) {
|
||||||
return denomination
|
return denomination
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
|
@ -219,6 +294,11 @@ export class Unit {
|
||||||
return denomination
|
return denomination
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (const denomination of this.denominations) {
|
||||||
|
if (denomination.canonical === "") {
|
||||||
|
return denomination
|
||||||
|
}
|
||||||
|
}
|
||||||
return this.denominations[0]
|
return this.denominations[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,95 +1,102 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
import { UIEventSource } from "../../Logic/UIEventSource";
|
||||||
import type { ValidatorType } from "./Validators"
|
import type { ValidatorType } from "./Validators";
|
||||||
import Validators from "./Validators"
|
import Validators from "./Validators";
|
||||||
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid"
|
import { ExclamationIcon } from "@rgossiaux/svelte-heroicons/solid";
|
||||||
import { Translation } from "../i18n/Translation"
|
import { Translation } from "../i18n/Translation";
|
||||||
import { createEventDispatcher, onDestroy } from "svelte"
|
import { createEventDispatcher, onDestroy } from "svelte";
|
||||||
import { Validator } from "./Validator"
|
import { Validator } from "./Validator";
|
||||||
import { Unit } from "../../Models/Unit"
|
import { Unit } from "../../Models/Unit";
|
||||||
import UnitInput from "../Popup/UnitInput.svelte"
|
import UnitInput from "../Popup/UnitInput.svelte";
|
||||||
import { Utils } from "../../Utils"
|
import { Utils } from "../../Utils";
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
export let type: ValidatorType
|
export let type: ValidatorType;
|
||||||
export let feedback: UIEventSource<Translation> | undefined = undefined
|
export let feedback: UIEventSource<Translation> | undefined = undefined;
|
||||||
export let cls: string = undefined
|
export let cls: string = undefined;
|
||||||
export let getCountry: () => string | undefined
|
export let getCountry: () => string | undefined;
|
||||||
export let placeholder: string | Translation | undefined
|
export let placeholder: string | Translation | undefined;
|
||||||
export let unit: Unit = undefined
|
export let unit: Unit = undefined;
|
||||||
export let value: UIEventSource<string>
|
/**
|
||||||
|
* Valid state, exported to the calling component
|
||||||
|
*/
|
||||||
|
export let value: UIEventSource<string | undefined>;
|
||||||
/**
|
/**
|
||||||
* Internal state bound to the input element.
|
* Internal state bound to the input element.
|
||||||
*
|
*
|
||||||
* This is only copied to 'value' when appropriate so that no invalid values leak outside;
|
* This is only copied to 'value' when appropriate so that no invalid values leak outside;
|
||||||
* Additionally, the unit is added when copying
|
* 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) {
|
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 selectedUnit: UIEventSource<string> = new UIEventSource<string>(undefined);
|
||||||
let _placeholder = placeholder ?? validator?.getPlaceholder() ?? type
|
let _placeholder = placeholder ?? validator?.getPlaceholder() ?? type;
|
||||||
|
|
||||||
function initValueAndDenom() {
|
function initValueAndDenom() {
|
||||||
if (unit && value.data) {
|
if (unit && value.data) {
|
||||||
const [v, denom] = unit?.findDenomination(value.data, getCountry)
|
const [v, denom] = unit?.findDenomination(value.data, getCountry);
|
||||||
if (denom) {
|
if (denom) {
|
||||||
_value.setData(v)
|
_value.setData(v);
|
||||||
selectedUnit.setData(denom.canonical)
|
selectedUnit.setData(denom.canonical);
|
||||||
} else {
|
} else {
|
||||||
_value.setData(value.data ?? "")
|
_value.setData(value.data ?? "");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_value.setData(value.data ?? "")
|
_value.setData(value.data ?? "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
initValueAndDenom()
|
initValueAndDenom();
|
||||||
|
|
||||||
$: {
|
$: {
|
||||||
// The type changed -> reset some values
|
// The type changed -> reset some values
|
||||||
validator = Validators.get(type ?? "string")
|
validator = Validators.get(type ?? "string");
|
||||||
|
|
||||||
_placeholder = placeholder ?? validator?.getPlaceholder() ?? type
|
_placeholder = placeholder ?? validator?.getPlaceholder() ?? type;
|
||||||
feedback?.setData(validator?.getFeedback(_value.data, getCountry))
|
feedback?.setData(validator?.getFeedback(_value.data, getCountry));
|
||||||
|
|
||||||
initValueAndDenom()
|
initValueAndDenom();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setValues() {
|
function setValues() {
|
||||||
// Update the value stores
|
// Update the value stores
|
||||||
const v = _value.data
|
const v = _value.data;
|
||||||
if (!validator?.isValid(v, getCountry) || v === "") {
|
if (!validator?.isValid(v, getCountry) || v === "") {
|
||||||
feedback?.setData(validator?.getFeedback(v, getCountry))
|
feedback?.setData(validator?.getFeedback(v, getCountry));
|
||||||
value.setData("")
|
value.setData("");
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unit !== undefined && isNaN(Number(v))) {
|
if (unit !== undefined && isNaN(Number(v))) {
|
||||||
value.setData(undefined)
|
value.setData(undefined);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
feedback?.setData(undefined)
|
feedback?.setData(undefined);
|
||||||
if (selectedUnit.data) {
|
if (selectedUnit.data) {
|
||||||
value.setData(v + selectedUnit.data)
|
value.setData(unit.toOsm(v, selectedUnit.data))
|
||||||
} else {
|
} else {
|
||||||
value.setData(v)
|
value.setData(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onDestroy(_value.addCallbackAndRun((_) => setValues()))
|
onDestroy(_value.addCallbackAndRun((_) => setValues()));
|
||||||
|
if (unit === undefined) {
|
||||||
onDestroy(
|
onDestroy(
|
||||||
value.addCallbackAndRunD((fromUpstream) => {
|
value.addCallbackAndRunD((fromUpstream) => {
|
||||||
if (_value.data !== fromUpstream && fromUpstream !== "") {
|
if (_value.data !== fromUpstream && fromUpstream !== "") {
|
||||||
_value.setData(fromUpstream)
|
_value.setData(fromUpstream);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
onDestroy(selectedUnit.addCallback((_) => setValues()))
|
}else{
|
||||||
|
// Handled by the UnitInput
|
||||||
|
}
|
||||||
|
onDestroy(selectedUnit.addCallback((_) => setValues()));
|
||||||
if (validator === undefined) {
|
if (validator === undefined) {
|
||||||
throw (
|
throw (
|
||||||
"Not a valid type (no validator found) for type '" +
|
"Not a valid type (no validator found) for type '" +
|
||||||
|
@ -102,17 +109,17 @@
|
||||||
)
|
)
|
||||||
.slice(0, 5)
|
.slice(0, 5)
|
||||||
.join(", ")
|
.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) {
|
if (htmlElem !== undefined) {
|
||||||
htmlElem.onfocus = () => dispatch("selected")
|
htmlElem.onfocus = () => dispatch("selected");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,9 +128,9 @@
|
||||||
*/
|
*/
|
||||||
function sendSubmit() {
|
function sendSubmit() {
|
||||||
if (feedback?.data) {
|
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>
|
</script>
|
||||||
|
|
||||||
|
@ -150,7 +157,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if unit !== undefined}
|
{#if unit !== undefined}
|
||||||
<UnitInput {unit} {selectedUnit} textValue={_value} upstreamValue={value} />
|
<UnitInput {unit} {selectedUnit} textValue={_value} upstreamValue={value} {getCountry} />
|
||||||
{/if}
|
{/if}
|
||||||
</form>
|
</form>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
@ -1,56 +1,67 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Unit } from "../../Models/Unit"
|
import { Unit } from "../../Models/Unit";
|
||||||
import { Store, UIEventSource } from "../../Logic/UIEventSource"
|
import { Store, UIEventSource } from "../../Logic/UIEventSource";
|
||||||
import Tr from "../Base/Tr.svelte"
|
import Tr from "../Base/Tr.svelte";
|
||||||
import { onDestroy } from "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
|
* 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 = () => "?";
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
export let selectedUnit: UIEventSource<string> = new UIEventSource<string>(undefined)
|
|
||||||
export let getCountry = () => "be"
|
|
||||||
console.log("Unit", unit)
|
|
||||||
onDestroy(
|
onDestroy(
|
||||||
upstreamValue.addCallbackAndRun((v) => {
|
upstreamValue.addCallbackAndRun((v) => {
|
||||||
if (v === undefined) {
|
if(v === undefined || v === ""){
|
||||||
if (!selectedUnit.data) {
|
|
||||||
selectedUnit.setData(unit.getDefaultDenomination(getCountry).canonical)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const selected = unit.findDenomination(v, getCountry)
|
let denomination: Denomination = unit.getDefaultDenomination(getCountry);
|
||||||
if (selected === undefined) {
|
const selected = unit.findDenomination(v, getCountry);
|
||||||
selectedUnit.setData(unit.getDefaultDenomination(getCountry).canonical)
|
if(selected){
|
||||||
return
|
denomination = selected[1];
|
||||||
}
|
}
|
||||||
const [value, denomination] = selected
|
selectedUnit.setData(denomination.canonical);
|
||||||
selectedUnit.setData(denomination.canonical)
|
|
||||||
return
|
|
||||||
})
|
})
|
||||||
)
|
);
|
||||||
|
|
||||||
onDestroy(
|
onDestroy(
|
||||||
textValue.addCallbackAndRunD((v) => {
|
textValue.addCallbackAndRunD((v) => {
|
||||||
// Fallback in case that the user manually types a denomination
|
// 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) {
|
if (value === undefined || denomination === undefined) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
textValue.setData(value)
|
if(value === v){
|
||||||
selectedUnit.setData(denomination.canonical)
|
// 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>
|
</script>
|
||||||
|
|
||||||
<select bind:value={$selectedUnit}>
|
<select bind:value={$selectedUnit}>
|
||||||
|
@ -59,7 +70,7 @@
|
||||||
{#if $isSingle}
|
{#if $isSingle}
|
||||||
<Tr t={denom.humanSingular} />
|
<Tr t={denom.humanSingular} />
|
||||||
{:else}
|
{:else}
|
||||||
<Tr t={denom.human} />
|
<Tr t={denom.human.Subs({quantity: ""})} />
|
||||||
{/if}
|
{/if}
|
||||||
</option>
|
</option>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -83,6 +83,7 @@ import NearbyImages from "./Image/NearbyImages.svelte"
|
||||||
import NearbyImagesCollapsed from "./Image/NearbyImagesCollapsed.svelte"
|
import NearbyImagesCollapsed from "./Image/NearbyImagesCollapsed.svelte"
|
||||||
import { svelte } from "@sveltejs/vite-plugin-svelte"
|
import { svelte } from "@sveltejs/vite-plugin-svelte"
|
||||||
import MoveWizard from "./Popup/MoveWizard.svelte"
|
import MoveWizard from "./Popup/MoveWizard.svelte"
|
||||||
|
import { Unit } from "../Models/Unit"
|
||||||
|
|
||||||
class NearbyImageVis implements SpecialVisualization {
|
class NearbyImageVis implements SpecialVisualization {
|
||||||
// Class must be in SpecialVisualisations due to weird cyclical import that breaks the tests
|
// 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) {
|
if (value === undefined) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const allUnits = [].concat(
|
const allUnits: Unit[] = [].concat(
|
||||||
...(state?.layout?.layers?.map((lyr) => lyr.units) ?? [])
|
...(state?.layout?.layers?.map((lyr) => lyr.units) ?? [])
|
||||||
)
|
)
|
||||||
const unit = allUnits.filter((unit) =>
|
const unit = allUnits.filter((unit) =>
|
||||||
|
@ -899,7 +900,9 @@ export default class SpecialVisualizations {
|
||||||
if (unit === undefined) {
|
if (unit === undefined) {
|
||||||
return value
|
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) {
|
static isProbablyATranslation(transl: any) {
|
||||||
if (typeof transl !== "object") {
|
if (!transl || typeof transl !== "object") {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (Object.keys(transl).length == 0) {
|
if (Object.keys(transl).length == 0) {
|
||||||
|
|
|
@ -1080,9 +1080,21 @@
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"allOf": [
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/TagRenderingConfigJson"
|
"$ref": "#/definitions/TagRenderingConfigJson"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
@ -1103,7 +1115,16 @@
|
||||||
"maxItems": 1
|
"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": [
|
"path": [
|
||||||
|
@ -10380,6 +10401,10 @@
|
||||||
"if": "value=tree_node",
|
"if": "value=tree_node",
|
||||||
"then": "tree_node - A layer showing trees"
|
"then": "tree_node - A layer showing trees"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"if": "value=unit",
|
||||||
|
"then": "unit - Library layer with all common units"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"if": "value=usersettings",
|
"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"
|
"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": {
|
"hints": {
|
||||||
"typehint": "tagrendering[]",
|
"typehint": "tagrendering[]",
|
||||||
"group": "tagrenderings",
|
"group": "tagrenderings",
|
||||||
"question": "Edit this attribute showing piece/question"
|
"question": "Edit this way this attributed is displayed or queried"
|
||||||
},
|
},
|
||||||
"type": [
|
"type": [
|
||||||
{
|
{
|
||||||
|
@ -15969,21 +15994,20 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": [
|
"path": [
|
||||||
"units"
|
"units",
|
||||||
|
"quantity"
|
||||||
],
|
],
|
||||||
"required": false,
|
"required": false,
|
||||||
"hints": {
|
"hints": {},
|
||||||
"default": "ult: true,"
|
"type": "string",
|
||||||
},
|
"description": "What is quantified? E.g. 'speed', 'length' (including width, diameter, ...), 'electric tension', 'electric current', 'duration'"
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": [
|
"path": [
|
||||||
"units",
|
"units",
|
||||||
"appliesToKey"
|
"appliesToKey"
|
||||||
],
|
],
|
||||||
"required": true,
|
"required": false,
|
||||||
"hints": {},
|
"hints": {},
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Every key from this list will be normalized.\nTo render the value properly (with a human readable denomination), use `{canonical(<key>)}`"
|
"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",
|
"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"
|
"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": [
|
"path": [
|
||||||
"units",
|
"units",
|
||||||
|
|
|
@ -832,6 +832,10 @@
|
||||||
"if": "value=tree_node",
|
"if": "value=tree_node",
|
||||||
"then": "tree_node - A layer showing trees"
|
"then": "tree_node - A layer showing trees"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"if": "value=unit",
|
||||||
|
"then": "unit - Library layer with all common units"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"if": "value=usersettings",
|
"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"
|
"then": "usersettings - A special layer which is not meant to be shown on a map, but which is used to set user settings"
|
||||||
|
@ -1183,15 +1187,27 @@
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"titleIcons": {
|
"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": [
|
"anyOf": [
|
||||||
{
|
{
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"allOf": [
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/TagRenderingConfigJson"
|
"$ref": "#/definitions/TagRenderingConfigJson"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
@ -1300,7 +1316,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"tagRenderings": {
|
"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",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
|
@ -1706,7 +1722,14 @@
|
||||||
"units": {
|
"units": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
"$ref": "#/definitions/default_2"
|
"$ref": "#/definitions/default_2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/definitions/Record<string,string|{quantity:string;denominations:string[];}>"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"syncSelection": {
|
"syncSelection": {
|
||||||
|
@ -2888,9 +2911,21 @@
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"allOf": [
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/TagRenderingConfigJson"
|
"$ref": "#/definitions/TagRenderingConfigJson"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
@ -2911,7 +2946,17 @@
|
||||||
"maxItems": 1
|
"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": [
|
"path": [
|
||||||
|
@ -12476,6 +12521,10 @@
|
||||||
"if": "value=tree_node",
|
"if": "value=tree_node",
|
||||||
"then": "tree_node - A layer showing trees"
|
"then": "tree_node - A layer showing trees"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"if": "value=unit",
|
||||||
|
"then": "unit - Library layer with all common units"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"if": "value=usersettings",
|
"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"
|
"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": {
|
"hints": {
|
||||||
"typehint": "tagrendering[]",
|
"typehint": "tagrendering[]",
|
||||||
"group": "tagrenderings",
|
"group": "tagrenderings",
|
||||||
"question": "Edit this attribute showing piece/question"
|
"question": "Edit this way this attributed is displayed or queried"
|
||||||
},
|
},
|
||||||
"type": [
|
"type": [
|
||||||
{
|
{
|
||||||
|
@ -18273,14 +18322,13 @@
|
||||||
{
|
{
|
||||||
"path": [
|
"path": [
|
||||||
"layers",
|
"layers",
|
||||||
"units"
|
"units",
|
||||||
|
"quantity"
|
||||||
],
|
],
|
||||||
"required": false,
|
"required": false,
|
||||||
"hints": {
|
"hints": {},
|
||||||
"default": "ult: true,"
|
"type": "string",
|
||||||
},
|
"description": "What is quantified? E.g. 'speed', 'length' (including width, diameter, ...), 'electric tension', 'electric current', 'duration'"
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": [
|
"path": [
|
||||||
|
@ -18288,7 +18336,7 @@
|
||||||
"units",
|
"units",
|
||||||
"appliesToKey"
|
"appliesToKey"
|
||||||
],
|
],
|
||||||
"required": true,
|
"required": false,
|
||||||
"hints": {},
|
"hints": {},
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Every key from this list will be normalized.\nTo render the value properly (with a human readable denomination), use `{canonical(<key>)}`"
|
"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",
|
"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"
|
"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": [
|
"path": [
|
||||||
"layers",
|
"layers",
|
||||||
|
@ -19645,9 +19705,21 @@
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"anyOf": [
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"allOf": [
|
||||||
{
|
{
|
||||||
"$ref": "#/definitions/TagRenderingConfigJson"
|
"$ref": "#/definitions/TagRenderingConfigJson"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
@ -19668,7 +19740,18 @@
|
||||||
"maxItems": 1
|
"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": [
|
"path": [
|
||||||
|
@ -29521,6 +29604,10 @@
|
||||||
"if": "value=tree_node",
|
"if": "value=tree_node",
|
||||||
"then": "tree_node - A layer showing trees"
|
"then": "tree_node - A layer showing trees"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"if": "value=unit",
|
||||||
|
"then": "unit - Library layer with all common units"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"if": "value=usersettings",
|
"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"
|
"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": {
|
"hints": {
|
||||||
"typehint": "tagrendering[]",
|
"typehint": "tagrendering[]",
|
||||||
"group": "tagrenderings",
|
"group": "tagrenderings",
|
||||||
"question": "Edit this attribute showing piece/question"
|
"question": "Edit this way this attributed is displayed or queried"
|
||||||
},
|
},
|
||||||
"type": [
|
"type": [
|
||||||
{
|
{
|
||||||
|
@ -35526,14 +35613,13 @@
|
||||||
"path": [
|
"path": [
|
||||||
"layers",
|
"layers",
|
||||||
"override",
|
"override",
|
||||||
"units"
|
"units",
|
||||||
|
"quantity"
|
||||||
],
|
],
|
||||||
"required": false,
|
"required": false,
|
||||||
"hints": {
|
"hints": {},
|
||||||
"default": "ult: true,"
|
"type": "string",
|
||||||
},
|
"description": "What is quantified? E.g. 'speed', 'length' (including width, diameter, ...), 'electric tension', 'electric current', 'duration'"
|
||||||
"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"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": [
|
"path": [
|
||||||
|
@ -35542,7 +35628,7 @@
|
||||||
"units",
|
"units",
|
||||||
"appliesToKey"
|
"appliesToKey"
|
||||||
],
|
],
|
||||||
"required": true,
|
"required": false,
|
||||||
"hints": {},
|
"hints": {},
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "Every key from this list will be normalized.\nTo render the value properly (with a human readable denomination), use `{canonical(<key>)}`"
|
"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",
|
"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"
|
"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": [
|
"path": [
|
||||||
"layers",
|
"layers",
|
||||||
|
|
|
@ -4,22 +4,21 @@ import { describe, expect, it } from "vitest"
|
||||||
|
|
||||||
describe("Unit", () => {
|
describe("Unit", () => {
|
||||||
it("should convert a value back and forth", () => {
|
it("should convert a value back and forth", () => {
|
||||||
const denomintion = new Denomination(
|
const denomintion = Denomination.fromJson(
|
||||||
{
|
{
|
||||||
canonicalDenomination: "MW",
|
canonicalDenomination: "MW",
|
||||||
alternativeDenomination: ["megawatts", "megawatt"],
|
alternativeDenomination: ["megawatts", "megawatt"],
|
||||||
human: {
|
human: {
|
||||||
en: " megawatts",
|
en: "{quantity} megawatts",
|
||||||
nl: " megawatt",
|
nl: "{quantity} megawatt",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
false,
|
|
||||||
"test"
|
"test"
|
||||||
)
|
)
|
||||||
|
|
||||||
const canonical = denomintion.canonicalValue("5", true)
|
const canonical = denomintion.canonicalValue("5", true)
|
||||||
expect(canonical).toBe("5 MW")
|
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")
|
const [detected, detectedDenom] = units.findDenomination("5 MW", () => "be")
|
||||||
expect(detected).toBe("5")
|
expect(detected).toBe("5")
|
||||||
expect(detectedDenom).toBe(denomintion)
|
expect(detectedDenom).toBe(denomintion)
|
||||||
|
|
Loading…
Reference in a new issue