Fix loading of relative images in custom themes
This commit is contained in:
parent
8d79d94e7b
commit
a3b32a3697
7 changed files with 346 additions and 235 deletions
|
@ -10,16 +10,17 @@ import {UIEventSource} from "./UIEventSource";
|
||||||
import {LocalStorageSource} from "./Web/LocalStorageSource";
|
import {LocalStorageSource} from "./Web/LocalStorageSource";
|
||||||
import LZString from "lz-string";
|
import LZString from "lz-string";
|
||||||
import * as personal from "../assets/themes/personal/personal.json";
|
import * as personal from "../assets/themes/personal/personal.json";
|
||||||
import {FixLegacyTheme} from "../Models/ThemeConfig/Conversion/LegacyJsonConvert";
|
import {FixImages, FixLegacyTheme} from "../Models/ThemeConfig/Conversion/LegacyJsonConvert";
|
||||||
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
|
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
|
||||||
import SharedTagRenderings from "../Customizations/SharedTagRenderings";
|
import SharedTagRenderings from "../Customizations/SharedTagRenderings";
|
||||||
import * as known_layers from "../assets/generated/known_layers.json"
|
import * as known_layers from "../assets/generated/known_layers.json"
|
||||||
import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson";
|
import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson";
|
||||||
import {PrepareTheme} from "../Models/ThemeConfig/Conversion/PrepareTheme";
|
import {PrepareTheme} from "../Models/ThemeConfig/Conversion/PrepareTheme";
|
||||||
import {Layer} from "leaflet";
|
import * as licenses from "../assets/generated/license_info.json"
|
||||||
|
|
||||||
export default class DetermineLayout {
|
export default class DetermineLayout {
|
||||||
|
|
||||||
|
private static readonly _knownImages =new Set( Array.from(licenses).map(l => l.path))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the correct layout for this website
|
* Gets the correct layout for this website
|
||||||
*/
|
*/
|
||||||
|
@ -144,6 +145,7 @@ export default class DetermineLayout {
|
||||||
sharedLayers: knownLayersDict
|
sharedLayers: knownLayersDict
|
||||||
}
|
}
|
||||||
json = new FixLegacyTheme().convertStrict(json, "While loading a dynamic theme")
|
json = new FixLegacyTheme().convertStrict(json, "While loading a dynamic theme")
|
||||||
|
json = new FixImages(DetermineLayout._knownImages).convertStrict(json, "While fixing the images")
|
||||||
json = new PrepareTheme(converState).convertStrict(json, "While preparing a dynamic theme")
|
json = new PrepareTheme(converState).convertStrict(json, "While preparing a dynamic theme")
|
||||||
console.log("The layoutconfig is ", json)
|
console.log("The layoutconfig is ", json)
|
||||||
return json
|
return json
|
||||||
|
|
|
@ -2,7 +2,8 @@ import {LayoutConfigJson} from "../Json/LayoutConfigJson";
|
||||||
import {Utils} from "../../../Utils";
|
import {Utils} from "../../../Utils";
|
||||||
import LineRenderingConfigJson from "../Json/LineRenderingConfigJson";
|
import LineRenderingConfigJson from "../Json/LineRenderingConfigJson";
|
||||||
import {LayerConfigJson} from "../Json/LayerConfigJson";
|
import {LayerConfigJson} from "../Json/LayerConfigJson";
|
||||||
import {DesugaringContext, DesugaringStep, Fuse, OnEvery} from "./Conversion";
|
import {DesugaringStep, Fuse, OnEvery} from "./Conversion";
|
||||||
|
import * as metapaths from "../../../assets/layoutconfigmeta.json"
|
||||||
|
|
||||||
export class UpdateLegacyLayer extends DesugaringStep<LayerConfigJson | string | { builtin, override }> {
|
export class UpdateLegacyLayer extends DesugaringStep<LayerConfigJson | string | { builtin, override }> {
|
||||||
|
|
||||||
|
@ -157,3 +158,103 @@ export class FixLegacyTheme extends Fuse<LayoutConfigJson> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class FixImages extends DesugaringStep<LayoutConfigJson> {
|
||||||
|
private readonly _knownImages: Set<string>;
|
||||||
|
|
||||||
|
constructor(knownImages: Set<string>) {
|
||||||
|
super("Walks over the entire theme and replaces images to the relative URL. Only works if the ID of the theme is an URL");
|
||||||
|
this._knownImages = knownImages;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walks the path into the object till the end.
|
||||||
|
*
|
||||||
|
* If a list is encountered, this is tranparently walked recursively on every object.
|
||||||
|
*
|
||||||
|
* The leaf objects are replaced
|
||||||
|
*/
|
||||||
|
private static WalkPath(path: string[], object: any, replaceLeaf: ((leaf: any) => any)) {
|
||||||
|
const head = path[0]
|
||||||
|
if (path.length === 1) {
|
||||||
|
// We have reached the leaf
|
||||||
|
const leaf = object[head];
|
||||||
|
if (leaf !== undefined) {
|
||||||
|
object[head] = replaceLeaf(leaf)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
const sub = object[head]
|
||||||
|
if (sub === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof sub !== "object") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sub["forEach"] !== undefined) {
|
||||||
|
sub.forEach(el => FixImages.WalkPath(path.slice(1), el, replaceLeaf))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FixImages.WalkPath(path.slice(1), sub, replaceLeaf)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors?: string[]; warnings?: string[] } {
|
||||||
|
let url: URL;
|
||||||
|
console.log("Fixing images!")
|
||||||
|
try {
|
||||||
|
url = new URL(json.id)
|
||||||
|
} catch (e) {
|
||||||
|
// Not a URL, we don't rewrite
|
||||||
|
return {result: json}
|
||||||
|
}
|
||||||
|
|
||||||
|
const absolute = url.protocol +"//"+url.host
|
||||||
|
let relative = url.protocol +"//"+ url.host + url.pathname
|
||||||
|
relative = relative.substring(0, relative.lastIndexOf("/"))
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
function replaceString(leaf: string) {
|
||||||
|
if (self._knownImages.has(leaf)) {
|
||||||
|
return leaf;
|
||||||
|
}
|
||||||
|
if (leaf.startsWith("./")) {
|
||||||
|
return relative + leaf.substring(1)
|
||||||
|
}
|
||||||
|
if (leaf.startsWith("/")) {
|
||||||
|
return absolute + leaf
|
||||||
|
}
|
||||||
|
return leaf;
|
||||||
|
}
|
||||||
|
|
||||||
|
json = Utils.Clone(json)
|
||||||
|
|
||||||
|
let paths = metapaths["default"] ?? metapaths
|
||||||
|
|
||||||
|
for (const metapath of paths) {
|
||||||
|
if (metapath.typeHint !== "image" && metapath.typeHint !== "icon") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
FixImages.WalkPath(metapath.path, json, leaf => {
|
||||||
|
console.log("Detected leaf: ", leaf)
|
||||||
|
if (typeof leaf === "string") {
|
||||||
|
return replaceString(leaf)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metapath.type["some"] !== undefined && (<any[]>metapath.type).some(t => t["$ref"] == "\"#/definitions/TagRenderingConfigJson\"")) {
|
||||||
|
console.log("Possibly found a tagrendering")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return leaf;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
result: json
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,7 +37,8 @@ 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
|
* 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,
|
iconBadges?: {
|
||||||
|
if: string | AndOrTagConfigJson,
|
||||||
/**
|
/**
|
||||||
* Badge to show
|
* Badge to show
|
||||||
* Type: icon
|
* Type: icon
|
||||||
|
|
|
@ -119,45 +119,64 @@ export default class MoreScreen extends Combine {
|
||||||
]).SetClass("flex flex-col border border-gray-300 p-2 rounded-lg")
|
]).SetClass("flex flex-col border border-gray-300 p-2 rounded-lg")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static createButtonFor(state: UserRelatedState, id: string): BaseUIElement {
|
||||||
|
const allPreferences = state.osmConnection.preferencesHandler.preferences.data;
|
||||||
|
const length = Number(allPreferences[id + "-combined-length"])
|
||||||
|
let str = "";
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
str += allPreferences[id + "-" + i]
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const value: {
|
||||||
|
id: string
|
||||||
|
icon: string,
|
||||||
|
title: any,
|
||||||
|
shortDescription: any
|
||||||
|
} = JSON.parse(str)
|
||||||
|
|
||||||
|
return MoreScreen.createLinkButton(state, value, true)
|
||||||
|
} catch (e) {
|
||||||
|
console.debug("Could not parse unofficial theme information for " + id, e)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static createUnofficialThemeList(buttonClass: string, state: UserRelatedState, themeListClasses): BaseUIElement {
|
private static createUnofficialThemeList(buttonClass: string, state: UserRelatedState, themeListClasses): BaseUIElement {
|
||||||
const prefix = "mapcomplete-unofficial-theme-";
|
const prefix = "mapcomplete-unofficial-theme-";
|
||||||
return new VariableUiElement(state.osmConnection.preferencesHandler.preferences.map(allPreferences => {
|
|
||||||
console.log("All preferences are ", allPreferences)
|
|
||||||
const allThemes: BaseUIElement[] = []
|
|
||||||
for (const key in allPreferences) {
|
|
||||||
if (key.startsWith(prefix) && key.endsWith("-combined-length")) {
|
|
||||||
const id = key.substring(0, key.length - "-length".length)
|
|
||||||
const length = Number(allPreferences[key])
|
|
||||||
|
|
||||||
let str = "";
|
var currentIds: UIEventSource<string[]> = state.osmConnection.preferencesHandler.preferences
|
||||||
for (let i = 0; i < length; i++) {
|
.map(allPreferences => {
|
||||||
str += allPreferences[id + "-" + i]
|
const ids: string[] = []
|
||||||
}
|
|
||||||
console.log("Theme " + id + " has settings ", str)
|
|
||||||
try {
|
|
||||||
const value: {
|
|
||||||
id: string
|
|
||||||
icon: string,
|
|
||||||
title: any,
|
|
||||||
shortDescription: any
|
|
||||||
} = JSON.parse(str)
|
|
||||||
|
|
||||||
const link = MoreScreen.createLinkButton(state, value, true).SetClass(buttonClass)
|
for (const key in allPreferences) {
|
||||||
allThemes.push(link)
|
if (key.startsWith(prefix) && key.endsWith("-combined-length")) {
|
||||||
} catch (e) {
|
const id = key.substring(0, key.length - "-length".length)
|
||||||
console.error("Could not parse unofficial theme information for " + id, e)
|
ids.push(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (allThemes.length <= 0) {
|
return ids
|
||||||
return undefined;
|
});
|
||||||
}
|
var stableIds = UIEventSource.ListStabilized<string>(currentIds)
|
||||||
return new Combine([
|
|
||||||
Translations.t.general.customThemeIntro.Clone(),
|
return new VariableUiElement(
|
||||||
new Combine(allThemes).SetClass(themeListClasses)
|
stableIds.map(ids => {
|
||||||
]);
|
const allThemes: BaseUIElement[] = []
|
||||||
}));
|
for (const id of ids) {
|
||||||
|
const link = this.createButtonFor(state, id)
|
||||||
|
if (link !== undefined) {
|
||||||
|
allThemes.push(link.SetClass(buttonClass))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allThemes.length <= 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return new Combine([
|
||||||
|
Translations.t.general.customThemeIntro.Clone(),
|
||||||
|
new Combine(allThemes).SetClass(themeListClasses)
|
||||||
|
]);
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static createPreviouslyVistedHiddenList(state: UserRelatedState, buttonClass: string, themeListStyle: string) {
|
private static createPreviouslyVistedHiddenList(state: UserRelatedState, buttonClass: string, themeListStyle: string) {
|
||||||
|
|
|
@ -134,7 +134,7 @@ function main() {
|
||||||
return {typeHint: type.substr("type: ".length), type: schemePart.type ?? schemePart.anyOf}
|
return {typeHint: type.substr("type: ".length), type: schemePart.type ?? schemePart.anyOf}
|
||||||
}, themeSchema)
|
}, themeSchema)
|
||||||
|
|
||||||
// writeFileSync("./assets/layoutconfigmeta.json",JSON.stringify(withTypes.map(({path, t}) => ({path, ...t})), null, " "))
|
writeFileSync("./assets/layoutconfigmeta.json",JSON.stringify(withTypes.map(({path, t}) => ({path, ...t})), null, " "))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import T from "./TestHelper";
|
import T from "./TestHelper";
|
||||||
import {FixLegacyTheme} from "../Models/ThemeConfig/Conversion/LegacyJsonConvert";
|
import {FixImages, FixLegacyTheme} from "../Models/ThemeConfig/Conversion/LegacyJsonConvert";
|
||||||
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
|
import LayoutConfig from "../Models/ThemeConfig/LayoutConfig";
|
||||||
import {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
|
|
||||||
import {TagRenderingConfigJson} from "../Models/ThemeConfig/Json/TagRenderingConfigJson";
|
import {TagRenderingConfigJson} from "../Models/ThemeConfig/Json/TagRenderingConfigJson";
|
||||||
import {AddMiniMap} from "../Models/ThemeConfig/Conversion/PrepareTheme";
|
import {AddMiniMap} from "../Models/ThemeConfig/Conversion/PrepareTheme";
|
||||||
import {DetectShadowedMappings} from "../Models/ThemeConfig/Conversion/Validation";
|
import {DetectShadowedMappings} from "../Models/ThemeConfig/Conversion/Validation";
|
||||||
|
import * as Assert from "assert";
|
||||||
|
|
||||||
export default class LegacyThemeLoaderSpec extends T {
|
export default class LegacyThemeLoaderSpec extends T {
|
||||||
|
|
||||||
|
@ -143,215 +143,203 @@ export default class LegacyThemeLoaderSpec extends T {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly organic_waste_theme = {
|
private static readonly verkeerde_borden ={
|
||||||
"id": "recycling-organic",
|
"id": "https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/VerkeerdeBordenDatabank.json",
|
||||||
"title": {
|
"title": {
|
||||||
"nl": "Inzamelpunt organisch alval"
|
"nl": "VerkeerdeBordenDatabank",
|
||||||
},
|
"en": "Erratic Signs Database"
|
||||||
"shortDescription": {
|
|
||||||
"nl": "Inzamelpunt organisch alval"
|
|
||||||
},
|
},
|
||||||
|
"maintainer": "Seppe Santens",
|
||||||
|
"icon": "https://upload.wikimedia.org/wikipedia/commons/b/bc/Belgian_traffic_sign_A51.svg",
|
||||||
"description": {
|
"description": {
|
||||||
"nl": "Op deze kaart vindt u inzamelpunten voor organisch afval. Beheer deze naar goeddunken en vermogen."
|
"nl": "Een kaart om verkeerde of ontbrekende verkeersborden te tonen en te editeren.",
|
||||||
|
"en": "A map to show and edit incorrect or missing traffic signs."
|
||||||
|
},
|
||||||
|
"version": "2021-09-16",
|
||||||
|
"startLat": 51.08881,
|
||||||
|
"startLon": 3.447282,
|
||||||
|
"startZoom": 15,
|
||||||
|
"clustering": {
|
||||||
|
"maxZoom": 8
|
||||||
},
|
},
|
||||||
"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": [
|
"layers": [
|
||||||
{
|
{
|
||||||
"id": "recycling-organic",
|
"id": "trafficsign",
|
||||||
"name": {
|
"name": {
|
||||||
"nl": "Inzamelpunt organisch alval"
|
"nl": "verkeersbord",
|
||||||
|
"en": "traffic sign"
|
||||||
},
|
},
|
||||||
"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": "<a href=\"{website}\">{website}</a>"
|
|
||||||
},
|
|
||||||
"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": {
|
"source": {
|
||||||
"osmTags": {
|
"osmTags": {
|
||||||
"and": [
|
"and": [
|
||||||
"recycling:organic~*"
|
"traffic_sign~*",
|
||||||
|
"traffic_sign:issue~*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mapRendering": [
|
"minzoom": 10,
|
||||||
|
"title": {
|
||||||
|
"render": {
|
||||||
|
"nl": "verkeersbord",
|
||||||
|
"en": "traffic sign"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tagRenderings": [
|
||||||
|
"images",
|
||||||
{
|
{
|
||||||
"icon": {
|
"render": {
|
||||||
"render": "circle:white;https://upload.wikimedia.org/wikipedia/commons/1/15/Compost_…able_waste_-_biodegradable_waste_-_biological_waste_icon.png"
|
"nl": "ID verkeersbord: {traffic_sign}",
|
||||||
|
"en": "traffic sign ID: {traffic_sign}"
|
||||||
},
|
},
|
||||||
"iconSize": {
|
"question": {
|
||||||
"render": "40,40,center"
|
"nl": "Wat is het ID voor dit verkeersbord?",
|
||||||
|
"en": "What is ID for this traffic sign?"
|
||||||
},
|
},
|
||||||
"location": [
|
"freeform": {
|
||||||
"point"
|
"key": "traffic_sign"
|
||||||
]
|
},
|
||||||
|
"id": "trafficsign-traffic_sign"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"color": {
|
"render": {
|
||||||
"render": "#00f"
|
"nl": "Probleem bij dit verkeersbord: {traffic_sign:issue}",
|
||||||
|
"en": "Issue with this traffic sign: {traffic_sign:issue}"
|
||||||
},
|
},
|
||||||
"width": {
|
"question": {
|
||||||
"render": "8"
|
"nl": "Wat is het probleem met dit verkeersbord?",
|
||||||
}
|
"en": "What is the issue with this traffic sign?"
|
||||||
|
},
|
||||||
|
"freeform": {
|
||||||
|
"key": "traffic_sign:issue"
|
||||||
|
},
|
||||||
|
"id": "trafficsign-traffic_sign:issue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": {
|
||||||
|
"nl": "Wanneer werd dit verkeersbord laatst gesurveyed?",
|
||||||
|
"en": "When was this traffic sign last surveyed?"
|
||||||
|
},
|
||||||
|
"render": {
|
||||||
|
"nl": "Dit verkeersbord werd laatst gesurveyed op {survey:date}",
|
||||||
|
"en": "This traffic sign was last surveyed on {survey:date}"
|
||||||
|
},
|
||||||
|
"freeform": {
|
||||||
|
"key": "survey:date",
|
||||||
|
"type": "date"
|
||||||
|
},
|
||||||
|
"mappings": [
|
||||||
|
{
|
||||||
|
"if": "survey:date:={_now:date}",
|
||||||
|
"then": "Vandaag gesurveyed!"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "trafficsign-survey:date"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mapRendering": [
|
||||||
|
{
|
||||||
|
"icon": "./TS_bolt.svg",
|
||||||
|
"location": [
|
||||||
|
"point",
|
||||||
|
"centroid"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "notrafficsign",
|
||||||
|
"name": {
|
||||||
|
"nl": "geen verkeersbord",
|
||||||
|
"en": "no traffic sign"
|
||||||
|
},
|
||||||
|
"source": {
|
||||||
|
"osmTags": {
|
||||||
|
"and": [
|
||||||
|
{
|
||||||
|
"or": [
|
||||||
|
"no:traffic_sign~*",
|
||||||
|
"not:traffic_sign~*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"traffic_sign:issue~*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minzoom": 10,
|
||||||
|
"title": {
|
||||||
|
"render": {
|
||||||
|
"nl": "ontbrekend verkeersbord",
|
||||||
|
"en": "missing traffic sign"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tagRenderings": [
|
||||||
|
"images",
|
||||||
|
{
|
||||||
|
"render": {
|
||||||
|
"nl": "ID ontbrekend verkeersbord: {no:traffic_sign}",
|
||||||
|
"en": "missing traffic sign ID: {no:traffic_sign}"
|
||||||
|
},
|
||||||
|
"question": {
|
||||||
|
"nl": "Wat is het ID voor het ontbrekende verkeersbord?",
|
||||||
|
"en": "What is ID for the missing traffic sign?"
|
||||||
|
},
|
||||||
|
"freeform": {
|
||||||
|
"key": "no:traffic_sign"
|
||||||
|
},
|
||||||
|
"id": "notrafficsign-no:traffic_sign"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"render": {
|
||||||
|
"nl": "Probleem bij deze situatie: {traffic_sign:issue}",
|
||||||
|
"en": "Issue with this situation: {traffic_sign:issue}"
|
||||||
|
},
|
||||||
|
"question": {
|
||||||
|
"nl": "Wat is er mis met deze situatie?",
|
||||||
|
"en": "What is the issue with this situation?"
|
||||||
|
},
|
||||||
|
"freeform": {
|
||||||
|
"key": "traffic_sign:issue"
|
||||||
|
},
|
||||||
|
"id": "notrafficsign-traffic_sign:issue"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"question": {
|
||||||
|
"nl": "Wanneer werd deze situatie laatst gesurveyed?",
|
||||||
|
"en": "When was this situation last surveyed?"
|
||||||
|
},
|
||||||
|
"render": {
|
||||||
|
"nl": "Deze situatie werd laatst gesurveyed op {survey:date}",
|
||||||
|
"en": "This situation was last surveyed on {survey:date}"
|
||||||
|
},
|
||||||
|
"freeform": {
|
||||||
|
"key": "survey:date",
|
||||||
|
"type": "date"
|
||||||
|
},
|
||||||
|
"mappings": [
|
||||||
|
{
|
||||||
|
"if": "survey:date:={_now:date}",
|
||||||
|
"then": "Vandaag gesurveyed!"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "notrafficsign-survey:date"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mapRendering": [
|
||||||
|
{
|
||||||
|
"icon": "./TS_questionmark.svg",
|
||||||
|
"location": [
|
||||||
|
"point",
|
||||||
|
"centroid"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"defaultBackgroundId": "Stamen.TonerLite"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super([
|
super([
|
||||||
["Walking_node_theme", () => {
|
["Walking_node_theme", () => {
|
||||||
|
@ -439,8 +427,13 @@ export default class LegacyThemeLoaderSpec extends T {
|
||||||
}, "test");
|
}, "test");
|
||||||
T.isTrue(r0.errors.length > 0, "Failing case is not detected")
|
T.isTrue(r0.errors.length > 0, "Failing case is not detected")
|
||||||
}
|
}
|
||||||
|
],
|
||||||
]
|
["Images are rewritten", () => {
|
||||||
|
const fixed = new FixImages(new Set<string>()).convertStrict(LegacyThemeLoaderSpec.verkeerde_borden, "test")
|
||||||
|
const fixedValue = fixed.layers[0]["mapRendering"][0].icon
|
||||||
|
Assert.equal("https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/TS_bolt.svg",
|
||||||
|
fixedValue)
|
||||||
|
} ]
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
15
theme.html
15
theme.html
|
@ -37,14 +37,7 @@
|
||||||
<link href="./assets/generated/svg_mapcomplete_logo72.png" rel="apple-touch-icon" sizes="72x72">
|
<link href="./assets/generated/svg_mapcomplete_logo72.png" rel="apple-touch-icon" sizes="72x72">
|
||||||
|
|
||||||
<!-- THEME-SPECIFIC-END-->
|
<!-- THEME-SPECIFIC-END-->
|
||||||
|
|
||||||
<style>
|
|
||||||
#decoration-desktop img {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
@ -70,13 +63,15 @@
|
||||||
|
|
||||||
<div class="clutter absolute h-24 left-24 right-24 top-56 text-xl text-center"
|
<div class="clutter absolute h-24 left-24 right-24 top-56 text-xl text-center"
|
||||||
id="centermessage" style="z-index: 4000">
|
id="centermessage" style="z-index: 4000">
|
||||||
<h2>Loading MapComplete, hang on...</h2>
|
<h1>Loading MapComplete, hang on...</h1>
|
||||||
<p>Powered by OpenStreetMap</p>
|
<p class="subtle">Powered by OpenStreetMap</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="absolute" id="belowmap" style="z-index: -1">Below</span>
|
<span class="absolute" id="belowmap" style="z-index: -1">Below</span>
|
||||||
<div id="leafletDiv"></div>
|
<div id="leafletDiv"></div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<script src="./index.ts"></script>
|
<script src="./index.ts"></script>
|
||||||
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js"></script>
|
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js"></script>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue