forked from MapComplete/MapComplete
Merge develop
This commit is contained in:
commit
29f6716fa9
209 changed files with 8475 additions and 4933 deletions
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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[] } {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)),
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
31
Models/ThemeConfig/ExtraLinkConfig.ts
Normal file
31
Models/ThemeConfig/ExtraLinkConfig.ts
Normal 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"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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)
|
||||
|
|
7
Models/ThemeConfig/Json/ExtraLinkConfigJson.ts
Normal file
7
Models/ThemeConfig/Json/ExtraLinkConfigJson.ts
Normal 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")[]
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue