I should have commited sooner...
This commit is contained in:
parent
2685b6e734
commit
16612b10ef
35 changed files with 570 additions and 177 deletions
|
@ -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 {Browser} from "leaflet";
|
||||||
|
|
||||||
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;
|
||||||
|
@ -53,10 +63,11 @@ export default class LayerConfig {
|
||||||
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),
|
||||||
|
@ -93,7 +104,10 @@ export default class LayerConfig {
|
||||||
|
|
||||||
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,11 +121,19 @@ 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", "");
|
||||||
|
|
||||||
|
|
||||||
|
@ -121,13 +143,16 @@ export default class LayerConfig {
|
||||||
public GenerateLeafletStyle(tags: any):
|
public GenerateLeafletStyle(tags: any):
|
||||||
{
|
{
|
||||||
color: string;
|
color: string;
|
||||||
icon: { popupAnchor: [number, number]; iconAnchor: [number, number]; iconSize: [number, number]; iconUrl: string }; weight: number; dashArray: number[]
|
icon: {
|
||||||
|
iconUrl: string,
|
||||||
|
popupAnchor: [number, number];
|
||||||
|
iconAnchor: [number, number];
|
||||||
|
iconSize: [number, number];
|
||||||
|
html: string;
|
||||||
|
rotation: number;
|
||||||
|
};
|
||||||
|
weight: number; dashArray: number[]
|
||||||
} {
|
} {
|
||||||
const iconUrl = this.icon?.GetRenderValue(tags)?.txt;
|
|
||||||
const iconSize = (this.iconSize?.GetRenderValue(tags)?.txt ?? "40,40,center").split(",");
|
|
||||||
|
|
||||||
|
|
||||||
const dashArray = this.dashArray.GetRenderValue(tags)?.txt.split(" ").map(Number);
|
|
||||||
|
|
||||||
function num(str, deflt = 40) {
|
function num(str, deflt = 40) {
|
||||||
const n = Number(str);
|
const n = Number(str);
|
||||||
|
@ -137,6 +162,33 @@ export default class LayerConfig {
|
||||||
return n;
|
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 = rendernum(this.rotation, 0);
|
||||||
|
|
||||||
|
|
||||||
const iconW = num(iconSize[0]);
|
const iconW = num(iconSize[0]);
|
||||||
const iconH = num(iconSize[1]);
|
const iconH = num(iconSize[1]);
|
||||||
const mode = iconSize[2] ?? "center"
|
const mode = iconSize[2] ?? "center"
|
||||||
|
@ -157,16 +209,22 @@ export default class LayerConfig {
|
||||||
anchorH = iconH;
|
anchorH = iconH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let html = `<img src="${iconUrl}" style="width:100%;height:100%;rotate:${rotation}deg;display:block;" />`;
|
||||||
const color = this.color?.GetRenderValue(tags)?.txt ?? "#00f";
|
if (iconUrl.startsWith(Utils.assets_path)) {
|
||||||
let weight = num(this.width?.GetRenderValue(tags)?.txt, 5);
|
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}deg;display:block;`).Render();
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
icon:
|
icon:
|
||||||
{
|
{
|
||||||
iconUrl: iconUrl,
|
html: html,
|
||||||
iconSize: [iconW, iconH],
|
iconSize: [iconW, iconH],
|
||||||
iconAnchor: [anchorW, anchorH],
|
iconAnchor: [anchorW, anchorH],
|
||||||
popupAnchor: [0, 3 - anchorH]
|
popupAnchor: [0, 3 - anchorH],
|
||||||
|
rotation: rotation,
|
||||||
|
iconUrl: iconUrl
|
||||||
},
|
},
|
||||||
color: color,
|
color: color,
|
||||||
weight: weight,
|
weight: weight,
|
||||||
|
|
|
@ -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,14 @@ export interface LayerConfigJson {
|
||||||
* Default is '40,40,center'
|
* Default is '40,40,center'
|
||||||
*/
|
*/
|
||||||
iconSize?: string | TagRenderingConfigJson;
|
iconSize?: string | TagRenderingConfigJson;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The color for way-elements
|
* The rotation of an icon, useful for e.g. directions
|
||||||
|
*/
|
||||||
|
rotation?: string | TagRenderingConfigJson;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 +103,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 +119,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) []
|
||||||
}
|
}
|
|
@ -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)[],
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ 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 LayerConfig from "./JSON/LayerConfig";
|
import LayerConfig from "./JSON/LayerConfig";
|
||||||
|
|
||||||
export default class SharedLayers {
|
export default class SharedLayers {
|
||||||
|
@ -37,6 +38,7 @@ export default class SharedLayers {
|
||||||
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(direction, "shared_layers"),
|
||||||
new LayerConfig(information_boards, "shared_layers")
|
new LayerConfig(information_boards, "shared_layers")
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
@ -142,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);
|
||||||
|
@ -153,6 +157,10 @@ 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 === undefined) {
|
||||||
|
State.state.fullScreenMessage.setData(undefined);
|
||||||
|
}
|
||||||
if (feature?.properties === undefined) {
|
if (feature?.properties === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -163,17 +171,22 @@ export class InitUiElements {
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -204,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;`)
|
||||||
|
@ -327,6 +355,10 @@ 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)
|
||||||
|
@ -435,13 +467,15 @@ 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;
|
||||||
|
@ -475,13 +509,12 @@ export class InitUiElements {
|
||||||
throw "Layer " + layer + " was not substituted";
|
throw "Layer " + layer + " was not substituted";
|
||||||
}
|
}
|
||||||
|
|
||||||
const flayer: FilteredLayer = new FilteredLayer(layer,
|
let generateContents = (tags: UIEventSource<any>) => new FeatureInfoBox(tags, layer);
|
||||||
(tagsES) => {
|
if (layer.title === undefined && (layer.tagRenderings ?? []).length === 0) {
|
||||||
return new FeatureInfoBox(
|
generateContents = undefined;
|
||||||
tagsES,
|
}
|
||||||
layer,
|
|
||||||
)
|
const flayer: FilteredLayer = new FilteredLayer(layer, generateContents);
|
||||||
});
|
|
||||||
flayers.push(flayer);
|
flayers.push(flayer);
|
||||||
|
|
||||||
QueryParameters.GetQueryParameter("layer-" + layer.id, "true", "Wehter or not layer " + layer.id + " is shown")
|
QueryParameters.GetQueryParameter("layer-" + layer.id, "true", "Wehter or not layer " + layer.id + " is shown")
|
||||||
|
|
|
@ -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
|
||||||
|
@ -75,11 +76,13 @@ export class FilteredLayer {
|
||||||
const selfFeatures = [];
|
const selfFeatures = [];
|
||||||
for (let feature of geojson.features) {
|
for (let feature of geojson.features) {
|
||||||
const tags = TagUtils.proprtiesToKV(feature.properties);
|
const tags = TagUtils.proprtiesToKV(feature.properties);
|
||||||
if (!this.filters.matches(tags)) {
|
const matches = this.filters.matches(tags);
|
||||||
leftoverFeatures.push(feature);
|
if (matches) {
|
||||||
continue;
|
selfFeatures.push(feature);
|
||||||
|
}
|
||||||
|
if (!matches || this.layerDef.passAllFeatures) {
|
||||||
|
leftoverFeatures.push(feature);
|
||||||
}
|
}
|
||||||
selfFeatures.push(feature);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.RenderLayer(selfFeatures)
|
this.RenderLayer(selfFeatures)
|
||||||
|
@ -117,7 +120,6 @@ export class FilteredLayer {
|
||||||
|
|
||||||
// We fetch all the data we have to show:
|
// We fetch all the data we have to show:
|
||||||
let fusedFeatures = this.ApplyWayHandling(this.FuseData(features));
|
let fusedFeatures = this.ApplyWayHandling(this.FuseData(features));
|
||||||
console.log("Fused:",fusedFeatures)
|
|
||||||
|
|
||||||
// And we copy some features as points - if needed
|
// And we copy some features as points - if needed
|
||||||
const data = {
|
const data = {
|
||||||
|
@ -126,7 +128,6 @@ export class FilteredLayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
let self = this;
|
let self = this;
|
||||||
console.log(data);
|
|
||||||
this._geolayer = L.geoJSON(data, {
|
this._geolayer = L.geoJSON(data, {
|
||||||
style: feature =>
|
style: feature =>
|
||||||
self.layerDef.GenerateLeafletStyle(feature.properties),
|
self.layerDef.GenerateLeafletStyle(feature.properties),
|
||||||
|
@ -147,19 +148,21 @@ export class FilteredLayer {
|
||||||
color: style.color
|
color: style.color
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (style.icon.iconSize === undefined) {
|
|
||||||
style.icon.iconSize = [50, 50]
|
|
||||||
}
|
|
||||||
|
|
||||||
marker = L.marker(latLng, {
|
marker = L.marker(latLng, {
|
||||||
icon: L.icon(style.icon)
|
icon: L.divIcon(style.icon)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return marker;
|
return marker;
|
||||||
},
|
},
|
||||||
onEachFeature: function (feature, layer: Layer) {
|
onEachFeature: function (feature, layer: Layer) {
|
||||||
|
|
||||||
layer.on("click", (e) => {
|
if (self._showOnPopup === undefined) {
|
||||||
|
// No popup contents defined -> don't do anything
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function openPopup(latlng: any) {
|
||||||
if (layer.getPopup() === undefined
|
if (layer.getPopup() === undefined
|
||||||
&& (window.screen.availHeight > 600 || window.screen.availWidth > 600) // We DON'T trigger this code on small screens! No need to create a popup
|
&& (window.screen.availHeight > 600 || window.screen.availWidth > 600) // We DON'T trigger this code on small screens! No need to create a popup
|
||||||
) {
|
) {
|
||||||
|
@ -168,30 +171,47 @@ export class FilteredLayer {
|
||||||
closeOnEscapeKey: true,
|
closeOnEscapeKey: true,
|
||||||
}, layer);
|
}, layer);
|
||||||
|
|
||||||
// @ts-ignore
|
popup.setLatLng(latlng)
|
||||||
popup.setLatLng(e.latlng)
|
|
||||||
|
layer.bindPopup(popup);
|
||||||
|
const eventSource = State.state.allElements.addOrGetElement(feature);
|
||||||
|
const uiElement = self._showOnPopup(eventSource, feature);
|
||||||
|
// We first render the UIelement (which'll still need an update later on...)
|
||||||
|
// But at least it'll be visible already
|
||||||
|
popup.setContent(uiElement.Render());
|
||||||
|
popup.openOn(State.state.bm.map);
|
||||||
|
// popup.openOn(State.state.bm.map);
|
||||||
|
// ANd we perform the pending update
|
||||||
|
uiElement.Update();
|
||||||
|
// @ts-ignore
|
||||||
|
popup.Update = () => {
|
||||||
|
uiElement.Update();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// @ts-ignore
|
||||||
|
layer.getPopup().Update();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// We set the element as selected...
|
||||||
|
State.state.selectedElement.setData(feature);
|
||||||
|
|
||||||
layer.bindPopup(popup);
|
|
||||||
const eventSource = State.state.allElements.addOrGetElement(feature);
|
|
||||||
const uiElement = self._showOnPopup(eventSource, feature);
|
|
||||||
// We first render the UIelement (which'll still need an update later on...)
|
|
||||||
// But at least it'll be visible already
|
|
||||||
popup.setContent(uiElement.Render());
|
|
||||||
popup.openOn(State.state.bm.map);
|
|
||||||
// popup.openOn(State.state.bm.map);
|
|
||||||
// ANd we perform the pending update
|
|
||||||
uiElement.Update();
|
|
||||||
}
|
}
|
||||||
// We set the element as selected...
|
|
||||||
State.state.selectedElement.setData(feature);
|
|
||||||
|
|
||||||
// We mark the event as consumed
|
layer.on("click", (e) => {
|
||||||
L.DomEvent.stop(e);
|
// @ts-ignore
|
||||||
});
|
openPopup(e.latlng);
|
||||||
}
|
// We mark the event as consumed
|
||||||
}
|
L.DomEvent.stop(e);
|
||||||
)
|
});
|
||||||
;
|
|
||||||
|
if (feature.properties.id.replace(/\//g, "_") === Hash.Get().data) {
|
||||||
|
const center = GeoOperations.centerpoint(feature).geometry.coordinates;
|
||||||
|
openPopup({lat: center[1], lng: center[0]})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (this.combinedIsDisplayed.data) {
|
if (this.combinedIsDisplayed.data) {
|
||||||
this._geolayer.addTo(State.state.bm.map);
|
this._geolayer.addTo(State.state.bm.map);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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++) {
|
||||||
|
|
18
Logic/Web/Hash.ts
Normal file
18
Logic/Web/Hash.ts
Normal 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
* 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 {
|
||||||
|
|
||||||
|
@ -57,7 +58,7 @@ 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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
23
State.ts
23
State.ts
|
@ -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
|
||||||
|
@ -21,9 +22,9 @@ 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.3-rc2+g";
|
public static vNumber = "0.1.3-rc4";
|
||||||
|
|
||||||
// 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 = {
|
||||||
addNewPointsUnlock: 0,
|
addNewPointsUnlock: 0,
|
||||||
|
@ -209,6 +210,22 @@ export default class State {
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
||||||
|
|
7
Svg.ts
7
Svg.ts
|
@ -79,6 +79,11 @@ export default class Svg {
|
||||||
public static direction_svg() { return new FixedUiElement(Svg.direction);}
|
public static direction_svg() { return new FixedUiElement(Svg.direction);}
|
||||||
public static direction_ui() { return new FixedUiElement(Svg.direction_img);}
|
public static direction_ui() { return new FixedUiElement(Svg.direction_img);}
|
||||||
|
|
||||||
|
public static direction_gradient = " <svg xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"100\" height=\"100\" viewBox=\"0 0 100 100\" version=\"1.1\" id=\"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></dc:title> </cc:Work> </rdf:RDF> </metadata> <defs id=\"defs6\"> <linearGradient id=\"linearGradient820\"> <stop id=\"stop816\" offset=\"0\" style=\"stop-color:#000000;stop-opacity:1;\" /> <stop id=\"stop818\" offset=\"1\" style=\"stop-color:#000000;stop-opacity:0\" /> </linearGradient> <radialGradient gradientUnits=\"userSpaceOnUse\" gradientTransform=\"matrix(1.5439431,-0.01852438,0.02075364,1.7297431,-28.198837,-42.329969)\" 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 49.787737,49.857275 20.830626,9.2566092 C 35.979158,-2.144159 60.514289,-3.8195259 78.598237,9.0063685 Z\" id=\"path821\" /> </svg> "
|
||||||
|
public static direction_gradient_img = Img.AsImageElement(Svg.direction_gradient)
|
||||||
|
public static direction_gradient_svg() { return new FixedUiElement(Svg.direction_gradient);}
|
||||||
|
public static direction_gradient_ui() { return new FixedUiElement(Svg.direction_gradient_img);}
|
||||||
|
|
||||||
public static down = " <svg xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" version=\"1.0\" width=\"700\" height=\"700\" id=\"svg6\" sodipodi:docname=\"down.svg\" inkscape:version=\"0.92.4 (5da689c313, 2019-01-14)\"> <metadata id=\"metadata12\"> <rdf:RDF> <cc:Work rdf:about=\"\"> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" /> </cc:Work> </rdf:RDF> </metadata> <defs id=\"defs10\" /> <sodipodi:namedview pagecolor=\"#ffffff\" bordercolor=\"#666666\" borderopacity=\"1\" objecttolerance=\"10\" gridtolerance=\"10\" guidetolerance=\"10\" inkscape:pageopacity=\"0\" inkscape:pageshadow=\"2\" inkscape:window-width=\"1920\" inkscape:window-height=\"1001\" id=\"namedview8\" showgrid=\"false\" inkscape:zoom=\"0.33714286\" inkscape:cx=\"477.91309\" inkscape:cy=\"350\" inkscape:window-x=\"0\" inkscape:window-y=\"0\" inkscape:window-maximized=\"1\" inkscape:current-layer=\"svg6\" /> <g transform=\"rotate(-180,342.1439,335.17672)\" id=\"g4\"> <path d=\"m -20,670.71582 c 0,-1.85843 349.99229,-699.98853 350.57213,-699.28671 1.94549,2.35478 350.06752,699.46087 349.427,699.71927 -0.41837,0.16878 -79.29725,-33.69092 -175.2864,-75.24377 l -174.52574,-75.55065 -174.2421,75.53732 c -95.83317,41.54551 -174.625237,75.5373 -175.093498,75.5373 -0.46826,0 -0.851382,-0.32075 -0.851382,-0.71276 z\" style=\"fill:#00ff00;stroke:none\" id=\"path2\" inkscape:connector-curvature=\"0\" /> </g> </svg> "
|
public static down = " <svg xmlns:dc=\"http://purl.org/dc/elements/1.1/\" xmlns:cc=\"http://creativecommons.org/ns#\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:sodipodi=\"http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd\" xmlns:inkscape=\"http://www.inkscape.org/namespaces/inkscape\" version=\"1.0\" width=\"700\" height=\"700\" id=\"svg6\" sodipodi:docname=\"down.svg\" inkscape:version=\"0.92.4 (5da689c313, 2019-01-14)\"> <metadata id=\"metadata12\"> <rdf:RDF> <cc:Work rdf:about=\"\"> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource=\"http://purl.org/dc/dcmitype/StillImage\" /> </cc:Work> </rdf:RDF> </metadata> <defs id=\"defs10\" /> <sodipodi:namedview pagecolor=\"#ffffff\" bordercolor=\"#666666\" borderopacity=\"1\" objecttolerance=\"10\" gridtolerance=\"10\" guidetolerance=\"10\" inkscape:pageopacity=\"0\" inkscape:pageshadow=\"2\" inkscape:window-width=\"1920\" inkscape:window-height=\"1001\" id=\"namedview8\" showgrid=\"false\" inkscape:zoom=\"0.33714286\" inkscape:cx=\"477.91309\" inkscape:cy=\"350\" inkscape:window-x=\"0\" inkscape:window-y=\"0\" inkscape:window-maximized=\"1\" inkscape:current-layer=\"svg6\" /> <g transform=\"rotate(-180,342.1439,335.17672)\" id=\"g4\"> <path d=\"m -20,670.71582 c 0,-1.85843 349.99229,-699.98853 350.57213,-699.28671 1.94549,2.35478 350.06752,699.46087 349.427,699.71927 -0.41837,0.16878 -79.29725,-33.69092 -175.2864,-75.24377 l -174.52574,-75.55065 -174.2421,75.53732 c -95.83317,41.54551 -174.625237,75.5373 -175.093498,75.5373 -0.46826,0 -0.851382,-0.32075 -0.851382,-0.71276 z\" style=\"fill:#00ff00;stroke:none\" id=\"path2\" inkscape:connector-curvature=\"0\" /> </g> </svg> "
|
||||||
public static down_img = Img.AsImageElement(Svg.down)
|
public static down_img = Img.AsImageElement(Svg.down)
|
||||||
public static down_svg() { return new FixedUiElement(Svg.down);}
|
public static down_svg() { return new FixedUiElement(Svg.down);}
|
||||||
|
@ -214,4 +219,4 @@ export default class Svg {
|
||||||
public static wikipedia_svg() { return new FixedUiElement(Svg.wikipedia);}
|
public static wikipedia_svg() { return new FixedUiElement(Svg.wikipedia);}
|
||||||
public static wikipedia_ui() { return new FixedUiElement(Svg.wikipedia_img);}
|
public static wikipedia_ui() { return new FixedUiElement(Svg.wikipedia_img);}
|
||||||
|
|
||||||
}
|
public static All = {"add.svg": Svg.add,"addSmall.svg": Svg.addSmall,"ampersand.svg": Svg.ampersand,"arrow-left-smooth.svg": Svg.arrow_left_smooth,"arrow-right-smooth.svg": Svg.arrow_right_smooth,"bug.svg": Svg.bug,"camera-plus.svg": Svg.camera_plus,"checkmark.svg": Svg.checkmark,"close.svg": Svg.close,"compass.svg": Svg.compass,"crosshair-blue-center.svg": Svg.crosshair_blue_center,"crosshair-blue.svg": Svg.crosshair_blue,"crosshair.svg": Svg.crosshair,"delete_icon.svg": Svg.delete_icon,"direction.svg": Svg.direction,"direction_gradient.svg": Svg.direction_gradient,"down.svg": Svg.down,"envelope.svg": Svg.envelope,"floppy.svg": Svg.floppy,"gear.svg": Svg.gear,"help.svg": Svg.help,"home.svg": Svg.home,"home_white_bg.svg": Svg.home_white_bg,"josm_logo.svg": Svg.josm_logo,"layers.svg": Svg.layers,"layersAdd.svg": Svg.layersAdd,"logo.svg": Svg.logo,"logout.svg": Svg.logout,"mapillary.svg": Svg.mapillary,"no_checkmark.svg": Svg.no_checkmark,"or.svg": Svg.or,"osm-logo-us.svg": Svg.osm_logo_us,"osm-logo.svg": Svg.osm_logo,"pencil.svg": Svg.pencil,"pop-out.svg": Svg.pop_out,"reload.svg": Svg.reload,"search.svg": Svg.search,"share.svg": Svg.share,"star.svg": Svg.star,"statistics.svg": Svg.statistics,"up.svg": Svg.up,"wikimedia-commons-white.svg": Svg.wikimedia_commons_white,"wikipedia.svg": Svg.wikipedia};}
|
||||||
|
|
|
@ -14,7 +14,6 @@ export class FullScreenMessageBox extends UIElement {
|
||||||
constructor(onClear: (() => void)) {
|
constructor(onClear: (() => void)) {
|
||||||
super(State.state.fullScreenMessage);
|
super(State.state.fullScreenMessage);
|
||||||
this.HideOnEmpty(true);
|
this.HideOnEmpty(true);
|
||||||
const self = this;
|
|
||||||
|
|
||||||
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")])
|
||||||
|
|
|
@ -2,6 +2,10 @@ import {InputElement} from "./InputElement";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import Svg from "../../Svg";
|
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
|
* Selects a direction in degrees
|
||||||
|
@ -34,8 +38,8 @@ export default class DirectionInput extends InputElement<string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): string {
|
||||||
console.log("Inner render direction")
|
|
||||||
return new Combine([
|
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(
|
Svg.direction_svg().SetStyle(
|
||||||
`position: absolute;top: 0;left: 0;width: 100%;height: 100%;rotate:${this.value.data}deg;`)
|
`position: absolute;top: 0;left: 0;width: 100%;height: 100%;rotate:${this.value.data}deg;`)
|
||||||
.SetClass("direction-svg"),
|
.SetClass("direction-svg"),
|
||||||
|
@ -47,8 +51,7 @@ export default class DirectionInput extends InputElement<string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected InnerUpdate(htmlElement: HTMLElement) {
|
protected InnerUpdate(htmlElement: HTMLElement) {
|
||||||
console.log("Inner update direction")
|
super.InnerUpdate(htmlElement);
|
||||||
super.InnerUpdate(htmlElement);
|
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
function onPosChange(x: number, y: number) {
|
function onPosChange(x: number, y: number) {
|
||||||
|
@ -57,7 +60,7 @@ export default class DirectionInput extends InputElement<string> {
|
||||||
const dy = (rect.top + rect.bottom) / 2 - y;
|
const dy = (rect.top + rect.bottom) / 2 - y;
|
||||||
const angle = 180 * Math.atan2(dy, dx) / Math.PI;
|
const angle = 180 * Math.atan2(dy, dx) / Math.PI;
|
||||||
const angleGeo = Math.floor((450 - angle) % 360);
|
const angleGeo = Math.floor((450 - angle) % 360);
|
||||||
self.value.setData(""+angleGeo)
|
self.value.setData("" + angleGeo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,9 @@ interface TextFieldDef {
|
||||||
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 {
|
||||||
|
@ -26,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;
|
||||||
|
@ -197,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;
|
||||||
|
@ -230,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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,8 @@ export class FeatureInfoBox extends UIElement {
|
||||||
this._layerConfig = layerConfig;
|
this._layerConfig = layerConfig;
|
||||||
|
|
||||||
|
|
||||||
this._title = new TagRenderingAnswer(tags, layerConfig.title)
|
this._title = layerConfig.title === undefined ? undefined :
|
||||||
|
new TagRenderingAnswer(tags, layerConfig.title)
|
||||||
.SetClass("featureinfobox-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)))
|
||||||
|
|
|
@ -15,6 +15,9 @@ export default class TagRenderingAnswer extends UIElement {
|
||||||
super(tags);
|
super(tags);
|
||||||
this._tags = tags;
|
this._tags = tags;
|
||||||
this._configuration = configuration;
|
this._configuration = configuration;
|
||||||
|
if(configuration === undefined){
|
||||||
|
throw "Trying to generate a tagRenderingAnswer without configuration..."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): string {
|
||||||
|
|
|
@ -251,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]);
|
||||||
|
|
|
@ -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[] {
|
||||||
|
|
|
@ -94,15 +94,6 @@ 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_white_bg),
|
|
||||||
iconSize: [30, 30],
|
|
||||||
iconAnchor: [15, 15]
|
|
||||||
});
|
|
||||||
L.marker([user.home.lat, user.home.lon], {icon: icon}).addTo(State.state.bm.map)
|
|
||||||
}
|
|
||||||
|
|
||||||
const settings =
|
const settings =
|
||||||
new Link(Svg.gear_svg(),
|
new Link(Svg.gear_svg(),
|
||||||
`https://www.openstreetmap.org/user/${encodeURIComponent(user.name)}/account`,
|
`https://www.openstreetmap.org/user/${encodeURIComponent(user.name)}/account`,
|
||||||
|
|
3
Utils.ts
3
Utils.ts
|
@ -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, '&')
|
return str.replace(/&/g, '&')
|
||||||
|
@ -167,4 +168,6 @@ export class Utils {
|
||||||
console.log("Added custom layout ",location)
|
console.log("Added custom layout ",location)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
32
assets/layers/direction/direction.json
Normal file
32
assets/layers/direction/direction.json
Normal 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}",
|
||||||
|
"mappings": [
|
||||||
|
{
|
||||||
|
"if": "direction~*",
|
||||||
|
"then": "{direction}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"iconSize": "200,200,center",
|
||||||
|
"color": "--catch-detail-color",
|
||||||
|
"stroke": "0",
|
||||||
|
"presets": [],
|
||||||
|
"wayHandling": 2
|
||||||
|
}
|
54
assets/svg/direction_gradient.svg
Normal file
54
assets/svg/direction_gradient.svg
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<?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"
|
||||||
|
width="100"
|
||||||
|
height="100"
|
||||||
|
viewBox="0 0 100 100"
|
||||||
|
version="1.1"
|
||||||
|
id="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></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,-28.198837,-42.329969)"
|
||||||
|
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 49.787737,49.857275 20.830626,9.2566092 C 35.979158,-2.144159 60.514289,-3.8195259 78.598237,9.0063685 Z"
|
||||||
|
id="path821" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -10,3 +10,17 @@ html {
|
||||||
--shadow-color: #0f0 !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;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
"customCss": "./assets/themes/surveillance_cameras/custom_theme.css",
|
"customCss": "./assets/themes/surveillance_cameras/custom_theme.css",
|
||||||
"defaultBackgroundId": "Stadia.AlidadeSmoothDark",
|
"defaultBackgroundId": "Stadia.AlidadeSmoothDark",
|
||||||
"layers": [
|
"layers": [
|
||||||
|
"direction",
|
||||||
{
|
{
|
||||||
"id": "cameras",
|
"id": "cameras",
|
||||||
"name": {
|
"name": {
|
||||||
|
@ -56,6 +57,7 @@
|
||||||
"tagRenderings": [
|
"tagRenderings": [
|
||||||
"images",
|
"images",
|
||||||
{
|
{
|
||||||
|
"#": "Camera type: fixed; panning; dome",
|
||||||
"question": {
|
"question": {
|
||||||
"en": "What kind of camera is this?",
|
"en": "What kind of camera is this?",
|
||||||
"nl": "Wat voor soort camera is dit?"
|
"nl": "Wat voor soort camera is dit?"
|
||||||
|
@ -97,18 +99,32 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"#": "direction. We don't ask this for a dome on a pole or ceiling as it has a 360° view",
|
||||||
"question": {
|
"question": {
|
||||||
"en": "In which geographical direction does this camera film?",
|
"en": "In which geographical direction does this camera film?",
|
||||||
"nl": "Naar welke geografische richting filmt deze camera?"
|
"nl": "Naar welke geografische richting filmt deze camera?"
|
||||||
},
|
},
|
||||||
"render": "Films to {camera:direction}",
|
"render": "Films to {camera:direction}",
|
||||||
"condition": "camera:type!=dome",
|
"condition": {
|
||||||
|
"not": {
|
||||||
|
"and": [
|
||||||
|
"camera:type=dome",
|
||||||
|
{
|
||||||
|
"or": [
|
||||||
|
"camera:mount=ceiling",
|
||||||
|
"camera:mount=pole"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"freeform": {
|
"freeform": {
|
||||||
"key": "camera:direction",
|
"key": "camera:direction",
|
||||||
"type": "direction"
|
"type": "direction"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"#": "Operator",
|
||||||
"freeform": {
|
"freeform": {
|
||||||
"key": "operator"
|
"key": "operator"
|
||||||
},
|
},
|
||||||
|
@ -122,6 +138,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"#": "Surveillance type: public, outdoor, indoor",
|
||||||
"question": {
|
"question": {
|
||||||
"en": "What kind of surveillance is this camera",
|
"en": "What kind of surveillance is this camera",
|
||||||
"nl": "Wat soort bewaking wordt hier uitgevoerd?"
|
"nl": "Wat soort bewaking wordt hier uitgevoerd?"
|
||||||
|
@ -134,8 +151,8 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"then": {
|
"then": {
|
||||||
"en": "A public area is surveilled, such as a street, a bridge, a square, a park, a train station...",
|
"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..."
|
"nl": "Bewaking van de publieke ruilmte, dus een straat, een brug, een park, een plein, een stationsgebouw, een publiek toegankelijke gang of tunnel..."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -156,13 +173,67 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"then": {
|
"then": {
|
||||||
"nl": "Een private binnenruimte wordt bewaakt, bv. een wiinkel, een parkeergarage, ...",
|
"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, ..."
|
"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": {
|
"question": {
|
||||||
"en": "What exactly is surveilled here?",
|
"en": "What exactly is surveilled here?",
|
||||||
"nl": "Wat wordt hier precies bewaakt?"
|
"nl": "Wat wordt hier precies bewaakt?"
|
||||||
|
@ -244,6 +315,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"#": "camera:mount",
|
||||||
"question": {
|
"question": {
|
||||||
"en": "How is this camera placed?",
|
"en": "How is this camera placed?",
|
||||||
"nl": "Hoe is deze camera geplaatst?"
|
"nl": "Hoe is deze camera geplaatst?"
|
||||||
|
@ -267,10 +339,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"if": "camera:mount=pole",
|
"if": "camera:mount=ceiling",
|
||||||
"then": {
|
"then": {
|
||||||
"en": "This camera is placed one a pole",
|
"en": "This camera is placed on the ceiling",
|
||||||
"nl": "Deze camera staat op een paal"
|
"nl": "Deze camera hangt aan het plafond"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -293,7 +365,7 @@
|
||||||
"render": "50,50,center"
|
"render": "50,50,center"
|
||||||
},
|
},
|
||||||
"color": {
|
"color": {
|
||||||
"render": "#00f"
|
"render": "#f00"
|
||||||
},
|
},
|
||||||
"presets": [
|
"presets": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -45,10 +45,6 @@ Contains tweaks for small screens
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-popup {
|
.leaflet-popup {
|
||||||
transform: unset !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-popup-content {
|
|
||||||
/* On mobile, the popups are shown as a full-screen element */
|
/* On mobile, the popups are shown as a full-screen element */
|
||||||
display: none;
|
display: none;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
|
|
@ -38,6 +38,11 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.question svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.question-text {
|
.question-text {
|
||||||
font-size: larger;
|
font-size: larger;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
|
@ -255,7 +255,6 @@ a {
|
||||||
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;
|
||||||
|
@ -410,6 +409,12 @@ a {
|
||||||
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 {
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
npm install
|
|
|
@ -16,10 +16,11 @@
|
||||||
"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: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: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",
|
||||||
|
|
|
@ -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 {
|
||||||
|
@ -100,7 +100,7 @@ const alreadyWritten = []
|
||||||
|
|
||||||
function createIcon(iconPath: string, size: number, layout: LayoutConfig) {
|
function createIcon(iconPath: string, size: number, layout: LayoutConfig) {
|
||||||
let name = iconPath.split(".").slice(0, -1).join(".");
|
let name = iconPath.split(".").slice(0, -1).join(".");
|
||||||
if (name.startsWith("./")) {
|
if (name.startsWith("../")) {
|
||||||
name = name.substr(2)
|
name = name.substr(2)
|
||||||
}
|
}
|
||||||
const newname = `${name}${size}.png`
|
const newname = `${name}${size}.png`
|
||||||
|
@ -151,7 +151,7 @@ function createManifest(layout: LayoutConfig, relativePath: string) {
|
||||||
let path = layout.icon;
|
let path = layout.icon;
|
||||||
if (layout.icon.startsWith("<")) {
|
if (layout.icon.startsWith("<")) {
|
||||||
// THis is already the svg
|
// THis is already the svg
|
||||||
path = "./assets/generated/" + layout.id + "_logo.svg"
|
path = "../assets/generated/" + layout.id + "_logo.svg"
|
||||||
writeFileSync(path, layout.icon)
|
writeFileSync(path, layout.icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,19 +212,19 @@ function createLandingPage(layout: LayoutConfig) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const og = `
|
const og = `
|
||||||
<meta property="og:image" content="${ogImage ?? './assets/svg/add.svg'}">
|
<meta property="og:image" content="${ogImage ?? '../assets/svg/add.svg'}">
|
||||||
<meta property="og:title" content="${ogTitle}">
|
<meta property="og:title" content="${ogTitle}">
|
||||||
<meta property="og:description" content="${ogDescr}">`
|
<meta property="og:description" content="${ogDescr}">`
|
||||||
|
|
||||||
let icon = layout.icon;
|
let icon = layout.icon;
|
||||||
if (icon.startsWith("<?xml") || icon.startsWith("<svg")) {
|
if (icon.startsWith("<?xml") || icon.startsWith("<svg")) {
|
||||||
// This already is an svg
|
// This already is an svg
|
||||||
icon = `./assets/generated/${layout.id}_icon.svg`
|
icon = `../assets/generated/${layout.id}_icon.svg`
|
||||||
writeFileSync(icon, layout.icon);
|
writeFileSync(icon, layout.icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = template
|
let output = template
|
||||||
.replace(`./manifest.manifest`, `./${enc(layout.id)}.webmanifest`)
|
.replace(`../manifest.manifest`, `../${enc(layout.id)}.webmanifest`)
|
||||||
.replace("<!-- $$$OG-META -->", og)
|
.replace("<!-- $$$OG-META -->", og)
|
||||||
.replace(/<title>.+?<\/title>/, `<title>${ogTitle}</title>`)
|
.replace(/<title>.+?<\/title>/, `<title>${ogTitle}</title>`)
|
||||||
.replace("Loading MapComplete, hang on...", `Loading MapComplete theme <i>${ogTitle}</i>...`)
|
.replace("Loading MapComplete, hang on...", `Loading MapComplete theme <i>${ogTitle}</i>...`)
|
||||||
|
@ -251,7 +251,7 @@ let wikiPage = "{|class=\"wikitable sortable\"\n" +
|
||||||
"|-";
|
"|-";
|
||||||
|
|
||||||
|
|
||||||
const generatedDir = "./assets/generated";
|
const generatedDir = "../assets/generated";
|
||||||
if (! existsSync(generatedDir)) {
|
if (! existsSync(generatedDir)) {
|
||||||
mkdirSync(generatedDir)
|
mkdirSync(generatedDir)
|
||||||
}
|
}
|
|
@ -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")) {
|
||||||
|
@ -26,47 +27,11 @@ function genImages() {
|
||||||
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}_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()
|
40
scripts/generateTranslations.ts
Normal file
40
scripts/generateTranslations.ts
Normal 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()
|
|
@ -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>
|
||||||
|
|
2
test.ts
2
test.ts
|
@ -6,7 +6,7 @@ import {UIEventSource} from "./Logic/UIEventSource";
|
||||||
import {VariableUiElement} from "./UI/Base/VariableUIElement";
|
import {VariableUiElement} from "./UI/Base/VariableUIElement";
|
||||||
|
|
||||||
const d = new UIEventSource("90");
|
const d = new UIEventSource("90");
|
||||||
new Direction(d).AttachTo("maindiv")
|
new Direction(d, [51.21576,3.22001]).AttachTo("maindiv")
|
||||||
new VariableUiElement(d.map(d => "" + d + "°")).AttachTo("extradiv")
|
new VariableUiElement(d.map(d => "" + d + "°")).AttachTo("extradiv")
|
||||||
|
|
||||||
UIEventSource.Chronic(25, () => {
|
UIEventSource.Chronic(25, () => {
|
||||||
|
|
Loading…
Reference in a new issue