diff --git a/Docs/Development_deployment.md b/Docs/Development_deployment.md index 3590b92d8..e163ebf79 100644 --- a/Docs/Development_deployment.md +++ b/Docs/Development_deployment.md @@ -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 `) -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 ` - - 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 `. 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#` as [Query parameter](URL_Parameters.md). Note that the shorter URLs ( e.g. `bookcases.html`, `aed.html`, ...) _don't_ exist on the development version. diff --git a/Docs/Personas/Xavier.md b/Docs/Personas/Xavier.md new file mode 100644 index 000000000..47a835866 --- /dev/null +++ b/Docs/Personas/Xavier.md @@ -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... diff --git a/Docs/Personas/_README.md b/Docs/Personas/_README.md new file mode 100644 index 000000000..5cd1947fb --- /dev/null +++ b/Docs/Personas/_README.md @@ -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. diff --git a/Docs/Personas/_TEMPLATE.md b/Docs/Personas/_TEMPLATE.md new file mode 100644 index 000000000..a2cd969e7 --- /dev/null +++ b/Docs/Personas/_TEMPLATE.md @@ -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 diff --git a/Docs/Schemas/DenominationConfigJson.schema.json b/Docs/Schemas/DenominationConfigJson.schema.json index 807226c6b..a24185884 100644 --- a/Docs/Schemas/DenominationConfigJson.schema.json +++ b/Docs/Schemas/DenominationConfigJson.schema.json @@ -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" + }, + { + "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" + }, + { + "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": { + "type": "object", + "additionalProperties": false } }, "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/Docs/Schemas/DenominationConfigJsonJSC.ts b/Docs/Schemas/DenominationConfigJsonJSC.ts index 8e4178640..34b627f58 100644 --- a/Docs/Schemas/DenominationConfigJsonJSC.ts +++ b/Docs/Schemas/DenominationConfigJsonJSC.ts @@ -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" + }, + { + "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" + }, + { + "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": { + "type": "object" } }, "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/Docs/Schemas/LayerConfigJson.schema.json b/Docs/Schemas/LayerConfigJson.schema.json index 5c1b2780d..c3dd69f6b 100644 --- a/Docs/Schemas/LayerConfigJson.schema.json +++ b/Docs/Schemas/LayerConfigJson.schema.json @@ -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" + }, + { + "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" + }, + { + "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": { + "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": [ diff --git a/Docs/Schemas/LayerConfigJsonJSC.ts b/Docs/Schemas/LayerConfigJsonJSC.ts index 26a8e4835..1b16d8156 100644 --- a/Docs/Schemas/LayerConfigJsonJSC.ts +++ b/Docs/Schemas/LayerConfigJsonJSC.ts @@ -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" + }, + { + "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" + }, + { + "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": { + "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": [ diff --git a/Docs/Schemas/LayoutConfigJson.schema.json b/Docs/Schemas/LayoutConfigJson.schema.json index 81dcad724..b1232b4e4 100644 --- a/Docs/Schemas/LayoutConfigJson.schema.json +++ b/Docs/Schemas/LayoutConfigJson.schema.json @@ -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" + }, + { + "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" + }, + { + "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": { + "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" diff --git a/Docs/Schemas/LayoutConfigJsonJSC.ts b/Docs/Schemas/LayoutConfigJsonJSC.ts index c38c0218f..438720be2 100644 --- a/Docs/Schemas/LayoutConfigJsonJSC.ts +++ b/Docs/Schemas/LayoutConfigJsonJSC.ts @@ -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" + }, + { + "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" + }, + { + "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": { + "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" diff --git a/Docs/Schemas/LineRenderingConfigJson.schema.json b/Docs/Schemas/LineRenderingConfigJson.schema.json index 941cfdad3..d2934b5a3 100644 --- a/Docs/Schemas/LineRenderingConfigJson.schema.json +++ b/Docs/Schemas/LineRenderingConfigJson.schema.json @@ -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" + }, + { + "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" + }, + { + "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": { + "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", diff --git a/Docs/Schemas/LineRenderingConfigJsonJSC.ts b/Docs/Schemas/LineRenderingConfigJsonJSC.ts index 1caf3bf98..60c8989fc 100644 --- a/Docs/Schemas/LineRenderingConfigJsonJSC.ts +++ b/Docs/Schemas/LineRenderingConfigJsonJSC.ts @@ -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" + }, + { + "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" + }, + { + "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": { + "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", diff --git a/Docs/Schemas/MappingConfigJson.schema.json b/Docs/Schemas/MappingConfigJson.schema.json index 01171adc5..f05a0c7f4 100644 --- a/Docs/Schemas/MappingConfigJson.schema.json +++ b/Docs/Schemas/MappingConfigJson.schema.json @@ -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" + }, + { + "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" + }, + { + "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": { + "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", diff --git a/Docs/Schemas/MappingConfigJsonJSC.ts b/Docs/Schemas/MappingConfigJsonJSC.ts index fc0104189..ee9704127 100644 --- a/Docs/Schemas/MappingConfigJsonJSC.ts +++ b/Docs/Schemas/MappingConfigJsonJSC.ts @@ -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" + }, + { + "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" + }, + { + "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": { + "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", diff --git a/Docs/Schemas/MoveConfigJson.schema.json b/Docs/Schemas/MoveConfigJson.schema.json index 90ef6daf4..b851d9fbb 100644 --- a/Docs/Schemas/MoveConfigJson.schema.json +++ b/Docs/Schemas/MoveConfigJson.schema.json @@ -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" + }, + { + "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" + }, + { + "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": { + "type": "object", + "additionalProperties": false } }, "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/Docs/Schemas/MoveConfigJsonJSC.ts b/Docs/Schemas/MoveConfigJsonJSC.ts index ac352d096..f963559b9 100644 --- a/Docs/Schemas/MoveConfigJsonJSC.ts +++ b/Docs/Schemas/MoveConfigJsonJSC.ts @@ -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" + }, + { + "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" + }, + { + "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": { + "type": "object" } }, "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/Docs/Schemas/PointRenderingConfigJson.schema.json b/Docs/Schemas/PointRenderingConfigJson.schema.json index 1e3c266cc..8359175c5 100644 --- a/Docs/Schemas/PointRenderingConfigJson.schema.json +++ b/Docs/Schemas/PointRenderingConfigJson.schema.json @@ -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" + }, + { + "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" + }, + { + "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": { + "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", diff --git a/Docs/Schemas/PointRenderingConfigJsonJSC.ts b/Docs/Schemas/PointRenderingConfigJsonJSC.ts index 9ae3c3455..a6cae71be 100644 --- a/Docs/Schemas/PointRenderingConfigJsonJSC.ts +++ b/Docs/Schemas/PointRenderingConfigJsonJSC.ts @@ -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" + }, + { + "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" + }, + { + "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": { + "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", diff --git a/Docs/Schemas/QuestionableTagRenderingConfigJson.schema.json b/Docs/Schemas/QuestionableTagRenderingConfigJson.schema.json index f9066d1c7..a90b75702 100644 --- a/Docs/Schemas/QuestionableTagRenderingConfigJson.schema.json +++ b/Docs/Schemas/QuestionableTagRenderingConfigJson.schema.json @@ -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" + }, + { + "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" + }, + { + "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": { + "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", diff --git a/Docs/Schemas/QuestionableTagRenderingConfigJsonJSC.ts b/Docs/Schemas/QuestionableTagRenderingConfigJsonJSC.ts index 502127f1e..433390afe 100644 --- a/Docs/Schemas/QuestionableTagRenderingConfigJsonJSC.ts +++ b/Docs/Schemas/QuestionableTagRenderingConfigJsonJSC.ts @@ -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" + }, + { + "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" + }, + { + "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": { + "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", diff --git a/Docs/Schemas/RewritableConfigJson.schema.json b/Docs/Schemas/RewritableConfigJson.schema.json index 860894ded..ce4f58302 100644 --- a/Docs/Schemas/RewritableConfigJson.schema.json +++ b/Docs/Schemas/RewritableConfigJson.schema.json @@ -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" + }, + { + "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" + }, + { + "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": { + "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", diff --git a/Docs/Schemas/RewritableConfigJsonJSC.ts b/Docs/Schemas/RewritableConfigJsonJSC.ts index fcca09785..b9b6318b8 100644 --- a/Docs/Schemas/RewritableConfigJsonJSC.ts +++ b/Docs/Schemas/RewritableConfigJsonJSC.ts @@ -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" + }, + { + "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" + }, + { + "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": { + "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", diff --git a/Docs/Schemas/TilesourceConfigJson.schema.json b/Docs/Schemas/TilesourceConfigJson.schema.json index cd4b84235..46c432e90 100644 --- a/Docs/Schemas/TilesourceConfigJson.schema.json +++ b/Docs/Schemas/TilesourceConfigJson.schema.json @@ -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" + }, + { + "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" + }, + { + "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": { + "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": [ diff --git a/Docs/Schemas/TilesourceConfigJsonJSC.ts b/Docs/Schemas/TilesourceConfigJsonJSC.ts index 532c862e3..6326bf5b6 100644 --- a/Docs/Schemas/TilesourceConfigJsonJSC.ts +++ b/Docs/Schemas/TilesourceConfigJsonJSC.ts @@ -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" + }, + { + "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" + }, + { + "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": { + "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": [ diff --git a/Docs/Schemas/UnitConfigJson.schema.json b/Docs/Schemas/UnitConfigJson.schema.json index 8b4ec1440..6d885363b 100644 --- a/Docs/Schemas/UnitConfigJson.schema.json +++ b/Docs/Schemas/UnitConfigJson.schema.json @@ -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" + }, + { + "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" + }, + { + "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": { + "type": "object", + "additionalProperties": false } }, "$schema": "http://json-schema.org/draft-07/schema#", diff --git a/Docs/Schemas/UnitConfigJsonJSC.ts b/Docs/Schemas/UnitConfigJsonJSC.ts index 7459b2310..b4b06c469 100644 --- a/Docs/Schemas/UnitConfigJsonJSC.ts +++ b/Docs/Schemas/UnitConfigJsonJSC.ts @@ -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" + }, + { + "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" + }, + { + "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": { + "type": "object" } }, "$schema": "http://json-schema.org/draft-07/schema#" diff --git a/Docs/Sponsors.md b/Docs/Sponsors.md new file mode 100644 index 000000000..933e3970a --- /dev/null +++ b/Docs/Sponsors.md @@ -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. diff --git a/Docs/UserTests/2023-01-02 Ad Hoc - cyclestreets.md b/Docs/UserTests/2023-01-02 Ad Hoc - cyclestreets.md new file mode 100644 index 000000000..8fa3528fc --- /dev/null +++ b/Docs/UserTests/2023-01-02 Ad Hoc - cyclestreets.md @@ -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 ' 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 diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index 15ad99aa3..6f702b768 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -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 = { diff --git a/Logic/Actors/SelectedFeatureHandler.ts b/Logic/Actors/SelectedFeatureHandler.ts index 69816866b..db6776fec 100644 --- a/Logic/Actors/SelectedFeatureHandler.ts +++ b/Logic/Actors/SelectedFeatureHandler.ts @@ -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() } diff --git a/Logic/Actors/StrayClickHandler.ts b/Logic/Actors/StrayClickHandler.ts index 003917f02..c4fac415b 100644 --- a/Logic/Actors/StrayClickHandler.ts +++ b/Logic/Actors/StrayClickHandler.ts @@ -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 = ( diff --git a/Logic/BBox.ts b/Logic/BBox.ts index a48497adc..abb0e3711 100644 --- a/Logic/BBox.ts +++ b/Logic/BBox.ts @@ -178,7 +178,7 @@ export class BBox { ]) } - toLeaflet() { + toLeaflet(): [[number, number], [number, number]] { return [ [this.minLat, this.minLon], [this.maxLat, this.maxLon], diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 692421b3f..85bd602df 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -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 public isLoggedIn: Store + public gpxServiceIsOnline: UIEventSource = new UIEventSource( + "unknown" + ) + public apiIsOnline: UIEventSource = new UIEventSource( + "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 fakeUser?: false | boolean oauth_token?: UIEventSource @@ -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, + 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, + /*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 = statusEl.getAttribute("api") + const gpx = statusEl.getAttribute("gpx") + return { api, gpx } + } } diff --git a/Logic/Osm/OsmObject.ts b/Logic/Osm/OsmObject.ts index 20c76f0ab..37ec0c41c 100644 --- a/Logic/Osm/OsmObject.ts +++ b/Logic/Osm/OsmObject.ts @@ -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( diff --git a/Logic/UIEventSource.ts b/Logic/UIEventSource.ts index 0ee8b5967..7db59652f 100644 --- a/Logic/UIEventSource.ts +++ b/Logic/UIEventSource.ts @@ -230,10 +230,12 @@ export abstract class Store { const newSource = new UIEventSource(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) diff --git a/Models/Constants.ts b/Models/Constants.ts index 243814b6c..9799c2647 100644 --- a/Models/Constants.ts +++ b/Models/Constants.ts @@ -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 = diff --git a/Models/Denomination.ts b/Models/Denomination.ts index 03770ef6c..cddd0ac7d 100644 --- a/Models/Denomination.ts +++ b/Models/Denomination.ts @@ -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" diff --git a/Models/ThemeConfig/Json/LayerConfigJson.ts b/Models/ThemeConfig/Json/LayerConfigJson.ts index e5e177221..fd80dc4b5 100644 --- a/Models/ThemeConfig/Json/LayerConfigJson.ts +++ b/Models/ThemeConfig/Json/LayerConfigJson.ts @@ -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[] diff --git a/Models/ThemeConfig/Json/UnitConfigJson.ts b/Models/ThemeConfig/Json/UnitConfigJson.ts index a997ae164..88d54009f 100644 --- a/Models/ThemeConfig/Json/UnitConfigJson.ts +++ b/Models/ThemeConfig/Json/UnitConfigJson.ts @@ -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 /** * 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 /** * If set, then the canonical value will be prefixed instead, e.g. for '€' diff --git a/Models/Unit.ts b/Models/Unit.ts index 7a4871975..74862557f 100644 --- a/Models/Unit.ts +++ b/Models/Unit.ts @@ -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) } diff --git a/UI/AllThemesGui.ts b/UI/AllThemesGui.ts index 7f393fe0b..68cb52ddb 100644 --- a/UI/AllThemesGui.ts +++ b/UI/AllThemesGui.ts @@ -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) }) diff --git a/UI/Base/MinimapImplementation.ts b/UI/Base/MinimapImplementation.ts index 3b6364602..ebb46f033 100644 --- a/UI/Base/MinimapImplementation.ts +++ b/UI/Base/MinimapImplementation.ts @@ -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) }) } diff --git a/UI/Base/ScrollableFullScreen.ts b/UI/Base/ScrollableFullScreen.ts index 90732719b..9dd6a83c8 100644 --- a/UI/Base/ScrollableFullScreen.ts +++ b/UI/Base/ScrollableFullScreen.ts @@ -24,6 +24,7 @@ export default class ScrollableFullScreen { private hashToShow: string private _fullscreencomponent: BaseUIElement private _resetScrollSignal: UIEventSource = new UIEventSource(undefined) + private _setHash: boolean constructor( title: (options: { mode: string }) => BaseUIElement, @@ -34,13 +35,14 @@ export default class ScrollableFullScreen { hashToShow: string, isShown: UIEventSource = new UIEventSource(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() + } } diff --git a/UI/BaseUIElement.ts b/UI/BaseUIElement.ts index cc5e1ad90..218be172b 100644 --- a/UI/BaseUIElement.ts +++ b/UI/BaseUIElement.ts @@ -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 diff --git a/UI/BigComponents/ActionButtons.ts b/UI/BigComponents/ActionButtons.ts new file mode 100644 index 000000000..0b3a6eeb7 --- /dev/null +++ b/UI/BigComponents/ActionButtons.ts @@ -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 + readonly locationControl: Store + readonly osmConnection: OsmConnection + readonly isTranslator: Store + }) { + 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") + } +} diff --git a/UI/BigComponents/CopyrightPanel.ts b/UI/BigComponents/CopyrightPanel.ts index 4e87190fb..83dad07db 100644 --- a/UI/BigComponents/CopyrightPanel.ts +++ b/UI/BigComponents/CopyrightPanel.ts @@ -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 }, + state: { readonly locationControl: Store }, 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")) diff --git a/UI/BigComponents/FullWelcomePaneWithTabs.ts b/UI/BigComponents/FullWelcomePaneWithTabs.ts index f21e01c29..e54b2e29a 100644 --- a/UI/BigComponents/FullWelcomePaneWithTabs.ts +++ b/UI/BigComponents/FullWelcomePaneWithTabs.ts @@ -37,12 +37,13 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { featurePipeline: FeaturePipeline backgroundLayer: UIEventSource filteredLayers: UIEventSource - } & UserRelatedState + } & UserRelatedState, + guistate?: { userInfoIsOpened: UIEventSource } ) { 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 } & UserRelatedState, isShown: UIEventSource, - currentTab: UIEventSource + currentTab: UIEventSource, + guistate?: { userInfoIsOpened: UIEventSource } ): { header: string | BaseUIElement; content: BaseUIElement }[] { const tabs: { header: string | BaseUIElement; content: BaseUIElement }[] = [ { header: ``, - 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 } & UserRelatedState, currentTab: UIEventSource, - isShown: UIEventSource + isShown: UIEventSource, + guistate?: { userInfoIsOpened: UIEventSource } ) { - 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({ diff --git a/UI/BigComponents/LeftControls.ts b/UI/BigComponents/LeftControls.ts index 952789d76..a75daabd2 100644 --- a/UI/BigComponents/LeftControls.ts +++ b/UI/BigComponents/LeftControls.ts @@ -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 - downloadControlIsOpened: UIEventSource - filterViewIsOpened: UIEventSource - copyrightViewIsOpened: UIEventSource - } - ) { + constructor(state: FeaturePipelineState, guiState: DefaultGuiState) { const currentViewFL = state.currentView?.layer const currentViewAction = new Toggle( new Lazy(() => { diff --git a/UI/BigComponents/MapillaryLink.ts b/UI/BigComponents/MapillaryLink.ts index 96272204e..afae03940 100644 --- a/UI/BigComponents/MapillaryLink.ts +++ b/UI/BigComponents/MapillaryLink.ts @@ -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 }, iconStyle?: string) { + constructor(state: { readonly locationControl: Store }, 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") + ) }) ) } diff --git a/UI/BigComponents/ShareScreen.ts b/UI/BigComponents/ShareScreen.ts index 5ff2354a6..3dcc5f597 100644 --- a/UI/BigComponents/ShareScreen.ts +++ b/UI/BigComponents/ShareScreen.ts @@ -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` } }) diff --git a/UI/BigComponents/SimpleAddUI.ts b/UI/BigComponents/SimpleAddUI.ts index d7d8c0533..8ce8be8d1 100644 --- a/UI/BigComponents/SimpleAddUI.ts +++ b/UI/BigComponents/SimpleAddUI.ts @@ -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, state: { featureSwitchIsTesting: UIEventSource + featureSwitchUserbadge: Store 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 ) } diff --git a/UI/BigComponents/ThemeIntroductionPanel.ts b/UI/BigComponents/ThemeIntroductionPanel.ts index f4cce6bae..b6817917a 100644 --- a/UI/BigComponents/ThemeIntroductionPanel.ts +++ b/UI/BigComponents/ThemeIntroductionPanel.ts @@ -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 layoutToUse: LayoutConfig osmConnection: OsmConnection - } + currentBounds: Store + locationControl: UIEventSource + isTranslator: Store + }, + guistate?: { userInfoIsOpened: UIEventSource } ) { 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(), ]) diff --git a/UI/BigComponents/TranslatorsPanel.ts b/UI/BigComponents/TranslatorsPanel.ts index ab797d7cc..0124c10b1 100644 --- a/UI/BigComponents/TranslatorsPanel.ts +++ b/UI/BigComponents/TranslatorsPanel.ts @@ -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}", diff --git a/UI/BigComponents/UploadTraceToOsmUI.ts b/UI/BigComponents/UploadTraceToOsmUI.ts index beaa426e4..290a6b3db 100644 --- a/UI/BigComponents/UploadTraceToOsmUI.ts +++ b/UI/BigComponents/UploadTraceToOsmUI.ts @@ -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 }, options?: { whenUploaded?: () => void | Promise @@ -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 + } } diff --git a/UI/BigComponents/UserInformation.ts b/UI/BigComponents/UserInformation.ts index 5f70fc228..25523b32d 100644 --- a/UI/BigComponents/UserInformation.ts +++ b/UI/BigComponents/UserInformation.ts @@ -140,12 +140,17 @@ class UserInformationMainPanel extends Combine { } export default class UserInformationPanel extends ScrollableFullScreen { - constructor(state: { - layoutToUse: LayoutConfig - osmConnection: OsmConnection - locationControl: UIEventSource - }) { - const isOpened = new UIEventSource(false) + constructor( + state: { + layoutToUse: LayoutConfig + osmConnection: OsmConnection + locationControl: UIEventSource + }, + options?: { + isOpened?: UIEventSource + } + ) { + const isOpened = options?.isOpened ?? new UIEventSource(false) super( () => { return new VariableUiElement( diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts index 7497da269..079090e4e 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -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()) diff --git a/UI/DefaultGuiState.ts b/UI/DefaultGuiState.ts index ccdfe323e..cb780b1c4 100644 --- a/UI/DefaultGuiState.ts +++ b/UI/DefaultGuiState.ts @@ -4,13 +4,22 @@ import Hash from "../Logic/Web/Hash" export class DefaultGuiState { static state: DefaultGuiState - public readonly welcomeMessageIsOpened: UIEventSource - public readonly downloadControlIsOpened: UIEventSource - public readonly filterViewIsOpened: UIEventSource - public readonly copyrightViewIsOpened: UIEventSource - public readonly currentViewControlIsOpened: UIEventSource + + public readonly welcomeMessageIsOpened: UIEventSource = new UIEventSource( + false + ) + public readonly downloadControlIsOpened: UIEventSource = new UIEventSource( + false + ) + public readonly filterViewIsOpened: UIEventSource = new UIEventSource(false) + public readonly copyrightViewIsOpened: UIEventSource = new UIEventSource( + false + ) + public readonly currentViewControlIsOpened: UIEventSource = new UIEventSource( + false + ) + public readonly userInfoIsOpened: UIEventSource = new UIEventSource(false) public readonly welcomeMessageOpenedTab: UIEventSource - public readonly allFullScreenStates: UIEventSource[] = [] 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) - } - }) - } - } } } diff --git a/UI/Image/ImageUploadFlow.ts b/UI/Image/ImageUploadFlow.ts index 0f7dcc7df..26759ea85 100644 --- a/UI/Image/ImageUploadFlow.ts +++ b/UI/Image/ImageUploadFlow.ts @@ -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>() @@ -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 diff --git a/UI/LoggedInUserIndicator.ts b/UI/LoggedInUserIndicator.ts new file mode 100644 index 000000000..4d0ca0ac4 --- /dev/null +++ b/UI/LoggedInUserIndicator.ts @@ -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" + ) + }) + ) + } +} diff --git a/UI/Popup/DeleteWizard.ts b/UI/Popup/DeleteWizard.ts index ed999b066..13e736087 100644 --- a/UI/Popup/DeleteWizard.ts +++ b/UI/Popup/DeleteWizard.ts @@ -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( diff --git a/UI/Popup/LanguageElement.ts b/UI/Popup/LanguageElement.ts index 15c6396f7..ffad59189 100644 --- a/UI/Popup/LanguageElement.ts +++ b/UI/Popup/LanguageElement.ts @@ -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()}'" diff --git a/UI/Popup/LoginButton.ts b/UI/Popup/LoginButton.ts index 872e143b4..e5c651fc3 100644 --- a/UI/Popup/LoginButton.ts +++ b/UI/Popup/LoginButton.ts @@ -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 } ) { 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> = { + 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] + ) ) } } diff --git a/UI/Popup/MoveWizard.ts b/UI/Popup/MoveWizard.ts index f1a617590..d643b799e 100644 --- a/UI/Popup/MoveWizard.ts +++ b/UI/Popup/MoveWizard.ts @@ -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() + }) } } diff --git a/UI/Popup/QuestionBox.ts b/UI/Popup/QuestionBox.ts index 7053a4a68..669f9a432 100644 --- a/UI/Popup/QuestionBox.ts +++ b/UI/Popup/QuestionBox.ts @@ -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() } } diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index df43c0969..036adec1c 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -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 } ) { 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( diff --git a/UI/Reviews/ReviewForm.ts b/UI/Reviews/ReviewForm.ts index 0a7ccda2a..101de512f 100644 --- a/UI/Reviews/ReviewForm.ts +++ b/UI/Reviews/ReviewForm.ts @@ -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 { IsSelected: UIEventSource = new UIEventSource(false) @@ -20,11 +21,21 @@ export default class ReviewForm extends InputElement { private _saveButton: BaseUIElement private readonly _isAffiliated: BaseUIElement private readonly _postingAs: BaseUIElement - private readonly _osmConnection: OsmConnection + private readonly _state: { + readonly osmConnection: OsmConnection + readonly featureSwitchUserbadge: Store + } - constructor(onSave: (r: Review, doneSaving: () => void) => void, osmConnection: OsmConnection) { + constructor( + onSave: (r: Review, doneSaving: () => void) => void, + state: { + readonly osmConnection: OsmConnection + readonly featureSwitchUserbadge: Store + } + ) { super() - this._osmConnection = osmConnection + this._state = state + const osmConnection = state.osmConnection this._value = new UIEventSource({ made_by_user: new UIEventSource(true), rating: undefined, @@ -112,12 +123,11 @@ export default class ReviewForm extends InputElement { " 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 { diff --git a/UI/ShowDataLayer/ShowDataLayerImplementation.ts b/UI/ShowDataLayer/ShowDataLayerImplementation.ts index 8febac169..2db66af1c 100644 --- a/UI/ShowDataLayer/ShowDataLayerImplementation.ts +++ b/UI/ShowDataLayer/ShowDataLayerImplementation.ts @@ -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, @@ -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) => 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(e) + Utils.preventDefaultOnMouseEvent(e.originalEvent) + }, + dblclick: (e) => { + const o = self.leafletLayersPerId.get(feature?.properties?.id) + o?.activateFunc(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, diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 9182383db..56ae3cf7c 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -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) }, diff --git a/UI/i18n/Translations.ts b/UI/i18n/Translations.ts index dc8080903..3002ef56d 100644 --- a/UI/i18n/Translations.ts +++ b/UI/i18n/Translations.ts @@ -42,7 +42,13 @@ export default class Translations { * */ static T( - t: string | undefined | null | Translation | TypedTranslation, + t: + | string + | Record + | undefined + | null + | Translation + | TypedTranslation, context = undefined ): TypedTranslation { if (t === undefined || t === null) { diff --git a/Utils.ts b/Utils.ts index 8817853e6..6fae645c5 100644 --- a/Utils.ts +++ b/Utils.ts @@ -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 { + ): 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) diff --git a/assets/layers/atm/atm.json b/assets/layers/atm/atm.json index 7fada727e..6775bb025 100644 --- a/assets/layers/atm/atm.json +++ b/assets/layers/atm/atm.json @@ -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 @@ ] } ] -} \ No newline at end of file +} diff --git a/assets/layers/bank/bank.json b/assets/layers/bank/bank.json new file mode 100644 index 000000000..0d589629b --- /dev/null +++ b/assets/layers/bank/bank.json @@ -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 not 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" + }] + } + ] +} diff --git a/assets/layers/bank/bank.svg b/assets/layers/bank/bank.svg new file mode 100644 index 000000000..b4145b19d --- /dev/null +++ b/assets/layers/bank/bank.svg @@ -0,0 +1,22 @@ + + + + image/svg+xml + + + + + + + + + diff --git a/assets/layers/bank/license_info.json b/assets/layers/bank/license_info.json new file mode 100644 index 000000000..e2c96540f --- /dev/null +++ b/assets/layers/bank/license_info.json @@ -0,0 +1,12 @@ +[ + { + "path": "bank.svg", + "license": "CC0", + "authors": [ + "nebulon42" + ], + "sources": [ + "https://github.com/gmgeo/osmic/blob/master/money/bank-14.svg" + ] + } +] \ No newline at end of file diff --git a/assets/layers/climbing_gym/climbing_gym.json b/assets/layers/climbing_gym/climbing_gym.json index 119e7b7f3..237f3aab5 100644 --- a/assets/layers/climbing_gym/climbing_gym.json +++ b/assets/layers/climbing_gym/climbing_gym.json @@ -226,5 +226,17 @@ ] } } + ], + "presets": [ + { + "title": { + "en": "Climbing gym", + "nl": "Klimzaal" + }, + "tags": [ + "leisure=sports_centre", + "sport=climbing" + ] + } ] } \ No newline at end of file diff --git a/assets/layers/elevator/elevator.json b/assets/layers/elevator/elevator.json index 620ae73ac..3ba73772e 100644 --- a/assets/layers/elevator/elevator.json +++ b/assets/layers/elevator/elevator.json @@ -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 @@ ] } ] -} \ No newline at end of file +} diff --git a/assets/layers/entrance/entrance.json b/assets/layers/entrance/entrance.json index d677f7182..3f467621d 100644 --- a/assets/layers/entrance/entrance.json +++ b/assets/layers/entrance/entrance.json @@ -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 @@ ] } ] -} \ No newline at end of file +} diff --git a/assets/layers/walls_and_buildings/walls_and_buildings.json b/assets/layers/walls_and_buildings/walls_and_buildings.json index 86461de30..a94389e62 100644 --- a/assets/layers/walls_and_buildings/walls_and_buildings.json +++ b/assets/layers/walls_and_buildings/walls_and_buildings.json @@ -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~*" } ] -} \ No newline at end of file +} diff --git a/assets/layoutconfigmeta.json b/assets/layoutconfigmeta.json index bb9d964ad..936df2964 100644 --- a/assets/layoutconfigmeta.json +++ b/assets/layoutconfigmeta.json @@ -3365,6 +3365,746 @@ "type": "string", "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" }, + { + "path": [ + "layers", + "mapRendering", + "css" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A snippet of css code" + }, + { + "path": [ + "layers", + "mapRendering", + "css" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "group" + ], + "type": "string", + "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "description" + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "render" + ], + "typeHint": "rendered", + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "mappings", + "then" + ], + "typeHint": "rendered", + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "css", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A snippet of css-classes. They can be space-separated" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "group" + ], + "type": "string", + "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "description" + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "render" + ], + "typeHint": "rendered", + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "mappings", + "then" + ], + "typeHint": "rendered", + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "cssClasses", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, { "path": [ "layers", @@ -8064,6 +8804,796 @@ "type": "string", "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A snippet of css code" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "group" + ], + "type": "string", + "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "description" + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "render" + ], + "typeHint": "rendered", + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings", + "then" + ], + "typeHint": "rendered", + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A snippet of css-classes. They can be space-separated" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "group" + ], + "type": "string", + "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "description" + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "render" + ], + "typeHint": "rendered", + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "then" + ], + "typeHint": "rendered", + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, { "path": [ "layers", @@ -15712,6 +17242,796 @@ "type": "string", "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A snippet of css code" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "group" + ], + "type": "string", + "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "description" + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "render" + ], + "typeHint": "rendered", + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings", + "then" + ], + "typeHint": "rendered", + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "css", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses" + ], + "type": [ + { + "$ref": "#/definitions/TagRenderingConfigJson" + }, + { + "type": "string" + } + ], + "description": "A snippet of css-classes. They can be space-separated" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses" + ], + "type": "object", + "description": "A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.\nFor an _editable_ tagRendering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "id" + ], + "type": "string", + "description": "The id of the tagrendering, should be an unique string.\nUsed to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.\n\nUse 'questions' to trigger the question box of this group (if a group is defined)" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "group" + ], + "type": "string", + "description": "If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.\nThe first tagRendering of a group will always be a sticky element." + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "labels" + ], + "type": "array", + "description": "A list of labels. These are strings that are used for various purposes, e.g. to filter them away" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "description" + ], + "description": "A human-readable text explaining what this tagRendering does" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "render" + ], + "typeHint": "rendered", + "description": "Renders this value. Note that \"{key}\"-parts are substituted by the corresponding values of the element.\nIf neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.\n\nNote that this is a HTML-interpreted value, so you can add links as e.g. '{website}' or include images such as `This is of type A
`\ntype: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "$ref": "#/definitions/OrTagConfigJson", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "type": "string" + } + ], + "description": "Only show this tagrendering (or ask the question) if the selected object also matches the tags specified as `condition`.\n\nThis is useful to ask a follow-up question.\nFor example, within toilets, asking _where_ the diaper changing table is is only useful _if_ there is one.\nThis can be done by adding `\"condition\": \"changing_table=yes\"`\n\nA full example would be:\n```json\n {\n \"question\": \"Where is the changing table located?\",\n \"render\": \"The changing table is located at {changing_table:location}\",\n \"condition\": \"changing_table=yes\",\n \"freeform\": {\n \"key\": \"changing_table:location\",\n \"inline\": true\n },\n \"mappings\": [\n {\n \"then\": \"The changing table is in the toilet for women.\",\n \"if\": \"changing_table:location=female_toilet\"\n },\n {\n \"then\": \"The changing table is in the toilet for men.\",\n \"if\": \"changing_table:location=male_toilet\"\n },\n {\n \"if\": \"changing_table:location=wheelchair_toilet\",\n \"then\": \"The changing table is in the toilet for wheelchair users.\",\n },\n {\n \"if\": \"changing_table:location=dedicated_room\",\n \"then\": \"The changing table is in a dedicated room. \",\n }\n ],\n \"id\": \"toilet-changing_table:location\"\n },\n```" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "and" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "and" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "or" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "condition", + "or" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "freeform" + ], + "type": "object", + "description": "Allow freeform text input from the user" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "freeform", + "key" + ], + "type": "string", + "description": "If this key is present, then 'render' is used to display the value.\nIf this is undefined, the rendering is _always_ shown" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings" + ], + "type": "array", + "description": "Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "if" + ], + "type": [ + { + "$ref": "#/definitions/AndTagConfigJson" + }, + { + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation", + "type": "object", + "properties": { + "or": { + "type": "array", + "items": { + "$ref": "#/definitions/TagConfigJson" + } + } + }, + "required": [ + "or" + ] + }, + { + "type": "string" + } + ], + "description": "The main representation of Tags.\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for more documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, a single of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "if" + ], + "type": "object", + "description": "Chain many tags, to match, all of these should be true\nSee https://github.com/pietervdvn/MapComplete/blob/develop/Docs/Tags_format.md for documentation" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "then" + ], + "typeHint": "rendered", + "description": "If the condition `if` is met, the text `then` will be rendered.\nIf not known yet, the user will be presented with `then` as an option\nType: rendered" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "icon" + ], + "typeHint": "icon", + "type": [ + { + "type": "object", + "properties": { + "path": { + "description": "The path to the icon\nType: icon", + "type": "string" + }, + "class": { + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)", + "type": "string" + } + }, + "required": [ + "class", + "path" + ] + }, + { + "type": "string" + } + ], + "description": "An icon supporting this mapping; typically shown pretty small\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "icon", + "path" + ], + "typeHint": "icon", + "type": "string", + "description": "The path to the icon\nType: icon" + }, + { + "path": [ + "layers", + "mapRendering", + "renderings", + "cssClasses", + "mappings", + "icon", + "class" + ], + "type": "string", + "description": "A hint to mapcomplete on how to render this icon within the mapping.\nThis is translated to 'mapping-icon-', so defining your own in combination with a custom CSS is possible (but discouraged)" + }, { "path": [ "layers", @@ -15805,10 +18125,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": [ @@ -20882,7 +23199,7 @@ "layers", "units" ], - "type": "array", + "type": "object", "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" }, { @@ -20910,7 +23227,7 @@ "applicableUnits" ], "type": "array", - "description": "The possible denominations" + "description": "The possible denominations for this unit.\nFor length, denominations could be \"meter\", \"kilometer\", \"miles\", \"foot\"" }, { "path": [ @@ -20932,26 +23249,6 @@ ], "description": "If this evaluates to true and the value to interpret has _no_ unit given, assumes that this unit is meant.\nAlternatively, a list of country codes can be given where this acts as the default interpretation\n\nE.g., a denomination using \"meter\" would probably set this flag to \"true\";\na denomination for \"mp/h\" will use the condition \"_country=gb\" to indicate that it is the default in the UK.\n\nIf none of the units indicate that they are the default, the first denomination will be used instead" }, - { - "path": [ - "layers", - "units", - "applicableUnits", - "useAsDefaultInput" - ], - "type": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "boolean" - } - ], - "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" - }, { "path": [ "layers", @@ -20970,7 +23267,7 @@ "canonicalDenominationSingular" ], "type": "string", - "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" }, { "path": [ @@ -20980,7 +23277,7 @@ "alternativeDenomination" ], "type": "array", - "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" }, { "path": [ @@ -20989,6 +23286,14 @@ "applicableUnits", "human" ], + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], "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}" }, { @@ -20998,7 +23303,15 @@ "applicableUnits", "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}" + "type": [ + { + "$ref": "#/definitions/Record" + }, + { + "type": "string" + } + ], + "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}" }, { "path": [ @@ -21010,6 +23323,15 @@ "type": "boolean", "description": "If set, then the canonical value will be prefixed instead, e.g. for '€'\nNote that if all values use 'prefix', the dropdown might move to before the text field" }, + { + "path": [ + "layers", + "units", + "defaultInput" + ], + "type": "string", + "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" + }, { "path": [ "layers", diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index 3993d46d4..ea9242503 100644 --- a/assets/tagRenderings/questions.json +++ b/assets/tagRenderings/questions.json @@ -284,7 +284,7 @@ "da": "Hvad er webstedet for {title()}?", "cs": "Jaká je webová stránka {title()}?" }, - "render": "{website}", + "render": "{website}", "freeform": { "key": "website", "type": "url", @@ -295,7 +295,7 @@ "mappings": [ { "if": "contact:website~*", - "then": "{contact:website}", + "then": "{contact:website}", "hideInAnswer": true } ] @@ -1711,4 +1711,4 @@ "fr": "Le nom du réseau est {internet_access:ssid}" } } -} \ No newline at end of file +} diff --git a/assets/themes/atm/atm.json b/assets/themes/atm/atm.json index f0f4ebc98..dce56a1c5 100644 --- a/assets/themes/atm/atm.json +++ b/assets/themes/atm/atm.json @@ -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" + } + } + } ] -} \ No newline at end of file +} diff --git a/assets/themes/blind_osm/blind_osm.json b/assets/themes/blind_osm/blind_osm.json index 56593d2b3..1115f7aca 100644 --- a/assets/themes/blind_osm/blind_osm.json +++ b/assets/themes/blind_osm/blind_osm.json @@ -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" ] -} \ No newline at end of file +} diff --git a/assets/themes/facadegardens/facadegardens.json b/assets/themes/facadegardens/facadegardens.json index 7a1dc5fa4..bc83d955e 100644 --- a/assets/themes/facadegardens/facadegardens.json +++ b/assets/themes/facadegardens/facadegardens.json @@ -551,8 +551,10 @@ "centroid" ] } - ] + ], + "deletion": true, + "allowMove": { "enableImproveAccuracy": true, "enableRelocation": false } } ], "credits": "joost schouppe; stla" -} \ No newline at end of file +} diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index 9d1212f18..f20b7451f 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -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; } diff --git a/index.css b/index.css index 6a2fb0a3f..2b5e5df62 100644 --- a/index.css +++ b/index.css @@ -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; } diff --git a/langs/en.json b/langs/en.json index 4df907c39..841a6e282 100644 --- a/langs/en.json +++ b/langs/en.json @@ -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", diff --git a/test/Models/Units.spec.ts b/test/Models/Units.spec.ts index 1490ba6a1..513744f31 100644 --- a/test/Models/Units.spec.ts +++ b/test/Models/Units.spec.ts @@ -14,6 +14,7 @@ describe("Unit", () => { nl: " megawatt", }, }, + false, "test" )