Merge pull request #1229 from pietervdvn/develop

Merge develop
This commit is contained in:
Pieter Vander Vennet 2023-01-07 18:45:25 +01:00 committed by GitHub
commit 8034f9a83a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
87 changed files with 4302 additions and 1063 deletions

View file

@ -24,23 +24,23 @@ the switch ;) ). If you are using Visual Studio Code you can use
a [WSL Remote](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl) window, or use the a [WSL Remote](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl) window, or use the
Devcontainer (see more details later). Devcontainer (see more details later).
You need at least 3Gb available to run MapComplete.
To develop and build MapComplete, you To develop and build MapComplete, you
0. Make a fork and clone the repository. (We recommend a shallow clone with `git clone --filter=blob:none <repo>`) 0. Make a fork and clone the repository. (We recommend a shallow clone with `git clone --filter=blob:none <repo>`)
0. Install `python3` if you do not have it already 1. Install `python3` if you do not have it already
- On linux: `sudo apt install python3` - On linux: `sudo apt install python3`
- On windows: find the latest download on the [Python Releases for Windows page](https://www.python.org/downloads/windows/) - On windows: find the latest download on the [Python Releases for Windows page](https://www.python.org/downloads/windows/)
0. Install the nodejs version specified in [/.tool-versions](/.tool-versions) 2. Install the nodejs version specified in [/.tool-versions](/.tool-versions)
- On linux: install npm first `sudo apt install npm`, then install `n` using npm: ` npm install -g n`, which can - On linux: install npm first `sudo apt install npm`, then install `n` using npm: ` npm install -g n`, which can
then install node with `n install <node-version>` then install node with `n install <node-version>`. You can [use asdf to manage your runtime versions](https://asdf-vm.com/).
- You can [use asdf to manage your runtime versions](https://asdf-vm.com/). - Windows: install nodeJS: https://nodejs.org/en/download/
0. Install `npm`. Linux: `sudo apt install npm` (or your favourite package manager), Windows: install 3. Run `npm run init` which …
nodeJS: https://nodejs.org/en/download/
0. Run `npm run init` which …
- runs `npm install` - runs `npm install`
- generates some additional dependencies and files - generates some additional dependencies and files
0. Run `npm run start` to host a local testversion at http://localhost:1234/index.html 4. Run `npm run start` to host a local testversion at http://localhost:1234/index.html
0. By default, a landing page with available themes is served. In order to load a single theme, use `layout=themename` 5. By default, a landing page with available themes is served. In order to load a single theme, use `layout=themename`
or `userlayout=true#<layout configuration>` as [Query parameter](URL_Parameters.md). Note that the shorter URLs ( or `userlayout=true#<layout configuration>` as [Query parameter](URL_Parameters.md). Note that the shorter URLs (
e.g. `bookcases.html`, `aed.html`, ...) _don't_ exist on the development version. e.g. `bookcases.html`, `aed.html`, ...) _don't_ exist on the development version.

31
Docs/Personas/Xavier.md Normal file
View file

@ -0,0 +1,31 @@
# Persona: Xavier (activist mapping campaign, data activism)
## Background
Name: Xavier
Age: 68
Occupation: Cycling activists
Computer skills: Basic
## Interests & Influences
Xavier is volunteer with the local cycle association.
Together, they want that more people take the bicycle and that cycling becomes safer.
## Motivations
In order to create a safer cycling environment, Xavier wants to urge the local municipality to remove bollards from the cycleways if those bollards don't add a lot of value.
But then, they first need to know _where_ all those bollards are.
## Goals
Create a map of all bollards
## Needs and expectations
Easily add bollards to a map, easily show an overview map of where all the bollards are
## Pain points & frustrations
It is hard to create such a map and get an overview of this...

8
Docs/Personas/_README.md Normal file
View file

@ -0,0 +1,8 @@
# Personas
In this directory, you'll find some possible user profiles and how they interact with MapComplete.
They serve as a baseline, to indicate what type of interactions are important to support for MapComplete.
In other words, these persona's are a part of the vision on what MapComplete should (not) be.
Note that the UserTests might also give a good idea on where usability might go wrong in small, individual steps.

View file

@ -0,0 +1,20 @@
# Persona: NAME (scenario summary)
## Background
Name:
Age:
Occupation:
Computer skills:
Demography:
## Interests & Influences
## Motivations
## Goals
## Needs and expectations
## Pain points & frustrations

View file

@ -76,40 +76,42 @@
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -120,6 +122,10 @@
"canonicalDenomination" "canonicalDenomination"
], ],
"additionalProperties": false "additionalProperties": false
},
"Record<string,string>": {
"type": "object",
"additionalProperties": false
} }
}, },
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",

View file

@ -74,40 +74,42 @@ export default {
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -117,6 +119,9 @@ export default {
"required": [ "required": [
"canonicalDenomination" "canonicalDenomination"
] ]
},
"Record<string,string>": {
"type": "object"
} }
}, },
"$schema": "http://json-schema.org/draft-07/schema#" "$schema": "http://json-schema.org/draft-07/schema#"

View file

@ -271,10 +271,7 @@
"description": "If specified, a new point will only be snapped if it is within this range.\nDistance in meter\n\nDefault: 10", "description": "If specified, a new point will only be snapped if it is within this range.\nDistance in meter\n\nDefault: 10",
"type": "number" "type": "number"
} }
}, }
"required": [
"preferredBackground"
]
}, },
{ {
"enum": [ "enum": [
@ -426,7 +423,6 @@
"type": "boolean" "type": "boolean"
}, },
"units": { "units": {
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\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\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\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```\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.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/default_2" "$ref": "#/definitions/default_2"
@ -524,40 +520,42 @@
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -569,6 +567,10 @@
], ],
"additionalProperties": false "additionalProperties": false
}, },
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",
@ -865,6 +867,28 @@
"type": "string" "type": "string"
} }
] ]
},
"css": {
"description": "A snippet of css code",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"cssClasses": {
"description": "A snippet of css-classes. They can be space-separated",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
} }
}, },
"required": [ "required": [
@ -1421,6 +1445,7 @@
"additionalProperties": false "additionalProperties": false
}, },
"default_2": { "default_2": {
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\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\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\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```\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.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given",
"type": "object", "type": "object",
"properties": { "properties": {
"appliesToKey": { "appliesToKey": {
@ -1435,11 +1460,15 @@
"type": "boolean" "type": "boolean"
}, },
"applicableUnits": { "applicableUnits": {
"description": "The possible denominations", "description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/DenominationConfigJson" "$ref": "#/definitions/DenominationConfigJson"
} }
},
"defaultInput": {
"description": "In some cases, the default denomination is not the most user friendly to input.\nE.g., when measuring kerb heights, it is illogical to ask contributors to input an amount in meters.\n\nWhen a default input method should be used, this can be specified by setting the canonical denomination here, e.g.\n`defaultInput: \"cm\"`. This must be a denomination which appears in the applicableUnits",
"type": "string"
} }
}, },
"required": [ "required": [

View file

@ -271,10 +271,7 @@ export default {
"description": "If specified, a new point will only be snapped if it is within this range.\nDistance in meter\n\nDefault: 10", "description": "If specified, a new point will only be snapped if it is within this range.\nDistance in meter\n\nDefault: 10",
"type": "number" "type": "number"
} }
}, }
"required": [
"preferredBackground"
]
}, },
{ {
"enum": [ "enum": [
@ -426,7 +423,6 @@ export default {
"type": "boolean" "type": "boolean"
}, },
"units": { "units": {
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\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\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\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```\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.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/default_2" "$ref": "#/definitions/default_2"
@ -522,40 +518,42 @@ export default {
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -566,6 +564,9 @@ export default {
"canonicalDenomination" "canonicalDenomination"
] ]
}, },
"Record<string,string>": {
"type": "object"
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",
@ -858,6 +859,28 @@ export default {
"type": "string" "type": "string"
} }
] ]
},
"css": {
"description": "A snippet of css code",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"cssClasses": {
"description": "A snippet of css-classes. They can be space-separated",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
} }
}, },
"required": [ "required": [
@ -1405,6 +1428,7 @@ export default {
} }
}, },
"default_2": { "default_2": {
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\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\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\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```\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.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given",
"type": "object", "type": "object",
"properties": { "properties": {
"appliesToKey": { "appliesToKey": {
@ -1419,11 +1443,15 @@ export default {
"type": "boolean" "type": "boolean"
}, },
"applicableUnits": { "applicableUnits": {
"description": "The possible denominations", "description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/DenominationConfigJson" "$ref": "#/definitions/DenominationConfigJson"
} }
},
"defaultInput": {
"description": "In some cases, the default denomination is not the most user friendly to input.\nE.g., when measuring kerb heights, it is illogical to ask contributors to input an amount in meters.\n\nWhen a default input method should be used, this can be specified by setting the canonical denomination here, e.g.\n`defaultInput: \"cm\"`. This must be a denomination which appears in the applicableUnits",
"type": "string"
} }
}, },
"required": [ "required": [

View file

@ -354,40 +354,42 @@
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -399,6 +401,10 @@
], ],
"additionalProperties": false "additionalProperties": false
}, },
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",
@ -695,6 +701,28 @@
"type": "string" "type": "string"
} }
] ]
},
"css": {
"description": "A snippet of css code",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"cssClasses": {
"description": "A snippet of css-classes. They can be space-separated",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
} }
}, },
"required": [ "required": [
@ -1251,6 +1279,7 @@
"additionalProperties": false "additionalProperties": false
}, },
"default_2": { "default_2": {
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\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\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\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```\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.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given",
"type": "object", "type": "object",
"properties": { "properties": {
"appliesToKey": { "appliesToKey": {
@ -1265,11 +1294,15 @@
"type": "boolean" "type": "boolean"
}, },
"applicableUnits": { "applicableUnits": {
"description": "The possible denominations", "description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/DenominationConfigJson" "$ref": "#/definitions/DenominationConfigJson"
} }
},
"defaultInput": {
"description": "In some cases, the default denomination is not the most user friendly to input.\nE.g., when measuring kerb heights, it is illogical to ask contributors to input an amount in meters.\n\nWhen a default input method should be used, this can be specified by setting the canonical denomination here, e.g.\n`defaultInput: \"cm\"`. This must be a denomination which appears in the applicableUnits",
"type": "string"
} }
}, },
"required": [ "required": [
@ -1594,10 +1627,7 @@
"description": "If specified, a new point will only be snapped if it is within this range.\nDistance in meter\n\nDefault: 10", "description": "If specified, a new point will only be snapped if it is within this range.\nDistance in meter\n\nDefault: 10",
"type": "number" "type": "number"
} }
}, }
"required": [
"preferredBackground"
]
}, },
{ {
"enum": [ "enum": [
@ -1749,7 +1779,6 @@
"type": "boolean" "type": "boolean"
}, },
"units": { "units": {
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\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\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\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```\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.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/default_2" "$ref": "#/definitions/default_2"

View file

@ -352,40 +352,42 @@ export default {
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -396,6 +398,9 @@ export default {
"canonicalDenomination" "canonicalDenomination"
] ]
}, },
"Record<string,string>": {
"type": "object"
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",
@ -688,6 +693,28 @@ export default {
"type": "string" "type": "string"
} }
] ]
},
"css": {
"description": "A snippet of css code",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"cssClasses": {
"description": "A snippet of css-classes. They can be space-separated",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
} }
}, },
"required": [ "required": [
@ -1235,6 +1262,7 @@ export default {
} }
}, },
"default_2": { "default_2": {
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\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\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\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```\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.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given",
"type": "object", "type": "object",
"properties": { "properties": {
"appliesToKey": { "appliesToKey": {
@ -1249,11 +1277,15 @@ export default {
"type": "boolean" "type": "boolean"
}, },
"applicableUnits": { "applicableUnits": {
"description": "The possible denominations", "description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/DenominationConfigJson" "$ref": "#/definitions/DenominationConfigJson"
} }
},
"defaultInput": {
"description": "In some cases, the default denomination is not the most user friendly to input.\nE.g., when measuring kerb heights, it is illogical to ask contributors to input an amount in meters.\n\nWhen a default input method should be used, this can be specified by setting the canonical denomination here, e.g.\n`defaultInput: \"cm\"`. This must be a denomination which appears in the applicableUnits",
"type": "string"
} }
}, },
"required": [ "required": [
@ -1575,10 +1607,7 @@ export default {
"description": "If specified, a new point will only be snapped if it is within this range.\nDistance in meter\n\nDefault: 10", "description": "If specified, a new point will only be snapped if it is within this range.\nDistance in meter\n\nDefault: 10",
"type": "number" "type": "number"
} }
}, }
"required": [
"preferredBackground"
]
}, },
{ {
"enum": [ "enum": [
@ -1730,7 +1759,6 @@ export default {
"type": "boolean" "type": "boolean"
}, },
"units": { "units": {
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\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\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\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```\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.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/default_2" "$ref": "#/definitions/default_2"

View file

@ -163,40 +163,42 @@
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -208,6 +210,10 @@
], ],
"additionalProperties": false "additionalProperties": false
}, },
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",

View file

@ -161,40 +161,42 @@ export default {
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -205,6 +207,9 @@ export default {
"canonicalDenomination" "canonicalDenomination"
] ]
}, },
"Record<string,string>": {
"type": "object"
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",

View file

@ -176,40 +176,42 @@
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -221,6 +223,10 @@
], ],
"additionalProperties": false "additionalProperties": false
}, },
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",

View file

@ -174,40 +174,42 @@ export default {
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -218,6 +220,9 @@ export default {
"canonicalDenomination" "canonicalDenomination"
] ]
}, },
"Record<string,string>": {
"type": "object"
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",

View file

@ -86,40 +86,42 @@
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -130,6 +132,10 @@
"canonicalDenomination" "canonicalDenomination"
], ],
"additionalProperties": false "additionalProperties": false
},
"Record<string,string>": {
"type": "object",
"additionalProperties": false
} }
}, },
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",

View file

@ -84,40 +84,42 @@ export default {
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -127,6 +129,9 @@ export default {
"required": [ "required": [
"canonicalDenomination" "canonicalDenomination"
] ]
},
"Record<string,string>": {
"type": "object"
} }
}, },
"$schema": "http://json-schema.org/draft-07/schema#" "$schema": "http://json-schema.org/draft-07/schema#"

View file

@ -80,6 +80,28 @@
"type": "string" "type": "string"
} }
] ]
},
"css": {
"description": "A snippet of css code",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"cssClasses": {
"description": "A snippet of css-classes. They can be space-separated",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
} }
}, },
"required": [ "required": [
@ -161,40 +183,42 @@
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -206,6 +230,10 @@
], ],
"additionalProperties": false "additionalProperties": false
}, },
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",

View file

@ -80,6 +80,28 @@ export default {
"type": "string" "type": "string"
} }
] ]
},
"css": {
"description": "A snippet of css code",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"cssClasses": {
"description": "A snippet of css-classes. They can be space-separated",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
} }
}, },
"required": [ "required": [
@ -159,40 +181,42 @@ export default {
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -203,6 +227,9 @@ export default {
"canonicalDenomination" "canonicalDenomination"
] ]
}, },
"Record<string,string>": {
"type": "object"
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",

View file

@ -169,40 +169,42 @@
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -214,6 +216,10 @@
], ],
"additionalProperties": false "additionalProperties": false
}, },
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",

View file

@ -167,40 +167,42 @@ export default {
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -211,6 +213,9 @@ export default {
"canonicalDenomination" "canonicalDenomination"
] ]
}, },
"Record<string,string>": {
"type": "object"
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",

View file

@ -108,40 +108,42 @@
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -153,6 +155,10 @@
], ],
"additionalProperties": false "additionalProperties": false
}, },
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",

View file

@ -106,40 +106,42 @@ export default {
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -150,6 +152,9 @@ export default {
"canonicalDenomination" "canonicalDenomination"
] ]
}, },
"Record<string,string>": {
"type": "object"
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",

View file

@ -111,40 +111,42 @@
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -156,6 +158,10 @@
], ],
"additionalProperties": false "additionalProperties": false
}, },
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",
@ -452,6 +458,28 @@
"type": "string" "type": "string"
} }
] ]
},
"css": {
"description": "A snippet of css code",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"cssClasses": {
"description": "A snippet of css-classes. They can be space-separated",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
} }
}, },
"required": [ "required": [
@ -1008,6 +1036,7 @@
"additionalProperties": false "additionalProperties": false
}, },
"default_2": { "default_2": {
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\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\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\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```\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.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given",
"type": "object", "type": "object",
"properties": { "properties": {
"appliesToKey": { "appliesToKey": {
@ -1022,11 +1051,15 @@
"type": "boolean" "type": "boolean"
}, },
"applicableUnits": { "applicableUnits": {
"description": "The possible denominations", "description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/DenominationConfigJson" "$ref": "#/definitions/DenominationConfigJson"
} }
},
"defaultInput": {
"description": "In some cases, the default denomination is not the most user friendly to input.\nE.g., when measuring kerb heights, it is illogical to ask contributors to input an amount in meters.\n\nWhen a default input method should be used, this can be specified by setting the canonical denomination here, e.g.\n`defaultInput: \"cm\"`. This must be a denomination which appears in the applicableUnits",
"type": "string"
} }
}, },
"required": [ "required": [

View file

@ -109,40 +109,42 @@ export default {
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -153,6 +155,9 @@ export default {
"canonicalDenomination" "canonicalDenomination"
] ]
}, },
"Record<string,string>": {
"type": "object"
},
"TagRenderingConfigJson": { "TagRenderingConfigJson": {
"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", "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",
"type": "object", "type": "object",
@ -445,6 +450,28 @@ export default {
"type": "string" "type": "string"
} }
] ]
},
"css": {
"description": "A snippet of css code",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
},
"cssClasses": {
"description": "A snippet of css-classes. They can be space-separated",
"anyOf": [
{
"$ref": "#/definitions/TagRenderingConfigJson"
},
{
"type": "string"
}
]
} }
}, },
"required": [ "required": [
@ -992,6 +1019,7 @@ export default {
} }
}, },
"default_2": { "default_2": {
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\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\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\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```\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.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given",
"type": "object", "type": "object",
"properties": { "properties": {
"appliesToKey": { "appliesToKey": {
@ -1006,11 +1034,15 @@ export default {
"type": "boolean" "type": "boolean"
}, },
"applicableUnits": { "applicableUnits": {
"description": "The possible denominations", "description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/DenominationConfigJson" "$ref": "#/definitions/DenominationConfigJson"
} }
},
"defaultInput": {
"description": "In some cases, the default denomination is not the most user friendly to input.\nE.g., when measuring kerb heights, it is illogical to ask contributors to input an amount in meters.\n\nWhen a default input method should be used, this can be specified by setting the canonical denomination here, e.g.\n`defaultInput: \"cm\"`. This must be a denomination which appears in the applicableUnits",
"type": "string"
} }
}, },
"required": [ "required": [

View file

@ -1,4 +1,5 @@
{ {
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\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\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\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```\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.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given",
"type": "object", "type": "object",
"properties": { "properties": {
"appliesToKey": { "appliesToKey": {
@ -13,11 +14,15 @@
"type": "boolean" "type": "boolean"
}, },
"applicableUnits": { "applicableUnits": {
"description": "The possible denominations", "description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/DenominationConfigJson" "$ref": "#/definitions/DenominationConfigJson"
} }
},
"defaultInput": {
"description": "In some cases, the default denomination is not the most user friendly to input.\nE.g., when measuring kerb heights, it is illogical to ask contributors to input an amount in meters.\n\nWhen a default input method should be used, this can be specified by setting the canonical denomination here, e.g.\n`defaultInput: \"cm\"`. This must be a denomination which appears in the applicableUnits",
"type": "string"
} }
}, },
"required": [ "required": [
@ -100,40 +105,42 @@
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -144,6 +151,10 @@
"canonicalDenomination" "canonicalDenomination"
], ],
"additionalProperties": false "additionalProperties": false
},
"Record<string,string>": {
"type": "object",
"additionalProperties": false
} }
}, },
"$schema": "http://json-schema.org/draft-07/schema#", "$schema": "http://json-schema.org/draft-07/schema#",

