diff --git a/Models/ThemeConfig/Json/LayerConfigJson.ts b/Models/ThemeConfig/Json/LayerConfigJson.ts index 9aef92aa4b..1656bf8f5a 100644 --- a/Models/ThemeConfig/Json/LayerConfigJson.ts +++ b/Models/ThemeConfig/Json/LayerConfigJson.ts @@ -141,6 +141,8 @@ export interface LayerConfigJson { * If not specified, the OsmLink and wikipedia links will be used by default. * Use an empty array to hide them. * Note that "defaults" will insert all the default titleIcons (which are added automatically) + * + * Type: icon[] */ titleIcons?: (string | TagRenderingConfigJson)[] | ["defaults"]; diff --git a/Models/ThemeConfig/Json/LayoutConfigJson.ts b/Models/ThemeConfig/Json/LayoutConfigJson.ts index 6bbf483f1f..85c936dab2 100644 --- a/Models/ThemeConfig/Json/LayoutConfigJson.ts +++ b/Models/ThemeConfig/Json/LayoutConfigJson.ts @@ -32,7 +32,7 @@ export interface LayoutConfigJson { credits?: string; /** - * Who does maintian this preset? + * Who does maintain this preset? */ maintainer: string; @@ -74,13 +74,17 @@ export interface LayoutConfigJson { * The icon representing this theme. * Used as logo in the more-screen and (for official themes) as favicon, webmanifest logo, ... * Either a URL or a base64 encoded value (which should include 'data:image/svg+xml;base64) + * + * Type: icon */ icon: string; /** * Link to a 'social image' which is included as og:image-tag on official themes. * Useful to share the theme on social media. - * See https://www.h3xed.com/web-and-internet/how-to-use-og-image-meta-tag-facebook-reddit for more information + * See https://www.h3xed.com/web-and-internet/how-to-use-og-image-meta-tag-facebook-reddit for more information$ + * + * Type: image */ socialImage?: string; diff --git a/Models/ThemeConfig/Json/PointRenderingConfigJson.ts b/Models/ThemeConfig/Json/PointRenderingConfigJson.ts index f214687ba8..95bd2cd08f 100644 --- a/Models/ThemeConfig/Json/PointRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/PointRenderingConfigJson.ts @@ -26,7 +26,8 @@ export default interface PointRenderingConfigJson { * As a result, on could use a generic pin, then overlay it with a specific icon. * To make things even more practical, one can use all SVG's from the folder "assets/svg" and _substitute the color_ in it. * E.g. to draw a red pin, use "pin:#f00", to have a green circle with your icon on top, use `circle:#0f0;` - * + + * Type: icon */ icon?: string | TagRenderingConfigJson; @@ -36,7 +37,13 @@ export default interface PointRenderingConfigJson { * * Note: strings are interpreted as icons, so layering and substituting is supported. You can use `circle:white;./my_icon.svg` to add a background circle */ - iconBadges?: { if: string | AndOrTagConfigJson, then: string | TagRenderingConfigJson }[] + iconBadges?: { if: string | AndOrTagConfigJson, + /** + * Badge to show + * Type: icon + */ + then: string | TagRenderingConfigJson + }[] /** diff --git a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts index 141c507512..8bed8de50e 100644 --- a/Models/ThemeConfig/Json/TagRenderingConfigJson.ts +++ b/Models/ThemeConfig/Json/TagRenderingConfigJson.ts @@ -108,6 +108,7 @@ export interface TagRenderingConfigJson { then: string | any, /** * An icon supporting this mapping; typically shown pretty small + * Type: icon */ icon?: string /** diff --git a/scripts/fixSchemas.ts b/scripts/fixSchemas.ts index 7899ae0957..629ea434bc 100644 --- a/scripts/fixSchemas.ts +++ b/scripts/fixSchemas.ts @@ -2,6 +2,105 @@ import ScriptUtils from "./ScriptUtils"; import {readFileSync, writeFileSync} from "fs"; +interface JsonSchema { + description?: string, + type?: string, + properties?: any, + items?: JsonSchema | JsonSchema[], + anyOf: JsonSchema[], + enum: JsonSchema[], + "$ref": string +} + +function WalkScheme( + onEach: (schemePart: JsonSchema) => T, + scheme: JsonSchema, + registerSchemePath = false, + fullScheme: JsonSchema & { definitions?: any } = undefined, + path: string[] = [], + isHandlingReference = [] +): { path: string[], t: T }[] { + const results: { path: string[], t: T } [] = [] + if (scheme === undefined) { + return [] + } + if (scheme["$ref"] !== undefined) { + const ref = scheme["$ref"] + const prefix = "#/definitions/" + if (!ref.startsWith(prefix)) { + throw "References is not relative!" + } + const definitionName = ref.substr(prefix.length) + if (isHandlingReference.indexOf(definitionName) >= 0) { + return; + } + const loadedScheme = fullScheme.definitions[definitionName] + return WalkScheme(onEach, loadedScheme, registerSchemePath, fullScheme, path, [...isHandlingReference, definitionName]); + } + + fullScheme = fullScheme ?? scheme + var t = onEach(scheme) + + function walk(v: JsonSchema, pathPart: string) { + if (v === undefined) { + return + } + if(registerSchemePath){ + path.push("" + pathPart) + } + results.push(...WalkScheme(onEach, v, registerSchemePath, fullScheme, path, isHandlingReference)) + if(registerSchemePath){ + path.pop() + } + } + + function walkEach(scheme: JsonSchema[], pathPart: string) { + if (scheme === undefined) { + return + } + if(registerSchemePath){ + path.push("" + pathPart) + } + scheme.forEach((v, i) => walk(v, "" + i)) + if(registerSchemePath){ + path.pop() + } + } + + if (t !== undefined) { + results.push({ + path: [...path], + t + }) + } else { + walkEach(scheme.enum, "enum") + walkEach(scheme.anyOf, "anyOf") + if (scheme.items !== undefined) { + + if (scheme.items["forEach"] !== undefined) { + walkEach(scheme.items, "items") + } else { + walk(scheme.items, "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() + } + } + + return results +} + function main() { const allSchemas = ScriptUtils.readDirRecSync("./Docs/Schemas").filter(pth => pth.endsWith("JSC.ts")) @@ -20,9 +119,23 @@ function main() { def["additionalProperties"] = false } } - writeFileSync(dir + "/" + name + ".schema.json", JSON.stringify(parsed, null, " "), "UTF8") } + + const themeSchema = JSON.parse(readFileSync("./Docs/Schemas/LayoutConfigJson.schema.json", "UTF-8")) + const withTypes =WalkScheme((schemePart) => { + if (schemePart.description === undefined) { + return; + } + const type = schemePart.description.split("\n").filter(line => line.trim().toLocaleLowerCase().startsWith("type: "))[0] + if (type === undefined) { + return undefined + } + return {typeHint: type.substr("type: ".length), type: schemePart.type ?? schemePart.anyOf} + }, themeSchema) + + // writeFileSync("./assets/layoutconfigmeta.json",JSON.stringify(withTypes.map(({path, t}) => ({path, ...t})), null, " ")) + } main() diff --git a/test/LegacyThemeLoader.spec.ts b/test/LegacyThemeLoader.spec.ts index 0e0c559e94..6f60d73247 100644 --- a/test/LegacyThemeLoader.spec.ts +++ b/test/LegacyThemeLoader.spec.ts @@ -4,6 +4,7 @@ import LayoutConfig from "../Models/ThemeConfig/LayoutConfig"; import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson"; import {TagRenderingConfigJson} from "../Models/ThemeConfig/Json/TagRenderingConfigJson"; import {AddMiniMap} from "../Models/ThemeConfig/Conversion/PrepareTheme"; +import FixRemoteLinks from "../Models/ThemeConfig/Conversion/FixRemoteLinks"; export default class LegacyThemeLoaderSpec extends T { @@ -142,6 +143,215 @@ export default class LegacyThemeLoaderSpec extends T { ] } + private static readonly organic_waste_theme = { + "id": "recycling-organic", + "title": { + "nl": "Inzamelpunt organisch alval" + }, + "shortDescription": { + "nl": "Inzamelpunt organisch alval" + }, + "description": { + "nl": "Op deze kaart vindt u inzamelpunten voor organisch afval. Beheer deze naar goeddunken en vermogen." + }, + "language": [ + "nl" + ], + "maintainer": "", + "icon": "https://upload.wikimedia.org/wikipedia/commons/1/15/Compost_…able_waste_-_biodegradable_waste_-_biological_waste_icon.png", + "version": "0", + "startLat": 0, + "startLon": 0, + "startZoom": 1, + "widenFactor": 0.05, + "socialImage": "", + "layers": [ + { + "id": "recycling-organic", + "name": { + "nl": "Inzamelpunt organisch alval" + }, + "minzoom": 12, + "title": { + "render": { + "nl": "Inzamelpunt organisch alval" + }, + "mappings": [ + { + "if": { + "and": [ + "name~*" + ] + }, + "then": { + "nl": "{name}" + } + } + ] + }, + "allowMove": true, + "deletion": {}, + "tagRenderings": [ + "images", + { + "freeform": { + "key": "opening_hours", + "type": "opening_hours", + "addExtraTags": [] + }, + "question": { + "nl": "Wat zijn de openingsuren?" + }, + "render": { + "nl": "{opening_hours_table()}" + }, + "mappings": [ + { + "if": { + "and": [ + "opening_hours=\"by appointment\"" + ] + }, + "then": { + "nl": "Op afspraak" + } + } + ], + "id": "Composthoekjes-opening_hours" + }, + { + "question": { + "nl": "Wat is de website voor meer informatie?" + }, + "freeform": { + "key": "website", + "type": "url" + }, + "render": { + "nl": "{website}" + }, + "id": "Composthoekjes-website" + }, + { + "question": { + "nl": "Wat is het type inzamelpunt?" + }, + "mappings":[ + { + "if":"recycling_type=container", + "then":"Container of vat" + }, + { + "if":"recycling_type=centre", + "then":"Verwerkingsplaats of containerpark" + }, + { + "if":"recycling_type=dump", + "then":"Composthoop" + } + + ], + "id": "Composthoekjes-type" + }, + { + "question": { + "nl": "Wie mag hier organisch afval bezorgen?" + }, + "mappings":[ + { + "if":"access=yes", + "then":"Publiek toegankelijk" + }, + { + "if":"access=no", + "then":"Privaat" + }, + { + "if":"access=permessive", + "then":"Mogelijks toegelaten tot nader order" + }, + { + "if":"access=", + "then":"Waarschijnlijk publiek toegankelijk", + "hideInAnswer":true + }, + { + "if":"access=residents", + "then":"Bewoners van gemeente", + "hideInAnswer":"recycling_type!=centre" + } + + ], + "id": "Composthoekjes-voor-wie" + }, + { + "question": { + "nl": "Wat is de naam van dit compost-inzamelpunt?" + }, + "freeform": { + "key": "name", + "addExtraTags": ["noname="] + }, + "render": { + "nl": "De naam van dit compost-inzamelpunt is {name}" + }, + "mappings":[ + { + "if":{"and":["noname=yes","name="]}, + "then":"Heeft geen naam" + }, + { + "if":"name=", + "then":"Geen naam bekend", + "hideInAnswer":true + } + ], + "id": "Composthoekjes-name" + }], + "presets": [ + { + "tags": [ + "amenity=recycling", + "recycling:organic=yes" + ], + "title": { + "nl": "een inzamelpunt voor organisch afval" + } + } + ], + "source": { + "osmTags": { + "and": [ + "recycling:organic~*" + ] + } + }, + "mapRendering": [ + { + "icon": { + "render": "circle:white;https://upload.wikimedia.org/wikipedia/commons/1/15/Compost_…able_waste_-_biodegradable_waste_-_biological_waste_icon.png" + }, + "iconSize": { + "render": "40,40,center" + }, + "location": [ + "point" + ] + }, + { + "color": { + "render": "#00f" + }, + "width": { + "render": "8" + } + } + ] + } + ] + } + + constructor() { super([ ["Walking_node_theme", () => { @@ -201,8 +411,7 @@ export default class LegacyThemeLoaderSpec extends T { render: "Some random value {minimap}" }) - } - ] + }] ] ); }