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 LZString from "lz-string";
|
||||
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 SharedTagRenderings from "../Customizations/SharedTagRenderings";
|
||||
import * as known_layers from "../assets/generated/known_layers.json"
|
||||
import {LayoutConfigJson} from "../Models/ThemeConfig/Json/LayoutConfigJson";
|
||||
import {PrepareTheme} from "../Models/ThemeConfig/Conversion/PrepareTheme";
|
||||
import {Layer} from "leaflet";
|
||||
|
||||
import * as licenses from "../assets/generated/license_info.json"
|
||||
export default class DetermineLayout {
|
||||
|
||||
private static readonly _knownImages =new Set( Array.from(licenses).map(l => l.path))
|
||||
|
||||
/**
|
||||
* Gets the correct layout for this website
|
||||
*/
|
||||
|
@ -144,6 +145,7 @@ export default class DetermineLayout {
|
|||
sharedLayers: knownLayersDict
|
||||
}
|
||||
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")
|
||||
console.log("The layoutconfig is ", json)
|
||||
return json
|
||||
|
|
|
@ -2,7 +2,8 @@ import {LayoutConfigJson} from "../Json/LayoutConfigJson";
|
|||
import {Utils} from "../../../Utils";
|
||||
import LineRenderingConfigJson from "../Json/LineRenderingConfigJson";
|
||||
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 }> {
|
||||
|
||||
|
@ -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
|
||||
*/
|
||||
iconBadges?: { if: string | AndOrTagConfigJson,
|
||||
iconBadges?: {
|
||||
if: string | AndOrTagConfigJson,
|
||||
/**
|
||||
* Badge to show
|
||||
* Type: icon
|
||||
|
|
|
@ -119,45 +119,64 @@ export default class MoreScreen extends Combine {
|
|||
]).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 {
|
||||
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 = "";
|
||||
for (let i = 0; i < length; i++) {
|
||||
str += allPreferences[id + "-" + i]
|
||||
}
|
||||
console.log("Theme " + id + " has settings ", str)
|
||||
try {
|
||||
const value: {
|
||||
id: string
|
||||
icon: string,
|
||||
title: any,
|
||||
shortDescription: any
|
||||
} = JSON.parse(str)
|
||||
var currentIds: UIEventSource<string[]> = state.osmConnection.preferencesHandler.preferences
|
||||
.map(allPreferences => {
|
||||
const ids: string[] = []
|
||||
|
||||
const link = MoreScreen.createLinkButton(state, value, true).SetClass(buttonClass)
|
||||
allThemes.push(link)
|
||||
} catch (e) {
|
||||
console.error("Could not parse unofficial theme information for " + id, e)
|
||||
for (const key in allPreferences) {
|
||||
if (key.startsWith(prefix) && key.endsWith("-combined-length")) {
|
||||
const id = key.substring(0, key.length - "-length".length)
|
||||
ids.push(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allThemes.length <= 0) {
|
||||
return undefined;
|
||||
}
|
||||
return new Combine([
|
||||
Translations.t.general.customThemeIntro.Clone(),
|
||||
new Combine(allThemes).SetClass(themeListClasses)
|
||||
]);
|
||||
}));
|
||||
return ids
|
||||
});
|
||||
var stableIds = UIEventSource.ListStabilized<string>(currentIds)
|
||||
|
||||
return new VariableUiElement(
|
||||
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) {
|
||||
|
|
|
@ -134,7 +134,7 @@ function main() {
|
|||
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, " "))
|
||||
writeFileSync("./assets/layoutconfigmeta.json",JSON.stringify(withTypes.map(({path, t}) => ({path, ...t})), null, " "))
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
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 {LayerConfigJson} from "../Models/ThemeConfig/Json/LayerConfigJson";
|
||||
import {TagRenderingConfigJson} from "../Models/ThemeConfig/Json/TagRenderingConfigJson";
|
||||
import {AddMiniMap} from "../Models/ThemeConfig/Conversion/PrepareTheme";
|
||||
import {DetectShadowedMappings} from "../Models/ThemeConfig/Conversion/Validation";
|
||||
import * as Assert from "assert";
|
||||
|
||||
export default class LegacyThemeLoaderSpec extends T {
|
||||
|
||||
|
@ -143,215 +143,203 @@ export default class LegacyThemeLoaderSpec extends T {
|
|||
]
|
||||
}
|
||||
|
||||
private static readonly organic_waste_theme = {
|
||||
"id": "recycling-organic",
|
||||
private static readonly verkeerde_borden ={
|
||||
"id": "https://raw.githubusercontent.com/seppesantens/MapComplete-Themes/main/VerkeerdeBordenDatabank/VerkeerdeBordenDatabank.json",
|
||||
"title": {
|
||||
"nl": "Inzamelpunt organisch alval"
|
||||
},
|
||||
"shortDescription": {
|
||||
"nl": "Inzamelpunt organisch alval"
|
||||
"nl": "VerkeerdeBordenDatabank",
|
||||
"en": "Erratic Signs Database"
|
||||
},
|
||||
"maintainer": "Seppe Santens",
|
||||
"icon": "https://upload.wikimedia.org/wikipedia/commons/b/bc/Belgian_traffic_sign_A51.svg",
|
||||
"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": [
|
||||
{
|
||||
"id": "recycling-organic",
|
||||
"id": "trafficsign",
|
||||
"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": {
|
||||
"osmTags": {
|
||||
"and": [
|
||||
"recycling:organic~*"
|
||||
"traffic_sign~*",
|
||||
"traffic_sign:issue~*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"mapRendering": [
|
||||
"minzoom": 10,
|
||||
"title": {
|
||||
"render": {
|
||||
"nl": "verkeersbord",
|
||||
"en": "traffic sign"
|
||||
}
|
||||
},
|
||||
"tagRenderings": [
|
||||
"images",
|
||||
{
|
||||
"icon": {
|
||||
"render": "circle:white;https://upload.wikimedia.org/wikipedia/commons/1/15/Compost_…able_waste_-_biodegradable_waste_-_biological_waste_icon.png"
|
||||
"render": {
|
||||
"nl": "ID verkeersbord: {traffic_sign}",
|
||||
"en": "traffic sign ID: {traffic_sign}"
|
||||
},
|
||||
"iconSize": {
|
||||
"render": "40,40,center"
|
||||
"question": {
|
||||
"nl": "Wat is het ID voor dit verkeersbord?",
|
||||
"en": "What is ID for this traffic sign?"
|
||||
},
|
||||
"location": [
|
||||
"point"
|
||||
]
|
||||
"freeform": {
|
||||
"key": "traffic_sign"
|
||||
},
|
||||
"id": "trafficsign-traffic_sign"
|
||||
},
|
||||
{
|
||||
"color": {
|
||||
"render": "#00f"
|
||||
"render": {
|
||||
"nl": "Probleem bij dit verkeersbord: {traffic_sign:issue}",
|
||||
"en": "Issue with this traffic sign: {traffic_sign:issue}"
|
||||
},
|
||||
"width": {
|
||||
"render": "8"
|
||||
}
|
||||
"question": {
|
||||
"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() {
|
||||
super([
|
||||
["Walking_node_theme", () => {
|
||||
|
@ -439,8 +427,13 @@ export default class LegacyThemeLoaderSpec extends T {
|
|||
}, "test");
|
||||
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">
|
||||
|
||||
<!-- THEME-SPECIFIC-END-->
|
||||
|
||||
<style>
|
||||
#decoration-desktop img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
@ -70,13 +63,15 @@
|
|||
|
||||
<div class="clutter absolute h-24 left-24 right-24 top-56 text-xl text-center"
|
||||
id="centermessage" style="z-index: 4000">
|
||||
<h2>Loading MapComplete, hang on...</h2>
|
||||
<p>Powered by OpenStreetMap</p>
|
||||
<h1>Loading MapComplete, hang on...</h1>
|
||||
<p class="subtle">Powered by OpenStreetMap</p>
|
||||
</div>
|
||||
|
||||
<span class="absolute" id="belowmap" style="z-index: -1">Below</span>
|
||||
<div id="leafletDiv"></div>
|
||||
|
||||
|
||||
|
||||
<script src="./index.ts"></script>
|
||||
<script async data-goatcounter="https://pietervdvn.goatcounter.com/count" src="//gc.zgo.at/count.js"></script>
|
||||
|
||||
|
|
Loading…
Reference in a new issue