View file

@ -1,4 +1,5 @@
export default { export default {
"description": "In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)\n\nSometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)\n\nThis brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)\n\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\n# Rendering\n\nTo render a value with long (human) denomination, use {canonical(key)}\n\n# Usage\n\nFirst of all, you define which keys have units applied, for example:\n\n```\nunits: [\n appliesTo: [\"maxspeed\", \"maxspeed:hgv\", \"maxspeed:bus\"]\n applicableUnits: [\n ...\n ]\n]\n```\n\nApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:\n\n```\napplicableUnits: [\n{\n canonicalDenomination: \"km/h\",\n alternativeDenomination: [\"km/u\", \"kmh\", \"kph\"]\n default: true,\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```\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.\n\nAlso, if a freeform text field is used, an extra dropdown with applicable denominations will be given",
"type": "object", "type": "object",
"properties": { "properties": {
"appliesToKey": { "appliesToKey": {
@ -13,11 +14,15 @@ export default {
"type": "boolean" "type": "boolean"
}, },
"applicableUnits": { "applicableUnits": {
"description": "The possible denominations", "description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array", "type": "array",
"items": { "items": {
"$ref": "#/definitions/DenominationConfigJson" "$ref": "#/definitions/DenominationConfigJson"
} }
},
"defaultInput": {
"description": "In some cases, the default denomination is not the most user friendly to input.\nE.g., when measuring kerb heights, it is illogical to ask contributors to input an amount in meters.\n\nWhen a default input method should be used, this can be specified by setting the canonical denomination here, e.g.\n`defaultInput: \"cm\"`. This must be a denomination which appears in the applicableUnits",
"type": "string"
} }
}, },
"required": [ "required": [
@ -98,40 +103,42 @@ export default {
} }
] ]
}, },
"useAsDefaultInput": {
"description": "Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).\nIf unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false",
"anyOf": [
{
"type": "array",
"items": {
"type": "string"
}
},
{
"type": "boolean"
}
]
},
"canonicalDenomination": { "canonicalDenomination": {
"description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used", "description": "The canonical value for this denomination which will be added to the value in OSM.\ne.g. \"m\" for meters\nIf the user inputs '42', the canonical value will be added and it'll become '42m'.\n\nImportant: often, _no_ canonical values are expected, e.g. in the case of 'maxspeed' where 'km/h' is the default.\nIn this case, an empty string should be used",
"type": "string" "type": "string"
}, },
"canonicalDenominationSingular": { "canonicalDenominationSingular": {
"description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes", "description": "The canonical denomination in the case that the unit is precisely '1'.\nUsed for display purposes only.\n\nE.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here",
"type": "string" "type": "string"
}, },
"alternativeDenomination": { "alternativeDenomination": {
"description": "A list of alternative values which can occur in the OSM database - used for parsing.", "description": "A list of alternative values which can occur in the OSM database - used for parsing.\nE.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well",
"type": "array", "type": "array",
"items": { "items": {
"type": "string" "type": "string"
} }
}, },
"human": { "human": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"meter\",\n \"fr\": \"metre\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"humanSingular": { "humanSingular": {
"description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"x²\n}" "description": "The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.\n{\n \"en\": \"minute\",\n \"nl\": \"minuut\"\n}",
"anyOf": [
{
"$ref": "#/definitions/Record<string,string>"
},
{
"type": "string"
}
]
}, },
"prefix": { "prefix": {
"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",
@ -141,6 +148,9 @@ export default {
"required": [ "required": [
"canonicalDenomination" "canonicalDenomination"
] ]
},
"Record<string,string>": {
"type": "object"
} }
}, },
"$schema": "http://json-schema.org/draft-07/schema#" "$schema": "http://json-schema.org/draft-07/schema#"

54
Docs/Sponsors.md Normal file
View file

