Merge develop

This commit is contained in:
Pieter Vander Vennet 2022-02-14 20:10:49 +01:00
commit 29f6716fa9
209 changed files with 8475 additions and 4933 deletions

View file

@ -2,7 +2,7 @@ import {Utils} from "../Utils";
export default class Constants {
public static vNumber = "0.15.7";
public static vNumber = "0.15.8";
public static ImgurApiKey = '7070e7167f0a25a'
public static readonly mapillary_client_token_v4 = "MLY|4441509239301885|b40ad2d3ea105435bd40c7e76993ae85"

View file

@ -12,10 +12,10 @@ export abstract class Conversion<TIn, TOut> {
protected readonly doc: string;
public readonly name: string
constructor(doc: string, modifiedAttributes: string[] = [], name?: string) {
constructor(doc: string, modifiedAttributes: string[] = [], name: string) {
this.modifiedAttributes = modifiedAttributes;
this.doc = doc + "\n\nModified attributes are\n" + modifiedAttributes.join(", ");
this.name = name ?? this.constructor.name
this.name = name
}
public static strict<T>(fixed: { errors?: string[], warnings?: string[], information?: string[], result?: T }): T {
@ -94,8 +94,8 @@ export class OnEveryConcat<X, T> extends DesugaringStep<T> {
private readonly step: Conversion<X, X[]>;
constructor(key: string, step: Conversion<X, X[]>) {
super(`Applies ${step.constructor.name} onto every object of the list \`${key}\`. The results are concatenated and used as new list`, [key],
"OnEveryConcat("+step.name+")");
super(`Applies ${step.name} onto every object of the list \`${key}\`. The results are concatenated and used as new list`, [key],
"OnEvery("+key+").Concat("+step.name+")");
this.step = step;
this.key = key;
}
@ -126,8 +126,9 @@ export class Fuse<T> extends DesugaringStep<T> {
private readonly steps: DesugaringStep<T>[];
constructor(doc: string, ...steps: DesugaringStep<T>[]) {
super((doc ?? "") + "This fused pipeline of the following steps: " + steps.map(s => s.constructor.name).join(", "),
Utils.Dedup([].concat(...steps.map(step => step.modifiedAttributes)))
super((doc ?? "") + "This fused pipeline of the following steps: " + steps.map(s => s.name).join(", "),
Utils.Dedup([].concat(...steps.map(step => step.modifiedAttributes))),
"Fuse of "+steps.map(s => s.name).join(", ")
);
this.steps = steps;
}
@ -163,7 +164,7 @@ export class SetDefault<T> extends DesugaringStep<T> {
private readonly _overrideEmptyString: boolean;
constructor(key: string, value: any, overrideEmptyString = false) {
super("Sets " + key + " to a default value if undefined");
super("Sets " + key + " to a default value if undefined", [], "SetDefault of "+key);
this.key = key;
this.value = value;
this._overrideEmptyString = overrideEmptyString;

View file

@ -16,7 +16,7 @@ export default class CreateNoteImportLayer extends Conversion<LayerConfigJson, L
super([
"Advanced conversion which deducts a layer showing all notes that are 'importable' (i.e. a note that contains a link to some MapComplete theme, with hash '#import').",
"The import buttons and matches will be based on the presets of the given theme",
].join("\n\n"), [])
].join("\n\n"), [],"CreateNoteImportLayer")
this._includeClosedNotesDays = includeClosedNotesDays;
}

View file

@ -6,13 +6,14 @@ import * as tagrenderingmetapaths from "../../../assets/tagrenderingconfigmeta.j
export class ExtractImages extends Conversion<LayoutConfigJson, string[]> {
constructor() {
super("Extract all images from a layoutConfig using the meta paths");
super("Extract all images from a layoutConfig using the meta paths",[],"ExctractImages");
}
convert(json: LayoutConfigJson, context: string): { result: string[] } {
convert(json: LayoutConfigJson, context: string): { result: string[], errors: string[] } {
const paths = metapaths["default"] ?? metapaths
const trpaths = tagrenderingmetapaths["default"] ?? tagrenderingmetapaths
const allFoundImages = []
const errors = []
for (const metapath of paths) {
if (metapath.typeHint === undefined) {
continue
@ -34,7 +35,13 @@ export class ExtractImages extends Conversion<LayoutConfigJson, string[]> {
if (trpath.typeHint !== "rendered") {
continue
}
Utils.CollectPath(trpath.path, foundImage, allFoundImages)
const fromPath = Utils.CollectPath(trpath.path, foundImage)
for (const img of fromPath) {
if (typeof img !== "string") {
errors.push("Found an image path that is not a path at " + context + "." + metapath.path.join(".") + ": " + JSON.stringify(img))
}
}
allFoundImages.push(...fromPath.filter(i => typeof i === "string"))
}
}
@ -44,9 +51,9 @@ export class ExtractImages extends Conversion<LayoutConfigJson, string[]> {
}
}
const splitParts = [].concat(...allFoundImages.map(img => img.split(";")))
const splitParts = [].concat(...Utils.NoNull(allFoundImages).map(img => img.split(";")))
.map(img => img.split(":")[0])
return {result: Utils.Dedup(splitParts)};
return {result: Utils.Dedup(splitParts), errors};
}
}
@ -55,7 +62,7 @@ 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");
super("Walks over the entire theme and replaces images to the relative URL. Only works if the ID of the theme is an URL",[],"fixImages");
this._knownImages = knownImages;
}

View file

@ -8,7 +8,8 @@ export class UpdateLegacyLayer extends DesugaringStep<LayerConfigJson | string |
constructor() {
super("Updates various attributes from the old data format to the new to provide backwards compatibility with the formats",
["overpassTags", "source.osmtags", "tagRenderings[*].id", "mapRendering"]);
["overpassTags", "source.osmtags", "tagRenderings[*].id", "mapRendering"],
"UpdateLegacyLayer");
}
convert(json: LayerConfigJson, context: string): { result: LayerConfigJson; errors: string[]; warnings: string[] } {
@ -120,7 +121,7 @@ export class UpdateLegacyLayer extends DesugaringStep<LayerConfigJson | string |
class UpdateLegacyTheme extends DesugaringStep<LayoutConfigJson> {
constructor() {
super("Small fixes in the theme config", ["roamingRenderings"]);
super("Small fixes in the theme config", ["roamingRenderings"],"UpdateLegacyTheme");
}
convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors: string[]; warnings: string[] } {

View file

@ -8,7 +8,7 @@ import {Translation} from "../../../UI/i18n/Translation";
class ExpandTagRendering extends Conversion<string | TagRenderingConfigJson | { builtin: string | string[], override: any }, TagRenderingConfigJson[]> {
private readonly _state: DesugaringContext;
constructor(state: DesugaringContext) {
super("Converts a tagRenderingSpec into the full tagRendering", []);
super("Converts a tagRenderingSpec into the full tagRendering", [],"ExpandTagRendering");
this._state = state;
}
@ -147,7 +147,8 @@ class ExpandGroupRewrite extends Conversion<{
constructor(state: DesugaringContext) {
super(
"Converts a rewrite config for tagRenderings into the expanded form"
"Converts a rewrite config for tagRenderings into the expanded form",[],
"ExpandGroupRewrite"
);
this._expandSubTagRenderings = new ExpandTagRendering(state)
}
@ -156,7 +157,7 @@ class ExpandGroupRewrite extends Conversion<{
{
rewrite:
{ sourceString: string; into: string[] }[]; renderings: (string | { builtin: string; override: any } | TagRenderingConfigJson)[]
} | TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson[]; errors: string[]; warnings: string[] } {
} | TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson[]; errors: string[]; warnings?: string[] } {
if (json["rewrite"] === undefined) {
return {result: [<TagRenderingConfigJson>json], errors: [], warnings: []}
@ -166,20 +167,33 @@ class ExpandGroupRewrite extends Conversion<{
{ sourceString: string[]; into: (string | any)[][] };
renderings: (string | { builtin: string; override: any } | TagRenderingConfigJson)[]
}>json;
{
const errors = []
if(!Array.isArray(config.rewrite.sourceString)){
let extra = "";
if(typeof config.rewrite.sourceString === "string"){
extra=`<br/>Try <span class='literal-code'>"sourceString": [ "${config.rewrite.sourceString}" ] </span> instead (note the [ and ])`
}
const msg = context+"<br/>Invalid format: a rewrite block is defined, but the 'sourceString' should be an array of strings, but it is a "+typeof config.rewrite.sourceString + extra
errors.push(msg)
}
const expectedLength = config.rewrite.sourceString.length
for (let i = 0; i < config.rewrite.into.length; i++){
const targets = config.rewrite.into[i];
if(targets.length !== expectedLength){
errors.push(context+".rewrite.into["+i+"]: expected "+expectedLength+" values, but got "+targets.length)
}
if(!Array.isArray(targets)){
errors.push(`${context}.rewrite.into[${i}] should be an array of values, but it is a `+typeof targets)
} else if(targets.length !== expectedLength){
errors.push(`${context}.rewrite.into[${i}]:<br/>The rewrite specified ${config.rewrite.sourceString} as sourcestring, which consists of ${expectedLength} values. The target ${JSON.stringify(targets)} has ${targets.length} items`)
if(typeof targets[0] !== "string"){
errors.push(context+".rewrite.into["+i+"]: expected a string as first rewrite value values, but got "+targets[0])
}
}
}
if (errors.length > 0) {

View file

@ -16,7 +16,7 @@ class SubstituteLayer extends Conversion<(string | LayerConfigJson), LayerConfig
constructor(
state: DesugaringContext,
) {
super("Converts the identifier of a builtin layer into the actual layer, or converts a 'builtin' syntax with override in the fully expanded form", []);
super("Converts the identifier of a builtin layer into the actual layer, or converts a 'builtin' syntax with override in the fully expanded form", [],"SubstuteLayers");
this._state = state;
}
@ -130,7 +130,7 @@ class AddDefaultLayers extends DesugaringStep<LayoutConfigJson> {
private _state: DesugaringContext;
constructor(state: DesugaringContext) {
super("Adds the default layers, namely: " + Constants.added_by_default.join(", "), ["layers"]);
super("Adds the default layers, namely: " + Constants.added_by_default.join(", "), ["layers"],"AddDefaultLayers");
this._state = state;
}
@ -182,7 +182,7 @@ class AddDefaultLayers extends DesugaringStep<LayoutConfigJson> {
class AddImportLayers extends DesugaringStep<LayoutConfigJson> {
constructor() {
super("For every layer in the 'layers'-list, create a new layer which'll import notes. (Note that priviliged layers and layers which have a geojson-source set are ignored)", ["layers"]);
super("For every layer in the 'layers'-list, create a new layer which'll import notes. (Note that priviliged layers and layers which have a geojson-source set are ignored)", ["layers"],"AddImportLayers");
}
convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors: string[] } {
@ -240,7 +240,7 @@ class AddImportLayers extends DesugaringStep<LayoutConfigJson> {
export class AddMiniMap extends DesugaringStep<LayerConfigJson> {
private readonly _state: DesugaringContext;
constructor(state: DesugaringContext, ) {
super("Adds a default 'minimap'-element to the tagrenderings if none of the elements define such a minimap", ["tagRenderings"]);
super("Adds a default 'minimap'-element to the tagrenderings if none of the elements define such a minimap", ["tagRenderings"],"AddMiniMap");
this._state = state;
}
@ -291,7 +291,7 @@ export class AddMiniMap extends DesugaringStep<LayerConfigJson> {
class ApplyOverrideAll extends DesugaringStep<LayoutConfigJson> {
constructor() {
super("Applies 'overrideAll' onto every 'layer'. The 'overrideAll'-field is removed afterwards", ["overrideAll", "layers"]);
super("Applies 'overrideAll' onto every 'layer'. The 'overrideAll'-field is removed afterwards", ["overrideAll", "layers"],"ApplyOverrideAll");
}
convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors: string[]; warnings: string[] } {
@ -321,7 +321,7 @@ class ApplyOverrideAll extends DesugaringStep<LayoutConfigJson> {
class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
private readonly _state: DesugaringContext;
constructor(state: DesugaringContext, ) {
super("If a layer has a dependency on another layer, these layers are added automatically on the theme. (For example: defibrillator depends on 'walls_and_buildings' to snap onto. This layer is added automatically)", ["layers"]);
super("If a layer has a dependency on another layer, these layers are added automatically on the theme. (For example: defibrillator depends on 'walls_and_buildings' to snap onto. This layer is added automatically)", ["layers"],"AddDependencyLayersToTheme");
this._state = state;
}
@ -406,12 +406,31 @@ class AddDependencyLayersToTheme extends DesugaringStep<LayoutConfigJson> {
}
}
class PreparePersonalTheme extends DesugaringStep<LayoutConfigJson> {
private readonly _state: DesugaringContext;
constructor(state: DesugaringContext) {
super("Adds every public layer to the personal theme",["layers"],"PreparePersonalTheme");
this._state = state;
}
convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors?: string[]; warnings?: string[]; information?: string[] } {
if(json.id !== "personal"){
return {result: json}
}
json.layers = Array.from(this._state.sharedLayers.keys())
return {result: json};
}
}
export class PrepareTheme extends Fuse<LayoutConfigJson> {
constructor(state: DesugaringContext) {
super(
"Fully prepares and expands a theme",
new PreparePersonalTheme(state),
new OnEveryConcat("layers", new SubstituteLayer(state)),
new SetDefault("socialImage", "assets/SocialImage.png", true),
new OnEvery("layers", new PrepareLayer(state)),

View file

@ -10,13 +10,14 @@ import {TagRenderingConfigJson} from "../Json/TagRenderingConfigJson";
import {TagUtils} from "../../../Logic/Tags/TagUtils";
import {ExtractImages} from "./FixImages";
import ScriptUtils from "../../../scripts/ScriptUtils";
import {And} from "../../../Logic/Tags/And";
class ValidateLanguageCompleteness extends DesugaringStep<any> {
private readonly _languages: string[];
constructor(...languages: string[]) {
super("Checks that the given object is fully translated in the specified languages", []);
super("Checks that the given object is fully translated in the specified languages", [], "ValidateLanguageCompleteness");
this._languages = languages;
}
@ -25,7 +26,7 @@ class ValidateLanguageCompleteness extends DesugaringStep<any> {
const translations = Translation.ExtractAllTranslationsFrom(
obj
)
for (const neededLanguage of this._languages) {
for (const neededLanguage of this._languages ?? ["en"]) {
translations
.filter(t => t.tr.translations[neededLanguage] === undefined && t.tr.translations["*"] === undefined)
.forEach(missing => {
@ -50,7 +51,7 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
private readonly _isBuiltin: boolean;
constructor(knownImagePaths: Set<string>, path: string, isBuiltin: boolean) {
super("Doesn't change anything, but emits warnings and errors", []);
super("Doesn't change anything, but emits warnings and errors", [],"ValidateTheme");
this.knownImagePaths = knownImagePaths;
this._path = path;
this._isBuiltin = isBuiltin;
@ -164,7 +165,7 @@ export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> {
class OverrideShadowingCheck extends DesugaringStep<LayoutConfigJson> {
constructor() {
super("Checks that an 'overrideAll' does not override a single override");
super("Checks that an 'overrideAll' does not override a single override",[],"OverrideShadowingCheck");
}
convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors?: string[]; warnings?: string[] } {
@ -204,7 +205,7 @@ export class PrevalidateTheme extends Fuse<LayoutConfigJson> {
export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJson> {
constructor() {
super("Checks that the mappings don't shadow each other");
super("Checks that the mappings don't shadow each other",[],"DetectShadowedMappings");
}
convert(json: TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[] } {
@ -254,7 +255,7 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
private readonly _isBuiltin: boolean;
constructor(knownImagePaths: Set<string>, path: string, isBuiltin: boolean) {
super("Doesn't change anything, but emits warnings and errors", []);
super("Doesn't change anything, but emits warnings and errors", [],"ValidateLayer");
this.knownImagePaths = knownImagePaths;
this._path = path;
this._isBuiltin = isBuiltin;
@ -339,6 +340,24 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
new DetectShadowedMappings().convertAll(<TagRenderingConfigJson[]>json.tagRenderings, context + ".tagRenderings")
}
if(json.presets !== undefined){
// Check that a preset will be picked up by the layer itself
const baseTags = TagUtils.Tag( json.source.osmTags)
for (let i = 0; i < json.presets.length; i++){
const preset = json.presets[i];
const tags : {k: string,v: string}[]= new And(preset.tags.map(t => TagUtils.Tag(t))).asChange({id:"node/-1"})
const properties = {}
for (const tag of tags) {
properties[tag.k] = tag.v
}
const doMatch = baseTags.matchesProperties(properties)
if(!doMatch){
errors.push(context+".presets["+i+"]: This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: "+JSON.stringify(properties)+"\n The required tags are: "+baseTags.asHumanString(false, false, {}))
}
}
}
} catch (e) {
errors.push(e)
}

View file

@ -0,0 +1,31 @@
import ExtraLinkConfigJson from "./Json/ExtraLinkConfigJson";
import {Translation} from "../../UI/i18n/Translation";
import Translations from "../../UI/i18n/Translations";
export default class ExtraLinkConfig {
public readonly icon?: string
public readonly text?: Translation
public readonly href: string
public readonly newTab?: false | boolean
public readonly requirements?: Set<("iframe" | "no-iframe" | "welcome-message" | "no-welcome-message")>
constructor(configJson: ExtraLinkConfigJson, context) {
this.icon = configJson.icon
this.text = Translations.T(configJson.text)
this.href = configJson.href
this.newTab = configJson.newTab
this.requirements = new Set(configJson.requirements)
for (let requirement of configJson.requirements) {
if (this.requirements.has(<any>("no-" + requirement))) {
throw "Conflicting requirements found for " + context + ".extraLink: both '" + requirement + "' and 'no-" + requirement + "' found"
}
}
if (this.icon === undefined && this.text === undefined) {
throw "At " + context + ".extraLink: define at least an icon or a text to show. Both are undefined, this is not allowed"
}
}
}

View file

@ -18,7 +18,7 @@ export default class FilterConfig {
originalTagsSpec: string | AndOrTagConfigJson
fields: { name: string, type: string }[]
}[];
public readonly defaultSelection : number
public readonly defaultSelection? : number
constructor(json: FilterConfigJson, context: string) {
if (json.options === undefined) {
@ -79,7 +79,7 @@ export default class FilterConfig {
return {question: question, osmTags: osmTags, fields, originalTagsSpec: option.osmTags};
});
this.defaultSelection = defaultSelection ?? 0
this.defaultSelection = defaultSelection
if (this.options.some(o => o.fields.length > 0) && this.options.length > 1) {
throw `Invalid filter at ${context}: a filter with textfields should only offer a single option.`
@ -103,10 +103,11 @@ export default class FilterConfig {
let defaultValue = ""
if(this.options.length > 1){
defaultValue = ""+this.defaultSelection
defaultValue = ""+(this.defaultSelection ?? 0)
}else{
if(this.defaultSelection > 0){
defaultValue = ""+this.defaultSelection
// Only a single option
if(this.defaultSelection === 0){
defaultValue = "true"
}
}
const qp = QueryParameters.GetQueryParameter("filter-" + this.id, defaultValue, "State of filter " + this.id)

View file

@ -0,0 +1,7 @@
export default interface ExtraLinkConfigJson {
icon?: string,
text?: string | any,
href: string,
newTab?: false | boolean,
requirements?: ("iframe" | "no-iframe" | "welcome-message" | "no-welcome-message")[]
}

View file

@ -399,4 +399,13 @@ export interface LayerConfigJson {
*/
units?: UnitConfigJson[]
/**
* If set, synchronizes wether or not this layer is selected.
*
* no: Do not sync at all, always revert to default
* local: keep selection on local storage
* theme-only: sync via OSM, but this layer will only be toggled in this theme
* global: all layers with this ID will be synced accross all themes
*/
syncSelection?: "no" | "local" | "theme-only" | "global"
}

View file

@ -1,5 +1,6 @@
import {LayerConfigJson} from "./LayerConfigJson";
import TilesourceConfigJson from "./TilesourceConfigJson";
import ExtraLinkConfigJson from "./ExtraLinkConfigJson";
/**
* Defines the entire theme.
@ -244,18 +245,70 @@ export interface LayoutConfigJson {
*/
lockLocation?: [[number, number], [number, number]] | number[][];
enableUserBadge?: boolean;
enableShareScreen?: boolean;
enableMoreQuests?: boolean;
enableLayers?: boolean;
enableSearch?: boolean;
enableAddNewPoints?: boolean;
enableGeolocation?: boolean;
enableBackgroundLayerSelection?: boolean;
enableShowAllQuestions?: boolean;
enableDownload?: boolean;
enablePdfDownload?: boolean;
enableIframePopout?: true | boolean;
/**
* Adds an additional button on the top-left of the application.
* This can link to an arbitrary location.
*
* Note that {lat},{lon},{zoom}, {language} and {theme} will be replaced
*
* Default: {icon: "./assets/svg/pop-out.svg", href: 'https://mapcomplete.osm.be/{theme}.html?lat={lat}&lon={lon}&z={zoom}, requirements: ["iframe","no-welcome-message]},
*
*/
extraLink?: ExtraLinkConfigJson
/**
* If set to false, disables logging in.
* The userbadge will be hidden, all login-buttons will be hidden and editing will be disabled
*/
enableUserBadge?: true | boolean;
/**
* If false, hides the tab 'share'-tab in the welcomeMessage
*/
enableShareScreen?: true | boolean;
/**
* Hides the tab with more themes in the welcomeMessage
*/
enableMoreQuests?: true | boolean;
/**
* If false, the layer selection/filter view will be hidden
* The corresponding URL-parameter is 'fs-filters' instead of 'fs-layers'
*/
enableLayers?: true | boolean;
/**
* If set to false, hides the search bar
*/
enableSearch?: true | boolean;
/**
* If set to false, the ability to add new points or nodes will be disabled.
* Editing already existing features will still be possible
*/
enableAddNewPoints?: true | boolean;
/**
* If set to false, the 'geolocation'-button will be hidden.
*/
enableGeolocation?: true | boolean;
/**
* Enable switching the backgroundlayer.
* If false, the quickswitch-buttons are removed (bottom left) and the dropdown in the layer selection is removed as well
*/
enableBackgroundLayerSelection?: true | boolean;
/**
* If set to true, will show _all_ unanswered questions in a popup instead of just the next one
*/
enableShowAllQuestions?: false | boolean;
/**
* If set to true, download button for the data will be shown (offers downloading as geojson and csv)
*/
enableDownload?: false | boolean;
/**
* If set to true, exporting a pdf is enabled
*/
enablePdfDownload?: false | boolean;
/**
* If true, notes will be loaded and parsed. If a note is an import (as created by the import_helper.html-tool from mapcomplete),
* these notes will be shown if a relevant layer is present.
*/
enableNoteImports?: true | boolean;
/**

View file

@ -61,6 +61,8 @@ export default class LayerConfig extends WithContextLoader {
public readonly filterIsSameAs: string;
public readonly forceLoad: boolean;
public readonly syncSelection: "no" | "local" | "theme-only" | "global"
constructor(
json: LayerConfigJson,
context?: string,
@ -90,7 +92,7 @@ export default class LayerConfig extends WithContextLoader {
}
this.maxAgeOfCache = json.source.maxCacheAge ?? 24 * 60 * 60 * 30
this.syncSelection = json.syncSelection;
const osmTags = TagUtils.Tag(
json.source.osmTags,
context + "source.osmTags"
@ -402,7 +404,7 @@ export default class LayerConfig extends WithContextLoader {
const icon = this.mapRendering
.filter(mr => mr.location.has("point"))
.map(mr => mr.icon.render.txt)
.map(mr => mr.icon?.render?.txt)
.find(i => i !== undefined)
let iconImg = ""
if (icon !== undefined) {

View file

@ -5,6 +5,7 @@ import {LayerConfigJson} from "./Json/LayerConfigJson";
import Constants from "../Constants";
import TilesourceConfig from "./TilesourceConfig";
import {ExtractImages} from "./Conversion/FixImages";
import ExtraLinkConfig from "./ExtraLinkConfig";
export default class LayoutConfig {
public readonly id: string;
@ -42,7 +43,6 @@ export default class LayoutConfig {
public readonly enableShowAllQuestions: boolean;
public readonly enableExportButton: boolean;
public readonly enablePdfDownload: boolean;
public readonly enableIframePopout: boolean;
public readonly customCss?: string;
@ -51,8 +51,9 @@ export default class LayoutConfig {
public readonly overpassMaxZoom: number
public readonly osmApiTileSize: number
public readonly official: boolean;
public readonly usedImages : string[]
public readonly usedImages: string[]
public readonly extraLink?: ExtraLinkConfig
constructor(json: LayoutConfigJson, official = true, context?: string) {
this.official = official;
@ -70,7 +71,7 @@ export default class LayoutConfig {
this.credits = json.credits;
this.version = json.version;
this.language = json.mustHaveLanguage ?? Array.from(Object.keys(json.title));
this.usedImages =Array.from( new ExtractImages().convertStrict(json, "while extracting the images")).sort()
this.usedImages = Array.from(new ExtractImages().convertStrict(json, "while extracting the images of " + json.id + " " + context ?? "")).sort()
{
if (typeof json.title === "string") {
throw `The title of a theme should always be a translation, as it sets the corresponding languages (${context}.title). The themenID is ${this.id}; the offending object is ${JSON.stringify(json.title)} which is a ${typeof json.title})`
@ -118,6 +119,13 @@ export default class LayoutConfig {
// At this point, layers should be expanded and validated either by the generateScript or the LegacyJsonConvert
this.layers = json.layers.map(lyrJson => new LayerConfig(<LayerConfigJson>lyrJson, json.id + ".layers." + lyrJson["id"], official));
this.extraLink = new ExtraLinkConfig(json.extraLink ?? {
icon: "./assets/svg/pop-out.svg",
href: "https://mapcomplete.osm.be/{theme}.html?lat={lat}&lon={lon}&z={zoom}&language={language}",
newTab: true,
requirements: ["iframe","no-welcome-message"]
}, context)
this.clustering = {
maxZoom: 16,
@ -148,7 +156,6 @@ export default class LayoutConfig {
this.enableShowAllQuestions = json.enableShowAllQuestions ?? false;
this.enableExportButton = json.enableDownload ?? false;
this.enablePdfDownload = json.enablePdfDownload ?? false;
this.enableIframePopout = json.enableIframePopout ?? true
this.customCss = json.customCss;
this.overpassUrl = Constants.defaultOverpassUrls
if (json.overpassUrl !== undefined) {

View file

@ -96,6 +96,14 @@ export default class PointRenderingConfig extends WithContextLoader {
img.SetClass("badge")
}
return img
} else if (Svg.All[htmlSpec + ".svg"] !== undefined) {
const svg = (Svg.All[htmlSpec + ".svg"] as string)
const img = new Img(svg, true)
.SetStyle(style)
if (isBadge) {
img.SetClass("badge")
}
return img
} else {
return new FixedUiElement(`<img src="${htmlSpec}" style="${style}" />`);
}
@ -126,7 +134,7 @@ export default class PointRenderingConfig extends WithContextLoader {
}
return PointRenderingConfig.FromHtmlMulti(htmlDefs, rotation, false, defaultPin)
}
public GetSimpleIcon(tags: UIEventSource<any>): BaseUIElement {
const self = this;
if (this.icon === undefined) {
@ -192,10 +200,10 @@ export default class PointRenderingConfig extends WithContextLoader {
const icon = this.GetSimpleIcon(tags)
let badges = undefined;
if( options?.includeBadges ?? true){
if (options?.includeBadges ?? true) {
badges = this.GetBadges(tags)
}
const iconAndBadges = new Combine([icon,badges ])
const iconAndBadges = new Combine([icon, badges])
.SetClass("block relative")
if (!options?.noSize) {