Merge branch 'master' into feature/climbing-layer

This commit is contained in:
Christian Neumann 2020-11-18 09:28:58 +01:00
commit b68eed207e
77 changed files with 2556 additions and 794 deletions

View file

@ -16,10 +16,11 @@ import * as charging_stations from "../assets/themes/charging_stations/charging_
import * as widths from "../assets/themes/widths/width.json" import * as widths from "../assets/themes/widths/width.json"
import * as drinking_water from "../assets/themes/drinking_water/drinking_water.json" import * as drinking_water from "../assets/themes/drinking_water/drinking_water.json"
import * as climbing from "../assets/themes/climbing/climbing.json" import * as climbing from "../assets/themes/climbing/climbing.json"
import LayerConfig from "./JSON/LayerConfig"; import * as surveillance_cameras from "../assets/themes/surveillance_cameras/surveillance_cameras.json"
import SharedLayers from "./SharedLayers";
import * as personal from "../assets/themes/personalLayout/personalLayout.json" import * as personal from "../assets/themes/personalLayout/personalLayout.json"
import LayerConfig from "./JSON/LayerConfig";
import LayoutConfig from "./JSON/LayoutConfig"; import LayoutConfig from "./JSON/LayoutConfig";
import SharedLayers from "./SharedLayers";
export class AllKnownLayouts { export class AllKnownLayouts {
@ -62,6 +63,7 @@ export class AllKnownLayouts {
new LayoutConfig(buurtnatuur), new LayoutConfig(buurtnatuur),
new LayoutConfig(bike_monitoring_stations), new LayoutConfig(bike_monitoring_stations),
new LayoutConfig(climbing), new LayoutConfig(climbing),
new LayoutConfig(surveillance_cameras)
]; ];

View file

@ -8,23 +8,33 @@ import {TagRenderingConfigJson} from "./TagRenderingConfigJson";
import {Translation} from "../../UI/i18n/Translation"; import {Translation} from "../../UI/i18n/Translation";
import {Img} from "../../UI/Img"; import {Img} from "../../UI/Img";
import Svg from "../../Svg"; import Svg from "../../Svg";
import {SubstitutedTranslation} from "../../UI/SpecialVisualizations";
import {Utils} from "../../Utils";
import Combine from "../../UI/Base/Combine";
import {VariableUiElement} from "../../UI/Base/VariableUIElement";
export default class LayerConfig { export default class LayerConfig {
id: string; id: string;
name: Translation name: Translation
description: Translation; description: Translation;
overpassTags: TagsFilter; overpassTags: TagsFilter;
doNotDownload: boolean;
passAllFeatures: boolean;
minzoom: number; minzoom: number;
title: TagRenderingConfig; title?: TagRenderingConfig;
titleIcons: TagRenderingConfig[]; titleIcons: TagRenderingConfig[];
icon: TagRenderingConfig; icon: TagRenderingConfig;
iconSize: TagRenderingConfig; iconSize: TagRenderingConfig;
rotation: TagRenderingConfig;
color: TagRenderingConfig; color: TagRenderingConfig;
width: TagRenderingConfig; width: TagRenderingConfig;
dashArray: TagRenderingConfig; dashArray: TagRenderingConfig;
@ -46,17 +56,19 @@ export default class LayerConfig {
tagRenderings: TagRenderingConfig []; tagRenderings: TagRenderingConfig [];
constructor(json: LayerConfigJson, context?: string) { constructor(json: LayerConfigJson, roamingRenderings: TagRenderingConfig[],
context?: string) {
context = context + "." + json.id; context = context + "." + json.id;
this.id = json.id; this.id = json.id;
this.name = Translations.T(json.name); this.name = Translations.T(json.name);
this.description = Translations.T(json.name); this.description = Translations.T(json.name);
this.overpassTags = FromJSON.Tag(json.overpassTags, context + ".overpasstags"); this.overpassTags = FromJSON.Tag(json.overpassTags, context + ".overpasstags");
this.doNotDownload = json.doNotDownload ?? false,
this.passAllFeatures = json.passAllFeatures ?? false;
this.minzoom = json.minzoom; this.minzoom = json.minzoom;
this.wayHandling = json.wayHandling ?? 0; this.wayHandling = json.wayHandling ?? 0;
this.hideUnderlayingFeaturesMinPercentage = json.hideUnderlayingFeaturesMinPercentage ?? 0; this.hideUnderlayingFeaturesMinPercentage = json.hideUnderlayingFeaturesMinPercentage ?? 0;
this.title = new TagRenderingConfig(json.title);
this.presets = (json.presets ?? []).map(pr => this.presets = (json.presets ?? []).map(pr =>
({ ({
title: Translations.T(pr.title), title: Translations.T(pr.title),
@ -87,13 +99,16 @@ export default class LayerConfig {
}); });
} }
this.tagRenderings = trs(json.tagRenderings); this.tagRenderings = trs(json.tagRenderings).concat(roamingRenderings);
this.titleIcons = trs(json.titleIcons ?? ["wikipedialink","osmlink"]); this.titleIcons = trs(json.titleIcons ?? ["wikipedialink","osmlink"]);
function tr(key, deflt) { function tr(key, deflt) {
const v = json[key]; const v = json[key];
if (v === undefined) { if (v === undefined || v === null) {
if (deflt === undefined) {
return undefined;
}
return new TagRenderingConfig(deflt); return new TagRenderingConfig(deflt);
} }
if (typeof v === "string") { if (typeof v === "string") {
@ -107,13 +122,120 @@ export default class LayerConfig {
} }
this.title = tr("title", ""); this.title = tr("title", undefined);
this.icon = tr("icon", Img.AsData(Svg.bug)); this.icon = tr("icon", Img.AsData(Svg.bug));
const iconPath = this.icon.GetRenderValue({id: "node/-1"}).txt;
if (iconPath.startsWith(Utils.assets_path)) {
const iconKey = iconPath.substr(Utils.assets_path.length);
if (Svg.All[iconKey] === undefined) {
throw "Builtin SVG asset not found: " + iconPath
}
}
this.iconSize = tr("iconSize", "40,40,center"); this.iconSize = tr("iconSize", "40,40,center");
this.color = tr("color", "#0000ff"); this.color = tr("color", "#0000ff");
this.width = tr("width", "7"); this.width = tr("width", "7");
this.rotation = tr("rotation", "0");
this.dashArray = tr("dashArray", ""); this.dashArray = tr("dashArray", "");
} }
public GenerateLeafletStyle(tags: any, clickable: boolean):
{
color: string;
icon: {
iconUrl: string,
popupAnchor: [number, number];
iconAnchor: [number, number];
iconSize: [number, number];
html: string;
rotation: string;
className?: string;
};
weight: number; dashArray: number[]
} {
function num(str, deflt = 40) {
const n = Number(str);
if (isNaN(n)) {
return deflt;
}
return n;
}
function rendernum(tr: TagRenderingConfig, deflt: number) {
const str = Number(render(tr, "" + deflt));
const n = Number(str);
if (isNaN(n)) {
return deflt;
}
return n;
}
function render(tr: TagRenderingConfig, deflt?: string) {
const str = (tr?.GetRenderValue(tags)?.txt ?? deflt);
return SubstitutedTranslation.SubstituteKeys(str, tags);
}
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");
if (color.startsWith("--")) {
color = getComputedStyle(document.body).getPropertyValue("--catch-detail-color")
}
const weight = rendernum(this.width, 5);
const rotation = render(this.rotation, "0deg");
const iconW = num(iconSize[0]);
const iconH = num(iconSize[1]);
const mode = iconSize[2] ?? "center"
let anchorW = iconW / 2;
let anchorH = iconH / 2;
if (mode === "left") {
anchorW = 0;
}
if (mode === "right") {
anchorW = iconW;
}
if (mode === "top") {
anchorH = 0;
}
if (mode === "bottom") {
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();
}
return {
icon:
{
html: html,
iconSize: [iconW, iconH],
iconAnchor: [anchorW, anchorH],
popupAnchor: [0, 3 - anchorH],
rotation: rotation,
iconUrl: iconUrl,
className: clickable ? "leaflet-div-icon" : "leaflet-div-icon unclickable"
},
color: color,
weight: weight,
dashArray: dashArray
};
}
} }

View file

@ -29,6 +29,12 @@ export interface LayerConfigJson {
*/ */
overpassTags: AndOrTagConfigJson | string; overpassTags: AndOrTagConfigJson | string;
/**
* If set, this layer will not query overpass; but it'll still match the tags above which are by chance returned by other layers.
* Works well together with 'passAllFeatures', to add decoration
*/
doNotDownload?: boolean;
/** /**
* The zoomlevel at which point the data is shown and loaded. * The zoomlevel at which point the data is shown and loaded.
*/ */
@ -39,8 +45,13 @@ export interface LayerConfigJson {
/** /**
* The title shown in a popup for elements of this layer. * The title shown in a popup for elements of this layer.
*/ */
title: string | TagRenderingConfigJson; title?: string | TagRenderingConfigJson;
/**
* Small icons shown next to the title.
* If not specified, the OsmLink and wikipedia links will be used by default.
* Use an empty array to hide them
*/
titleIcons?: (string | TagRenderingConfigJson)[]; titleIcons?: (string | TagRenderingConfigJson)[];
/** /**
@ -54,9 +65,15 @@ export interface LayerConfigJson {
* Default is '40,40,center' * Default is '40,40,center'
*/ */
iconSize?: string | TagRenderingConfigJson; iconSize?: string | TagRenderingConfigJson;
/**
* The rotation of an icon, useful for e.g. directions.
* Usage: as if it were a css property for 'rotate', thus has to end with 'deg', e.g. `90deg`, `{direction}deg`, `calc(90deg - {camera:direction}deg)``
*/
rotation?: string | TagRenderingConfigJson;
/** /**
* The color for way-elements * The color for way-elements and SVG-elements.
* If the value starts with "--", the style of the body element will be queried for the corresponding variable instead
*/ */
color?: string | TagRenderingConfigJson; color?: string | TagRenderingConfigJson;
/** /**
@ -87,6 +104,11 @@ export interface LayerConfigJson {
*/ */
hideUnderlayingFeaturesMinPercentage?:number; hideUnderlayingFeaturesMinPercentage?:number;
/**
* If set, this layer will pass all the features it receives onto the next layer
*/
passAllFeatures?:boolean
/** /**
* Presets for this layer * Presets for this layer
*/ */
@ -98,6 +120,7 @@ export interface LayerConfigJson {
/** /**
* All the tag renderings. * All the tag renderings.
* A tag rendering is a block that either shows the known value or asks a question.
*/ */
tagRenderings?: (string | TagRenderingConfigJson) [] tagRenderings?: (string | TagRenderingConfigJson) []
} }

View file

@ -80,7 +80,7 @@ export default class LayoutConfig {
} else { } else {
throw "Unkown fixed layer " + layer; throw "Unkown fixed layer " + layer;
} }
return new LayerConfig(layer, `${this.id}.layers[${i}]`); return new LayerConfig(layer, this.roamingRenderings,`${this.id}.layers[${i}]`);
}); });
this.hideFromOverview = json.hideFromOverview ?? false; this.hideFromOverview = json.hideFromOverview ?? false;

View file

@ -102,7 +102,19 @@ export interface LayoutConfigJson {
/** /**
* The layers to display * The layers to display.
*
* Every layer contains a description of which feature to display - the overpassTags which are queried.
* Instead of running one query for every layer, the query is fused.
*
* Afterwards, every layer is given the list of features.
* Every layer takes away the features that match with them*, and give the leftovers to the next layers.
*
* This implies that the _order_ of the layers is important in the case of features with the same tags;
* as the later layers might never receive their feature.
*
* *layers can also remove 'leftover'-features if the leftovers overlap with a feature in the layer itself
*
*/ */
layers: (LayerConfigJson | string)[], layers: (LayerConfigJson | string)[],

View file

@ -13,6 +13,8 @@ import * as bike_shops from "../assets/layers/bike_shop/bike_shop.json"
import * as bike_cleaning from "../assets/layers/bike_cleaning/bike_cleaning.json" import * as bike_cleaning from "../assets/layers/bike_cleaning/bike_cleaning.json"
import * as maps from "../assets/layers/maps/maps.json" import * as maps from "../assets/layers/maps/maps.json"
import * as information_boards from "../assets/layers/information_board/information_board.json" import * as information_boards from "../assets/layers/information_board/information_board.json"
import * as direction from "../assets/layers/direction/direction.json"
import * as surveillance_camera from "../assets/layers/surveillance_cameras/surveillance_cameras.json"
import LayerConfig from "./JSON/LayerConfig"; import LayerConfig from "./JSON/LayerConfig";
export default class SharedLayers { export default class SharedLayers {
@ -24,20 +26,22 @@ export default class SharedLayers {
private static getSharedLayers(){ private static getSharedLayers(){
const sharedLayersList = [ const sharedLayersList = [
new LayerConfig(drinkingWater, "shared_layers"), new LayerConfig(drinkingWater,[], "shared_layers"),
new LayerConfig(ghostbikes, "shared_layers"), new LayerConfig(ghostbikes,[], "shared_layers"),
new LayerConfig(viewpoint, "shared_layers"), new LayerConfig(viewpoint,[], "shared_layers"),
new LayerConfig(bike_parking, "shared_layers"), new LayerConfig(bike_parking,[], "shared_layers"),
new LayerConfig(bike_repair_station, "shared_layers"), new LayerConfig(bike_repair_station,[], "shared_layers"),
new LayerConfig(bike_monitoring_station, "shared_layers"), new LayerConfig(bike_monitoring_station,[], "shared_layers"),
new LayerConfig(birdhides, "shared_layers"), new LayerConfig(birdhides,[], "shared_layers"),
new LayerConfig(nature_reserve, "shared_layers"), new LayerConfig(nature_reserve,[], "shared_layers"),
new LayerConfig(bike_cafes, "shared_layers"), new LayerConfig(bike_cafes,[], "shared_layers"),
new LayerConfig(cycling_themed_objects, "shared_layers"), new LayerConfig(cycling_themed_objects,[], "shared_layers"),
new LayerConfig(bike_shops, "shared_layers"), new LayerConfig(bike_shops,[], "shared_layers"),
new LayerConfig(bike_cleaning, "shared_layers"), new LayerConfig(bike_cleaning,[], "shared_layers"),
new LayerConfig(maps, "shared_layers"), new LayerConfig(maps,[], "shared_layers"),
new LayerConfig(information_boards, "shared_layers") new LayerConfig(direction,[], "shared_layers"),
new LayerConfig(information_boards,[], "shared_layers"),
new LayerConfig(surveillance_camera,[], "shared_layers")
]; ];
const sharedLayers = new Map<string, LayerConfig>(); const sharedLayers = new Map<string, LayerConfig>();

View file

@ -35,6 +35,9 @@ import Svg from "./Svg";
import Link from "./UI/Base/Link"; import Link from "./UI/Base/Link";
import * as personal from "./assets/themes/personalLayout/personalLayout.json" import * as personal from "./assets/themes/personalLayout/personalLayout.json"
import LayoutConfig from "./Customizations/JSON/LayoutConfig"; import LayoutConfig from "./Customizations/JSON/LayoutConfig";
import * as L from "leaflet";
import {Img} from "./UI/Img";
import {UserDetails} from "./Logic/Osm/OsmConnection";
export class InitUiElements { export class InitUiElements {
@ -110,6 +113,9 @@ export class InitUiElements {
InitUiElements.setupAllLayerElements(); InitUiElements.setupAllLayerElements();
if (layoutToUse.customCss !== undefined) {
Utils.LoadCustomCss(layoutToUse.customCss);
}
function updateFavs() { function updateFavs() {
const favs = State.state.favouriteLayers.data ?? []; const favs = State.state.favouriteLayers.data ?? [];
@ -139,6 +145,7 @@ export class InitUiElements {
} }
if (layoutToUse.id === personal.id) { if (layoutToUse.id === personal.id) {
State.state.favouriteLayers.addCallback(updateFavs); State.state.favouriteLayers.addCallback(updateFavs);
State.state.installedThemes.addCallback(updateFavs); State.state.installedThemes.addCallback(updateFavs);
@ -150,27 +157,36 @@ export class InitUiElements {
* This is given to the div which renders fullscreen on mobile devices * This is given to the div which renders fullscreen on mobile devices
*/ */
State.state.selectedElement.addCallback((feature) => { State.state.selectedElement.addCallback((feature) => {
if (feature?.feature?.properties === undefined) {
if (feature === undefined) {
State.state.fullScreenMessage.setData(undefined);
}
if (feature?.properties === undefined) {
return; return;
} }
const data = feature.feature.properties; const data = feature.properties;
// Which is the applicable set? // Which is the applicable set?
for (const layer of layoutToUse.layers) { for (const layer of layoutToUse.layers) {
if (typeof layer === "string") { if (typeof layer === "string") {
continue; continue;
} }
const applicable = layer.overpassTags.matches(TagUtils.proprtiesToKV(data)); const applicable = layer.overpassTags.matches(TagUtils.proprtiesToKV(data));
if (applicable) { if (!applicable) {
// This layer is the layer that gives the questions continue;
const featureBox = new FeatureInfoBox(
State.state.allElements.getElement(data.id),
layer
);
State.state.fullScreenMessage.setData(featureBox);
break;
} }
if(layer.title === null && layer.tagRenderings.length === 0){
continue;
}
// This layer is the layer that gives the questions
const featureBox = new FeatureInfoBox(
State.state.allElements.getElement(data.id),
layer
);
State.state.fullScreenMessage.setData(featureBox);
break;
} }
} }
); );
@ -201,6 +217,21 @@ export class InitUiElements {
content.AttachTo("messagesbox"); content.AttachTo("messagesbox");
} }
State.state.osmConnection.userDetails.map((userDetails: UserDetails) => userDetails?.home)
.addCallbackAndRun(home => {
if (home === undefined) {
return;
}
const color = getComputedStyle(document.body).getPropertyValue("--subtle-detail-color")
const icon = L.icon({
iconUrl: Img.AsData(Svg.home_white_bg.replace(/#ffffff/g, color)),
iconSize: [30, 30],
iconAnchor: [15, 15]
});
const marker = L.marker([home.lat, home.lon], {icon: icon})
marker.addTo(State.state.bm.map)
console.log(marker)
});
new GeoLocationHandler() new GeoLocationHandler()
.SetStyle(`position:relative;display:block;border: solid 2px #0005;cursor: pointer; z-index: 999; /*Just below leaflets zoom*/background-color: white;border-radius: 5px;width: 43px;height: 43px;`) .SetStyle(`position:relative;display:block;border: solid 2px #0005;cursor: pointer; z-index: 999; /*Just below leaflets zoom*/background-color: white;border-radius: 5px;width: 43px;height: 43px;`)
@ -269,7 +300,7 @@ export class InitUiElements {
] ]
if (State.state.featureSwitchShareScreen.data) { if (State.state.featureSwitchShareScreen.data) {
tabs.push({header: Svg.share_img, content: new ShareScreen()}); tabs.push({header: Svg.share, content: new ShareScreen()});
} }
if (State.state.featureSwitchMoreQuests.data) { if (State.state.featureSwitchMoreQuests.data) {
@ -282,12 +313,12 @@ export class InitUiElements {
tabs.push({ tabs.push({
header: Svg.help_img, header: Svg.help ,
content: new VariableUiElement(State.state.osmConnection.userDetails.map(userdetails => { content: new VariableUiElement(State.state.osmConnection.userDetails.map(userdetails => {
if (userdetails.csCount < State.userJourney.mapCompleteHelpUnlock) { if (userdetails.csCount < State.userJourney.mapCompleteHelpUnlock) {
return "" return ""
} }
return Translations.t.general.aboutMapcomplete.Render(); return new Combine([Translations.t.general.aboutMapcomplete, "<br/>Version "+State.vNumber]).Render();
}, [Locale.language])) }, [Locale.language]))
} }
); );
@ -303,8 +334,8 @@ export class InitUiElements {
const fullOptions = this.CreateWelcomePane(); const fullOptions = this.CreateWelcomePane();
const help = Svg.help_ui().SetClass("open-welcome-button"); const help = Svg.help_svg().SetClass("open-welcome-button");
const close = Svg.close_ui().SetClass("close-welcome-button"); const close = Svg.close_svg().SetClass("close-welcome-button");
const checkbox = new CheckBox( const checkbox = new CheckBox(
new Combine([ new Combine([
close, close,
@ -324,11 +355,15 @@ export class InitUiElements {
checkbox.isEnabled.setData(false); checkbox.isEnabled.setData(false);
}) })
State.state.selectedElement.addCallback(() => {
checkbox.isEnabled.setData(false);
})
const fullOptions2 = this.CreateWelcomePane(); const fullOptions2 = this.CreateWelcomePane();
State.state.fullScreenMessage.setData(fullOptions2) State.state.fullScreenMessage.setData(fullOptions2)
Svg.help_ui() Svg.help_svg()
.SetClass("open-welcome-button") .SetClass("open-welcome-button")
.SetClass("shadow") .SetClass("shadow")
.onClick(() => { .onClick(() => {
@ -365,15 +400,15 @@ export class InitUiElements {
return; return;
} }
layerControlPanel.SetStyle("display:block;padding:1em;border-radius:1em;"); layerControlPanel.SetStyle("display:block;padding:0.75em;border-radius:1em;");
const closeButton = Svg.close_ui().SetClass("layer-selection-toggle").SetStyle(" background: #e5f5ff;") const closeButton = Svg.close_svg().SetClass("layer-selection-toggle").SetStyle(" background: var(--subtle-detail-color);")
const checkbox = new CheckBox( const checkbox = new CheckBox(
new Combine([ new Combine([
closeButton, closeButton,
layerControlPanel]).SetStyle("display:flex;flex-direction:row;") layerControlPanel]).SetStyle("display:flex;flex-direction:row;")
.SetClass("hidden-on-mobile") .SetClass("hidden-on-mobile")
, ,
Svg.layers_ui().SetClass("layer-selection-toggle"), Svg.layers_svg().SetClass("layer-selection-toggle"),
State.state.layerControlIsOpened State.state.layerControlIsOpened
); );
@ -432,17 +467,19 @@ export class InitUiElements {
return new Combine([mapComplete, reportBug, " | ", stats, " | ", editHere, editWithJosm]).Render(); return new Combine([mapComplete, reportBug, " | ", stats, " | ", editHere, editWithJosm]).Render();
}, [State.state.osmConnection.userDetails]) }, [State.state.osmConnection.userDetails])
).SetClass("map-attribution") ).SetClass("map-attribution")
} }
static InitBaseMap() { static InitBaseMap() {
const bm = new Basemap("leafletDiv", State.state.locationControl, this.CreateAttribution()); const bm = new Basemap("leafletDiv", State.state.locationControl, this.CreateAttribution());
State.state.bm = bm; State.state.bm = bm;
bm.map.on("popupclose", () => {
State.state.selectedElement.setData(undefined)
})
State.state.layerUpdater = new UpdateFromOverpass(State.state); State.state.layerUpdater = new UpdateFromOverpass(State.state);
State.state.availableBackgroundLayers = new AvailableBaseLayers(State.state).availableEditorLayers; State.state.availableBackgroundLayers = new AvailableBaseLayers(State.state).availableEditorLayers;
const queryParam = QueryParameters.GetQueryParameter("background", State.state.layoutToUse.data.defaultBackgroundId); const queryParam = QueryParameters.GetQueryParameter("background", State.state.layoutToUse.data.defaultBackgroundId, "The id of the background layer to start with");
queryParam.addCallbackAndRun((selectedId: string) => { queryParam.addCallbackAndRun((selectedId: string) => {
const available = State.state.availableBackgroundLayers.data; const available = State.state.availableBackgroundLayers.data;
@ -472,18 +509,15 @@ export class InitUiElements {
throw "Layer " + layer + " was not substituted"; throw "Layer " + layer + " was not substituted";
} }
const generateInfo = (tagsES, feature) => { let generateContents = (tags: UIEventSource<any>) => new FeatureInfoBox(tags, layer);
if (layer.title === undefined && (layer.tagRenderings ?? []).length === 0) {
generateContents = undefined;
}
return new FeatureInfoBox( const flayer: FilteredLayer = new FilteredLayer(layer, generateContents);
tagsES,
layer,
)
};
const flayer: FilteredLayer = FilteredLayer.fromDefinition(layer, generateInfo);
flayers.push(flayer); flayers.push(flayer);
QueryParameters.GetQueryParameter("layer-" + layer.id, "true") QueryParameters.GetQueryParameter("layer-" + layer.id, "true", "Wehter or not layer " + layer.id + " is shown")
.map<boolean>((str) => str !== "false", [], (b) => b.toString()) .map<boolean>((str) => str !== "false", [], (b) => b.toString())
.syncWith( .syncWith(
flayer.isDisplayed flayer.isDisplayed

View file

@ -6,6 +6,7 @@ import {GeoOperations} from "./GeoOperations";
import {UIElement} from "../UI/UIElement"; import {UIElement} from "../UI/UIElement";
import State from "../State"; import State from "../State";
import LayerConfig from "../Customizations/JSON/LayerConfig"; import LayerConfig from "../Customizations/JSON/LayerConfig";
import Hash from "./Web/Hash";
/*** /***
* A filtered layer is a layer which offers a 'set-data' function * A filtered layer is a layer which offers a 'set-data' function
@ -25,13 +26,9 @@ export class FilteredLayer {
public readonly layerDef: LayerConfig; public readonly layerDef: LayerConfig;
private readonly _maxAllowedOverlap: number; private readonly _maxAllowedOverlap: number;
private readonly _style: (properties) => { color: string, weight?: number, icon: { iconUrl: string, iconSize?: [number, number], popupAnchor?: [number, number], iconAnchor?: [number, number] } };
/** The featurecollection from overpass /** The featurecollection from overpass
*/ */
private _dataFromOverpass: any[]; private _dataFromOverpass: any[];
private readonly _wayHandling: number;
/** List of new elements, geojson features /** List of new elements, geojson features
*/ */
private _newElements = []; private _newElements = [];
@ -49,60 +46,7 @@ export class FilteredLayer {
) { ) {
this.layerDef = layerDef; this.layerDef = layerDef;
this._wayHandling = layerDef.wayHandling;
this._showOnPopup = showOnPopup; this._showOnPopup = showOnPopup;
this._style = (tags) => {
const iconUrl = layerDef.icon?.GetRenderValue(tags)?.txt;
const iconSize = (layerDef.iconSize?.GetRenderValue(tags)?.txt ?? "40,40,center").split(",");
const dashArray = layerDef.dashArray.GetRenderValue(tags)?.txt.split(" ").map(Number);
function num(str, deflt = 40) {
const n = Number(str);
if (isNaN(n)) {
return deflt;
}
return n;
}
const iconW = num(iconSize[0]);
const iconH = num(iconSize[1]);
const mode = iconSize[2] ?? "center"
let anchorW = iconW / 2;
let anchorH = iconH / 2;
if (mode === "left") {
anchorW = 0;
}
if (mode === "right") {
anchorW = iconW;
}
if (mode === "top") {
anchorH = 0;
}
if (mode === "bottom") {
anchorH = iconH;
}
const color = layerDef.color?.GetRenderValue(tags)?.txt ?? "#00f";
let weight = num(layerDef.width?.GetRenderValue(tags)?.txt, 5);
return {
icon:
{
iconUrl: iconUrl,
iconSize: [iconW, iconH],
iconAnchor: [anchorW, anchorH],
popupAnchor: [0, 3 - anchorH]
},
color: color,
weight: weight,
dashArray: dashArray
};
};
this.name = name; this.name = name;
this.filters = layerDef.overpassTags; this.filters = layerDef.overpassTags;
this._maxAllowedOverlap = layerDef.hideUnderlayingFeaturesMinPercentage; this._maxAllowedOverlap = layerDef.hideUnderlayingFeaturesMinPercentage;
@ -123,17 +67,6 @@ export class FilteredLayer {
} }
}) })
} }
static fromDefinition(
definition,
showOnPopup: (tags: UIEventSource<any>, feature: any) => UIElement):
FilteredLayer {
return new FilteredLayer(
definition, showOnPopup);
}
/** /**
* The main function to load data into this layer. * The main function to load data into this layer.
* The data that is NOT used by this layer, is returned as a geojson object; the other data is rendered * The data that is NOT used by this layer, is returned as a geojson object; the other data is rendered
@ -142,32 +75,17 @@ export class FilteredLayer {
const leftoverFeatures = []; const leftoverFeatures = [];
const selfFeatures = []; const selfFeatures = [];
for (let feature of geojson.features) { for (let feature of geojson.features) {
// feature.properties contains all the properties
const tags = TagUtils.proprtiesToKV(feature.properties); const tags = TagUtils.proprtiesToKV(feature.properties);
const matches = this.filters.matches(tags);
if (!this.filters.matches(tags)) { if (matches) {
selfFeatures.push(feature);
}
if (!matches || this.layerDef.passAllFeatures) {
leftoverFeatures.push(feature); leftoverFeatures.push(feature);
continue;
} }
if (feature.geometry.type !== "Point") {
const centerPoint = GeoOperations.centerpoint(feature);
if (this._wayHandling === LayerConfig.WAYHANDLING_CENTER_AND_WAY) {
selfFeatures.push(centerPoint);
} else if (this._wayHandling === LayerConfig.WAYHANDLING_CENTER_ONLY) {
feature = centerPoint;
}
}
selfFeatures.push(feature);
} }
this.RenderLayer(selfFeatures)
this.RenderLayer({
type: "FeatureCollection",
features: selfFeatures
})
const notShadowed = []; const notShadowed = [];
for (const feature of leftoverFeatures) { for (const feature of leftoverFeatures) {
@ -190,18 +108,126 @@ export class FilteredLayer {
public AddNewElement(element) { public AddNewElement(element) {
this._newElements.push(element); this._newElements.push(element);
this.RenderLayer({features: this._dataFromOverpass}, element); // Update the layer this.RenderLayer( this._dataFromOverpass); // Update the layer
} }
private RenderLayer(data, openPopupOf = undefined) { private RenderLayer(features) {
let self = this;
if (this._geolayer !== undefined && this._geolayer !== null) { if (this._geolayer !== undefined && this._geolayer !== null) {
// Remove the old geojson layer from the map - we'll reshow all the elements later on anyway // Remove the old geojson layer from the map - we'll reshow all the elements later on anyway
State.state.bm.map.removeLayer(this._geolayer); State.state.bm.map.removeLayer(this._geolayer);
} }
// We fetch all the data we have to show:
let fusedFeatures = this.ApplyWayHandling(this.FuseData(features));
// And we copy some features as points - if needed
const data = {
type: "FeatureCollection",
features: fusedFeatures
}
let self = this;
this._geolayer = L.geoJSON(data, {
style: feature =>
self.layerDef.GenerateLeafletStyle(feature.properties, self._showOnPopup !== undefined),
pointToLayer: function (feature, latLng) {
// Point to layer converts the 'point' to a layer object - as the geojson layer natively cannot handle points
// Click handling is done in the next step
const style = self.layerDef.GenerateLeafletStyle(feature.properties, self._showOnPopup !== undefined);
let marker;
if (style.icon === undefined) {
marker = L.circle(latLng, {
radius: 25,
color: style.color
});
} else if (style.icon.iconUrl.startsWith("$circle")) {
marker = L.circle(latLng, {
radius: 25,
color: style.color
});
} else {
marker = L.marker(latLng, {
icon: L.divIcon(style.icon)
});
}
return marker;
},
onEachFeature: function (feature, layer: Layer) {
if (self._showOnPopup === undefined) {
// No popup contents defined -> don't do anything
return;
}
const popup = L.popup({
autoPan: true,
closeOnEscapeKey: true,
}, layer);
let uiElement: UIElement;
const eventSource = State.state.allElements.addOrGetElement(feature);
uiElement = self._showOnPopup(eventSource, feature);
popup.setContent(uiElement.Render());
layer.bindPopup(popup);
// We first render the UIelement (which'll still need an update later on...)
// But at least it'll be visible already
layer.on("click", (e) => {
// We set the element as selected...
State.state.selectedElement.setData(feature);
uiElement.Update();
});
if (feature.properties.id.replace(/\//g, "_") === Hash.Get().data) {
const center = GeoOperations.centerpoint(feature).geometry.coordinates;
popup.setLatLng({lat: center[1], lng: center[0]});
popup.openOn(State.state.bm.map)
}
}
});
if (this.combinedIsDisplayed.data) {
this._geolayer.addTo(State.state.bm.map);
}
}
private ApplyWayHandling(fusedFeatures: any[]) {
if (this.layerDef.wayHandling === LayerConfig.WAYHANDLING_DEFAULT) {
// We don't have to do anything special
return fusedFeatures;
}
// We have to convert all the ways into centerpoints
const existingPoints = [];
const newPoints = [];
const existingWays = [];
for (const feature of fusedFeatures) {
if (feature.geometry.type === "Point") {
existingPoints.push(feature);
continue;
}
existingWays.push(feature);
const centerPoint = GeoOperations.centerpoint(feature);
newPoints.push(centerPoint);
}
fusedFeatures = existingPoints.concat(newPoints);
if (this.layerDef.wayHandling === LayerConfig.WAYHANDLING_CENTER_AND_WAY) {
fusedFeatures = fusedFeatures.concat(existingWays)
}
return fusedFeatures;
}
//*Fuses the old and the new datasets*/
private FuseData(data: any[]) {
const oldData = this._dataFromOverpass ?? []; const oldData = this._dataFromOverpass ?? [];
// We keep track of all the ids that are freshly loaded in order to avoid adding duplicates // We keep track of all the ids that are freshly loaded in order to avoid adding duplicates
@ -209,7 +235,7 @@ export class FilteredLayer {
// A list of all the features to show // A list of all the features to show
const fusedFeatures = []; const fusedFeatures = [];
// First, we add all the fresh data: // First, we add all the fresh data:
for (const feature of data.features) { for (const feature of data) {
idsFromOverpass.add(feature.properties.id); idsFromOverpass.add(feature.properties.id);
fusedFeatures.push(feature); fusedFeatures.push(feature);
} }
@ -230,132 +256,6 @@ export class FilteredLayer {
fusedFeatures.push(feature); fusedFeatures.push(feature);
} }
} }
return fusedFeatures;
// We use a new, fused dataset
data = {
type: "FeatureCollection",
features: fusedFeatures
}
// The data is split in two parts: the poinst and the rest
// The points get a special treatment in order to render them properly
// Note that some features might get a point representation as well
const runWhenAdded: (() => void)[] = []
this._geolayer = L.geoJSON(data, {
style: function (feature) {
return self._style(feature.properties);
},
pointToLayer: function (feature, latLng) {
const style = self._style(feature.properties);
let marker;
if (style.icon === undefined) {
marker = L.circle(latLng, {
radius: 25,
color: style.color
});
} else if (style.icon.iconUrl.startsWith("$circle")) {
marker = L.circle(latLng, {
radius: 25,
color: style.color
});
} else {
if (style.icon.iconSize === undefined) {
style.icon.iconSize = [50, 50]
}
// @ts-ignore
marker = L.marker(latLng, {
icon: L.icon(style.icon),
});
}
let eventSource = State.state.allElements.addOrGetElement(feature);
const popup = L.popup({}, marker);
let uiElement: UIElement;
let content = undefined;
let p = marker.bindPopup(popup)
.on("popupopen", () => {
if (content === undefined) {
uiElement = self._showOnPopup(eventSource, feature);
// Lazily create the content
content = uiElement.Render();
}
popup.setContent(content);
uiElement.Update();
});
if (feature === openPopupOf) {
runWhenAdded.push(() => {
p.openPopup();
})
}
return marker;
},
onEachFeature: function (feature, layer:Layer) {
// We monky-patch the feature element with an update-style
function updateStyle () {
// @ts-ignore
if (layer.setIcon) {
const style = self._style(feature.properties);
const icon = style.icon;
if (icon.iconUrl) {
if (icon.iconUrl.startsWith("$circle")) {
// pass
} else {
// @ts-ignore
layer.setIcon(L.icon(icon))
}
}
} else {
self._geolayer.setStyle(function (featureX) {
return self._style(featureX.properties);
});
}
}
let eventSource = State.state.allElements.addOrGetElement(feature);
eventSource.addCallback(updateStyle);
function openPopup(e) {
State.state.selectedElement.setData({feature: feature});
updateStyle()
if (feature.geometry.type === "Point") {
return; // Points bind there own popups
}
const uiElement = self._showOnPopup(eventSource, feature);
L.popup({
autoPan: true,
}).setContent(uiElement.Render())
.setLatLng(e.latlng)
.openOn(State.state.bm.map);
uiElement.Update();
if (e) {
L.DomEvent.stop(e); // Marks the event as consumed
}
}
layer.on("click", openPopup);
}
});
if (this.combinedIsDisplayed.data) {
this._geolayer.addTo(State.state.bm.map);
for (const f of runWhenAdded) {
f();
}
}
} }
} }

View file

@ -4,7 +4,6 @@ import {UIElement} from "../../UI/UIElement";
import State from "../../State"; import State from "../../State";
import {Utils} from "../../Utils"; import {Utils} from "../../Utils";
import {Basemap} from "./Basemap"; import {Basemap} from "./Basemap";
import {FixedUiElement} from "../../UI/Base/FixedUiElement";
import Svg from "../../Svg"; import Svg from "../../Svg";
import {Img} from "../../UI/Img"; import {Img} from "../../UI/Img";
@ -48,15 +47,18 @@ export class GeoLocationHandler extends UIElement {
map.on('accuratepositionfound', onAccuratePositionFound); map.on('accuratepositionfound', onAccuratePositionFound);
map.on('accuratepositionerror', onAccuratePositionError); map.on('accuratepositionerror', onAccuratePositionError);
FixedUiElement
const icon = L.icon(
{
iconUrl: Img.AsData(Svg.crosshair_blue),
iconSize: [40, 40], // size of the icon
iconAnchor: [20, 20], // point of the icon which will correspond to marker's location
})
State.state.currentGPSLocation.addCallback((location) => { State.state.currentGPSLocation.addCallback((location) => {
const color = getComputedStyle(document.body).getPropertyValue("--catch-detail-color")
const icon = L.icon(
{
iconUrl: Img.AsData(Svg.crosshair.replace(/#000000/g, color)),
iconSize: [40, 40], // size of the icon
iconAnchor: [20, 20], // point of the icon which will correspond to marker's location
})
const newMarker = L.marker(location.latlng, {icon: icon}); const newMarker = L.marker(location.latlng, {icon: icon});
newMarker.addTo(map); newMarker.addTo(map);

View file

@ -82,7 +82,7 @@ export default class MetaTagging {
}) })
) )
private static isOpen = new SimpleMetaTagger( private static isOpen = new SimpleMetaTagger(
["_isOpen", "_isOpen:description"], "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no", ["_isOpen", "_isOpen:description"], "If 'opening_hours' is present, it will add the current state of the feature (being 'yes' or 'no')",
(feature => { (feature => {
const tagsSource = State.state.allElements.addOrGetElement(feature); const tagsSource = State.state.allElements.addOrGetElement(feature);
tagsSource.addCallback(tags => { tagsSource.addCallback(tags => {
@ -123,13 +123,37 @@ export default class MetaTagging {
}) })
) )
public static carriageWayWidth = new SimpleMetaTagger( private static directionSimplified = new SimpleMetaTagger(
["_width:needed","_width:needed:no_pedestrians", "_width:difference"], ["_direction:simplified", "_direction:leftright"], "_direction:simplified turns 'camera:direction' and 'direction' into either 0, 45, 90, 135, 180, 225, 270 or 315, whichever is closest. _direction:leftright is either 'left' or 'right', which is left-looking on the map or 'right-looking' on the map",
(feature => {
const tags = feature.properties;
const direction = tags["camera:direction"] ?? tags["direction"];
if (direction === undefined) {
return;
}
let n = Number(direction);
if (isNaN(n)) {
return;
}
// [22.5 -> 67.5] is sector 1
// [67.5 -> ] is sector 1
n = (n + 22.5) % 360;
n = Math.floor(n / 45);
tags["_direction:simplified"] = n;
tags["_direction:leftright"] = n <= 3 ? "right" : "left";
})
)
private static carriageWayWidth = new SimpleMetaTagger(
["_width:needed", "_width:needed:no_pedestrians", "_width:difference"],
"Legacy for a specific project calculating the needed width for safe traffic on a road. Only activated if 'width:carriageway' is present", "Legacy for a specific project calculating the needed width for safe traffic on a road. Only activated if 'width:carriageway' is present",
(feature: any, index: number) => { (feature: any, index: number) => {
const properties = feature.properties; const properties = feature.properties;
if(properties["width:carriageway"] === undefined){ if (properties["width:carriageway"] === undefined) {
return; return;
} }
@ -239,7 +263,8 @@ export default class MetaTagging {
MetaTagging.surfaceArea, MetaTagging.surfaceArea,
MetaTagging.country, MetaTagging.country,
MetaTagging.isOpen, MetaTagging.isOpen,
MetaTagging.carriageWayWidth MetaTagging.carriageWayWidth,
MetaTagging.directionSimplified
]; ];

View file

@ -62,6 +62,11 @@ export class UpdateFromOverpass {
if (state.locationControl.data.zoom < layer.minzoom) { if (state.locationControl.data.zoom < layer.minzoom) {
continue; continue;
} }
if(layer.doNotDownload){
continue;
}
// Check if data for this layer has already been loaded // Check if data for this layer has already been loaded
let previouslyLoaded = false; let previouslyLoaded = false;
for (let z = layer.minzoom; z < 25 && !previouslyLoaded; z++) { for (let z = layer.minzoom; z < 25 && !previouslyLoaded; z++) {
@ -128,6 +133,7 @@ export class UpdateFromOverpass {
this.ForceRefresh(); this.ForceRefresh();
console.log(`QUERY FAILED (retrying in ${5 * this.retries.data} sec)`, reason); console.log(`QUERY FAILED (retrying in ${5 * this.retries.data} sec)`, reason);
this.retries.ping(); this.retries.ping();
this.runningQuery.setData(false)
const self = this; const self = this;
window?.setTimeout( window?.setTimeout(
function () { function () {

18
Logic/Web/Hash.ts Normal file
View file

@ -0,0 +1,18 @@
import {UIEventSource} from "../UIEventSource";
export default class Hash {
public static Get() : UIEventSource<string>{
const hash = new UIEventSource<string>(window.location.hash.substr(1));
hash.addCallback(h => {
h = h.replace(/\//g, "_");
return window.location.hash = "#" + h;
});
window.onhashchange = () => {
hash.setData(window.location.hash.substr(1))
}
return hash;
}
}

View file

@ -56,8 +56,8 @@ export class Imgur {
}, },
}; };
$.ajax(settings).done(function (response) { $.ajax(settings).done(function (response) {
const descr : string= response.data.description; const descr: string = response.data.description ?? "";
const data : any = {}; const data: any = {};
for (const tag of descr.split("\n")) { for (const tag of descr.split("\n")) {
const kv = tag.split(":"); const kv = tag.split(":");
const k = kv[0]; const k = kv[0];

View file

@ -2,23 +2,26 @@
* Wraps the query parameters into UIEventSources * Wraps the query parameters into UIEventSources
*/ */
import {UIEventSource} from "../UIEventSource"; import {UIEventSource} from "../UIEventSource";
import Hash from "./Hash";
export class QueryParameters { export class QueryParameters {
private static order: string [] = ["layout","test","z","lat","lon"]; private static order: string [] = ["layout", "test", "z", "lat", "lon"];
private static knownSources = {}; private static knownSources = {};
private static initialized = false; private static initialized = false;
private static defaults = {} private static defaults = {}
private static addOrder(key){ private static documentation = {}
if(this.order.indexOf(key) < 0){
private static addOrder(key) {
if (this.order.indexOf(key) < 0) {
this.order.push(key) this.order.push(key)
} }
} }
private static init() { private static init() {
if(this.initialized){ if (this.initialized) {
return; return;
} }
this.initialized = true; this.initialized = true;
@ -55,14 +58,15 @@ export class QueryParameters {
parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(QueryParameters.knownSources[key].data)) parts.push(encodeURIComponent(key) + "=" + encodeURIComponent(QueryParameters.knownSources[key].data))
} }
history.replaceState(null, "", "?" + parts.join("&")); history.replaceState(null, "", "?" + parts.join("&") + "#" + Hash.Get().data);
} }
public static GetQueryParameter(key: string, deflt: string): UIEventSource<string> { public static GetQueryParameter(key: string, deflt: string, documentation?: string): UIEventSource<string> {
if(!this.initialized){ if(!this.initialized){
this.init(); this.init();
} }
QueryParameters.documentation[key] = documentation;
if (deflt !== undefined) { if (deflt !== undefined) {
QueryParameters.defaults[key] = deflt; QueryParameters.defaults[key] = deflt;
} }
@ -76,4 +80,12 @@ export class QueryParameters {
return source; return source;
} }
public static GenerateQueryParameterDocs(): string {
const docs = [];
for (const key in QueryParameters.documentation) {
docs.push("**" + key + "**: " + QueryParameters.documentation[key] + " (default value: _" + QueryParameters.defaults[key] + "_)")
}
return docs.join("\n\n");
}
} }

View file

@ -103,8 +103,8 @@ A theme has translations into the preset.json (`assets/themes/themename/themenam
0. Fork this repository 0. Fork this repository
1. Modify `"language"` to contain the new language, e.g. `"language": "nl"` becomes `"language": ["nl", "en"]` 1. Modify `"language"` to contain the new language, e.g. `"language": "nl"` becomes `"language": ["nl", "en"]`
2. Add extra strings to the texts. If it used to be a single-language theme, one can replace the strings, e.g.: `"description": "Welcome to Open Bookcase Map"` to `"description": {"en": "Welcome to Open Bookcase Map", "nl": "Welkom bij de OpenBoekenruilkastenKaart", "fr": "Bienvenue sûr la carte des microbibliotheques"}`. If the correct language is not found, it'll fallback to another supported language. 2. Add extra strings to the texts. If it used to be a single-language theme, one can replace the strings, e.g.: `"description": "Welcome to Open Bookcase Map"` to `"description": {"en": "Welcome to Open Bookcase Map", "nl": "Welkom bij de OpenBoekenruilkastenKaart", "fr": "Bienvenue sûr la carte des petites bibliotheques"}`. If the correct language is not found, it'll fallback to another supported language.
3. If you notice missing translations in the core of MapComplete, fork this project, open [the file containing all translations](https://github.com/pietervdvn/MapComplete/blob/master/UI/i18n/Translations.ts), add add a language string there 3. If you notice missing translations in the core of MapComplete, fork this project, open [the file containing all translations](https://github.com/pietervdvn/MapComplete/blob/master/assets/translations.json), add add a language string there
4. Send a pull request to update the languages, I'll gladly add it! It doesn't have to be a complete translation from the start ;) 4. Send a pull request to update the languages, I'll gladly add it! It doesn't have to be a complete translation from the start ;)
### Adding your theme to the repository ### Adding your theme to the repository
@ -166,6 +166,50 @@ Whenever a change is made -even adding a single tag- the change is uploaded into
Note that changesets are closed automatically after one hour of inactivity, so we don't have to worry about closing them. Note that changesets are closed automatically after one hour of inactivity, so we don't have to worry about closing them.
### Query parameters
By adding extra query parameters, more options are available to influence:
**test**: If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org (default value: _false_)
**layout**: The layout to load into MapComplete (default value: _bookcases_)
**userlayout**: undefined (default value: _false_)
**layer-control-toggle**: Wether or not the layer control is shown (default value: _false_)
**tab**: The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >200 changesets) (default value: _0_)
**z**: The initial/current zoom level (default value: _1_)
**lat**: The initial/current latitude (default value: _0_)
**lon**: The initial/current longitude of the app (default value: _0_)
**fs-userbadge**: Disables/Enables the userbadge (and thus disables login capabilities) (default value: _true_)
**fs-search**: Disables/Enables the search bar (default value: _true_)
**fs-layers**: Disables/Enables the layer control (default value: _true_)
**fs-add-new**: Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place) (default value: _true_)
**fs-welcome-message**: undefined (default value: _true_)
**fs-iframe**: Disables/Enables the iframe-popup (default value: _false_)
**fs-more-quests**: Disables/Enables the 'More Quests'-tab in the welcome message (default value: _true_)
**fs-share-screen**: Disables/Enables the 'Share-screen'-tab in the welcome message (default value: _true_)
**fs-geolocation**: Disables/Enables the geolocation button (default value: _true_)
**oauth_token**: Used to complete the login (default value: _undefined_)
**background**: The id of the background layer to start with (default value: _undefined_)
**layer-bookcases**: Wehter or not layer bookcases is shown (default value: _true_) index.ts:104:8
# Privacy # Privacy
Privacy is important, we try to leak as little information as possible. Privacy is important, we try to leak as little information as possible.

View file

@ -12,6 +12,7 @@ import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
import {QueryParameters} from "./Logic/Web/QueryParameters"; import {QueryParameters} from "./Logic/Web/QueryParameters";
import {BaseLayer} from "./Logic/BaseLayer"; import {BaseLayer} from "./Logic/BaseLayer";
import LayoutConfig from "./Customizations/JSON/LayoutConfig"; import LayoutConfig from "./Customizations/JSON/LayoutConfig";
import Hash from "./Logic/Web/Hash";
/** /**
* Contains the global state: a bunch of UI-event sources * Contains the global state: a bunch of UI-event sources
@ -22,7 +23,7 @@ export default class State {
// The singleton of the global state // The singleton of the global state
public static state: State; public static state: State;
public static vNumber = "0.1.2g"; public static vNumber = "0.2.0";
// The user journey states thresholds when a new feature gets unlocked // The user journey states thresholds when a new feature gets unlocked
public static userJourney = { public static userJourney = {
@ -82,7 +83,7 @@ export default class State {
/** /**
The latest element that was selected - used to generate the right UI at the right place The latest element that was selected - used to generate the right UI at the right place
*/ */
public readonly selectedElement = new UIEventSource<{ feature: any }>(undefined); public readonly selectedElement = new UIEventSource<any>(undefined);
public readonly zoom: UIEventSource<number>; public readonly zoom: UIEventSource<number>;
public readonly lat: UIEventSource<number>; public readonly lat: UIEventSource<number>;
@ -115,10 +116,10 @@ export default class State {
public layoutDefinition: string; public layoutDefinition: string;
public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>; public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>;
public layerControlIsOpened: UIEventSource<boolean> = QueryParameters.GetQueryParameter("layer-control-toggle", "false") public layerControlIsOpened: UIEventSource<boolean> = QueryParameters.GetQueryParameter("layer-control-toggle", "false", "Wether or not the layer control is shown")
.map<boolean>((str) => str !== "false", [], b => "" + b) .map<boolean>((str) => str !== "false", [], b => "" + b)
public welcomeMessageOpenedTab = QueryParameters.GetQueryParameter("tab", "0").map<number>( public welcomeMessageOpenedTab = QueryParameters.GetQueryParameter("tab", "0", `The tab that is shown in the welcome-message. 0 = the explanation of the theme,1 = OSM-credits, 2 = sharescreen, 3 = more themes, 4 = about mapcomplete (user must be logged in and have >${State.userJourney.mapCompleteHelpUnlock} changesets)`).map<number>(
str => isNaN(Number(str)) ? 0 : Number(str), [], n => "" + n str => isNaN(Number(str)) ? 0 : Number(str), [], n => "" + n
); );
@ -138,11 +139,11 @@ export default class State {
}) })
} }
this.zoom = asFloat( this.zoom = asFloat(
QueryParameters.GetQueryParameter("z", "" + layoutToUse.startZoom) QueryParameters.GetQueryParameter("z", "" + layoutToUse.startZoom, "The initial/current zoom level")
.syncWith(LocalStorageSource.Get("zoom"))); .syncWith(LocalStorageSource.Get("zoom")));
this.lat = asFloat(QueryParameters.GetQueryParameter("lat", "" + layoutToUse.startLat) this.lat = asFloat(QueryParameters.GetQueryParameter("lat", "" + layoutToUse.startLat, "The initial/current latitude")
.syncWith(LocalStorageSource.Get("lat"))); .syncWith(LocalStorageSource.Get("lat")));
this.lon = asFloat(QueryParameters.GetQueryParameter("lon", "" + layoutToUse.startLon) this.lon = asFloat(QueryParameters.GetQueryParameter("lon", "" + layoutToUse.startLon, "The initial/current longitude of the app")
.syncWith(LocalStorageSource.Get("lon"))); .syncWith(LocalStorageSource.Get("lon")));
@ -165,39 +166,66 @@ export default class State {
}); });
function featSw(key: string, deflt: (layout: LayoutConfig) => boolean): UIEventSource<boolean> { function featSw(key: string, deflt: (layout: LayoutConfig) => boolean, documentation: string): UIEventSource<boolean> {
const queryParameterSource = QueryParameters.GetQueryParameter(key, undefined); const queryParameterSource = QueryParameters.GetQueryParameter(key, undefined, documentation);
// I'm so sorry about someone trying to decipher this // I'm so sorry about someone trying to decipher this
// It takes the current layout, extracts the default value for this query paramter. A query parameter event source is then retreived and flattened // It takes the current layout, extracts the default value for this query paramter. A query parameter event source is then retreived and flattened
return UIEventSource.flatten( return UIEventSource.flatten(
self.layoutToUse.map((layout) => { self.layoutToUse.map((layout) => {
const defaultValue = deflt(layout); const defaultValue = deflt(layout);
const queryParam = QueryParameters.GetQueryParameter(key, "" + defaultValue) const queryParam = QueryParameters.GetQueryParameter(key, "" + defaultValue, documentation)
return queryParam.map((str) => str === undefined ? defaultValue : (str !== "false")); return queryParam.map((str) => str === undefined ? defaultValue : (str !== "false"));
}), [queryParameterSource]); }), [queryParameterSource]);
} }
this.featureSwitchUserbadge = featSw("fs-userbadge", (layoutToUse) => layoutToUse?.enableUserBadge ?? true); this.featureSwitchUserbadge = featSw("fs-userbadge", (layoutToUse) => layoutToUse?.enableUserBadge ?? true,
this.featureSwitchSearch = featSw("fs-search", (layoutToUse) => layoutToUse?.enableSearch ?? true); "Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode.");
this.featureSwitchLayers = featSw("fs-layers", (layoutToUse) => layoutToUse?.enableLayers ?? true); this.featureSwitchSearch = featSw("fs-search", (layoutToUse) => layoutToUse?.enableSearch ?? true,
this.featureSwitchAddNew = featSw("fs-add-new", (layoutToUse) => layoutToUse?.enableAddNewPoints ?? true); "Disables/Enables the search bar");
this.featureSwitchWelcomeMessage = featSw("fs-welcome-message", () => true); this.featureSwitchLayers = featSw("fs-layers", (layoutToUse) => layoutToUse?.enableLayers ?? true,
this.featureSwitchIframe = featSw("fs-iframe", () => false); "Disables/Enables the layer control");
this.featureSwitchMoreQuests = featSw("fs-more-quests", (layoutToUse) => layoutToUse?.enableMoreQuests ?? true); this.featureSwitchAddNew = featSw("fs-add-new", (layoutToUse) => layoutToUse?.enableAddNewPoints ?? true,
this.featureSwitchShareScreen = featSw("fs-share-screen", (layoutToUse) => layoutToUse?.enableShareScreen ?? true); "Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)");
this.featureSwitchGeolocation = featSw("fs-geolocation", (layoutToUse) => layoutToUse?.enableGeolocation ?? true); this.featureSwitchWelcomeMessage = featSw("fs-welcome-message", () => true,
"Disables/enables the help menu or welcome message");
this.featureSwitchIframe = featSw("fs-iframe", () => false,
"Disables/Enables the iframe-popup");
this.featureSwitchMoreQuests = featSw("fs-more-quests", (layoutToUse) => layoutToUse?.enableMoreQuests ?? true,
"Disables/Enables the 'More Quests'-tab in the welcome message");
this.featureSwitchShareScreen = featSw("fs-share-screen", (layoutToUse) => layoutToUse?.enableShareScreen ?? true,
"Disables/Enables the 'Share-screen'-tab in the welcome message");
this.featureSwitchGeolocation = featSw("fs-geolocation", (layoutToUse) => layoutToUse?.enableGeolocation ?? true,
"Disables/Enables the geolocation button");
const testParam = QueryParameters.GetQueryParameter("test", "false").data; const testParam = QueryParameters.GetQueryParameter("test", "false",
"If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org").data;
this.osmConnection = new OsmConnection( this.osmConnection = new OsmConnection(
testParam === "true", testParam === "true",
QueryParameters.GetQueryParameter("oauth_token", undefined), QueryParameters.GetQueryParameter("oauth_token", undefined,
"Used to complete the login"),
layoutToUse.id, layoutToUse.id,
true true
); );
const h = Hash.Get();
this.selectedElement.addCallback(selected => {
if (selected === undefined) {
h.setData("");
} else {
h.setData(selected.id)
}
}
)
h.addCallbackAndRun(hash => {
if(hash === undefined || hash === ""){
self.selectedElement.setData(undefined);
}
})
this.installedThemes = this.osmConnection.preferencesHandler.preferences.map<{ layout: LayoutConfig, definition: string }[]>(allPreferences => { this.installedThemes = this.osmConnection.preferencesHandler.preferences.map<{ layout: LayoutConfig, definition: string }[]>(allPreferences => {
const installedThemes: { layout: LayoutConfig, definition: string }[] = []; const installedThemes: { layout: LayoutConfig, definition: string }[] = [];
if (allPreferences === undefined) { if (allPreferences === undefined) {
@ -213,8 +241,10 @@ export default class State {
continue; continue;
} }
try { try {
const json = btoa(customLayout.data);
console.log(json);
const layout = new LayoutConfig( const layout = new LayoutConfig(
JSON.parse(btoa(customLayout.data))); JSON.parse(json));
installedThemes.push({ installedThemes.push({
layout: layout, layout: layout,
definition: customLayout.data definition: customLayout.data

131
Svg.ts

File diff suppressed because one or more lines are too long

View file

@ -8,64 +8,23 @@ import Combine from "./Base/Combine";
*/ */
export class FullScreenMessageBox extends UIElement { export class FullScreenMessageBox extends UIElement {
private static readonly _toTheMap_height : string = "5em";
private _uielement: UIElement;
private readonly returnToTheMap: UIElement; private readonly returnToTheMap: UIElement;
private _content: UIElement;
constructor(onClear: (() => void)) { constructor(onClear: (() => void)) {
super(State.state.fullScreenMessage); super(State.state.fullScreenMessage);
const self = this;
State.state.fullScreenMessage.addCallbackAndRun(uiElement => {
this._uielement = new Combine([State.state.fullScreenMessage.data]).SetStyle(
"display:block;"+
"padding: 1em;"+
"padding-bottom:6em;"+
`margin-bottom:${FullScreenMessageBox._toTheMap_height};`+
"box-sizing:border-box;"+
`height:calc(100vh - ${FullScreenMessageBox._toTheMap_height});`+
"overflow-y: auto;" +
"max-width:100vw;" +
"overflow-x:hidden;" +
"background:white;"
);
});
this.HideOnEmpty(true); this.HideOnEmpty(true);
State.state.fullScreenMessage.addCallback(latestData => {
if (latestData === undefined) {
location.hash = "";
} else {
// The 'hash' makes sure a new piece of history is added. This makes the 'back-button' on android remove the popup
location.hash = "#element";
}
this.Update();
})
if (window !== undefined) {
window.onhashchange = function () {
if (location.hash === "") {
// No more element: back to the map!
console.log("Clearing full screen message");
State.state.fullScreenMessage.setData(undefined);
onClear();
}
}
}
this.returnToTheMap = this.returnToTheMap =
new Combine([Translations.t.general.returnToTheMap.Clone().SetStyle("font-size:xx-large")]) new Combine([Translations.t.general.returnToTheMap.Clone().SetStyle("font-size:xx-large")])
.SetStyle("background:#7ebc6f;" + .SetStyle("background:var(--catch-detail-color);" +
"position: fixed;" + "position: fixed;" +
"z-index: 10000;" + "z-index: 10000;" +
"bottom: 0;" + "bottom: 0;" +
"left: 0;" + "left: 0;" +
`height: ${FullScreenMessageBox._toTheMap_height};` + `height: var(--return-to-the-map-height);` +
"width: 100vw;" + "width: 100vw;" +
"color: white;" + "color: var(--catch-detail-color-contrast);" +
"font-weight: bold;" + "font-weight: bold;" +
"pointer-events: all;" + "pointer-events: all;" +
"cursor: pointer;" + "cursor: pointer;" +
@ -74,10 +33,8 @@ export class FullScreenMessageBox extends UIElement {
"padding-bottom: 1.2em;" + "padding-bottom: 1.2em;" +
"box-sizing:border-box") "box-sizing:border-box")
.onClick(() => { .onClick(() => {
console.log("Returning...")
State.state.fullScreenMessage.setData(undefined); State.state.fullScreenMessage.setData(undefined);
onClear(); onClear();
self.Update();
}); });
} }
@ -87,7 +44,21 @@ export class FullScreenMessageBox extends UIElement {
if (State.state.fullScreenMessage.data === undefined) { if (State.state.fullScreenMessage.data === undefined) {
return ""; return "";
} }
return new Combine([this._uielement, this.returnToTheMap]) this._content = State.state.fullScreenMessage.data;
const uielement = new Combine([this._content]).SetStyle(
"display:block;" +
"padding: 1em;" +
"padding-bottom:6em;" +
`margin-bottom: var(--return-to-the-map-height);` +
"box-sizing:border-box;" +
`height:calc(100vh - var(--return-to-the-map-height));` +
"overflow-y: auto;" +
"max-width:100vw;" +
"overflow-x:hidden;" +
"background:var(--background-color);" +
"color: var(--foreground-color);"
);
return new Combine([uielement, this.returnToTheMap])
.Render(); .Render();
} }

View file

@ -42,6 +42,9 @@ export default class DeleteImage extends UIElement {
} }
InnerRender(): string { InnerRender(): string {
if(!State.state.featureSwitchUserbadge.data){
return "";
}
const value = this.tags.data[this.key]; const value = this.tags.data[this.key];
if (value === undefined || value === "") { if (value === undefined || value === "") {

View file

@ -44,7 +44,7 @@ export class ImageUploadFlow extends UIElement {
this._licensePicker = licensePicker; this._licensePicker = licensePicker;
this._selectedLicence = licensePicker.GetValue(); this._selectedLicence = licensePicker.GetValue();
this._connectButton = new Combine([t.pleaseLogin]) this._connectButton = t.pleaseLogin.Clone()
.onClick(() => State.state.osmConnection.AttemptLogin()) .onClick(() => State.state.osmConnection.AttemptLogin())
.SetClass("login-button-friendly"); .SetClass("login-button-friendly");
@ -52,6 +52,10 @@ export class ImageUploadFlow extends UIElement {
InnerRender(): string { InnerRender(): string {
if(!State.state.featureSwitchUserbadge.data){
return "";
}
const t = Translations.t.image; const t = Translations.t.image;
if (State.state.osmConnection.userDetails === undefined) { if (State.state.osmConnection.userDetails === undefined) {
return ""; // No user details -> logging in is probably disabled or smthing return ""; // No user details -> logging in is probably disabled or smthing
@ -97,22 +101,9 @@ export class ImageUploadFlow extends UIElement {
]); ]);
const label = new Combine([ const label = new Combine([
Svg.camera_plus_ui().SetStyle("width: 36px;height: 36px;padding: 0.1em;margin-top: 5px;border-radius: 0;float: left;display:block"), Svg.camera_plus_svg().SetStyle("width: 36px;height: 36px;padding: 0.1em;margin-top: 5px;border-radius: 0;float: left;display:block"),
Translations.t.image.addPicture Translations.t.image.addPicture
.SetStyle("width:max-content;font-size: 28px;" + ]).SetClass("image-upload-flow-button")
"font-weight: bold;" +
"float: left;" +
"margin-top: 4px;" +
"padding-top: 4px;" +
"padding-bottom: 4px;" +
"padding-left: 13px;"),
]).SetStyle(" display: flex;" +
"cursor:pointer;" +
"padding: 0.5em;" +
"border-radius: 1em;" +
"border: 3px solid black;" +
"box-sizing:border-box;")
const actualInputElement = const actualInputElement =
`<input style='display: none' id='fileselector-${this.id}' type='file' accept='image/*' name='picField' multiple='multiple' alt=''/>`; `<input style='display: none' id='fileselector-${this.id}' type='file' accept='image/*' name='picField' multiple='multiple' alt=''/>`;
@ -127,7 +118,8 @@ export class ImageUploadFlow extends UIElement {
return new Combine([ return new Combine([
form, form,
extraInfo extraInfo
]).SetStyle("margin-top: 1em;margin-bottom: 2em;text-align: center;") ]).SetClass("image-upload-flow")
.SetStyle("margin-top: 1em;margin-bottom: 2em;text-align: center;")
.Render(); .Render();
} }

100
UI/Input/DirectionInput.ts Normal file
View file

@ -0,0 +1,100 @@
import {InputElement} from "./InputElement";
import {UIEventSource} from "../../Logic/UIEventSource";
import Combine from "../Base/Combine";
import Svg from "../../Svg";
import * as L from "leaflet"
import * as X from "leaflet-providers"
import {Basemap} from "../../Logic/Leaflet/Basemap";
import State from "../../State";
/**
* Selects a direction in degrees
*/
export default class DirectionInput extends InputElement<string> {
private readonly value: UIEventSource<string>;
public readonly IsSelected: UIEventSource<boolean> = new UIEventSource<boolean>(false);
constructor(value?: UIEventSource<string>) {
super();
this.dumbMode = false;
this.value = value ?? new UIEventSource<string>(undefined);
this.value.addCallbackAndRun(rotation => {
const selfElement = document.getElementById(this.id);
if (selfElement === null) {
return;
}
const cone = selfElement.getElementsByClassName("direction-svg")[0] as HTMLElement
cone.style.rotate = rotation + "deg";
})
}
GetValue(): UIEventSource<string> {
return this.value;
}
InnerRender(): string {
return new Combine([
`<div id="direction-leaflet-div-${this.id}" style="width:100%;height: 100%;position: absolute;top:0;left:0;border-radius:100%;"></div>`,
Svg.direction_svg().SetStyle(
`position: absolute;top: 0;left: 0;width: 100%;height: 100%;rotate:${this.value.data}deg;`)
.SetClass("direction-svg"),
Svg.compass_svg().SetStyle(
"position: absolute;top: 0;left: 0;width: 100%;height: 100%;")
])
.SetStyle("position:relative;display:block;width: min(100%, 25em); padding-top: min(100% , 25em); background:white; border: 1px solid black; border-radius: 999em")
.Render();
}
protected InnerUpdate(htmlElement: HTMLElement) {
super.InnerUpdate(htmlElement);
const self = this;
function onPosChange(x: number, y: number) {
const rect = htmlElement.getBoundingClientRect();
const dx = -(rect.left + rect.right) / 2 + x;
const dy = (rect.top + rect.bottom) / 2 - y;
const angle = 180 * Math.atan2(dy, dx) / Math.PI;
const angleGeo = Math.floor((450 - angle) % 360);
self.value.setData("" + angleGeo)
}
htmlElement.ontouchmove = (ev: TouchEvent) => {
onPosChange(ev.touches[0].clientX, ev.touches[0].clientY);
ev.preventDefault();
}
htmlElement.ontouchstart = (ev: TouchEvent) => {
onPosChange(ev.touches[0].clientX, ev.touches[0].clientY);
}
let isDown = false;
htmlElement.onmousedown = (ev: MouseEvent) => {
isDown = true;
onPosChange(ev.x, ev.y);
ev.preventDefault();
}
htmlElement.onmouseup = (ev) => {
isDown = false; ev.preventDefault();
}
htmlElement.onmousemove = (ev: MouseEvent) => {
if (isDown) {
onPosChange(ev.x, ev.y);
} ev.preventDefault();
}
}
IsValid(str: string): boolean {
const t = Number(str);
return !isNaN(t) && t >= 0 && t <= 360;
}
}

View file

@ -71,7 +71,7 @@ export class MultiInput<T> extends InputElement<T[]> {
input.IsSelected.addCallback(() => this.UpdateIsSelected()); input.IsSelected.addCallback(() => this.UpdateIsSelected());
const moveUpBtn = Svg.up_ui() const moveUpBtn = Svg.up_ui()
.onClick(() => { .SetClass('small-image').onClick(() => {
const v = self._value.data[i]; const v = self._value.data[i];
self._value.data[i] = self._value.data[i - 1]; self._value.data[i] = self._value.data[i - 1];
self._value.data[i - 1] = v; self._value.data[i - 1] = v;
@ -79,8 +79,8 @@ export class MultiInput<T> extends InputElement<T[]> {
}); });
const moveDownBtn = const moveDownBtn =
Svg.down_ui().SetStyle('max-width: 1.5em; margin-left: 5px;display:block;') Svg.down_ui()
.onClick(() => { .SetClass('small-image') .onClick(() => {
const v = self._value.data[i]; const v = self._value.data[i];
self._value.data[i] = self._value.data[i + 1]; self._value.data[i] = self._value.data[i + 1];
self._value.data[i + 1] = v; self._value.data[i + 1] = v;
@ -98,7 +98,7 @@ export class MultiInput<T> extends InputElement<T[]> {
const deleteBtn = const deleteBtn =
Svg.delete_icon_ui().SetStyle('max-width: 1.5em;width:1.5em; margin-left: 5px;') Svg.delete_icon_ui().SetClass('small-image')
.onClick(() => { .onClick(() => {
self._value.data.splice(i, 1); self._value.data.splice(i, 1);
self._value.ping(); self._value.ping();

View file

@ -9,13 +9,16 @@ import {UIEventSource} from "../../Logic/UIEventSource";
import CombinedInputElement from "./CombinedInputElement"; import CombinedInputElement from "./CombinedInputElement";
import SimpleDatePicker from "./SimpleDatePicker"; import SimpleDatePicker from "./SimpleDatePicker";
import OpeningHoursInput from "./OpeningHours/OpeningHoursInput"; import OpeningHoursInput from "./OpeningHours/OpeningHoursInput";
import DirectionInput from "./DirectionInput";
interface TextFieldDef { interface TextFieldDef {
name: string, name: string,
explanation: string, explanation: string,
isValid: ((s: string, country?: string) => boolean), isValid: ((s: string, country?: string) => boolean),
reformat?: ((s: string, country?: string) => string), reformat?: ((s: string, country?: string) => string),
inputHelper?: (value: UIEventSource<string>) => InputElement<string>, inputHelper?: (value: UIEventSource<string>, options?: {
location: [number, number]
}) => InputElement<string>,
} }
export default class ValidatedTextField { export default class ValidatedTextField {
@ -25,7 +28,9 @@ export default class ValidatedTextField {
explanation: string, explanation: string,
isValid?: ((s: string, country?: string) => boolean), isValid?: ((s: string, country?: string) => boolean),
reformat?: ((s: string, country?: string) => string), reformat?: ((s: string, country?: string) => string),
inputHelper?: (value: UIEventSource<string>) => InputElement<string>): TextFieldDef { inputHelper?: (value: UIEventSource<string>, options?:{
location: [number, number]
}) => InputElement<string>): TextFieldDef {
if (isValid === undefined) { if (isValid === undefined) {
isValid = () => true; isValid = () => true;
@ -97,6 +102,17 @@ export default class ValidatedTextField {
str = "" + str; str = "" + str;
return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) > 0 return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) > 0
}), }),
ValidatedTextField.tp(
"direction",
"A geographical direction, in degrees. 0° is north, 90° is east, ... Will return a value between 0 (incl) and 360 (excl)",
(str) => {
str = "" + str;
return str !== undefined && str.indexOf(".") < 0 && !isNaN(Number(str)) && Number(str) >= 0 && Number(str) <= 360
},str => str,
(value) => {
return new DirectionInput(value);
}
),
ValidatedTextField.tp( ValidatedTextField.tp(
"float", "float",
"A decimal", "A decimal",
@ -185,7 +201,8 @@ export default class ValidatedTextField {
textArea?: boolean, textArea?: boolean,
textAreaRows?: number, textAreaRows?: number,
isValid?: ((s: string, country: string) => boolean), isValid?: ((s: string, country: string) => boolean),
country?: string country?: string,
location?: [number /*lat*/, number /*lon*/]
}): InputElement<string> { }): InputElement<string> {
options = options ?? {}; options = options ?? {};
options.placeholder = options.placeholder ?? type; options.placeholder = options.placeholder ?? type;
@ -218,7 +235,9 @@ export default class ValidatedTextField {
} }
if (tp.inputHelper) { if (tp.inputHelper) {
input = new CombinedInputElement(input, tp.inputHelper(input.GetValue())); input = new CombinedInputElement(input, tp.inputHelper(input.GetValue(),{
location: options.location
}));
} }
return input; return input;
} }

View file

@ -27,24 +27,27 @@ export default class EditableTagRendering extends UIElement {
this.ListenTo(this._editMode); this.ListenTo(this._editMode);
this.ListenTo(State.state?.osmConnection?.userDetails) this.ListenTo(State.state?.osmConnection?.userDetails)
const self = this;
this._answer = new TagRenderingAnswer(tags, configuration); this._answer = new TagRenderingAnswer(tags, configuration);
this._answer.SetStyle("width:100%;") this._answer.SetStyle("width:100%;")
this._question = this.GenerateQuestion();
this.dumbMode = false;
if (this._configuration.question !== undefined) { if (this._configuration.question !== undefined) {
// 2.3em total width if (State.state.featureSwitchUserbadge.data) {
if(State.state.featureSwitchUserbadge.data){ // 2.3em total width
const self = this;
this._editButton = this._editButton =
Svg.pencil_ui().SetClass("edit-button") Svg.pencil_svg().SetClass("edit-button")
.onClick(() => { .onClick(() => {
self._editMode.setData(true); self._editMode.setData(true);
}); });
} }
}
}
private GenerateQuestion() {
const self = this;
if (this._configuration.question !== undefined) {
// And at last, set up the skip button // And at last, set up the skip button
const cancelbutton = const cancelbutton =
Translations.t.general.cancel.Clone() Translations.t.general.cancel.Clone()
@ -53,7 +56,7 @@ export default class EditableTagRendering extends UIElement {
self._editMode.setData(false) self._editMode.setData(false)
}); });
this._question = new TagRenderingQuestion(tags, configuration, return new TagRenderingQuestion(this._tags, this._configuration,
() => { () => {
self._editMode.setData(false) self._editMode.setData(false)
}, },

View file

@ -21,15 +21,16 @@ export class FeatureInfoBox extends UIElement {
layerConfig: LayerConfig layerConfig: LayerConfig
) { ) {
super(); super();
if(layerConfig === undefined){ if (layerConfig === undefined) {
throw "Undefined layerconfig" throw "Undefined layerconfig"
} }
this._tags = tags; this._tags = tags;
this._layerConfig = layerConfig; this._layerConfig = layerConfig;
this._title = new TagRenderingAnswer(tags, layerConfig.title) this._title = layerConfig.title === undefined ? undefined :
.SetClass("featureinfobox-title"); new TagRenderingAnswer(tags, layerConfig.title)
.SetClass("featureinfobox-title");
this._titleIcons = new Combine( this._titleIcons = new Combine(
layerConfig.titleIcons.map(icon => new TagRenderingAnswer(tags, icon))) layerConfig.titleIcons.map(icon => new TagRenderingAnswer(tags, icon)))
.SetClass("featureinfobox-icons"); .SetClass("featureinfobox-icons");
@ -37,7 +38,6 @@ export class FeatureInfoBox extends UIElement {
if (State.state.featureSwitchUserbadge.data) { if (State.state.featureSwitchUserbadge.data) {
this._questionBox = new QuestionBox(tags, layerConfig.tagRenderings); this._questionBox = new QuestionBox(tags, layerConfig.tagRenderings);
} }
} }
InnerRender(): string { InnerRender(): string {

View file

@ -15,19 +15,9 @@ export default class TagRenderingAnswer extends UIElement {
super(tags); super(tags);
this._tags = tags; this._tags = tags;
this._configuration = configuration; this._configuration = configuration;
const self = this; if(configuration === undefined){
tags.addCallbackAndRun(tags => { throw "Trying to generate a tagRenderingAnswer without configuration..."
if (tags === undefined) { }
self._content = undefined
return;
}
const tr = this._configuration.GetRenderValue(tags);
if (tr === undefined) {
self._content = undefined
return
}
self._content = new SubstitutedTranslation(tr, self._tags)
})
} }
InnerRender(): string { InnerRender(): string {
@ -36,9 +26,17 @@ export default class TagRenderingAnswer extends UIElement {
return ""; return "";
} }
} }
if(this._content === undefined){
const tags = this._tags.data;
if (tags === undefined) {
return ""; return "";
} }
const tr = this._configuration.GetRenderValue(tags);
if (tr === undefined) {
return "";
}
// Bit of a hack; remember that the fields are updated
this._content = new SubstitutedTranslation(tr, this._tags);
return this._content.Render(); return this._content.Render();
} }

View file

@ -118,7 +118,6 @@ export default class TagRenderingQuestion extends UIElement {
const inputEl = new InputElementMap<number[], TagsFilter>( const inputEl = new InputElementMap<number[], TagsFilter>(
checkBoxes, checkBoxes,
(t0, t1) => { (t0, t1) => {
console.log("IsEquiv?",t0, t1, t0?.isEquivalent(t1))
return t0?.isEquivalent(t1) ?? false return t0?.isEquivalent(t1) ?? false
}, },
(indices) => { (indices) => {
@ -162,8 +161,6 @@ export default class TagRenderingQuestion extends UIElement {
} }
} }
console.log(indices, freeformExtras);
if (freeformField) { if (freeformField) {
if (freeformExtras.length > 0) { if (freeformExtras.length > 0) {
freeformField.GetValue().setData(new Tag(this._configuration.freeform.key, freeformExtras.join(";"))); freeformField.GetValue().setData(new Tag(this._configuration.freeform.key, freeformExtras.join(";")));
@ -254,7 +251,8 @@ export default class TagRenderingQuestion extends UIElement {
const textField = ValidatedTextField.InputForType(this._configuration.freeform.type, { const textField = ValidatedTextField.InputForType(this._configuration.freeform.type, {
isValid: (str) => (str.length <= 255), isValid: (str) => (str.length <= 255),
country: this._tags.data._country country: this._tags.data._country,
location: [this._tags.data._lat, this._tags.data._lon]
}); });
textField.GetValue().setData(this._tags.data[this._configuration.freeform.key]); textField.GetValue().setData(this._tags.data[this._configuration.freeform.key]);

View file

@ -32,9 +32,17 @@ export class ShareScreen extends UIElement {
const optionCheckboxes: UIElement[] = [] const optionCheckboxes: UIElement[] = []
const optionParts: (UIEventSource<string>)[] = []; const optionParts: (UIEventSource<string>)[] = [];
function check() {
return Svg.checkmark_svg().SetStyle("width: 1.5em; display:inline-block;");
}
function nocheck() {
return Svg.no_checkmark_svg().SetStyle("width: 1.5em; display: inline-block;");
}
const includeLocation = new CheckBox( const includeLocation = new CheckBox(
new Combine([Svg.checkmark, tr.fsIncludeCurrentLocation]), new Combine([check(), tr.fsIncludeCurrentLocation]),
new Combine([Svg.no_checkmark, tr.fsIncludeCurrentLocation]), new Combine([nocheck(), tr.fsIncludeCurrentLocation]),
true true
) )
optionCheckboxes.push(includeLocation); optionCheckboxes.push(includeLocation);
@ -68,8 +76,8 @@ export class ShareScreen extends UIElement {
return tr.fsIncludeCurrentBackgroundMap.Subs({name: layer?.name ?? ""}).Render(); return tr.fsIncludeCurrentBackgroundMap.Subs({name: layer?.name ?? ""}).Render();
})); }));
const includeCurrentBackground = new CheckBox( const includeCurrentBackground = new CheckBox(
new Combine([Svg.checkmark, currentBackground]), new Combine([check(), currentBackground]),
new Combine([Svg.no_checkmark, currentBackground]), new Combine([nocheck(), currentBackground]),
true true
) )
optionCheckboxes.push(includeCurrentBackground); optionCheckboxes.push(includeCurrentBackground);
@ -83,8 +91,8 @@ export class ShareScreen extends UIElement {
const includeLayerChoices = new CheckBox( const includeLayerChoices = new CheckBox(
new Combine([Svg.checkmark, tr.fsIncludeCurrentLayers]), new Combine([check(), tr.fsIncludeCurrentLayers]),
new Combine([Svg.no_checkmark, tr.fsIncludeCurrentLayers]), new Combine([nocheck(), tr.fsIncludeCurrentLayers]),
true true
) )
optionCheckboxes.push(includeLayerChoices); optionCheckboxes.push(includeLayerChoices);
@ -113,8 +121,8 @@ export class ShareScreen extends UIElement {
for (const swtch of switches) { for (const swtch of switches) {
const checkbox = new CheckBox( const checkbox = new CheckBox(
new Combine([Svg.checkmark, Translations.W(swtch.human)]), new Combine([check(), Translations.W(swtch.human)]),
new Combine([Svg.no_checkmark, Translations.W(swtch.human)]), !swtch.reverse new Combine([nocheck(), Translations.W(swtch.human)]), !swtch.reverse
); );
optionCheckboxes.push(checkbox); optionCheckboxes.push(checkbox);
optionParts.push(checkbox.isEnabled.map((isEn) => { optionParts.push(checkbox.isEnabled.map((isEn) => {

View file

@ -8,7 +8,6 @@ import Locale from "./i18n/Locale";
import State from "../State"; import State from "../State";
import {UIEventSource} from "../Logic/UIEventSource"; import {UIEventSource} from "../Logic/UIEventSource";
import {Img} from "./Img";
import Svg from "../Svg"; import Svg from "../Svg";
/** /**
@ -115,8 +114,8 @@ export class SimpleAddUI extends UIElement {
const loc = State.state.bm.LastClickLocation.data; const loc = State.state.bm.LastClickLocation.data;
let feature = State.state.changes.createElement(tags, loc.lat, loc.lon); let feature = State.state.changes.createElement(tags, loc.lat, loc.lon);
State.state.selectedElement.setData(feature);
layerToAddTo.AddNewElement(feature); layerToAddTo.AddNewElement(feature);
State.state.selectedElement.setData({feature: feature});
} }
} }

View file

@ -39,12 +39,16 @@ export class SubstitutedTranslation extends UIElement {
return [] return []
} }
const tags = this.tags.data; const tags = this.tags.data;
txt = SubstitutedTranslation.SubstituteKeys(txt, tags);
return this.EvaluateSpecialComponents(txt);
}
public static SubstituteKeys(txt: string, tags: any) {
for (const key in tags) { for (const key in tags) {
// Poor mans replace all // Poor mans replace all
txt = txt.split("{" + key + "}").join(tags[key]); txt = txt.split("{" + key + "}").join(tags[key]);
} }
return txt;
return this.EvaluateSpecialComponents(txt);
} }
private EvaluateSpecialComponents(template: string): UIElement[] { private EvaluateSpecialComponents(template: string): UIElement[] {
@ -98,7 +102,20 @@ export default class SpecialVisualizations {
args: { name: string, defaultValue?: string, doc: string }[] args: { name: string, defaultValue?: string, doc: string }[]
}[] = }[] =
[ [{
funcName: "all_tags",
docs: "Prints all key-value pairs of the object - used for debugging",
args: [],
constr: ((tags: UIEventSource<any>) => {
return new VariableUiElement(tags.map(tags => {
const parts = [];
for (const key in tags) {
parts.push(key + "=" + tags[key]);
}
return parts.join("<br/>")
})).SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;")
})
},
{ {
funcName: "image_carousel", funcName: "image_carousel",
docs: "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)", docs: "Creates an image carousel for the given sources. An attempt will be made to guess what source is used. Supported: Wikidata identifiers, Wikipedia pages, Wikimedia categories, IMGUR (with attribution, direct links)",
@ -167,20 +184,6 @@ export default class SpecialVisualizations {
} }
}, },
{
funcName: "all_tags",
docs: "Prints all key-value pairs of the object - used for debugging",
args:[],
constr: ((tags: UIEventSource<any>) => {
return new VariableUiElement(tags.map(tags => {
const parts = [];
for (const key in tags) {
parts.push(key+"="+tags[key]);
}
return parts.join("<br/>")
})).SetStyle("border: 1px solid black; border-radius: 1em;padding:1em;display:block;")
})
}
] ]
static HelpMessage: UIElement = SpecialVisualizations.GenHelpMessage(); static HelpMessage: UIElement = SpecialVisualizations.GenHelpMessage();

View file

@ -34,7 +34,7 @@ export class UserBadge extends UIElement {
.SetClass("userbadge-login") .SetClass("userbadge-login")
.onClick(() => State.state.osmConnection.AttemptLogin()); .onClick(() => State.state.osmConnection.AttemptLogin());
this._logout = this._logout =
Svg.logout_ui() Svg.logout_svg()
.onClick(() => { .onClick(() => {
State.state.osmConnection.LogOut(); State.state.osmConnection.LogOut();
}); });
@ -52,7 +52,7 @@ export class UserBadge extends UIElement {
this._homeButton = new VariableUiElement( this._homeButton = new VariableUiElement(
this._userDetails.map((userinfo) => { this._userDetails.map((userinfo) => {
if (userinfo.home) { if (userinfo.home) {
return Svg.home_img; return Svg.home;
} }
return ""; return "";
}) })
@ -75,7 +75,7 @@ export class UserBadge extends UIElement {
let messageSpan: UIElement = let messageSpan: UIElement =
new Link( new Link(
new Combine([Svg.envelope_img, "" + user.totalMessages]), new Combine([Svg.envelope, "" + user.totalMessages]),
'https://www.openstreetmap.org/messages/inbox', 'https://www.openstreetmap.org/messages/inbox',
true true
) )
@ -83,7 +83,7 @@ export class UserBadge extends UIElement {
if (user.unreadMessages > 0) { if (user.unreadMessages > 0) {
messageSpan = new Link( messageSpan = new Link(
new Combine([Svg.envelope_img, "" + user.unreadMessages]), new Combine([Svg.envelope, "" + user.unreadMessages]),
'https://www.openstreetmap.org/messages/inbox', 'https://www.openstreetmap.org/messages/inbox',
true true
).SetClass("alert") ).SetClass("alert")
@ -94,17 +94,8 @@ export class UserBadge extends UIElement {
dryrun = new FixedUiElement("TESTING").SetClass("alert"); dryrun = new FixedUiElement("TESTING").SetClass("alert");
} }
if (user.home !== undefined) {
const icon = L.icon({
iconUrl: Img.AsData(Svg.home),
iconSize: [20, 20],
iconAnchor: [10, 10]
});
L.marker([user.home.lat, user.home.lon], {icon: icon}).addTo(State.state.bm.map);
}
const settings = const settings =
new Link(Svg.gear_ui(), new Link(Svg.gear_svg(),
`https://www.openstreetmap.org/user/${encodeURIComponent(user.name)}/account`, `https://www.openstreetmap.org/user/${encodeURIComponent(user.name)}/account`,
true) true)
@ -124,7 +115,7 @@ export class UserBadge extends UIElement {
const csCount = const csCount =
new Link( new Link(
new Combine([Svg.star_img, "" + user.csCount]), new Combine([Svg.star, "" + user.csCount]),
`https://www.openstreetmap.org/user/${user.name}/history`, `https://www.openstreetmap.org/user/${user.name}/history`,
true); true);

View file

@ -24,11 +24,12 @@ export class WelcomeMessage extends UIElement {
this.description = new Combine([ this.description = new Combine([
"<h3>", layout.title, "</h3>", "<h3>", layout.title, "</h3>",
layout.description layout.description
]) ])
layout.descriptionTail layout.descriptionTail
this.plzLogIn = this.plzLogIn =
Translations.t.general.loginWithOpenStreetMap Translations.t.general.loginWithOpenStreetMap
.onClick(() => { .onClick(() => {

View file

@ -3,6 +3,7 @@ import * as $ from "jquery"
export class Utils { export class Utils {
public static readonly assets_path = "./assets/svg/";
static EncodeXmlValue(str) { static EncodeXmlValue(str) {
return str.replace(/&/g, '&amp;') return str.replace(/&/g, '&amp;')
@ -155,4 +156,18 @@ export class Utils {
} }
public static LoadCustomCss(location: string){
var head = document.getElementsByTagName('head')[0];
var link = document.createElement('link');
link.id = "customCss";
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = location;
link.media = 'all';
head.appendChild(link);
console.log("Added custom layout ",location)
}
} }

View file

@ -0,0 +1,32 @@
{
"id": "direction",
"name": {
"en": "Direction visualization"
},
"minzoom": 16,
"overpassTags": {
"or": ["camera:direction~*","direction~*"]
},
"doNotDownload": true,
"passAllFeatures": true,
"title": null,
"description": {
"en": "This layer visualizes directions"
},
"tagRenderings": [],
"icon": "./assets/svg/direction_gradient.svg",
"rotation": {
"render": "{camera:direction}deg",
"mappings": [
{
"if": "direction~*",
"then": "{direction}deg"
}
]
},
"iconSize": "200,200,center",
"color": "--catch-detail-color",
"stroke": "0",
"presets": [],
"wayHandling": 2
}

View file

@ -43,7 +43,6 @@
} }
], ],
"tagRenderings": [ "tagRenderings": [
"images",
{ {
"render": { "render": {
"en": "A <b>ghost bike</b> is a memorial for a cyclist who died in a traffic accident, in the form of a white bicycle placed permanently near the accident location.", "en": "A <b>ghost bike</b> is a memorial for a cyclist who died in a traffic accident, in the form of a white bicycle placed permanently near the accident location.",

View file

@ -0,0 +1,379 @@
{
"id": "surveillance_cameras",
"name": {
"en": "Surveillance camera's",
"nl": "Bewakingscamera's"
},
"minzoom": 12,
"overpassTags": {
"and": [
"man_made=surveillance",
{
"or": [
"surveillance:type=camera",
"surveillance:type=ALPR",
"surveillance:type=ANPR"
]
}
]
},
"title": {
"render": {
"en": "Surveillance Camera",
"nl": "Bewakingscamera"
}
},
"description": {},
"tagRenderings": [
"images",
{
"#": "Camera type: fixed; panning; dome",
"question": {
"en": "What kind of camera is this?",
"nl": "Wat voor soort camera is dit?"
},
"mappings": [
{
"if": {
"and": [
"camera:type=fixed"
]
},
"then": {
"en": "A fixed (non-moving) camera",
"nl": "Een vaste camera"
}
},
{
"if": {
"and": [
"camera:type=dome"
]
},
"then": {
"en": "A dome camera (which can turn)",
"nl": "Een dome (bolvormige camera die kan draaien)"
}
},
{
"if": {
"and": [
"camera:type=panning"
]
},
"then": {
"en": "A panning camera",
"nl": "Een camera die (met een motor) van links naar rechts kan draaien"
}
}
]
},
{
"#": "direction. We don't ask this for a dome on a pole or ceiling as it has a 360° view",
"question": {
"en": "In which geographical direction does this camera film?",
"nl": "Naar welke geografische richting filmt deze camera?"
},
"render": "Films to {camera:direction}",
"condition": {
"or": [
"camera:type!=dome",
{
"and": [
"camera:mount!=ceiling",
"camera:mount!=pole"
]
}
]
},
"freeform": {
"key": "camera:direction",
"type": "direction"
}
},
{
"#": "Operator",
"freeform": {
"key": "operator"
},
"question": {
"en": "Who operates this CCTV?",
"nl": "Wie beheert deze bewakingscamera?"
},
"render": {
"en": "Operated by {operator}",
"nl": "Beheer door {operator}"
}
},
{
"#": "Surveillance type: public, outdoor, indoor",
"question": {
"en": "What kind of surveillance is this camera",
"nl": "Wat soort bewaking wordt hier uitgevoerd?"
},
"mappings": [
{
"if": {
"and": [
"surveillance=public"
]
},
"then": {
"en": "A public area is surveilled, such as a street, a bridge, a square, a park, a train station, a public corridor or tunnel,...",
"nl": "Bewaking van de publieke ruilmte, dus een straat, een brug, een park, een plein, een stationsgebouw, een publiek toegankelijke gang of tunnel..."
}
},
{
"if": {
"and": [
"surveillance=outdoor"
]
},
"then": {
"en": "An outdoor, yet private area is surveilled (e.g. a parking lot, a fuel station, courtyard, entrance, private driveway, ...)",
"nl": "Een buitenruimte met privaat karakter (zoals een privé-oprit, een parking, tankstation, ...)"
}
},
{
"if": {
"and": [
"surveillance=indoor"
]
},
"then": {
"nl": "Een private binnenruimte wordt bewaakt, bv. een winkel, een parkeergarage, ...",
"en": "A private indoor area is surveilled, e.g. a shop, a private underground parking, ..."
}
}
]
},
{
"#": "Indoor camera? This isn't clear for 'public'-cameras",
"question": {
"en": "Is the public space surveilled by this camera an indoor or outdoor space?",
"nl": "Bevindt de bewaakte publieke ruimte camera zich binnen of buiten?"
},
"condition": {
"and": [
"surveillance:type=public"
]
},
"mappings": [
{
"if": "indoor=yes",
"then": {
"en": "This camera is located indoors",
"nl": "Deze camera bevindt zich binnen"
}
},
{
"if": "indoor=no",
"then": {
"en": "This camera is located outdoors",
"nl": "Deze camera bevindt zich buiten"
}
},
{
"if": "indoor=",
"then": {
"en": "This camera is probably located outdoors",
"nl": "Deze camera bevindt zich waarschijnlijk buiten"
},
"hideInAnswer": true
}
]
},
{
"#": "Level",
"question": {
"en": "On which level is this camera located?",
"nl": "Op welke verdieping bevindt deze camera zich?"
},
"freeform": {
"key": "level",
"type": "nat"
},
"condition": {
"or": [
"indoor=yes",
"surveillance:type=ye"
]
}
},
{
"#": "Surveillance:zone",
"question": {
"en": "What exactly is surveilled here?",
"nl": "Wat wordt hier precies bewaakt?"
},
"freeform": {
"key": "surveillance:zone"
},
"render": {
"en": " Surveills a {surveillance:zone}",
"nl": "Bewaakt een {surveillance:zone}"
},
"mappings": [
{
"if": {
"and": [
"surveillance:zone=parking"
]
},
"then": {
"en": "Surveills a parking",
"nl": "Bewaakt een parking"
}
},
{
"if": {
"and": [
"surveillance:zone=traffic"
]
},
"then": {
"en": "Surveills the traffic",
"nl": "Bewaakt het verkeer"
}
},
{
"if": {
"and": [
"surveillance:zone=entrance"
]
},
"then": {
"en": "Surveills an entrance",
"nl": "Bewaakt een ingang"
}
},
{
"if": {
"and": [
"surveillance:zone=corridor"
]
},
"then": {
"en": "Surveills a corridor",
"nl": "Bewaakt een gang"
}
},
{
"if": {
"and": [
"surveillance:zone=public_transport_platform"
]
},
"then": {
"en": "Surveills a public tranport platform",
"nl": "Bewaakt een perron of bushalte"
}
},
{
"if": {
"and": [
"surveillance:zone=shop"
]
},
"then": {
"en": "Surveills a shop",
"nl": "Bewaakt een winkel"
}
}
]
},
{
"#": "camera:mount",
"question": {
"en": "How is this camera placed?",
"nl": "Hoe is deze camera geplaatst?"
},
"freeform": {
"key": "camera:mount"
},
"mappings": [
{
"if": "camera:mount=wall",
"then": {
"en": "This camera is placed against a wall",
"nl": "Deze camera hangt aan een muur"
}
},
{
"if": "camera:mount=pole",
"then": {
"en": "This camera is placed one a pole",
"nl": "Deze camera staat op een paal"
}
},
{
"if": "camera:mount=ceiling",
"then": {
"en": "This camera is placed on the ceiling",
"nl": "Deze camera hangt aan het plafond"
}
}
]
}
],
"hideUnderlayingFeaturesMinPercentage": 0,
"icon": {
"render": "./assets/themes/surveillance_cameras/logo.svg",
"mappings": [
{
"if": "camera:type=dome",
"then": "./assets/themes/surveillance_cameras/dome.svg"
},
{
"if": "_direction:leftright=right",
"then": "./assets/themes/surveillance_cameras/cam_right.svg"
},
{
"if": "_direction:leftright=left",
"then": "./assets/themes/surveillance_cameras/cam_left.svg"
}
]
},
"rotation": {
"render": "calc({camera:direction}deg + 90deg)",
"mappings": [
{
"if": "camera:type=dome",
"then": "0"
},
{
"if": "_direction:leftright=right",
"then": "calc({camera:direction}deg - 90deg)"
}
]
},
"width": {
"render": "8"
},
"iconSize": {
"mappings": [
{
"if": "camera:type=dome",
"then": "50,50,center"
},
{
"if": "_direction:leftright~*",
"then": "100,35,center"
}
],
"render": "50,50,center"
},
"color": {
"render": "#f00"
},
"presets": [
{
"tags": [
"man_made=surveillance",
"surveillance:type=camera"
],
"title": "Surveillance camera"
}
],
"wayHandling": 2
}

View file

@ -4,7 +4,7 @@
}, },
"osmlink": { "osmlink": {
"render": "<a href='https://openstreetmap.org/{id}' target='_blank'><img src='data:image/svg+xml;base64,PHN2ZyBjbGFzcz0nb3NtLWxvZ28nIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgaGVpZ2h0PSIxMDBweCIgd2lkdGg9IjEwMHB4IiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA2NiA2NCI+CiAgICA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMC44NDksIC02MSkiIGZpbGw9IiM3ZWJjNmYiPgogICAgICAgIDxwYXRoIGQ9Ik0wLjg0OSw2MSBMNi40MTQsNzUuNjA5IEwwLjg0OSw5MC4yMTcgTDYuNDE0LDEwNC44MjYgTDAuODQ5LDExOS40MzUgTDQuMjY2LDEyMC43MzkgTDIyLjgzMSwxMDIuMTgzIEwyNi4xNjIsMTAyLjY5NiBMMzAuMjA1LDk4LjY1MiBDMjcuODE5LDk1Ljg4OCAyNi4wMzMsOTIuNTkgMjUuMDU3LDg4Ljk0OCBMMjYuOTUzLDg3LjM5MSBDMjYuNjI3LDg1Ljg3OSAyNi40NDksODQuMzEzIDI2LjQ0OSw4Mi43MDQgQzI2LjQ0OSw3NC42NyAzMC43MzQsNjcuNjExIDM3LjEzNiw2My42OTYgTDMwLjA2Niw2MSBMMTUuNDU3LDY2LjU2NSBMMC44NDksNjEgeiIvPgogICAgICAgIDxwYXRoIGQ9Ik00OC43MSw2NC42MTcgQzQ4LjQwNiw2NC42MTcgNDguMTA1LDY0LjYyOSA0Ny44MDUsNjQuNjQzIEM0Ny41Miw2NC42NTcgNDcuMjM0LDY0LjY3NyA0Ni45NTMsNjQuNzA0IEM0Ni43MjYsNjQuNzI2IDQ2LjQ5OSw2NC43NTMgNDYuMjc1LDY0Ljc4MyBDNDYuMDM5LDY0LjgxNCA0NS44MTEsNjQuODQ3IDQ1LjU3OSw2NC44ODcgQzQ1LjUwNiw2NC45IDQ1LjQzNCw2NC45MTcgNDUuMzYyLDY0LjkzIEM0NS4yMTYsNjQuOTU4IDQ1LjA3Miw2NC45ODcgNDQuOTI3LDY1LjAxNyBDNDQuODEyLDY1LjA0MiA0NC42OTQsNjUuMDYgNDQuNTc5LDY1LjA4NyBDNDQuNDQyLDY1LjExOSA0NC4zMDcsNjUuMTU2IDQ0LjE3LDY1LjE5MSBDNDMuOTQzLDY1LjI1IDQzLjcxNiw2NS4zMTUgNDMuNDkyLDY1LjM4MyBDNDMuMzIzLDY1LjQzMyA0My4xNTUsNjUuNDg0IDQyLjk4OCw2NS41MzkgQzQyLjgxOSw2NS41OTUgNDIuNjUsNjUuNjUyIDQyLjQ4Myw2NS43MTMgQzQyLjQ3NSw2NS43MTYgNDIuNDY2LDY1LjcxOSA0Mi40NTcsNjUuNzIyIEMzNS44MTksNjguMTU4IDMxLjAyMiw3NC4zNjkgMzAuNjQ5LDgxLjc3NCBDMzAuNjMzLDgyLjA4MyAzMC42MjIsODIuMzkxIDMwLjYyMiw4Mi43MDQgQzMwLjYyMiw4My4wMTQgMzAuNjMxLDgzLjMyMSAzMC42NDksODMuNjI2IEMzMC42NDksODMuNjI5IDMwLjY0OCw4My42MzIgMzAuNjQ5LDgzLjYzNSBDMzAuNjYyLDgzLjg2MiAzMC42ODEsODQuMDg4IDMwLjcwMSw4NC4zMTMgQzMxLjQ2Niw5My4wMzcgMzguMzc3LDk5Ljk0OCA0Ny4xMDEsMTAwLjcxMyBDNDcuMzI2LDEwMC43MzMgNDcuNTUyLDEwMC43NTQgNDcuNzc5LDEwMC43NjUgQzQ3Ljc4MiwxMDAuNzY1IDQ3Ljc4NSwxMDAuNzY1IDQ3Ljc4OCwxMDAuNzY1IEM0OC4wOTMsMTAwLjc4MyA0OC4zOTksMTAwLjc5MSA0OC43MDksMTAwLjc5MSBDNTMuNjM5LDEwMC43OTEgNTguMDk2LDk4LjgzMyA2MS4zNTMsOTUuNjUyIEM2MS41MzIsOTUuNDc3IDYxLjcxMiw5NS4zMDQgNjEuODgzLDk1LjEyMiBDNjEuOTEzLDk1LjA5IDYxLjk0MSw5NS4wNTggNjEuOTcsOTUuMDI2IEM2MS45OCw5NS4wMTUgNjEuOTg3LDk1LjAwMiA2MS45OTYsOTQuOTkxIEM2Mi4xMzIsOTQuODQ1IDYyLjI2Niw5NC42OTggNjIuMzk2LDk0LjU0OCBDNjIuNDQ5LDk0LjQ4NyA2Mi41MDEsOTQuNDI2IDYyLjU1Myw5NC4zNjUgQzYyLjU5NCw5NC4zMTYgNjIuNjM0LDk0LjI2NyA2Mi42NzUsOTQuMjE3IEM2Mi44MjEsOTQuMDQgNjIuOTYxLDkzLjg2MSA2My4xMDEsOTMuNjc4IEM2My4yNzksOTMuNDQ0IDYzLjQ1Niw5My4xOTkgNjMuNjIyLDkyLjk1NiBDNjMuOTU2LDkyLjQ3MSA2NC4yNjcsOTEuOTcgNjQuNTUzLDkxLjQ1MiBDNjQuNjYxLDkxLjI1NyA2NC43NTcsOTEuMDYgNjQuODU3LDkwLjg2MSBDNjQuODksOTAuNzk2IDY0LjkzLDkwLjczNSA2NC45NjIsOTAuNjcgQzY0Ljk4LDkwLjYzMyA2NC45OTYsOTAuNTk0IDY1LjAxNCw5MC41NTYgQzY1LjEyNSw5MC4zMjQgNjUuMjM0LDkwLjA5IDY1LjMzNiw4OS44NTIgQzY1LjM0OSw4OS44MiA2NS4zNjUsODkuNzg5IDY1LjM3OSw4OS43NTYgQzY1LjQ4LDg5LjUxNyA2NS41NzUsODkuMjcxIDY1LjY2Niw4OS4wMjYgQzY1LjY3OCw4OC45OTQgNjUuNjg5LDg4Ljk2MiA2NS43MDEsODguOTMgQzY1Ljc5Miw4OC42NzkgNjUuODgxLDg4LjQzIDY1Ljk2Miw4OC4xNzQgQzY1Ljk3LDg4LjE0OCA2NS45OCw4OC4xMjIgNjUuOTg4LDg4LjA5NiBDNjYuMDY5LDg3LjgzMiA2Ni4xNDQsODcuNTY0IDY2LjIxNCw4Ny4yOTYgQzY2LjIxOSw4Ny4yNzUgNjYuMjI2LDg3LjI1NSA2Ni4yMzEsODcuMjM1IEM2Ni4zMDEsODYuOTYyIDY2LjM2NSw4Ni42ODYgNjYuNDIzLDg2LjQwOSBDNjYuNDI2LDg2LjM5MSA2Ni40MjgsODYuMzc0IDY2LjQzMSw4Ni4zNTYgQzY2LjQ0NSw4Ni4yOTEgNjYuNDUzLDg2LjIyMyA2Ni40NjYsODYuMTU2IEM2Ni41MTEsODUuOTI1IDY2LjU1Miw4NS42OTUgNjYuNTg4LDg1LjQ2MSBDNjYuNjMyLDg1LjE2OSA2Ni42NzEsODQuODc4IDY2LjcwMSw4NC41ODMgQzY2LjcwMSw4NC41NzQgNjYuNzAxLDg0LjU2NSA2Ni43MDEsODQuNTU2IEM2Ni43MzEsODQuMjU4IDY2Ljc1NSw4My45NTUgNjYuNzcsODMuNjUyIEM2Ni43Nyw4My42NDYgNjYuNzcsODMuNjQxIDY2Ljc3LDgzLjYzNSBDNjYuNzg2LDgzLjMyNiA2Ni43OTcsODMuMDE3IDY2Ljc5Nyw4Mi43MDQgQzY2Ljc5Nyw3Mi42OSA1OC43MjMsNjQuNjE3IDQ4LjcxLDY0LjYxNyB6Ii8+CiAgICAgICAgPHBhdGggZD0iTTYyLjkzNiw5OS44MDkgQzU5LjA3NCwxMDMuMDI4IDU0LjExNSwxMDQuOTY1IDQ4LjcxLDEwNC45NjUgQzQ3LjEwMSwxMDQuOTY1IDQ1LjUzNSwxMDQuNzg3IDQ0LjAyMywxMDQuNDYxIEw0Mi40NjYsMTA2LjM1NyBDMzkuMDA3LDEwNS40MyAzNS44NTUsMTAzLjc4MSAzMy4xNzksMTAxLjU3NCBMMjguOTk2LDEwNS43NjUgTDI5LjUxLDEwOC44NjEgTDEzLjk1MywxMjQuNDI2IEwxNS40NTcsMTI1IEwzMC4wNjYsMTE5LjQzNSBMNDQuNjc1LDEyNSBMNTkuMjgzLDExOS40MzUgTDY0Ljg0OSwxMDQuODI2IEw2Mi45MzYsOTkuODA5IHoiLz4KICAgIDwvZz4KPC9zdmc+'/></a>", "render": "<a href='https://openstreetmap.org/{id}' target='_blank'><img src='./assets/svg/osm-logo-us.svg'/></a>",
"mappings":[{ "mappings":[{
"if": "id~=-", "if": "id~=-",
"then": "<span class='alert'>Uploading...</alert>" "then": "<span class='alert'>Uploading...</alert>"

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> e<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#" xmlns:cc="http://creativecommons.org/ns#"

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

@ -65,12 +65,12 @@
<g <g
id="g844"> id="g844">
<path <path
style="fill:none;stroke:#fffff0;stroke-width:95.51803589;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="fill:none;stroke:#ffffff !important;stroke-width:95.51803589;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 1154.7797,1044.3443 h 389.1358" d="m 1154.7797,1044.3443 h 389.1358"
id="path821" id="path821"
inkscape:connector-curvature="0" /> inkscape:connector-curvature="0" />
<path <path
style="fill:none;stroke:#fffff0;stroke-width:95.51803589;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="fill:none;stroke:#ffffff !important;stroke-width:95.51803589;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 1349.9206,849.36269 V 1238.4985" d="M 1349.9206,849.36269 V 1238.4985"
id="path821-3" id="path821-3"
inkscape:connector-curvature="0" /> inkscape:connector-curvature="0" />

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -1 +1 @@
<svg width="26" height="18" viewBox="0 0 26 18" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 7.28571L10.8261 15L23 3" stroke="black" stroke-width="4" stroke-linejoin="round"/></svg> <svg width="26" height="18" viewBox="0 0 26 18" xmlns="http://www.w3.org/2000/svg"><path d="M3 7.28571L10.8261 15L23 3" stroke="black" stroke-width="4" stroke-linejoin="round" style="fill:none !important;"/></svg>

Before

Width:  |  Height:  |  Size: 195 B

After

Width:  |  Height:  |  Size: 214 B

199
assets/svg/compass.svg Normal file
View file

@ -0,0 +1,199 @@
<?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"
id="svg8"
version="1.1"
viewBox="0 0 100 100"
height="100"
width="100"
sodipodi:docname="compass.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<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="namedview6"
showgrid="false"
inkscape:zoom="4.72"
inkscape:cx="59.602211"
inkscape:cy="33.556025"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg8" />
<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" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6" />
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:27.00369644px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#f00000;fill-opacity:1;stroke:none;stroke-width:1;"
x="39.290302"
y="25.678265"
id="text818"><tspan
sodipodi:role="line"
id="tspan816"
x="39.290302"
y="25.678265"
style="stroke-width:1;fill:#f00000;fill-opacity:1;">N</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:26.37678909px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;"
x="41.584614"
y="90.136795"
id="text822"><tspan
sodipodi:role="line"
id="tspan820"
x="41.584614"
y="90.136795"
style="stroke-width:1;fill:#ffffff;fill-opacity:1;">S</tspan><tspan
sodipodi:role="line"
x="41.584614"
y="123.10778"
id="tspan824"
style="stroke-width:1;fill:#ffffff;fill-opacity:1;" /></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:26.37678909px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;"
x="75.969528"
y="59.416515"
id="text822-3"><tspan
sodipodi:role="line"
id="tspan820-6"
x="75.969528"
y="59.416515"
style="stroke-width:1;fill:#ffffff;fill-opacity:1;">E</tspan><tspan
sodipodi:role="line"
x="75.969528"
y="92.387505"
id="tspan824-7"
style="stroke-width:1;fill:#ffffff;fill-opacity:1;" /></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:23.66192627px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;"
x="9.1795397"
y="54.821461"
id="text822-5"
transform="scale(0.8970738,1.1147355)"><tspan
sodipodi:role="line"
id="tspan820-3"
x="9.1795397"
y="54.821461"
style="stroke-width:1;fill:#ffffff;fill-opacity:1;">W</tspan><tspan
sodipodi:role="line"
x="9.1795397"
y="84.398872"
id="tspan824-5"
style="stroke-width:1;fill:#ffffff;fill-opacity:1;" /></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:9.30213261px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;"
x="18.772408"
y="9.4910574"
id="text822-5-3"
transform="scale(0.89707379,1.1147355)"><tspan
sodipodi:role="line"
id="tspan820-3-6"
x="18.772408"
y="17.954838"
style="fill:#ffffff;fill-opacity:1;stroke-width:1;"></tspan><tspan
sodipodi:role="line"
x="18.772408"
y="29.582502"
style="fill:#ffffff;fill-opacity:1;stroke-width:1;"
id="tspan846">NW</tspan><tspan
sodipodi:role="line"
x="18.772408"
y="41.210167"
id="tspan824-5-7"
style="fill:#ffffff;fill-opacity:1;stroke-width:1;" /></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:9.30213261px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;"
x="20.798214"
y="50.509953"
id="text822-5-3-5"
transform="scale(0.89707379,1.1147355)"><tspan
sodipodi:role="line"
id="tspan820-3-6-3"
x="20.798214"
y="58.973732"
style="fill:#ffffff;fill-opacity:1;stroke-width:1;" /><tspan
sodipodi:role="line"
x="20.798214"
y="70.601395"
style="fill:#ffffff;fill-opacity:1;stroke-width:1;"
id="tspan846-5">SW</tspan><tspan
sodipodi:role="line"
x="20.798214"
y="82.229057"
id="tspan824-5-7-6"
style="fill:#ffffff;fill-opacity:1;stroke-width:1;" /></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:9.30213261px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;"
x="74.850243"
y="9.8078442"
id="text822-5-3-2"
transform="scale(0.89707379,1.1147355)"><tspan
sodipodi:role="line"
id="tspan820-3-6-9"
x="74.850243"
y="18.271624"
style="fill:#ffffff;fill-opacity:1;stroke-width:1;" /><tspan
sodipodi:role="line"
x="74.850243"
y="29.899288"
style="fill:#ffffff;fill-opacity:1;stroke-width:1;"
id="tspan846-1">NE</tspan><tspan
sodipodi:role="line"
x="74.850243"
y="41.526955"
id="tspan824-5-7-2"
style="fill:#ffffff;fill-opacity:1;stroke-width:1;" /></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;font-size:9.30213261px;line-height:1.25;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;"
x="76.876053"
y="50.826736"
id="text822-5-3-5-7"
transform="scale(0.89707379,1.1147355)"><tspan
sodipodi:role="line"
id="tspan820-3-6-3-0"
x="76.876053"
y="59.290516"
style="fill:#ffffff;fill-opacity:1;stroke-width:1;" /><tspan
sodipodi:role="line"
x="76.876053"
y="70.918182"
style="fill:#ffffff;fill-opacity:1;stroke-width:1;"
id="tspan846-5-9">SE</tspan><tspan
sodipodi:role="line"
x="76.876053"
y="82.545845"
id="tspan824-5-7-6-3"
style="fill:#ffffff;fill-opacity:1;stroke-width:1;" /></text>
</svg>

After

Width:  |  Height:  |  Size: 7.1 KiB

View file

@ -1,55 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg <svg
xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#" xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="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"
width="100"
height="100"
viewBox="0 0 26.458333 26.458334"
version="1.1"
id="svg8" id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)" version="1.1"
sodipodi:docname="crosshair.svg"> viewBox="0 0 26.458333 26.458334"
height="100"
width="100">
<defs <defs
id="defs2" /> id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2"
inkscape:cx="12.898245"
inkscape:cy="23.072799"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
showguides="true"
inkscape:guide-bbox="true"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1">
<sodipodi:guide
position="13.229167,23.859748"
orientation="1,0"
id="guide815"
inkscape:locked="false" />
<sodipodi:guide
position="14.944824,13.229167"
orientation="0,1"
id="guide817"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata <metadata
id="metadata5"> id="metadata5">
<rdf:RDF> <rdf:RDF>
@ -58,46 +20,40 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title /> <dc:title></dc:title>
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
<g <g
inkscape:label="Layer 1" transform="translate(0,-270.54165)"
inkscape:groupmode="layer" id="layer1">
id="layer1"
transform="translate(0,-270.54165)">
<circle <circle
style="fill:none;fill-opacity:1;stroke:#555555;stroke-width:2.64583335;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.98823529" r="8.8715391"
id="path815" cy="283.77081"
cx="13.16302" cx="13.16302"
cy="283.77081" id="path815"
r="8.8715391" /> style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:2.64583335;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.98823529" />
<path <path
style="fill:none;stroke:#555555;stroke-width:2.09723878;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529"
d="M 3.2841366,283.77082 H 1.0418969"
id="path817" id="path817"
inkscape:connector-curvature="0" /> d="M 3.2841366,283.77082 H 1.0418969"
style="fill:none;stroke:#000000;stroke-width:2.09723878;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529" />
<path <path
style="fill:none;stroke:#555555;stroke-width:2.11666679;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529"
d="M 25.405696,283.77082 H 23.286471"
id="path817-3" id="path817-3"
inkscape:connector-curvature="0" /> d="M 25.405696,283.77082 H 23.286471"
style="fill:none;stroke:#000000;stroke-width:2.11666679;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529" />
<path <path
style="fill:none;stroke:#555555;stroke-width:2.11666679;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529"
d="m 13.229167,295.9489 v -2.11763"
id="path817-3-6" id="path817-3-6"
inkscape:connector-curvature="0" /> d="m 13.229167,295.9489 v -2.11763"
style="fill:none;stroke:#000000;stroke-width:2.11666679;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529" />
<path <path
style="fill:none;stroke:#555555;stroke-width:2.11666668;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529"
d="m 13.229167,275.05759 v -3.44507"
id="path817-3-6-7" id="path817-3-6-7"
inkscape:connector-curvature="0" /> d="m 13.229167,275.05759 v -3.44507"
style="fill:none;stroke:#000000;stroke-width:2.11666668;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.98823529" />
<circle <circle
style="fill:#555555;fill-opacity:0.99004978;stroke:none;stroke-width:2.81138086;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" r="3.4070117"
id="path866"
cx="13.229166"
cy="283.77081" cy="283.77081"
r="3.4070117" /> cx="13.229166"
id="path866"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.81138086;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

31
assets/svg/direction.svg Normal file
View file

@ -0,0 +1,31 @@
<?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"
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" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6" />
<path
id="path821"
d="M 49.787737,49.857275 20.830626,9.2566092 C 35.979158,-2.144159 60.514289,-3.8195259 78.598237,9.0063685 Z"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 1,017 B

View file

@ -0,0 +1,92 @@
<?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: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"
width="100"
height="100"
viewBox="0 0 100 100"
version="1.1"
id="svg8"
sodipodi:docname="direction_gradient.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<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="namedview10"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="9.44"
inkscape:cx="69.372244"
inkscape:cy="85.073455"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg8">
<sodipodi:guide
position="50,117.79661"
orientation="1,0"
id="guide819"
inkscape:locked="false" />
<sodipodi:guide
position="57.627119,50"
orientation="0,1"
id="guide821"
inkscape:locked="false" />
</sodipodi:namedview>
<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" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6">
<linearGradient
id="linearGradient820">
<stop
id="innercolor"
offset="0"
style="stop-color:#000000;stop-opacity:1;" />
<stop
id="outercolor"
offset="1"
style="stop-color:#000000;stop-opacity:0" />
</linearGradient>
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.5439431,-0.01852438,0.02075364,1.7297431,-27.986574,-42.187244)"
r="28.883806"
fy="53.828533"
fx="49.787739"
cy="53.828533"
cx="49.787739"
id="radialGradient828"
xlink:href="#linearGradient820" />
</defs>
<path
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"
d="M 50,50 21.042889,9.3993342 C 36.191421,-2.001434 60.726552,-3.6768009 78.8105,9.1490935 Z"
id="path821"
inkscape:connector-curvature="0" />
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -1,4 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" width="114" height="114"> <svg
<path stroke="#000" stroke-width="7" fill="none" d="m7,20h98v72H7zl44,44q5,4 10,0l44-44M7,92l36-36m26,0 36,36"/> 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"
id="svg4"
version="1.1"
viewBox="0 0 114 114"
height="114"
width="114">
<metadata
id="metadata10">
<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>
<defs
id="defs8" />
<path
id="path817"
d="M 9.6696131,22.725039 42.932885,56.977838 c 0,0 12.343058,14.397206 26.130256,0 C 88.864049,36.30085 103.12276,22.224485 103.12276,22.224485"
style="fill:none;stroke:#000000;stroke-width:8.16401958;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path823"
d="M 9.9025424,22.944915 102.88983,22.461864 v 69.921611"
style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:1;stroke-miterlimit:4;stroke-dasharray:none" />
<path
id="path823-3"
d="M 102.88983,92.383475 9.902543,92.866526 V 22.944915"
style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path840"
d="M 15.059836,89.897056 52.138595,60.125694"
style="fill:none;stroke:#000000;stroke-width:2.88938379;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
<path
id="path840-7"
d="M 59.644597,61.331869 98.071505,89.49552"
style="fill:none;stroke:#000000;stroke-width:2.89490533;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</svg> </svg>

Before

Width:  |  Height:  |  Size: 224 B

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -1 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M16 12a4 4 0 11-8 0 4 4 0 018 0zm-1.5 0a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z"/><path fill-rule="evenodd" d="M12 1c-.268 0-.534.01-.797.028-.763.055-1.345.617-1.512 1.304l-.352 1.45c-.02.078-.09.172-.225.22a8.45 8.45 0 00-.728.303c-.13.06-.246.044-.315.002l-1.274-.776c-.604-.368-1.412-.354-1.99.147-.403.348-.78.726-1.129 1.128-.5.579-.515 1.387-.147 1.99l.776 1.275c.042.069.059.185-.002.315-.112.237-.213.48-.302.728-.05.135-.143.206-.221.225l-1.45.352c-.687.167-1.249.749-1.304 1.512a11.149 11.149 0 000 1.594c.055.763.617 1.345 1.304 1.512l1.45.352c.078.02.172.09.22.225.09.248.191.491.303.729.06.129.044.245.002.314l-.776 1.274c-.368.604-.354 1.412.147 1.99.348.403.726.78 1.128 1.129.579.5 1.387.515 1.99.147l1.275-.776c.069-.042.185-.059.315.002.237.112.48.213.728.302.135.05.206.143.225.221l.352 1.45c.167.687.749 1.249 1.512 1.303a11.125 11.125 0 001.594 0c.763-.054 1.345-.616 1.512-1.303l.352-1.45c.02-.078.09-.172.225-.22.248-.09.491-.191.729-.303.129-.06.245-.044.314-.002l1.274.776c.604.368 1.412.354 1.99-.147.403-.348.78-.726 1.129-1.128.5-.579.515-1.387.147-1.99l-.776-1.275c-.042-.069-.059-.185.002-.315.112-.237.213-.48.302-.728.05-.135.143-.206.221-.225l1.45-.352c.687-.167 1.249-.749 1.303-1.512a11.125 11.125 0 000-1.594c-.054-.763-.616-1.345-1.303-1.512l-1.45-.352c-.078-.02-.172-.09-.22-.225a8.469 8.469 0 00-.303-.728c-.06-.13-.044-.246-.002-.315l.776-1.274c.368-.604.354-1.412-.147-1.99-.348-.403-.726-.78-1.128-1.129-.579-.5-1.387-.515-1.99-.147l-1.275.776c-.069.042-.185.059-.315-.002a8.465 8.465 0 00-.728-.302c-.135-.05-.206-.143-.225-.221l-.352-1.45c-.167-.687-.749-1.249-1.512-1.304A11.149 11.149 0 0012 1zm-.69 1.525a9.648 9.648 0 011.38 0c.055.004.135.05.162.16l.351 1.45c.153.628.626 1.08 1.173 1.278.205.074.405.157.6.249a1.832 1.832 0 001.733-.074l1.275-.776c.097-.06.186-.036.228 0 .348.302.674.628.976.976.036.042.06.13 0 .228l-.776 1.274a1.832 1.832 0 00-.074 1.734c.092.195.175.395.248.6.198.547.652 1.02 1.278 1.172l1.45.353c.111.026.157.106.161.161a9.653 9.653 0 010 1.38c-.004.055-.05.135-.16.162l-1.45.351a1.833 1.833 0 00-1.278 1.173 6.926 6.926 0 01-.25.6 1.832 1.832 0 00.075 1.733l.776 1.275c.06.097.036.186 0 .228a9.555 9.555 0 01-.976.976c-.042.036-.13.06-.228 0l-1.275-.776a1.832 1.832 0 00-1.733-.074 6.926 6.926 0 01-.6.248 1.833 1.833 0 00-1.172 1.278l-.353 1.45c-.026.111-.106.157-.161.161a9.653 9.653 0 01-1.38 0c-.055-.004-.135-.05-.162-.16l-.351-1.45a1.833 1.833 0 00-1.173-1.278 6.928 6.928 0 01-.6-.25 1.832 1.832 0 00-1.734.075l-1.274.776c-.097.06-.186.036-.228 0a9.56 9.56 0 01-.976-.976c-.036-.042-.06-.13 0-.228l.776-1.275a1.832 1.832 0 00.074-1.733 6.948 6.948 0 01-.249-.6 1.833 1.833 0 00-1.277-1.172l-1.45-.353c-.111-.026-.157-.106-.161-.161a9.648 9.648 0 010-1.38c.004-.055.05-.135.16-.162l1.45-.351a1.833 1.833 0 001.278-1.173 6.95 6.95 0 01.249-.6 1.832 1.832 0 00-.074-1.734l-.776-1.274c-.06-.097-.036-.186 0-.228.302-.348.628-.674.976-.976.042-.036.13-.06.228 0l1.274.776a1.832 1.832 0 001.734.074 6.95 6.95 0 01.6-.249 1.833 1.833 0 001.172-1.277l.353-1.45c.026-.111.106-.157.161-.161z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill-rule="evenodd" d="M16 12a4 4 0 11-8 0 4 4 0 018 0zm-1.5 0a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z"/>
<path fill-rule="evenodd" d="M12 1c-.268 0-.534.01-.797.028-.763.055-1.345.617-1.512 1.304l-.352 1.45c-.02.078-.09.172-.225.22a8.45 8.45 0 00-.728.303c-.13.06-.246.044-.315.002l-1.274-.776c-.604-.368-1.412-.354-1.99.147-.403.348-.78.726-1.129 1.128-.5.579-.515 1.387-.147 1.99l.776 1.275c.042.069.059.185-.002.315-.112.237-.213.48-.302.728-.05.135-.143.206-.221.225l-1.45.352c-.687.167-1.249.749-1.304 1.512a11.149 11.149 0 000 1.594c.055.763.617 1.345 1.304 1.512l1.45.352c.078.02.172.09.22.225.09.248.191.491.303.729.06.129.044.245.002.314l-.776 1.274c-.368.604-.354 1.412.147 1.99.348.403.726.78 1.128 1.129.579.5 1.387.515 1.99.147l1.275-.776c.069-.042.185-.059.315.002.237.112.48.213.728.302.135.05.206.143.225.221l.352 1.45c.167.687.749 1.249 1.512 1.303a11.125 11.125 0 001.594 0c.763-.054 1.345-.616 1.512-1.303l.352-1.45c.02-.078.09-.172.225-.22.248-.09.491-.191.729-.303.129-.06.245-.044.314-.002l1.274.776c.604.368 1.412.354 1.99-.147.403-.348.78-.726 1.129-1.128.5-.579.515-1.387.147-1.99l-.776-1.275c-.042-.069-.059-.185.002-.315.112-.237.213-.48.302-.728.05-.135.143-.206.221-.225l1.45-.352c.687-.167 1.249-.749 1.303-1.512a11.125 11.125 0 000-1.594c-.054-.763-.616-1.345-1.303-1.512l-1.45-.352c-.078-.02-.172-.09-.22-.225a8.469 8.469 0 00-.303-.728c-.06-.13-.044-.246-.002-.315l.776-1.274c.368-.604.354-1.412-.147-1.99-.348-.403-.726-.78-1.128-1.129-.579-.5-1.387-.515-1.99-.147l-1.275.776c-.069.042-.185.059-.315-.002a8.465 8.465 0 00-.728-.302c-.135-.05-.206-.143-.225-.221l-.352-1.45c-.167-.687-.749-1.249-1.512-1.304A11.149 11.149 0 0012 1zm-.69 1.525a9.648 9.648 0 011.38 0c.055.004.135.05.162.16l.351 1.45c.153.628.626 1.08 1.173 1.278.205.074.405.157.6.249a1.832 1.832 0 001.733-.074l1.275-.776c.097-.06.186-.036.228 0 .348.302.674.628.976.976.036.042.06.13 0 .228l-.776 1.274a1.832 1.832 0 00-.074 1.734c.092.195.175.395.248.6.198.547.652 1.02 1.278 1.172l1.45.353c.111.026.157.106.161.161a9.653 9.653 0 010 1.38c-.004.055-.05.135-.16.162l-1.45.351a1.833 1.833 0 00-1.278 1.173 6.926 6.926 0 01-.25.6 1.832 1.832 0 00.075 1.733l.776 1.275c.06.097.036.186 0 .228a9.555 9.555 0 01-.976.976c-.042.036-.13.06-.228 0l-1.275-.776a1.832 1.832 0 00-1.733-.074 6.926 6.926 0 01-.6.248 1.833 1.833 0 00-1.172 1.278l-.353 1.45c-.026.111-.106.157-.161.161a9.653 9.653 0 01-1.38 0c-.055-.004-.135-.05-.162-.16l-.351-1.45a1.833 1.833 0 00-1.173-1.278 6.928 6.928 0 01-.6-.25 1.832 1.832 0 00-1.734.075l-1.274.776c-.097.06-.186.036-.228 0a9.56 9.56 0 01-.976-.976c-.036-.042-.06-.13 0-.228l.776-1.275a1.832 1.832 0 00.074-1.733 6.948 6.948 0 01-.249-.6 1.833 1.833 0 00-1.277-1.172l-1.45-.353c-.111-.026-.157-.106-.161-.161a9.648 9.648 0 010-1.38c.004-.055.05-.135.16-.162l1.45-.351a1.833 1.833 0 001.278-1.173 6.95 6.95 0 01.249-.6 1.832 1.832 0 00-.074-1.734l-.776-1.274c-.06-.097-.036-.186 0-.228.302-.348.628-.674.976-.976.042-.036.13-.06.228 0l1.274.776a1.832 1.832 0 001.734.074 6.95 6.95 0 01.6-.249 1.833 1.833 0 001.172-1.277l.353-1.45c.026-.111.106-.157.161-.161z"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -8,33 +8,21 @@
id="svg11382" id="svg11382"
height="900" height="900"
width="900" width="900"
viewBox="0 0 900 900"
version="1.0"> version="1.0">
<metadata
id="metadata10">
<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>
<defs
id="defs11384" />
<g <g
id="layer1" id="layer1"
transform="matrix(0.90103258,0,0,0.90103258,112.84058,-1.9060177)"> transform="matrix(0.90103258,0,0,0.90103258,112.84058,-1.9060177)"
>
<g <g
id="g11476"> id="g11476">
<path <path
id="path11472" id="path11472"
style="font-style:normal;font-weight:normal;font-size:1201.92492676px;font-family:'Bitstream Vera Sans';text-align:center;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" style="font-style:normal;font-weight:normal;font-size:1201.92492676px;font-family:'Bitstream Vera Sans';text-align:center;text-anchor:middle;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 474.50888,718.22841 H 303.49547 v -22.30134 c -2.4e-4,-37.95108 4.30352,-68.76211 12.9113,-92.43319 8.60728,-23.67032 23.63352,-45.28695 40.65324,-64.84996 17.01914,-19.56211 41.98734,-26.33264 101.45793,-75.63085 31.69095,-25.82203 55.2813,-77.1523 55.28175,-98.67174 2.21232,-56.92245 -13.93983,-79.3422 -34.56287,-99.96524 -22.67355,-19.67717 -60.67027,-30.06998 -90.99892,-30.06998 -27.77921,6.9e-4 -68.46735,8.08871 -87.7666,25.37047 -25.93817,17.28308 -65.23747,73.70611 -57.04687,130.54577 l -194.516943,1.70222 c 0,-157.21399 29.393699,-198.69465 99.004113,-263.03032 67.39739,-54.376643 126.53128,-73.268365 243.84757,-73.268365 89.71791,0 161.89728,17.80281 214.32552,53.405855 71.20714,48.12472 122.30105,111.18354 122.30105,230.11281 -6.9e-4,44.32081 -19.15253,90.78638 -43.0726,128.33299 -18.38947,30.90938 -60.37511,66.45236 -118.21237,104.41628 -42.83607,25.7686 -66.67196,53.11926 -77.03964,72.0946 -10.36863,18.97603 -15.55271,43.72267 -15.55225,74.23999 z" /> d="M 474.50888,718.22841 H 303.49547 v -22.30134 c -2.4e-4,-37.95108 4.30352,-68.76211 12.9113,-92.43319 8.60728,-23.67032 23.63352,-45.28695 40.65324,-64.84996 17.01914,-19.56211 41.98734,-26.33264 101.45793,-75.63085 31.69095,-25.82203 55.2813,-77.1523 55.28175,-98.67174 2.21232,-56.92245 -13.93983,-79.3422 -34.56287,-99.96524 -22.67355,-19.67717 -60.67027,-30.06998 -90.99892,-30.06998 -27.77921,6.9e-4 -68.46735,8.08871 -87.7666,25.37047 -25.93817,17.28308 -65.23747,73.70611 -57.04687,130.54577 l -194.516943,1.70222 c 0,-157.21399 29.393699,-198.69465 99.004113,-263.03032 67.39739,-54.376643 126.53128,-73.268365 243.84757,-73.268365 89.71791,0 161.89728,17.80281 214.32552,53.405855 71.20714,48.12472 122.30105,111.18354 122.30105,230.11281 -6.9e-4,44.32081 -19.15253,90.78638 -43.0726,128.33299 -18.38947,30.90938 -60.37511,66.45236 -118.21237,104.41628 -42.83607,25.7686 -66.67196,53.11926 -77.03964,72.0946 -10.36863,18.97603 -15.55271,43.72267 -15.55225,74.23999 z" />
<path <path
id="path11474" id="path11474"
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" style="fill-opacity:1;stroke:none;stroke-width:3;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
transform="translate(1.106383,-5.5319149)" transform="translate(1.106383,-5.5319149)"
d="m 482.38298,869.80902 a 94.042557,73.021278 0 1 1 -188.08511,0 94.042557,73.021278 0 1 1 188.08511,0 z" /> d="m 482.38298,869.80902 a 94.042557,73.021278 0 1 1 -188.08511,0 94.042557,73.021278 0 1 1 188.08511,0 z" />
</g> </g>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -1,3 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg height="16px" id="Layer_1" style="enable-background:new 0 0 16 16;" version="1.1" viewBox="0 0 16 16" width="16px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M15.45,7L14,5.551V2c0-0.55-0.45-1-1-1h-1c-0.55,0-1,0.45-1,1v0.553L9,0.555C8.727,0.297,8.477,0,8,0S7.273,0.297,7,0.555 L0.55,7C0.238,7.325,0,7.562,0,8c0,0.563,0.432,1,1,1h1v6c0,0.55,0.45,1,1,1h3v-5c0-0.55,0.45-1,1-1h2c0.55,0,1,0.45,1,1v5h3 c0.55,0,1-0.45,1-1V9h1c0.568,0,1-0.437,1-1C16,7.562,15.762,7.325,15.45,7z"/></svg> <svg height="16px" id="Layer_1" style="enable-background:new 0 0 16 16;fill: #000000;" version="1.1" viewBox="0 0 16 16" width="16px"
xml:space="preserve" xmlns="http://www.w3.org/2000/svg">
<path d="M15.45,7L14,5.551V2c0-0.55-0.45-1-1-1h-1c-0.55,0-1,0.45-1,1v0.553L9,0.555C8.727,0.297,8.477,0,8,0S7.273,0.297,7,0.555 L0.55,7C0.238,7.325,0,7.562,0,8c0,0.563,0.432,1,1,1h1v6c0,0.55,0.45,1,1,1h3v-5c0-0.55,0.45-1,1-1h2c0.55,0,1,0.45,1,1v5h3 c0.55,0,1-0.45,1-1V9h1c0.568,0,1-0.437,1-1C16,7.562,15.762,7.325,15.45,7z"/>
</svg>

Before

Width:  |  Height:  |  Size: 689 B

After

Width:  |  Height:  |  Size: 671 B

View file

@ -0,0 +1,29 @@
<?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"
xml:space="preserve"
width="16px"
viewBox="0 0 16 16"
version="1.1"
style="enable-background:new 0 0 16 16;fill: #000000;"
id="Layer_1"
height="16px"><metadata
id="metadata11"><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><defs
id="defs9" />
<circle
id="circle2"
fill="white"
r="8"
cy="8"
cx="8" />
<path
style="stroke-width:1"
id="path4"
d="m 13.429661,7.2711864 -1.05678,-1.0560508 v -2.588017 c 0,-0.4008474 -0.327966,-0.7288135 -0.728813,-0.7288135 h -0.728814 c -0.400847,0 -0.728813,0.3279661 -0.728813,0.7288135 V 4.0301525 L 8.7288135,2.573983 C 8.5298474,2.3859491 8.347644,2.1694915 8,2.1694915 c -0.3476441,0 -0.5298475,0.2164576 -0.7288136,0.4044915 L 2.570339,7.2711864 C 2.3429491,7.5080508 2.1694915,7.6807796 2.1694915,8 c 0,0.410322 0.3148475,0.7288135 0.7288136,0.7288135 h 0.7288135 v 4.3728815 c 0,0.400847 0.3279661,0.728813 0.7288136,0.728813 h 2.1864407 v -3.644067 c 0,-0.4008478 0.3279661,-0.7288139 0.7288135,-0.7288139 h 1.4576271 c 0.4008475,0 0.7288136,0.3279661 0.7288136,0.7288139 v 3.644067 h 2.1864409 c 0.400847,0 0.728813,-0.327966 0.728813,-0.728813 V 8.7288135 h 0.728814 c 0.413966,0 0.728813,-0.3184915 0.728813,-0.7288135 0,-0.3192204 -0.173457,-0.4919492 -0.400847,-0.7288136 z" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -1,3 +1,3 @@
<svg height="1024" width="896" xmlns="http://www.w3.org/2000/svg"> <svg height="1024" width="896" viewBox="0 0 896 1024" xmlns="http://www.w3.org/2000/svg">
<path d="M704 64L576 192l192 192 128-128L704 64zM0 768l0.688 192.562L192 960l512-512L512 256 0 768zM192 896H64V768h64v64h64V896z"/> <path d="M704 64L576 192l192 192 128-128L704 64zM0 768l0.688 192.562L192 960l512-512L512 256 0 768zM192 896H64V768h64v64h64V896z"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 207 B

After

Width:  |  Height:  |  Size: 230 B

View file

@ -73,25 +73,25 @@
id="layer1" id="layer1"
transform="translate(0,-270.54165)"> transform="translate(0,-270.54165)">
<path <path
style="fill:none;stroke:#000000;stroke-width:2.43863511;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" style="fill:none;stroke-width:2.43863511;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 19.212364,278.17517 -11.9689358,5.52059 11.9388628,5.50669" d="m 19.212364,278.17517 -11.9689358,5.52059 11.9388628,5.50669"
id="path819" id="path819"
inkscape:connector-curvature="0" inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" /> sodipodi:nodetypes="ccc" />
<circle <circle
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.53329796;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.97014926" style="fill-opacity:1;stroke:none;stroke-width:0.53329796;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.97014926"
id="path820" id="path820"
cx="7.2434282" cx="7.2434282"
cy="283.69574" cy="283.69574"
r="3.9119694" /> r="3.9119694" />
<circle <circle
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.53329796;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.97014926" style="fill-opacity:1;stroke:none;stroke-width:0.53329796;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.97014926"
id="path820-3" id="path820-3"
cx="19.48818" cx="19.48818"
cy="289.22873" cy="289.22873"
r="3.9119689" /> r="3.9119689" />
<circle <circle
style="fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.53329796;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.97014926" style="fill-opacity:1;stroke:none;stroke-width:0.53329796;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.97014926"
id="path820-3-6" id="path820-3-6"
cx="19.48818" cx="19.48818"
cy="277.56281" cy="277.56281"

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -190,6 +190,16 @@
"fr": "De quel matériau ce banc est-il fait ?" "fr": "De quel matériau ce banc est-il fait ?"
} }
}, },
{
"question": {
"en": "In which direction are you looking when sitting on the bench?"
},
"render": "When sitting on the bench, one looks towards {direction}*",
"freeform": {
"key": "direction",
"type": "direction"
}
},
{ {
"render": { "render": {
"en": "Colour: {colour}", "en": "Colour: {colour}",
@ -297,7 +307,7 @@
"render": "8" "render": "8"
}, },
"iconSize": { "iconSize": {
"render": "20,20,center" "render": "30,30,center"
}, },
"color": { "color": {
"render": "#00f" "render": "#00f"

View file

@ -11,7 +11,7 @@
"enableShareScreen": false, "enableShareScreen": false,
"enableMoreQuests": false, "enableMoreQuests": false,
"description": { "description": {
"nl": "<img style='float:right;margin: 1em;width: 10em;height: auto;' src='./assets/themes/buurtnatuur/groen_logo.svg' alt='logo-groen' class='logo'> <br /><b>Natuur maakt gelukkig.</b> Aan de hand van deze website willen we de natuur dicht bij ons beter inventariseren. Met als doel meer mensen te laten genieten van toegankelijke natuur én te strijden voor meer natuur in onze buurten. \n<ul><li>In welke natuurgebieden kan jij terecht? Hoe toegankelijk zijn ze?</li><li>In welke bossen kan een gezin in jouw gemeente opnieuw op adem komen?</li><li>Op welke onbekende plekjes is het zalig spelen?</li></ul><p>Samen kleuren we heel Vlaanderen en Brussel groen.</p>Blijf op de hoogte van de resultaten van buurtnatuur.be: <a href=\"https://www.groen.be/buurtnatuur\" target='_blank'>meld je aan voor e-mailupdates</a>." "nl": "<img style='float:right;margin: 1em;width: 10em;height: auto;' src='./assets/themes/buurtnatuur/groen_logo.svg' alt='logo-groen' class='logo/> <br /><b>Natuur maakt gelukkig.</b> Aan de hand van deze website willen we de natuur dicht bij ons beter inventariseren. Met als doel meer mensen te laten genieten van toegankelijke natuur én te strijden voor meer natuur in onze buurten.<ul><li>In welke natuurgebieden kan jij terecht? Hoe toegankelijk zijn ze?</li><li>In welke bossen kan een gezin in jouw gemeente opnieuw op adem komen?</li><li>Op welke onbekende plekjes is het zalig spelen?</li></ul><p>Samen kleuren we heel Vlaanderen en Brussel groen.Blijf op de hoogte van de resultaten van buurtnatuur.be: <a href='https://www.groen.be/buurtnatuur' target='_blank'>meld je aan voor e-mailupdates</a>."
}, },
"descriptionTail": { "descriptionTail": {
"nl": "<h4>Tips</h4><ul><li>Over groen ingekleurde gebieden weten we alles wat we willen weten.</li><li>Bij rood ingekleurde gebieden ontbreekt nog heel wat info: klik een gebied aan en beantwoord de vragen.</li><li>Je kan altijd een vraag overslaan als je het antwoord niet weet of niet zeker bent</li><li>Je kan altijd een foto toevoegen</li><li>Je kan ook zelf een gebied toevoegen door op de kaart te klikken</li><li>Open buurtnatuur.be <b>op je smartphone</b> om al wandelend foto's te maken en vragen te beantwoorden</li></ul><small><p>De oorspronkelijke data komt van <b>OpenStreetMap</b> en je antwoorden worden daar bewaard.<br/> Omdat iedereen vrij kan meewerken aan dit project, kunnen we niet garanderen dat er geen fouten opduiken.Kan je hier niet aanpassen wat je wilt, dan kan je dat zelf via OpenStreetMap.org doen. Groen kan <b>geen enkele verantwoordelijkheid</b> nemen over de kaart.</p>Je privacy is belangrijk. We tellen wel hoeveel gebruikers deze website bezoeken. We plaatsen een cookie waar geen persoonlijke informatie in bewaard wordt. Als je inlogt, komt er een tweede cookie bij met je inloggegevens.</small>" "nl": "<h4>Tips</h4><ul><li>Over groen ingekleurde gebieden weten we alles wat we willen weten.</li><li>Bij rood ingekleurde gebieden ontbreekt nog heel wat info: klik een gebied aan en beantwoord de vragen.</li><li>Je kan altijd een vraag overslaan als je het antwoord niet weet of niet zeker bent</li><li>Je kan altijd een foto toevoegen</li><li>Je kan ook zelf een gebied toevoegen door op de kaart te klikken</li><li>Open buurtnatuur.be <b>op je smartphone</b> om al wandelend foto's te maken en vragen te beantwoorden</li></ul><small><p>De oorspronkelijke data komt van <b>OpenStreetMap</b> en je antwoorden worden daar bewaard.<br/> Omdat iedereen vrij kan meewerken aan dit project, kunnen we niet garanderen dat er geen fouten opduiken.Kan je hier niet aanpassen wat je wilt, dan kan je dat zelf via OpenStreetMap.org doen. Groen kan <b>geen enkele verantwoordelijkheid</b> nemen over de kaart.</p>Je privacy is belangrijk. We tellen wel hoeveel gebruikers deze website bezoeken. We plaatsen een cookie waar geen persoonlijke informatie in bewaard wordt. Als je inlogt, komt er een tweede cookie bij met je inloggegevens.</small>"

View file

@ -0,0 +1,63 @@
<?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"
id="svg6"
version="1.1"
height="210"
width="600"
sodipodi:docname="cam.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<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="namedview6"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="0.55625733"
inkscape:cx="680.78346"
inkscape:cy="482.50413"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg6">
<sodipodi:guide
position="192.35701,251.68207"
orientation="1,0"
id="guide816"
inkscape:locked="false" />
</sodipodi:namedview>
<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" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<path
style="fill:#000000;stroke:#ffffff;stroke-width:17.40477371;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4"
d="m 275.84577,72.939087 31.07239,-1.014243 26.20685,70.688356 -54.3295,3.69054 z m 20.0366,-37.552344 269.15255,-4.09941 c 6.98913,0.09009 15.84901,3.554569 15.7875,16.336335 0,0 -2.99159,70.195112 -3.76963,103.307682 -0.27414,11.66795 -4.43254,15.57088 -12.5089,15.57422 l -214.63586,4.14367 z"
inkscape:connector-curvature="0" />
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -0,0 +1,68 @@
<?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"
id="svg6"
version="1.1"
height="210"
width="600"
sodipodi:docname="cam_right.svg"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)">
<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="namedview6"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="1.5733333"
inkscape:cx="159.31"
inkscape:cy="209.94521"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg6">
<sodipodi:guide
position="490.78006,256.1764"
orientation="1,0"
id="guide816"
inkscape:locked="false" />
<sodipodi:guide
position="-154.49235,105.0548"
orientation="0,1"
id="guide817"
inkscape:locked="false" />
</sodipodi:namedview>
<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" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10" />
<path
style="fill:#000000;stroke:#ffffff;stroke-width:14.99999809;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4"
d="m 320.67971,63.887724 -31.22595,-0.646766 -25.27602,71.641802 54.62643,3.06524 z M 299.99723,26.240468 29.583962,25.366424 c -7.018953,0.175611 -15.866995,3.778632 -15.615761,16.674621 0,0 4.045287,70.790225 5.317545,104.191185 0.448302,11.76958 4.683081,15.65722 12.795492,15.56269 l 215.654182,1.57947 z"
inkscape:connector-curvature="0" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,26 @@
html {
--subtle-detail-color: #2c2 !important;
--subtle-detail-color-contrast: white !important;
--popup-border: #00ff00 !important;
--catch-detail-color: #00ff00 !important;
--catch-detail-color-contrast: black !important;
--alert-color: #eb00ff !important;
--background-color: black !important;
--foreground-color: white !important;
--shadow-color: #0f0 !important;
}
#innercolor {
stop-color:#ff0000
}
.leaflet-div-icon svg {
width: calc(100% - 3px);
height: calc(100% + 3px);
}
/*
.leaflet-div-icon svg path {
fill: none !important;
stroke-width: 1px !important;
stroke: #0f0 !important;
}
*/

View file

@ -0,0 +1,78 @@
<?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: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" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs6">
<linearGradient
id="linearGradient823">
<stop
id="stop819"
offset="0"
style="stop-color:#55ff55;stop-opacity:1;" />
<stop
id="stop821"
offset="1"
style="stop-color:#55ff55;stop-opacity:0;" />
</linearGradient>
<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,-28.198837,-42.329969)"
gradientUnits="userSpaceOnUse" />
<radialGradient
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.99367722,0,0.31814846)"
r="50.261864"
fy="50.317799"
fx="50.211864"
cy="50.317799"
cx="50.211864"
id="radialGradient825"
xlink:href="#linearGradient823" />
</defs>
<ellipse
ry="49.89407"
rx="50.211864"
cy="50.317799"
cx="50.211864"
id="path817"
style="fill:url(#radialGradient825);fill-opacity:1;stroke:#0f0000;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:0.98823529" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -0,0 +1,93 @@
<?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"
width="100"
height="100"
version="1.1"
id="svg6"
sodipodi:docname="dome.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" />
<dc:title></dc:title>
</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="11.313708"
inkscape:cx="52.167677"
inkscape:cy="58.44587"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg6"
showguides="true"
inkscape:guide-bbox="true">
<sodipodi:guide
position="50.25,94.25"
orientation="1,0"
id="guide819"
inkscape:locked="false" />
<sodipodi:guide
position="-9.9375,50"
orientation="0,1"
id="guide821"
inkscape:locked="false" />
</sodipodi:namedview>
<path
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:4.36474609;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 11.439881,11.609325 h 76.341284 c 12.547715,0.03599 12.938695,15.219042 0,15.060906 H 11.439881 c -11.36071647,0.03243 -11.220258,-15.184617 0,-15.060906 z"
id="path818"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<path
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:4.36474609;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 17.002109,29.775376 66.727903,0.0063 c -0.08223,51.179228 -66.932094,49.692075 -66.727903,-0.0063 z"
id="path823"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<g
id="g848"
transform="matrix(0.47524917,0,0,0.45894372,-20.936465,-23.265017)"
style="stroke-width:1.86916912">
<ellipse
ry="23.81991"
rx="24.494061"
cy="159.77713"
cx="149.88567"
id="path842"
style="fill:#ffffff;fill-opacity:1;stroke:#ee0000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<circle
r="15.280697"
cy="160.22656"
cx="149.66095"
id="path844"
style="fill:#000000;fill-opacity:1;stroke:#ee0000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1,62 @@
<?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"
width="300"
height="300"
version="1.1"
id="svg6"
sodipodi:docname="logo.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="1.1125147"
inkscape:cx="-288.60478"
inkscape:cy="92.396852"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg6" />
<circle
style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:0.1;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path812"
cx="-150.55981"
cy="149.44019"
r="148.76208"
transform="scale(-1,1)" />
<path
d="m 260.41537,162.40461 -21.57957,-6.49818 -31.63148,44.29458 37.37159,12.69576 z M 253.42705,132.54304 65.622579,79.515388 c -4.91364,-1.240239 -11.77146,-0.481561 -14.12789,8.422577 0,0 -11.08182,49.394605 -16.75297,72.577085 -1.99836,8.16887 0.18239,11.65946 5.84024,13.16739 l 149.600591,42.89541 z"
id="path4"
inkscape:connector-curvature="0"
style="fill:#000000;stroke:#00ff00;stroke-width:15;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
sodipodi:nodetypes="cccccccssccc" />
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View file

@ -0,0 +1,34 @@
{
"id": "surveillance",
"title": {
"en": "Surveillance under Surveillance",
"nl": "Surveillance under Surveillance"
},
"shortDescription": {
"en": "Surveillance cameras and other means of surveillance",
"nl": "Bewakingscameras en dergelijke"
},
"description": {
"en": "On this open map, you can find surveillance cameras.",
"nl": "Op deze open kaart kan je bewakingscamera's vinden."
},
"language": [
"en",
"nl"
],
"maintainer": "",
"icon": "./assets/themes/surveillance_cameras/logo.svg",
"version": "0",
"startLat": 0,
"startLon": 0,
"startZoom": 1,
"widenFactor": 0.05,
"socialImage": "",
"customCss": "./assets/themes/surveillance_cameras/custom_theme.css",
"defaultBackgroundId": "Stadia.AlidadeSmoothDark",
"layers": [
"direction",
"surveillance_cameras"
],
"roamingRenderings": []
}

23
css/imageUploadFlow.css Normal file
View file

@ -0,0 +1,23 @@
.image-upload-flow-button span {
width: max-content;
font-size: 28px;
font-weight: bold;
margin-top: 4px;
padding-top: 4px;
padding-bottom: 4px;
padding-left: 13px;
}
.image-upload-flow-button {
display: flex;
cursor: pointer;
padding: 0.5em;
border-radius: 1em;
border: 3px solid var(--foreground-color);
box-sizing: border-box;
}
.image-upload-flow svg {
fill: var(--foreground-color);
stroke: var(--foreground-color);
}

View file

@ -4,6 +4,8 @@ Contains tweaks for small screens
.only-on-mobile { .only-on-mobile {
display: none; display: none;
background-color: var(--background-color);
color: var(--foreground-color);
} }
@media only screen and (max-width: 600px), only screen and (max-height: 600px) { @media only screen and (max-width: 600px), only screen and (max-height: 600px) {
@ -14,6 +16,8 @@ Contains tweaks for small screens
.only-on-mobile { .only-on-mobile {
display: unset; display: unset;
background-color: var(--background-color);
color: var(--foreground-color);
} }
.hidden-on-mobile { .hidden-on-mobile {
@ -22,6 +26,8 @@ Contains tweaks for small screens
#messagesbox { #messagesbox {
display: none; display: none;
background-color: var(--background-color);
color: var(--foreground-color);
} }
#help-button-mobile{ #help-button-mobile{
@ -39,8 +45,9 @@ Contains tweaks for small screens
} }
.leaflet-popup { .leaflet-popup {
/* Popups are hidden on mobile */ /* On mobile, the popups are shown as a full-screen element */
display: none; display: none;
visibility: hidden;
} }
#centermessage { #centermessage {
@ -57,15 +64,15 @@ Contains tweaks for small screens
#messagesboxmobile { #messagesboxmobile {
display: block; display: block;
position: absolute; position: absolute;
z-index: 10000; z-index: 10000;
background-color: white;
width: 100vw; width: 100vw;
} }
#welcomeMessage { #welcomeMessage {
display: inline-block; display: inline-block;
background-color: white; background-color: var(--background-color);
border-radius: 0; border-radius: 0;
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;

View file

@ -5,7 +5,6 @@
text-align: center; text-align: center;
word-break: normal; word-break: normal;
} }
.oh-table th { .oh-table th {
padding: 0; padding: 0;
margin: 0; margin: 0;
@ -34,22 +33,22 @@
} }
.oh-timecell:hover { .oh-timecell:hover {
background-color: #92b1ff !important; background-color: var(--catch-detail-color) !important;
} }
.oh-timecell-selected { .oh-timecell-selected {
background-color: #0048ff; background-color: var(--catch-detail-color);
} }
.oh-timecell-half { .oh-timecell-half {
border-bottom: 1px solid #ddd; border-bottom: 1px solid #ddd;
background-color: aliceblue; background-color: var(--subtle-detail-color);
} }
.oh-timecell-half.oh-timecell-selected { .oh-timecell-half.oh-timecell-selected {
background-color: #0048ff; background-color: var(--catch-detail-color);
} }
.oh-table tr { .oh-table tr {
@ -75,12 +74,12 @@
} }
.oh-timecol-selected { .oh-timecol-selected {
border-right: #0048ff; border-right: var(--catch-detail-color);
} }
.oh-timecol-selected > span { .oh-timecol-selected > span {
background-color: #0048ff; background-color: var(--catch-detail-color);
color: white; color: var(--background-color);
width: 100%; width: 100%;
display: block; display: block;
} }
@ -96,11 +95,11 @@
} }
.oh-timerow-selected .oh-timecell-0 { .oh-timerow-selected .oh-timecell-0 {
border-left: 10px solid #0048ff !important; border-left: 10px solid var(--catch-detail-color) !important;
} }
.oh-timerow-selected .oh-timecell-6 { .oh-timerow-selected .oh-timecell-6 {
border-right: 10px solid #0048ff !important; border-right: 10px solid var(--catch-detail-color) !important;
} }
@ -117,7 +116,7 @@
top: 0; top: 0;
left: 0; left: 0;
width: calc(100% - 4px); width: calc(100% - 4px);
background: #0048ff; background: var(--catch-detail-color);
z-index: 1; z-index: 1;
box-sizing: border-box; box-sizing: border-box;
} }
@ -276,7 +275,7 @@
.ohviz-today { .ohviz-today {
background-color: #e5f5ff; background-color: var(--subtle-detail-color);
} }
.ohviz-weekday { .ohviz-weekday {
@ -290,7 +289,7 @@
} }
.ohviz-container { .ohviz-container {
border: 0.5em solid #e5f5ff; border: 0.5em solid var(--subtle-detail-color);
border-radius: 1em; border-radius: 1em;
display: block; display: block;
} }

View file

@ -7,7 +7,7 @@
flex-wrap: nowrap; flex-wrap: nowrap;
justify-content: flex-start; justify-content: flex-start;
align-items: start; align-items: start;
background-color: white; background-color: var(--background-color);
max-width: 100vw; max-width: 100vw;
overflow-x: auto; overflow-x: auto;
} }
@ -21,10 +21,19 @@
margin:auto; margin:auto;
} }
.tab-single-header svg {
height: 3em;
max-width: 3em;
padding: 0.5em;
display:block;
margin:auto;
}
.tab-content { .tab-content {
z-index: 5002; z-index: 5002;
background-color: white; background-color: var(--background-color);
color: var(--foreground-color);
position: relative; position: relative;
padding: 1em; padding: 1em;
display: inline-block; display: inline-block;
@ -41,19 +50,36 @@
} }
.tab-active { .tab-active {
background-color: white; background-color: var(--background-color);
color: var(--foreground-color);
z-index: 5001; z-index: 5001;
box-shadow: 0 0 10px black; box-shadow: 0 0 10px var(--shadow-color);
border: 1px solid white; border: 1px solid var(--background-color);
min-width: 4em; min-width: 4em;
} }
.tab-active svg {
fill: var(--foreground-color);
stroke: var(--foreground-color);
}
.tab-non-active { .tab-non-active {
background-color: #e5f5ff; background-color: var(--subtle-detail-color);
color: var(--foreground-color);
opacity: 0.5; opacity: 0.5;
border-left: 1px solid gray; border-left: 1px solid gray;
border-right:1px solid gray; border-right: 1px solid gray;
border-top: 1px solid gray; border-top: 1px solid gray;
border-bottom: 1px solid lightgray; border-bottom: 1px solid lightgray;
min-width: 4em; min-width: 4em;
} }
.tab-non-active svg {
fill: var(--foreground-color) !important;
stroke: var(--foreground-color) !important;
}
.tab-non-active svg path{
fill: var(--foreground-color) !important;
stroke: var(--foreground-color) !important;
}

View file

@ -30,13 +30,19 @@
.question { .question {
display: block; display: block;
margin-top: 1em; margin-top: 1em;
background-color: #e5f5ff; background-color: var(--subtle-detail-color);
color: var(--subtle-detail-color-contrast);
padding: 1em; padding: 1em;
border-radius: 1em; border-radius: 1em;
font-size: larger; font-size: larger;
} }
.question svg {
width: 100%;
height: 100%;
}
.question-text { .question-text {
font-size: larger; font-size: larger;
font-weight: bold; font-weight: bold;
@ -65,15 +71,15 @@
} }
input:checked + label .question-option-with-border { input:checked + label .question-option-with-border {
border: 2px solid black; border: 2px solid var(--subtle-detail-color-contrast);
} }
.save { .save {
display: inline-block; display: inline-block;
border: solid white 2px; border: solid var(--catch-detail-color-contrast) 2px;
background-color: #3a3aeb; background-color: var(--catch-detail-color);
color: white; color: var(--catch-detail-color-contrast);
padding: 0.2em 0.6em; padding: 0.2em 0.6em;
font-size: x-large; font-size: x-large;
font-weight: bold; font-weight: bold;
@ -89,9 +95,9 @@ input:checked + label .question-option-with-border {
.login-button-friendly { .login-button-friendly {
display: inline-block; display: inline-block;
border: solid white 2px; background-color:var(--catch-detail-color);
background-color: #3a3aeb; color: var(--catch-detail-color-contrast);
color: white; border: solid var(--catch-detail-color-contrast) 2px;
padding: 0.2em 0.6em; padding: 0.2em 0.6em;
font-size: large; font-size: large;
font-weight: bold; font-weight: bold;
@ -115,7 +121,24 @@ input:checked + label .question-option-with-border {
height: 1.3em; height: 1.3em;
padding: 0.5em; padding: 0.5em;
border-radius: 0.65em; border-radius: 0.65em;
border: solid black 1px; border: solid var(--popup-border) 1px;
font-size: medium; font-size: medium;
float: right; float: right;
} }
.edit-button svg {
width: 1.3em;
height: 1.3em;
padding: 0.5em;
border-radius: 0.65em;
border: solid var(--foreground-color) 1px;
stroke: var(--foreground-color) !important;
fill: var(--foreground-color) !important;
font-size: medium;
float: right;
}
.edit-button svg path{
stroke: var(--foreground-color) !important;
fill: var(--foreground-color) !important;
}

View file

@ -1,6 +1,7 @@
#userbadge { #userbadge {
display: inline-block; display: inline-block;
background-color: white; background-color: var(--background-color);
color: var(--foreground-color);
-webkit-border-radius: 2em; -webkit-border-radius: 2em;
-moz-border-radius: 2em; -moz-border-radius: 2em;
border-radius: 2em; border-radius: 2em;
@ -15,7 +16,7 @@
#userbadge a { #userbadge a {
text-decoration: none; text-decoration: none;
color: black; color: var(--foreground-color);
} }
@ -42,14 +43,19 @@
margin-bottom: 0.2em; margin-bottom: 0.2em;
} }
.userstats img { .userstats svg {
width: 1em; width: 1em;
height: 1em; height: 1em;
fill: black;
border-radius: 0; border-radius: 0;
display: block; display: block;
} }
.userstats img {
width: 1em;
height: 1em;
border-radius: 0;
display: block;
}
#profile-pic { #profile-pic {
float: left; float: left;
@ -76,7 +82,8 @@
height: 2.2em; /*SHould equal profile-pic height - padding*/ height: 2.2em; /*SHould equal profile-pic height - padding*/
z-index: 5000; z-index: 5000;
text-align: left; text-align: left;
background-color: white; background-color: var(--background-color);
color: var(--foreground-color);
background-size: 100%; background-size: 100%;
line-height: 0.75em; line-height: 0.75em;
@ -88,7 +95,8 @@
.userbadge-login { .userbadge-login {
font-weight: bold; font-weight: bold;
font-size: large; font-size: large;
background-color: #e5f5ff !important; background-color: var(--subtle-detail-color) !important;
color: var(--subtle-detail-color-contrast);
height:3em; height:3em;
display: inline-block; display: inline-block;

135
index.css
View file

@ -1,11 +1,42 @@
:root {
--subtle-detail-color: #e5f5ff;
--subtle-detail-color-contrast: black;
--catch-detail-color: #3a3aeb;
--catch-detail-color-contrast: white;
--alert-color: #fee4d1;
--background-color: white;
--foreground-color: black;
--popup-border: white;
--shadow-color: #00000066;
--return-to-the-map-height: 5em;
}
html, body { html, body {
height: 100%; height: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
background-color: var(--background-color);
color: var(--foreground-color);
font-family: 'Helvetica Neue', Arial, sans-serif;
} }
body { a {
font-family: 'Helvetica Neue', Arial, sans-serif; color: var(--foreground-color)
}
#topleft-tools svg {
fill: var(--foreground-color) !important;
stroke: var(--foreground-color) !important;
}
#topleft-tools svg path {
fill: var(--foreground-color) !important;
stroke: var(--foreground-color) !important;
}
.direction-svg svg path{
fill: var(--catch-detail-color) !important;
} }
@ -13,6 +44,23 @@ body {
height: 100%; height: 100%;
} }
.leaflet-popup-content-wrapper {
background-color: var(--background-color);
color: var(--foreground-color);
border: 2px solid var(--popup-border);
box-shadow: 0 3px 14px var(--shadow-color) !important;
}
.leaflet-container {
background-color: var(--background-color) !important;
}
.leaflet-popup-tip {
background-color: var(--popup-border) !important;
color: var(--popup-border) !important;
box-shadow: 0 3px 14px var(--shadow-color) !important;
}
#geolocate-button { #geolocate-button {
position: absolute; position: absolute;
bottom: 25px; bottom: 25px;
@ -39,30 +87,33 @@ body {
bottom: 1em; bottom: 1em;
left: 1em; left: 1em;
z-index: 9000; z-index: 9000;
background-color: white; background-color: var(--background-color);
color: var(--foreground-color);
border-radius: 1em; border-radius: 1em;
cursor: pointer; cursor: pointer;
box-shadow: 0 0 10px #00000066; box-shadow: 0 0 10px var(--shadow-color);
} }
.layer-selection-toggle { .layer-selection-toggle {
border-top-left-radius: 1em; border-radius: 1em;
border-bottom-left-radius: 1em;
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column-reverse;
background: var(--subtle-detail-color);
} }
.layer-selection-toggle img { .layer-selection-toggle svg {
display: block; display: block;
width: 2em; width: 2em;
padding: 1em; height: 2em;
padding: 0.75em;
} }
/**************** GENERIC ****************/ /**************** GENERIC ****************/
.alert { .alert {
background-color: #fee4d1; background-color: var(--alert-color);
font-weight: bold; font-weight: bold;
border-radius: 1em; border-radius: 1em;
margin: 0.25em; margin: 0.25em;
@ -82,7 +133,7 @@ body {
} }
.shadow { .shadow {
box-shadow: 0 0 10px #00000066; box-shadow: 0 0 10px var(--shadow-color);
} }
.title-font span { .title-font span {
@ -91,7 +142,8 @@ body {
} }
.soft { .soft {
background-color: #e5f5ff; background-color: var(--subtle-detail-color);
color: var(--subtle-detail-color-contrast);
font-weight: bold; font-weight: bold;
border-radius: 1em; border-radius: 1em;
margin: 0.25em; margin: 0.25em;
@ -121,6 +173,10 @@ body {
pointer-events: all; pointer-events: all;
} }
.unclickable {
pointer-events: none !important;
}
.page-split { .page-split {
display: flex; display: flex;
height: 100%; height: 100%;
@ -137,7 +193,9 @@ body {
#searchbox { #searchbox {
display: inline-block; display: inline-block;
text-align: left; text-align: left;
background-color: white; background-color: var(--background-color);
color: var(--foreground-color);
transition: all 500ms linear; transition: all 500ms linear;
pointer-events: all; pointer-events: all;
border-radius: 1.3em; border-radius: 1.3em;
@ -170,6 +228,7 @@ body {
font-size: large; font-size: large;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
color: var(--foreground-color);
} }
.search-go img { .search-go img {
@ -200,7 +259,6 @@ body {
position: absolute; position: absolute;
z-index: 5000; z-index: 5000;
transition: all 500ms linear; transition: all 500ms linear;
overflow-x: hidden;
pointer-events: none; pointer-events: none;
/* Shadow offset */ /* Shadow offset */
padding: 0.5em 10px 0 0.5em; padding: 0.5em 10px 0 0.5em;
@ -215,13 +273,16 @@ body {
overflow-y: auto; overflow-y: auto;
border-top-right-radius: 1em; border-top-right-radius: 1em;
border-bottom-right-radius: 1em; border-bottom-right-radius: 1em;
background-color: var(--background-color);
color: var(--foreground-color);
} }
.close-welcome-button { .close-welcome-button {
position: absolute; position: absolute;
display: inline-block; display: inline-block;
height: 100%; height: 100%;
background-color: #e5f5ff; background-color: var(--subtle-detail-color);
color: var(--subtle-detail-color-contrast);
box-sizing: border-box; box-sizing: border-box;
width: 4em; width: 4em;
padding: 1em; padding: 1em;
@ -229,22 +290,26 @@ body {
border-bottom-left-radius: 1em; border-bottom-left-radius: 1em;
} }
.close-welcome-button img { .close-welcome-button svg {
width: 2em; width: 2em;
height: 2em;
} }
.open-welcome-button { .open-welcome-button {
display: inline-block; display: inline-block;
box-sizing: border-box; box-sizing: border-box;
background-color: white; background: var(--subtle-detail-color);
height: 4em; color: var(--foreground-color);
width: 4em;
padding: 1em; height: 3.5em;
width: 3.5em;
padding: 0.75em;
border-radius: 1em; border-radius: 1em;
} }
.open-welcome-button img { .open-welcome-button svg {
width: 2em; width: 2em;
height: 2em;
} }
#messagesbox { #messagesbox {
@ -252,9 +317,11 @@ body {
position: relative; position: relative;
padding: 0; padding: 0;
pointer-events: all; pointer-events: all;
box-shadow: 0 0 10px #00000066; box-shadow: 0 0 10px var(--shadow-color);
border-radius: 1em; border-radius: 1em;
width: min-content width: min-content;
background-color: var(--background-color);
color: var(--foreground-color);
} }
@ -273,7 +340,9 @@ body {
pointer-events: none; pointer-events: none;
opacity: 1; opacity: 1;
background-color: white; background-color: var(--background-color);
color: var(--foreground-color);
transition: opacity 500ms linear; transition: opacity 500ms linear;
@ -344,6 +413,13 @@ body {
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
} }
.leaflet-div-icon {
background-color: unset !important;
border: unset !important;
}
/****** ShareScreen *****/ /****** ShareScreen *****/
.literal-code { .literal-code {
@ -357,7 +433,9 @@ body {
.iframe-escape { .iframe-escape {
background-color: white; background-color: var(--background-color);
color: var(--foreground-color);
border-radius: 2em; border-radius: 2em;
display: block; display: block;
width: min-content; width: min-content;
@ -376,11 +454,11 @@ body {
flex-direction: row; flex-direction: row;
font-size: large; font-size: large;
margin: 0.5em; margin: 0.5em;
background-color: #e5f5ff; background-color: var(--subtle-detail-color);
color: var(--subtle-detail-color-contrast);
border-radius: 1em; border-radius: 1em;
align-items: center; align-items: center;
text-decoration: none; text-decoration: none;
color: black;
} }
@ -416,3 +494,8 @@ body {
} }
.small-image img {
height: 1em;
max-width: 1em;
}

View file

@ -13,6 +13,7 @@
<link rel="stylesheet" href="./css/mobile.css"/> <link rel="stylesheet" href="./css/mobile.css"/>
<link rel="stylesheet" href="./css/openinghourstable.css"/> <link rel="stylesheet" href="./css/openinghourstable.css"/>
<link rel="stylesheet" href="./css/tagrendering.css"/> <link rel="stylesheet" href="./css/tagrendering.css"/>
<link rel="stylesheet" href="./css/imageUploadFlow.css"/>
<!-- $$$CUSTOM-CSS --> <!-- $$$CUSTOM-CSS -->
<link rel="manifest" href="./manifest.manifest"> <link rel="manifest" href="./manifest.manifest">
<link rel="icon" href="assets/svg/add.svg" sizes="any" type="image/svg+xml"> <link rel="icon" href="assets/svg/add.svg" sizes="any" type="image/svg+xml">

View file

@ -4,8 +4,8 @@ import {InitUiElements} from "./InitUiElements";
import {QueryParameters} from "./Logic/Web/QueryParameters"; import {QueryParameters} from "./Logic/Web/QueryParameters";
import {UIEventSource} from "./Logic/UIEventSource"; import {UIEventSource} from "./Logic/UIEventSource";
import * as $ from "jquery"; import * as $ from "jquery";
import SharedLayers from "./Customizations/SharedLayers";
import LayoutConfig from "./Customizations/JSON/LayoutConfig"; import LayoutConfig from "./Customizations/JSON/LayoutConfig";
import {Utils} from "./Utils";
let defaultLayout = "bookcases" let defaultLayout = "bookcases"
// --------------------- Special actions based on the parameters ----------------- // --------------------- Special actions based on the parameters -----------------
@ -30,6 +30,10 @@ if(location.href.indexOf("pietervdvn.github.io") >= 0){
defaultLayout = "bookcases" defaultLayout = "bookcases"
} }
const customCssQP = QueryParameters.GetQueryParameter("custom-css", "", "If specified, the custom css from the given link will be loaded additionaly");
if(customCssQP.data !== undefined && customCssQP.data !== ""){
Utils.LoadCustomCss(customCssQP.data);
}
let testing: UIEventSource<string>; let testing: UIEventSource<string>;
@ -54,23 +58,7 @@ if (path !== "index.html" && path !== "") {
defaultLayout = path.substr(0, path.length - 5); defaultLayout = path.substr(0, path.length - 5);
console.log("Using layout", defaultLayout); console.log("Using layout", defaultLayout);
} }
defaultLayout = QueryParameters.GetQueryParameter("layout", defaultLayout,"The layout to load into MapComplete").data;
// Run over all questsets. If a part of the URL matches a searched-for part in the layout, it'll take that as the default
for (const k in AllKnownLayouts.allSets) {
const layout : LayoutConfig= AllKnownLayouts.allSets[k];
const possibleParts = (layout.locationContains ?? []);
for (const locationMatch of possibleParts) {
if (locationMatch === "") {
continue
}
if (window.location.href.toLowerCase().indexOf(locationMatch.toLowerCase()) >= 0) {
defaultLayout = layout.name;
}
}
}
defaultLayout = QueryParameters.GetQueryParameter("layout", defaultLayout).data;
let layoutToUse: LayoutConfig = AllKnownLayouts.allSets[defaultLayout.toLowerCase()] ?? AllKnownLayouts["all"]; let layoutToUse: LayoutConfig = AllKnownLayouts.allSets[defaultLayout.toLowerCase()] ?? AllKnownLayouts["all"];
@ -86,18 +74,20 @@ if (layoutFromBase64.startsWith("wiki:")) {
$.ajax({ $.ajax({
url: url, url: url,
dataType: 'xml',
success: function (data) { success: function (data) {
const layoutJson = data.querySelector('[id="bodyContent"]') // Hacky McHackFace has been working here. This probably break in the future
.querySelector('[class="mw-parser-output"]') const startTrigger = "<div class=\"mw-parser-output\">";
.children[0] const start = data.indexOf(startTrigger);
.firstChild.textContent; data = data.substr(start,
data.indexOf("<div class=\"printfooter\">") - start)
data = data.substr(0, data.lastIndexOf("</p>"))
data = data.substr(startTrigger.length + 3);
try { try {
console.log("DOWNLOADED:",layoutJson); const parsed = JSON.parse(data);
const parsed = JSON.parse(layoutJson);
parsed["id"] = layoutFromBase64 parsed["id"] = layoutFromBase64
const layout =new LayoutConfig(parsed); const layout = new LayoutConfig(parsed);
InitUiElements.InitAll(layout, layoutFromBase64, testing, layoutFromBase64, btoa(layoutJson)); InitUiElements.InitAll(layout, layoutFromBase64, testing, layoutFromBase64, btoa(data));
} catch (e) { } catch (e) {
new FixedUiElement(`<a href="${cleanUrl}">${themeName}</a> is invalid:<br/>${e}`) new FixedUiElement(`<a href="${cleanUrl}">${themeName}</a> is invalid:<br/>${e}`)
.SetClass("clickable") .SetClass("clickable")
@ -118,3 +108,4 @@ if (layoutFromBase64.startsWith("wiki:")) {
InitUiElements.InitAll(layoutToUse, layoutFromBase64, testing, defaultLayout); InitUiElements.InitAll(layoutToUse, layoutFromBase64, testing, defaultLayout);
} }
// console.log(QueryParameters.GenerateQueryParameterDocs())

View file

@ -1 +0,0 @@
npm install

6
package-lock.json generated
View file

@ -1774,9 +1774,9 @@
} }
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001066", "version": "1.0.30001157",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001066.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001157.tgz",
"integrity": "sha512-Gfj/WAastBtfxLws0RCh2sDbTK/8rJuSeZMecrSkNGYxPcv7EzblmDGfWQCFEQcSqYE2BRgQiJh8HOD07N5hIw==" "integrity": "sha512-gOerH9Wz2IRZ2ZPdMfBvyOi3cjaz4O4dgNwPGzx8EhqAs4+2IL/O+fJsbt+znSigujoZG8bVcIAUM/I/E5K3MA=="
}, },
"canvas": { "canvas": {
"version": "2.6.1", "version": "2.6.1",

View file

@ -16,16 +16,17 @@
"start": "parcel *.html UI/** Logic/** assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*", "start": "parcel *.html UI/** Logic/** assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*",
"test": "ts-node test/*", "test": "ts-node test/*",
"generate:editor-layer-index": "cd assets/ && wget https://osmlab.github.io/editor-layer-index/imagery.geojson --output-document=editor-layer-index.json", "generate:editor-layer-index": "cd assets/ && wget https://osmlab.github.io/editor-layer-index/imagery.geojson --output-document=editor-layer-index.json",
"generate:included-images": "ts-node generateIncludedImages.ts", "generate:images": "ts-node scripts/generateIncludedImages.ts",
"generate:layouts": "ts-node createLayouts.ts", "generate:translations": "ts-node scripts/generateTranslations.ts",
"generate:layouts": "ts-node scripts/createLayouts.ts",
"optimize-images": "cd assets/generated/ && find -name '*.png' -exec optipng '{}' \\; && echo 'PNGs are optimized'", "optimize-images": "cd assets/generated/ && find -name '*.png' -exec optipng '{}' \\; && echo 'PNGs are optimized'",
"generate": "npm run generate:included-images && npm run generate:layouts && npm run generate:editor-layer-index", "generate": "npm run generate:images && npm run generate:translations && npm run generate:layouts && npm run generate:editor-layer-index",
"build": "rm -rf dist/ npm run generate && parcel build --public-url ./ *.html assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*", "build": "rm -rf dist/ && npm run generate && parcel build --public-url ./ *.html assets/** assets/**/** assets/**/**/** vendor/* vendor/*/*",
"prepare-deploy": "npm run generate && npm run build && rm -rf .cache", "prepare-deploy": "npm run generate && npm run build && rm -rf .cache",
"deploy:staging": "npm run prepare-deploy && rm -rf /home/pietervdvn/git/pietervdvn.github.io/Staging/* && cp -r dist/* /home/pietervdvn/git/pietervdvn.github.io/Staging/ && cd /home/pietervdvn/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean", "deploy:staging": "npm run prepare-deploy && rm -rf /home/pietervdvn/git/pietervdvn.github.io/Staging/* && cp -r dist/* /home/pietervdvn/git/pietervdvn.github.io/Staging/ && cd /home/pietervdvn/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean",
"deploy:production": "npm run prepare-deploy && npm run optimize-images && rm -rf /home/pietervdvn/git/pietervdvn.github.io/MapComplete/* && cp -r dist/* /home/pietervdvn/git/pietervdvn.github.io/MapComplete/ && cd /home/pietervdvn/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean", "deploy:production": "npm run prepare-deploy && npm run optimize-images && rm -rf /home/pietervdvn/git/pietervdvn.github.io/MapComplete/* && cp -r dist/* /home/pietervdvn/git/pietervdvn.github.io/MapComplete/ && cd /home/pietervdvn/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean",
"clean": "rm *.webmanifest && find *.html | grep -v \"\\(index\\|land\\|test\\|preferences\\|customGenerator\\).html\" | xargs rm" "clean": "rm -rf .cache/ && (find *.html | grep -v \"\\(index\\|land\\|test\\|preferences\\|customGenerator\\).html\" | xargs rm) && (find *.webmanifest | xargs rm)"
}, },
"keywords": [ "keywords": [

View file

@ -1,16 +1,16 @@
import {Img} from "./UI/Img" import {Img} from "../UI/Img"
import {UIElement} from "./UI/UIElement"; import {UIElement} from "../UI/UIElement";
Img.runningFromConsole = true; Img.runningFromConsole = true;
// We HAVE to mark this while importing // We HAVE to mark this while importing
UIElement.runningFromConsole = true; UIElement.runningFromConsole = true;
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; import {AllKnownLayouts} from "../Customizations/AllKnownLayouts";
import {existsSync, mkdirSync, readFileSync, writeFile, writeFileSync} from "fs"; import {existsSync, mkdirSync, readFileSync, writeFile, writeFileSync} from "fs";
import Locale from "./UI/i18n/Locale"; import Locale from "../UI/i18n/Locale";
import svg2img from 'promise-svg2img'; import svg2img from 'promise-svg2img';
import Translations from "./UI/i18n/Translations"; import Translations from "../UI/i18n/Translations";
import {Translation} from "./UI/i18n/Translation"; import {Translation} from "../UI/i18n/Translation";
import LayoutConfig from "./Customizations/JSON/LayoutConfig"; import LayoutConfig from "../Customizations/JSON/LayoutConfig";
function enc(str: string): string { function enc(str: string): string {
@ -197,12 +197,18 @@ function createLandingPage(layout: LayoutConfig) {
Locale.language.setData(layout.language[0]); Locale.language.setData(layout.language[0]);
const ogTitle = Translations.W(layout.title)?.InnerRender(); const ogTitle = Translations.W(layout.title)?.InnerRender();
const ogDescr = Translations.W(layout.description ?? "Easily add and edit geodata with OpenStreetMap")?.InnerRender(); const ogDescr = Translations.W(layout.shortDescription ?? "Easily add and edit geodata with OpenStreetMap")?.InnerRender();
const ogImage = layout.socialImage; const ogImage = layout.socialImage;
let customCss = ""; let customCss = "";
if (layout.customCss !== undefined && layout.customCss !== "") { if (layout.customCss !== undefined && layout.customCss !== "") {
customCss = `<link rel='stylesheet" href=${layout.customCss}"/>`
try {
const cssContent = readFileSync(layout.customCss);
customCss = "<style>" + cssContent + "</style>";
} catch (e) {
customCss = `<link rel='stylesheet' href="${layout.customCss}"/>`
}
} }
const og = ` const og = `
@ -237,6 +243,11 @@ function createLandingPage(layout: LayoutConfig) {
return output; return output;
} }
const generatedDir = "./assets/generated";
if (! existsSync(generatedDir)) {
mkdirSync(generatedDir)
}
const blacklist = ["", "test", ".", "..", "manifest", "index", "land", "preferences", "account", "openstreetmap"] const blacklist = ["", "test", ".", "..", "manifest", "index", "land", "preferences", "account", "openstreetmap"]
const all = AllKnownLayouts.allSets; const all = AllKnownLayouts.allSets;
@ -245,10 +256,7 @@ let wikiPage = "{|class=\"wikitable sortable\"\n" +
"|-"; "|-";
const generatedDir = "./assets/generated";
if (! existsSync(generatedDir)) {
mkdirSync(generatedDir)
}
for (const layoutName in all) { for (const layoutName in all) {
if (blacklist.indexOf(layoutName.toLowerCase()) >= 0) { if (blacklist.indexOf(layoutName.toLowerCase()) >= 0) {

View file

@ -1,5 +1,5 @@
import * as fs from "fs"; import * as fs from "fs";
import {Utils} from "./Utils"; import {Utils} from "../Utils";
function genImages() { function genImages() {
@ -7,6 +7,7 @@ function genImages() {
const dir = fs.readdirSync("./assets/svg") const dir = fs.readdirSync("./assets/svg")
let module = "import {Img} from \"./UI/Img\";\nimport {FixedUiElement} from \"./UI/Base/FixedUiElement\";\n\nexport default class Svg {\n\n\n"; let module = "import {Img} from \"./UI/Img\";\nimport {FixedUiElement} from \"./UI/Base/FixedUiElement\";\n\nexport default class Svg {\n\n\n";
const allNames: string[] = [];
for (const path of dir) { for (const path of dir) {
if (!path.endsWith(".svg")) { if (!path.endsWith(".svg")) {
@ -14,6 +15,8 @@ function genImages() {
} }
const svg = fs.readFileSync("./assets/svg/" + path, "utf-8") const svg = fs.readFileSync("./assets/svg/" + path, "utf-8")
.replace(/<\?xml.*?>/, "")
.replace(/fill: ?none;/g,"fill: none !important;") // This is such a brittle hack...
.replace(/\n/g, " ") .replace(/\n/g, " ")
.replace(/\r/g, "") .replace(/\r/g, "")
.replace(/\\/g, "\\") .replace(/\\/g, "\\")
@ -22,48 +25,13 @@ function genImages() {
.replace(/[ -]/g, "_"); .replace(/[ -]/g, "_");
module += ` public static ${name} = "${svg}"\n` module += ` public static ${name} = "${svg}"\n`
module += ` public static ${name}_img = Img.AsImageElement(Svg.${name})\n` module += ` public static ${name}_img = Img.AsImageElement(Svg.${name})\n`
module += ` public static ${name}_svg() { return new FixedUiElement(Svg.${name});}\n`
module += ` public static ${name}_ui() { return new FixedUiElement(Svg.${name}_img);}\n\n` module += ` public static ${name}_ui() { return new FixedUiElement(Svg.${name}_img);}\n\n`
allNames.push(`"${path}": Svg.${name}`)
} }
module += `public static All = {${allNames.join(",")}};`
module += "}\n"; module += "}\n";
fs.writeFileSync("Svg.ts", module); fs.writeFileSync("Svg.ts", module);
console.log("Done") console.log("Done")
} }
function isTranslation(tr: any): boolean {
for (const key in tr) {
if (typeof tr[key] !== "string") {
return false;
}
}
return true;
}
function transformTranslation(obj: any, depth = 1) {
if (isTranslation(obj)) {
return `new Translation( ${JSON.stringify(obj)} )`
}
let values = ""
for (const key in obj) {
values += (Utils.Times((_) => " ", depth)) + key + ": " + transformTranslation(obj[key], depth + 1) + ",\n"
}
return `{${values}}`;
}
function genTranslations() {
const translations = JSON.parse(fs.readFileSync("./assets/translations.json", "utf-8"))
const transformed = transformTranslation(translations);
let module = `import {Translation} from "./UI/i18n/Translation"\n\nexport default class AllTranslationAssets {\n\n`;
module += " public static t = " + transformed;
module += "}"
fs.writeFileSync("AllTranslationAssets.ts", module);
}
genTranslations()
genImages() genImages()

View file

@ -0,0 +1,40 @@
import * as fs from "fs";
import {Utils} from "../Utils";
function isTranslation(tr: any): boolean {
for (const key in tr) {
if (typeof tr[key] !== "string") {
return false;
}
}
return true;
}
function transformTranslation(obj: any, depth = 1) {
if (isTranslation(obj)) {
return `new Translation( ${JSON.stringify(obj)} )`
}
let values = ""
for (const key in obj) {
values += (Utils.Times((_) => " ", depth)) + key + ": " + transformTranslation(obj[key], depth + 1) + ",\n"
}
return `{${values}}`;
}
function genTranslations() {
const translations = JSON.parse(fs.readFileSync("./assets/translations.json", "utf-8"))
const transformed = transformTranslation(translations);
let module = `import {Translation} from "./UI/i18n/Translation"\n\nexport default class AllTranslationAssets {\n\n`;
module += " public static t = " + transformed;
module += "}"
fs.writeFileSync("AllTranslationAssets.ts", module);
}
genTranslations()

View file

@ -25,7 +25,10 @@
</style> </style>
</head> </head>
<body> <body>
<div id="maindiv">'maindiv' not attached</div> <div class="question">
<div id="maindiv">'maindiv' not attached</div>
</div>
<div id="extradiv">'extradiv' not attached</div> <div id="extradiv">'extradiv' not attached</div>
<script src="./test.ts"></script> <script src="./test.ts"></script>
</body> </body>

22
test.ts
View file

@ -1,20 +1,20 @@
/* //*
import Direction from "./UI/Input/DirectionInput";
import {UIEventSource} from "./Logic/UIEventSource"; import {UIEventSource} from "./Logic/UIEventSource";
import {FeatureInfoBox} from "./UI/Popup/FeatureInfoBox"; import {VariableUiElement} from "./UI/Base/VariableUIElement";
import SharedLayers from "./Customizations/SharedLayers";
const tags = { const d = new UIEventSource("90");
mapillary: "wweALGY5g8_T8UjGkcWCfw", new Direction(d, [51.21576,3.22001]).AttachTo("maindiv")
wikimedia_commons: "File:Boekenkast Sint-Lodewijks.jpg" new VariableUiElement(d.map(d => "" + d + "°")).AttachTo("extradiv")
}
const src = new UIEventSource(tags);
new FeatureInfoBox(src, SharedLayers.sharedLayers["ghost_bike"]).AttachTo('maindiv'); UIEventSource.Chronic(25, () => {
const degr = (Number(d.data) + 1) % 360;
d.setData(""+ degr);
return true;
})
//const subs = new SubstitutedTranslation(new Translation({"nl":"NL {image_carousel()} {image_upload()}"}), src)
//subs.AttachTo("maindiv")
/*/ /*/