forked from MapComplete/MapComplete
Refactoring: split tagRenderingConfigJson into a version without and with questions
This commit is contained in:
parent
c89bb32c61
commit
9f81628f64
12 changed files with 544 additions and 2348 deletions
|
@ -2,35 +2,50 @@ import {Conversion, DesugaringStep} from "./Conversion";
|
|||
import {LayoutConfigJson} from "../Json/LayoutConfigJson";
|
||||
import {Utils} from "../../../Utils";
|
||||
import * as metapaths from "../../../assets/layoutconfigmeta.json";
|
||||
import * as tagrenderingmetapaths from "../../../assets/tagrenderingconfigmeta.json";
|
||||
import * as tagrenderingmetapaths from "../../../assets/questionabletagrenderingconfigmeta.json";
|
||||
import Translations from "../../../UI/i18n/Translations";
|
||||
|
||||
export class ExtractImages extends Conversion<LayoutConfigJson, string[]> {
|
||||
private _isOfficial: boolean;
|
||||
private _sharedTagRenderings: Map<string, any>;
|
||||
|
||||
private static readonly layoutMetaPaths = (metapaths["default"] ?? metapaths).filter(mp => mp.typeHint !== undefined && (mp.typeHint === "image" || mp.typeHint === "icon"))
|
||||
private static readonly tagRenderingMetaPaths = (tagrenderingmetapaths["default"] ?? tagrenderingmetapaths).filter(trpath => trpath.typeHint === "rendered")
|
||||
private static readonly layoutMetaPaths = (metapaths["default"] ?? metapaths)
|
||||
.filter(mp => (ExtractImages.mightBeTagRendering(mp)) || mp.typeHint !== undefined && (mp.typeHint === "image" || mp.typeHint === "icon"))
|
||||
private static readonly tagRenderingMetaPaths = (tagrenderingmetapaths["default"] ?? tagrenderingmetapaths)
|
||||
|
||||
|
||||
constructor(isOfficial: boolean, sharedTagRenderings: Map<string, any>) {
|
||||
super("Extract all images from a layoutConfig using the meta paths",[],"ExctractImages");
|
||||
super("Extract all images from a layoutConfig using the meta paths.",[],"ExctractImages");
|
||||
this._isOfficial = isOfficial;
|
||||
this._sharedTagRenderings = sharedTagRenderings;
|
||||
}
|
||||
|
||||
public static mightBeTagRendering(metapath: {type: string | string[]}) : boolean{
|
||||
if(!Array.isArray(metapath.type)){
|
||||
return false
|
||||
}
|
||||
return metapath.type.some(t =>
|
||||
t["$ref"] == "#/definitions/TagRenderingConfigJson" || t["$ref"] == "#/definitions/QuestionableTagRenderingConfigJson")
|
||||
}
|
||||
|
||||
convert(json: LayoutConfigJson, context: string): { result: string[], errors: string[], warnings: string[] } {
|
||||
const allFoundImages : string[] = []
|
||||
const errors = []
|
||||
const warnings = []
|
||||
for (const metapath of ExtractImages.layoutMetaPaths) {
|
||||
const mightBeTr = Array.isArray(metapath.type) && metapath.type.some(t => t["$ref"] == "#/definitions/TagRenderingConfigJson")
|
||||
const mightBeTr = ExtractImages.mightBeTagRendering(metapath)
|
||||
const allRenderedValuesAreImages = metapath.typeHint === "icon" || metapath.typeHint === "image"
|
||||
const found = Utils.CollectPath(metapath.path, json)
|
||||
if (mightBeTr) {
|
||||
// We might have tagRenderingConfigs containing icons here
|
||||
for (const el of found) {
|
||||
const path = el.path
|
||||
const foundImage = el.leaf;
|
||||
if (typeof foundImage === "string") {
|
||||
if (typeof foundImage === "string") {
|
||||
|
||||
if(!allRenderedValuesAreImages){
|
||||
continue
|
||||
}
|
||||
|
||||
if(foundImage == ""){
|
||||
warnings.push(context+"."+path.join(".")+" Found an empty image")
|
||||
|
@ -38,27 +53,37 @@ export class ExtractImages extends Conversion<LayoutConfigJson, string[]> {
|
|||
|
||||
if(this._sharedTagRenderings?.has(foundImage)){
|
||||
// This is not an image, but a shared tag rendering
|
||||
// At key positions for checking, they'll be expanded already, so we can safely ignore them here
|
||||
continue
|
||||
}
|
||||
|
||||
allFoundImages.push(foundImage)
|
||||
} else{
|
||||
// This is a tagRendering where every rendered value might be an icon!
|
||||
// This is a tagRendering.
|
||||
// Either every rendered value might be an icon
|
||||
// or -in the case of a normal tagrendering- only the 'icons' in the mappings have an icon (or exceptionally an '<img>' tag in the translation
|
||||
for (const trpath of ExtractImages.tagRenderingMetaPaths) {
|
||||
// Inspect all the rendered values
|
||||
const fromPath = Utils.CollectPath(trpath.path, foundImage)
|
||||
const isRendered = trpath.typeHint === "rendered"
|
||||
const isImage = trpath.typeHint === "icon" || trpath.typeHint === "image"
|
||||
for (const img of fromPath) {
|
||||
if (typeof img.leaf !== "string") {
|
||||
(this._isOfficial ? errors: warnings).push(context+"."+img.path.join(".")+": found an image path that is not a string: " + JSON.stringify(img.leaf))
|
||||
if (allRenderedValuesAreImages && isRendered) {
|
||||
// What we found is an image
|
||||
if(img.leaf === "" || img.leaf["path"] == ""){
|
||||
warnings.push(context+[...path,...img.path].join(".")+": Found an empty image at ")
|
||||
}else if(typeof img.leaf !== "string"){
|
||||
(this._isOfficial ? errors: warnings).push(context+"."+img.path.join(".")+": found an image path that is not a string: " + JSON.stringify(img.leaf))
|
||||
}else{
|
||||
allFoundImages.push(img.leaf)
|
||||
}
|
||||
}
|
||||
}
|
||||
allFoundImages.push(...fromPath.map(i => i.leaf).filter(i => typeof i=== "string"))
|
||||
for (const pathAndImg of fromPath) {
|
||||
if(pathAndImg.leaf === "" || pathAndImg.leaf["path"] == ""){
|
||||
warnings.push(context+[...path,...pathAndImg.path].join(".")+": Found an empty image at ")
|
||||
if(!allRenderedValuesAreImages && isImage){
|
||||
// Extract images from the translations
|
||||
allFoundImages.push(...(Translations.T(img.leaf, "extract_images from "+img.path.join(".")).ExtractImages(false)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -127,7 +152,7 @@ export class FixImages extends DesugaringStep<LayoutConfigJson> {
|
|||
if (metapath.typeHint !== "image" && metapath.typeHint !== "icon") {
|
||||
continue
|
||||
}
|
||||
const mightBeTr = Array.isArray(metapath.type) && metapath.type.some(t => t["$ref"] == "#/definitions/TagRenderingConfigJson")
|
||||
const mightBeTr = ExtractImages.mightBeTagRendering(metapath)
|
||||
Utils.WalkPath(metapath.path, json, (leaf, path) => {
|
||||
if (typeof leaf === "string") {
|
||||
return replaceString(leaf)
|
||||
|
|
|
@ -13,6 +13,7 @@ import ScriptUtils from "../../../scripts/ScriptUtils";
|
|||
import {And} from "../../../Logic/Tags/And";
|
||||
import Translations from "../../../UI/i18n/Translations";
|
||||
import Svg from "../../../Svg";
|
||||
import {QuestionableTagRenderingConfigJson} from "../Json/QuestionableTagRenderingConfigJson";
|
||||
|
||||
|
||||
class ValidateLanguageCompleteness extends DesugaringStep<any> {
|
||||
|
@ -237,12 +238,12 @@ export class PrevalidateTheme extends Fuse<LayoutConfigJson> {
|
|||
|
||||
}
|
||||
|
||||
export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJson> {
|
||||
export class DetectShadowedMappings extends DesugaringStep<QuestionableTagRenderingConfigJson> {
|
||||
constructor() {
|
||||
super("Checks that the mappings don't shadow each other", [], "DetectShadowedMappings");
|
||||
}
|
||||
|
||||
convert(json: TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[] } {
|
||||
convert(json: QuestionableTagRenderingConfigJson, context: string): { result: QuestionableTagRenderingConfigJson; errors?: string[]; warnings?: string[] } {
|
||||
const errors = []
|
||||
const warnings = []
|
||||
if (json.mappings === undefined || json.mappings.length === 0) {
|
||||
|
|
|
@ -6,6 +6,8 @@ import UnitConfigJson from "./UnitConfigJson";
|
|||
import MoveConfigJson from "./MoveConfigJson";
|
||||
import PointRenderingConfigJson from "./PointRenderingConfigJson";
|
||||
import LineRenderingConfigJson from "./LineRenderingConfigJson";
|
||||
import {QuestionableTagRenderingConfigJson} from "./QuestionableTagRenderingConfigJson";
|
||||
import RewritableConfigJson from "./RewritableConfigJson";
|
||||
|
||||
/**
|
||||
* Configuration for a single layer
|
||||
|
@ -58,40 +60,40 @@ export interface LayerConfigJson {
|
|||
*/
|
||||
overpassScript?: string
|
||||
} |
|
||||
{
|
||||
/**
|
||||
* The actual source of the data to load, if loaded via geojson.
|
||||
*
|
||||
* # A single geojson-file
|
||||
* source: {geoJson: "https://my.source.net/some-geo-data.geojson"}
|
||||
* fetches a geojson from a third party source
|
||||
*
|
||||
* # A tiled geojson source
|
||||
* source: {geoJson: "https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson", geoJsonZoomLevel: 14}
|
||||
* to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer
|
||||
*
|
||||
* Some API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}
|
||||
*/
|
||||
geoJson: string,
|
||||
/**
|
||||
* To load a tiled geojson layer, set the zoomlevel of the tiles
|
||||
*/
|
||||
geoJsonZoomLevel?: number,
|
||||
/**
|
||||
* Indicates that the upstream geojson data is OSM-derived.
|
||||
* Useful for e.g. merging or for scripts generating this cache
|
||||
*/
|
||||
isOsmCache?: boolean,
|
||||
/**
|
||||
* Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this
|
||||
*/
|
||||
mercatorCrs?: boolean,
|
||||
/**
|
||||
* Some API's have an id-field, but give it a different name.
|
||||
* Setting this key will rename this field into 'id'
|
||||
*/
|
||||
idKey?: string
|
||||
})
|
||||
{
|
||||
/**
|
||||
* The actual source of the data to load, if loaded via geojson.
|
||||
*
|
||||
* # A single geojson-file
|
||||
* source: {geoJson: "https://my.source.net/some-geo-data.geojson"}
|
||||
* fetches a geojson from a third party source
|
||||
*
|
||||
* # A tiled geojson source
|
||||
* source: {geoJson: "https://my.source.net/some-tile-geojson-{layer}-{z}-{x}-{y}.geojson", geoJsonZoomLevel: 14}
|
||||
* to use a tiled geojson source. The web server must offer multiple geojsons. {z}, {x} and {y} are substituted by the location; {layer} is substituted with the id of the loaded layer
|
||||
*
|
||||
* Some API's use a BBOX instead of a tile, this can be used by specifying {y_min}, {y_max}, {x_min} and {x_max}
|
||||
*/
|
||||
geoJson: string,
|
||||
/**
|
||||
* To load a tiled geojson layer, set the zoomlevel of the tiles
|
||||
*/
|
||||
geoJsonZoomLevel?: number,
|
||||
/**
|
||||
* Indicates that the upstream geojson data is OSM-derived.
|
||||
* Useful for e.g. merging or for scripts generating this cache
|
||||
*/
|
||||
isOsmCache?: boolean,
|
||||
/**
|
||||
* Some API's use a mercator-projection (EPSG:900913) instead of WGS84. Set the flag `mercatorCrs: true` in the source for this
|
||||
*/
|
||||
mercatorCrs?: boolean,
|
||||
/**
|
||||
* Some API's have an id-field, but give it a different name.
|
||||
* Setting this key will rename this field into 'id'
|
||||
*/
|
||||
idKey?: string
|
||||
})
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -174,7 +176,9 @@ export interface LayerConfigJson {
|
|||
*/
|
||||
titleIcons?: (string | TagRenderingConfigJson)[] | ["defaults"];
|
||||
|
||||
|
||||
/**
|
||||
* Visualisation of the items on the map
|
||||
*/
|
||||
mapRendering: null | (PointRenderingConfigJson | LineRenderingConfigJson)[]
|
||||
|
||||
/**
|
||||
|
@ -260,13 +264,12 @@ export interface LayerConfigJson {
|
|||
* This is mainly create questions for a 'left' and a 'right' side of the road.
|
||||
* These will be grouped and questions will be asked together
|
||||
*/
|
||||
tagRenderings?: (string | { builtin: string, override: any } | TagRenderingConfigJson | {
|
||||
rewrite: {
|
||||
sourceString: string[],
|
||||
into: (string | any)[][]
|
||||
},
|
||||
renderings: (string | { builtin: string, override: any } | TagRenderingConfigJson)[]
|
||||
}) [],
|
||||
tagRenderings?:
|
||||
(string
|
||||
| { builtin: string, override: any }
|
||||
| QuestionableTagRenderingConfigJson
|
||||
| RewritableConfigJson<(string | { builtin: string, override: any } | QuestionableTagRenderingConfigJson)[]>
|
||||
) [],
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,38 +1,11 @@
|
|||
import {AndOrTagConfigJson} from "./TagConfigJson";
|
||||
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
|
||||
|
||||
/**
|
||||
* A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.
|
||||
* A QuestionableTagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.
|
||||
* If the desired tags are missing and a question is defined, a question will be shown instead.
|
||||
*/
|
||||
export interface TagRenderingConfigJson {
|
||||
|
||||
/**
|
||||
* The id of the tagrendering, should be an unique string.
|
||||
* Used to keep the translations in sync. Only used in the tagRenderings-array of a layerConfig, not requered otherwise.
|
||||
*
|
||||
* Use 'questions' to trigger the question box of this group (if a group is defined)
|
||||
*/
|
||||
id?: string,
|
||||
|
||||
/**
|
||||
* If 'group' is defined on many tagRenderings, these are grouped together when shown. The questions are grouped together as well.
|
||||
* The first tagRendering of a group will always be a sticky element.
|
||||
*/
|
||||
group?: string
|
||||
|
||||
/**
|
||||
* A list of labels. These are strings that are used for various purposes, e.g. to filter them away
|
||||
*/
|
||||
labels?: string[]
|
||||
|
||||
/**
|
||||
* Renders this value. Note that "{key}"-parts are substituted by the corresponding values of the element.
|
||||
* If neither 'textFieldQuestion' nor 'mappings' are defined, this text is simply shown as default value.
|
||||
*
|
||||
* Note that this is a HTML-interpreted value, so you can add links as e.g. '<a href='{website}'>{website}</a>' or include images such as `This is of type A <br><img src='typeA-icon.svg' />`
|
||||
* type: rendered
|
||||
*/
|
||||
render?: string | any,
|
||||
export interface QuestionableTagRenderingConfigJson extends TagRenderingConfigJson {
|
||||
|
||||
/**
|
||||
* If it turns out that this tagRendering doesn't match _any_ value, then we show this question.
|
||||
|
@ -40,12 +13,6 @@ export interface TagRenderingConfigJson {
|
|||
*/
|
||||
question?: string | any,
|
||||
|
||||
/**
|
||||
* Only show this question if the object also matches the following tags.
|
||||
*
|
||||
* This is useful to ask a follow-up question. E.g. if there is a diaper table, then ask a follow-up question on diaper tables...
|
||||
* */
|
||||
condition?: AndOrTagConfigJson | string;
|
||||
|
||||
/**
|
||||
* Allow freeform text input from the user
|
||||
|
@ -53,10 +20,10 @@ export interface TagRenderingConfigJson {
|
|||
freeform?: {
|
||||
|
||||
/**
|
||||
* If this key is present, then 'render' is used to display the value.
|
||||
* If this is undefined, the rendering is _always_ shown
|
||||
* @inheritDoc
|
||||
*/
|
||||
key: string,
|
||||
key: string
|
||||
|
||||
/**
|
||||
* The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...
|
||||
* See Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values
|
||||
|
@ -104,36 +71,30 @@ export interface TagRenderingConfigJson {
|
|||
mappings?: {
|
||||
|
||||
/**
|
||||
* If this condition is met, then the text under `then` will be shown.
|
||||
* If no value matches, and the user selects this mapping as an option, then these tags will be uploaded to OSM.
|
||||
*
|
||||
* For example: {'if': 'diet:vegetarion=yes', 'then':'A vegetarian option is offered here'}
|
||||
*
|
||||
* This can be an substituting-tag as well, e.g. {'if': 'addr:street:={_calculated_nearby_streetname}', 'then': '{_calculated_nearby_streetname}'}
|
||||
* @inheritDoc
|
||||
*/
|
||||
if: AndOrTagConfigJson | string,
|
||||
/**
|
||||
* If the condition `if` is met, the text `then` will be rendered.
|
||||
* If not known yet, the user will be presented with `then` as an option
|
||||
* Shown if the 'if is fulfilled
|
||||
* Type: rendered
|
||||
*/
|
||||
then: string | any,
|
||||
/**
|
||||
* An icon supporting this mapping; typically shown pretty small
|
||||
* An extra icon supporting the choice
|
||||
* Type: icon
|
||||
*/
|
||||
icon?: string | {
|
||||
/**
|
||||
* The path to the icon
|
||||
* The path to the icon
|
||||
* Type: icon
|
||||
*/
|
||||
path: string,
|
||||
/**
|
||||
* A hint to mapcomplete on how to render this icon within the mapping.
|
||||
* This is translated to 'mapping-icon-<classtype>', so defining your own in combination with a custom CSS is possible (but discouraged)
|
||||
* Size of the image
|
||||
*/
|
||||
class: "small" | "medium" | "large" | string
|
||||
}
|
||||
|
||||
/**
|
||||
* In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).
|
||||
*
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
|
||||
|
||||
export default interface RewritableConfigJson<T> {
|
||||
rewrite: {
|
||||
sourceString: string[],
|
||||
into: (string | any)[][]
|
||||
},
|
||||
renderings: T
|
||||
}
|
|
@ -2,7 +2,7 @@ import {AndOrTagConfigJson} from "./TagConfigJson";
|
|||
|
||||
/**
|
||||
* A TagRenderingConfigJson is a single piece of code which converts one ore more tags into a HTML-snippet.
|
||||
* If the desired tags are missing and a question is defined, a question will be shown instead.
|
||||
* For an _editable_ tagRenerdering, use 'QuestionableTagRenderingConfigJson' instead, which extends this one
|
||||
*/
|
||||
export interface TagRenderingConfigJson {
|
||||
|
||||
|
@ -35,13 +35,7 @@ export interface TagRenderingConfigJson {
|
|||
render?: string | any,
|
||||
|
||||
/**
|
||||
* If it turns out that this tagRendering doesn't match _any_ value, then we show this question.
|
||||
* If undefined, the question is never asked and this tagrendering is read-only
|
||||
*/
|
||||
question?: string | any,
|
||||
|
||||
/**
|
||||
* Only show this question if the object also matches the following tags.
|
||||
* Only show this tagrendering (or question) if the object also matches the following tags.
|
||||
*
|
||||
* This is useful to ask a follow-up question. E.g. if there is a diaper table, then ask a follow-up question on diaper tables...
|
||||
* */
|
||||
|
@ -57,47 +51,8 @@ export interface TagRenderingConfigJson {
|
|||
* If this is undefined, the rendering is _always_ shown
|
||||
*/
|
||||
key: string,
|
||||
/**
|
||||
* The type of the text-field, e.g. 'string', 'nat', 'float', 'date',...
|
||||
* See Docs/SpecialInputElements.md and UI/Input/ValidatedTextField.ts for supported values
|
||||
*/
|
||||
type?: string,
|
||||
/**
|
||||
* A (translated) text that is shown (as gray text) within the textfield
|
||||
*/
|
||||
placeholder?: string | any
|
||||
|
||||
/**
|
||||
* Extra parameters to initialize the input helper arguments.
|
||||
* For semantics, see the 'SpecialInputElements.md'
|
||||
*/
|
||||
helperArgs?: (string | number | boolean | any)[];
|
||||
/**
|
||||
* If a value is added with the textfield, these extra tag is addded.
|
||||
* Useful to add a 'fixme=freeform textfield used - to be checked'
|
||||
**/
|
||||
addExtraTags?: string[];
|
||||
|
||||
/**
|
||||
* When set, influences the way a question is asked.
|
||||
* Instead of showing a full-widht text field, the text field will be shown within the rendering of the question.
|
||||
*
|
||||
* This combines badly with special input elements, as it'll distort the layout.
|
||||
*/
|
||||
inline?: boolean
|
||||
|
||||
/**
|
||||
* default value to enter if no previous tagging is present.
|
||||
* Normally undefined (aka do not enter anything)
|
||||
*/
|
||||
default?: string
|
||||
},
|
||||
|
||||
/**
|
||||
* If true, use checkboxes instead of radio buttons when asking the question
|
||||
*/
|
||||
multiAnswer?: boolean,
|
||||
|
||||
/**
|
||||
* Allows fixed-tag inputs, shown either as radiobuttons or as checkboxes
|
||||
*/
|
||||
|
@ -134,82 +89,6 @@ export interface TagRenderingConfigJson {
|
|||
*/
|
||||
class: "small" | "medium" | "large" | string
|
||||
}
|
||||
/**
|
||||
* In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).
|
||||
*
|
||||
* In the latter case, a correct text should be shown, but only a single, canonical tagging should be selectable by the user.
|
||||
* In this case, one of the mappings can be hiden by setting this flag.
|
||||
*
|
||||
* To demonstrate an example making a default assumption:
|
||||
*
|
||||
* mappings: [
|
||||
* {
|
||||
* if: "access=", -- no access tag present, we assume accessible
|
||||
* then: "Accessible to the general public",
|
||||
* hideInAnswer: true
|
||||
* },
|
||||
* {
|
||||
* if: "access=yes",
|
||||
* then: "Accessible to the general public", -- the user selected this, we add that to OSM
|
||||
* },
|
||||
* {
|
||||
* if: "access=no",
|
||||
* then: "Not accessible to the public"
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
*
|
||||
* For example, for an operator, we have `operator=Agentschap Natuur en Bos`, which is often abbreviated to `operator=ANB`.
|
||||
* Then, we would add two mappings:
|
||||
* {
|
||||
* if: "operator=Agentschap Natuur en Bos" -- the non-abbreviated version which should be uploaded
|
||||
* then: "Maintained by Agentschap Natuur en Bos"
|
||||
* },
|
||||
* {
|
||||
* if: "operator=ANB", -- we don't want to upload abbreviations
|
||||
* then: "Maintained by Agentschap Natuur en Bos"
|
||||
* hideInAnswer: true
|
||||
* }
|
||||
*
|
||||
* Hide in answer can also be a tagsfilter, e.g. to make sure an option is only shown when appropriate.
|
||||
* Keep in mind that this is reverse logic: it will be hidden in the answer if the condition is true, it will thus only show in the case of a mismatch
|
||||
*
|
||||
* e.g., for toilets: if "wheelchair=no", we know there is no wheelchair dedicated room.
|
||||
* For the location of the changing table, the option "in the wheelchair accessible toilet is weird", so we write:
|
||||
*
|
||||
* {
|
||||
* "question": "Where is the changing table located?"
|
||||
* "mappings": [
|
||||
* {"if":"changing_table:location=female","then":"In the female restroom"},
|
||||
* {"if":"changing_table:location=male","then":"In the male restroom"},
|
||||
* {"if":"changing_table:location=wheelchair","then":"In the wheelchair accessible restroom", "hideInAnswer": "wheelchair=no"},
|
||||
*
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* Also have a look for the meta-tags
|
||||
* {
|
||||
* if: "operator=Agentschap Natuur en Bos",
|
||||
* then: "Maintained by Agentschap Natuur en Bos",
|
||||
* hideInAnswer: "_country!=be"
|
||||
* }
|
||||
*/
|
||||
hideInAnswer?: boolean | string | AndOrTagConfigJson,
|
||||
/**
|
||||
* Only applicable if 'multiAnswer' is set.
|
||||
* This is for situations such as:
|
||||
* `accepts:coins=no` where one can select all the possible payment methods. However, we want to make explicit that some options _were not_ selected.
|
||||
* This can be done with `ifnot`
|
||||
* Note that we can not explicitly render this negative case to the user, we cannot show `does _not_ accept coins`.
|
||||
* If this is important to your usecase, consider using multiple radiobutton-fields without `multiAnswer`
|
||||
*/
|
||||
ifnot?: AndOrTagConfigJson | string
|
||||
|
||||
/**
|
||||
* If chosen as answer, these tags will be applied as well onto the object.
|
||||
* Not compatible with multiAnswer
|
||||
*/
|
||||
addExtraTags?: string[]
|
||||
|
||||
}[]
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
import {Translation} from "../../UI/i18n/Translation";
|
||||
import {TagsFilter} from "../../Logic/Tags/TagsFilter";
|
||||
import {TagRenderingConfigJson} from "./Json/TagRenderingConfigJson";
|
||||
import Translations from "../../UI/i18n/Translations";
|
||||
import {TagUtils} from "../../Logic/Tags/TagUtils";
|
||||
import {And} from "../../Logic/Tags/And";
|
||||
|
@ -12,6 +11,7 @@ import Combine from "../../UI/Base/Combine";
|
|||
import Title from "../../UI/Base/Title";
|
||||
import Link from "../../UI/Base/Link";
|
||||
import List from "../../UI/Base/List";
|
||||
import {QuestionableTagRenderingConfigJson} from "./Json/QuestionableTagRenderingConfigJson";
|
||||
|
||||
/***
|
||||
* The parsed version of TagRenderingConfigJSON
|
||||
|
@ -50,7 +50,7 @@ export default class TagRenderingConfig {
|
|||
}[]
|
||||
public readonly labels: string[]
|
||||
|
||||
constructor(json: string | TagRenderingConfigJson, context?: string) {
|
||||
constructor(json: string | QuestionableTagRenderingConfigJson, context?: string) {
|
||||
if (json === undefined) {
|
||||
throw "Initing a TagRenderingConfig with undefined in " + context;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
198
assets/questionabletagrenderingconfigmeta.json
Normal file
198
assets/questionabletagrenderingconfigmeta.json
Normal file
|
@ -0,0 +1,198 @@
|
|||
[
|
||||
{
|
||||
"path": [],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"question"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"freeform"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"freeform",
|
||||
"type"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"freeform",
|
||||
"placeholder"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"freeform",
|
||||
"helperArgs"
|
||||
],
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"freeform",
|
||||
"addExtraTags"
|
||||
],
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"freeform",
|
||||
"inline"
|
||||
],
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"freeform",
|
||||
"default"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"multiAnswer"
|
||||
],
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"mappings"
|
||||
],
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"mappings",
|
||||
"then"
|
||||
],
|
||||
"typeHint": "rendered"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"mappings",
|
||||
"icon"
|
||||
],
|
||||
"typeHint": "icon",
|
||||
"type": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"path": {
|
||||
"description": "The path to the icon\nType: icon",
|
||||
"type": "string"
|
||||
},
|
||||
"class": {
|
||||
"description": "Size of the image",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"class",
|
||||
"path"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"mappings",
|
||||
"icon",
|
||||
"path"
|
||||
],
|
||||
"typeHint": "icon",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"mappings",
|
||||
"icon",
|
||||
"class"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"mappings",
|
||||
"hideInAnswer"
|
||||
],
|
||||
"type": [
|
||||
{
|
||||
"$ref": "#/definitions/AndOrTagConfigJson"
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"string",
|
||||
"boolean"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"mappings",
|
||||
"ifnot"
|
||||
],
|
||||
"type": [
|
||||
{
|
||||
"$ref": "#/definitions/AndOrTagConfigJson"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"mappings",
|
||||
"addExtraTags"
|
||||
],
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"id"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"group"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"labels"
|
||||
],
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"render"
|
||||
],
|
||||
"typeHint": "rendered"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"condition"
|
||||
],
|
||||
"type": [
|
||||
{
|
||||
"$ref": "#/definitions/AndOrTagConfigJson"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
|
@ -27,11 +27,6 @@
|
|||
],
|
||||
"typeHint": "rendered"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"question"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"condition"
|
||||
|
@ -58,53 +53,6 @@
|
|||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"freeform",
|
||||
"type"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"freeform",
|
||||
"placeholder"
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"freeform",
|
||||
"helperArgs"
|
||||
],
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"freeform",
|
||||
"addExtraTags"
|
||||
],
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"freeform",
|
||||
"inline"
|
||||
],
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"freeform",
|
||||
"default"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"multiAnswer"
|
||||
],
|
||||
"type": "boolean"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"mappings"
|
||||
|
@ -177,43 +125,5 @@
|
|||
"class"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"mappings",
|
||||
"hideInAnswer"
|
||||
],
|
||||
"type": [
|
||||
{
|
||||
"$ref": "#/definitions/AndOrTagConfigJson"
|
||||
},
|
||||
{
|
||||
"type": [
|
||||
"string",
|
||||
"boolean"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"mappings",
|
||||
"ifnot"
|
||||
],
|
||||
"type": [
|
||||
{
|
||||
"$ref": "#/definitions/AndOrTagConfigJson"
|
||||
},
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"path": [
|
||||
"mappings",
|
||||
"addExtraTags"
|
||||
],
|
||||
"type": "array"
|
||||
}
|
||||
]
|
|
@ -6,7 +6,7 @@ interface JsonSchema {
|
|||
description?: string,
|
||||
type?: string,
|
||||
properties?: any,
|
||||
items?: JsonSchema | JsonSchema[],
|
||||
items?: JsonSchema,
|
||||
anyOf: JsonSchema[],
|
||||
enum: JsonSchema[],
|
||||
"$ref": string
|
||||
|
@ -15,7 +15,6 @@ interface JsonSchema {
|
|||
function WalkScheme<T>(
|
||||
onEach: (schemePart: JsonSchema) => T,
|
||||
scheme: JsonSchema,
|
||||
registerSchemePath = false,
|
||||
fullScheme: JsonSchema & { definitions?: any } = undefined,
|
||||
path: string[] = [],
|
||||
isHandlingReference = []
|
||||
|
@ -24,6 +23,7 @@ function WalkScheme<T>(
|
|||
if (scheme === undefined) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (scheme["$ref"] !== undefined) {
|
||||
const ref = scheme["$ref"]
|
||||
const prefix = "#/definitions/"
|
||||
|
@ -35,70 +35,48 @@ function WalkScheme<T>(
|
|||
return;
|
||||
}
|
||||
const loadedScheme = fullScheme.definitions[definitionName]
|
||||
return WalkScheme(onEach, loadedScheme, registerSchemePath, fullScheme, path, [...isHandlingReference, definitionName]);
|
||||
return WalkScheme(onEach, loadedScheme, fullScheme, path, [...isHandlingReference, definitionName]);
|
||||
}
|
||||
|
||||
fullScheme = fullScheme ?? scheme
|
||||
var t = onEach(scheme)
|
||||
if (t !== undefined) {
|
||||
results.push({
|
||||
path: [...path],
|
||||
path,
|
||||
t
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function walk(v: JsonSchema, pathPart: string) {
|
||||
function walk(v: JsonSchema) {
|
||||
if (v === undefined) {
|
||||
return
|
||||
}
|
||||
if (registerSchemePath) {
|
||||
path.push("" + pathPart)
|
||||
}
|
||||
results.push(...WalkScheme(onEach, v, registerSchemePath, fullScheme, path, isHandlingReference))
|
||||
if (registerSchemePath) {
|
||||
path.pop()
|
||||
}
|
||||
results.push(...WalkScheme(onEach, v, fullScheme, path, isHandlingReference))
|
||||
}
|
||||
|
||||
function walkEach(scheme: JsonSchema[], pathPart: string) {
|
||||
function walkEach(scheme: JsonSchema[]) {
|
||||
if (scheme === undefined) {
|
||||
return
|
||||
}
|
||||
if (registerSchemePath) {
|
||||
path.push("" + pathPart)
|
||||
}
|
||||
scheme.forEach((v, i) => walk(v, "" + i))
|
||||
if (registerSchemePath) {
|
||||
path.pop()
|
||||
}
|
||||
|
||||
scheme.forEach(v => walk(v))
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
{
|
||||
walkEach(scheme.enum, "enum")
|
||||
walkEach(scheme.anyOf, "anyOf")
|
||||
if (scheme.items !== undefined) {
|
||||
|
||||
if (scheme.items["forEach"] !== undefined) {
|
||||
walkEach(<any>scheme.items, "items")
|
||||
} else {
|
||||
walk(<any>scheme.items, "items")
|
||||
}
|
||||
walkEach(scheme.enum)
|
||||
walkEach(scheme.anyOf)
|
||||
if (Array.isArray(scheme.items)) {
|
||||
walkEach(<any>scheme.items)
|
||||
} else {
|
||||
walk(<any>scheme.items)
|
||||
}
|
||||
|
||||
if (registerSchemePath) {
|
||||
path.push("properties")
|
||||
}
|
||||
for (const key in scheme.properties) {
|
||||
const prop = scheme.properties[key]
|
||||
path.push(key)
|
||||
results.push(...WalkScheme(onEach, prop, registerSchemePath, fullScheme, path, isHandlingReference))
|
||||
path.pop()
|
||||
}
|
||||
if (registerSchemePath) {
|
||||
path.pop()
|
||||
results.push(...WalkScheme(onEach, prop, fullScheme, [...path, key], isHandlingReference))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,14 +92,16 @@ function extractMeta(typename: string, path: string) {
|
|||
const typeHint = schemePart.description.split("\n")
|
||||
.find(line => line.trim().toLocaleLowerCase().startsWith("type:"))
|
||||
?.substr("type:".length)?.trim()
|
||||
const type = schemePart.type ?? schemePart.anyOf;
|
||||
const type = schemePart.items?.anyOf ?? schemePart.type ?? schemePart.anyOf;
|
||||
return {typeHint, type}
|
||||
}, themeSchema)
|
||||
|
||||
writeFileSync("./assets/" + path + ".json", JSON.stringify(withTypes.map(({
|
||||
path,
|
||||
t
|
||||
}) => ({path, ...t})), null, " "))
|
||||
const paths = withTypes.map(({
|
||||
path,
|
||||
t
|
||||
}) => ({path, ...t}))
|
||||
writeFileSync("./assets/" + path + ".json", JSON.stringify(paths, null, " "))
|
||||
console.log("Written meta to ./assets/" + path)
|
||||
}
|
||||
|
||||
|
||||
|
@ -148,6 +128,7 @@ function main() {
|
|||
|
||||
extractMeta("LayoutConfigJson", "layoutconfigmeta")
|
||||
extractMeta("TagRenderingConfigJson", "tagrenderingconfigmeta")
|
||||
extractMeta("QuestionableTagRenderingConfigJson", "questionabletagrenderingconfigmeta")
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -449,7 +449,7 @@ export default class LegacyThemeLoaderSpec extends T {
|
|||
Assert.equal("https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/Something.svg",
|
||||
fixedMapping)
|
||||
}],
|
||||
["Images in 'thens' are detected", () => {
|
||||
["Images in simple mappings are detected", () => {
|
||||
const r = new DetectMappingsWithImages().convert({
|
||||
"mappings": [
|
||||
{
|
||||
|
@ -470,7 +470,7 @@ export default class LegacyThemeLoaderSpec extends T {
|
|||
T.isTrue(errors.length > 0, "No images found");
|
||||
T.isTrue(errors.some(msg => msg.indexOf("./assets/layers/bike_parking/staple.svg") >= 0), "staple.svg not mentioned");
|
||||
}],
|
||||
["Images in 'thens' icons are detected", () => {
|
||||
["Images in 'thens' are detected in QuestionableTagRenderings", () => {
|
||||
const r = new ExtractImages(true, new Map<string, any>()).convert(<any>{
|
||||
"layers": [
|
||||
{
|
||||
|
@ -504,6 +504,26 @@ export default class LegacyThemeLoaderSpec extends T {
|
|||
T.isTrue(images.length > 0, "No images found");
|
||||
T.isTrue(images.findIndex(img => img =="./assets/layers/bike_parking/staple.svg") >= 0, "staple.svg not mentioned");
|
||||
T.isTrue(images.findIndex(img => img == "./assets/layers/bike_parking/bollard.svg") >= 0, "bollard.svg not mentioned");
|
||||
}],
|
||||
["Rotation and colours is not detected as image", () => {
|
||||
const r = new ExtractImages(true, new Map<string, any>()).convert(<any>{
|
||||
"layers": [
|
||||
{
|
||||
mapRendering: [
|
||||
{
|
||||
"location":["point","centroid"],
|
||||
"icon": "pin:black",
|
||||
rotation: 180,
|
||||
iconSize: "40,40,center"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}, "test");
|
||||
const images = r.result
|
||||
T.isTrue(images.length > 0, "No images found");
|
||||
T.isTrue(images.length < 2, "To much images found: "+images.join(", "));
|
||||
T.isTrue(images[0] === "pin", "pin not mentioned");
|
||||
}]
|
||||
]
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue