forked from MapComplete/MapComplete
Merge latest master
This commit is contained in:
commit
c693faea95
249 changed files with 8610 additions and 2657 deletions
|
@ -12,6 +12,9 @@ import {SubstitutedTranslation} from "../../UI/SpecialVisualizations";
|
|||
import {Utils} from "../../Utils";
|
||||
import Combine from "../../UI/Base/Combine";
|
||||
import {VariableUiElement} from "../../UI/Base/VariableUIElement";
|
||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||
import {FixedUiElement} from "../../UI/Base/FixedUiElement";
|
||||
import {UIElement} from "../../UI/UIElement";
|
||||
|
||||
export default class LayerConfig {
|
||||
|
||||
|
@ -33,6 +36,7 @@ export default class LayerConfig {
|
|||
titleIcons: TagRenderingConfig[];
|
||||
|
||||
icon: TagRenderingConfig;
|
||||
iconOverlays: { if: TagsFilter, then: TagRenderingConfig, badge: boolean }[]
|
||||
iconSize: TagRenderingConfig;
|
||||
rotation: TagRenderingConfig;
|
||||
color: TagRenderingConfig;
|
||||
|
@ -89,6 +93,12 @@ export default class LayerConfig {
|
|||
return tagRenderings.map(
|
||||
(renderingJson, i) => {
|
||||
if (typeof renderingJson === "string") {
|
||||
|
||||
if(renderingJson === "questions"){
|
||||
return new TagRenderingConfig("questions")
|
||||
}
|
||||
|
||||
|
||||
const shared = SharedTagRenderings.SharedTagRendering[renderingJson];
|
||||
if (shared !== undefined) {
|
||||
return shared;
|
||||
|
@ -100,8 +110,20 @@ export default class LayerConfig {
|
|||
}
|
||||
|
||||
this.tagRenderings = trs(json.tagRenderings).concat(roamingRenderings);
|
||||
this.titleIcons = trs(json.titleIcons ?? ["phonelink","wikipedialink","osmlink", "sharelink"]);
|
||||
|
||||
|
||||
|
||||
const titleIcons = [];
|
||||
const defaultIcons = ["phonelink", "emaillink", "wikipedialink", "osmlink", "sharelink"];
|
||||
for (const icon of (json.titleIcons ?? defaultIcons)) {
|
||||
if (icon === "defaults") {
|
||||
titleIcons.push(...defaultIcons);
|
||||
} else {
|
||||
titleIcons.push(icon);
|
||||
}
|
||||
}
|
||||
|
||||
this.titleIcons = trs(titleIcons);
|
||||
|
||||
|
||||
function tr(key, deflt) {
|
||||
const v = json[key];
|
||||
|
@ -124,6 +146,18 @@ export default class LayerConfig {
|
|||
|
||||
this.title = tr("title", undefined);
|
||||
this.icon = tr("icon", Img.AsData(Svg.bug));
|
||||
this.iconOverlays = (json.iconOverlays ?? []).map(overlay => {
|
||||
let tr = new TagRenderingConfig(overlay.then);
|
||||
if (typeof overlay.then === "string" && SharedTagRenderings.SharedIcons[overlay.then] !== undefined) {
|
||||
tr = SharedTagRenderings.SharedIcons[overlay.then];
|
||||
}
|
||||
return {
|
||||
if: FromJSON.Tag(overlay.if),
|
||||
then: tr,
|
||||
badge: overlay.badge ?? false
|
||||
}
|
||||
});
|
||||
|
||||
const iconPath = this.icon.GetRenderValue({id: "node/-1"}).txt;
|
||||
if (iconPath.startsWith(Utils.assets_path)) {
|
||||
const iconKey = iconPath.substr(Utils.assets_path.length);
|
||||
|
@ -141,7 +175,7 @@ export default class LayerConfig {
|
|||
}
|
||||
|
||||
|
||||
public GenerateLeafletStyle(tags: any, clickable: boolean):
|
||||
public GenerateLeafletStyle(tags: UIEventSource<any>, clickable: boolean):
|
||||
{
|
||||
color: string;
|
||||
icon: {
|
||||
|
@ -149,8 +183,7 @@ export default class LayerConfig {
|
|||
popupAnchor: [number, number];
|
||||
iconAnchor: [number, number];
|
||||
iconSize: [number, number];
|
||||
html: string;
|
||||
rotation: string;
|
||||
html: UIElement;
|
||||
className?: string;
|
||||
};
|
||||
weight: number; dashArray: number[]
|
||||
|
@ -174,11 +207,10 @@ export default class LayerConfig {
|
|||
}
|
||||
|
||||
function render(tr: TagRenderingConfig, deflt?: string) {
|
||||
const str = (tr?.GetRenderValue(tags)?.txt ?? deflt);
|
||||
return SubstitutedTranslation.SubstituteKeys(str, tags);
|
||||
const str = (tr?.GetRenderValue(tags.data)?.txt ?? deflt);
|
||||
return SubstitutedTranslation.SubstituteKeys(str, tags.data);
|
||||
}
|
||||
|
||||
const iconUrl = render(this.icon);
|
||||
const iconSize = render(this.iconSize, "40,40,center").split(",");
|
||||
const dashArray = render(this.dashArray).split(" ").map(Number);
|
||||
let color = render(this.color, "#00f");
|
||||
|
@ -188,8 +220,6 @@ export default class LayerConfig {
|
|||
}
|
||||
|
||||
const weight = rendernum(this.width, 5);
|
||||
const rotation = render(this.rotation, "0deg");
|
||||
|
||||
|
||||
const iconW = num(iconSize[0]);
|
||||
const iconH = num(iconSize[1]);
|
||||
|
@ -211,24 +241,84 @@ export default class LayerConfig {
|
|||
anchorH = iconH;
|
||||
}
|
||||
|
||||
|
||||
let html = `<img src="${iconUrl}" style="width:100%;height:100%;rotate:${rotation};display:block;" />`;
|
||||
|
||||
if (iconUrl.startsWith(Utils.assets_path)) {
|
||||
const key = iconUrl.substr(Utils.assets_path.length);
|
||||
html = new Combine([
|
||||
(Svg.All[key] as string).replace(/stop-color:#000000/g, 'stop-color:' + color)
|
||||
]).SetStyle(`width:100%;height:100%;rotate:${rotation};display:block;`).Render();
|
||||
}
|
||||
const iconUrlStatic = render(this.icon);
|
||||
const self = this;
|
||||
var mappedHtml = tags.map(tgs => {
|
||||
// What do you mean, 'tgs' is never read?
|
||||
// It is read implicitly in the 'render' method
|
||||
const iconUrl = render(self.icon);
|
||||
const rotation = render(self.rotation, "0deg");
|
||||
|
||||
let htmlParts: UIElement[] = [];
|
||||
let sourceParts = iconUrl.split(";");
|
||||
|
||||
function genHtmlFromString(sourcePart: string): UIElement {
|
||||
const style = `width:100%;height:100%;rotate:${rotation};display:block;position: absolute; top: 0, left: 0`;
|
||||
let html: UIElement = new FixedUiElement(`<img src="${sourcePart}" style="${style}" />`);
|
||||
const match = sourcePart.match(/([a-zA-Z0-9_]*):#([0-9a-fA-F]{3,6})/)
|
||||
if (match !== null && Svg.All[match[1] + ".svg"] !== undefined) {
|
||||
html = new Combine([
|
||||
(Svg.All[match[1] + ".svg"] as string)
|
||||
.replace(/#000000/g, "#" + match[2])
|
||||
]).SetStyle(style);
|
||||
}
|
||||
|
||||
if (sourcePart.startsWith(Utils.assets_path)) {
|
||||
const key = sourcePart.substr(Utils.assets_path.length);
|
||||
html = new Combine([
|
||||
(Svg.All[key] as string).replace(/stop-color:#000000/g, 'stop-color:' + color)
|
||||
]).SetStyle(style);
|
||||
}
|
||||
return html;
|
||||
}
|
||||
|
||||
|
||||
for (const sourcePart of sourceParts) {
|
||||
htmlParts.push(genHtmlFromString(sourcePart))
|
||||
}
|
||||
|
||||
|
||||
let badges = [];
|
||||
for (const iconOverlay of self.iconOverlays) {
|
||||
if (!iconOverlay.if.matchesProperties(tgs)) {
|
||||
continue;
|
||||
}
|
||||
if (iconOverlay.badge) {
|
||||
const badgeParts: UIElement[] = [];
|
||||
const partDefs = iconOverlay.then.GetRenderValue(tgs).txt.split(";");
|
||||
|
||||
for (const badgePartStr of partDefs) {
|
||||
badgeParts.push(genHtmlFromString(badgePartStr))
|
||||
}
|
||||
|
||||
const badgeCompound = new Combine(badgeParts)
|
||||
.SetStyle("display:flex;position:relative;width:100%;height:100%;");
|
||||
|
||||
badges.push(badgeCompound)
|
||||
|
||||
} else {
|
||||
htmlParts.push(genHtmlFromString(
|
||||
iconOverlay.then.GetRenderValue(tgs).txt));
|
||||
}
|
||||
}
|
||||
|
||||
if (badges.length > 0) {
|
||||
const badgesComponent = new Combine(badges)
|
||||
.SetStyle("display:flex;height:50%;width:100%;position:absolute;top:50%;left:50%;");
|
||||
htmlParts.push(badgesComponent)
|
||||
}
|
||||
return new Combine(htmlParts).Render();
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
icon:
|
||||
{
|
||||
html: html,
|
||||
html: new VariableUiElement(mappedHtml),
|
||||
iconSize: [iconW, iconH],
|
||||
iconAnchor: [anchorW, anchorH],
|
||||
popupAnchor: [0, 3 - anchorH],
|
||||
rotation: rotation,
|
||||
iconUrl: iconUrl,
|
||||
iconUrl: iconUrlStatic,
|
||||
className: clickable ? "leaflet-div-icon" : "leaflet-div-icon unclickable"
|
||||
},
|
||||
color: color,
|
||||
|
|
|
@ -57,9 +57,25 @@ export interface LayerConfigJson {
|
|||
/**
|
||||
* The icon for an element.
|
||||
* Note that this also doubles as the icon for this layer (rendered with the overpass-tags) ánd the icon in the presets.
|
||||
*
|
||||
* The result of the icon is rendered as follows:
|
||||
* the resulting string is interpreted as a _list_ of items, seperated by ";". The bottommost layer is the first layer.
|
||||
* As a result, on could use a generic pin, then overlay it with a specific icon.
|
||||
* To make things even more practical, one can use all svgs from the folder "assets/svg" and _substitute the color_ in it.
|
||||
* E.g. to draw a red pin, use "pin:#f00", to have a green circle with your icon on top, use `circle:#0f0;<path to my icon.svg>`
|
||||
*/
|
||||
icon?: string | TagRenderingConfigJson;
|
||||
|
||||
/**
|
||||
* IconsOverlays are a list of extra icons/badges to overlay over the icon.
|
||||
* The 'badge'-toggle changes their behaviour.
|
||||
* If badge is set, it will be added as a 25% height icon at the bottom right of the icon, with all the badges in a flex layout.
|
||||
* If badges is false, it'll be a simple overlay
|
||||
*
|
||||
* Note: strings are interpreted as icons, so layering and substituting is supported
|
||||
*/
|
||||
iconOverlays?: {if: string | AndOrTagConfigJson, then: string | TagRenderingConfigJson, badge?: boolean}[]
|
||||
|
||||
/**
|
||||
* A string containing "width,height" or "width,height,anchorpoint" where anchorpoint is any of 'center', 'top', 'bottom', 'left', 'right', 'bottomleft','topright', ...
|
||||
* Default is '40,40,center'
|
||||
|
@ -110,7 +126,17 @@ export interface LayerConfigJson {
|
|||
passAllFeatures?:boolean
|
||||
|
||||
/**
|
||||
* Presets for this layer
|
||||
* Presets for this layer.
|
||||
* A preset shows up when clicking the map on a without data (or when right-clicking/long-pressing);
|
||||
* it will prompt the user to add a new point.
|
||||
*
|
||||
* The most important aspect are the tags, which define which tags the new point will have;
|
||||
* The title is shown in the dialog, along with the first sentence of the description.
|
||||
*
|
||||
* Upon confirmation, the full description is shown beneath the buttons - perfect to add pictures and examples.
|
||||
*
|
||||
* Note: the icon of the preset is determined automatically based on the tags and the icon above. Don't worry about that!
|
||||
* NB: if no presets are defined, the popup to add new points doesn't show up at all
|
||||
*/
|
||||
presets?: {
|
||||
title: string | any,
|
||||
|
@ -127,6 +153,10 @@ export interface LayerConfigJson {
|
|||
* Note that we can also use a string here - where the string refers to a tagrenering defined in `assets/questions/questions.json`,
|
||||
* where a few very general questions are defined e.g. website, phone number, ...
|
||||
*
|
||||
* A special value is 'questions', which indicates the location of the questions box. If not specified, it'll be appended to the bottom of the featureInfobox.
|
||||
*
|
||||
*/
|
||||
tagRenderings?: (string | TagRenderingConfigJson) []
|
||||
|
||||
|
||||
}
|
|
@ -26,13 +26,20 @@ export default class TagRenderingConfig {
|
|||
mappings?: {
|
||||
if: TagsFilter,
|
||||
then: Translation
|
||||
hideInAnswer: boolean
|
||||
hideInAnswer: boolean | TagsFilter
|
||||
}[]
|
||||
|
||||
constructor(json: string | TagRenderingConfigJson, context?: string) {
|
||||
|
||||
if(json === undefined){
|
||||
throw "Initing a TagRenderingConfig with undefined in "+context;
|
||||
if (json === "questions") {
|
||||
// Very special value
|
||||
this.render = null;
|
||||
this.question = null;
|
||||
this.condition = null;
|
||||
}
|
||||
|
||||
if (json === undefined) {
|
||||
throw "Initing a TagRenderingConfig with undefined in " + context;
|
||||
}
|
||||
if (typeof json === "string") {
|
||||
this.render = Translations.T(json);
|
||||
|
@ -62,10 +69,16 @@ export default class TagRenderingConfig {
|
|||
if (mapping.then === undefined) {
|
||||
throw "Invalid mapping: if without body"
|
||||
}
|
||||
let hideInAnswer : boolean | TagsFilter = false;
|
||||
if (typeof mapping.hideInAnswer === "boolean") {
|
||||
hideInAnswer = mapping.hideInAnswer;
|
||||
} else if (mapping.hideInAnswer !== undefined) {
|
||||
hideInAnswer = FromJSON.Tag(mapping.hideInAnswer, `${context}.mapping[${i}].hideInAnswer`);
|
||||
}
|
||||
return {
|
||||
if: FromJSON.Tag(mapping.if, `${context}.mapping[${i}]`),
|
||||
if: FromJSON.Tag(mapping.if, `${context}.mapping[${i}].if`),
|
||||
then: Translations.T(mapping.then),
|
||||
hideInAnswer: mapping.hideInAnswer ?? false
|
||||
hideInAnswer: hideInAnswer
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import * as information_boards from "../assets/layers/information_board/informat
|
|||
import * as direction from "../assets/layers/direction/direction.json"
|
||||
import * as surveillance_camera from "../assets/layers/surveillance_cameras/surveillance_cameras.json"
|
||||
import * as toilets from "../assets/layers/toilets/toilets.json"
|
||||
|
||||
import * as bookcases from "../assets/layers/public_bookcases/public_bookcases.json"
|
||||
import LayerConfig from "./JSON/LayerConfig";
|
||||
|
||||
export default class SharedLayers {
|
||||
|
@ -44,6 +44,7 @@ export default class SharedLayers {
|
|||
new LayerConfig(direction,[], "shared_layers"),
|
||||
new LayerConfig(information_boards,[], "shared_layers"),
|
||||
new LayerConfig(toilets,[], "shared_layers"),
|
||||
new LayerConfig(bookcases,[], "shared_layers"),
|
||||
new LayerConfig(surveillance_camera,[], "shared_layers")
|
||||
];
|
||||
|
||||
|
|
|
@ -5,27 +5,28 @@ import * as icons from "../assets/tagRenderings/icons.json";
|
|||
export default class SharedTagRenderings {
|
||||
|
||||
public static SharedTagRendering = SharedTagRenderings.generatedSharedFields();
|
||||
public static SharedIcons = SharedTagRenderings.generatedSharedFields(true);
|
||||
|
||||
private static generatedSharedFields() {
|
||||
private static generatedSharedFields(iconsOnly = false) {
|
||||
const dict = {}
|
||||
|
||||
|
||||
function add(key, store){
|
||||
|
||||
function add(key, store) {
|
||||
try {
|
||||
dict[key] = new TagRenderingConfig(store[key])
|
||||
dict[key] = new TagRenderingConfig(store[key], key)
|
||||
} catch (e) {
|
||||
console.error("BUG: could not parse", key, " from questions.json or icons.json", e)
|
||||
console.error("BUG: could not parse", key, " from questions.json or icons.json - this error happened during the build step of the SharedTagRenderings", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (const key in questions) {
|
||||
add(key, questions);
|
||||
|
||||
if (!iconsOnly) {
|
||||
for (const key in questions) {
|
||||
add(key, questions);
|
||||
}
|
||||
}
|
||||
for (const key in icons) {
|
||||
add(key, icons);
|
||||
}
|
||||
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue