From b81fa1499940defa46c9f35acfa832e65f8acf69 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 31 Dec 2022 01:56:39 +0100 Subject: [PATCH 01/38] Keep geolocation displayed, even if it isn't known anymore --- Logic/Actors/GeoLocationHandler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index 15ad99aa3..2354ee5c3 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -120,9 +120,9 @@ export default class GeoLocationHandler { const state = this._state this.geolocationState.currentGPSLocation.addCallbackAndRun((location) => { if (location === undefined) { - state.currentUserLocation?.features?.setData([]) return } + console.log("GeoLocationCoordinates are", location) const feature = { type: "Feature", properties: { From 2c21e125d9d5dfbf95582356a4b36bcf84b7055c Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 31 Dec 2022 02:22:44 +0100 Subject: [PATCH 02/38] Remove console.Log --- Logic/Actors/GeoLocationHandler.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index 2354ee5c3..6f702b768 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -122,7 +122,6 @@ export default class GeoLocationHandler { if (location === undefined) { return } - console.log("GeoLocationCoordinates are", location) const feature = { type: "Feature", properties: { From bb140025bfa21832a33c393969800e905fbf77ff Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 31 Dec 2022 02:42:59 +0100 Subject: [PATCH 03/38] Add a very basic 'bank'-layer which only shows ATM-status, add banks with ATM to ATM-theme, make ATMs moveable and deletable; fix #1207 --- assets/layers/atm/atm.json | 53 +++++++-------------- assets/layers/bank/bank.json | 70 ++++++++++++++++++++++++++++ assets/layers/bank/bank.svg | 22 +++++++++ assets/layers/bank/license_info.json | 12 +++++ assets/themes/atm/atm.json | 30 +++++++++++- 5 files changed, 148 insertions(+), 39 deletions(-) create mode 100644 assets/layers/bank/bank.json create mode 100644 assets/layers/bank/bank.svg create mode 100644 assets/layers/bank/license_info.json diff --git a/assets/layers/atm/atm.json b/assets/layers/atm/atm.json index 807d65718..47200d678 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,25 +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" - } - }, + "images", { "id": "name", "render": { @@ -139,12 +111,7 @@ "nl": "Deze geldautomaat wordt beheerd door {operator}" } }, - { - "builtin": "opening_hours", - "override": { - "condition": "amenity!=bank" - } - }, + "opening_hours", { "id": "cash_out", "question": { @@ -262,6 +229,18 @@ ] } ], + "allowMove": { + "enableImproveAccuracy": true, + "enableRelocation": false + }, + "deletion": { + "softDeletionTags": { + "and": [ + "disused:amenity=atm", + "amenity=" + ] + } + }, "filter": [ "open_now", { @@ -277,4 +256,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/themes/atm/atm.json b/assets/themes/atm/atm.json index 859a83a8f..2e7c30f7d 100644 --- a/assets/themes/atm/atm.json +++ b/assets/themes/atm/atm.json @@ -15,6 +15,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 +} From f2a1dcf8d8a3a0b78478b332a3aedb35a952a35b Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 31 Dec 2022 02:53:25 +0100 Subject: [PATCH 04/38] Fix #1209 --- UI/Popup/LanguageElement.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) 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()}'" From a4239ba187b49e027876719f4fbe6282af77d0bd Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 31 Dec 2022 03:04:35 +0100 Subject: [PATCH 05/38] Blind-osm: leave maprendering of roads in place, but use shownByDefault:false instead to hide it on the main map. Fix #1205 --- assets/themes/blind_osm/blind_osm.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 +} From 8fc63d8908e9095a1d8fc1c326114cbdc06b6589 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 31 Dec 2022 03:31:57 +0100 Subject: [PATCH 06/38] Add rudimentary Sponsors-overview, fix #1200 --- Docs/Sponsors.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 Docs/Sponsors.md 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. From 18399bafb07416515f57c1ff2dddaa87654f0d95 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Sat, 31 Dec 2022 15:11:12 +0100 Subject: [PATCH 07/38] Add 'rel=nofollow noopener noreferrer' to external links --- assets/tagRenderings/questions.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/tagRenderings/questions.json b/assets/tagRenderings/questions.json index 2d84385d1..c52e09640 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 } ] @@ -1676,4 +1676,4 @@ "es": "El nombre de red es {internet_access:ssid}" } } -} \ No newline at end of file +} From 03057b2eff4ce356bbe0603d733e5d3f74ad4605 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 2 Jan 2023 02:34:15 +0100 Subject: [PATCH 08/38] Allow Record as input for Translations.T --- UI/i18n/Translations.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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) { From 67b5a33f0b5b6a7e4445d27be9c071dada8c11d4 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 2 Jan 2023 02:35:40 +0100 Subject: [PATCH 09/38] Move 'defaultInputUnit' into Unit, away from denomination --- Models/Denomination.ts | 8 +- Models/ThemeConfig/Json/LayerConfigJson.ts | 57 +----------- Models/ThemeConfig/Json/UnitConfigJson.ts | 87 ++++++++++++++++--- Models/Unit.ts | 51 +++++++++-- assets/layers/elevator/elevator.json | 4 +- assets/layers/entrance/entrance.json | 4 +- assets/layers/units/units.json | 3 + .../walls_and_buildings.json | 4 +- test/Models/Units.spec.ts | 1 + 9 files changed, 135 insertions(+), 84 deletions(-) create mode 100644 assets/layers/units/units.json 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..90661ecc9 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 // => false + * + * // 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,13 @@ 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.trim() === json.defaultInput, + `${ctx}.units[${i}]` + ) ) return new Unit(appliesTo, applicable, json.eraseInvalidValues ?? false) } 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/units/units.json b/assets/layers/units/units.json new file mode 100644 index 000000000..0db3279e4 --- /dev/null +++ b/assets/layers/units/units.json @@ -0,0 +1,3 @@ +{ + +} 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/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" ) From 23a1fe99e3810f7953ba62e37df072d5b0f03230 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 2 Jan 2023 02:36:45 +0100 Subject: [PATCH 10/38] Update schemas --- .../DenominationConfigJson.schema.json | 42 +- Docs/Schemas/DenominationConfigJsonJSC.ts | 41 +- Docs/Schemas/LayerConfigJson.schema.json | 77 +- Docs/Schemas/LayerConfigJsonJSC.ts | 76 +- Docs/Schemas/LayoutConfigJson.schema.json | 77 +- Docs/Schemas/LayoutConfigJsonJSC.ts | 76 +- .../LineRenderingConfigJson.schema.json | 42 +- Docs/Schemas/LineRenderingConfigJsonJSC.ts | 41 +- Docs/Schemas/MappingConfigJson.schema.json | 42 +- Docs/Schemas/MappingConfigJsonJSC.ts | 41 +- Docs/Schemas/MoveConfigJson.schema.json | 42 +- Docs/Schemas/MoveConfigJsonJSC.ts | 41 +- .../PointRenderingConfigJson.schema.json | 64 +- Docs/Schemas/PointRenderingConfigJsonJSC.ts | 63 +- ...tionableTagRenderingConfigJson.schema.json | 42 +- .../QuestionableTagRenderingConfigJsonJSC.ts | 41 +- Docs/Schemas/RewritableConfigJson.schema.json | 42 +- Docs/Schemas/RewritableConfigJsonJSC.ts | 41 +- Docs/Schemas/TilesourceConfigJson.schema.json | 71 +- Docs/Schemas/TilesourceConfigJsonJSC.ts | 70 +- Docs/Schemas/UnitConfigJson.schema.json | 49 +- Docs/Schemas/UnitConfigJsonJSC.ts | 48 +- assets/layoutconfigmeta.json | 2380 ++++++++++++++++- 23 files changed, 3096 insertions(+), 453 deletions(-) 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/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", From cfed0f6f5f0e91c2956918b960eeffc2584dc57d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 2 Jan 2023 19:46:23 +0100 Subject: [PATCH 11/38] Remove empty file --- assets/layers/units/units.json | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 assets/layers/units/units.json diff --git a/assets/layers/units/units.json b/assets/layers/units/units.json deleted file mode 100644 index 0db3279e4..000000000 --- a/assets/layers/units/units.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - -} From d13a0769bc3849a30f70008fd462e5c135099552 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 2 Jan 2023 21:18:07 +0100 Subject: [PATCH 12/38] Add usertesting and first personas, see #1180 --- Docs/Personas/Xavier.md | 31 +++++++++ Docs/Personas/_README.md | 8 +++ Docs/Personas/_TEMPLATE.md | 20 ++++++ .../2023-01-02 Ad Hoc - cyclestreets.md | 63 +++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 Docs/Personas/Xavier.md create mode 100644 Docs/Personas/_README.md create mode 100644 Docs/Personas/_TEMPLATE.md create mode 100644 Docs/UserTests/2023-01-02 Ad Hoc - cyclestreets.md 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/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 From 9f3ea0581a235ee4e66a7b550999059a21eab442 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Mon, 2 Jan 2023 21:19:01 +0100 Subject: [PATCH 13/38] Use 'useIfNoUnitSet' as 'defaultUnit' again --- Models/Unit.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Models/Unit.ts b/Models/Unit.ts index 90661ecc9..e6ab43a4e 100644 --- a/Models/Unit.ts +++ b/Models/Unit.ts @@ -116,7 +116,9 @@ export class Unit { (u, i) => new Denomination( u, - u.canonicalDenomination.trim() === json.defaultInput, + u.canonicalDenomination === undefined + ? undefined + : u.canonicalDenomination.trim() === json.defaultInput, `${ctx}.units[${i}]` ) ) From 65f8a4c3e4f6df031aa77ebcee9511d4b0401a7a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 3 Jan 2023 00:36:44 +0100 Subject: [PATCH 14/38] Fix unit tests --- Models/Unit.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Models/Unit.ts b/Models/Unit.ts index e6ab43a4e..74862557f 100644 --- a/Models/Unit.ts +++ b/Models/Unit.ts @@ -77,9 +77,9 @@ export class Unit { * ] * },"test") * }catch(e){ - * threwError =true + * threwError = true * } - * threwError // => false + * threwError // => true * * // Should work * Unit.fromJson({ From b4f739e5062381f92ea6c38e54db3c471e754fdb Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 3 Jan 2023 01:01:59 +0100 Subject: [PATCH 15/38] Use translated text on disabled button of splitRoadWizard --- UI/Popup/SplitRoadWizard.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index df43c0969..ab963a8f5 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -113,7 +113,7 @@ export default class SplitRoadWizard extends Combine { }) 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( From 98866b4a5775648316a00c5bccce4b30fc91a255 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 3 Jan 2023 02:24:03 +0100 Subject: [PATCH 16/38] User flow improvements for the theme introduction panel --- Logic/Osm/OsmConnection.ts | 3 +- Logic/UIEventSource.ts | 6 +- UI/BigComponents/FullWelcomePaneWithTabs.ts | 17 ++-- UI/BigComponents/ThemeIntroductionPanel.ts | 17 +++- UI/BigComponents/UserInformation.ts | 17 ++-- UI/DefaultGUI.ts | 14 +++- UI/DefaultGuiState.ts | 12 ++- UI/LoggedInUserIndicator.ts | 42 ++++++++++ css/index-tailwind-output.css | 88 ++++++++++++--------- langs/en.json | 2 +- 10 files changed, 156 insertions(+), 62 deletions(-) create mode 100644 UI/LoggedInUserIndicator.ts diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 692421b3f..6e294d2bc 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -62,7 +62,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 +71,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 = 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/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/ThemeIntroductionPanel.ts b/UI/BigComponents/ThemeIntroductionPanel.ts index 75b4fd1e2..09669d693 100644 --- a/UI/BigComponents/ThemeIntroductionPanel.ts +++ b/UI/BigComponents/ThemeIntroductionPanel.ts @@ -9,6 +9,7 @@ import Svg from "../../Svg" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import { OsmConnection } from "../../Logic/Osm/OsmConnection" import FullWelcomePaneWithTabs from "./FullWelcomePaneWithTabs" +import LoggedInUserIndicator from "../LoggedInUserIndicator" export default class ThemeIntroductionPanel extends Combine { constructor( @@ -20,7 +21,8 @@ export default class ThemeIntroductionPanel extends Combine { featureSwitchUserbadge: UIEventSource layoutToUse: LayoutConfig osmConnection: OsmConnection - } + }, + guistate?: { userInfoIsOpened: UIEventSource } ) { const t = Translations.t.general const layout = state.layoutToUse @@ -36,9 +38,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"), @@ -60,7 +71,7 @@ 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"), 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..b0f319ff8 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 @@ -205,7 +204,9 @@ export default class DefaultGUI { const self = this new Combine([ Toggle.If(state.featureSwitchUserbadge, () => { - const userInfo = new UserInformationPanel(state) + const userInfo = new UserInformationPanel(state, { + isOpened: guiState.userInfoIsOpened, + }) const mapControl = new MapControlButton( new VariableUiElement( @@ -219,7 +220,7 @@ export default class DefaultGUI { { dontStyle: true, } - ).onClick(() => userInfo.Activate()) + ).onClick(() => guiState.userInfoIsOpened.setData(true)) return new LoginToggle( mapControl, @@ -292,7 +293,12 @@ export default class DefaultGUI { 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..43c2c94de 100644 --- a/UI/DefaultGuiState.ts +++ b/UI/DefaultGuiState.ts @@ -9,6 +9,7 @@ export class DefaultGuiState { public readonly filterViewIsOpened: UIEventSource public readonly copyrightViewIsOpened: UIEventSource public readonly currentViewControlIsOpened: UIEventSource + public readonly userInfoIsOpened: UIEventSource public readonly welcomeMessageOpenedTab: UIEventSource public readonly allFullScreenStates: UIEventSource[] = [] @@ -43,8 +44,14 @@ export class DefaultGuiState { this.currentViewControlIsOpened = QueryParameters.GetBooleanQueryParameter( "currentview-toggle", false, - "Whether or not the current view box is shown" + "Whether or not the current view box is shown (metalayer showing current view, allows to do calculate stats for all in view)" ) + this.userInfoIsOpened = QueryParameters.GetBooleanQueryParameter( + "userinfo-toggle", + false, + "Whether or not the user info is shown" + ) + const states = { download: this.downloadControlIsOpened, filters: this.filterViewIsOpened, @@ -66,7 +73,8 @@ export class DefaultGuiState { this.filterViewIsOpened, this.copyrightViewIsOpened, this.welcomeMessageIsOpened, - this.currentViewControlIsOpened + this.currentViewControlIsOpened, + this.userInfoIsOpened ) for (let i = 0; i < this.allFullScreenStates.length; i++) { 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/css/index-tailwind-output.css b/css/index-tailwind-output.css index e76ec2439..e67dc4498 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -624,6 +624,10 @@ video { position: relative; } +.\!relative { + position: relative !important; +} + .sticky { position: -webkit-sticky; position: sticky; @@ -807,18 +811,22 @@ video { margin-top: 0.25rem; } -.mt-4 { - margin-top: 1rem; -} - -.ml-3 { - margin-left: 0.75rem; -} - .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; } @@ -839,6 +843,10 @@ video { margin-left: 0.5rem; } +.ml-3 { + margin-left: 0.75rem; +} + .ml-12 { margin-left: 3rem; } @@ -960,6 +968,18 @@ video { height: 16rem; } +.h-8 { + height: 2rem; +} + +.h-16 { + height: 4rem; +} + +.h-32 { + height: 8rem; +} + .h-10 { height: 2.5rem; } @@ -988,22 +1008,10 @@ video { height: 1.5rem; } -.h-8 { - height: 2rem; -} - -.h-32 { - height: 8rem; -} - .h-96 { height: 24rem; } -.h-16 { - height: 4rem; -} - .h-0 { height: 0px; } @@ -1048,6 +1056,18 @@ video { width: 1.5rem; } +.w-8 { + width: 2rem; +} + +.w-16 { + width: 4rem; +} + +.w-32 { + width: 8rem; +} + .w-10 { width: 2.5rem; } @@ -1072,10 +1092,6 @@ video { width: 2.75rem; } -.w-8 { - width: 2rem; -} - .w-min { width: -webkit-min-content; width: min-content; @@ -1090,14 +1106,6 @@ video { width: 24rem; } -.w-32 { - width: 8rem; -} - -.w-16 { - width: 4rem; -} - .w-auto { width: auto; } @@ -1374,6 +1382,10 @@ video { border-bottom-width: 1px; } +.border-dotted { + border-style: dotted; +} + .border-black { --tw-border-opacity: 1; border-color: rgb(0 0 0 / var(--tw-border-opacity)); @@ -2738,6 +2750,10 @@ input { border-radius: 0.75rem; } + .md\:border-t-2 { + border-top-width: 2px; + } + .md\:p-1 { padding: 0.25rem; } diff --git a/langs/en.json b/langs/en.json index 0d63cc6f8..a67879fcf 100644 --- a/langs/en.json +++ b/langs/en.json @@ -327,7 +327,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", From 017fccf861d11748102e37b6f9b8242c1d9a446b Mon Sep 17 00:00:00 2001 From: flaburgan Date: Tue, 3 Jan 2023 12:54:23 +0100 Subject: [PATCH 17/38] Improve README --- Docs/Development_deployment.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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. From 09507b353775bb500231bc3b0cec5992deb2a17d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 3 Jan 2023 23:45:04 +0100 Subject: [PATCH 18/38] Fix typo which prevented updating the selected element, improve comments and typings --- Logic/Actors/StrayClickHandler.ts | 4 +++- Logic/BBox.ts | 2 +- Logic/Osm/OsmObject.ts | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) 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/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( From 057c3fde4fa75b39fd6df1928bd19eb81324f894 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 4 Jan 2023 17:41:11 +0100 Subject: [PATCH 19/38] Right-clicking an element (if no presets are defined) will open up the popup of the element --- .../ShowDataLayerImplementation.ts | 124 ++++++++++-------- Utils.ts | 12 +- css/index-tailwind-output.css | 5 + index.css | 4 + 4 files changed, 90 insertions(+), 55 deletions(-) diff --git a/UI/ShowDataLayer/ShowDataLayerImplementation.ts b/UI/ShowDataLayer/ShowDataLayerImplementation.ts index 8febac169..c168fbeab 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) }) @@ -171,17 +174,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 +183,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 +248,7 @@ export default class ShowDataLayerImplementation { return self.geoLayer !== selfLayer }) } else { - this.geoLayer.addData(feat) + geojsonLayer.addData(feat) } } catch (e) { console.error( @@ -242,14 +261,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 +331,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 +351,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/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/css/index-tailwind-output.css b/css/index-tailwind-output.css index e67dc4498..f3ae9bd25 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -1876,6 +1876,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; } 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; } From a7da5d65cf1e03976a7606edc22f9e2931d53e72 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 4 Jan 2023 18:52:49 +0100 Subject: [PATCH 20/38] Close popup after splitting; scroll popup into view --- Logic/Actors/SelectedFeatureHandler.ts | 14 -------------- Logic/State/ElementsState.ts | 3 +++ UI/Popup/SplitRoadWizard.ts | 13 ++++++++++--- UI/ShowDataLayer/ShowDataLayerImplementation.ts | 4 ++++ 4 files changed, 17 insertions(+), 17 deletions(-) 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/State/ElementsState.ts b/Logic/State/ElementsState.ts index cecb04009..f4d0b1249 100644 --- a/Logic/State/ElementsState.ts +++ b/Logic/State/ElementsState.ts @@ -38,6 +38,9 @@ export default class ElementsState extends FeatureSwitchState { constructor(layoutToUse: LayoutConfig) { super(layoutToUse) + this.selectedElement.addCallbackAndRun((e) => { + console.trace("Selected element is now", e) + }) function localStorageSynced( key: string, deflt: number, diff --git a/UI/Popup/SplitRoadWizard.ts b/UI/Popup/SplitRoadWizard.ts index ab963a8f5..dbdac314c 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -24,6 +24,7 @@ import BaseLayer from "../../Models/BaseLayer" import FilteredLayer from "../../Models/FilteredLayer" import BaseUIElement from "../BaseUIElement" import { VariableUiElement } from "../Base/VariableUIElement" +import ScrollableFullScreen from "../Base/ScrollableFullScreen" export default class SplitRoadWizard extends Combine { // @ts-ignore @@ -54,6 +55,7 @@ export default class SplitRoadWizard extends Combine { changes: Changes layoutToUse: LayoutConfig allElements: ElementStorage + selectedElement: UIEventSource } ) { const t = Translations.t.split @@ -79,9 +81,6 @@ 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 @@ -110,6 +109,9 @@ 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") @@ -147,6 +149,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/ShowDataLayer/ShowDataLayerImplementation.ts b/UI/ShowDataLayer/ShowDataLayerImplementation.ts index c168fbeab..2db66af1c 100644 --- a/UI/ShowDataLayer/ShowDataLayerImplementation.ts +++ b/UI/ShowDataLayer/ShowDataLayerImplementation.ts @@ -115,6 +115,10 @@ export default class ShowDataLayerImplementation { }) this._selectedElement?.addCallbackAndRunD((selected) => { + if (selected === undefined) { + ScrollableFullScreen.collapse() + return + } self.openPopupOfSelectedElement(selected) }) From ede03a31e4a7fdc2fe8ded331726f63ebec2088f Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 4 Jan 2023 19:12:54 +0100 Subject: [PATCH 21/38] Scroll 'delete' and 'move' into view when expanded --- UI/Popup/DeleteWizard.ts | 7 +++++++ UI/Popup/MoveWizard.ts | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/UI/Popup/DeleteWizard.ts b/UI/Popup/DeleteWizard.ts index ed999b066..ff5950aff 100644 --- a/UI/Popup/DeleteWizard.ts +++ b/UI/Popup/DeleteWizard.ts @@ -176,6 +176,13 @@ export default class DeleteWizard extends Toggle { undefined, isShown ) + + const self = this + confirm.addCallbackAndRunD((dialogIsOpened) => { + if (dialogIsOpened) { + self.ScrollIntoView() + } + }) } private static constructConfirmButton( diff --git a/UI/Popup/MoveWizard.ts b/UI/Popup/MoveWizard.ts index f1a617590..8a11d97b6 100644 --- a/UI/Popup/MoveWizard.ts +++ b/UI/Popup/MoveWizard.ts @@ -284,5 +284,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() + }) } } From 02981b198228fb78b133cd1debd85c1e9b286844 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Wed, 4 Jan 2023 19:13:40 +0100 Subject: [PATCH 22/38] Remove debug logging --- Logic/State/ElementsState.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/Logic/State/ElementsState.ts b/Logic/State/ElementsState.ts index f4d0b1249..cecb04009 100644 --- a/Logic/State/ElementsState.ts +++ b/Logic/State/ElementsState.ts @@ -38,9 +38,6 @@ export default class ElementsState extends FeatureSwitchState { constructor(layoutToUse: LayoutConfig) { super(layoutToUse) - this.selectedElement.addCallbackAndRun((e) => { - console.trace("Selected element is now", e) - }) function localStorageSynced( key: string, deflt: number, From 2583790483101b7dd166749062f528bf58d91a2f Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 5 Jan 2023 01:05:04 +0100 Subject: [PATCH 23/38] Zoom in on right click --- UI/Base/MinimapImplementation.ts | 1 + 1 file changed, 1 insertion(+) 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) }) } From b504547fe37adcc576b97e65de535a8b0bbbc2fb Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 6 Jan 2023 03:27:26 +0100 Subject: [PATCH 24/38] Add preset to climbing gym --- assets/layers/climbing_gym/climbing_gym.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/assets/layers/climbing_gym/climbing_gym.json b/assets/layers/climbing_gym/climbing_gym.json index 79b81437d..04419ac92 100644 --- a/assets/layers/climbing_gym/climbing_gym.json +++ b/assets/layers/climbing_gym/climbing_gym.json @@ -210,5 +210,17 @@ ] } } + ], + "presets": [ + { + "title": { + "en": "Climbing gym", + "nl": "Klimzaal" + }, + "tags": [ + "leisure=sports_centre", + "sport=climbing" + ] + } ] } \ No newline at end of file From 29b0b2871bcf89bc94998b5b7c44d5596fa4b821 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 6 Jan 2023 03:30:18 +0100 Subject: [PATCH 25/38] Add awareness of 'api/capabilities', see #880 --- Logic/Osm/OsmConnection.ts | 45 +++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 6e294d2bc..fe2aa2e83 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" ) @@ -94,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 @@ -160,11 +175,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") @@ -504,4 +525,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 } + } } From e4c586ef99272bb650d081f9c285897aea33f6a6 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 6 Jan 2023 03:32:17 +0100 Subject: [PATCH 26/38] Loginbutton now shows a warning if the API is offline, fix #880 --- UI/Popup/LoginButton.ts | 75 ++++++++++++++++++++++++++--------- css/index-tailwind-output.css | 8 ++++ langs/en.json | 4 ++ 3 files changed, 69 insertions(+), 18 deletions(-) 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/css/index-tailwind-output.css b/css/index-tailwind-output.css index f3ae9bd25..94276247c 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -1382,6 +1382,10 @@ video { border-bottom-width: 1px; } +.border-b-2 { + border-bottom-width: 2px; +} + .border-dotted { border-style: dotted; } @@ -1561,6 +1565,10 @@ video { padding-right: 0.5rem; } +.pb-8 { + padding-bottom: 2rem; +} + .pl-5 { padding-left: 1.25rem; } diff --git a/langs/en.json b/langs/en.json index a67879fcf..cfe6d0d47 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 edit the map", "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": { From e31c87feab3b6a41bc565247e9fff5774981e5e2 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 6 Jan 2023 03:37:22 +0100 Subject: [PATCH 27/38] Use LoginToggle where possible, --- UI/AllThemesGui.ts | 9 ++-- UI/BigComponents/SimpleAddUI.ts | 14 +++--- UI/Image/ImageUploadFlow.ts | 11 ++--- UI/Popup/DeleteWizard.ts | 77 +++++++++++++++------------------ UI/Popup/MoveWizard.ts | 7 +-- UI/Popup/QuestionBox.ts | 43 ++++++++++-------- UI/Popup/SplitRoadWizard.ts | 8 +--- UI/Reviews/ReviewForm.ts | 30 ++++++++----- UI/SpecialVisualizations.ts | 2 +- 9 files changed, 101 insertions(+), 100 deletions(-) diff --git a/UI/AllThemesGui.ts b/UI/AllThemesGui.ts index 06195d6e4..7a4cd705a 100644 --- a/UI/AllThemesGui.ts +++ b/UI/AllThemesGui.ts @@ -13,6 +13,7 @@ import { SubtleButton } from "./Base/SubtleButton" import { VariableUiElement } from "./Base/VariableUIElement" import Svg from "../Svg" import { ImportViewerLinks } from "./BigComponents/UserInformation" +import {LoginToggle} from "./Popup/LoginButton"; export default class AllThemesGui { setup() { @@ -29,12 +30,10 @@ export default class AllThemesGui { intro, new FeaturedMessage().SetClass("mb-4 block"), new MoreScreen(state, true), - new Toggle( + new LoginToggle( undefined, - new SubtleButton(undefined, Translations.t.index.logIn) - .SetStyle("height:min-content") - .onClick(() => state.osmConnection.AttemptLogin()), - state.osmConnection.isLoggedIn + Translations.t.index.logIn, + state ), new ImportViewerLinks(state.osmConnection), Translations.t.general.aboutMapcomplete 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/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/Popup/DeleteWizard.ts b/UI/Popup/DeleteWizard.ts index ff5950aff..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,47 +165,7 @@ 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, diff --git a/UI/Popup/MoveWizard.ts b/UI/Popup/MoveWizard.ts index 8a11d97b6..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 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 dbdac314c..036adec1c 100644 --- a/UI/Popup/SplitRoadWizard.ts +++ b/UI/Popup/SplitRoadWizard.ts @@ -25,6 +25,7 @@ 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 @@ -82,12 +83,7 @@ export default class SplitRoadWizard extends Combine { ) ) - // 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 () => { 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/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) }, From 16aedfec4ad1df6c6de0082c1fff47ed89e59623 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 6 Jan 2023 03:40:55 +0100 Subject: [PATCH 28/38] Small fix: remove undefined from untranslated strings --- UI/BigComponents/TranslatorsPanel.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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}", From 9b0b87c878b1c0ebe2b009631c5d98cdba10544d Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 6 Jan 2023 03:42:12 +0100 Subject: [PATCH 29/38] Use loginToggle, disable upload is gpxApi is offline --- UI/BigComponents/UploadTraceToOsmUI.ts | 56 +++++++++++++++++--------- 1 file changed, 37 insertions(+), 19 deletions(-) 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 + } } From 297aabf2b7a06bfbc3e1a5c05621bdbda9076a68 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 6 Jan 2023 03:43:24 +0100 Subject: [PATCH 30/38] Improve typing, improve layout of MapillaryLink-button --- UI/BigComponents/MapillaryLink.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) 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") + ) }) ) } From 8c35bfd67bc6f601bdce29f2dbacf4b0aeeaf987 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 6 Jan 2023 03:44:04 +0100 Subject: [PATCH 31/38] Use GuiState type instead of fully typed states --- UI/BigComponents/LeftControls.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) 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(() => { From 3709dc323f5afedb5e4ba1ad990b2e49417ff820 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 6 Jan 2023 03:46:10 +0100 Subject: [PATCH 32/38] Move extra actions into themeExplanationpanel, switch userinfo and help buttons, remove obsolete scrollableTriggers --- UI/BigComponents/ActionButtons.ts | 59 +++++++++++++++ UI/BigComponents/CopyrightPanel.ts | 38 +--------- UI/BigComponents/ThemeIntroductionPanel.ts | 11 ++- UI/DefaultGUI.ts | 84 ++++++++++------------ 4 files changed, 105 insertions(+), 87 deletions(-) create mode 100644 UI/BigComponents/ActionButtons.ts 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/ThemeIntroductionPanel.ts b/UI/BigComponents/ThemeIntroductionPanel.ts index 09669d693..9c117a1a9 100644 --- a/UI/BigComponents/ThemeIntroductionPanel.ts +++ b/UI/BigComponents/ThemeIntroductionPanel.ts @@ -3,13 +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" export default class ThemeIntroductionPanel extends Combine { constructor( @@ -21,6 +24,9 @@ export default class ThemeIntroductionPanel extends Combine { featureSwitchUserbadge: UIEventSource layoutToUse: LayoutConfig osmConnection: OsmConnection + currentBounds: Store + locationControl: UIEventSource + isTranslator: Store }, guistate?: { userInfoIsOpened: UIEventSource } ) { @@ -74,7 +80,7 @@ export default class ThemeIntroductionPanel extends Combine { 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([ @@ -89,6 +95,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/DefaultGUI.ts b/UI/DefaultGUI.ts index b0f319ff8..079090e4e 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -55,6 +55,7 @@ export default class DefaultGUI { public setup() { this.SetupUIElements() this.SetupMap() + ScrollableFullScreen.ActivateCurrent() if ( this.state.layoutToUse.customCss !== undefined && @@ -202,41 +203,43 @@ export default class DefaultGUI { const guiState = this.guiState const self = this - new Combine([ - Toggle.If(state.featureSwitchUserbadge, () => { - const userInfo = new UserInformationPanel(state, { - isOpened: guiState.userInfoIsOpened, - }) - 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(() => guiState.userInfoIsOpened.setData(true)) + 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") @@ -274,21 +277,6 @@ 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 { From 8bffd2a9745035ae8eea310f849953ac4aec6433 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 6 Jan 2023 03:46:49 +0100 Subject: [PATCH 33/38] Rework initial behaviour of scrollableFullScreen --- UI/Base/ScrollableFullScreen.ts | 38 +++++++------- UI/DefaultGuiState.ts | 88 ++++++++------------------------- 2 files changed, 41 insertions(+), 85 deletions(-) diff --git a/UI/Base/ScrollableFullScreen.ts b/UI/Base/ScrollableFullScreen.ts index 90732719b..3c20d89ef 100644 --- a/UI/Base/ScrollableFullScreen.ts +++ b/UI/Base/ScrollableFullScreen.ts @@ -32,15 +32,12 @@ export default class ScrollableFullScreen { resetScrollSignal: UIEventSource }) => BaseUIElement, hashToShow: string, - isShown: UIEventSource = new UIEventSource(false), - options?: { - setHash?: true | boolean - } + isShown: UIEventSource = new UIEventSource(false) ) { this.hashToShow = hashToShow this.isShown = isShown - if (hashToShow === undefined) { + if (hashToShow === undefined || hashToShow === "") { throw "HashToShow should be defined as it is vital for the 'back' key functionality" } @@ -55,22 +52,17 @@ export default class ScrollableFullScreen { ) const self = this - const setHash = options?.setHash ?? true - if (setHash) { - Hash.hash.addCallback((h) => { - if (h === undefined) { - isShown.setData(false) - } - }) - } + Hash.hash.addCallback((h) => { + if (h === undefined) { + isShown.setData(false) + } + }) - 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) - } + Hash.hash.setData(hashToShow) ScrollableFullScreen._currentlyOpen = self self.Activate() } else { @@ -81,6 +73,11 @@ export default class ScrollableFullScreen { ScrollableFullScreen.collapse() } }) + if (isShown.data) { + Hash.hash.setData(hashToShow) + ScrollableFullScreen._currentlyOpen = self + this.Activate() + } } private static initEmpty(): FixedUiElement { @@ -115,6 +112,9 @@ export default class ScrollableFullScreen { */ public Activate(): void { this.isShown.setData(true) + if (this.hashToShow && this.hashToShow !== "") { + Hash.hash.setData(this.hashToShow) + } this._fullscreencomponent.AttachTo("fullscreen") const fs = document.getElementById("fullscreen") ScrollableFullScreen._currentlyOpen = this @@ -157,4 +157,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/DefaultGuiState.ts b/UI/DefaultGuiState.ts index 43c2c94de..cb780b1c4 100644 --- a/UI/DefaultGuiState.ts +++ b/UI/DefaultGuiState.ts @@ -4,14 +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 userInfoIsOpened: 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( @@ -21,75 +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 (metalayer showing current view, allows to do calculate stats for all in view)" - ) - this.userInfoIsOpened = QueryParameters.GetBooleanQueryParameter( - "userinfo-toggle", - false, - "Whether or not the user info 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, - this.userInfoIsOpened - ) - - 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) - } - }) - } - } } } From 55480c0616810867fbc4a2c9a7a15456e577b5a0 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 6 Jan 2023 03:47:57 +0100 Subject: [PATCH 34/38] Version bump --- Models/Constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 = From cfa8ff896e122973d79ddcd476f067d0bb2d5a3a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 6 Jan 2023 03:53:03 +0100 Subject: [PATCH 35/38] Drop option to start with expanded layer menu from sharescreen --- UI/BigComponents/ShareScreen.ts | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) 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` } }) From 7f6611f9d55284708526bd327c16755843eea8dc Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 6 Jan 2023 04:06:05 +0100 Subject: [PATCH 36/38] Fix tests --- Logic/Osm/OsmConnection.ts | 13 +++++++++++-- UI/Base/ScrollableFullScreen.ts | 27 ++++++++++++++++----------- 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index fe2aa2e83..85bd602df 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -122,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) @@ -146,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( diff --git a/UI/Base/ScrollableFullScreen.ts b/UI/Base/ScrollableFullScreen.ts index 3c20d89ef..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, @@ -32,12 +33,16 @@ export default class ScrollableFullScreen { resetScrollSignal: UIEventSource }) => BaseUIElement, hashToShow: string, - isShown: UIEventSource = new UIEventSource(false) + isShown: UIEventSource = new UIEventSource(false), + options?: { + setHash?: boolean + } ) { this.hashToShow = hashToShow this.isShown = isShown + this._setHash = options?.setHash ?? true - if (hashToShow === undefined || hashToShow === "") { + if ((hashToShow === undefined || hashToShow === "") && this._setHash) { throw "HashToShow should be defined as it is vital for the 'back' key functionality" } @@ -52,17 +57,18 @@ export default class ScrollableFullScreen { ) const self = this - Hash.hash.addCallback((h) => { - if (h === undefined) { - isShown.setData(false) - } - }) + if (this._setHash) { + Hash.hash.addCallback((h) => { + if (h === undefined) { + isShown.setData(false) + } + }) + } 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 - Hash.hash.setData(hashToShow) ScrollableFullScreen._currentlyOpen = self self.Activate() } else { @@ -74,7 +80,6 @@ export default class ScrollableFullScreen { } }) if (isShown.data) { - Hash.hash.setData(hashToShow) ScrollableFullScreen._currentlyOpen = self this.Activate() } @@ -111,10 +116,10 @@ export default class ScrollableFullScreen { * @constructor */ public Activate(): void { - this.isShown.setData(true) - if (this.hashToShow && this.hashToShow !== "") { + 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") ScrollableFullScreen._currentlyOpen = this From 6cb5ced361867fac2ab6b01f6274ac8f2508975a Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 6 Jan 2023 04:16:32 +0100 Subject: [PATCH 37/38] Fix jumping up when a question is answered --- UI/BaseUIElement.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) 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 From 7aea97c68bfe96687f3a10e9be7055a0d8054431 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Fri, 6 Jan 2023 17:41:24 +0100 Subject: [PATCH 38/38] Allow to move and delete facadeGardens --- assets/themes/facadegardens/facadegardens.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 +}