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
Devcontainer (see more details later).
You need at least 3Gb available to run MapComplete.
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. 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 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
then install node with `n install <node-version>`
- You can [use asdf to manage your runtime versions](https://asdf-vm.com/).
0. Install `npm`. Linux: `sudo apt install npm` (or your favourite package manager), Windows: install
nodeJS: https://nodejs.org/en/download/
0. Run `npm run init` which …
then install node with `n install <node-version>`. You can [use asdf to manage your runtime versions](https://asdf-vm.com/).
- Windows: install nodeJS: https://nodejs.org/en/download/
3. Run `npm run init` which …
- runs `npm install`
- generates some additional dependencies and files
0. 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`
4. Run `npm run start` to host a local testversion at http://localhost:1234/index.html
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 (
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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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"
],
"additionalProperties": false
},
"Record<string,string>": {
"type": "object",
"additionalProperties": false
}
},
"$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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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": [
"canonicalDenomination"
]
},
"Record<string,string>": {
"type": "object"
}
},
"$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",
"type": "number"
}
},
"required": [
"preferredBackground"
]
}
},
{
"enum": [
@ -426,7 +423,6 @@
"type": "boolean"
},
"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",
"items": {
"$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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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
},
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"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",
"type": "object",
@ -865,6 +867,28 @@
"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": [
@ -1421,6 +1445,7 @@
"additionalProperties": false
},
"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",
"properties": {
"appliesToKey": {
@ -1435,11 +1460,15 @@
"type": "boolean"
},
"applicableUnits": {
"description": "The possible denominations",
"description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array",
"items": {
"$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": [

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",
"type": "number"
}
},
"required": [
"preferredBackground"
]
}
},
{
"enum": [
@ -426,7 +423,6 @@ export default {
"type": "boolean"
},
"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",
"items": {
"$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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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"
]
},
"Record<string,string>": {
"type": "object"
},
"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",
"type": "object",
@ -858,6 +859,28 @@ export default {
"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": [
@ -1405,6 +1428,7 @@ export default {
}
},
"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",
"properties": {
"appliesToKey": {
@ -1419,11 +1443,15 @@ export default {
"type": "boolean"
},
"applicableUnits": {
"description": "The possible denominations",
"description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array",
"items": {
"$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": [

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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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
},
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"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",
"type": "object",
@ -695,6 +701,28 @@
"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": [
@ -1251,6 +1279,7 @@
"additionalProperties": false
},
"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",
"properties": {
"appliesToKey": {
@ -1265,11 +1294,15 @@
"type": "boolean"
},
"applicableUnits": {
"description": "The possible denominations",
"description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array",
"items": {
"$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": [
@ -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",
"type": "number"
}
},
"required": [
"preferredBackground"
]
}
},
{
"enum": [
@ -1749,7 +1779,6 @@
"type": "boolean"
},
"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",
"items": {
"$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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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"
]
},
"Record<string,string>": {
"type": "object"
},
"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",
"type": "object",
@ -688,6 +693,28 @@ export default {
"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": [
@ -1235,6 +1262,7 @@ export default {
}
},
"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",
"properties": {
"appliesToKey": {
@ -1249,11 +1277,15 @@ export default {
"type": "boolean"
},
"applicableUnits": {
"description": "The possible denominations",
"description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array",
"items": {
"$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": [
@ -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",
"type": "number"
}
},
"required": [
"preferredBackground"
]
}
},
{
"enum": [
@ -1730,7 +1759,6 @@ export default {
"type": "boolean"
},
"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",
"items": {
"$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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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
},
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"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",
"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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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"
]
},
"Record<string,string>": {
"type": "object"
},
"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",
"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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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
},
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"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",
"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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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"
]
},
"Record<string,string>": {
"type": "object"
},
"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",
"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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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"
],
"additionalProperties": false
},
"Record<string,string>": {
"type": "object",
"additionalProperties": false
}
},
"$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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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": [
"canonicalDenomination"
]
},
"Record<string,string>": {
"type": "object"
}
},
"$schema": "http://json-schema.org/draft-07/schema#"

View file

@ -80,6 +80,28 @@
"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": [
@ -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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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
},
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"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",
"type": "object",

View file

@ -80,6 +80,28 @@ export default {
"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": [
@ -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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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"
]
},
"Record<string,string>": {
"type": "object"
},
"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",
"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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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
},
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"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",
"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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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"
]
},
"Record<string,string>": {
"type": "object"
},
"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",
"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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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
},
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"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",
"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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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"
]
},
"Record<string,string>": {
"type": "object"
},
"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",
"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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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
},
"Record<string,string>": {
"type": "object",
"additionalProperties": false
},
"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",
"type": "object",
@ -452,6 +458,28 @@
"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": [
@ -1008,6 +1036,7 @@
"additionalProperties": false
},
"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",
"properties": {
"appliesToKey": {
@ -1022,11 +1051,15 @@
"type": "boolean"
},
"applicableUnits": {
"description": "The possible denominations",
"description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array",
"items": {
"$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": [

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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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"
]
},
"Record<string,string>": {
"type": "object"
},
"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",
"type": "object",
@ -445,6 +450,28 @@ export default {
"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": [
@ -992,6 +1019,7 @@ export default {
}
},
"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",
"properties": {
"appliesToKey": {
@ -1006,11 +1034,15 @@ export default {
"type": "boolean"
},
"applicableUnits": {
"description": "The possible denominations",
"description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array",
"items": {
"$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": [

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",
"properties": {
"appliesToKey": {
@ -13,11 +14,15 @@
"type": "boolean"
},
"applicableUnits": {
"description": "The possible denominations",
"description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array",
"items": {
"$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": [
@ -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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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"
],
"additionalProperties": false
},
"Record<string,string>": {
"type": "object",
"additionalProperties": false
}
},
"$schema": "http://json-schema.org/draft-07/schema#",

View file

@ -1,4 +1,5 @@
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",
"properties": {
"appliesToKey": {
@ -13,11 +14,15 @@ export default {
"type": "boolean"
},
"applicableUnits": {
"description": "The possible denominations",
"description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"",
"type": "array",
"items": {
"$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": [
@ -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": {
"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"
},
"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"
},
"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",
"items": {
"type": "string"
}
},
"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": {
"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": {
"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": [
"canonicalDenomination"
]
},
"Record<string,string>": {
"type": "object"
}
},
"$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
this.geolocationState.currentGPSLocation.addCallbackAndRun((location) => {
if (location === undefined) {
state.currentUserLocation?.features?.setData([])
return
}
const feature = {

View file

@ -45,20 +45,6 @@ export default class SelectedFeatureHandler {
const self = this
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()
}

View file

@ -4,8 +4,10 @@ import ScrollableFullScreen from "../../UI/Base/ScrollableFullScreen"
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
*
* Note: the actual implementation is in StrayClickHandlerImplementation
*/
export default class StrayClickHandler {
public static construct = (

View file

@ -178,7 +178,7 @@ export class BBox {
])
}
toLeaflet() {
toLeaflet(): [[number, number], [number, number]] {
return [
[this.minLat, this.minLon],
[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 {
public static readonly oauth_configs = {
osm: {
@ -46,6 +48,13 @@ export class OsmConnection {
public auth
public userDetails: UIEventSource<UserDetails>
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">(
"not-attempted"
)
@ -62,7 +71,7 @@ export class OsmConnection {
private readonly _singlePage: boolean
private isChecking = false
constructor(options: {
constructor(options?: {
dryRun?: UIEventSource<boolean>
fakeUser?: false | boolean
oauth_token?: UIEventSource<string>
@ -71,6 +80,7 @@ export class OsmConnection {
osmConfiguration?: "osm" | "osm-test"
attemptLogin?: true | boolean
}) {
options = options ?? {}
this.fakeUser = options.fakeUser ?? false
this._singlePage = options.singlePage ?? true
this._oauth_config =
@ -93,7 +103,13 @@ export class OsmConnection {
ud.totalMessages = 42
}
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) => {
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
@ -106,7 +122,10 @@ export class OsmConnection {
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) {
console.log(options.oauth_token.data)
@ -130,7 +149,13 @@ export class OsmConnection {
}
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(
@ -159,11 +184,17 @@ export class OsmConnection {
this.loadingStatus.setData("not-attempted")
}
/**
* The backend host, without path or trailing '/'
*
* new OsmConnection().Backend() // => "https://www.openstreetmap.org"
*/
public Backend(): string {
return this._oauth_config.url
}
public AttemptLogin() {
this.UpdateCapabilities()
this.loadingStatus.setData("loading")
if (this.fakeUser) {
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) {
return "deleted"
}
return rawData["contents"].elements[0].tags
return rawData["content"].elements[0].tags
}
static async DownloadObjectAsync(

View file

@ -230,10 +230,12 @@ export abstract class Store<T> {
const newSource = new UIEventSource<T>(this.data)
const self = this
this.addCallback((latestData) => {
window.setTimeout(() => {
if (this.data == latestData) {
// compare by reference
if (self.data == latestData) {
// 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)
}
}, millisToStabilize)

View file

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

View file

@ -15,7 +15,7 @@ export class Denomination {
private readonly _human: Translation
private readonly _humanSingular?: Translation
constructor(json: DenominationConfigJson, context: string) {
constructor(json: DenominationConfigJson, useAsDefaultInput: boolean, context: string) {
context = `${context}.unit(${json.canonicalDenomination})`
this.canonical = json.canonicalDenomination.trim()
if (this.canonical === undefined) {
@ -35,7 +35,7 @@ export class Denomination {
throw `${context} uses the old 'default'-key. Use "useIfNoUnitGiven" or "useAsDefaultInput" instead`
}
this.useIfNoUnitGiven = json.useIfNoUnitGiven
this.useAsDefaultInput = json.useAsDefaultInput ?? json.useIfNoUnitGiven
this.useAsDefaultInput = useAsDefaultInput ?? json.useIfNoUnitGiven
this._human = Translations.T(json.human, context + "human")
this._humanSingular = Translations.T(json.humanSingular, context + "humanSingular")
@ -69,7 +69,7 @@ export class Denomination {
* human: {
* en: "meter"
* }
* }, "test")
* }, false, "test")
* unit.canonicalValue("42m", true) // =>"42 m"
* unit.canonicalValue("42", true) // =>"42 m"
* unit.canonicalValue("42 m", true) // =>"42 m"
@ -84,7 +84,7 @@ export class Denomination {
* human: {
* en: "meter"
* }
* }, "test")
* }, false, "test")
* unit.canonicalValue("42m", true) // =>"42"
* unit.canonicalValue("42", true) // =>"42"
* unit.canonicalValue("42 m", true) // =>"42"

View file

@ -389,62 +389,7 @@ export interface LayerConfigJson {
allowSplit?: boolean
/**
* 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
*
* @see 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 {
/**
* Every key from this list will be normalized.
@ -11,9 +69,19 @@ export default interface UnitConfigJson {
*/
eraseInvalidValues?: boolean
/**
* The possible denominations
* The possible denominations for this unit.
* For length, denominations could be "meter", "kilometer", "miles", "foot"
*/
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 {
@ -28,12 +96,6 @@ export interface DenominationConfigJson {
*/
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.
* e.g. "m" for meters
@ -46,12 +108,15 @@ export interface DenominationConfigJson {
/**
* 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
/**
* 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[]
@ -62,16 +127,16 @@ export interface DenominationConfigJson {
* "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.
* {
* "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 '€'

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) {
const appliesTo = json.appliesToKey
for (let i = 0; i < appliesTo.length; i++) {
@ -74,14 +112,15 @@ export class Unit {
}
// 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(
(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)
}

View file

@ -8,11 +8,8 @@ import { Utils } from "../Utils"
import LanguagePicker1 from "./LanguagePicker"
import IndexText from "./BigComponents/IndexText"
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 { LoginToggle } from "./Popup/LoginButton"
import UserSurveyPanel from "./UserSurveyPanel"
export default class AllThemesGui {
@ -31,13 +28,7 @@ export default class AllThemesGui {
new FeaturedMessage().SetClass("mb-4 block"),
new Combine([new UserSurveyPanel()]).SetClass("flex justify-center"),
new MoreScreen(state, true),
new Toggle(
undefined,
new SubtleButton(undefined, Translations.t.index.logIn)
.SetStyle("height:min-content")
.onClick(() => state.osmConnection.AttemptLogin()),
state.osmConnection.isLoggedIn
),
new LoginToggle(undefined, Translations.t.index.logIn, state),
new ImportViewerLinks(state.osmConnection),
Translations.t.general.aboutMapcomplete
.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) {
// @ts-ignore
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 _fullscreencomponent: BaseUIElement
private _resetScrollSignal: UIEventSource<void> = new UIEventSource<void>(undefined)
private _setHash: boolean
constructor(
title: (options: { mode: string }) => BaseUIElement,
@ -34,13 +35,14 @@ export default class ScrollableFullScreen {
hashToShow: string,
isShown: UIEventSource<boolean> = new UIEventSource<boolean>(false),
options?: {
setHash?: true | boolean
setHash?: boolean
}
) {
this.hashToShow = hashToShow
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"
}
@ -55,8 +57,7 @@ export default class ScrollableFullScreen {
)
const self = this
const setHash = options?.setHash ?? true
if (setHash) {
if (this._setHash) {
Hash.hash.addCallback((h) => {
if (h === undefined) {
isShown.setData(false)
@ -64,13 +65,10 @@ export default class ScrollableFullScreen {
})
}
isShown.addCallback((isShown) => {
isShown.addCallbackD((isShown) => {
if (isShown) {
// We first must set the hash, then activate the panel
// If the order is wrong, this will cause the panel to disactivate again
if (setHash) {
Hash.hash.setData(hashToShow)
}
ScrollableFullScreen._currentlyOpen = self
self.Activate()
} else {
@ -81,6 +79,10 @@ export default class ScrollableFullScreen {
ScrollableFullScreen.collapse()
}
})
if (isShown.data) {
ScrollableFullScreen._currentlyOpen = self
this.Activate()
}
}
private static initEmpty(): FixedUiElement {
@ -114,6 +116,9 @@ export default class ScrollableFullScreen {
* @constructor
*/
public Activate(): void {
if (this.hashToShow && this.hashToShow !== "" && this._setHash) {
Hash.hash.setData(this.hashToShow)
}
this.isShown.setData(true)
this._fullscreencomponent.AttachTo("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"
)
}
static ActivateCurrent() {
ScrollableFullScreen._currentlyOpen?.Activate()
}
}

View file

@ -31,13 +31,19 @@ export default abstract class BaseUIElement {
throw "SEVERE: could not attach UIElement to " + divId
}
while (element.firstChild) {
//The list is LIVE so it will re-index each call
element.removeChild(element.firstChild)
let alreadyThere = false
const elementToAdd = this.ConstructElement()
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) {
element.appendChild(el)
if (elementToAdd !== undefined && !alreadyThere) {
element.appendChild(elementToAdd)
}
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 Img from "../Base/Img"
import { TypedTranslation } from "../i18n/Translation"
import TranslatorsPanel from "./TranslatorsPanel"
import { MapillaryLink } from "./MapillaryLink"
import FullWelcomePaneWithTabs from "./FullWelcomePaneWithTabs"
export class OpenIdEditor extends VariableUiElement {
constructor(
state: { locationControl: UIEventSource<Loc> },
state: { readonly locationControl: Store<Loc> },
iconStyle?: string,
objectId?: string
) {
@ -125,38 +122,6 @@ export default class CopyrightPanel extends Combine {
}) {
const t = Translations.t.general.attribution
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)
@ -213,7 +178,6 @@ export default class CopyrightPanel extends Combine {
CopyrightPanel.CodeContributors(contributors, t.codeContributionsBy),
CopyrightPanel.CodeContributors(translators, t.translatedBy),
new FixedUiElement("MapComplete " + Constants.vNumber).SetClass("font-bold"),
new Combine(actionButtons).SetClass("block w-full link-no-underline"),
new Title(t.iconAttribution.title, 3),
...iconAttributions,
].map((e) => e?.SetClass("mt-4"))

View file

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

View file

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

View file

@ -1,14 +1,13 @@
import { VariableUiElement } from "../Base/VariableUIElement"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Store } from "../../Logic/UIEventSource"
import Loc from "../../Models/Loc"
import Translations from "../i18n/Translations"
import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg"
import Combine from "../Base/Combine"
import Title from "../Base/Title"
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
super(
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)}`
return new SubtleButton(
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,
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-welcome-message", human: tr.fsWelcomeMessage },
{ urlName: "fs-layers", human: tr.fsLayers },
{ urlName: "layer-control-toggle", human: tr.fsLayerControlToggle, reverse: true },
{ urlName: "fs-add-new", human: tr.fsAddNew },
{ urlName: "fs-geolocation", human: tr.fsGeolocation },
]
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)
optionParts.push(
checkbox.GetValue().map((isEn) => {
if (isEn) {
if (swtch.reverse) {
return `${swtch.urlName}=true`
}
return null
} else {
if (swtch.reverse) {
return null
}
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
*/
import { UIEventSource } from "../../Logic/UIEventSource"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Svg from "../../Svg"
import { SubtleButton } from "../Base/SubtleButton"
import Combine from "../Base/Combine"
@ -28,6 +28,7 @@ import Hash from "../../Logic/Web/Hash"
import { GlobalFilter } from "../../Logic/State/MapState"
import { WayId } from "../../Models/OsmFeature"
import { Tag } from "../../Logic/Tags/Tag"
import { LoginToggle } from "../Popup/LoginButton"
/*
* The SimpleAddUI is a single panel, which can have multiple states:
@ -44,7 +45,7 @@ export interface PresetInfo extends PresetConfig {
boundsFactor?: 0.25 | number
}
export default class SimpleAddUI extends Toggle {
export default class SimpleAddUI extends LoginToggle {
/**
*
* @param isShown
@ -59,6 +60,7 @@ export default class SimpleAddUI extends Toggle {
filterViewIsOpened: UIEventSource<boolean>,
state: {
featureSwitchIsTesting: UIEventSource<boolean>
featureSwitchUserbadge: Store<boolean>
layoutToUse: LayoutConfig
osmConnection: OsmConnection
changes: Changes
@ -74,10 +76,6 @@ export default class SimpleAddUI extends Toggle {
},
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([
Translations.t.general.readYourMessages.Clone().SetClass("alert"),
new SubtleButton(Svg.envelope_ui(), Translations.t.general.goToInbox, {
@ -187,8 +185,8 @@ export default class SimpleAddUI extends Toggle {
userdetails.unreadMessages == 0
)
),
loginButton,
state.osmConnection.isLoggedIn
Translations.t.general.add.pleaseLogin,
state
)
}

View file

@ -3,12 +3,16 @@ import LanguagePicker from "../LanguagePicker"
import Translations from "../i18n/Translations"
import Toggle from "../Input/Toggle"
import { SubtleButton } from "../Base/SubtleButton"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import { LoginToggle } from "../Popup/LoginButton"
import Svg from "../../Svg"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
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"
export default class ThemeIntroductionPanel extends Combine {
@ -21,7 +25,11 @@ export default class ThemeIntroductionPanel extends Combine {
featureSwitchUserbadge: UIEventSource<boolean>
layoutToUse: LayoutConfig
osmConnection: OsmConnection
}
currentBounds: Store<BBox>
locationControl: UIEventSource<Loc>
isTranslator: Store<boolean>
},
guistate?: { userInfoIsOpened: UIEventSource<boolean> }
) {
const t = Translations.t.general
const layout = state.layoutToUse
@ -37,9 +45,18 @@ export default class ThemeIntroductionPanel extends Combine {
})
.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(
new LoginToggle(
undefined,
loggedInUserInfo,
new Combine([
Translations.t.general.loginWithOpenStreetMap.SetClass("text-xl 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"),
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"),
languagePicker?.SetClass("block mt-4"),
languagePicker?.SetClass("block mt-4 pb-8 border-b-2 border-dotted border-gray-400"),
Toggle.If(state.featureSwitchMoreQuests, () =>
new Combine([
@ -80,6 +97,7 @@ export default class ThemeIntroductionPanel extends Combine {
.SetClass("h-12"),
]).SetClass("flex flex-col mt-6")
),
new ActionButtons(state),
...layout.CustomCodeSnippets(),
])

View file

@ -66,7 +66,7 @@ class TranslatorsPanelContent extends Combine {
"missingLayers:",
missingLayers
)
return [
return Utils.NoNull([
hasMissingTheme
? new Link(
"themes:" + layout.id + ".* (zen mode)",
@ -86,7 +86,7 @@ class TranslatorsPanelContent extends Combine {
(context) =>
new Link(context, LinkToWeblate.hrefToWeblate(language, context), true)
),
]
])
}
// "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 Translations from "../i18n/Translations"
import { TextField } from "../Input/TextField"
import { UIEventSource } from "../../Logic/UIEventSource"
import { Store, UIEventSource } from "../../Logic/UIEventSource"
import Title from "../Base/Title"
import { SubtleButton } from "../Base/SubtleButton"
import Svg from "../../Svg"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
import { Translation } from "../i18n/Translation"
import { LoginToggle } from "../Popup/LoginButton"
export default class UploadTraceToOsmUI extends Toggle {
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
}
export default class UploadTraceToOsmUI extends LoginToggle {
constructor(
trace: (title: string) => string,
state: {
layoutToUse: LayoutConfig
osmConnection: OsmConnection
readonly featureSwitchUserbadge: Store<boolean>
},
options?: {
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")
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(
confirmPanel,
new SubtleButton(Svg.upload_svg(), t.title).onClick(() => clicked.setData(true)),
clicked
new Toggle(
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(
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 {
constructor(state: {
layoutToUse: LayoutConfig
osmConnection: OsmConnection
locationControl: UIEventSource<Loc>
}) {
const isOpened = new UIEventSource<boolean>(false)
constructor(
state: {
layoutToUse: LayoutConfig
osmConnection: OsmConnection
locationControl: UIEventSource<Loc>
},
options?: {
isOpened?: UIEventSource<boolean>
}
) {
const isOpened = options?.isOpened ?? new UIEventSource<boolean>(false)
super(
() => {
return new VariableUiElement(

View file

@ -33,7 +33,6 @@ import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"
import { GeoLocationState } from "../Logic/State/GeoLocationState"
import Hotkeys from "./Base/Hotkeys"
import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers"
import { Translation } from "./i18n/Translation"
/**
* The default MapComplete GUI initializer
@ -56,6 +55,7 @@ export default class DefaultGUI {
public setup() {
this.SetupUIElements()
this.SetupMap()
ScrollableFullScreen.ActivateCurrent()
if (
this.state.layoutToUse.customCss !== undefined &&
@ -203,39 +203,43 @@ export default class DefaultGUI {
const guiState = this.guiState
const self = this
new Combine([
Toggle.If(state.featureSwitchUserbadge, () => {
const userInfo = new UserInformationPanel(state)
const mapControl = new MapControlButton(
new VariableUiElement(
state.osmConnection.userDetails.map((ud) => {
if (ud?.img === undefined) {
return Svg.person_ui().SetClass("mt-1 block")
}
return new Img(ud?.img)
})
).SetClass("block rounded-full overflow-hidden"),
{
dontStyle: true,
}
).onClick(() => userInfo.Activate())
const userInfoMapControl = Toggle.If(state.featureSwitchUserbadge, () => {
console.log("Guistate is", guiState)
new UserInformationPanel(state, {
isOpened: guiState.userInfoIsOpened,
})
return new LoginToggle(
mapControl,
Translations.t.general.loginWithOpenStreetMap,
state
)
}),
Toggle.If(
state.featureSwitchExtraLinkEnabled,
() => new ExtraLinkButton(state, state.layoutToUse.extraLink)
),
Toggle.If(state.featureSwitchWelcomeMessage, () => self.InitWelcomeMessage()),
Toggle.If(state.featureSwitchIsTesting, () =>
new FixedUiElement("TESTING").SetClass("alert m-2 border-2 border-black")
),
])
const mapControl = new MapControlButton(
new VariableUiElement(
state.osmConnection.userDetails.map((ud) => {
if (ud?.img === undefined) {
return Svg.person_ui().SetClass("mt-1 block")
}
return new Img(ud?.img)
})
).SetClass("block rounded-full overflow-hidden"),
{
dontStyle: true,
}
).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")
.AttachTo("top-left")
@ -273,26 +277,16 @@ export default class DefaultGUI {
new CenterMessageBox(state).AttachTo("centermessage")
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 {
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.
const help = new MapControlButton(Svg.help_svg())

View file

@ -4,13 +4,22 @@ import Hash from "../Logic/Web/Hash"
export class DefaultGuiState {
static state: DefaultGuiState
public readonly welcomeMessageIsOpened: UIEventSource<boolean>
public readonly downloadControlIsOpened: UIEventSource<boolean>
public readonly filterViewIsOpened: UIEventSource<boolean>
public readonly copyrightViewIsOpened: UIEventSource<boolean>
public readonly currentViewControlIsOpened: UIEventSource<boolean>
public readonly welcomeMessageIsOpened: UIEventSource<boolean> = new UIEventSource<boolean>(
false
)
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 allFullScreenStates: UIEventSource<boolean>[] = []
constructor() {
this.welcomeMessageOpenedTab = UIEventSource.asFloat(
@ -20,68 +29,19 @@ export class DefaultGuiState {
`The tab that is shown in the welcome-message.`
)
)
this.welcomeMessageIsOpened = QueryParameters.GetBooleanQueryParameter(
"welcome-control-toggle",
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 = {
const sources = {
welcome: this.welcomeMessageIsOpened,
download: this.downloadControlIsOpened,
filters: this.filterViewIsOpened,
copyright: this.copyrightViewIsOpened,
currentview: this.currentViewControlIsOpened,
welcome: this.welcomeMessageIsOpened,
userinfo: this.userInfoIsOpened,
}
Hash.hash.addCallbackAndRunD((hash) => {
hash = hash.toLowerCase()
states[hash]?.setData(true)
})
sources[Hash.hash.data?.toLowerCase()]?.setData(true)
if (Hash.hash.data === "" || Hash.hash.data === undefined) {
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 { Changes } from "../../Logic/Osm/Changes"
import Loading from "../Base/Loading"
import {LoginToggle} from "../Popup/LoginButton";
export class ImageUploadFlow extends Toggle {
private static readonly uploadCountsPerId = new Map<string, UIEventSource<number>>()
@ -180,16 +181,12 @@ export class ImageUploadFlow extends Toggle {
chosenLicense.SetClass("subtle text-sm"),
]).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(
new Toggle(
new LoginToggle(
/*We can show the actual upload button!*/
uploadFlow,
/* User not logged in*/ pleaseLoginButton,
state?.osmConnection?.isLoggedIn
/* User not logged in*/ t.pleaseLogin.Clone(),
state
),
undefined /* Nothing as the user badge is disabled*/,
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 TagRenderingQuestion from "./TagRenderingQuestion"
import { OsmId } from "../../Models/OsmFeature"
import { LoginToggle } from "./LoginButton"
export default class DeleteWizard extends Toggle {
/**
@ -122,6 +123,40 @@ export default class DeleteWizard extends Toggle {
]).SetClass("flex mt-2 justify-between"),
]).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(
new Toggle(
new Combine([
@ -130,52 +165,19 @@ export default class DeleteWizard extends Toggle {
),
t.isDeleted,
]).SetClass("flex m-2 rounded-full"),
new Toggle(
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
),
new LoginToggle(deleteFlow, undefined, state),
isDeleted
),
undefined,
isShown
)
const self = this
confirm.addCallbackAndRunD((dialogIsOpened) => {
if (dialogIsOpened) {
self.ScrollIntoView()
}
})
}
private static constructConfirmButton(

View file

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

View file

@ -1,11 +1,13 @@
import { SubtleButton } from "../Base/SubtleButton"
import BaseUIElement from "../BaseUIElement"
import Svg from "../../Svg"
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
import Toggle from "../Input/Toggle"
import { OsmConnection, OsmServiceState } from "../../Logic/Osm/OsmConnection"
import { VariableUiElement } from "../Base/VariableUIElement"
import Loading from "../Base/Loading"
import Translations from "../i18n/Translations"
import { Store } from "../../Logic/UIEventSource"
import Combine from "../Base/Combine"
import { Translation } from "../i18n/Translation"
class LoginButton extends SubtleButton {
constructor(
@ -23,30 +25,67 @@ class LoginButton extends SubtleButton {
}
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(
el: BaseUIElement,
text: BaseUIElement | string,
state: {
osmConnection: OsmConnection
readonly osmConnection: OsmConnection
readonly featureSwitchUserbadge: Store<boolean>
}
) {
const loading = new Loading("Trying to log in...")
const login = new LoginButton(text, state)
super(
state.osmConnection.loadingStatus.map((osmConnectionState) => {
if (osmConnectionState === "loading") {
return loading
}
if (osmConnectionState === "not-attempted") {
return login
}
if (osmConnectionState === "logged-in") {
return el
}
const login = text === undefined ? undefined : new LoginButton(text, state)
const t = Translations.t.general
const offlineModes: Partial<Record<OsmServiceState, Translation>> = {
offline: t.loginFailedOfflineMode,
unreachable: t.loginFailedUnreachableMode,
readonly: t.loginFailedReadonlyMode,
}
// Error!
return new LoginButton(Translations.t.general.loginFailed, state, Svg.invalid_svg())
})
super(
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 { And } from "../../Logic/Tags/And"
import { Tag } from "../../Logic/Tags/Tag"
import { LoginToggle } from "./LoginButton"
interface MoveReason {
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 moveFlow = new Toggle(
const moveFlow = new LoginToggle(
new VariableUiElement(
currentStep.map((currentStep) => {
switch (currentStep) {
@ -246,8 +247,8 @@ export default class MoveWizard extends Toggle {
}
})
),
loginButton,
state.osmConnection.isLoggedIn
undefined,
state
)
let id = featureToMove.properties.id
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"),
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 { Unit } from "../../Models/Unit"
import Lazy from "../Base/Lazy"
import { OsmServiceState } from "../../Logic/Osm/OsmConnection"
/**
* Generates all the questions, one by one
@ -119,30 +120,34 @@ export default class QuestionBox extends VariableUiElement {
)
super(
questionsToAsk.map((allQuestions) => {
const els: BaseUIElement[] = []
if (
options.showAllQuestionsAtOnce === true ||
options.showAllQuestionsAtOnce["data"]
) {
els.push(...questionsToAsk.data)
} else {
els.push(allQuestions[0])
}
questionsToAsk.map(
(allQuestions) => {
const apiState: OsmServiceState = state.osmConnection.apiIsOnline.data
if (apiState !== "online" && apiState !== "unknown") {
return undefined
}
const els: BaseUIElement[] = []
if (
options.showAllQuestionsAtOnce === true ||
options.showAllQuestionsAtOnce["data"]
) {
els.push(...questionsToAsk.data)
} else {
els.push(allQuestions[0])
}
if (skippedQuestions.data.length > 0) {
els.push(skippedQuestionsButton)
}
if (skippedQuestions.data.length > 0) {
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.restingQuestions = questionsToAsk
focus = () =>
this.ScrollIntoView({
onlyIfPartiallyHidden: true,
})
focus = () => this.ScrollIntoView()
}
}

View file

@ -24,6 +24,8 @@ import BaseLayer from "../../Models/BaseLayer"
import FilteredLayer from "../../Models/FilteredLayer"
import BaseUIElement from "../BaseUIElement"
import { VariableUiElement } from "../Base/VariableUIElement"
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
import { LoginToggle } from "./LoginButton"
export default class SplitRoadWizard extends Combine {
// @ts-ignore
@ -54,6 +56,7 @@ export default class SplitRoadWizard extends Combine {
changes: Changes
layoutToUse: LayoutConfig
allElements: ElementStorage
selectedElement: UIEventSource<any>
}
) {
const t = Translations.t.split
@ -79,16 +82,8 @@ export default class SplitRoadWizard extends Combine {
hasBeenSplit
)
)
splitButton.onClick(() => {
splitClicked.setData(true)
})
// Only show the splitButton if logged in, else show login prompt
const loginBtn = t.loginToSplit
.Clone()
.onClick(() => state.osmConnection.AttemptLogin())
.SetClass("login-button-friendly")
const splitToggle = new Toggle(splitButton, loginBtn, state.osmConnection.isLoggedIn)
const splitToggle = new LoginToggle(splitButton, t.loginToSplit.Clone(), state)
// Save button
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
splitPoints.setData([])
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")
const disabledSaveButton = new Button("Split", undefined)
const disabledSaveButton = new Button(t.split.Clone(), undefined)
disabledSaveButton.SetClass("btn btn-disabled mr-3")
// Only show the save button if there are split points defined
const saveToggle = new Toggle(
@ -147,6 +145,11 @@ export default class SplitRoadWizard extends Combine {
new Toggle(mapView, splitToggle, splitClicked),
])
this.dialogIsOpened = splitClicked
const self = this
splitButton.onClick(() => {
splitClicked.setData(true)
self.ScrollIntoView()
})
}
private static setupMapComponent(

View file

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

View file

@ -4,8 +4,10 @@ import { ShowDataLayerOptions } from "./ShowDataLayerOptions"
import { ElementStorage } from "../../Logic/ElementStorage"
import RenderingMultiPlexerFeatureSource from "../../Logic/FeatureSource/Sources/RenderingMultiPlexerFeatureSource"
import ScrollableFullScreen from "../Base/ScrollableFullScreen"
import { LeafletMouseEvent } from "leaflet"
import { LeafletMouseEvent, PathOptions } from "leaflet"
import Hash from "../../Logic/Web/Hash"
import { BBox } from "../../Logic/BBox"
import { Utils } from "../../Utils"
/*
// 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.
@ -47,6 +49,7 @@ export default class ShowDataLayerImplementation {
string,
{ feature: any; activateFunc: (event: LeafletMouseEvent) => void }
>()
private readonly showDataLayerid: number
private readonly createPopup: (
tags: UIEventSource<any>,
@ -81,7 +84,7 @@ export default class ShowDataLayerImplementation {
}
const self = this
options.leafletMap.addCallback((_) => {
options.leafletMap.addCallback(() => {
return self.update(options)
})
@ -112,6 +115,10 @@ export default class ShowDataLayerImplementation {
})
this._selectedElement?.addCallbackAndRunD((selected) => {
if (selected === undefined) {
ScrollableFullScreen.collapse()
return
}
self.openPopupOfSelectedElement(selected)
})
@ -171,17 +178,8 @@ export default class ShowDataLayerImplementation {
}
const self = this
const data = {
type: "FeatureCollection",
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),
})
this.geoLayer = new L.LayerGroup()
const selfLayer = this.geoLayer
const allFeats = this._features.features.data
@ -189,6 +187,31 @@ export default class ShowDataLayerImplementation {
if (feat === undefined) {
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 {
if (feat.geometry.type === "LineString") {
const coords = L.GeoJSON.coordsToLatLngs(feat.geometry.coordinates)
@ -229,7 +252,7 @@ export default class ShowDataLayerImplementation {
return self.geoLayer !== selfLayer
})
} else {
this.geoLayer.addData(feat)
geojsonLayer.addData(feat)
}
} catch (e) {
console.error(
@ -242,14 +265,14 @@ export default class ShowDataLayerImplementation {
}
}
if (options.zoomToFeatures ?? false) {
if (this.geoLayer.getLayers().length > 0) {
try {
const bounds = this.geoLayer.getBounds()
mp.fitBounds(bounds, { animate: false })
} catch (e) {
console.debug("Invalid bounds", e)
}
if ((options.zoomToFeatures ?? false) && allFeats.length > 0) {
let bound = undefined
for (const feat of allFeats) {
const fbound = BBox.get(feat)
bound = bound?.unionWith(fbound) ?? fbound
}
if (bound !== undefined) {
mp.fitBounds(bound?.toLeaflet(), { animate: false })
}
}
@ -312,29 +335,7 @@ export default class ShowDataLayerImplementation {
icon: L.divIcon(style),
})
}
/**
* 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
}
private createActivateFunction(feature, key: string, layer: LayerConfig): (event) => void {
let infobox: ScrollableFullScreen = undefined
const self = this
@ -354,17 +355,36 @@ export default class ShowDataLayerImplementation {
self._selectedElement.setData(
self.allElements.ContainingFeatures.get(feature.id) ?? feature
)
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
}
}
return activate
}
/**
* 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
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
this.leafletLayersPerId.set(key, {
feature: feature,

View file

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

View file

@ -42,7 +42,13 @@ export default class Translations {
*
*/
static T(
t: string | undefined | null | Translation | TypedTranslation<object>,
t:
| string
| Record<string, string>
| undefined
| null
| Translation
| TypedTranslation<object>,
context = undefined
): TypedTranslation<object> {
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,
maxCacheTimeMs: number,
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)
if (cached !== undefined) {
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 {
const now = new Date()
const lastWeek = new Date(now.getTime() - daysInThePast * 24 * 60 * 60 * 1000)

View file

@ -32,17 +32,7 @@
]
},
"source": {
"osmTags": {
"or": [
"amenity=atm",
{
"and": [
"amenity=bank",
"atm=yes"
]
}
]
}
"osmTags": "amenity=atm"
},
"minzoom": 13,
"presets": [
@ -59,26 +49,7 @@
}
],
"tagRenderings": [
{
"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"
}
},
"images",
{
"id": "name",
"render": {
@ -140,12 +111,7 @@
"nl": "Deze geldautomaat wordt beheerd door {operator}"
}
},
{
"builtin": "opening_hours",
"override": {
"condition": "amenity!=bank"
}
},
"opening_hours",
{
"id": "cash_out",
"question": {
@ -276,6 +242,18 @@
]
}
],
"allowMove": {
"enableImproveAccuracy": true,
"enableRelocation": false
},
"deletion": {
"softDeletionTags": {
"and": [
"disused:amenity=atm",
"amenity="
]
}
},
"filter": [
"open_now",
{
@ -292,4 +270,4 @@
]
}
]
}
}

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:depth"
],
"defaultInput": "cm",
"applicableUnits": [
{
"canonicalDenomination": "m",
@ -221,7 +222,6 @@
}
},
{
"useAsDefaultInput": true,
"canonicalDenomination": "cm",
"alternativeDenomination": [
"centimeter",
@ -238,4 +238,4 @@
]
}
]
}
}

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

@ -284,7 +284,7 @@
"da": "Hvad er webstedet for {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": {
"key": "website",
"type": "url",
@ -295,7 +295,7 @@
"mappings": [
{
"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
}
]
@ -1711,4 +1711,4 @@
"fr": "Le nom du réseau est <b>{internet_access:ssid}</b>"
}
}
}
}

View file

@ -17,6 +17,32 @@
"startLon": 0,
"startZoom": 0,
"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",
"override": {
"mapRendering": null,
"title": null
"title": null,
"forceLoad": true,
"minzoom": 18,
"passAllFeatures": true,
"shownByDefault": false
}
},
{
@ -95,4 +98,4 @@
},
"stairs"
]
}
}

View file

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

View file

@ -811,14 +811,22 @@ video {
margin-top: 0.25rem;
}
.mt-4 {
margin-top: 1rem;
}
.mr-2 {
margin-right: 0.5rem;
}
.mr-4 {
margin-right: 1rem;
}
.mr-6 {
margin-right: 1.5rem;
}
.mt-4 {
margin-top: 1rem;
}
.ml-4 {
margin-left: 1rem;
}
@ -827,10 +835,6 @@ video {
margin-bottom: 6rem;
}
.mr-4 {
margin-right: 1rem;
}
.mb-2 {
margin-bottom: 0.5rem;
}
@ -964,6 +968,18 @@ video {
height: 16rem;
}
.h-8 {
height: 2rem;
}
.h-16 {
height: 4rem;
}
.h-32 {
height: 8rem;
}
.h-10 {
height: 2.5rem;
}
@ -972,6 +988,10 @@ video {
height: 3rem;
}
.h-6 {
height: 1.5rem;
}
.h-4 {
height: 1rem;
}
@ -988,26 +1008,10 @@ video {
height: 2.75rem;
}
.h-6 {
height: 1.5rem;
}
.h-8 {
height: 2rem;
}
.h-32 {
height: 8rem;
}
.h-96 {
height: 24rem;
}
.h-16 {
height: 4rem;
}
.h-0 {
height: 0px;
}
@ -1052,6 +1056,18 @@ video {
width: 1.5rem;
}
.w-8 {
width: 2rem;
}
.w-16 {
width: 4rem;
}
.w-32 {
width: 8rem;
}
.w-10 {
width: 2.5rem;
}
@ -1076,10 +1092,6 @@ video {
width: 2.75rem;
}
.w-8 {
width: 2rem;
}
.w-min {
width: -webkit-min-content;
width: min-content;
@ -1094,14 +1106,6 @@ video {
width: 24rem;
}
.w-32 {
width: 8rem;
}
.w-16 {
width: 4rem;
}
.w-auto {
width: auto;
}
@ -1378,6 +1382,14 @@ video {
border-bottom-width: 1px;
}
.border-b-2 {
border-bottom-width: 2px;
}
.border-dotted {
border-style: dotted;
}
.border-black {
--tw-border-opacity: 1;
border-color: rgb(0 0 0 / var(--tw-border-opacity));
@ -1491,6 +1503,11 @@ video {
padding: 0.125rem;
}
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.px-0 {
padding-left: 0px;
padding-right: 0px;
@ -1553,6 +1570,10 @@ video {
padding-right: 0.5rem;
}
.pb-8 {
padding-bottom: 2rem;
}
.pl-5 {
padding-left: 1.25rem;
}
@ -1868,6 +1889,11 @@ body {
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 {
display: block ruby;
}
@ -2742,6 +2768,10 @@ input {
border-radius: 0.75rem;
}
.md\:border-t-2 {
border-top-width: 2px;
}
.md\:p-1 {
padding: 0.25rem;
}

View file

@ -113,6 +113,10 @@ body {
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 {
display: block ruby;
}

View file

@ -188,6 +188,9 @@
"loading": "Loading…",
"loadingTheme": "Loading {theme}…",
"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",
"loginToStart": "Log in to answer this question",
"loginWithOpenStreetMap": "Login with OpenStreetMap",
@ -284,6 +287,7 @@
"uploadGpx": {
"choosePermission": "Choose below if your track should be shared:",
"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.",
"intro1": "You will be able to download your track again and to load them into OpenStreetMap editing programs",
"meta": {
@ -327,7 +331,7 @@
"tuesday": "Tuesday",
"wednesday": "Wednesday"
},
"welcomeBack": "You are logged in, welcome back!",
"welcomeBack": "Welcome back!",
"welcomeExplanation": {
"addNew": "Tap the map to add a new POI.",
"browseMoreMaps": "Discover more maps",

View file

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