Merge master

This commit is contained in:
Pieter Vander Vennet 2021-06-24 01:55:45 +02:00
commit aa50d33b81
53 changed files with 1094 additions and 411 deletions

View file

@ -187,7 +187,7 @@ export default class LayerConfig {
const keys = Array.from(SharedTagRenderings.SharedTagRendering.keys())
throw `Predefined tagRendering ${renderingJson} not found in ${context}.\n Try one of ${(keys.join(", "))}`;
throw `Predefined tagRendering ${renderingJson} not found in ${context}.\n Try one of ${(keys.join(", "))}\n If you intent to output this text literally, use {\"render\": <your text>} instead"}`;
}
return new TagRenderingConfig(renderingJson, self.source.osmTags, `${context}.tagrendering[${i}]`);
});
@ -343,7 +343,7 @@ export default class LayerConfig {
const iconW = num(iconSize[0]);
let iconH = num(iconSize[1]);
const mode = iconSize[2] ?? "center"
const mode = iconSize[2]?.trim()?.toLowerCase() ?? "center"
let anchorW = iconW / 2;
let anchorH = iconH / 2;
@ -365,12 +365,6 @@ export default class LayerConfig {
const self = this;
const mappedHtml = tags.map(tgs => {
function genHtmlFromString(sourcePart: string): BaseUIElement {
if (sourcePart.indexOf("html:") == 0) {
// We use § as a replacement for ;
const html = sourcePart.substring("html:".length)
const inner = new FixedUiElement(SubstitutingTag.substituteString(html, tgs)).SetClass("block w-min text-center")
return new Combine([inner]).SetClass("flex flex-col items-center");
}
const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0; left: 0`;
let html: BaseUIElement = new FixedUiElement(`<img src="${sourcePart}" style="${style}" />`);
@ -432,7 +426,7 @@ export default class LayerConfig {
try {
const label = self.label?.GetRenderValue(tgs)?.Subs(tgs)
?.SetClass("block w-min text-center")
?.SetClass("block text-center")
?.SetStyle("margin-top: " + (iconH + 2) + "px")
if (label !== undefined) {
htmlParts.push(new Combine([label]).SetClass("flex flex-col items-center"))

View file

@ -424,13 +424,13 @@ def clean_input(contents):
yield row
def contributor_count(stats):
def contributor_count(stats, index=1, item = "contributor"):
seen_contributors = set()
for line in stats:
contributor = line[1]
contributor = line[index]
if(contributor in seen_contributors):
continue
print("New contributor " + str(len(seen_contributors) + 1) + ": "+contributor)
print("New " + item + " " + str(len(seen_contributors) + 1) + ": "+contributor)
seen_contributors.add(contributor)
print(line)
@ -440,10 +440,10 @@ def main():
stats = list(clean_input(csv.reader(csvfile, delimiter=',', quotechar='"')))
print("Found " + str(len(stats)) + " changesets")
contributor_count(stats)
create_graphs(stats)
create_per_theme_graphs(stats, 15)
create_per_contributor_graphs(stats, 25)
contributor_count(stats, 3, "theme")
# create_graphs(stats)
# create_per_theme_graphs(stats, 15)
# create_per_contributor_graphs(stats, 25)
print("All done!")

View file

@ -352,6 +352,11 @@ export class InitUiElements {
State.state.backgroundLayer = State.state.backgroundLayerId
.map((selectedId: string) => {
if(selectedId === undefined){
return AvailableBaseLayers.osmCarto
}
const available = State.state.availableBackgroundLayers.data;
for (const layer of available) {
if (layer.id === selectedId) {

View file

@ -18,7 +18,7 @@ export default class AvailableBaseLayers {
{
id: "osm",
name: "OpenStreetMap",
layer: AvailableBaseLayers.CreateBackgroundLayer("osm", "OpenStreetMap",
layer: () => AvailableBaseLayers.CreateBackgroundLayer("osm", "OpenStreetMap",
"https://tile.openstreetmap.org/{z}/{x}/{y}.png", "OpenStreetMap", "https://openStreetMap.org/copyright",
19,
false, false),
@ -28,6 +28,7 @@ export default class AvailableBaseLayers {
}
public static layerOverview = AvailableBaseLayers.LoadRasterIndex().concat(AvailableBaseLayers.LoadProviderIndex());
public availableEditorLayers: UIEventSource<BaseLayer[]>;
@ -123,7 +124,7 @@ export default class AvailableBaseLayers {
continue
}
const leafletLayer = AvailableBaseLayers.CreateBackgroundLayer(
const leafletLayer: () => TileLayer = () => AvailableBaseLayers.CreateBackgroundLayer(
props.id,
props.name,
props.url,
@ -150,10 +151,10 @@ export default class AvailableBaseLayers {
private static LoadProviderIndex(): BaseLayer[] {
// @ts-ignore
X; // Import X to make sure the namespace is not optimized away
function l(id: string, name: string) {
function l(id: string, name: string) : BaseLayer{
try {
const layer: any = L.tileLayer.provider(id, undefined);
return {
const layer: any = () => L.tileLayer.provider(id, undefined);
const baseLayer : BaseLayer = {
feature: null,
id: id,
name: name,
@ -161,6 +162,7 @@ export default class AvailableBaseLayers {
min_zoom: layer.minzoom,
max_zoom: layer.maxzoom
}
return baseLayer
} catch (e) {
console.error("Could not find provided layer", name, e);
return null;

View file

@ -1,4 +1,4 @@
import {ImagesInCategory, Wikidata, Wikimedia} from "../Web/Wikimedia";
import {ImagesInCategory, Wikidata, Wikimedia} from "../ImageProviders/Wikimedia";
import {UIEventSource} from "../UIEventSource";
/**

View file

@ -0,0 +1,9 @@
import {Mapillary} from "./Mapillary";
import {Wikimedia} from "./Wikimedia";
import {Imgur} from "./Imgur";
export default class AllImageProviders{
public static ImageAttributionSource = [Imgur.singleton, Mapillary.singleton, Wikimedia.singleton]
}

View file

@ -5,7 +5,6 @@ import BaseUIElement from "../../UI/BaseUIElement";
export default abstract class ImageAttributionSource {
private _cache = new Map<string, UIEventSource<LicenseInfo>>()
GetAttributionFor(url: string): UIEventSource<LicenseInfo> {
@ -22,6 +21,7 @@ export default abstract class ImageAttributionSource {
public abstract SourceIcon(backlinkSource?: string) : BaseUIElement;
protected abstract DownloadAttribution(url: string): UIEventSource<LicenseInfo>;
/*Converts a value to a URL. Can return null if not applicable*/
public PrepareUrl(value: string): string{
return value;
}

View file

@ -4,6 +4,7 @@ import BaseUIElement from "../../UI/BaseUIElement";
import Svg from "../../Svg";
import {UIEventSource} from "../UIEventSource";
import Link from "../../UI/Base/Link";
import {Utils} from "../../Utils";
/**
* This module provides endpoints for wikipedia/wikimedia and others
@ -138,10 +139,15 @@ export class Wikimedia extends ImageAttributionSource {
"api.php?action=query&prop=imageinfo&iiprop=extmetadata&" +
"titles=" + filename +
"&format=json&origin=*";
console.log("Getting attribution at ", url)
$.getJSON(url, function (data) {
Utils.downloadJson(url).then(
data =>{
const licenseInfo = new LicenseInfo();
const license = data.query.pages[-1].imageinfo[0].extmetadata;
const license = (data.query.pages[-1].imageinfo ?? [])[0]?.extmetadata;
if(license === undefined){
console.error("This file has no usable metedata or license attached... Please fix the license info file yourself!")
source.setData(null)
return;
}
licenseInfo.artist = license.Artist?.value;
licenseInfo.license = license.License?.value;
@ -152,7 +158,9 @@ export class Wikimedia extends ImageAttributionSource {
licenseInfo.credit = license.Credit?.value;
licenseInfo.description = license.ImageDescription?.value;
source.setData(licenseInfo);
});
}
)
return source;
}

View file

@ -97,7 +97,7 @@ export class OsmPreferences {
public GetPreference(key: string, prefix: string = "mapcomplete-"): UIEventSource<string> {
key = prefix + key;
key = key.replace(/[:\\\/"' {}.%_]/g, '')
key = key.replace(/[:\\\/"' {}.%]/g, '')
if (key.length >= 255) {
throw "Preferences: key length to big";
}

View file

@ -3,7 +3,7 @@ import {TileLayer} from "leaflet";
export default interface BaseLayer {
id: string,
name: string,
layer: TileLayer,
layer: () => TileLayer,
max_zoom: number,
min_zoom: number;
feature: any,

19
Svg.ts
View file

@ -104,11 +104,26 @@ export default class Svg {
public static direction_svg() { return new Img(Svg.direction, true);}
public static direction_ui() { return new FixedUiElement(Svg.direction_img);}
public static direction_gradient = " <svg xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" id=\"svg8\" version=\"1.1\" viewBox=\"0 0 100 100\" height=\"100\" width=\"100\"> <metadata id=\"metadata8\"> <rdf:RDF> <cc:Work rdf:about=\"\"> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" /> </cc:Work> </rdf:RDF> </metadata> <defs id=\"defs6\"> <linearGradient id=\"linearGradient820\"> <stop style=\"stop-color:#000000;stop-opacity:1;\" offset=\"0\" id=\"innercolor\" /> <stop style=\"stop-color:#000000;stop-opacity:0\" offset=\"1\" id=\"outercolor\" /> </linearGradient> <radialGradient xlink:href=\"#linearGradient820\" id=\"radialGradient828\" cx=\"49.787739\" cy=\"53.828533\" fx=\"49.787739\" fy=\"53.828533\" r=\"28.883806\" gradientTransform=\"matrix(1.5439431,-0.01852438,0.02075364,1.7297431,-27.986574,-42.187244)\" gradientUnits=\"userSpaceOnUse\" /> </defs> <path id=\"path821\" d=\"M 50,50 21.042889,9.3993342 C 36.191421,-2.001434 60.726552,-3.6768009 78.8105,9.1490935 Z\" style=\"fill:url(#radialGradient828);fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1\" /> </svg> "
public static direction_gradient = " <svg xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" version=\"1.0\" width=\"860.50732pt\" height=\"860.50732pt\" viewBox=\"0 0 860.50732 860.50732\" preserveAspectRatio=\"xMidYMid meet\" id=\"svg14\" sodipodi:docname=\"direction_gradient.svg\" inkscape:version=\"0.92.5 (2060ec1f9f, 2020-04-08)\"> <defs id=\"defs18\"> <linearGradient inkscape:collect=\"always\" id=\"linearGradient832\"> <stop style=\"stop-color:#000000;stop-opacity:1;\" offset=\"0\" id=\"stop828\" /> <stop style=\"stop-color:#000000;stop-opacity:0;\" offset=\"1\" id=\"stop830\" /> </linearGradient> <radialGradient inkscape:collect=\"always\" xlink:href=\"#linearGradient832\" id=\"radialGradient838\" cx=\"430.25363\" cy=\"519.61188\" fx=\"430.25363\" fy=\"519.61188\" r=\"305.54589\" gradientTransform=\"matrix(0.95288409,-0.94890664,0.94542304,0.94938587,-470.98122,345.21193)\" gradientUnits=\"userSpaceOnUse\" /> </defs> <sodipodi:namedview pagecolor=\"#ffffff\" bordercolor=\"#666666\" borderopacity=\"1\" objecttolerance=\"10\" gridtolerance=\"10\" guidetolerance=\"10\" inkscape:pageopacity=\"0\" inkscape:pageshadow=\"2\" inkscape:window-width=\"1920\" inkscape:window-height=\"999\" id=\"namedview16\" showgrid=\"false\" showguides=\"true\" inkscape:guide-bbox=\"true\" inkscape:zoom=\"0.70710678\" inkscape:cx=\"279.00239\" inkscape:cy=\"856.75313\" inkscape:window-x=\"0\" inkscape:window-y=\"0\" inkscape:window-maximized=\"1\" inkscape:current-layer=\"svg14\"> <sodipodi:guide position=\"430.25363,862.49682\" orientation=\"1,0\" id=\"guide832\" inkscape:locked=\"false\" /> <sodipodi:guide position=\"-42.427977,430.25368\" orientation=\"0,1\" id=\"guide834\" inkscape:locked=\"false\" /> <sodipodi:guide position=\"398.27788,720.18823\" orientation=\"0,1\" id=\"guide840\" inkscape:locked=\"false\" /> </sodipodi:namedview> <metadata id=\"metadata2\"> Created by potrace 1.15, written by Peter Selinger 2001-2017 <rdf:RDF> <cc:Work rdf:about=\"\"> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" /> <dc:title /> </cc:Work> </rdf:RDF> </metadata> <path style=\"fill:url(#radialGradient838);fill-opacity:1;stroke:none;stroke-width:2.83464575;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1\" d=\"M 735.79979,124.70799 C 654.79116,43.598883 544.88842,-2.0206645 430.25389,-2.121103 315.61937,-2.0206592 205.71663,43.598888 124.70801,124.70799 l 305.54588,305.54589 z\" id=\"path836\" inkscape:connector-curvature=\"0\" sodipodi:nodetypes=\"ccccc\" /> </svg> "
public static direction_gradient_img = Img.AsImageElement(Svg.direction_gradient)
public static direction_gradient_svg() { return new Img(Svg.direction_gradient, true);}
public static direction_gradient_ui() { return new FixedUiElement(Svg.direction_gradient_img);}
public static direction_masked = " <svg xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" version=\"1.0\" width=\"860.50732pt\" height=\"860.50732pt\" viewBox=\"0 0 860.50732 860.50732\" preserveAspectRatio=\"xMidYMid meet\" id=\"svg14\" sodipodi:docname=\"direction_masked.svg\" inkscape:version=\"0.92.5 (2060ec1f9f, 2020-04-08)\"> <defs id=\"defs18\" /> <sodipodi:namedview pagecolor=\"#ffffff\" bordercolor=\"#666666\" borderopacity=\"1\" objecttolerance=\"10\" gridtolerance=\"10\" guidetolerance=\"10\" inkscape:pageopacity=\"0\" inkscape:pageshadow=\"2\" inkscape:window-width=\"1920\" inkscape:window-height=\"999\" id=\"namedview16\" showgrid=\"false\" showguides=\"true\" inkscape:guide-bbox=\"true\" inkscape:zoom=\"0.70710678\" inkscape:cx=\"418.71218\" inkscape:cy=\"725.43016\" inkscape:window-x=\"0\" inkscape:window-y=\"0\" inkscape:window-maximized=\"1\" inkscape:current-layer=\"svg14\"> <sodipodi:guide position=\"430.25363,862.49682\" orientation=\"1,0\" id=\"guide832\" inkscape:locked=\"false\" /> <sodipodi:guide position=\"-42.427977,430.25368\" orientation=\"0,1\" id=\"guide834\" inkscape:locked=\"false\" /> </sodipodi:namedview> <metadata id=\"metadata2\"> Created by potrace 1.15, written by Peter Selinger 2001-2017 <rdf:RDF> <cc:Work rdf:about=\"\"> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" /> </cc:Work> </rdf:RDF> </metadata> <path style=\"fill:#000000;fill-opacity:0.39163497;stroke:none;stroke-width:2.83464575;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1\" d=\"M 124.70776,124.7084 A 432.375,432.375 0 0 0 -2.121338,430.25429 432.375,432.375 0 0 0 430.25366,862.62929 432.375,432.375 0 0 0 862.62866,430.25429 432.375,432.375 0 0 0 735.79811,124.70986 L 430.25366,430.25429 Z\" id=\"path836\" inkscape:connector-curvature=\"0\" /> </svg> "
public static direction_masked_img = Img.AsImageElement(Svg.direction_masked)
public static direction_masked_svg() { return new Img(Svg.direction_masked, true);}
public static direction_masked_ui() { return new FixedUiElement(Svg.direction_masked_img);}
public static direction_outline = " <svg xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" version=\"1.0\" width=\"860.50732pt\" height=\"860.50732pt\" viewBox=\"0 0 860.50732 860.50732\" preserveAspectRatio=\"xMidYMid meet\" id=\"svg14\" sodipodi:docname=\"direction_outline.svg\" inkscape:version=\"0.92.5 (2060ec1f9f, 2020-04-08)\"> <defs id=\"defs18\" /> <sodipodi:namedview pagecolor=\"#ffffff\" bordercolor=\"#666666\" borderopacity=\"1\" objecttolerance=\"10\" gridtolerance=\"10\" guidetolerance=\"10\" inkscape:pageopacity=\"0\" inkscape:pageshadow=\"2\" inkscape:window-width=\"1920\" inkscape:window-height=\"999\" id=\"namedview16\" showgrid=\"false\" showguides=\"true\" inkscape:guide-bbox=\"true\" inkscape:zoom=\"0.35355339\" inkscape:cx=\"72.756921\" inkscape:cy=\"792.67347\" inkscape:window-x=\"0\" inkscape:window-y=\"0\" inkscape:window-maximized=\"1\" inkscape:current-layer=\"svg14\"> <sodipodi:guide position=\"430.25363,862.49682\" orientation=\"1,0\" id=\"guide832\" inkscape:locked=\"false\" /> <sodipodi:guide position=\"-42.427977,430.25368\" orientation=\"0,1\" id=\"guide834\" inkscape:locked=\"false\" /> </sodipodi:namedview> <metadata id=\"metadata2\"> Created by potrace 1.15, written by Peter Selinger 2001-2017 <rdf:RDF> <cc:Work rdf:about=\"\"> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" /> <dc:title></dc:title> </cc:Work> </rdf:RDF> </metadata> <path style=\"fill:#000000;fill-opacity:0.39163501;stroke:none;stroke-width:2.83464575;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1\" d=\"M 735.79979,124.70799 C 654.79116,43.598883 544.88842,-2.0206645 430.25389,-2.121103 315.61937,-2.0206592 205.71663,43.598888 124.70801,124.70799 l 305.54588,305.54589 z\" id=\"path836\" inkscape:connector-curvature=\"0\" sodipodi:nodetypes=\"ccccc\" /> </svg> "
public static direction_outline_img = Img.AsImageElement(Svg.direction_outline)
public static direction_outline_svg() { return new Img(Svg.direction_outline, true);}
public static direction_outline_ui() { return new FixedUiElement(Svg.direction_outline_img);}
public static direction_stroke = " <svg xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" version=\"1.0\" width=\"860.50732pt\" height=\"860.50732pt\" viewBox=\"0 0 860.50732 860.50732\" preserveAspectRatio=\"xMidYMid meet\" id=\"svg14\" sodipodi:docname=\"direction_stroke.svg\" inkscape:version=\"0.92.5 (2060ec1f9f, 2020-04-08)\"> <defs id=\"defs18\" /> <sodipodi:namedview pagecolor=\"#ffffff\" bordercolor=\"#666666\" borderopacity=\"1\" objecttolerance=\"10\" gridtolerance=\"10\" guidetolerance=\"10\" inkscape:pageopacity=\"0\" inkscape:pageshadow=\"2\" inkscape:window-width=\"1920\" inkscape:window-height=\"999\" id=\"namedview16\" showgrid=\"false\" showguides=\"true\" inkscape:guide-bbox=\"true\" inkscape:zoom=\"1\" inkscape:cx=\"482.69344\" inkscape:cy=\"931.96415\" inkscape:window-x=\"0\" inkscape:window-y=\"0\" inkscape:window-maximized=\"1\" inkscape:current-layer=\"svg14\"> <sodipodi:guide position=\"430.25363,862.49682\" orientation=\"1,0\" id=\"guide832\" inkscape:locked=\"false\" /> <sodipodi:guide position=\"-42.427977,430.25368\" orientation=\"0,1\" id=\"guide834\" inkscape:locked=\"false\" /> </sodipodi:namedview> <metadata id=\"metadata2\"> Created by potrace 1.15, written by Peter Selinger 2001-2017 <rdf:RDF> <cc:Work rdf:about=\"\"> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" /> <dc:title /> </cc:Work> </rdf:RDF> </metadata> <path style=\"fill: none !important;fill-opacity:0.39163501;stroke:#000000;stroke-width:3.74999976;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1\" d=\"M 735.79953,129.20772 C 654.7909,48.098612 544.88816,2.4790648 430.25363,2.3786263 315.61911,2.4790701 205.71637,48.098617 124.70775,129.20772 l 305.54588,305.54589 z\" id=\"path836\" inkscape:connector-curvature=\"0\" sodipodi:nodetypes=\"ccccc\" /> </svg> "
public static direction_stroke_img = Img.AsImageElement(Svg.direction_stroke)
public static direction_stroke_svg() { return new Img(Svg.direction_stroke, true);}
public static direction_stroke_ui() { return new FixedUiElement(Svg.direction_stroke_img);}
public static down = " <svg xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" version=\"1.0\" width=\"700\" height=\"700\" id=\"svg6\" sodipodi:docname=\"down.svg\" inkscape:version=\"0.92.4 (5da689c313, 2019-01-14)\"> <metadata id=\"metadata12\"> <rdf:RDF> <cc:Work rdf:about=\"\"> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" /> </cc:Work> </rdf:RDF> </metadata> <defs id=\"defs10\" /> <sodipodi:namedview pagecolor=\"#ffffff\" bordercolor=\"#666666\" borderopacity=\"1\" objecttolerance=\"10\" gridtolerance=\"10\" guidetolerance=\"10\" inkscape:pageopacity=\"0\" inkscape:pageshadow=\"2\" inkscape:window-width=\"1920\" inkscape:window-height=\"1001\" id=\"namedview8\" showgrid=\"false\" inkscape:zoom=\"0.33714286\" inkscape:cx=\"477.91309\" inkscape:cy=\"350\" inkscape:window-x=\"0\" inkscape:window-y=\"0\" inkscape:window-maximized=\"1\" inkscape:current-layer=\"svg6\" /> <g transform=\"rotate(-180,342.1439,335.17672)\" id=\"g4\"> <path d=\"m -20,670.71582 c 0,-1.85843 349.99229,-699.98853 350.57213,-699.28671 1.94549,2.35478 350.06752,699.46087 349.427,699.71927 -0.41837,0.16878 -79.29725,-33.69092 -175.2864,-75.24377 l -174.52574,-75.55065 -174.2421,75.53732 c -95.83317,41.54551 -174.625237,75.5373 -175.093498,75.5373 -0.46826,0 -0.851382,-0.32075 -0.851382,-0.71276 z\" style=\"fill:#00ff00;stroke:none\" id=\"path2\" inkscape:connector-curvature=\"0\" /> </g> </svg> "
public static down_img = Img.AsImageElement(Svg.down)
public static down_svg() { return new Img(Svg.down, true);}
@ -319,4 +334,4 @@ export default class Svg {
public static wikipedia_svg() { return new Img(Svg.wikipedia, true);}
public static wikipedia_ui() { return new FixedUiElement(Svg.wikipedia_img);}
public static All = {"SocialImageForeground.svg": Svg.SocialImageForeground,"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"back.svg": Svg.back,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"circle.svg": Svg.circle,"clock.svg": Svg.clock,"close.svg": Svg.close,"compass.svg": Svg.compass,"cross_bottom_right.svg": Svg.cross_bottom_right,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapcomplete_logo.svg": Svg.mapcomplete_logo,"mapillary.svg": Svg.mapillary,"mapillary_black.svg": Svg.mapillary_black,"min.svg": Svg.min,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-copyright.svg": Svg.osm_copyright,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pin.svg": Svg.pin,"plus.svg": Svg.plus,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"ring.svg": Svg.ring,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"square.svg": Svg.square,"star.svg": Svg.star,"star_half.svg": Svg.star_half,"star_outline.svg": Svg.star_outline,"star_outline_half.svg": Svg.star_outline_half,"statistics.svg": Svg.statistics,"translate.svg": Svg.translate,"up.svg": Svg.up,"wikidata.svg": Svg.wikidata,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};}
public static All = {"SocialImageForeground.svg": Svg.SocialImageForeground,"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"back.svg": Svg.back,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"circle.svg": Svg.circle,"clock.svg": Svg.clock,"close.svg": Svg.close,"compass.svg": Svg.compass,"cross_bottom_right.svg": Svg.cross_bottom_right,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"direction_masked.svg": Svg.direction_masked,"direction_outline.svg": Svg.direction_outline,"direction_stroke.svg": Svg.direction_stroke,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapcomplete_logo.svg": Svg.mapcomplete_logo,"mapillary.svg": Svg.mapillary,"mapillary_black.svg": Svg.mapillary_black,"min.svg": Svg.min,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-copyright.svg": Svg.osm_copyright,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"phone.svg": Svg.phone,"pin.svg": Svg.pin,"plus.svg": Svg.plus,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"ring.svg": Svg.ring,"search.svg": Svg.search,"send_email.svg": Svg.send_email,"share.svg": Svg.share,"square.svg": Svg.square,"star.svg": Svg.star,"star_half.svg": Svg.star_half,"star_outline.svg": Svg.star_outline,"star_outline_half.svg": Svg.star_outline_half,"statistics.svg": Svg.statistics,"translate.svg": Svg.translate,"up.svg": Svg.up,"wikidata.svg": Svg.wikidata,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};}

144
UI/Base/Minimap.ts Normal file
View file

@ -0,0 +1,144 @@
import BaseUIElement from "../BaseUIElement";
import * as L from "leaflet";
import {UIEventSource} from "../../Logic/UIEventSource";
import Loc from "../../Models/Loc";
import BaseLayer from "../../Models/BaseLayer";
import AvailableBaseLayers from "../../Logic/Actors/AvailableBaseLayers";
import {Map} from "leaflet";
export default class Minimap extends BaseUIElement {
private static _nextId = 0;
public readonly leafletMap: UIEventSource<Map> = new UIEventSource<Map>(undefined)
private readonly _id: string;
private readonly _background: UIEventSource<BaseLayer>;
private readonly _location: UIEventSource<Loc>;
private _isInited = false;
private _allowMoving: boolean;
constructor(options?: {
background?: UIEventSource<BaseLayer>,
location?: UIEventSource<Loc>,
allowMoving?: boolean
}
) {
super()
options = options ?? {}
this._background = options?.background ?? new UIEventSource<BaseLayer>(AvailableBaseLayers.osmCarto)
this._location = options?.location ?? new UIEventSource<Loc>(undefined)
this._id = "minimap" + Minimap._nextId;
this._allowMoving = options.allowMoving ?? true;
Minimap._nextId++
}
protected InnerConstructElement(): HTMLElement {
const div = document.createElement("div")
div.id = this._id;
div.style.height = "100%"
div.style.width = "100%"
div.style.minWidth = "40px"
div.style.minHeight = "40px"
const wrapper = document.createElement("div")
wrapper.appendChild(div)
const self = this;
// @ts-ignore
const resizeObserver = new ResizeObserver(_ => {
console.log("Change in size detected!")
self.InitMap();
self.leafletMap?.data?.invalidateSize()
});
resizeObserver.observe(div);
return wrapper;
}
private InitMap() {
if (this._constructedHtmlElement === undefined) {
// This element isn't initialized yet
return;
}
if (document.getElementById(this._id) === null) {
// not yet attached, we probably got some other event
return;
}
if (this._isInited) {
return;
}
this._isInited = true;
const location = this._location;
let currentLayer = this._background.data.layer()
const map = L.map(this._id, {
center: [location.data?.lat ?? 0, location.data?.lon ?? 0],
zoom: location.data?.zoom ?? 2,
layers: [currentLayer],
zoomControl: false,
attributionControl: false,
dragging: this._allowMoving,
scrollWheelZoom: this._allowMoving,
doubleClickZoom: this._allowMoving,
keyboard: this._allowMoving,
touchZoom: this._allowMoving
});
map.setMaxBounds(
[[-100, -200], [100, 200]]
);
this._background.addCallbackAndRun(layer => {
const newLayer = layer.layer()
if (currentLayer !== undefined) {
map.removeLayer(currentLayer);
}
currentLayer = newLayer;
map.addLayer(newLayer);
})
let isRecursing = false;
map.on("moveend", function () {
if (isRecursing) {
return
}
if (map.getZoom() === location.data.zoom &&
map.getCenter().lat === location.data.lat &&
map.getCenter().lng === location.data.lon) {
return;
}
console.trace(map.getZoom(), map.getCenter(), location.data)
location.data.zoom = map.getZoom();
location.data.lat = map.getCenter().lat;
location.data.lon = map.getCenter().lng;
isRecursing = true;
location.ping();
isRecursing = false; // This is ugly, I know
})
location.addCallback(loc => {
const mapLoc = map.getCenter()
const dlat = Math.abs(loc.lat - mapLoc[0])
const dlon = Math.abs(loc.lon - mapLoc[1])
if (dlat < 0.000001 && dlon < 0.000001 && map.getZoom() === loc.zoom) {
return;
}
map.setView([loc.lat, loc.lon], loc.zoom)
})
location.map(loc => loc.zoom)
.addCallback(zoom => {
if (Math.abs(map.getZoom() - zoom) > 0.1) {
map.setZoom(zoom, {});
}
})
this.leafletMap.setData(map)
}
}

View file

@ -8,16 +8,12 @@ import {UIEventSource} from "../Logic/UIEventSource";
*/
export default abstract class BaseUIElement {
protected _constructedHtmlElement: HTMLElement;
private clss: Set<string> = new Set<string>();
private style: string;
private _onClick: () => void;
private _onHover: UIEventSource<boolean>;
protected _constructedHtmlElement: HTMLElement;
protected abstract InnerConstructElement(): HTMLElement;
public onClick(f: (() => void)) {
this._onClick = f;
this.SetClass("clickable")
@ -44,6 +40,7 @@ export default abstract class BaseUIElement {
return this;
}
/**
* Adds all the relevant classes, space seperated
*/
@ -151,7 +148,8 @@ try{
el.addEventListener('mouseout', () => self._onHover.setData(false));
}
return el}catch(e){
return el
} catch (e) {
const domExc = e as DOMException;
if (domExc) {
console.log("An exception occured", domExc.code, domExc.message, domExc.name)
@ -163,4 +161,6 @@ try{
public AsMarkdown(): string {
throw "AsMarkdown is not implemented by " + this.constructor.name
}
protected abstract InnerConstructElement(): HTMLElement;
}

View file

@ -14,10 +14,14 @@ export class Basemap {
currentLayer: UIEventSource<BaseLayer>,
lastClickLocation?: UIEventSource<{ lat: number, lon: number }>,
extraAttribution?: BaseUIElement) {
console.log("Currentlayer is" ,currentLayer, currentLayer.data, currentLayer.data?.id)
let previousLayer = currentLayer.data.layer();
this.map = L.map(leafletElementId, {
center: [location.data.lat ?? 0, location.data.lon ?? 0],
zoom: location.data.zoom ?? 2,
layers: [currentLayer.data.layer],
layers: [previousLayer],
zoomControl: false,
attributionControl: extraAttribution !== undefined
});
@ -42,16 +46,16 @@ export class Basemap {
extraAttribution.AttachTo('leaflet-attribution')
const self = this;
let previousLayer = currentLayer.data;
currentLayer.addCallbackAndRun(layer => {
if (layer === previousLayer) {
const newLayer = layer.layer()
if (newLayer === previousLayer) {
return;
}
if (previousLayer !== undefined) {
self.map.removeLayer(previousLayer.layer);
self.map.removeLayer(previousLayer);
}
previousLayer = layer;
self.map.addLayer(layer.layer);
previousLayer = newLayer;
self.map.addLayer(newLayer);
})

View file

@ -29,7 +29,8 @@ export default class MoreScreen extends Combine {
LanguagePicker.CreateLanguagePicker(Translations.t.index.title.SupportedLanguages())
.SetClass("absolute top-2 right-3"),
new IndexText()
])
]);
themeButtonStyle = "h-32 min-h-32 max-h-32 overflow-ellipsis overflow-hidden"
themeListStyle = "md:grid md:grid-flow-row md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-g4 gap-4"
}
@ -59,10 +60,23 @@ export default class MoreScreen extends Combine {
private static createOfficialThemesList(state: State, buttonClass: string): BaseUIElement {
let officialThemes = AllKnownLayouts.layoutsList
if (State.state.osmConnection.userDetails.data.csCount < Constants.userJourney.personalLayoutUnlock) {
officialThemes = officialThemes.filter(theme => theme.id !== personal.id)
let buttons = officialThemes.map((layout) => {
const button = MoreScreen.createLinkButton(layout)?.SetClass(buttonClass);
if(layout.id === personal.id){
return new VariableUiElement(
State.state.osmConnection.userDetails.map(userdetails => userdetails.csCount)
.map(csCount => {
if(csCount < Constants.userJourney.personalLayoutUnlock){
return undefined
}else{
return button
}
let buttons = officialThemes.map((layout) => MoreScreen.createLinkButton(layout)?.SetClass(buttonClass))
})
)
}
return button;
})
let customGeneratorLink = MoreScreen.createCustomGeneratorButton(state)
buttons.splice(0, 0, customGeneratorLink);

View file

@ -1,7 +1,7 @@
import Combine from "../Base/Combine";
import Attribution from "./Attribution";
import Img from "../Base/Img";
import ImageAttributionSource from "../../Logic/Web/ImageAttributionSource";
import ImageAttributionSource from "../../Logic/ImageProviders/ImageAttributionSource";
export class AttributedImage extends Combine {

View file

@ -3,7 +3,7 @@ import Translations from "../i18n/Translations";
import BaseUIElement from "../BaseUIElement";
import {VariableUiElement} from "../Base/VariableUIElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import {LicenseInfo} from "../../Logic/Web/Wikimedia";
import {LicenseInfo} from "../../Logic/ImageProviders/Wikimedia";
export default class Attribution extends VariableUiElement {

View file

@ -6,10 +6,9 @@ import {AttributedImage} from "./AttributedImage";
import BaseUIElement from "../BaseUIElement";
import Img from "../Base/Img";
import Toggle from "../Input/Toggle";
import ImageAttributionSource from "../../Logic/Web/ImageAttributionSource";
import {Wikimedia} from "../../Logic/Web/Wikimedia";
import {Mapillary} from "../../Logic/Web/Mapillary";
import {Imgur} from "../../Logic/Web/Imgur";
import {Wikimedia} from "../../Logic/ImageProviders/Wikimedia";
import {Imgur} from "../../Logic/ImageProviders/Imgur";
import {Mapillary} from "../../Logic/ImageProviders/Mapillary";
export class ImageCarousel extends Toggle {

View file

@ -8,7 +8,7 @@ import BaseUIElement from "../BaseUIElement";
import LicensePicker from "../BigComponents/LicensePicker";
import Toggle from "../Input/Toggle";
import FileSelectorButton from "../Input/FileSelectorButton";
import ImgurUploader from "../../Logic/Web/ImgurUploader";
import ImgurUploader from "../../Logic/ImageProviders/ImgurUploader";
import UploadFlowStateUI from "../BigComponents/UploadFlowStateUI";
import LayerConfig from "../../Customizations/JSON/LayerConfig";

View file

@ -2,21 +2,30 @@ import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import Combine from "../Base/Combine";
import Svg from "../../Svg";
import BaseUIElement from "../BaseUIElement";
import {FixedUiElement} from "../Base/FixedUiElement";
import {Utils} from "../../Utils";
import Loc from "../../Models/Loc";
/**
* Selects a direction in degrees
*/
export default class DirectionInput extends InputElement<string> {
public static constructMinimap: ((any) => BaseUIElement);
private readonly _location: UIEventSource<Loc>;
public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
private readonly value: UIEventSource<string>;
private background;
constructor(value?: UIEventSource<string>) {
constructor(mapBackground: UIEventSource<any>,
location: UIEventSource<Loc>,
value?: UIEventSource<string>) {
super();
this._location = location;
this.value = value ?? new UIEventSource<string>(undefined);
this.background = mapBackground;
}
GetValue(): UIEventSource<string> {
@ -30,16 +39,23 @@ export default class DirectionInput extends InputElement<string> {
protected InnerConstructElement(): HTMLElement {
let map: BaseUIElement = new FixedUiElement("")
if (!Utils.runningFromConsole) {
map = DirectionInput.constructMinimap({
background: this.background,
allowMoving: false,
location: this._location
})
}
const element = new Combine([
new FixedUiElement("").SetClass("w-full h-full absolute top-0 left-O rounded-full"),
Svg.direction_svg().SetStyle(
Svg.direction_stroke_svg().SetStyle(
`position: absolute;top: 0;left: 0;width: 100%;height: 100%;transform:rotate(${this.value.data ?? 0}deg);`)
.SetClass("direction-svg"),
Svg.compass_svg().SetStyle(
"position: absolute;top: 0;left: 0;width: 100%;height: 100%;")
.SetClass("direction-svg relative")
.SetStyle("z-index: 1000"),
map.SetClass("w-full h-full absolute top-0 left-O rounded-full overflow-hidden"),
])
.SetStyle("position:relative;display:block;width: min(100%, 25em); padding-top: min(100% , 25em); background:white; border: 1px solid black; border-radius: 999em")
.SetStyle("position:relative;display:block;width: min(100%, 25em); height: min(100% , 25em); background:white; border: 1px solid black; border-radius: 999em")
.ConstructElement()

View file

@ -1,7 +1,6 @@
import {DropDown} from "./DropDown";
import * as EmailValidator from "email-validator";
import {parsePhoneNumberFromString} from "libphonenumber-js";
import InputElementMap from "./InputElementMap";
import {InputElement} from "./InputElement";
import {TextField} from "./TextField";
import {UIElement} from "../UIElement";
@ -12,6 +11,7 @@ import OpeningHoursInput from "../OpeningHours/OpeningHoursInput";
import DirectionInput from "./DirectionInput";
import ColorPicker from "./ColorPicker";
import {Utils} from "../../Utils";
import Loc from "../../Models/Loc";
interface TextFieldDef {
name: string,
@ -19,7 +19,8 @@ interface TextFieldDef {
isValid: ((s: string, country?: () => string) => boolean),
reformat?: ((s: string, country?: () => string) => string),
inputHelper?: (value: UIEventSource<string>, options?: {
location: [number, number]
location: [number, number],
mapBackgroundLayer?: UIEventSource<any>
}) => InputElement<string>,
inputmode?: string
@ -118,8 +119,12 @@ export default class ValidatedTextField {
str = "" + str;
return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) >= 0 && Number(str) <= 360
}, str => str,
(value) => {
return new DirectionInput(value);
(value, options) => {
return new DirectionInput(options.mapBackgroundLayer , new UIEventSource<Loc>({
lat: options.location[0],
lon: options.location[1],
zoom: 19
}),value);
},
"numeric"
),
@ -216,16 +221,6 @@ export default class ValidatedTextField {
* {string (typename) --> TextFieldDef}
*/
public static AllTypes = ValidatedTextField.allTypesDict();
public static TypeDropdown(): DropDown<string> {
const values: { value: string, shown: string }[] = [];
const expl = ValidatedTextField.tpList;
for (const key in expl) {
values.push({value: expl[key].name, shown: `${expl[key].name} - ${expl[key].explanation}`})
}
return new DropDown<string>("", values)
}
public static InputForType(type: string, options?: {
placeholder?: string | UIElement,
value?: UIEventSource<string>,
@ -235,7 +230,8 @@ export default class ValidatedTextField {
textAreaRows?: number,
isValid?: ((s: string, country: () => string) => boolean),
country?: () => string,
location?: [number /*lat*/, number /*lon*/]
location?: [number /*lat*/, number /*lon*/],
mapBackgroundLayer?: UIEventSource<any>
}): InputElement<string> {
options = options ?? {};
options.placeholder = options.placeholder ?? type;
@ -269,90 +265,16 @@ export default class ValidatedTextField {
if (tp.inputHelper) {
input = new CombinedInputElement(input, tp.inputHelper(input.GetValue(), {
location: options.location
location: options.location,
mapBackgroundLayer: options.mapBackgroundLayer
}),
(a, b) => a, // We can ignore b, as they are linked earlier
(a, _) => a, // We can ignore b, as they are linked earlier
a => [a, a]
);
}
return input;
}
public static NumberInput(type: string = "int", extraValidation: (number: Number) => boolean = undefined): InputElement<number> {
const isValid = ValidatedTextField.AllTypes[type].isValid;
extraValidation = extraValidation ?? (() => true)
const fromString = str => {
if (!isValid(str)) {
return undefined;
}
const n = Number(str);
if (!extraValidation(n)) {
return undefined;
}
return n;
};
const toString = num => {
if (num === undefined) {
return undefined;
}
return "" + num;
};
const textField = ValidatedTextField.InputForType(type);
return new InputElementMap(textField, (n0, n1) => n0 === n1, fromString, toString)
}
public static KeyInput(allowEmpty: boolean = false): InputElement<string> {
function fromString(str) {
if (str?.match(/^[a-zA-Z][a-zA-Z0-9:_-]*$/)) {
return str;
}
if (str === "" && allowEmpty) {
return "";
}
return undefined
}
const toString = str => str
function isSame(str0, str1) {
return str0 === str1;
}
const textfield = new TextField({
placeholder: "key",
isValid: str => fromString(str) !== undefined,
value: new UIEventSource<string>("")
});
return new InputElementMap(textfield, isSame, fromString, toString);
}
static Mapped<T>(fromString: (str) => T, toString: (T) => string, options?: {
placeholder?: string | UIElement,
type?: string,
value?: UIEventSource<string>,
startValidated?: boolean,
textArea?: boolean,
textAreaRows?: number,
isValid?: ((string: string) => boolean),
country?: () => string
}): InputElement<T> {
let textField: InputElement<string>;
if (options?.type) {
textField = ValidatedTextField.InputForType(options.type, options);
} else {
textField = new TextField(options);
}
return new InputElementMap(
textField, (a, b) => a === b,
fromString, toString
);
}
public static HelpText(): string {
const explanations = ValidatedTextField.tpList.map(type => ["## " + type.name, "", type.explanation].join("\n")).join("\n\n")
return "# Available types for text fields\n\nThe listed types here trigger a special input element. Use them in `tagrendering.freeform.type` of your tagrendering to activate them\n\n" + explanations
@ -363,7 +285,8 @@ export default class ValidatedTextField {
isValid?: ((s: string, country?: () => string) => boolean),
reformat?: ((s: string, country?: () => string) => string),
inputHelper?: (value: UIEventSource<string>, options?: {
location: [number, number]
location: [number, number],
mapBackgroundLayer: UIEventSource<any>
}) => InputElement<string>,
inputmode?: string): TextFieldDef {

View file

@ -331,7 +331,8 @@ export default class TagRenderingQuestion extends UIElement {
let input: InputElement<string> = ValidatedTextField.InputForType(this._configuration.freeform.type, {
isValid: (str) => (str.length <= 255),
country: () => this._tags.data._country,
location: [this._tags.data._lat, this._tags.data._lon]
location: [this._tags.data._lat, this._tags.data._lon],
mapBackgroundLayer: State.state.backgroundLayer
});
if (this._applicableUnit) {

View file

@ -15,13 +15,18 @@ export default class ShowDataLayer {
private _layerDict;
private readonly _leafletMap: UIEventSource<L.Map>;
private _cleanCount = 0;
private readonly _enablePopups: boolean;
private readonly _features : UIEventSource<{ feature: any, freshness: Date }[]>
constructor(features: UIEventSource<{ feature: any, freshness: Date }[]>,
leafletMap: UIEventSource<L.Map>,
layoutToUse: UIEventSource<LayoutConfig>) {
layoutToUse: UIEventSource<LayoutConfig>,
enablePopups= true,
zoomToFeatures = false) {
this._leafletMap = leafletMap;
this._enablePopups = enablePopups;
this._features = features;
const self = this;
const mp = leafletMap.data;
self._layerDict = {};
layoutToUse.addCallbackAndRun(layoutToUse => {
@ -39,7 +44,9 @@ export default class ShowDataLayer {
if (features.data === undefined) {
return;
}
if (leafletMap.data === undefined) {
const mp = leafletMap.data;
if(mp === undefined){
return;
}
@ -68,6 +75,11 @@ export default class ShowDataLayer {
mp.addLayer(geoLayer)
}
if(zoomToFeatures){
mp.fitBounds(geoLayer.getBounds())
}
State.state.selectedElement.ping();
}
@ -77,6 +89,7 @@ export default class ShowDataLayer {
}
private createStyleFor(feature) {
const tagsSource = State.state.allElements.addOrGetElement(feature);
// Every object is tied to exactly one layer
@ -97,9 +110,13 @@ export default class ShowDataLayer {
}
const style = layer.GenerateLeafletStyle(tagSource, !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0));
const baseElement = style.icon.html;
if(!this._enablePopups){
baseElement.SetStyle("cursor: initial !important")
}
return L.marker(latLng, {
icon: L.divIcon({
html: style.icon.html.ConstructElement(),
html: baseElement.ConstructElement(),
className: style.icon.className,
iconAnchor: style.icon.iconAnchor,
iconUrl: style.icon.iconUrl,
@ -115,10 +132,12 @@ export default class ShowDataLayer {
console.warn("No layer found for object (probably a now disabled layer)", feature, this._layerDict)
return;
}
if (layer.title === undefined) {
if (layer.title === undefined || !this._enablePopups) {
// No popup action defined -> Don't do anything
// or probably a map in the popup - no popups needed!
return;
}
const popup = L.popup({
autoPan: true,
closeOnEscapeKey: true,

View file

@ -21,6 +21,10 @@ import LayerConfig from "../Customizations/JSON/LayerConfig";
import Title from "./Base/Title";
import Table from "./Base/Table";
import Histogram from "./BigComponents/Histogram";
import Loc from "../Models/Loc";
import ShowDataLayer from "./ShowDataLayer";
import Minimap from "./Base/Minimap";
import {Utils} from "../Utils";
export default class SpecialVisualizations {
@ -32,7 +36,6 @@ export default class SpecialVisualizations {
example?: string,
args: { name: string, defaultValue?: string, doc: string }[]
}[] =
[
{
funcName: "all_tags",
@ -86,7 +89,80 @@ export default class SpecialVisualizations {
return new ImageUploadFlow(tags, args[0])
}
},
{
funcName: "minimap",
docs: "A small map showing the selected feature. Note that no styling is applied, wrap this in a div",
args: [
{
doc: "The zoomlevel: the higher, the more zoomed in with 1 being the entire world and 19 being really close",
name: "zoomlevel",
defaultValue: "18"
},
{
doc: "(Matches all resting arguments) This argument should be the key of a property of the feature. The corresponding value is interpreted as either the id or the a list of ID's. The features with these ID's will be shown on this minimap.",
name: "idKey",
defaultValue: "id"
}
],
example: "`{minimap()}`, `{minimap(17, id, _list_of_embedded_feature_ids_calculated_by_calculated_tag):height:10rem; border: 2px solid black}`",
constr: (state, tagSource, args) => {
const keys = [...args]
keys.splice(0, 1)
const featureStore = state.allElements.ContainingFeatures
const featuresToShow: UIEventSource<{ freshness: Date, feature: any }[]> = tagSource.map(properties => {
const values: string[] = Utils.NoNull(keys.map(key => properties[key]))
const features: { freshness: Date, feature: any }[] = []
for (const value of values) {
let idList = [value]
if (value.startsWith("[")) {
// This is a list of values
idList = JSON.parse(value)
}
for (const id of idList) {
features.push({
freshness: new Date(),
feature: featureStore.get(id)
})
}
}
return features
})
const properties = tagSource.data;
let zoom = 18
if (args[0]) {
const parsed = Number(args[0])
if (!isNaN(parsed) && parsed > 0 && parsed < 25) {
zoom = parsed;
}
}
const minimap = new Minimap(
{
background: state.backgroundLayer,
location: new UIEventSource<Loc>({
lat: Number(properties._lat),
lon: Number(properties._lon),
zoom: zoom
}),
allowMoving: false
}
)
new ShowDataLayer(
featuresToShow,
minimap.leafletMap,
State.state.layoutToUse,
false,
true
)
minimap.SetStyle("overflow: hidden; pointer-events: none;")
return minimap;
}
},
{
funcName: "reviews",
docs: "Adds an overview of the mangrove-reviews of this object. Mangrove.Reviews needs - in order to identify the reviewed object - a coordinate and a name. By default, the name of the object is given, but this can be overwritten",
@ -169,7 +245,7 @@ export default class SpecialVisualizations {
defaultValue: ""
},
{
name: "colors",
name: "colors*",
doc: "(Matches all resting arguments - optional) Matches a regex onto a color value, e.g. `3[a-zA-Z+-]*:#33cc33`"
}
@ -260,7 +336,8 @@ export default class SpecialVisualizations {
}
},
{funcName: "canonical",
{
funcName: "canonical",
docs: "Converts a short, canonical value into the long, translated text",
example: "{canonical(length)} will give 42 metre (in french)",
args: [{
@ -283,10 +360,9 @@ export default class SpecialVisualizations {
},
[state.layoutToUse])
)
}}
}
}
]
static HelpMessage: BaseUIElement = SpecialVisualizations.GenHelpMessage();
@ -313,7 +389,7 @@ export default class SpecialVisualizations {
return new Combine([
new Title("Special tag renderings", 3),
"In a tagrendering, some special values are substituted by an advanced UI-element. This allows advanced features and visualizations to be reused by custom themes or even to query third-party API's.",
"General usage is <b>{func_name()}</b> or <b>{func_name(arg, someotherarg)}</b>. Note that you <i>do not</i> need to use quotes around your arguments, the comma is enough to seperate them. This also implies you cannot use a comma in your args",
"General usage is <b>{func_name()}</b>, <b>{func_name(arg, someotherarg)}</b> or <b>{func_name(args):cssStyle}</b>. Note that you <i>do not</i> need to use quotes around your arguments, the comma is enough to seperate them. This also implies you cannot use a comma in your args",
...helpTexts
]
);

View file

@ -34,13 +34,14 @@ export class SubstitutedTranslation extends VariableUiElement {
for (const knownSpecial of SpecialVisualizations.specialVisualizations) {
// Note: the '.*?' in the regex reads as 'any character, but in a non-greedy way'
const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)}(.*)`);
const matched = template.match(`(.*){${knownSpecial.funcName}\\((.*?)\\)(:.*)?}(.*)`);
if (matched != null) {
// We found a special component that should be brought to live
const partBefore = SubstitutedTranslation.EvaluateSpecialComponents(matched[1], tags);
const argument = matched[2].trim();
const partAfter = SubstitutedTranslation.EvaluateSpecialComponents(matched[3], tags);
const style = matched[3]?.substring(1) ?? ""
const partAfter = SubstitutedTranslation.EvaluateSpecialComponents(matched[4], tags);
try {
const args = knownSpecial.args.map(arg => arg.defaultValue ?? "");
if (argument.length > 0) {
@ -58,6 +59,7 @@ export class SubstitutedTranslation extends VariableUiElement {
let element: BaseUIElement = new FixedUiElement(`Constructing ${knownSpecial}(${args.join(", ")})`)
try {
element = knownSpecial.constr(State.state, tags, args);
element.SetStyle(style)
} catch (e) {
console.error("SPECIALRENDERING FAILED for", tags.data.id, e)
element = new FixedUiElement(`Could not generate special rendering for ${knownSpecial}(${args.join(", ")}) ${e}`).SetClass("alert")

View file

@ -1,5 +1,4 @@
import * as colors from "./assets/colors.json"
import {Util} from "leaflet";
export class Utils {
@ -325,6 +324,34 @@ export class Utils {
return result;
}
public static externalDownloadFunction: (url: string) => Promise<any>;
public static downloadJson(url: string): Promise<any>{
if(this.externalDownloadFunction !== undefined){
return this.externalDownloadFunction(url)
}
return new Promise(
(resolve, reject) => {
try{
const xhr = new XMLHttpRequest();
xhr.onload = () => {
if (xhr.status == 200) {
resolve(JSON.parse(xhr.response))
} else {
reject(xhr.statusText)
}
};
xhr.open('GET', url);
xhr.send();
}catch(e){
reject(e)
}
}
)
}
/**
* Triggers a 'download file' popup which will download the contents
* @param contents

View file

@ -1 +1 @@
{"contributors":[{"contributor":"Pieter Vander Vennet", "commits":738},{"contributor":"pietervdvn", "commits":718},{"contributor":"Weblate", "commits":35},{"contributor":"Tobias", "commits":35},{"contributor":"Christian Neumann", "commits":33},{"contributor":"Win Olario", "commits":31},{"contributor":"Pieter Fiers", "commits":31},{"contributor":"Sebastian Kürten", "commits":16},{"contributor":"Marco", "commits":16},{"contributor":"Joost", "commits":16},{"contributor":"ToastHawaii", "commits":15},{"contributor":"J. Lavoie", "commits":14},{"contributor":"Bavo Vanderghote", "commits":12},{"contributor":"Artem", "commits":12},{"contributor":"Supaplex", "commits":9},{"contributor":"Jacque Fresco", "commits":9},{"contributor":"Midgard", "commits":8},{"contributor":"Mateusz Konieczny", "commits":8},{"contributor":"yopaseopor", "commits":7},{"contributor":"Flo Edelmann", "commits":7},{"contributor":"Binnette", "commits":7},{"contributor":"Allan Nordhøy", "commits":7},{"contributor":"pelderson", "commits":6},{"contributor":"lvgx", "commits":6},{"contributor":"dependabot[bot]", "commits":6},{"contributor":"Alexey Shabanov", "commits":6},{"contributor":"SiegbjornSitumeang", "commits":4},{"contributor":"Polgár Sándor", "commits":4},{"contributor":"Hiroshi Miura", "commits":4},{"contributor":"vankos", "commits":3},{"contributor":"Léo Villeveygoux", "commits":3},{"contributor":"JCGF-OSM", "commits":3},{"contributor":"Jan Zabel", "commits":3},{"contributor":"Hosted Weblate", "commits":3},{"contributor":"David Haberthür", "commits":3},{"contributor":"快乐的老鼠宝宝", "commits":2},{"contributor":"Wiktor Przybylski", "commits":2},{"contributor":"Vinicius", "commits":2},{"contributor":"Stanislas Gueniffey", "commits":2},{"contributor":"Robin van der Linde", "commits":2},{"contributor":"riiga", "commits":2},{"contributor":"pbarban", "commits":2},{"contributor":"mic140", "commits":2},{"contributor":"Leo Alcaraz", "commits":2},{"contributor":"Jose Luis Infante", "commits":2},{"contributor":"Heiko", "commits":2},{"contributor":"graveelius", "commits":2},{"contributor":"Tomas Fiers", "commits":1},{"contributor":"Thibault Molleman", "commits":1},{"contributor":"tbowdecl97", "commits":1},{"contributor":"Sebastian", "commits":1},{"contributor":"Sean Young", "commits":1},{"contributor":"Schouppe Joost", "commits":1},{"contributor":"Noémie", "commits":1},{"contributor":"mozita", "commits":1},{"contributor":"Michał Targoński", "commits":1},{"contributor":"Iváns", "commits":1},{"contributor":"Eric Armijo", "commits":1},{"contributor":"Damian Pułka", "commits":1},{"contributor":"Carlos Ramos Carreño", "commits":1},{"contributor":"Beardhatcode", "commits":1}]}
{"contributors":[{"contributor":"pietervdvn", "commits":794},{"contributor":"Pieter Vander Vennet", "commits":744},{"contributor":"Weblate", "commits":38},{"contributor":"Tobias", "commits":35},{"contributor":"Christian Neumann", "commits":33},{"contributor":"Win Olario", "commits":31},{"contributor":"Pieter Fiers", "commits":31},{"contributor":"Sebastian Kürten", "commits":17},{"contributor":"Joost", "commits":17},{"contributor":"Marco", "commits":16},{"contributor":"Artem", "commits":16},{"contributor":"Allan Nordhøy", "commits":16},{"contributor":"ToastHawaii", "commits":15},{"contributor":"Supaplex", "commits":14},{"contributor":"J. Lavoie", "commits":14},{"contributor":"Bavo Vanderghote", "commits":12},{"contributor":"Jacque Fresco", "commits":9},{"contributor":"Midgard", "commits":8},{"contributor":"Mateusz Konieczny", "commits":8},{"contributor":"yopaseopor", "commits":7},{"contributor":"Hosted Weblate", "commits":7},{"contributor":"Flo Edelmann", "commits":7},{"contributor":"Binnette", "commits":7},{"contributor":"pelderson", "commits":6},{"contributor":"lvgx", "commits":6},{"contributor":"dependabot[bot]", "commits":6},{"contributor":"Alexey Shabanov", "commits":6},{"contributor":"SiegbjornSitumeang", "commits":4},{"contributor":"Polgár Sándor", "commits":4},{"contributor":"Hiroshi Miura", "commits":4},{"contributor":"Wiktor Przybylski", "commits":3},{"contributor":"vankos", "commits":3},{"contributor":"Léo Villeveygoux", "commits":3},{"contributor":"JCGF-OSM", "commits":3},{"contributor":"Jan Zabel", "commits":3},{"contributor":"Erik Palm", "commits":3},{"contributor":"David Haberthür", "commits":3},{"contributor":"快乐的老鼠宝宝", "commits":2},{"contributor":"Vinicius", "commits":2},{"contributor":"Stanislas Gueniffey", "commits":2},{"contributor":"Robin van der Linde", "commits":2},{"contributor":"riiga", "commits":2},{"contributor":"pbarban", "commits":2},{"contributor":"mic140", "commits":2},{"contributor":"Leo Alcaraz", "commits":2},{"contributor":"Jose Luis Infante", "commits":2},{"contributor":"Heiko", "commits":2},{"contributor":"graveelius", "commits":2},{"contributor":"Damian Tokarski", "commits":2},{"contributor":"Tomas Fiers", "commits":1},{"contributor":"Thibault Molleman", "commits":1},{"contributor":"tbowdecl97", "commits":1},{"contributor":"Sebastian", "commits":1},{"contributor":"Sean Young", "commits":1},{"contributor":"Schouppe Joost", "commits":1},{"contributor":"Noémie", "commits":1},{"contributor":"mozita", "commits":1},{"contributor":"Michał Targoński", "commits":1},{"contributor":"liimee", "commits":1},{"contributor":"Jeff Huang", "commits":1},{"contributor":"Iváns", "commits":1},{"contributor":"Eric Armijo", "commits":1},{"contributor":"Damian Pułka", "commits":1},{"contributor":"Carlos Ramos Carreño", "commits":1},{"contributor":"Beardhatcode", "commits":1}]}

View file

@ -72,6 +72,9 @@
],
"tagRenderings": [
"images",
{
"render": "{minimap():height: 9rem; border-radius: 2.5rem; overflow:hidden;border:1px solid gray}"
},
{
"render": {
"en": "The name of this bookcase is {name}",

View file

@ -6,48 +6,97 @@
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
id="svg8"
version="1.1"
viewBox="0 0 100 100"
height="100"
width="100">
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0"
width="860.50732pt"
height="860.50732pt"
viewBox="0 0 860.50732 860.50732"
preserveAspectRatio="xMidYMid meet"
id="svg14"
sodipodi:docname="direction_gradient.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
<defs
id="defs18">
<linearGradient
inkscape:collect="always"
id="linearGradient832">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop828" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop830" />
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient832"
id="radialGradient838"
cx="430.25363"
cy="519.61188"
fx="430.25363"
fy="519.61188"
r="305.54589"
gradientTransform="matrix(0.95288409,-0.94890664,0.94542304,0.94938587,-470.98122,345.21193)"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="999"
id="namedview16"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="0.70710678"
inkscape:cx="279.00239"
inkscape:cy="856.75313"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg14">
<sodipodi:guide
position="430.25363,862.49682"
orientation="1,0"
id="guide832"
inkscape:locked="false" />
<sodipodi:guide
position="-42.427977,430.25368"
orientation="0,1"
id="guide834"
inkscape:locked="false" />
<sodipodi:guide
position="398.27788,720.18823"
orientation="0,1"
id="guide840"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata8">
id="metadata2">
Created by potrace 1.15, written by Peter Selinger 2001-2017
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6">
<linearGradient
id="linearGradient820">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="innercolor" />
<stop
style="stop-color:#000000;stop-opacity:0"
offset="1"
id="outercolor" />
</linearGradient>
<radialGradient
xlink:href="#linearGradient820"
id="radialGradient828"
cx="49.787739"
cy="53.828533"
fx="49.787739"
fy="53.828533"
r="28.883806"
gradientTransform="matrix(1.5439431,-0.01852438,0.02075364,1.7297431,-27.986574,-42.187244)"
gradientUnits="userSpaceOnUse" />
</defs>
<path
id="path821"
d="M 50,50 21.042889,9.3993342 C 36.191421,-2.001434 60.726552,-3.6768009 78.8105,9.1490935 Z"
style="fill:url(#radialGradient828);fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
style="fill:url(#radialGradient838);fill-opacity:1;stroke:none;stroke-width:2.83464575;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 735.79979,124.70799 C 654.79116,43.598883 544.88842,-2.0206645 430.25389,-2.121103 315.61937,-2.0206592 205.71663,43.598888 124.70801,124.70799 l 305.54588,305.54589 z"
id="path836"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Before After
Before After

View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0"
width="860.50732pt"
height="860.50732pt"
viewBox="0 0 860.50732 860.50732"
preserveAspectRatio="xMidYMid meet"
id="svg14"
sodipodi:docname="direction_masked.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
<defs
id="defs18" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="999"
id="namedview16"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="0.70710678"
inkscape:cx="418.71218"
inkscape:cy="725.43016"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg14">
<sodipodi:guide
position="430.25363,862.49682"
orientation="1,0"
id="guide832"
inkscape:locked="false" />
<sodipodi:guide
position="-42.427977,430.25368"
orientation="0,1"
id="guide834"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata2">
Created by potrace 1.15, written by Peter Selinger 2001-2017
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="fill:#000000;fill-opacity:0.39163497;stroke:none;stroke-width:2.83464575;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 124.70776,124.7084 A 432.375,432.375 0 0 0 -2.121338,430.25429 432.375,432.375 0 0 0 430.25366,862.62929 432.375,432.375 0 0 0 862.62866,430.25429 432.375,432.375 0 0 0 735.79811,124.70986 L 430.25366,430.25429 Z"
id="path836"
inkscape:connector-curvature="0" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0"
width="860.50732pt"
height="860.50732pt"
viewBox="0 0 860.50732 860.50732"
preserveAspectRatio="xMidYMid meet"
id="svg14"
sodipodi:docname="direction_outline.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
<defs
id="defs18" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="999"
id="namedview16"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="0.35355339"
inkscape:cx="72.756921"
inkscape:cy="792.67347"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg14">
<sodipodi:guide
position="430.25363,862.49682"
orientation="1,0"
id="guide832"
inkscape:locked="false" />
<sodipodi:guide
position="-42.427977,430.25368"
orientation="0,1"
id="guide834"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata2">
Created by potrace 1.15, written by Peter Selinger 2001-2017
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="fill:#000000;fill-opacity:0.39163501;stroke:none;stroke-width:2.83464575;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 735.79979,124.70799 C 654.79116,43.598883 544.88842,-2.0206645 430.25389,-2.121103 315.61937,-2.0206592 205.71663,43.598888 124.70801,124.70799 l 305.54588,305.54589 z"
id="path836"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.0"
width="860.50732pt"
height="860.50732pt"
viewBox="0 0 860.50732 860.50732"
preserveAspectRatio="xMidYMid meet"
id="svg14"
sodipodi:docname="direction_stroke.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
<defs
id="defs18" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="999"
id="namedview16"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="1"
inkscape:cx="482.69344"
inkscape:cy="931.96415"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg14">
<sodipodi:guide
position="430.25363,862.49682"
orientation="1,0"
id="guide832"
inkscape:locked="false" />
<sodipodi:guide
position="-42.427977,430.25368"
orientation="0,1"
id="guide834"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata2">
Created by potrace 1.15, written by Peter Selinger 2001-2017
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="fill:none;fill-opacity:0.39163501;stroke:#000000;stroke-width:3.74999976;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 735.79953,129.20772 C 654.7909,48.098612 544.88816,2.4790648 430.25363,2.3786263 315.61911,2.4790701 205.71637,48.098617 124.70775,129.20772 l 305.54588,305.54589 z"
id="path836"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -5,6 +5,9 @@
"reviews": {
"render": "{reviews()}"
},
"minimap": {
"render": "{minimap(19, id): width:100%; height:6rem; border-radius:999rem; overflow: hidden; pointer-events: none;}"
},
"phone": {
"question": {
"en": "What is the phone number of {name}?",

View file

@ -15,7 +15,8 @@
"ru": "Найти места остановки, чтобы провести ночь в автофургоне",
"ja": "キャンパーと夜を共にするキャンプサイトを見つける",
"fr": "Trouver des sites pour passer la nuit avec votre camping-car",
"zh_Hant": "露營者尋找渡過夜晚的場地"
"zh_Hant": "露營者尋找渡過夜晚的場地",
"nl": "Vind locaties waar je de nacht kan doorbrengen met je mobilehome"
},
"description": {
"en": "This site collects all official camper stopover places and places where you can dump grey and black water. You can add details about the services provided and the cost. Add pictures and reviews. This is a website and a webapp. The data is stored in OpenStreetMap, so it will be free forever and can be re-used by any app.",

View file

@ -781,7 +781,8 @@
"en": "Climbing is not possible here",
"de": "Hier kann nicht geklettert werden",
"ja": "ここでは登ることができない",
"nb_NO": "Klatring er ikke mulig her"
"nb_NO": "Klatring er ikke mulig her",
"nl": "Klimmen is hier niet mogelijk"
},
"hideInAnswer": true
},
@ -795,7 +796,8 @@
"en": "Climbing is possible here",
"de": "Hier kann geklettert werden",
"ja": "ここでは登ることができる",
"nb_NO": "Klatring er mulig her"
"nb_NO": "Klatring er mulig her",
"nl": "Klimmen is hier niet toegelaten"
}
},
{
@ -804,7 +806,8 @@
"en": "Climbing is not possible here",
"de": "Hier kann nicht geklettert werden",
"ja": "ここでは登ることができない",
"nb_NO": "Klatring er ikke mulig her"
"nb_NO": "Klatring er ikke mulig her",
"nl": "Klimmen is hier niet toegelaten"
}
}
]
@ -848,7 +851,8 @@
"question": {
"en": "Is there a (unofficial) website with more informations (e.g. topos)?",
"de": "Gibt es eine (inoffizielle) Website mit mehr Informationen (z.B. Topos)?",
"ja": "もっと情報のある(非公式の)ウェブサイトはありますか(例えば、topos)?"
"ja": "もっと情報のある(非公式の)ウェブサイトはありますか(例えば、topos)?",
"nl": "Is er een (onofficiële) website met meer informatie (b.v. met topos)?"
},
"condition": {
"and": [
@ -870,13 +874,15 @@
{
"if": "_embedding_feature:access=yes",
"then": {
"en": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that this is</span> publicly accessible<br/>{_embedding_feature:access:description}"
"en": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that this is</span> publicly accessible<br/>{_embedding_feature:access:description}",
"nl": "<span class='subtle'>Een <a href='#{_embedding_feature:id}'>omvattend element</a> geeft aan dat dit <span>publiek toegangkelijk is</span><br/>{_embedding_feature:access:description}"
}
},
{
"if": "_embedding_feature:access=permit",
"then": {
"en": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that </span> a permit is needed to access<br/>{_embedding_feature:access:description}"
"en": "<span class='subtle'>The <a href='#{_embedding_feature:id}'>containing feature</a> states that </span> a permit is needed to access<br/>{_embedding_feature:access:description}",
"nl": "<span class='subtle'>Een <a href='#{_embedding_feature:id}'>omvattend element</a> geeft aan dat</span> een toelating nodig is om hier te klimmen<br/>{_embedding_feature:access:description}"
}
},
{

View file

@ -14,10 +14,12 @@ import Translations from "./UI/i18n/Translations";
import CountryCoder from "latlon2country"
import SimpleMetaTagger from "./Logic/SimpleMetaTagger";
import Minimap from "./UI/Base/Minimap";
import DirectionInput from "./UI/Input/DirectionInput";
// Workaround for a stupid crash: inject the function
// Workaround for a stupid crash: inject some functions which would give stupid circular dependencies or crash the other nodejs scripts
SimpleMetaTagger.coder = new CountryCoder("https://pietervdvn.github.io/latlon2country/");
DirectionInput.constructMinimap = options => new Minimap(options)
let defaultLayout = ""
// --------------------- Special actions based on the parameters -----------------

View file

@ -72,6 +72,12 @@
"emailOf": "What is the email address of {category}?",
"emailIs": "The email address of this {category} is <a href='mailto:{email}' target='_blank'>{email}</a>"
},
"morescreen": {
"intro": "<h3>More thematic maps?</h3>Do you enjoy collecting geodata? <br/>There are more themes available.",
"requestATheme": "If you want a custom-built quest, request it in the issue tracker",
"streetcomplete": "Another, similar application is <a href='https://play.google.com/store/apps/details?id=de.westnordost.streetcomplete' class='underline hover:text-blue-800' class='underline hover:text-blue-800' target='_blank'>StreetComplete</a>.",
"createYourOwnTheme": "Create your own MapComplete theme from scratch"
},
"sharescreen": {
"intro": "<h3>Share this map</h3> Share this map by copying the link below and sending it to friends and family:",
"addToHomeScreen": "<h3>Add to your home screen</h3>You can easily add this website to your smartphone home screen for a native feel. Click the 'add to home screen button' in the URL bar to do this.",

View file

@ -1072,7 +1072,7 @@
}
},
"tagRenderings": {
"1": {
"2": {
"render": "Der Name dieses Bücherschrank lautet {name}",
"question": "Wie heißt dieser öffentliche Bücherschrank?",
"mappings": {
@ -1081,11 +1081,11 @@
}
}
},
"2": {
"3": {
"render": "{capacity} Bücher passen in diesen Bücherschrank",
"question": "Wie viele Bücher passen in diesen öffentlichen Bücherschrank?"
},
"3": {
"4": {
"question": "Welche Art von Büchern sind in diesem öffentlichen Bücherschrank zu finden?",
"mappings": {
"0": {
@ -1099,7 +1099,7 @@
}
}
},
"4": {
"5": {
"question": "Befindet sich dieser Bücherschrank im Freien?",
"mappings": {
"0": {
@ -1113,7 +1113,7 @@
}
}
},
"5": {
"6": {
"question": "Ist dieser öffentliche Bücherschrank frei zugänglich?",
"mappings": {
"0": {
@ -1124,11 +1124,11 @@
}
}
},
"6": {
"7": {
"question": "Wer unterhält diesen öffentlichen Bücherschrank?",
"render": "Betrieben von {operator}"
},
"7": {
"8": {
"question": "Ist dieser öffentliche Bücherschrank Teil eines größeren Netzwerks?",
"render": "Dieser Bücherschrank ist Teil von {brand}",
"mappings": {
@ -1140,7 +1140,7 @@
}
}
},
"8": {
"9": {
"render": "Die Referenznummer dieses öffentlichen Bücherschranks innerhalb {brand} lautet {ref}",
"question": "Wie lautet die Referenznummer dieses öffentlichen Bücherschranks?",
"mappings": {
@ -1149,11 +1149,11 @@
}
}
},
"9": {
"10": {
"question": "Wann wurde dieser öffentliche Bücherschrank installiert?",
"render": "Installiert am {start_date}"
},
"10": {
"11": {
"render": "Weitere Informationen auf <a href='{website}' target='_blank'>der Webseite</a>",
"question": "Gibt es eine Website mit weiteren Informationen über diesen öffentlichen Bücherschrank?"
}

View file

@ -1183,7 +1183,7 @@
}
},
"tagRenderings": {
"1": {
"2": {
"render": "The name of this bookcase is {name}",
"question": "What is the name of this public bookcase?",
"mappings": {
@ -1192,11 +1192,11 @@
}
}
},
"2": {
"3": {
"render": "{capacity} books fit in this bookcase",
"question": "How many books fit into this public bookcase?"
},
"3": {
"4": {
"question": "What kind of books can be found in this public bookcase?",
"mappings": {
"0": {
@ -1210,7 +1210,7 @@
}
}
},
"4": {
"5": {
"question": "Is this bookcase located outdoors?",
"mappings": {
"0": {
@ -1224,7 +1224,7 @@
}
}
},
"5": {
"6": {
"question": "Is this public bookcase freely accessible?",
"mappings": {
"0": {
@ -1235,11 +1235,11 @@
}
}
},
"6": {
"7": {
"question": "Who maintains this public bookcase?",
"render": "Operated by {operator}"
},
"7": {
"8": {
"question": "Is this public bookcase part of a bigger network?",
"render": "This public bookcase is part of {brand}",
"mappings": {
@ -1251,7 +1251,7 @@
}
}
},
"8": {
"9": {
"render": "The reference number of this public bookcase within {brand} is {ref}",
"question": "What is the reference number of this public bookcase?",
"mappings": {
@ -1260,11 +1260,11 @@
}
}
},
"9": {
"10": {
"question": "When was this public bookcase installed?",
"render": "Installed on {start_date}"
},
"10": {
"11": {
"render": "More info on <a href='{website}' target='_blank'>the website</a>",
"question": "Is there a website with more information about this public bookcase?"
}

View file

@ -1073,7 +1073,7 @@
}
},
"tagRenderings": {
"1": {
"2": {
"render": "Le nom de cette microbibliothèque est {name}",
"question": "Quel est le nom de cette microbibliothèque ?",
"mappings": {
@ -1082,11 +1082,11 @@
}
}
},
"2": {
"3": {
"render": "{capacity} livres peuvent entrer dans cette microbibliothèque",
"question": "Combien de livres peuvent entrer dans cette microbibliothèque ?"
},
"3": {
"4": {
"question": "Quel type de livres peut-on dans cette microbibliothèque ?",
"mappings": {
"0": {
@ -1100,7 +1100,7 @@
}
}
},
"4": {
"5": {
"question": "Cette microbiliothèque est-elle en extérieur ?",
"mappings": {
"0": {
@ -1114,7 +1114,7 @@
}
}
},
"5": {
"6": {
"question": "Cette microbibliothèque est-elle librement accèssible ?",
"mappings": {
"0": {
@ -1125,11 +1125,11 @@
}
}
},
"6": {
"7": {
"question": "Qui entretien cette microbibliothèque ?",
"render": "Entretenue par {operator}"
},
"7": {
"8": {
"question": "Cette microbibliothèque fait-elle partie d'un réseau/groupe ?",
"render": "Cette microbibliothèque fait partie du groupe {brand}",
"mappings": {
@ -1141,7 +1141,7 @@
}
}
},
"8": {
"9": {
"render": "Cette microbibliothèque du réseau {brand} possède le numéro {ref}",
"question": "Quelle est le numéro de référence de cette microbibliothèque ?",
"mappings": {
@ -1150,11 +1150,11 @@
}
}
},
"9": {
"10": {
"question": "Quand a été installée cette microbibliothèque ?",
"render": "Installée le {start_date}"
},
"10": {
"11": {
"render": "Plus d'infos sur <a href='{website}' target='_blank'>le site web</a>",
"question": "Y a-t-il un site web avec plus d'informations sur cette microbibliothèque ?"
}

View file

@ -1361,7 +1361,7 @@
}
},
"tagRenderings": {
"1": {
"2": {
"render": "De naam van dit boekenruilkastje is {name}",
"question": "Wat is de naam van dit boekenuilkastje?",
"mappings": {
@ -1370,11 +1370,11 @@
}
}
},
"2": {
"3": {
"render": "Er passen {capacity} boeken",
"question": "Hoeveel boeken passen er in dit boekenruilkastje?"
},
"3": {
"4": {
"question": "Voor welke doelgroep zijn de meeste boeken in dit boekenruilkastje?",
"mappings": {
"0": {
@ -1388,7 +1388,7 @@
}
}
},
"4": {
"5": {
"question": "Staat dit boekenruilkastje binnen of buiten?",
"mappings": {
"0": {
@ -1402,7 +1402,7 @@
}
}
},
"5": {
"6": {
"question": "Is dit boekenruilkastje publiek toegankelijk?",
"mappings": {
"0": {
@ -1413,11 +1413,11 @@
}
}
},
"6": {
"7": {
"question": "Wie is verantwoordelijk voor dit boekenruilkastje?",
"render": "Onderhouden door {operator}"
},
"7": {
"8": {
"question": "Is dit boekenruilkastje deel van een netwerk?",
"render": "Dit boekenruilkastje is deel van het netwerk {brand}",
"mappings": {
@ -1429,7 +1429,7 @@
}
}
},
"8": {
"9": {
"render": "Het referentienummer binnen {brand} is {ref}",
"question": "Wat is het referentienummer van dit boekenruilkastje?",
"mappings": {
@ -1438,11 +1438,11 @@
}
}
},
"9": {
"10": {
"question": "Op welke dag werd dit boekenruilkastje geinstalleerd?",
"render": "Geplaatst op {start_date}"
},
"10": {
"11": {
"render": "Meer info op <a href='{website}' target='_blank'>de website</a>",
"question": "Is er een website over dit boekenruilkastje?"
}

View file

@ -578,7 +578,7 @@
}
},
"tagRenderings": {
"1": {
"2": {
"render": "Название книжного шкафа — {name}",
"question": "Как называется общественный книжный шкаф?",
"mappings": {
@ -587,10 +587,10 @@
}
}
},
"2": {
"3": {
"question": "Сколько книг помещается в этом общественном книжном шкафу?"
},
"3": {
"4": {
"mappings": {
"0": {
"then": "В основном детские книги"
@ -600,7 +600,7 @@
}
}
},
"10": {
"11": {
"render": "Более подробная информация <a href='{website}' target='_blank'>на сайте</a>"
}
}

View file

@ -239,7 +239,8 @@
}
},
"campersite": {
"title": "Kampeersite"
"title": "Kampeersite",
"shortDescription": "Vind locaties waar je de nacht kan doorbrengen met je mobilehome"
},
"climbing": {
"title": "Open Klimkaart",
@ -368,10 +369,38 @@
"title": {
"render": "Klimgelegenheid?"
},
"description": "Een klimgelegenheid?"
"description": "Een klimgelegenheid?",
"tagRenderings": {
"1": {
"mappings": {
"0": {
"then": "Klimmen is hier niet mogelijk"
},
"1": {
"then": "Klimmen is hier niet toegelaten"
},
"2": {
"then": "Klimmen is hier niet toegelaten"
}
}
}
}
}
},
"roamingRenderings": {
"0": {
"question": "Is er een (onofficiële) website met meer informatie (b.v. met topos)?"
},
"1": {
"mappings": {
"0": {
"then": "<span class='subtle'>Een <a href='#{_embedding_feature:id}'>omvattend element</a> geeft aan dat dit <span>publiek toegangkelijk is</span><br/>{_embedding_feature:access:description}"
},
"1": {
"then": "<span class='subtle'>Een <a href='#{_embedding_feature:id}'>omvattend element</a> geeft aan dat</span> een toelating nodig is om hier te klimmen<br/>{_embedding_feature:access:description}"
}
}
},
"4": {
"render": "De klimroutes zijn gemiddeld <b>{climbing:length}m</b> lang",
"question": "Wat is de (gemiddelde) lengte van de klimroutes, in meter?"

View file

@ -30,7 +30,7 @@
"reset:layeroverview": "echo {\\\"layers\\\":[], \\\"themes\\\":[]} > ./assets/generated/known_layers_and_themes.json",
"generate": "mkdir -p ./assets/generated && npm run reset:layeroverview && npm run generate:images && npm run generate:translations && npm run generate:licenses && npm run generate:licenses && npm run generate:layeroverview",
"build": "rm -rf dist/ && npm run generate && parcel build --public-url ./ *.html assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*",
"prepare-deploy": "npm run generate:contributor-list && npm run generate && npm run test && npm run generate:editor-layer-index && npm run generate:layeroverview && npm run generate:layouts && npm run build && rm -rf .cache && npm run generate:docs",
"prepare-deploy": "npm run generate && npm run test && npm run generate:editor-layer-index && npm run generate:layeroverview && npm run generate:layouts && npm run build && rm -rf .cache && npm run generate:docs",
"deploy:staging": "npm run prepare-deploy && rm -rf ~/git/pietervdvn.github.io/Staging/* && cp -r dist/* ~/git/pietervdvn.github.io/Staging/ && cd ~/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean",
"deploy:pietervdvn": "cd ~/git/pietervdvn.github.io/ && git pull && cd - && npm run prepare-deploy && rm -rf ~/git/pietervdvn.github.io/MapComplete/* && cp -r dist/* ~/git/pietervdvn.github.io/MapComplete/ && cd ~/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean",
"deploy:production": "cd ~/git/mapcomplete.github.io/ && git pull && cd - && rm -rf ./assets/generated && npm run prepare-deploy && npm run optimize-images && rm -rf ~/git/mapcomplete.github.io/* && cp -r dist/* ~/git/mapcomplete.github.io/ && cd ~/git/mapcomplete.github.io/ && echo \"mapcomplete.osm.be\" > CNAME && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean && npm run gittag",

View file

@ -3,7 +3,6 @@ import Combine from "./UI/Base/Combine";
import {Button} from "./UI/Base/Button";
import {TextField} from "./UI/Input/TextField";
import {FixedUiElement} from "./UI/Base/FixedUiElement";
import {UIElement} from "./UI/UIElement";
import {UIEventSource} from "./Logic/UIEventSource";
import {Utils} from "./Utils";
import {SubtleButton} from "./UI/Base/SubtleButton";

View file

@ -1,9 +1,20 @@
import {lstatSync, readdirSync, readFileSync} from "fs";
import {Utils} from "../Utils";
Utils.runningFromConsole = true
import * as https from "https";
import {LayerConfigJson} from "../Customizations/JSON/LayerConfigJson";
import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson";
import * as fs from "fs";
export default class ScriptUtils {
public static fixUtils() {
Utils.externalDownloadFunction = ScriptUtils.DownloadJSON
}
public static readDirRecSync(path, maxDepth = 999): string[] {
const result = []
if (maxDepth <= 0) {
@ -23,6 +34,20 @@ export default class ScriptUtils {
return result;
}
public static DownloadFileTo(url, targetFilePath: string): void {
console.log("Downloading ", url, "to", targetFilePath)
https.get(url, (res) => {
const filePath = fs.createWriteStream(targetFilePath);
res.pipe(filePath);
filePath.on('finish', () => {
filePath.close();
console.log('Download Completed');
})
})
}
public static DownloadJSON(url): Promise<any> {
return new Promise((resolve, reject) => {
try {

View file

@ -1,15 +1,19 @@
/*
* This script attempt to automatically fix some basic issues when a theme from the custom generator is loaded
*/
import {Utils} from "../Utils"
Utils.runningFromConsole = true;
import {readFileSync, writeFileSync} from "fs";
import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson";
import {Layer} from "leaflet";
import LayerConfig from "../Customizations/JSON/LayerConfig";
import SmallLicense from "../Models/smallLicense";
import AllKnownLayers from "../Customizations/AllKnownLayers";
import ScriptUtils from "./ScriptUtils";
import AllImageProviders from "../Logic/ImageProviders/AllImageProviders";
ScriptUtils.fixUtils()
if(process.argv.length == 2){
console.log("USAGE: ts-node scripts/fixTheme <path to theme>")
@ -23,7 +27,6 @@ console.log("Fixing up ", path)
const themeConfigJson : LayoutConfigJson = JSON.parse(readFileSync(path, "UTF8"))
const linuxHints = []
const licenses : SmallLicense[] = []
const replacements: {source: string, destination: string}[] = []
@ -41,15 +44,32 @@ for (const layerConfigJson of themeConfigJson.layers) {
const layerConfig = new LayerConfig(layerConfigJson, AllKnownLayers.sharedUnits, "fix theme",true)
const images : string[] = Array.from(layerConfig.ExtractImages())
const remoteImages = images.filter(img => img.startsWith("http"))
for (const remoteImage of remoteImages) {
linuxHints.push("wget " + remoteImage)
const filename = remoteImage.substring(remoteImage.lastIndexOf("/"))
ScriptUtils.DownloadFileTo(remoteImage, dir + "/" + filename)
const imgPath = remoteImage.substring(remoteImage.lastIndexOf("/") + 1)
for (const attributionSrc of AllImageProviders.ImageAttributionSource) {
try {
attributionSrc.GetAttributionFor(remoteImage).addCallbackAndRun(license => {
console.log("Downloaded an attribution!")
licenses.push({
path: imgPath,
license: "",
authors: [],
license: license?.license ?? "",
authors:Utils.NoNull([license?.artist]),
sources: [remoteImage]
})
})
}catch(e){
// Hush hush
}
}
replacements.push({source: remoteImage, destination: `${dir}/${imgPath}`})
}
}
@ -59,13 +79,9 @@ for (const replacement of replacements) {
fixedThemeJson = fixedThemeJson.replace(new RegExp(replacement.source, "g"), replacement.destination)
}
const fixScriptPath = dir + "/fix_script_"+path.replace(/\//g,"_")+".sh"
writeFileSync(dir + "/generated.license_info.json", JSON.stringify(licenses, null, " "))
writeFileSync(fixScriptPath, linuxHints.join("\n"))
writeFileSync(path+".autofixed.json", fixedThemeJson)
console.log(`IMPORTANT:
1) run ${fixScriptPath}
2) Copy generated.license_info.json over into license_info.json and add the missing attributions and authors
3) Verify ${path}.autofixed.json as theme, and rename it to ${path}
4) Delete the fix script and other unneeded files`)
1) Copy generated.license_info.json over into license_info.json and add the missing attributions and authors
2) Verify ${path}.autofixed.json as theme, and rename it to ${path}`)

View file

@ -1,8 +1,5 @@
import ScriptUtils from "./ScriptUtils";
import {Utils} from "../Utils";
import {readFileSync, writeFileSync} from "fs";
Utils.runningFromConsole = true
import {writeFileSync} from "fs";
import LayerConfig from "../Customizations/JSON/LayerConfig";
import * as licenses from "../assets/generated/license_info.json"
import LayoutConfig from "../Customizations/JSON/LayoutConfig";
@ -10,6 +7,7 @@ import {LayerConfigJson} from "../Customizations/JSON/LayerConfigJson";
import {Translation} from "../UI/i18n/Translation";
import {LayoutConfigJson} from "../Customizations/JSON/LayoutConfigJson";
import AllKnownLayers from "../Customizations/AllKnownLayers";
// This scripts scans 'assets/layers/*.json' for layer definition files and 'assets/themes/*.json' for theme definition files.
// It spits out an overview of those to be used to load them

View file

@ -3,6 +3,7 @@
<head>
<title>Small tests</title>
<link href="index.css" rel="stylesheet"/>
<link rel="stylesheet" href="./vendor/leaflet.css"/>
<link href="css/tabbedComponent.css" rel="stylesheet"/>
<link href="css/openinghourstable.css" rel="stylesheet"/>
<link href="css/tagrendering.css" rel="stylesheet"/>

85
test.ts
View file

@ -9,9 +9,13 @@ import {SlideShow} from "./UI/Image/SlideShow";
import {FixedUiElement} from "./UI/Base/FixedUiElement";
import Img from "./UI/Base/Img";
import {AttributedImage} from "./UI/Image/AttributedImage";
import {Imgur} from "./Logic/Web/Imgur";
import ReviewForm from "./UI/Reviews/ReviewForm";
import {OsmConnection} from "./Logic/Osm/OsmConnection";
import {Imgur} from "./Logic/ImageProviders/Imgur";
import Minimap from "./UI/Base/Minimap";
import Loc from "./Models/Loc";
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
import ShowDataLayer from "./UI/ShowDataLayer";
import LayoutConfig from "./Customizations/JSON/LayoutConfig";
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
function TestSlideshow() {
@ -65,6 +69,75 @@ function TestAllInputMethods(){
})).AttachTo("maindiv")
}
new ReviewForm(() => {
return undefined;
}, new OsmConnection(true, new UIEventSource<string>(undefined), "test")).AttachTo("maindiv");
const location = new UIEventSource<Loc>({
lon: 4.84771728515625,
lat: 51.17920846421931,
zoom: 14
})
const map0 = new Minimap({
location: location,
allowMoving: true,
background: new AvailableBaseLayers(location).availableEditorLayers.map(layers => layers[2])
})
map0.SetStyle("width: 500px; height: 250px; overflow: hidden; border: 2px solid red")
.AttachTo("maindiv")
const layout = AllKnownLayouts.layoutsList[1]
State.state = new State(layout)
console.log("LAYOUT is", layout.id)
const feature = {
"type": "Feature",
_matching_layer_id: "bike_repair_station",
"properties": {
id: "node/-1",
"amenity": "bicycle_repair_station"
},
"geometry": {
"type": "Point",
"coordinates": [
4.84771728515625,
51.17920846421931
]
}
}
;
State.state.allElements.addOrGetElement(feature)
const featureSource = new UIEventSource([{
freshness: new Date(),
feature: feature
}])
new ShowDataLayer(
featureSource,
map0.leafletMap,
new UIEventSource<LayoutConfig>(layout)
)
const map1 = new Minimap({
location: location,
allowMoving: true,
background: new AvailableBaseLayers(location).availableEditorLayers.map(layers => layers[5])
},
)
map1.SetStyle("width: 500px; height: 250px; overflow: hidden; border : 2px solid black")
.AttachTo("extradiv")
new ShowDataLayer(
featureSource,
map1.leafletMap,
new UIEventSource<LayoutConfig>(layout)
)
featureSource.ping()
// */