@ -0,0 +1,54 @@
# Sponsors
For transparency, here is a short overview of sponsors to MapComplete.
This ties in closely to the history of the project as well.
## Current sponsors
There is a [Liberapay](https://liberapay.com/pietervdvn).
NL-Net is currently sponsoring Pietervdvn as well [for specific improvements](https://github.com/pietervdvn/MapComplete/issues?q=is%3Aissue+is%3Aopen+label%3ANLNet).
## Historical sponsors
The following organisations ordered a theme to Pieter Vander Vennet and thus sponsered the project.
As these projects are finished, they have no relation with the project anymore.
Most recent projects are first:
### BOSA (OSOC 2022)
See https://osoc.be/editions/2022/removing-obstacles-with-open-data
### Antwerpse Zuidrand (2020 - 2021)
Antwerpse Zuidrand commissioned a map with [playgrounds and hiking routes](https://mapcomplete.osm.be/speelplekken)
### OSM UK (2021)
OSM UK commissioned an [address importer](https://mapcomplete.osm.be/uk_addresses)
### Toerisme Vlaanderen (2021 - 2022)
Visit Flanders commissioned a theme with electrical charging stations for ebikes, benches, parks, playgrounds, ... and ordered the 'note import'-flow.
For more information, see https://toerismevlaanderen.be/nl/pinjepunt
### Natuurpunt and Provincie Oost-Vlaanderen (OSOC 2021)
See https://osoc.be/editions/2021/nature-moves and https://osoc.be/editions/2021/bikeinfrastructure
### Brussels Mobility (OSOC 2020)
When the first version of Buurtnatuur.be was online, Brussels Mobility sponsored an [Open Summer of Code Project](https://osoc.be/editions/2020/cyclofix), resulting in the cyclofix theme.
The codebase was refactored in order to support multiple themes just before the start of OSOC.
### Belgian Green Party (2020)
The first version of what would later become 'MapComplete' was financed by the Belgian Green Party.
With the Corona-lockdown in full swing, they wanted to do something with _nature and green spaces near people, maybe inventorizing all parks, forests and nature reserves_.
[The original website](https://buurtnatuur.be/) can still be visited, the oldest version still online.

View file

@ -0,0 +1,63 @@
# Ad Hoc User test
Subject: K Vs
Tech Skills: basic computer skills
Demography: F, 50-60 yo
Language: Dutch
Medium: Android phone(s), DuckDuckGo browser + Fennec Browser
## Task
A street nearby has become a cyclestreet. Mark the correct segment of the street as cyclestreet - this will require *splitting* the screen.
## How it went
K takes her phone and opens up MC via the DuckDuckGo browser.
She is not logged in.
The button 'Login met OpenStreetMap indien je de kaart wilt aanpassen' is confusing, as she 'wants to change an attribute, not the geometry' (1)
As she has forgotten her password, she switches to the phone of the examinator.
She scrolls manually through all the themes. As the new road is actually part of a cycle zone and not a cycle street, the theme is considered but there is some doubt if it is truly the right theme (2). She opens the theme after all.
She is a bit confused as why the 'login'-button is lacking on the welcome screen. (3)
She proceeds to the map view.
As the street to turn into a cyclestreet is nearby, the street is already in view.
However, the cyclestreet theme uses a 'shizophrenic' approach: cyclestreets are visible on all zoom levels, but non-cyclestreets are only visible when zoomed in a lot.
As such, the street is only visible in the background layer. Zooming in _would_ show the street overlay.
The tester attempts to turn this street into a cyclestreet by long pressing the map, but this does nothing. (Long-pressing the map causes the 'new item' to popup on the screen if a new POI can be placed; but this theme does _not_ allow the creation of new POI).
Instead of zooming in, a search via the search bar is attempted. She slightly misspells the name and omits the 'street'-part, causing the map to jump to a small village on the other side of the world. (4) (5)
The examinator steps in to set the tester on the right track again and zoom in so that the 'all streets'-overlay is visible.
The tester continues the attempt by _long_ pressing the 'pencil'-icon on the street. Long pressing yields the right-click menu of fennec, to download the pencil icon. (6) Swiping 'back' removes it, but she swipes back twice accidentally, opening the theme that was opened previously. After some stumbling, the correct theme is opened again.
The examinator steps in to indicate that a _short_ press will open up the popup. The examinator also indicates that only a **part** of the street is a cyclestreet (the examinator has local knowledge about the actual situation there).
With the popup open, the button to 'split' ("Knip deze weg in kleinere segmenten (om andere eigenschappen toe te kennen per segment)") is easily found.
Tapping the map to add a cut is easily found. However, the 'confirm' button is hidden as the dialog is not scrolled into view completely. (7)
While experimenting, an extra, unnecessary cut is made - but this unneccessary cut is easily removed again by tapping it again.
The test subject also insists on making a second cut, to be very precise; resulting a 10 meter stretch of road which is not a cyclestreet.
When the confirm-button is moved into view, the test subject is confused. The Dutch 'Knip weg' can be translated as 'cut the road' but also as 'remove cut'. (8)
When the cut is made, the shorter segment is selected and the popup of this segment opens. The tester however doesn't realize that it is the _other_ segment that should be marked as a cyclestreet. (9) The examinator steps in to close the popup, after which the tester opens the correct segment and marks it as a cyclestreet.
## To improve
1. Change wording on the login button to 'if you want to make changes' 'als je iets wilt wijzigen'
2. Dutch theme title should mention 'cycle zones' as well
3. Add a 'welcome back <username>' to the welcome panel
4. Rethink the search flow
5. Think about the behaviour of a long press when no presets are defined. Zooming in would be acceptable
6. Long-pressing/right-clicking a POI on the map should open the popup as well
7. Scroll the cut-dialog into view when opening
8. Don't use 'Knip', but use 'Deel deze weg op' in Dutch Translations
9. Close the popup when a road is split

View file

@ -120,7 +120,6 @@ export default class GeoLocationHandler {
const state = this._state const state = this._state
this.geolocationState.currentGPSLocation.addCallbackAndRun((location) => { this.geolocationState.currentGPSLocation.addCallbackAndRun((location) => {
if (location === undefined) { if (location === undefined) {
state.currentUserLocation?.features?.setData([])
return return
} }
const feature = { const feature = {

View file

@ -45,20 +45,6 @@ export default class SelectedFeatureHandler {
const self = this const self = this
hash.addCallback(() => self.setSelectedElementFromHash()) hash.addCallback(() => self.setSelectedElementFromHash())
state.featurePipeline?.newDataLoadedSignal?.addCallbackAndRunD((_) => {
// New data was loaded. In initial startup, the hash might be set (via the URL) but might not be selected yet
if (hash.data === undefined || SelectedFeatureHandler._no_trigger_on.has(hash.data)) {
// This is an invalid hash anyway
return
}
if (state.selectedElement.data !== undefined) {
// We already have something selected
return
}
self.setSelectedElementFromHash()
})
this.initialLoad() this.initialLoad()
} }

View file

@ -4,8 +4,10 @@ import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen"
import BaseUIElement from "../../UI/BaseUIElement" import BaseUIElement from "../../UI/BaseUIElement"
/** /**
* The stray-click-hanlders adds a marker to the map if no feature was clicked. * The stray-click-handler adds a marker to the map if no feature was clicked.
* Shows the given uiToShow-element in the messagebox * Shows the given uiToShow-element in the messagebox
*
* Note: the actual implementation is in StrayClickHandlerImplementation
*/ */
export default class StrayClickHandler { export default class StrayClickHandler {
public static construct = ( public static construct = (

View file

@ -178,7 +178,7 @@ export class BBox {
]) ])
} }
toLeaflet() { toLeaflet(): [[number, number], [number, number]] {
return [ return [
[this.minLat, this.minLon], [this.minLat, this.minLon],
[this.maxLat, this.maxLon], [this.maxLat, this.maxLon],

View file

@ -28,6 +28,8 @@ export default class UserDetails {
} }
} }
export type OsmServiceState = "online" | "readonly" | "offline" | "unknown" | "unreachable"
export class OsmConnection { export class OsmConnection {
public static readonly oauth_configs = { public static readonly oauth_configs = {
osm: { osm: {
@ -46,6 +48,13 @@ export class OsmConnection {
public auth public auth
public userDetails: UIEventSource<UserDetails> public userDetails: UIEventSource<UserDetails>
public isLoggedIn: Store<boolean> public isLoggedIn: Store<boolean>
public gpxServiceIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
"unknown"
)
public apiIsOnline: UIEventSource<OsmServiceState> = new UIEventSource<OsmServiceState>(
"unknown"
)
public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">( public loadingStatus = new UIEventSource<"not-attempted" | "loading" | "error" | "logged-in">(
"not-attempted" "not-attempted"
) )
@ -62,7 +71,7 @@ export class OsmConnection {
private readonly _singlePage: boolean private readonly _singlePage: boolean
private isChecking = false private isChecking = false
constructor(options: { constructor(options?: {
dryRun?: UIEventSource<boolean> dryRun?: UIEventSource<boolean>
fakeUser?: false | boolean fakeUser?: false | boolean
oauth_token?: UIEventSource<string> oauth_token?: UIEventSource<string>
@ -71,6 +80,7 @@ export class OsmConnection {
osmConfiguration?: "osm" | "osm-test" osmConfiguration?: "osm" | "osm-test"
attemptLogin?: true | boolean attemptLogin?: true | boolean
}) { }) {
options = options ?? {}
this.fakeUser = options.fakeUser ?? false this.fakeUser = options.fakeUser ?? false
this._singlePage = options.singlePage ?? true this._singlePage = options.singlePage ?? true
this._oauth_config = this._oauth_config =
@ -93,7 +103,13 @@ export class OsmConnection {
ud.totalMessages = 42 ud.totalMessages = 42
} }
const self = this const self = this
this.isLoggedIn = this.userDetails.map((user) => user.loggedIn) this.UpdateCapabilities()
this.isLoggedIn = this.userDetails.map(
(user) =>
user.loggedIn &&
(self.apiIsOnline.data === "unknown" || self.apiIsOnline.data === "online"),
[this.apiIsOnline]
)
this.isLoggedIn.addCallback((isLoggedIn) => { this.isLoggedIn.addCallback((isLoggedIn) => {
if (self.userDetails.data.loggedIn == false && isLoggedIn == true) { if (self.userDetails.data.loggedIn == false && isLoggedIn == true) {
// We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do // We have an inconsistency: the userdetails say we _didn't_ log in, but this actor says we do
@ -106,7 +122,10 @@ export class OsmConnection {
this.updateAuthObject() this.updateAuthObject()
this.preferencesHandler = new OsmPreferences(this.auth, this) this.preferencesHandler = new OsmPreferences(
this.auth,
<any /*This is needed to make the tests work*/>this
)
if (options.oauth_token?.data !== undefined) { if (options.oauth_token?.data !== undefined) {
console.log(options.oauth_token.data) console.log(options.oauth_token.data)
@ -130,7 +149,13 @@ export class OsmConnection {
} }
public CreateChangesetHandler(allElements: ElementStorage, changes: Changes) { public CreateChangesetHandler(allElements: ElementStorage, changes: Changes) {
return new ChangesetHandler(this._dryRun, this, allElements, changes, this.auth) return new ChangesetHandler(
this._dryRun,
<any>/*casting is needed to make the tests work*/ this,
allElements,
changes,
this.auth
)
} }
public GetPreference( public GetPreference(
@ -159,11 +184,17 @@ export class OsmConnection {
this.loadingStatus.setData("not-attempted") this.loadingStatus.setData("not-attempted")
} }
/**
* The backend host, without path or trailing '/'
*
* new OsmConnection().Backend() // => "https://www.openstreetmap.org"
*/
public Backend(): string { public Backend(): string {
return this._oauth_config.url return this._oauth_config.url
} }
public AttemptLogin() { public AttemptLogin() {
this.UpdateCapabilities()
this.loadingStatus.setData("loading") this.loadingStatus.setData("loading")
if (this.fakeUser) { if (this.fakeUser) {
this.loadingStatus.setData("logged-in") this.loadingStatus.setData("logged-in")
@ -503,4 +534,26 @@ export class OsmConnection {
} }
}) })
} }
private UpdateCapabilities(): void {
const self = this
this.FetchCapabilities().then(({ api, gpx }) => {
self.apiIsOnline.setData(api)
self.gpxServiceIsOnline.setData(gpx)
})
}
private async FetchCapabilities(): Promise<{ api: OsmServiceState; gpx: OsmServiceState }> {
const result = await Utils.downloadAdvanced(this.Backend() + "/api/0.6/capabilities")
if (result["content"] === undefined) {
console.log("Something went wrong:", result)
return { api: "unreachable", gpx: "unreachable" }
}
const xmlRaw = result["content"]
const parsed = new DOMParser().parseFromString(xmlRaw, "text/xml")
const statusEl = parsed.getElementsByTagName("status")[0]
const api = <OsmServiceState>statusEl.getAttribute("api")
const gpx = <OsmServiceState>statusEl.getAttribute("gpx")
return { api, gpx }
}
} }

View file

@ -73,7 +73,7 @@ export abstract class OsmObject {
if (rawData["error"] !== undefined && rawData["statuscode"] === 410) { if (rawData["error"] !== undefined && rawData["statuscode"] === 410) {
return "deleted" return "deleted"
} }
return rawData["contents"].elements[0].tags return rawData["content"].elements[0].tags
} }
static async DownloadObjectAsync( static async DownloadObjectAsync(

View file

@ -230,10 +230,12 @@ export abstract class Store<T> {
const newSource = new UIEventSource<T>(this.data) const newSource = new UIEventSource<T>(this.data)
const self = this
this.addCallback((latestData) => { this.addCallback((latestData) => {
window.setTimeout(() => { window.setTimeout(() => {
if (this.data == latestData) { if (self.data == latestData) {
// compare by reference // compare by reference.
// Note that 'latestData' and 'self.data' are both from the same UIEVentSource, but both are dereferenced at a different time
newSource.setData(latestData) newSource.setData(latestData)
} }
}, millisToStabilize) }, millisToStabilize)

View file

@ -1,7 +1,7 @@
import { Utils } from "../Utils" import { Utils } from "../Utils"
export default class Constants { export default class Constants {
public static vNumber = "0.25.3" public static vNumber = "0.25.4"
public static ImgurApiKey = "7070e7167f0a25a" public static ImgurApiKey = "7070e7167f0a25a"
public static readonly mapillary_client_token_v4 = public static readonly mapillary_client_token_v4 =

View file

@ -15,7 +15,7 @@ export class Denomination {
private readonly _human: Translation private readonly _human: Translation
private readonly _humanSingular?: Translation private readonly _humanSingular?: Translation
constructor(json: DenominationConfigJson, context: string) { constructor(json: DenominationConfigJson, useAsDefaultInput: boolean, context: string) {
context = `${context}.unit(${json.canonicalDenomination})` context = `${context}.unit(${json.canonicalDenomination})`
this.canonical = json.canonicalDenomination.trim() this.canonical = json.canonicalDenomination.trim()
if (this.canonical === undefined) { if (this.canonical === undefined) {
@ -35,7 +35,7 @@ export class Denomination {
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.useIfNoUnitGiven = json.useIfNoUnitGiven
this.useAsDefaultInput = json.useAsDefaultInput ?? json.useIfNoUnitGiven this.useAsDefaultInput = useAsDefaultInput ?? json.useIfNoUnitGiven
this._human = Translations.T(json.human, context + "human") this._human = Translations.T(json.human, context + "human")
this._humanSingular = Translations.T(json.humanSingular, context + "humanSingular") this._humanSingular = Translations.T(json.humanSingular, context + "humanSingular")
@ -69,7 +69,7 @@ export class Denomination {
* human: { * human: {
* en: "meter" * en: "meter"
* } * }
* }, "test") * }, false, "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"
@ -84,7 +84,7 @@ export class Denomination {
* human: { * human: {
* en: "meter" * en: "meter"
* } * }
* }, "test") * }, false, "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"

View file

@ -389,62 +389,7 @@ export interface LayerConfigJson {
allowSplit?: boolean allowSplit?: boolean
/** /**
* In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...) * @see UnitConfigJson
*
* Sometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)
*
* This brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)
*
* Not only do we want to write consistent data to OSM, we also want to present this consistently to the user.
* This is handled by defining units.
*
* # Rendering
*
* To render a value with long (human) denomination, use {canonical(key)}
*
* # Usage
*
* First of all, you define which keys have units applied, for example:
*
* ```
* units: [
* appliesTo: ["maxspeed", "maxspeed:hgv", "maxspeed:bus"]
* applicableUnits: [
* ...
* ]
* ]
* ```
*
* ApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:
*
* ```
* applicableUnits: [
* {
* canonicalDenomination: "km/h",
* alternativeDenomination: ["km/u", "kmh", "kph"]
* default: true,
* human: {
* en: "kilometer/hour",
* nl: "kilometer/uur"
* },
* humanShort: {
* en: "km/h",
* nl: "km/u"
* }
* },
* {
* canoncialDenomination: "mph",
* ... similar for miles an hour ...
* }
* ]
* ```
*
*
* If this is defined, then every key which the denominations apply to (`maxspeed`, `maxspeed:hgv` and `maxspeed:bus`) will be rewritten at the metatagging stage:
* every value will be parsed and the canonical extension will be added add presented to the other parts of the code.
*
* Also, if a freeform text field is used, an extra dropdown with applicable denominations will be given
*
*/ */
units?: UnitConfigJson[] units?: UnitConfigJson[]

View file

@ -1,3 +1,61 @@
/**
* In some cases, a value is represented in a certain unit (such as meters for heigt/distance/..., km/h for speed, ...)
*
* Sometimes, multiple denominations are possible (e.g. km/h vs mile/h; megawatt vs kilowatt vs gigawatt for power generators, ...)
*
* This brings in some troubles, as there are multiple ways to write it (no denomitation, 'm' vs 'meter' 'metre', ...)
*
* Not only do we want to write consistent data to OSM, we also want to present this consistently to the user.
* This is handled by defining units.
*
* # Rendering
*
* To render a value with long (human) denomination, use {canonical(key)}
*
* # Usage
*
* First of all, you define which keys have units applied, for example:
*
* ```
* units: [
* appliesTo: ["maxspeed", "maxspeed:hgv", "maxspeed:bus"]
* applicableUnits: [
* ...
* ]
* ]
* ```
*
* ApplicableUnits defines which is the canonical extension, how it is presented to the user, ...:
*
* ```
* applicableUnits: [
* {
* canonicalDenomination: "km/h",
* alternativeDenomination: ["km/u", "kmh", "kph"]
* default: true,
* human: {
* en: "kilometer/hour",
* nl: "kilometer/uur"
* },
* humanShort: {
* en: "km/h",
* nl: "km/u"
* }
* },
* {
* canoncialDenomination: "mph",
* ... similar for miles an hour ...
* }
* ]
* ```
*
*
* If this is defined, then every key which the denominations apply to (`maxspeed`, `maxspeed:hgv` and `maxspeed:bus`) will be rewritten at the metatagging stage:
* every value will be parsed and the canonical extension will be added add presented to the other parts of the code.
*
* Also, if a freeform text field is used, an extra dropdown with applicable denominations will be given
*
*/
export default interface UnitConfigJson { export default interface UnitConfigJson {
/** /**
* Every key from this list will be normalized. * Every key from this list will be normalized.
@ -11,9 +69,19 @@ export default interface UnitConfigJson {
*/ */
eraseInvalidValues?: boolean eraseInvalidValues?: boolean
/** /**
* The possible denominations * The possible denominations for this unit.
* For length, denominations could be "meter", "kilometer", "miles", "foot"
*/ */
applicableUnits: DenominationConfigJson[] applicableUnits: DenominationConfigJson[]
/**
* In some cases, the default denomination is not the most user friendly to input.
* E.g., when measuring kerb heights, it is illogical to ask contributors to input an amount in meters.
*
* When a default input method should be used, this can be specified by setting the canonical denomination here, e.g.
* `defaultInput: "cm"`. This must be a denomination which appears in the applicableUnits
*/
defaultInput?: string
} }
export interface DenominationConfigJson { export interface DenominationConfigJson {
@ -28,12 +96,6 @@ export interface DenominationConfigJson {
*/ */
useIfNoUnitGiven?: boolean | string[] useIfNoUnitGiven?: boolean | string[]
/**
* Use this value as default denomination when the user inputs a value (e.g. to force using 'centimeters' instead of 'meters' by default).
* If unset for all values, this will use 'useIfNoUnitGiven'. If at least one denomination has this set, this will default to false
*/
useAsDefaultInput?: boolean | string[]
/** /**
* The canonical value for this denomination which will be added to the value in OSM. * The canonical value for this denomination which will be added to the value in OSM.
* e.g. "m" for meters * e.g. "m" for meters
@ -46,12 +108,15 @@ export interface DenominationConfigJson {
/** /**
* The canonical denomination in the case that the unit is precisely '1'. * The canonical denomination in the case that the unit is precisely '1'.
* Used for display purposes * Used for display purposes only.
*
* E.g.: for duration of something in minutes: `2 minutes` but `1 minute`; the `minute` goes here
*/ */
canonicalDenominationSingular?: string canonicalDenominationSingular?: string
/** /**
* A list of alternative values which can occur in the OSM database - used for parsing. * A list of alternative values which can occur in the OSM database - used for parsing.
* E.g.: while 'm' is canonical, `meter`, `mtrs`, ... can occur as well
*/ */
alternativeDenomination?: string[] alternativeDenomination?: string[]
@ -62,16 +127,16 @@ export interface DenominationConfigJson {
* "fr": "metre" * "fr": "metre"
* } * }
*/ */
human?: string | any human?: string | Record<string, string>
/** /**
* The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g. * The value for humans in the dropdown. This should not use abbreviations and should be translated, e.g.
* { * {
* "en": "minute", * "en": "minute",
* "nl": "minuut"x² * "nl": "minuut"
* } * }
*/ */
humanSingular?: string | any humanSingular?: string | Record<string, string>
/** /**
* If set, then the canonical value will be prefixed instead, e.g. for '€' * If set, then the canonical value will be prefixed instead, e.g. for '€'

View file

@ -60,6 +60,44 @@ export class Unit {
} }
} }
/**
*
* // Should detect invalid defaultInput
* let threwError = false
* try{
* Unit.fromJson({
* appliesToKey: ["length"],
* defaultInput: "xcm",
* applicableUnits: [
* {
* canonicalDenomination: "m",
* useIfNoUnitGiven: true,
* human: "meter"
* }
* ]
* },"test")
* }catch(e){
* threwError = true
* }
* threwError // => true
*
* // Should work
* Unit.fromJson({
* appliesToKey: ["length"],
* defaultInput: "xcm",
* applicableUnits: [
* {
* canonicalDenomination: "m",
* useIfNoUnitGiven: true,
* humen: "meter"
* },
* {
* canonicalDenomination: "cm",
* human: "centimeter"
* }
* ]
* }, "test")
*/
static fromJson(json: UnitConfigJson, ctx: string) { static fromJson(json: UnitConfigJson, ctx: string) {
const appliesTo = json.appliesToKey const appliesTo = json.appliesToKey
for (let i = 0; i < appliesTo.length; i++) { for (let i = 0; i < appliesTo.length; i++) {
@ -74,14 +112,15 @@ export class Unit {
} }
// Some keys do have unit handling // Some keys do have unit handling
if (json.applicableUnits.some((denom) => denom.useAsDefaultInput !== undefined)) {
json.applicableUnits.forEach((denom) => {
denom.useAsDefaultInput = denom.useAsDefaultInput ?? false
})
}
const applicable = json.applicableUnits.map( const applicable = json.applicableUnits.map(
(u, i) => new Denomination(u, `${ctx}.units[${i}]`) (u, i) =>
new Denomination(
u,
u.canonicalDenomination === undefined
? undefined
: u.canonicalDenomination.trim() === json.defaultInput,
`${ctx}.units[${i}]`
)
) )
return new Unit(appliesTo, applicable, json.eraseInvalidValues ?? false) return new Unit(appliesTo, applicable, json.eraseInvalidValues ?? false)
} }

View file

@ -8,11 +8,8 @@ import { Utils } from "../Utils"
import LanguagePicker1 from "./LanguagePicker" import LanguagePicker1 from "./LanguagePicker"
import IndexText from "./BigComponents/IndexText" import IndexText from "./BigComponents/IndexText"
import FeaturedMessage from "./BigComponents/FeaturedMessage" import FeaturedMessage from "./BigComponents/FeaturedMessage"
import Toggle from "./Input/Toggle"
import { SubtleButton } from "./Base/SubtleButton"
import { VariableUiElement } from "./Base/VariableUIElement"
import Svg from "../Svg"
import { ImportViewerLinks } from "./BigComponents/UserInformation" import { ImportViewerLinks } from "./BigComponents/UserInformation"
import { LoginToggle } from "./Popup/LoginButton"
import UserSurveyPanel from "./UserSurveyPanel" import UserSurveyPanel from "./UserSurveyPanel"
export default class AllThemesGui { export default class AllThemesGui {
@ -31,13 +28,7 @@ export default class AllThemesGui {
new FeaturedMessage().SetClass("mb-4 block"), new FeaturedMessage().SetClass("mb-4 block"),
new Combine([new UserSurveyPanel()]).SetClass("flex justify-center"), new Combine([new UserSurveyPanel()]).SetClass("flex justify-center"),
new MoreScreen(state, true), new MoreScreen(state, true),
new Toggle( new LoginToggle(undefined, Translations.t.index.logIn, state),
undefined,
new SubtleButton(undefined, Translations.t.index.logIn)
.SetStyle("height:min-content")
.onClick(() => state.osmConnection.AttemptLogin()),
state.osmConnection.isLoggedIn
),
new ImportViewerLinks(state.osmConnection), new ImportViewerLinks(state.osmConnection),
Translations.t.general.aboutMapcomplete Translations.t.general.aboutMapcomplete
.Subs({ osmcha_link: Utils.OsmChaLinkFor(7) }) .Subs({ osmcha_link: Utils.OsmChaLinkFor(7) })

View file

@ -414,6 +414,7 @@ export default class MinimapImplementation extends BaseUIElement implements Mini
map.on("contextmenu", function (e) { map.on("contextmenu", function (e) {
// @ts-ignore // @ts-ignore
lastClickLocation?.setData({ lat: e.latlng.lat, lon: e.latlng.lng }) lastClickLocation?.setData({ lat: e.latlng.lat, lon: e.latlng.lng })
map.setZoom(map.getZoom() + 1)
}) })
} }

View file

@ -24,6 +24,7 @@ export default class ScrollableFullScreen {
private hashToShow: string private hashToShow: string
private _fullscreencomponent: BaseUIElement private _fullscreencomponent: BaseUIElement
private _resetScrollSignal: UIEventSource<void> = new UIEventSource<void>(undefined) private _resetScrollSignal: UIEventSource<void> = new UIEventSource<void>(undefined)
private _setHash: boolean
constructor( constructor(
title: (options: { mode: string }) => BaseUIElement, title: (options: { mode: string }) => BaseUIElement,
@ -34,13 +35,14 @@ export default class ScrollableFullScreen {
hashToShow: string, hashToShow: string,
isShown: UIEventSource<boolean> = new UIEventSource<boolean>(false), isShown: UIEventSource<boolean> = new UIEventSource<boolean>(false),
options?: { options?: {
setHash?: true | boolean setHash?: boolean
} }
) { ) {
this.hashToShow = hashToShow this.hashToShow = hashToShow
this.isShown = isShown this.isShown = isShown
this._setHash = options?.setHash ?? true
if (hashToShow === undefined) { if ((hashToShow === undefined || hashToShow === "") && this._setHash) {
throw "HashToShow should be defined as it is vital for the 'back' key functionality" throw "HashToShow should be defined as it is vital for the 'back' key functionality"
} }
@ -55,8 +57,7 @@ export default class ScrollableFullScreen {
) )
const self = this const self = this
const setHash = options?.setHash ?? true if (this._setHash) {
if (setHash) {
Hash.hash.addCallback((h) => { Hash.hash.addCallback((h) => {
if (h === undefined) { if (h === undefined) {
isShown.setData(false) isShown.setData(false)
@ -64,13 +65,10 @@ export default class ScrollableFullScreen {
}) })
} }
isShown.addCallback((isShown) => { isShown.addCallbackD((isShown) => {
if (isShown) { if (isShown) {
// We first must set the hash, then activate the panel // We first must set the hash, then activate the panel
// If the order is wrong, this will cause the panel to disactivate again // If the order is wrong, this will cause the panel to disactivate again
if (setHash) {
Hash.hash.setData(hashToShow)
}
ScrollableFullScreen._currentlyOpen = self ScrollableFullScreen._currentlyOpen = self
self.Activate() self.Activate()
} else { } else {
@ -81,6 +79,10 @@ export default class ScrollableFullScreen {
ScrollableFullScreen.collapse() ScrollableFullScreen.collapse()
} }
}) })
if (isShown.data) {
ScrollableFullScreen._currentlyOpen = self
this.Activate()
}
} }
private static initEmpty(): FixedUiElement { private static initEmpty(): FixedUiElement {
@ -114,6 +116,9 @@ export default class ScrollableFullScreen {
* @constructor * @constructor
*/ */
public Activate(): void { public Activate(): void {
if (this.hashToShow && this.hashToShow !== "" && this._setHash) {
Hash.hash.setData(this.hashToShow)
}
this.isShown.setData(true) this.isShown.setData(true)
this._fullscreencomponent.AttachTo("fullscreen") this._fullscreencomponent.AttachTo("fullscreen")
const fs = document.getElementById("fullscreen") const fs = document.getElementById("fullscreen")
@ -157,4 +162,8 @@ export default class ScrollableFullScreen {
"fixed top-0 left-0 right-0 h-screen w-screen desktop:max-h-65vh md:w-auto md:relative z-above-controls md:rounded-xl overflow-hidden" "fixed top-0 left-0 right-0 h-screen w-screen desktop:max-h-65vh md:w-auto md:relative z-above-controls md:rounded-xl overflow-hidden"
) )
} }
static ActivateCurrent() {
ScrollableFullScreen._currentlyOpen?.Activate()
}
} }

View file

@ -31,13 +31,19 @@ export default abstract class BaseUIElement {
throw "SEVERE: could not attach UIElement to " + divId throw "SEVERE: could not attach UIElement to " + divId
} }
while (element.firstChild) { let alreadyThere = false
//The list is LIVE so it will re-index each call const elementToAdd = this.ConstructElement()
element.removeChild(element.firstChild) const childs = Array.from(element.childNodes)
for (const child of childs) {
if (child === elementToAdd) {
alreadyThere = true
continue
}
element.removeChild(child)
} }
const el = this.ConstructElement()
if (el !== undefined) { if (elementToAdd !== undefined && !alreadyThere) {
element.appendChild(el) element.appendChild(elementToAdd)
} }
return this return this

View file

@ -0,0 +1,59 @@
import Combine from "../Base/Combine"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { BBox } from "../../Logic/BBox"
import Loc from "../../Models/Loc"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import Translations from "../i18n/Translations"
import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg"
import { Utils } from "../../Utils"
import { MapillaryLink } from "./MapillaryLink"
import TranslatorsPanel from "./TranslatorsPanel"
import { OpenIdEditor, OpenJosm } from "./CopyrightPanel"
export class ActionButtons extends Combine {
constructor(state: {
readonly layoutToUse: LayoutConfig
readonly currentBounds: Store<BBox>
readonly locationControl: Store<Loc>
readonly osmConnection: OsmConnection
readonly isTranslator: Store<boolean>
}) {
const imgSize = "h-6 w-6"
const iconStyle = "height: 1.5rem; width: 1.5rem"
const t = Translations.t.general.attribution
super([
new SubtleButton(Svg.liberapay_ui(), t.donate, {
url: "https://liberapay.com/pietervdvn/",
newTab: true,
imgSize,
}),
new SubtleButton(Svg.bug_ui(), t.openIssueTracker, {
url: "https://github.com/pietervdvn/MapComplete/issues",
newTab: true,
imgSize,
}),
new SubtleButton(
Svg.statistics_ui(),
t.openOsmcha.Subs({ theme: state.layoutToUse.title }),
{
url: Utils.OsmChaLinkFor(31, state.layoutToUse.id),
newTab: true,
imgSize,
}
),
new SubtleButton(Svg.mastodon_ui(), t.followOnMastodon, {
url: "https://en.osm.town/@MapComplete",
newTab: true,
imgSize,
}),
new OpenIdEditor(state, iconStyle),
new MapillaryLink(state, iconStyle),
new OpenJosm(state, iconStyle).SetClass("hidden-on-mobile"),
new TranslatorsPanel(state, iconStyle),
])
this.SetClass("block w-full link-no-underline")
}
}

View file

@ -23,13 +23,10 @@ import Constants from "../../Models/Constants"
import ContributorCount from "../../Logic/ContributorCount" import ContributorCount from "../../Logic/ContributorCount"
import Img from "../Base/Img" import Img from "../Base/Img"
import { TypedTranslation } from "../i18n/Translation" import { TypedTranslation } from "../i18n/Translation"
import TranslatorsPanel from "./TranslatorsPanel"
import { MapillaryLink } from "./MapillaryLink"
import FullWelcomePaneWithTabs from "./FullWelcomePaneWithTabs"
export class OpenIdEditor extends VariableUiElement { export class OpenIdEditor extends VariableUiElement {
constructor( constructor(
state: { locationControl: UIEventSource<Loc> }, state: { readonly locationControl: Store<Loc> },
iconStyle?: string, iconStyle?: string,
objectId?: string objectId?: string
) { ) {
@ -125,38 +122,6 @@ export default class CopyrightPanel extends Combine {
}) { }) {
const t = Translations.t.general.attribution const t = Translations.t.general.attribution
const layoutToUse = state.layoutToUse const layoutToUse = state.layoutToUse
const imgSize = "h-6 w-6"
const iconStyle = "height: 1.5rem; width: 1.5rem"
const actionButtons = [
new SubtleButton(Svg.liberapay_ui(), t.donate, {
url: "https://liberapay.com/pietervdvn/",
newTab: true,
imgSize,
}),
new SubtleButton(Svg.bug_ui(), t.openIssueTracker, {
url: "https://github.com/pietervdvn/MapComplete/issues",
newTab: true,
imgSize,
}),
new SubtleButton(
Svg.statistics_ui(),
t.openOsmcha.Subs({ theme: state.layoutToUse.title }),
{
url: Utils.OsmChaLinkFor(31, state.layoutToUse.id),
newTab: true,
imgSize,
}
),
new SubtleButton(Svg.mastodon_ui(), t.followOnMastodon, {
url: "https://en.osm.town/@MapComplete",
newTab: true,
imgSize,
}),
new OpenIdEditor(state, iconStyle),
new MapillaryLink(state, iconStyle),
new OpenJosm(state, iconStyle),
new TranslatorsPanel(state, iconStyle),
]
const iconAttributions = layoutToUse.usedImages.map(CopyrightPanel.IconAttribution) const iconAttributions = layoutToUse.usedImages.map(CopyrightPanel.IconAttribution)
@ -213,7 +178,6 @@ export default class CopyrightPanel extends Combine {
CopyrightPanel.CodeContributors(contributors, t.codeContributionsBy), CopyrightPanel.CodeContributors(contributors, t.codeContributionsBy),
CopyrightPanel.CodeContributors(translators, t.translatedBy), CopyrightPanel.CodeContributors(translators, t.translatedBy),
new FixedUiElement("MapComplete " + Constants.vNumber).SetClass("font-bold"), new FixedUiElement("MapComplete " + Constants.vNumber).SetClass("font-bold"),
new Combine(actionButtons).SetClass("block w-full link-no-underline"),
new Title(t.iconAttribution.title, 3), new Title(t.iconAttribution.title, 3),
...iconAttributions, ...iconAttributions,
].map((e) => e?.SetClass("mt-4")) ].map((e) => e?.SetClass("mt-4"))

View file

@ -37,12 +37,13 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
featurePipeline: FeaturePipeline featurePipeline: FeaturePipeline
backgroundLayer: UIEventSource<BaseLayer> backgroundLayer: UIEventSource<BaseLayer>
filteredLayers: UIEventSource<FilteredLayer[]> filteredLayers: UIEventSource<FilteredLayer[]>
} & UserRelatedState } & UserRelatedState,
guistate?: { userInfoIsOpened: UIEventSource<boolean> }
) { ) {
const layoutToUse = state.layoutToUse const layoutToUse = state.layoutToUse
super( super(
() => layoutToUse.title.Clone(), () => layoutToUse.title.Clone(),
() => FullWelcomePaneWithTabs.GenerateContents(state, currentTab, isShown), () => FullWelcomePaneWithTabs.GenerateContents(state, currentTab, isShown, guistate),
"welcome", "welcome",
isShown isShown
) )
@ -60,12 +61,13 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
filteredLayers: UIEventSource<FilteredLayer[]> filteredLayers: UIEventSource<FilteredLayer[]>
} & UserRelatedState, } & UserRelatedState,
isShown: UIEventSource<boolean>, isShown: UIEventSource<boolean>,
currentTab: UIEventSource<number> currentTab: UIEventSource<number>,
guistate?: { userInfoIsOpened: UIEventSource<boolean> }
): { header: string | BaseUIElement; content: BaseUIElement }[] { ): { header: string | BaseUIElement; content: BaseUIElement }[] {
const tabs: { header: string | BaseUIElement; content: BaseUIElement }[] = [ const tabs: { header: string | BaseUIElement; content: BaseUIElement }[] = [
{ {
header: `<img src='${state.layoutToUse.icon}'>`, header: `<img src='${state.layoutToUse.icon}'>`,
content: new ThemeIntroductionPanel(isShown, currentTab, state), content: new ThemeIntroductionPanel(isShown, currentTab, state, guistate),
}, },
] ]
@ -113,11 +115,12 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
filteredLayers: UIEventSource<FilteredLayer[]> filteredLayers: UIEventSource<FilteredLayer[]>
} & UserRelatedState, } & UserRelatedState,
currentTab: UIEventSource<number>, currentTab: UIEventSource<number>,
isShown: UIEventSource<boolean> isShown: UIEventSource<boolean>,
guistate?: { userInfoIsOpened: UIEventSource<boolean> }
) { ) {
const tabs = FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown, currentTab) const tabs = FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown, currentTab, guistate)
const tabsWithAboutMc = [ const tabsWithAboutMc = [
...FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown, currentTab), ...FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown, currentTab, guistate),
] ]
tabsWithAboutMc.push({ tabsWithAboutMc.push({

View file

@ -14,17 +14,10 @@ import FeatureInfoBox from "../Popup/FeatureInfoBox"
import CopyrightPanel from "./CopyrightPanel" import CopyrightPanel from "./CopyrightPanel"
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
import Hotkeys from "../Base/Hotkeys" import Hotkeys from "../Base/Hotkeys"
import { DefaultGuiState } from "../DefaultGuiState"
export default class LeftControls extends Combine { export default class LeftControls extends Combine {
constructor( constructor(state: FeaturePipelineState, guiState: DefaultGuiState) {
state: FeaturePipelineState,
guiState: {
currentViewControlIsOpened: UIEventSource<boolean>
downloadControlIsOpened: UIEventSource<boolean>
filterViewIsOpened: UIEventSource<boolean>
copyrightViewIsOpened: UIEventSource<boolean>
}
) {
const currentViewFL = state.currentView?.layer const currentViewFL = state.currentView?.layer
const currentViewAction = new Toggle( const currentViewAction = new Toggle(
new Lazy(() => { new Lazy(() => {

View file

@ -1,14 +1,13 @@
import { VariableUiElement } from "../Base/VariableUIElement" import { VariableUiElement } from "../Base/VariableUIElement"
import { UIEventSource } from "../../Logic/UIEventSource" import { Store } from "../../Logic/UIEventSource"
import Loc from "../../Models/Loc" import Loc from "../../Models/Loc"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import { SubtleButton } from "../Base/SubtleButton" import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg" import Svg from "../../Svg"
import Combine from "../Base/Combine" import Combine from "../Base/Combine"
import Title from "../Base/Title"
export class MapillaryLink extends VariableUiElement { export class MapillaryLink extends VariableUiElement {
constructor(state: { locationControl: UIEventSource<Loc> }, iconStyle?: string) { constructor(state: { readonly locationControl: Store<Loc> }, iconStyle?: string) {
const t = Translations.t.general.attribution const t = Translations.t.general.attribution
super( super(
state.locationControl.map((location) => { state.locationControl.map((location) => {
@ -17,12 +16,14 @@ export class MapillaryLink extends VariableUiElement {
}&lng=${location?.lon ?? 0}&z=${Math.max((location?.zoom ?? 2) - 1, 1)}` }&lng=${location?.lon ?? 0}&z=${Math.max((location?.zoom ?? 2) - 1, 1)}`
return new SubtleButton( return new SubtleButton(
Svg.mapillary_black_ui().SetStyle(iconStyle), Svg.mapillary_black_ui().SetStyle(iconStyle),
new Combine([t.openMapillary.SetClass("font-bold"), t.mapillaryHelp]), new Combine([t.openMapillary.SetClass("font-bold"), t.mapillaryHelp]).SetClass(
"flex flex-col link-no-underline"
),
{ {
url: mapillaryLink, url: mapillaryLink,
newTab: true, newTab: true,
} }
).SetClass("flex flex-col link-no-underline") )
}) })
) )
} }

View file

@ -110,25 +110,18 @@ export default class ShareScreen extends Combine {
{ urlName: "fs-search", human: tr.fsSearch }, { urlName: "fs-search", human: tr.fsSearch },
{ urlName: "fs-welcome-message", human: tr.fsWelcomeMessage }, { urlName: "fs-welcome-message", human: tr.fsWelcomeMessage },
{ urlName: "fs-layers", human: tr.fsLayers }, { urlName: "fs-layers", human: tr.fsLayers },
{ urlName: "layer-control-toggle", human: tr.fsLayerControlToggle, reverse: true },
{ urlName: "fs-add-new", human: tr.fsAddNew }, { urlName: "fs-add-new", human: tr.fsAddNew },
{ urlName: "fs-geolocation", human: tr.fsGeolocation }, { urlName: "fs-geolocation", human: tr.fsGeolocation },
] ]
for (const swtch of switches) { for (const swtch of switches) {
const checkbox = new CheckBox(Translations.W(swtch.human), !swtch.reverse) const checkbox = new CheckBox(Translations.W(swtch.human))
optionCheckboxes.push(checkbox) optionCheckboxes.push(checkbox)
optionParts.push( optionParts.push(
checkbox.GetValue().map((isEn) => { checkbox.GetValue().map((isEn) => {
if (isEn) { if (isEn) {
if (swtch.reverse) {
return `${swtch.urlName}=true`
}
return null return null
} else { } else {
if (swtch.reverse) {
return null
}
return `${swtch.urlName}=false` return `${swtch.urlName}=false`
} }
}) })

View file

@ -1,7 +1,7 @@
/** /**
* Asks to add a feature at the last clicked location, at least if zoom is sufficient * Asks to add a feature at the last clicked location, at least if zoom is sufficient
*/ */
import { UIEventSource } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Svg from "../../Svg" import Svg from "../../Svg"
import { SubtleButton } from "../Base/SubtleButton" import { SubtleButton } from "../Base/SubtleButton"
import Combine from "../Base/Combine" import Combine from "../Base/Combine"
@ -28,6 +28,7 @@ import Hash from "../../Logic/Web/Hash"
import { GlobalFilter } from "../../Logic/State/MapState" import { GlobalFilter } from "../../Logic/State/MapState"
import { WayId } from "../../Models/OsmFeature" import { WayId } from "../../Models/OsmFeature"
import { Tag } from "../../Logic/Tags/Tag" import { Tag } from "../../Logic/Tags/Tag"
import { LoginToggle } from "../Popup/LoginButton"
/* /*
* The SimpleAddUI is a single panel, which can have multiple states: * The SimpleAddUI is a single panel, which can have multiple states:
@ -44,7 +45,7 @@ export interface PresetInfo extends PresetConfig {
boundsFactor?: 0.25 | number boundsFactor?: 0.25 | number
} }
export default class SimpleAddUI extends Toggle { export default class SimpleAddUI extends LoginToggle {
/** /**
* *
* @param isShown * @param isShown
@ -59,6 +60,7 @@ export default class SimpleAddUI extends Toggle {
filterViewIsOpened: UIEventSource<boolean>, filterViewIsOpened: UIEventSource<boolean>,
state: { state: {
featureSwitchIsTesting: UIEventSource<boolean> featureSwitchIsTesting: UIEventSource<boolean>
featureSwitchUserbadge: Store<boolean>
layoutToUse: LayoutConfig layoutToUse: LayoutConfig
osmConnection: OsmConnection osmConnection: OsmConnection
changes: Changes changes: Changes
@ -74,10 +76,6 @@ export default class SimpleAddUI extends Toggle {
}, },
takeLocationFrom?: UIEventSource<{ lat: number; lon: number }> takeLocationFrom?: UIEventSource<{ lat: number; lon: number }>
) { ) {
const loginButton = new SubtleButton(
Svg.osm_logo_ui(),
Translations.t.general.add.pleaseLogin.Clone()
).onClick(() => state.osmConnection.AttemptLogin())
const readYourMessages = new Combine([ const readYourMessages = new Combine([
Translations.t.general.readYourMessages.Clone().SetClass("alert"), Translations.t.general.readYourMessages.Clone().SetClass("alert"),
new SubtleButton(Svg.envelope_ui(), Translations.t.general.goToInbox, { new SubtleButton(Svg.envelope_ui(), Translations.t.general.goToInbox, {
@ -187,8 +185,8 @@ export default class SimpleAddUI extends Toggle {
userdetails.unreadMessages == 0 userdetails.unreadMessages == 0
) )
), ),
loginButton, Translations.t.general.add.pleaseLogin,
state.osmConnection.isLoggedIn state
) )
} }

View file

@ -3,12 +3,16 @@ import LanguagePicker from "../LanguagePicker"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import Toggle from "../Input/Toggle" import Toggle from "../Input/Toggle"
import { SubtleButton } from "../Base/SubtleButton" import { SubtleButton } from "../Base/SubtleButton"
import { UIEventSource } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { LoginToggle } from "../Popup/LoginButton" import { LoginToggle } from "../Popup/LoginButton"
import Svg from "../../Svg" import Svg from "../../Svg"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import FullWelcomePaneWithTabs from "./FullWelcomePaneWithTabs" import FullWelcomePaneWithTabs from "./FullWelcomePaneWithTabs"
import LoggedInUserIndicator from "../LoggedInUserIndicator"
import { ActionButtons } from "./ActionButtons"
import { BBox } from "../../Logic/BBox"
import Loc from "../../Models/Loc"
import UserSurveyPanel from "../UserSurveyPanel" import UserSurveyPanel from "../UserSurveyPanel"
export default class ThemeIntroductionPanel extends Combine { export default class ThemeIntroductionPanel extends Combine {
@ -21,7 +25,11 @@ export default class ThemeIntroductionPanel extends Combine {
featureSwitchUserbadge: UIEventSource<boolean> featureSwitchUserbadge: UIEventSource<boolean>
layoutToUse: LayoutConfig layoutToUse: LayoutConfig
osmConnection: OsmConnection osmConnection: OsmConnection
} currentBounds: Store<BBox>
locationControl: UIEventSource<Loc>
isTranslator: Store<boolean>
},
guistate?: { userInfoIsOpened: UIEventSource<boolean> }
) { ) {
const t = Translations.t.general const t = Translations.t.general
const layout = state.layoutToUse const layout = state.layoutToUse
@ -37,9 +45,18 @@ export default class ThemeIntroductionPanel extends Combine {
}) })
.SetClass("only-on-mobile") .SetClass("only-on-mobile")
const loggedInUserInfo = new LoggedInUserIndicator(state.osmConnection, {
firstLine: Translations.t.general.welcomeBack.Clone(),
})
if (guistate?.userInfoIsOpened) {
loggedInUserInfo.onClick(() => {
guistate.userInfoIsOpened.setData(true)
})
}
const loginStatus = new Toggle( const loginStatus = new Toggle(
new LoginToggle( new LoginToggle(
undefined, loggedInUserInfo,
new Combine([ new Combine([
Translations.t.general.loginWithOpenStreetMap.SetClass("text-xl font-bold"), Translations.t.general.loginWithOpenStreetMap.SetClass("text-xl font-bold"),
Translations.t.general.loginOnlyNeededToEdit.Clone().SetClass("font-bold"), Translations.t.general.loginOnlyNeededToEdit.Clone().SetClass("font-bold"),
@ -62,10 +79,10 @@ export default class ThemeIntroductionPanel extends Combine {
]).SetClass("flex flex-col mt-2"), ]).SetClass("flex flex-col mt-2"),
toTheMap, toTheMap,
loginStatus.SetClass("block"), loginStatus.SetClass("block mt-6 pt-2 md:border-t-2 border-dotted border-gray-400"),
layout.descriptionTail?.Clone().SetClass("block mt-4"), layout.descriptionTail?.Clone().SetClass("block mt-4"),
languagePicker?.SetClass("block mt-4"), languagePicker?.SetClass("block mt-4 pb-8 border-b-2 border-dotted border-gray-400"),
Toggle.If(state.featureSwitchMoreQuests, () => Toggle.If(state.featureSwitchMoreQuests, () =>
new Combine([ new Combine([
@ -80,6 +97,7 @@ export default class ThemeIntroductionPanel extends Combine {
.SetClass("h-12"), .SetClass("h-12"),
]).SetClass("flex flex-col mt-6") ]).SetClass("flex flex-col mt-6")
), ),
new ActionButtons(state),
...layout.CustomCodeSnippets(), ...layout.CustomCodeSnippets(),
]) ])

View file

@ -66,7 +66,7 @@ class TranslatorsPanelContent extends Combine {
"missingLayers:", "missingLayers:",
missingLayers missingLayers
) )
return [ return Utils.NoNull([
hasMissingTheme hasMissingTheme
? new Link( ? new Link(
"themes:" + layout.id + ".* (zen mode)", "themes:" + layout.id + ".* (zen mode)",
@ -86,7 +86,7 @@ class TranslatorsPanelContent extends Combine {
(context) => (context) =>
new Link(context, LinkToWeblate.hrefToWeblate(language, context), true) new Link(context, LinkToWeblate.hrefToWeblate(language, context), true)
), ),
] ])
} }
// "translationCompleteness": "Translations for {theme} in {language} are at {percentage}: {translated} out of {total}", // "translationCompleteness": "Translations for {theme} in {language} are at {percentage}: {translated} out of {total}",

View file

@ -4,30 +4,22 @@ import { FixedInputElement } from "../Input/FixedInputElement"
import Combine from "../Base/Combine" import Combine from "../Base/Combine"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import { TextField } from "../Input/TextField" import { TextField } from "../Input/TextField"
import { UIEventSource } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Title from "../Base/Title" import Title from "../Base/Title"
import { SubtleButton } from "../Base/SubtleButton" import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg" import Svg from "../../Svg"
import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { Translation } from "../i18n/Translation" import { Translation } from "../i18n/Translation"
import { LoginToggle } from "../Popup/LoginButton"
export default class UploadTraceToOsmUI extends Toggle { export default class UploadTraceToOsmUI extends LoginToggle {
private static createDefault(s: string, defaultValue: string) {
if (defaultValue.length < 1) {
throw "Default value should have some characters"
}
if (s === undefined || s === null || s === "") {
return defaultValue
}
return s
}
constructor( constructor(
trace: (title: string) => string, trace: (title: string) => string,
state: { state: {
layoutToUse: LayoutConfig layoutToUse: LayoutConfig
osmConnection: OsmConnection osmConnection: OsmConnection
readonly featureSwitchUserbadge: Store<boolean>
}, },
options?: { options?: {
whenUploaded?: () => void | Promise<void> whenUploaded?: () => void | Promise<void>
@ -119,15 +111,41 @@ export default class UploadTraceToOsmUI extends Toggle {
]).SetClass("flex flex-col p-4 rounded border-2 m-2 border-subtle") ]).SetClass("flex flex-col p-4 rounded border-2 m-2 border-subtle")
super( super(
new Combine([Svg.confirm_svg().SetClass("w-12 h-12 mr-2"), t.uploadFinished]).SetClass(
"flex p-2 rounded-xl border-2 subtle-border items-center"
),
new Toggle( new Toggle(
confirmPanel, new Toggle(
new SubtleButton(Svg.upload_svg(), t.title).onClick(() => clicked.setData(true)), new Combine([
clicked Svg.confirm_svg().SetClass("w-12 h-12 mr-2"),
t.uploadFinished,
]).SetClass("flex p-2 rounded-xl border-2 subtle-border items-center"),
new Toggle(
confirmPanel,
new SubtleButton(Svg.upload_svg(), t.title).onClick(() =>
clicked.setData(true)
),
clicked
),
uploadFinished
),
new Combine([
Svg.invalid_ui().SetClass("w-8 h-8 m-2"),
t.gpxServiceOffline.SetClass("p-2"),
]).SetClass("flex border alert items-center"),
state.osmConnection.gpxServiceIsOnline.map(
(serviceState) => serviceState === "online"
)
), ),
uploadFinished undefined,
state
) )
} }
private static createDefault(s: string, defaultValue: string) {
if (defaultValue.length < 1) {
throw "Default value should have some characters"
}
if (s === undefined || s === null || s === "") {
return defaultValue
}
return s
}
} }

View file

@ -140,12 +140,17 @@ class UserInformationMainPanel extends Combine {
} }
export default class UserInformationPanel extends ScrollableFullScreen { export default class UserInformationPanel extends ScrollableFullScreen {
constructor(state: { constructor(
layoutToUse: LayoutConfig state: {
osmConnection: OsmConnection layoutToUse: LayoutConfig
locationControl: UIEventSource<Loc> osmConnection: OsmConnection
}) { locationControl: UIEventSource<Loc>
const isOpened = new UIEventSource<boolean>(false) },
options?: {
isOpened?: UIEventSource<boolean>
}
) {
const isOpened = options?.isOpened ?? new UIEventSource<boolean>(false)
super( super(
() => { () => {
return new VariableUiElement( return new VariableUiElement(

View file

@ -33,7 +33,6 @@ import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"
import { GeoLocationState } from "../Logic/State/GeoLocationState" import { GeoLocationState } from "../Logic/State/GeoLocationState"
import Hotkeys from "./Base/Hotkeys" import Hotkeys from "./Base/Hotkeys"
import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers" import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers"
import { Translation } from "./i18n/Translation"
/** /**
* The default MapComplete GUI initializer * The default MapComplete GUI initializer
@ -56,6 +55,7 @@ export default class DefaultGUI {
public setup() { public setup() {
this.SetupUIElements() this.SetupUIElements()
this.SetupMap() this.SetupMap()
ScrollableFullScreen.ActivateCurrent()
if ( if (
this.state.layoutToUse.customCss !== undefined && this.state.layoutToUse.customCss !== undefined &&
@ -203,39 +203,43 @@ export default class DefaultGUI {
const guiState = this.guiState const guiState = this.guiState
const self = this const self = this
new Combine([
Toggle.If(state.featureSwitchUserbadge, () => {
const userInfo = new UserInformationPanel(state)
const mapControl = new MapControlButton( const userInfoMapControl = Toggle.If(state.featureSwitchUserbadge, () => {
new VariableUiElement( console.log("Guistate is", guiState)
state.osmConnection.userDetails.map((ud) => { new UserInformationPanel(state, {
if (ud?.img === undefined) { isOpened: guiState.userInfoIsOpened,
return Svg.person_ui().SetClass("mt-1 block") })
}
return new Img(ud?.img)
})
).SetClass("block rounded-full overflow-hidden"),
{
dontStyle: true,
}
).onClick(() => userInfo.Activate())
return new LoginToggle( const mapControl = new MapControlButton(
mapControl, new VariableUiElement(
Translations.t.general.loginWithOpenStreetMap, state.osmConnection.userDetails.map((ud) => {
state if (ud?.img === undefined) {
) return Svg.person_ui().SetClass("mt-1 block")
}), }
Toggle.If( return new Img(ud?.img)
state.featureSwitchExtraLinkEnabled, })
() => new ExtraLinkButton(state, state.layoutToUse.extraLink) ).SetClass("block rounded-full overflow-hidden"),
), {
Toggle.If(state.featureSwitchWelcomeMessage, () => self.InitWelcomeMessage()), dontStyle: true,
Toggle.If(state.featureSwitchIsTesting, () => }
new FixedUiElement("TESTING").SetClass("alert m-2 border-2 border-black") ).onClick(() => {
), self.guiState.userInfoIsOpened.setData(true)
]) })
return new LoginToggle(mapControl, Translations.t.general.loginWithOpenStreetMap, state)
})
const extraLink = Toggle.If(
state.featureSwitchExtraLinkEnabled,
() => new ExtraLinkButton(state, state.layoutToUse.extraLink)
)
const welcomeMessageMapControl = Toggle.If(state.featureSwitchWelcomeMessage, () =>
self.InitWelcomeMessage()
)
const testingBadge = Toggle.If(state.featureSwitchIsTesting, () =>
new FixedUiElement("TESTING").SetClass("alert m-2 border-2 border-black")
)
new Combine([welcomeMessageMapControl, userInfoMapControl, extraLink, testingBadge])
.SetClass("flex flex-col") .SetClass("flex flex-col")
.AttachTo("top-left") .AttachTo("top-left")
@ -273,26 +277,16 @@ export default class DefaultGUI {
new CenterMessageBox(state).AttachTo("centermessage") new CenterMessageBox(state).AttachTo("centermessage")
document?.getElementById("centermessage")?.classList?.add("pointer-events-none") document?.getElementById("centermessage")?.classList?.add("pointer-events-none")
// We have to ping the welcomeMessageIsOpened and other isOpened-stuff to activate the FullScreenMessage if needed
for (const state of guiState.allFullScreenStates) {
if (state.data) {
state.ping()
}
}
/**
* At last, if the map moves or an element is selected, we close all the panels just as well
*/
state.selectedElement.addCallbackAndRunD((_) => {
guiState.allFullScreenStates.forEach((s) => s.setData(false))
})
} }
private InitWelcomeMessage(): BaseUIElement { private InitWelcomeMessage(): BaseUIElement {
const isOpened = this.guiState.welcomeMessageIsOpened const isOpened = this.guiState.welcomeMessageIsOpened
new FullWelcomePaneWithTabs(isOpened, this.guiState.welcomeMessageOpenedTab, this.state) new FullWelcomePaneWithTabs(
isOpened,
this.guiState.welcomeMessageOpenedTab,
this.state,
this.guiState
)
// ?-Button on Desktop, opens panel with close-X. // ?-Button on Desktop, opens panel with close-X.
const help = new MapControlButton(Svg.help_svg()) const help = new MapControlButton(Svg.help_svg())

View file

@ -4,13 +4,22 @@ import Hash from "../Logic/Web/Hash"
export class DefaultGuiState { export class DefaultGuiState {
static state: DefaultGuiState static state: DefaultGuiState
public readonly welcomeMessageIsOpened: UIEventSource<boolean>
public readonly downloadControlIsOpened: UIEventSource<boolean> public readonly welcomeMessageIsOpened: UIEventSource<boolean> = new UIEventSource<boolean>(
public readonly filterViewIsOpened: UIEventSource<boolean> false
public readonly copyrightViewIsOpened: UIEventSource<boolean> )
public readonly currentViewControlIsOpened: UIEventSource<boolean> public readonly downloadControlIsOpened: UIEventSource<boolean> = new UIEventSource<boolean>(
false
)
public readonly filterViewIsOpened: UIEventSource<boolean> = new UIEventSource<boolean>(false)
public readonly copyrightViewIsOpened: UIEventSource<boolean> = new UIEventSource<boolean>(
false
)
public readonly currentViewControlIsOpened: UIEventSource<boolean> = new UIEventSource<boolean>(
false
)
public readonly userInfoIsOpened: UIEventSource<boolean> = new UIEventSource<boolean>(false)
public readonly welcomeMessageOpenedTab: UIEventSource<number> public readonly welcomeMessageOpenedTab: UIEventSource<number>
public readonly allFullScreenStates: UIEventSource<boolean>[] = []
constructor() { constructor() {
this.welcomeMessageOpenedTab = UIEventSource.asFloat( this.welcomeMessageOpenedTab = UIEventSource.asFloat(
@ -20,68 +29,19 @@ export class DefaultGuiState {
`The tab that is shown in the welcome-message.` `The tab that is shown in the welcome-message.`
) )
) )
this.welcomeMessageIsOpened = QueryParameters.GetBooleanQueryParameter( const sources = {
"welcome-control-toggle", welcome: this.welcomeMessageIsOpened,
false,
"Whether or not the welcome panel is shown"
)
this.downloadControlIsOpened = QueryParameters.GetBooleanQueryParameter(
"download-control-toggle",
false,
"Whether or not the download panel is shown"
)
this.filterViewIsOpened = QueryParameters.GetBooleanQueryParameter(
"filter-toggle",
false,
"Whether or not the filter view is shown"
)
this.copyrightViewIsOpened = QueryParameters.GetBooleanQueryParameter(
"copyright-toggle",
false,
"Whether or not the copyright view is shown"
)
this.currentViewControlIsOpened = QueryParameters.GetBooleanQueryParameter(
"currentview-toggle",
false,
"Whether or not the current view box is shown"
)
const states = {
download: this.downloadControlIsOpened, download: this.downloadControlIsOpened,
filters: this.filterViewIsOpened, filters: this.filterViewIsOpened,
copyright: this.copyrightViewIsOpened, copyright: this.copyrightViewIsOpened,
currentview: this.currentViewControlIsOpened, currentview: this.currentViewControlIsOpened,
welcome: this.welcomeMessageIsOpened, userinfo: this.userInfoIsOpened,
} }
Hash.hash.addCallbackAndRunD((hash) => {
hash = hash.toLowerCase() sources[Hash.hash.data?.toLowerCase()]?.setData(true)
states[hash]?.setData(true)
})
if (Hash.hash.data === "" || Hash.hash.data === undefined) { if (Hash.hash.data === "" || Hash.hash.data === undefined) {
this.welcomeMessageIsOpened.setData(true) this.welcomeMessageIsOpened.setData(true)
} }
this.allFullScreenStates.push(
this.downloadControlIsOpened,
this.filterViewIsOpened,
this.copyrightViewIsOpened,
this.welcomeMessageIsOpened,
this.currentViewControlIsOpened
)
for (let i = 0; i < this.allFullScreenStates.length; i++) {
const fullScreenState = this.allFullScreenStates[i]
for (let j = 0; j < this.allFullScreenStates.length; j++) {
if (i == j) {
continue
}
const otherState = this.allFullScreenStates[j]
fullScreenState.addCallbackAndRunD((isOpened) => {
if (isOpened) {
otherState.setData(false)
}
})
}
}
} }
} }

View file

@ -16,6 +16,7 @@ import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import { Changes } from "../../Logic/Osm/Changes" import { Changes } from "../../Logic/Osm/Changes"
import Loading from "../Base/Loading" import Loading from "../Base/Loading"
import {LoginToggle} from "../Popup/LoginButton";
export class ImageUploadFlow extends Toggle { export class ImageUploadFlow extends Toggle {
private static readonly uploadCountsPerId = new Map<string, UIEventSource<number>>() private static readonly uploadCountsPerId = new Map<string, UIEventSource<number>>()
@ -180,16 +181,12 @@ export class ImageUploadFlow extends Toggle {
chosenLicense.SetClass("subtle text-sm"), chosenLicense.SetClass("subtle text-sm"),
]).SetClass("flex flex-col image-upload-flow mt-4 mb-8 text-center") ]).SetClass("flex flex-col image-upload-flow mt-4 mb-8 text-center")
const pleaseLoginButton = t.pleaseLogin
.Clone()
.onClick(() => state.osmConnection.AttemptLogin())
.SetClass("login-button-friendly")
super( super(
new Toggle( new LoginToggle(
/*We can show the actual upload button!*/ /*We can show the actual upload button!*/
uploadFlow, uploadFlow,
/* User not logged in*/ pleaseLoginButton, /* User not logged in*/ t.pleaseLogin.Clone(),
state?.osmConnection?.isLoggedIn state
), ),
undefined /* Nothing as the user badge is disabled*/, undefined /* Nothing as the user badge is disabled*/,
state?.featureSwitchUserbadge state?.featureSwitchUserbadge

View file

@ -0,0 +1,42 @@
import { VariableUiElement } from "./Base/VariableUIElement"
import { OsmConnection } from "../Logic/Osm/OsmConnection"
import Svg from "../Svg"
import Img from "./Base/Img"
import Combine from "./Base/Combine"
import { FixedUiElement } from "./Base/FixedUiElement"
import BaseUIElement from "./BaseUIElement"
export default class LoggedInUserIndicator extends VariableUiElement {
constructor(
osmConnection: OsmConnection,
options?: {
size?: "small" | "medium" | "large"
firstLine?: BaseUIElement
}
) {
options = options ?? {}
let size = "w-8 h-8 mr-2"
if (options.size == "medium") {
size = "w-16 h-16 mr-4"
} else if (options.size == "large") {
size = "w-32 h-32 mr-6"
}
super(
osmConnection.userDetails.mapD((ud) => {
let img = Svg.person_svg().SetClass(
"rounded-full border border-black overflow-hidden"
)
if (ud.img) {
img = new Img(ud.img)
}
let contents: BaseUIElement = new FixedUiElement(ud.name).SetClass("font-bold")
if (options?.firstLine) {
contents = new Combine([options.firstLine, contents]).SetClass("flex flex-col")
}
return new Combine([img.SetClass("rounded-full " + size), contents]).SetClass(
"flex items-center"
)
})
)
}
}

View file

@ -23,6 +23,7 @@ import { SubstitutedTranslation } from "../SubstitutedTranslation"
import FeaturePipelineState from "../../Logic/State/FeaturePipelineState" import FeaturePipelineState from "../../Logic/State/FeaturePipelineState"
import TagRenderingQuestion from "./TagRenderingQuestion" import TagRenderingQuestion from "./TagRenderingQuestion"
import { OsmId } from "../../Models/OsmFeature" import { OsmId } from "../../Models/OsmFeature"
import { LoginToggle } from "./LoginButton"
export default class DeleteWizard extends Toggle { export default class DeleteWizard extends Toggle {
/** /**
@ -122,6 +123,40 @@ export default class DeleteWizard extends Toggle {
]).SetClass("flex mt-2 justify-between"), ]).SetClass("flex mt-2 justify-between"),
]).SetClass("question") ]).SetClass("question")
const deleteFlow = new Toggle(
new Toggle(
new Toggle(
deleteDialog,
new SubtleButton(Svg.envelope_ui(), t.readMessages),
state.osmConnection.userDetails.map(
(ud) =>
ud.csCount >
Constants.userJourney.addNewPointWithUnreadMessagesUnlock ||
ud.unreadMessages == 0
)
),
deleteButton,
confirm
),
new VariableUiElement(
deleteAbility.canBeDeleted.map((cbd) =>
new Combine([
Svg.delete_not_allowed_svg()
.SetStyle("height: 2rem; width: auto")
.SetClass("mr-2"),
new Combine([
t.cannotBeDeleted,
cbd.reason.SetClass("subtle"),
t.useSomethingElse.SetClass("subtle"),
]).SetClass("flex flex-col"),
]).SetClass("flex m-2 p-2 rounded-lg bg-gray-200 bg-gray-200")
)
),
deleteAbility.canBeDeleted.map((cbd) => allowSoftDeletion || cbd.canBeDeleted !== false)
)
super( super(
new Toggle( new Toggle(
new Combine([ new Combine([
@ -130,52 +165,19 @@ export default class DeleteWizard extends Toggle {
), ),
t.isDeleted, t.isDeleted,
]).SetClass("flex m-2 rounded-full"), ]).SetClass("flex m-2 rounded-full"),
new Toggle( new LoginToggle(deleteFlow, undefined, state),
new Toggle(
new Toggle(
new Toggle(
deleteDialog,
new SubtleButton(Svg.envelope_ui(), t.readMessages),
state.osmConnection.userDetails.map(
(ud) =>
ud.csCount >
Constants.userJourney
.addNewPointWithUnreadMessagesUnlock ||
ud.unreadMessages == 0
)
),
deleteButton,
confirm
),
new VariableUiElement(
deleteAbility.canBeDeleted.map((cbd) =>
new Combine([
Svg.delete_not_allowed_svg()
.SetStyle("height: 2rem; width: auto")
.SetClass("mr-2"),
new Combine([
t.cannotBeDeleted,
cbd.reason.SetClass("subtle"),
t.useSomethingElse.SetClass("subtle"),
]).SetClass("flex flex-col"),
]).SetClass("flex m-2 p-2 rounded-lg bg-gray-200 bg-gray-200")
)
),
deleteAbility.canBeDeleted.map(
(cbd) => allowSoftDeletion || cbd.canBeDeleted !== false
)
),
t.loginToDelete.onClick(state.osmConnection.AttemptLogin),
state.osmConnection.isLoggedIn
),
isDeleted isDeleted
), ),
undefined, undefined,
isShown isShown
) )
const self = this
confirm.addCallbackAndRunD((dialogIsOpened) => {
if (dialogIsOpened) {
self.ScrollIntoView()
}
})
} }
private static constructConfirmButton( private static constructConfirmButton(

View file

@ -88,7 +88,7 @@ export class LanguageElement implements SpecialVisualization {
if (mode === undefined || mode.length == 0) { if (mode === undefined || mode.length == 0) {
mode = "multi" mode = "multi"
} }
if (item_render === undefined) { if (item_render === undefined || item_render.trim() === "") {
item_render = "{language()}" item_render = "{language()}"
} }
if (all_render === undefined || all_render.length == 0) { if (all_render === undefined || all_render.length == 0) {
@ -100,8 +100,17 @@ export class LanguageElement implements SpecialVisualization {
mode mode
) )
} }
if (single_render.indexOf("{language()") < 0 || item_render.indexOf("{language()") < 0) { if (single_render.indexOf("{language()") < 0) {
throw "Error while calling language_chooser: render_single_language and render_list_item must contain '{language()}'" throw (
"Error while calling language_chooser: render_single_language must contain '{language()}' but it is " +
single_render
)
}
if (item_render.indexOf("{language()") < 0) {
throw (
"Error while calling language_chooser: render_list_item must contain '{language()}' but it is " +
item_render
)
} }
if (all_render.indexOf("{list()") < 0) { if (all_render.indexOf("{list()") < 0) {
throw "Error while calling language_chooser: render_all must contain '{list()}'" throw "Error while calling language_chooser: render_all must contain '{list()}'"

View file

@ -1,11 +1,13 @@
import { SubtleButton } from "../Base/SubtleButton" import { SubtleButton } from "../Base/SubtleButton"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import Svg from "../../Svg" import Svg from "../../Svg"
import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { OsmConnection, OsmServiceState } from "../../Logic/Osm/OsmConnection"
import Toggle from "../Input/Toggle"
import { VariableUiElement } from "../Base/VariableUIElement" import { VariableUiElement } from "../Base/VariableUIElement"
import Loading from "../Base/Loading" import Loading from "../Base/Loading"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import { Store } from "../../Logic/UIEventSource"
import Combine from "../Base/Combine"
import { Translation } from "../i18n/Translation"
class LoginButton extends SubtleButton { class LoginButton extends SubtleButton {
constructor( constructor(
@ -23,30 +25,67 @@ class LoginButton extends SubtleButton {
} }
export class LoginToggle extends VariableUiElement { export class LoginToggle extends VariableUiElement {
/**
* Constructs an element which shows 'el' if the user is logged in
* If not logged in, 'text' is shown on the button which invites to login.
*
* If logging in is not possible for some reason, an appropriate error message is shown
*
* State contains the 'osmConnection' to work with
*/
constructor( constructor(
el: BaseUIElement, el: BaseUIElement,
text: BaseUIElement | string, text: BaseUIElement | string,
state: { state: {
osmConnection: OsmConnection readonly osmConnection: OsmConnection
readonly featureSwitchUserbadge: Store<boolean>
} }
) { ) {
const loading = new Loading("Trying to log in...") const loading = new Loading("Trying to log in...")
const login = new LoginButton(text, state) const login = text === undefined ? undefined : new LoginButton(text, state)
super( const t = Translations.t.general
state.osmConnection.loadingStatus.map((osmConnectionState) => { const offlineModes: Partial<Record<OsmServiceState, Translation>> = {
if (osmConnectionState === "loading") { offline: t.loginFailedOfflineMode,
return loading unreachable: t.loginFailedUnreachableMode,
} readonly: t.loginFailedReadonlyMode,
if (osmConnectionState === "not-attempted") { }
return login
}
if (osmConnectionState === "logged-in") {
return el
}
// Error! super(
return new LoginButton(Translations.t.general.loginFailed, state, Svg.invalid_svg()) state.osmConnection.loadingStatus.map(
}) (osmConnectionState) => {
if (state.featureSwitchUserbadge.data == false) {
// All features to login with are disabled
return undefined
}
const apiState = state.osmConnection.apiIsOnline.data
const apiTranslation = offlineModes[apiState]
if (apiTranslation !== undefined) {
return new Combine([
Svg.invalid_svg().SetClass("w-8 h-8 m-2 shrink-0"),
apiTranslation,
]).SetClass("flex items-center alert max-w-64")
}
if (osmConnectionState === "loading") {
return loading
}
if (osmConnectionState === "not-attempted") {
return login
}
if (osmConnectionState === "logged-in") {
return el
}
// Error!
return new LoginButton(
Translations.t.general.loginFailed,
state,
Svg.invalid_svg()
)
},
[state.featureSwitchUserbadge, state.osmConnection.apiIsOnline]
)
) )
} }
} }

View file

@ -23,6 +23,7 @@ import SearchAndGo from "../BigComponents/SearchAndGo"
import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction" import ChangeTagAction from "../../Logic/Osm/Actions/ChangeTagAction"
import { And } from "../../Logic/Tags/And" import { And } from "../../Logic/Tags/And"
import { Tag } from "../../Logic/Tags/Tag" import { Tag } from "../../Logic/Tags/Tag"
import { LoginToggle } from "./LoginButton"
interface MoveReason { interface MoveReason {
text: Translation | string text: Translation | string
@ -220,7 +221,7 @@ export default class MoveWizard extends Toggle {
const dialogClasses = "p-2 md:p-4 m-2 border border-gray-400 rounded-xl flex flex-col" const dialogClasses = "p-2 md:p-4 m-2 border border-gray-400 rounded-xl flex flex-col"
const moveFlow = new Toggle( const moveFlow = new LoginToggle(
new VariableUiElement( new VariableUiElement(
currentStep.map((currentStep) => { currentStep.map((currentStep) => {
switch (currentStep) { switch (currentStep) {
@ -246,8 +247,8 @@ export default class MoveWizard extends Toggle {
} }
}) })
), ),
loginButton, undefined,
state.osmConnection.isLoggedIn state
) )
let id = featureToMove.properties.id let id = featureToMove.properties.id
const backend = state.osmConnection._oauth_config.url const backend = state.osmConnection._oauth_config.url
@ -284,5 +285,13 @@ export default class MoveWizard extends Toggle {
]).SetClass("flex m-2 p-2 rounded-lg bg-gray-200"), ]).SetClass("flex m-2 p-2 rounded-lg bg-gray-200"),
moveDisallowedReason.map((r) => r === undefined) moveDisallowedReason.map((r) => r === undefined)
) )
const self = this
currentStep.addCallback((state) => {
if (state === "start") {
return
}
self.ScrollIntoView()
})
} }
} }

View file

@ -7,6 +7,7 @@ import { VariableUiElement } from "../Base/VariableUIElement"
import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig" import TagRenderingConfig from "../../Models/ThemeConfig/TagRenderingConfig"
import { Unit } from "../../Models/Unit" import { Unit } from "../../Models/Unit"
import Lazy from "../Base/Lazy" import Lazy from "../Base/Lazy"
import { OsmServiceState } from "../../Logic/Osm/OsmConnection"
/** /**
* Generates all the questions, one by one * Generates all the questions, one by one
@ -119,30 +120,34 @@ export default class QuestionBox extends VariableUiElement {
) )
super( super(
questionsToAsk.map((allQuestions) => { questionsToAsk.map(
const els: BaseUIElement[] = [] (allQuestions) => {
if ( const apiState: OsmServiceState = state.osmConnection.apiIsOnline.data
options.showAllQuestionsAtOnce === true || if (apiState !== "online" && apiState !== "unknown") {
options.showAllQuestionsAtOnce["data"] return undefined
) { }
els.push(...questionsToAsk.data) const els: BaseUIElement[] = []
} else { if (
els.push(allQuestions[0]) options.showAllQuestionsAtOnce === true ||
} options.showAllQuestionsAtOnce["data"]
) {
els.push(...questionsToAsk.data)
} else {
els.push(allQuestions[0])
}
if (skippedQuestions.data.length > 0) { if (skippedQuestions.data.length > 0) {
els.push(skippedQuestionsButton) els.push(skippedQuestionsButton)
} }
return new Combine(els).SetClass("block mb-8") return new Combine(els).SetClass("block mb-8")
}) },
[state.osmConnection.apiIsOnline]
)
) )
this.skippedQuestions = skippedQuestions this.skippedQuestions = skippedQuestions
this.restingQuestions = questionsToAsk this.restingQuestions = questionsToAsk
focus = () => focus = () => this.ScrollIntoView()
this.ScrollIntoView({
onlyIfPartiallyHidden: true,
})
} }
} }

View file

@ -24,6 +24,8 @@ import BaseLayer from "../../Models/BaseLayer"
import FilteredLayer from "../../Models/FilteredLayer" import FilteredLayer from "../../Models/FilteredLayer"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import { VariableUiElement } from "../Base/VariableUIElement" import { VariableUiElement } from "../Base/VariableUIElement"
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
import { LoginToggle } from "./LoginButton"
export default class SplitRoadWizard extends Combine { export default class SplitRoadWizard extends Combine {
// @ts-ignore // @ts-ignore
@ -54,6 +56,7 @@ export default class SplitRoadWizard extends Combine {
changes: Changes changes: Changes
layoutToUse: LayoutConfig layoutToUse: LayoutConfig
allElements: ElementStorage allElements: ElementStorage
selectedElement: UIEventSource<any>
} }
) { ) {
const t = Translations.t.split const t = Translations.t.split
@ -79,16 +82,8 @@ export default class SplitRoadWizard extends Combine {
hasBeenSplit hasBeenSplit
) )
) )
splitButton.onClick(() => {
splitClicked.setData(true)
})
// Only show the splitButton if logged in, else show login prompt const splitToggle = new LoginToggle(splitButton, t.loginToSplit.Clone(), state)
const loginBtn = t.loginToSplit
.Clone()
.onClick(() => state.osmConnection.AttemptLogin())
.SetClass("login-button-friendly")
const splitToggle = new Toggle(splitButton, loginBtn, state.osmConnection.isLoggedIn)
// Save button // Save button
const saveButton = new Button(t.split.Clone(), async () => { const saveButton = new Button(t.split.Clone(), async () => {
@ -110,10 +105,13 @@ export default class SplitRoadWizard extends Combine {
// We throw away the old map and splitpoints, and create a new map from scratch // We throw away the old map and splitpoints, and create a new map from scratch
splitPoints.setData([]) splitPoints.setData([])
leafletMap.setData(SplitRoadWizard.setupMapComponent(id, splitPoints, state)) leafletMap.setData(SplitRoadWizard.setupMapComponent(id, splitPoints, state))
// Close the popup. The contributor has to select a segment again to make sure they continue editing the correct segment; see #1219
ScrollableFullScreen.collapse()
}) })
saveButton.SetClass("btn btn-primary mr-3") saveButton.SetClass("btn btn-primary mr-3")
const disabledSaveButton = new Button("Split", undefined) const disabledSaveButton = new Button(t.split.Clone(), undefined)
disabledSaveButton.SetClass("btn btn-disabled mr-3") disabledSaveButton.SetClass("btn btn-disabled mr-3")
// Only show the save button if there are split points defined // Only show the save button if there are split points defined
const saveToggle = new Toggle( const saveToggle = new Toggle(
@ -147,6 +145,11 @@ export default class SplitRoadWizard extends Combine {
new Toggle(mapView, splitToggle, splitClicked), new Toggle(mapView, splitToggle, splitClicked),
]) ])
this.dialogIsOpened = splitClicked this.dialogIsOpened = splitClicked
const self = this
splitButton.onClick(() => {
splitClicked.setData(true)
self.ScrollIntoView()
})
} }
private static setupMapComponent( private static setupMapComponent(

View file

@ -1,6 +1,6 @@
import { InputElement } from "../Input/InputElement" import { InputElement } from "../Input/InputElement"
import { Review } from "../../Logic/Web/Review" import { Review } from "../../Logic/Web/Review"
import { UIEventSource } from "../../Logic/UIEventSource" import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { TextField } from "../Input/TextField" import { TextField } from "../Input/TextField"
import Translations from "../i18n/Translations" import Translations from "../i18n/Translations"
import Combine from "../Base/Combine" import Combine from "../Base/Combine"
@ -11,6 +11,7 @@ import CheckBoxes from "../Input/Checkboxes"
import UserDetails, { OsmConnection } from "../../Logic/Osm/OsmConnection" import UserDetails, { OsmConnection } from "../../Logic/Osm/OsmConnection"
import BaseUIElement from "../BaseUIElement" import BaseUIElement from "../BaseUIElement"
import Toggle from "../Input/Toggle" import Toggle from "../Input/Toggle"
import { LoginToggle } from "../Popup/LoginButton"
export default class ReviewForm extends InputElement<Review> { export default class ReviewForm extends InputElement<Review> {
IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false) IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false)
@ -20,11 +21,21 @@ export default class ReviewForm extends InputElement<Review> {
private _saveButton: BaseUIElement private _saveButton: BaseUIElement
private readonly _isAffiliated: BaseUIElement private readonly _isAffiliated: BaseUIElement
private readonly _postingAs: BaseUIElement private readonly _postingAs: BaseUIElement
private readonly _osmConnection: OsmConnection private readonly _state: {
readonly osmConnection: OsmConnection
readonly featureSwitchUserbadge: Store<boolean>
}
constructor(onSave: (r: Review, doneSaving: () => void) => void, osmConnection: OsmConnection) { constructor(
onSave: (r: Review, doneSaving: () => void) => void,
state: {
readonly osmConnection: OsmConnection
readonly featureSwitchUserbadge: Store<boolean>
}
) {
super() super()
this._osmConnection = osmConnection this._state = state
const osmConnection = state.osmConnection
this._value = new UIEventSource({ this._value = new UIEventSource({
made_by_user: new UIEventSource<boolean>(true), made_by_user: new UIEventSource<boolean>(true),
rating: undefined, rating: undefined,
@ -112,12 +123,11 @@ export default class ReviewForm extends InputElement<Review> {
" border: 2px solid var(--subtle-detail-color-contrast)" " border: 2px solid var(--subtle-detail-color-contrast)"
) )
const connection = this._osmConnection return new LoginToggle(
const login = Translations.t.reviews.plz_login form,
.Clone() Translations.t.reviews.plz_login.Clone(),
.onClick(() => connection.AttemptLogin()) this._state
).ConstructElement()
return new Toggle(form, login, connection.isLoggedIn).ConstructElement()
} }
IsValid(r: Review): boolean { IsValid(r: Review): boolean {

View file

@ -4,8 +4,10 @@ import { ShowDataLayerOptions } from "./ShowDataLayerOptions"
import { ElementStorage } from "../../Logic/ElementStorage" import { ElementStorage } from "../../Logic/ElementStorage"
import RenderingMultiPlexerFeatureSource from "../../Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource" import RenderingMultiPlexerFeatureSource from "../../Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource"
import ScrollableFullScreen from "../Base/ScrollableFullScreen" import ScrollableFullScreen from "../Base/ScrollableFullScreen"
import { LeafletMouseEvent } from "leaflet" import { LeafletMouseEvent, PathOptions } from "leaflet"
import Hash from "../../Logic/Web/Hash" import Hash from "../../Logic/Web/Hash"
import { BBox } from "../../Logic/BBox"
import { Utils } from "../../Utils"
/* /*
// import 'leaflet-polylineoffset'; // import 'leaflet-polylineoffset';
We don't actually import it here. It is imported in the 'MinimapImplementation'-class, which'll result in a patched 'L' object. We don't actually import it here. It is imported in the 'MinimapImplementation'-class, which'll result in a patched 'L' object.
@ -47,6 +49,7 @@ export default class ShowDataLayerImplementation {
string, string,
{ feature: any; activateFunc: (event: LeafletMouseEvent) => void } { feature: any; activateFunc: (event: LeafletMouseEvent) => void }
>() >()
private readonly showDataLayerid: number private readonly showDataLayerid: number
private readonly createPopup: ( private readonly createPopup: (
tags: UIEventSource<any>, tags: UIEventSource<any>,
@ -81,7 +84,7 @@ export default class ShowDataLayerImplementation {
} }
const self = this const self = this
options.leafletMap.addCallback((_) => { options.leafletMap.addCallback(() => {
return self.update(options) return self.update(options)
}) })
@ -112,6 +115,10 @@ export default class ShowDataLayerImplementation {
}) })
this._selectedElement?.addCallbackAndRunD((selected) => { this._selectedElement?.addCallbackAndRunD((selected) => {
if (selected === undefined) {
ScrollableFullScreen.collapse()
return
}
self.openPopupOfSelectedElement(selected) self.openPopupOfSelectedElement(selected)
}) })
@ -171,17 +178,8 @@ export default class ShowDataLayerImplementation {
} }
const self = this const self = this
const data = {
type: "FeatureCollection", this.geoLayer = new L.LayerGroup()
features: [],
}
// @ts-ignore
this.geoLayer = L.geoJSON(data, {
style: (feature) => self.createStyleFor(feature),
pointToLayer: (feature, latLng) => self.pointToLayer(feature, latLng),
onEachFeature: (feature, leafletLayer) =>
self.postProcessFeature(feature, leafletLayer),
})
const selfLayer = this.geoLayer const selfLayer = this.geoLayer
const allFeats = this._features.features.data const allFeats = this._features.features.data
@ -189,6 +187,31 @@ export default class ShowDataLayerImplementation {
if (feat === undefined) { if (feat === undefined) {
continue continue
} }
// Why not one geojson layer with _all_ features, and attaching a right-click onto every feature individually?
// Because that somehow doesn't work :(
const feature = feat
const geojsonLayer = L.geoJSON(feature, {
style: (feature) => <PathOptions>self.createStyleFor(feature),
pointToLayer: (feature, latLng) => self.pointToLayer(feature, latLng),
onEachFeature: (feature, leafletLayer) =>
self.postProcessFeature(feature, leafletLayer),
})
if (feature.geometry.type === "Point") {
geojsonLayer.on({
contextmenu: (e) => {
const o = self.leafletLayersPerId.get(feature?.properties?.id)
o?.activateFunc(<LeafletMouseEvent>e)
Utils.preventDefaultOnMouseEvent(e.originalEvent)
},
dblclick: (e) => {
const o = self.leafletLayersPerId.get(feature?.properties?.id)
o?.activateFunc(<LeafletMouseEvent>e)
Utils.preventDefaultOnMouseEvent(e.originalEvent)
},
})
}
this.geoLayer.addLayer(geojsonLayer)
try { try {
if (feat.geometry.type === "LineString") { if (feat.geometry.type === "LineString") {
const coords = L.GeoJSON.coordsToLatLngs(feat.geometry.coordinates) const coords = L.GeoJSON.coordsToLatLngs(feat.geometry.coordinates)
@ -229,7 +252,7 @@ export default class ShowDataLayerImplementation {
return self.geoLayer !== selfLayer return self.geoLayer !== selfLayer
}) })
} else { } else {
this.geoLayer.addData(feat) geojsonLayer.addData(feat)
} }
} catch (e) { } catch (e) {
console.error( console.error(
@ -242,14 +265,14 @@ export default class ShowDataLayerImplementation {
} }
} }
if (options.zoomToFeatures ?? false) { if ((options.zoomToFeatures ?? false) && allFeats.length > 0) {
if (this.geoLayer.getLayers().length > 0) { let bound = undefined
try { for (const feat of allFeats) {
const bounds = this.geoLayer.getBounds() const fbound = BBox.get(feat)
mp.fitBounds(bounds, { animate: false }) bound = bound?.unionWith(fbound) ?? fbound
} catch (e) { }
console.debug("Invalid bounds", e) if (bound !== undefined) {
} mp.fitBounds(bound?.toLeaflet(), { animate: false })
} }
} }
@ -312,29 +335,7 @@ export default class ShowDataLayerImplementation {
icon: L.divIcon(style), icon: L.divIcon(style),
}) })
} }
private createActivateFunction(feature, key: string, layer: LayerConfig): (event) => void {
/**
* Post processing - basically adding the popup
* @param feature
* @param leafletLayer
* @private
*/
private postProcessFeature(feature, leafletLayer: L.Evented) {
const layer: LayerConfig = this._layerToShow
if (layer.title === undefined || !this._enablePopups) {
// No popup action defined -> Don't do anything
// or probably a map in the popup - no popups needed!
return
}
const key = feature.properties.id
if (this.leafletLayersPerId.has(key)) {
const activate = this.leafletLayersPerId.get(key)
leafletLayer.addEventListener("click", activate.activateFunc)
if (Hash.hash.data === key) {
activate.activateFunc(null)
}
return
}
let infobox: ScrollableFullScreen = undefined let infobox: ScrollableFullScreen = undefined
const self = this const self = this
@ -354,17 +355,36 @@ export default class ShowDataLayerImplementation {
self._selectedElement.setData( self._selectedElement.setData(
self.allElements.ContainingFeatures.get(feature.id) ?? feature self.allElements.ContainingFeatures.get(feature.id) ?? feature
) )
event?.originalEvent?.preventDefault() }
event?.originalEvent?.stopPropagation() return activate
event?.originalEvent?.stopImmediatePropagation() }
if (event?.originalEvent) { /**
// This is a total workaround, as 'preventDefault' and everything above seems to be not working * Post processing - basically adding the popup
event.originalEvent["dismissed"] = true * @param feature
} * @param leafletLayer
* @private
*/
private postProcessFeature(feature, leafletLayer: L.Evented) {
const layer: LayerConfig = this._layerToShow
if (layer.title === undefined || !this._enablePopups) {
// No popup action defined -> Don't do anything
// or probably a map in the popup - no popups needed!
return
}
const key = feature.properties.id
let activate: (event) => void
if (this.leafletLayersPerId.has(key)) {
activate = this.leafletLayersPerId.get(key).activateFunc
} else {
activate = this.createActivateFunction(feature, key, layer)
} }
leafletLayer.addEventListener("click", activate) // We also have to open on rightclick, doubleclick, ... as users sometimes do this. See #1219
leafletLayer.on({
dblclick: activate,
contextmenu: activate,
click: activate,
})
// Add the feature to the index to open the popup when needed // Add the feature to the index to open the popup when needed
this.leafletLayersPerId.set(key, { this.leafletLayersPerId.set(key, {
feature: feature, feature: feature,

View file

@ -219,7 +219,7 @@ export default class SpecialVisualizations {
) )
const form = new ReviewForm( const form = new ReviewForm(
(r, whenDone) => mangrove.AddReview(r, whenDone), (r, whenDone) => mangrove.AddReview(r, whenDone),
state.osmConnection state
) )
return new ReviewElement(mangrove.GetSubjectUri(), mangrove.GetReviews(), form) return new ReviewElement(mangrove.GetSubjectUri(), mangrove.GetReviews(), form)
}, },

View file

@ -42,7 +42,13 @@ export default class Translations {
* *
*/ */
static T( static T(
t: string | undefined | null | Translation | TypedTranslation<object>, t:
| string
| Record<string, string>
| undefined
| null
| Translation
| TypedTranslation<object>,
context = undefined context = undefined
): TypedTranslation<object> { ): TypedTranslation<object> {
if (t === undefined || t === null) { if (t === undefined || t === null) {

View file

@ -900,7 +900,7 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
url: string, url: string,
maxCacheTimeMs: number, maxCacheTimeMs: number,
headers?: any headers?: any
): Promise<any | { error: string; url: string; statuscode?: number }> { ): Promise<{ content: any } | { error: string; url: string; statuscode?: number }> {
const cached = Utils._download_cache.get(url) const cached = Utils._download_cache.get(url)
if (cached !== undefined) { if (cached !== undefined) {
if (new Date().getTime() - cached.timestamp <= maxCacheTimeMs) { if (new Date().getTime() - cached.timestamp <= maxCacheTimeMs) {
@ -1074,6 +1074,16 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be
) )
} }
public static preventDefaultOnMouseEvent(event: any) {
event?.originalEvent?.preventDefault()
event?.originalEvent?.stopPropagation()
event?.originalEvent?.stopImmediatePropagation()
if (event?.originalEvent) {
// This is a total workaround, as 'preventDefault' and everything above seems to be not working
event.originalEvent["dismissed"] = true
}
}
public static OsmChaLinkFor(daysInThePast, theme = undefined): string { public static OsmChaLinkFor(daysInThePast, theme = undefined): string {
const now = new Date() const now = new Date()
const lastWeek = new Date(now.getTime() - daysInThePast * 24 * 60 * 60 * 1000) const lastWeek = new Date(now.getTime() - daysInThePast * 24 * 60 * 60 * 1000)

View file

@ -32,17 +32,7 @@
] ]
}, },
"source": { "source": {
"osmTags": { "osmTags": "amenity=atm"
"or": [
"amenity=atm",
{
"and": [
"amenity=bank",
"atm=yes"
]
}
]
}
}, },
"minzoom": 13, "minzoom": 13,
"presets": [ "presets": [
@ -59,26 +49,7 @@
} }
], ],
"tagRenderings": [ "tagRenderings": [
{ "images",
"builtin": "images",
"override": {
"condition": "amenity!=bank"
}
},
{
"id": "bank-images",
"render": "{image_carousel()}",
"condition": "amenity=bank"
},
{
"id": "atm-in-bank-notice",
"condition": "amenity=bank",
"render": {
"en": "This ATM is located in or near a bank",
"de": "Dieser Geldautomat befindet sich in oder in der Nähe einer Bank",
"nl": "Deze geldautomaat bevindt zich in of bij een bank"
}
},
{ {
"id": "name", "id": "name",
"render": { "render": {
@ -140,12 +111,7 @@
"nl": "Deze geldautomaat wordt beheerd door {operator}" "nl": "Deze geldautomaat wordt beheerd door {operator}"
} }
}, },
{ "opening_hours",
"builtin": "opening_hours",
"override": {
"condition": "amenity!=bank"
}
},
{ {
"id": "cash_out", "id": "cash_out",
"question": { "question": {
@ -276,6 +242,18 @@
] ]
} }
], ],
"allowMove": {
"enableImproveAccuracy": true,
"enableRelocation": false
},
"deletion": {
"softDeletionTags": {
"and": [
"disused:amenity=atm",
"amenity="
]
}
},
"filter": [ "filter": [
"open_now", "open_now",
{ {

View file

@ -0,0 +1,70 @@
{
"id": "bank",
"description": {
"en": "A financial institution to deposit money"
},
"name": {
"en": "Banks"
},
"title": {
"render": "Bank",
"mappings": [
{
"if": "name~*",
"then": "{name}"
}
]
},
"source": {
"osmTags": "amenity=bank"
},
"mapRendering": [
{
"icon": "circle:white;./assets/layers/bank/bank.svg",
"location": [
"point",
"centroid"
]
}
],
"tagRenderings": [
{
"id": "has_atm",
"question": {
"en": "Does this bank have an ATM?"
},
"mappings": [
{
"if": "atm=yes",
"then": {
"en": "This bank has an ATM"
}
},
{
"if": "atm=no",
"then": {
"en": "This bank does <b>not</b> have an ATM"
}
},
{
"if": "atm=separate",
"then": {
"en": "This bank does have an ATM, but it is mapped as a different icon"
}
}
]
}
],
"filter": [
"open_now",
{
"id": "has_atm",
"options": [{
"question": {
"en": "With an ATM"
},
"osmTags": "atm=yes"
}]
}
]
}

View file

@ -0,0 +1,22 @@
<svg xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.1"
width="16"
height="16"
viewBox="0 0 16 16"
id="svg2"> <metadata id="metadata8">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<defs id="defs6"/>
<rect width="16" height="16" x="0" y="0" id="canvas" style="fill:none;stroke:none;visibility:hidden"/>
<path d="m 0,2 0,7 c 0,0.627119 0.423729,1 1,1 l 12,0 c 0.576271,0 1,-0.423729 1,-1 L 14,2 0,2 z m 3,1 8,0 c 0,1 1,2 2,2 l 0,2 C 12,7 11,8 11,9 L 3,9 C 3,8 2,7 1,7 L 1,5 C 2,5 3,4 3,3 z M 7,4 C 5.343146,4 4,4.8954305 4,6 4,7.10457 5.343146,8 7,8 8.656855,8 10,7.10457 10,6 10,4.8954305 8.656855,4 7,4 z M 0,10.5 C 0,11.626577 0.448696,12 1,12 l 12,0 c 0.551304,0 1,-0.445333 1,-1.5 -0.288136,0.271186 -0.559322,0.5 -1,0.5 L 1,11 C 0.559322,11 0.271186,10.754237 0,10.5 z" id="bank" style="fill:#734a08;fill-opacity:1;stroke:none" transform="translate(1,1)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,12 @@
[
{
"path": "bank.svg",
"license": "CC0",
"authors": [
"nebulon42"
],
"sources": [
"https://github.com/gmgeo/osmic/blob/master/money/bank-14.svg"
]
}
]

View file

@ -226,5 +226,17 @@
] ]
} }
} }
],
"presets": [
{
"title": {
"en": "Climbing gym",
"nl": "Klimzaal"
},
"tags": [
"leisure=sports_centre",
"sport=climbing"
]
}
] ]
} }

View file

@ -205,6 +205,7 @@
"elevator:width", "elevator:width",
"elevator:depth" "elevator:depth"
], ],
"defaultInput": "cm",
"applicableUnits": [ "applicableUnits": [
{ {
"canonicalDenomination": "m", "canonicalDenomination": "m",
@ -221,7 +222,6 @@
} }
}, },
{ {
"useAsDefaultInput": true,
"canonicalDenomination": "cm", "canonicalDenomination": "cm",
"alternativeDenomination": [ "alternativeDenomination": [
"centimeter", "centimeter",

View file

@ -473,6 +473,7 @@
"kerb:height", "kerb:height",
"width" "width"
], ],
"defaultInput": "cm",
"applicableUnits": [ "applicableUnits": [
{ {
"useIfNoUnitGiven": true, "useIfNoUnitGiven": true,
@ -489,7 +490,6 @@
} }
}, },
{ {
"useAsDefaultInput": true,
"canonicalDenomination": "cm", "canonicalDenomination": "cm",
"alternativeDenomination": [ "alternativeDenomination": [
"centimeter", "centimeter",

View file

@ -63,6 +63,7 @@
"width", "width",
"_biggest_width" "_biggest_width"
], ],
"defaultUnit": "cm",
"applicableUnits": [ "applicableUnits": [
{ {
"useIfNoUnitGiven": true, "useIfNoUnitGiven": true,
@ -79,7 +80,6 @@
} }
}, },
{ {
"useAsDefaultInput": true,
"canonicalDenomination": "cm", "canonicalDenomination": "cm",
"alternativeDenomination": [ "alternativeDenomination": [
"centimeter", "centimeter",

File diff suppressed because it is too large Load diff

View file

@ -284,7 +284,7 @@
"da": "Hvad er webstedet for {title()}?", "da": "Hvad er webstedet for {title()}?",
"cs": "Jaká je webová stránka {title()}?" "cs": "Jaká je webová stránka {title()}?"
}, },
"render": "<a href='{website}' target='_blank'>{website}</a>", "render": "<a href='{website}' rel='nofollow noopener noreferrer' target='_blank'>{website}</a>",
"freeform": { "freeform": {
"key": "website", "key": "website",
"type": "url", "type": "url",
@ -295,7 +295,7 @@
"mappings": [ "mappings": [
{ {
"if": "contact:website~*", "if": "contact:website~*",
"then": "<a href='{contact:website}' target='_blank'>{contact:website}</a>", "then": "<a href='{contact:website}' rel='nofollow noopener noreferrer' target='_blank'>{contact:website}</a>",
"hideInAnswer": true "hideInAnswer": true
} }
] ]

View file

@ -17,6 +17,32 @@
"startLon": 0, "startLon": 0,
"startZoom": 0, "startZoom": 0,
"layers": [ "layers": [
"atm" "atm",
{
"builtin": "bank",
"override": {
"id": "banks_with_atm",
"name": null,
"source": {
"osmTags": {
"and+": [
"atm=yes"
]
}
},
"filter": [
"open_now"
]
}
},
{
"builtin": "bank",
"override": {
"minzoom": 18,
"filter": {
"sameAs": "bank"
}
}
}
] ]
} }

View file

@ -22,8 +22,11 @@
{ {
"builtin": "cycleways_and_roads", "builtin": "cycleways_and_roads",
"override": { "override": {
"mapRendering": null, "title": null,
"title": null "forceLoad": true,
"minzoom": 18,
"passAllFeatures": true,
"shownByDefault": false
} }
}, },
{ {

View file

@ -551,7 +551,9 @@
"centroid" "centroid"
] ]
} }
] ],
"deletion": true,
"allowMove": { "enableImproveAccuracy": true, "enableRelocation": false }
} }
], ],
"credits": "joost schouppe; stla" "credits": "joost schouppe; stla"

View file

@ -811,14 +811,22 @@ video {
margin-top: 0.25rem; margin-top: 0.25rem;
} }
.mt-4 {
margin-top: 1rem;
}
.mr-2 { .mr-2 {
margin-right: 0.5rem; margin-right: 0.5rem;
} }
.mr-4 {
margin-right: 1rem;
}
.mr-6 {
margin-right: 1.5rem;
}
.mt-4 {
margin-top: 1rem;
}
.ml-4 { .ml-4 {
margin-left: 1rem; margin-left: 1rem;
} }
@ -827,10 +835,6 @@ video {
margin-bottom: 6rem; margin-bottom: 6rem;
} }
.mr-4 {
margin-right: 1rem;
}
.mb-2 { .mb-2 {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
} }
@ -964,6 +968,18 @@ video {
height: 16rem; height: 16rem;
} }
.h-8 {
height: 2rem;
}
.h-16 {
height: 4rem;
}
.h-32 {
height: 8rem;
}
.h-10 { .h-10 {
height: 2.5rem; height: 2.5rem;
} }
@ -972,6 +988,10 @@ video {
height: 3rem; height: 3rem;
} }
.h-6 {
height: 1.5rem;
}
.h-4 { .h-4 {
height: 1rem; height: 1rem;
} }
@ -988,26 +1008,10 @@ video {
height: 2.75rem; height: 2.75rem;
} }
.h-6 {
height: 1.5rem;
}
.h-8 {
height: 2rem;
}
.h-32 {
height: 8rem;
}
.h-96 { .h-96 {
height: 24rem; height: 24rem;
} }
.h-16 {
height: 4rem;
}
.h-0 { .h-0 {
height: 0px; height: 0px;
} }
@ -1052,6 +1056,18 @@ video {
width: 1.5rem; width: 1.5rem;
} }
.w-8 {
width: 2rem;
}
.w-16 {
width: 4rem;
}
.w-32 {
width: 8rem;
}
.w-10 { .w-10 {
width: 2.5rem; width: 2.5rem;
} }
@ -1076,10 +1092,6 @@ video {
width: 2.75rem; width: 2.75rem;
} }
.w-8 {
width: 2rem;
}
.w-min { .w-min {
width: -webkit-min-content; width: -webkit-min-content;
width: min-content; width: min-content;
@ -1094,14 +1106,6 @@ video {
width: 24rem; width: 24rem;
} }
.w-32 {
width: 8rem;
}
.w-16 {
width: 4rem;
}
.w-auto { .w-auto {
width: auto; width: auto;
} }
@ -1378,6 +1382,14 @@ video {
border-bottom-width: 1px; border-bottom-width: 1px;
} }
.border-b-2 {
border-bottom-width: 2px;
}
.border-dotted {
border-style: dotted;
}
.border-black { .border-black {
--tw-border-opacity: 1; --tw-border-opacity: 1;
border-color: rgb(0 0 0 / var(--tw-border-opacity)); border-color: rgb(0 0 0 / var(--tw-border-opacity));
@ -1491,6 +1503,11 @@ video {
padding: 0.125rem; padding: 0.125rem;
} }
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.px-0 { .px-0 {
padding-left: 0px; padding-left: 0px;
padding-right: 0px; padding-right: 0px;
@ -1553,6 +1570,10 @@ video {
padding-right: 0.5rem; padding-right: 0.5rem;
} }
.pb-8 {
padding-bottom: 2rem;
}
.pl-5 { .pl-5 {
padding-left: 1.25rem; padding-left: 1.25rem;
} }
@ -1868,6 +1889,11 @@ body {
box-sizing: initial !important; box-sizing: initial !important;
} }
.leaflet-marker-icon img {
-webkit-touch-callout: none;
/* prevent callout to copy image, etc when tap to hold */
}
.leaflet-control-attribution { .leaflet-control-attribution {
display: block ruby; display: block ruby;
} }
@ -2742,6 +2768,10 @@ input {
border-radius: 0.75rem; border-radius: 0.75rem;
} }
.md\:border-t-2 {
border-top-width: 2px;
}
.md\:p-1 { .md\:p-1 {
padding: 0.25rem; padding: 0.25rem;
} }

View file

@ -113,6 +113,10 @@ body {
box-sizing: initial !important; box-sizing: initial !important;
} }
.leaflet-marker-icon img {
-webkit-touch-callout: none; /* prevent callout to copy image, etc when tap to hold */
}
.leaflet-control-attribution { .leaflet-control-attribution {
display: block ruby; display: block ruby;
} }

View file

@ -188,6 +188,9 @@
"loading": "Loading…", "loading": "Loading…",
"loadingTheme": "Loading {theme}…", "loadingTheme": "Loading {theme}…",
"loginFailed": "Logging in into OpenStreetMap failed", "loginFailed": "Logging in into OpenStreetMap failed",
"loginFailedOfflineMode": "OpenStreetMap.org is currently not available due to maintenance. Making edits will be possible soon",
"loginFailedReadonlyMode": "OpenStreetMap.org is currently in readonly mode due to maintenance. Making edits will be possible soon",
"loginFailedUnreachableMode": "OpenStreetMap.org is currently not reachable. Are you connected to the internet or do you block third parties? Try again later",
"loginOnlyNeededToEdit": "if you want to make changes", "loginOnlyNeededToEdit": "if you want to make changes",
"loginToStart": "Log in to answer this question", "loginToStart": "Log in to answer this question",
"loginWithOpenStreetMap": "Login with OpenStreetMap", "loginWithOpenStreetMap": "Login with OpenStreetMap",
@ -284,6 +287,7 @@
"uploadGpx": { "uploadGpx": {
"choosePermission": "Choose below if your track should be shared:", "choosePermission": "Choose below if your track should be shared:",
"confirm": "Confirm upload", "confirm": "Confirm upload",
"gpxServiceOffline": "The GPX-service is currently offline - uploading is currently not possible. Try again later.",
"intro0": "By uploading your track, OpenStreetMap.org will retain a full copy of the track.", "intro0": "By uploading your track, OpenStreetMap.org will retain a full copy of the track.",
"intro1": "You will be able to download your track again and to load them into OpenStreetMap editing programs", "intro1": "You will be able to download your track again and to load them into OpenStreetMap editing programs",
"meta": { "meta": {
@ -327,7 +331,7 @@
"tuesday": "Tuesday", "tuesday": "Tuesday",
"wednesday": "Wednesday" "wednesday": "Wednesday"
}, },
"welcomeBack": "You are logged in, welcome back!", "welcomeBack": "Welcome back!",
"welcomeExplanation": { "welcomeExplanation": {
"addNew": "Tap the map to add a new POI.", "addNew": "Tap the map to add a new POI.",
"browseMoreMaps": "Discover more maps", "browseMoreMaps": "Discover more maps",

View file

@ -14,6 +14,7 @@ describe("Unit", () => {
nl: " megawatt", nl: " megawatt",
}, },
}, },
false,
"test" "test"
) )