diff --git a/Customizations/JSON/FromJSON.ts b/Customizations/JSON/FromJSON.ts index 949288a..ed66275 100644 --- a/Customizations/JSON/FromJSON.ts +++ b/Customizations/JSON/FromJSON.ts @@ -11,7 +11,7 @@ export class FromJSON { } public static Tag(json: AndOrTagConfigJson | string, context: string = ""): TagsFilter { - if(json === undefined){ + if (json === undefined) { throw `Error while parsing a tag: 'json' is undefined in ${context}. Make sure all the tags are defined and at least one tag is present in a complex expression` } if (typeof (json) == "string") { @@ -33,7 +33,7 @@ export class FromJSON { split[1] = "..*" } return new RegexTag( - new RegExp("^" + split[0] + "$"), + new RegExp("^" + split[0] + "$"), new RegExp("^" + split[1] + "$") ); } @@ -58,11 +58,17 @@ export class FromJSON { new RegExp("^" + split[1] + "$") ); } - const split = Utils.SplitFirst(tag, "="); - if(split[1] == "*"){ - throw `Error while parsing tag '${tag}' in ${context}: detected a wildcard on a normal value. Use a regex pattern instead` + if (tag.indexOf("=") >= 0) { + + + const split = Utils.SplitFirst(tag, "="); + if (split[1] == "*") { + throw `Error while parsing tag '${tag}' in ${context}: detected a wildcard on a normal value. Use a regex pattern instead` + } + return new Tag(split[0], split[1]) } - return new Tag(split[0], split[1]) + throw `Error while parsing tag '${tag}' in ${context}: no key part and value part were found` + } if (json.and !== undefined) { return new And(json.and.map(t => FromJSON.Tag(t, context))); diff --git a/Customizations/JSON/LayerConfig.ts b/Customizations/JSON/LayerConfig.ts index 8cd20fd..42c0936 100644 --- a/Customizations/JSON/LayerConfig.ts +++ b/Customizations/JSON/LayerConfig.ts @@ -253,20 +253,13 @@ export default class LayerConfig { let sourceParts = iconUrl.split(";"); function genHtmlFromString(sourcePart: string): UIElement { - const style = `width:100%;height:100%;rotate:${rotation};display:block;position: absolute; top: 0, left: 0`; + const style = `width:100%;height:100%;transform: rotate( ${rotation} );display:block;position: absolute; top: 0, left: 0`; let html: UIElement = new FixedUiElement(``); - const match = sourcePart.match(/([a-zA-Z0-9_]*):#([0-9a-fA-F]{3,6})/) + const match = sourcePart.match(/([a-zA-Z0-9_]*):([^;]*)/) if (match !== null && Svg.All[match[1] + ".svg"] !== undefined) { html = new Combine([ (Svg.All[match[1] + ".svg"] as string) - .replace(/#000000/g, "#" + match[2]) - ]).SetStyle(style); - } - - if (sourcePart.startsWith(Utils.assets_path)) { - const key = sourcePart.substr(Utils.assets_path.length); - html = new Combine([ - (Svg.All[key] as string).replace(/stop-color:#000000/g, 'stop-color:' + color) + .replace(/#000000/g, match[2]) ]).SetStyle(style); } return html; diff --git a/InitUiElements.ts b/InitUiElements.ts index fbaa371..9193142 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -1,53 +1,45 @@ -import Translations from "./UI/i18n/Translations"; -import {TabbedComponent} from "./UI/Base/TabbedComponent"; -import {ShareScreen} from "./UI/ShareScreen"; import {FixedUiElement} from "./UI/Base/FixedUiElement"; import CheckBox from "./UI/Input/CheckBox"; import Combine from "./UI/Base/Combine"; -import {UIElement} from "./UI/UIElement"; -import {MoreScreen} from "./UI/MoreScreen"; -import {FilteredLayer} from "./Logic/FilteredLayer"; -import {Basemap} from "./UI/Basemap"; +import {Basemap} from "./UI/BigComponents/Basemap"; import State from "./State"; -import {WelcomeMessage} from "./UI/WelcomeMessage"; -import {LayerSelection} from "./UI/LayerSelection"; -import {VariableUiElement} from "./UI/Base/VariableUIElement"; import LoadFromOverpass from "./Logic/Actors/UpdateFromOverpass"; import {UIEventSource} from "./Logic/UIEventSource"; import {QueryParameters} from "./Logic/Web/QueryParameters"; -import {PersonalLayersPanel} from "./UI/PersonalLayersPanel"; -import Locale from "./UI/i18n/Locale"; -import {StrayClickHandler} from "./Logic/Actors/StrayClickHandler"; -import {SimpleAddUI} from "./UI/SimpleAddUI"; -import {CenterMessageBox} from "./UI/CenterMessageBox"; +import StrayClickHandler from "./Logic/Actors/StrayClickHandler"; +import SimpleAddUI from "./UI/BigComponents/SimpleAddUI"; +import CenterMessageBox from "./UI/CenterMessageBox"; import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; import {TagUtils} from "./Logic/Tags"; -import {UserBadge} from "./UI/UserBadge"; -import {SearchAndGo} from "./UI/SearchAndGo"; -import {FullScreenMessageBox} from "./UI/FullScreenMessageBoxHandler"; -import {GeoLocationHandler} from "./Logic/Actors/GeoLocationHandler"; +import UserBadge from "./UI/BigComponents/UserBadge"; +import SearchAndGo from "./UI/BigComponents/SearchAndGo"; +import FullScreenMessageBox from "./UI/FullScreenMessageBoxHandler"; +import GeoLocationHandler from "./Logic/Actors/GeoLocationHandler"; import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; import {Utils} from "./Utils"; -import BackgroundSelector from "./UI/BackgroundSelector"; -import {FeatureInfoBox} from "./UI/Popup/FeatureInfoBox"; +import FeatureInfoBox from "./UI/Popup/FeatureInfoBox"; import Svg from "./Svg"; import Link from "./UI/Base/Link"; import * as personal from "./assets/themes/personalLayout/personalLayout.json" import LayoutConfig from "./Customizations/JSON/LayoutConfig"; import * as L from "leaflet"; import Img from "./UI/Base/Img"; -import {UserDetails} from "./Logic/Osm/OsmConnection"; -import Attribution from "./UI/Misc/Attribution"; -import Constants from "./Models/Constants"; +import UserDetails from "./Logic/Osm/OsmConnection"; +import Attribution from "./UI/BigComponents/Attribution"; import MetaTagging from "./Logic/MetaTagging"; import FeatureSourceMerger from "./Logic/FeatureSource/FeatureSourceMerger"; import RememberingSource from "./Logic/FeatureSource/RememberingSource"; import FilteringFeatureSource from "./Logic/FeatureSource/FilteringFeatureSource"; import WayHandlingApplyingFeatureSource from "./Logic/FeatureSource/WayHandlingApplyingFeatureSource"; -import FeatureSource from "./Logic/FeatureSource/FeatureSource"; import NoOverlapSource from "./Logic/FeatureSource/NoOverlapSource"; import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; import LayerResetter from "./Logic/Actors/LayerResetter"; +import FullWelcomePaneWithTabs from "./UI/BigComponents/FullWelcomePaneWithTabs"; +import LayerControlPanel from "./UI/BigComponents/LayerControlPanel"; +import FeatureSwitched from "./UI/Base/FeatureSwitched"; +import FeatureDuplicatorPerLayer from "./Logic/FeatureSource/FeatureDuplicatorPerLayer"; +import LayerConfig from "./Customizations/JSON/LayerConfig"; +import ShowDataLayer from "./UI/ShowDataLayer"; export class InitUiElements { @@ -87,10 +79,6 @@ export class InitUiElements { } - - - - InitUiElements.InitBaseMap(); new FixedUiElement("").AttachTo("decoration-desktop"); // Remove the decoration @@ -216,13 +204,15 @@ export class InitUiElements { marker.addTo(State.state.leafletMap.data) }); - new GeoLocationHandler( - State.state.currentGPSLocation, - State.state.leafletMap, - State.state.featureSwitchGeolocation - ) - .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;`) + new FeatureSwitched( + new GeoLocationHandler( + State.state.currentGPSLocation, + State.state.leafletMap + ) + .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;`) + , State.state.featureSwitchGeolocation) .AttachTo("geolocate-button"); + State.state.locationControl.ping(); } @@ -254,23 +244,18 @@ export class InitUiElements { } } - static OnlyIf(featureSwitch: UIEventSource, callback: () => void) { - featureSwitch.addCallback(() => { + private static OnlyIf(featureSwitch: UIEventSource, callback: () => void) { + featureSwitch.addCallbackAndRun(() => { if (featureSwitch.data) { callback(); } }); - - if (featureSwitch.data) { - callback(); - } - } - static InitWelcomeMessage() { + private static InitWelcomeMessage() { - const fullOptions = this.CreateWelcomePane(); + const fullOptions = new FullWelcomePaneWithTabs(); const help = Svg.help_svg().SetClass("open-welcome-button"); const close = Svg.close_svg().SetClass("close-welcome-button"); @@ -298,7 +283,7 @@ export class InitUiElements { }) - const fullOptions2 = this.CreateWelcomePane(); + const fullOptions2 = new FullWelcomePaneWithTabs(); State.state.fullScreenMessage.setData(fullOptions2) Svg.help_svg() @@ -311,15 +296,11 @@ export class InitUiElements { } - static InitLayerSelection() { + private static InitLayerSelection() { InitUiElements.OnlyIf(State.state.featureSwitchLayers, () => { - const layerControlPanel = this.GenerateLayerControlPanel(); - if (layerControlPanel === undefined) { - return; - } - - layerControlPanel.SetStyle("display:block;padding:0.75em;border-radius:1em;"); + const layerControlPanel = new LayerControlPanel() + .SetStyle("display:block;padding:0.75em;border-radius:1em;"); const closeButton = Svg.close_svg().SetClass("layer-selection-toggle").SetStyle(" background: var(--subtle-detail-color);") const checkbox = new CheckBox( new Combine([ @@ -334,18 +315,19 @@ export class InitUiElements { checkbox.AttachTo("layer-selection"); - State.state.locationControl.addCallback(() => { + State.state.locationControl + .addCallback(() => { // Close the layer selection when the map is moved - checkbox.isEnabled.setData(false); + // checkbox.isEnabled.setData(false); }); - const fullScreen = this.GenerateLayerControlPanel(); + const fullScreen = new LayerControlPanel(); checkbox.isEnabled.addCallback(isEnabled => { if (isEnabled) { State.state.fullScreenMessage.setData(fullScreen); } }) - State.state.fullScreenMessage.addCallbackAndRun(latest => { + State.state.fullScreenMessage.addCallback(latest => { if (latest === undefined) { checkbox.isEnabled.setData(false); } @@ -354,7 +336,7 @@ export class InitUiElements { }); } - static InitBaseMap() { + private static InitBaseMap() { State.state.availableBackgroundLayers = new AvailableBaseLayers(State.state.locationControl).availableEditorLayers; State.state.backgroundLayer = QueryParameters.GetQueryParameter("background", @@ -371,8 +353,6 @@ export class InitUiElements { }, [], layer => layer.id); - - new LayerResetter( State.state.backgroundLayer, State.state.locationControl, State.state.availableBackgroundLayers, State.state.layoutToUse.map((layout: LayoutConfig) => layout.defaultBackgroundId)); @@ -393,61 +373,33 @@ export class InitUiElements { } - static InitLayers() { - - + private static InitLayers() { const state = State.state; - const flayers: FilteredLayer[] = [] + const flayers: { layerDef: LayerConfig, isDisplayed: UIEventSource }[] = [] for (const layer of state.layoutToUse.data.layers) { if (typeof (layer) === "string") { throw "Layer " + layer + " was not substituted"; } - let generateContents = (tags: UIEventSource) => new FeatureInfoBox(tags, layer); - if (layer.title === undefined && (layer.tagRenderings ?? []).length === 0) { - generateContents = undefined; + const isDisplayed = QueryParameters.GetQueryParameter("layer-" + layer.id, "true", "Wether or not layer " + layer.id + " is shown") + .map((str) => str !== "false", [], (b) => b.toString()); + const flayer = { + isDisplayed: isDisplayed, + layerDef: layer } - - const flayer: FilteredLayer = new FilteredLayer(layer, generateContents); flayers.push(flayer); - - QueryParameters.GetQueryParameter("layer-" + layer.id, "true", "Wether or not layer " + layer.id + " is shown") - .map((str) => str !== "false", [], (b) => b.toString()) - .syncWith( - flayer.isDisplayed - ) } State.state.filteredLayers.setData(flayers); - function addMatchingIds(src: FeatureSource) { - - src.features.addCallback(features => { - features.forEach(f => { - const properties = f.feature.properties; - if (properties._matching_layer_id) { - return; - } - - for (const flayer of flayers) { - if (flayer.layerDef.overpassTags.matchesProperties(properties)) { - properties._matching_layer_id = flayer.layerDef.id; - break; - } - } - }) - }); - } + const updater = new LoadFromOverpass(state.locationControl, state.layoutToUse, state.leafletMap); State.state.layerUpdater = updater; - - addMatchingIds(updater); - addMatchingIds(State.state.changes); - + const source = new FilteringFeatureSource( @@ -455,9 +407,9 @@ export class InitUiElements { State.state.locationControl, new FeatureSourceMerger([ new RememberingSource(new WayHandlingApplyingFeatureSource(flayers, - new NoOverlapSource(flayers, updater) + new NoOverlapSource(flayers, new FeatureDuplicatorPerLayer(flayers, updater)) )), - State.state.changes])); + new FeatureDuplicatorPerLayer(flayers, State.state.changes)])); source.features.addCallback((featuresFreshness: { feature: any, freshness: Date }[]) => { @@ -466,25 +418,9 @@ export class InitUiElements { State.state.allElements.addElement(feature); }) MetaTagging.addMetatags(features); - - function renderLayers(layers) { - - - if (layers.length === 0) { - if (features.length > 0) { - console.warn("Got some leftovers: ", features.join("; ")) - } - return; - } - const layer = layers[0]; - const rest = layers.slice(1, layers.length); - features = layer.SetApplicableData(features); - renderLayers(rest); - } - - renderLayers(flayers); - }) + + new ShowDataLayer(source.features, State.state.leafletMap, flayers); } @@ -528,72 +464,4 @@ export class InitUiElements { new CenterMessageBox().AttachTo("centermessage"); } - - private static CreateWelcomePane() { - - const layoutToUse = State.state.layoutToUse.data; - let welcome: UIElement = new WelcomeMessage(); - if (layoutToUse.id === personal.id) { - welcome = new PersonalLayersPanel(); - } - - const tabs = [ - {header: ``, content: welcome}, - { - header: Svg.osm_logo_img, - content: Translations.t.general.openStreetMapIntro as UIElement - }, - - ] - - if (State.state.featureSwitchShareScreen.data) { - tabs.push({header: Svg.share_img, content: new ShareScreen()}); - } - - if (State.state.featureSwitchMoreQuests.data) { - - tabs.push({ - header: Svg.add_img, - content: new MoreScreen() - }); - } - - - tabs.push({ - header: Svg.help, - content: new VariableUiElement(State.state.osmConnection.userDetails.map(userdetails => { - if (userdetails.csCount < Constants.userJourney.mapCompleteHelpUnlock) { - return "" - } - return new Combine([Translations.t.general.aboutMapcomplete, "
Version " + Constants.vNumber]).Render(); - }, [Locale.language])) - } - ); - - - return new TabbedComponent(tabs, State.state.welcomeMessageOpenedTab) - .ListenTo(State.state.osmConnection.userDetails); - - } - - private static GenerateLayerControlPanel() { - - - let layerControlPanel: UIElement = undefined; - if (State.state.layoutToUse.data.enableBackgroundLayerSelection) { - layerControlPanel = new BackgroundSelector(); - layerControlPanel.SetStyle("margin:1em"); - layerControlPanel.onClick(() => { - }); - } - - if (State.state.filteredLayers.data.length > 1) { - const layerSelection = new LayerSelection(); - layerSelection.onClick(() => { - }); - layerControlPanel = new Combine([layerSelection, "
", layerControlPanel]); - } - return layerControlPanel; - } - } \ No newline at end of file diff --git a/Logic/Actors/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts index a8d2ec2..688c14a 100644 --- a/Logic/Actors/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -5,7 +5,7 @@ import {Utils} from "../../Utils"; import Svg from "../../Svg"; import Img from "../../UI/Base/Img"; -export class GeoLocationHandler extends UIElement { +export default class GeoLocationHandler extends UIElement { private readonly _isActive: UIEventSource = new UIEventSource(false); private readonly _permission: UIEventSource = new UIEventSource(""); @@ -13,17 +13,14 @@ export class GeoLocationHandler extends UIElement { private readonly _hasLocation: UIEventSource; private readonly _currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>; private readonly _leafletMap: UIEventSource; - private readonly _featureSwitch: UIEventSource; constructor(currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>, - leafletMap: UIEventSource, - featureSwitch: UIEventSource) { + leafletMap: UIEventSource) { super(undefined); this._currentGPSLocation = currentGPSLocation; this._leafletMap = leafletMap; - this._featureSwitch = featureSwitch; this._hasLocation = currentGPSLocation.map((location) => location !== undefined); - var self = this; + const self = this; import("../../vendor/Leaflet.AccuratePosition.js").then(() => { self.init(); }) @@ -92,10 +89,6 @@ export class GeoLocationHandler extends UIElement { } InnerRender(): string { - if (!this._featureSwitch.data) { - return ""; - } - if (this._hasLocation.data) { return Svg.crosshair_blue_img; } @@ -124,7 +117,7 @@ export class GeoLocationHandler extends UIElement { private StartGeolocating(zoomlevel = 19) { const self = this; - const map : any = this._leafletMap.data; + const map: any = this._leafletMap.data; if (self._permission.data === "denied") { return ""; } diff --git a/Logic/Actors/StrayClickHandler.ts b/Logic/Actors/StrayClickHandler.ts index 558a0d2..a7041d6 100644 --- a/Logic/Actors/StrayClickHandler.ts +++ b/Logic/Actors/StrayClickHandler.ts @@ -8,14 +8,14 @@ import Img from "../../UI/Base/Img"; * The stray-click-hanlders adds a marker to the map if no feature was clicked. * Shows the given uiToShow-element in the messagebox */ -export class StrayClickHandler { +export default class StrayClickHandler { private _lastMarker; private _uiToShow: (() => UIElement); constructor( - lastClickLocation: UIEventSource<{ lat: number, lon:number }>, + lastClickLocation: UIEventSource<{ lat: number, lon: number }>, selectedElement: UIEventSource, - filteredLayers: UIEventSource<{ readonly isDisplayed: UIEventSource}[]>, + filteredLayers: UIEventSource<{ readonly isDisplayed: UIEventSource }[]>, leafletMap: UIEventSource, fullscreenMessage: UIEventSource, uiToShow: (() => UIElement)) { @@ -23,14 +23,14 @@ export class StrayClickHandler { const self = this; filteredLayers.data.forEach((filteredLayer) => { filteredLayer.isDisplayed.addCallback(isEnabled => { - if(isEnabled && self._lastMarker && leafletMap.data !== undefined){ + if (isEnabled && self._lastMarker && leafletMap.data !== undefined) { // When a layer is activated, we remove the 'last click location' in order to force the user to reclick // This reclick might be at a location where a feature now appeared... - leafletMap.data.removeLayer(self._lastMarker); + leafletMap.data.removeLayer(self._lastMarker); } }) }) - + lastClickLocation.addCallback(function (lastClick) { selectedElement.setData(undefined); diff --git a/Logic/Actors/UpdateFromOverpass.ts b/Logic/Actors/UpdateFromOverpass.ts index ab4e95b..63067b5 100644 --- a/Logic/Actors/UpdateFromOverpass.ts +++ b/Logic/Actors/UpdateFromOverpass.ts @@ -38,6 +38,7 @@ export default class UpdateFromOverpass implements FeatureSource{ location: UIEventSource, layoutToUse: UIEventSource, leafletMap: UIEventSource) { + console.log("Crating overpass updater") this._location = location; this._layoutToUse = layoutToUse; this._leafletMap = leafletMap; diff --git a/Logic/FeatureSource/FeatureDuplicatorPerLayer.ts b/Logic/FeatureSource/FeatureDuplicatorPerLayer.ts new file mode 100644 index 0000000..b27ba2a --- /dev/null +++ b/Logic/FeatureSource/FeatureDuplicatorPerLayer.ts @@ -0,0 +1,68 @@ +import FeatureSource from "./FeatureSource"; +import {UIEventSource} from "../UIEventSource"; +import LayerConfig from "../../Customizations/JSON/LayerConfig"; + + +/** + * In some rare cases, some elements are shown on multiple layers (when 'passthrough' is enabled) + * If this is the case, multiple objects with a different _matching_layer_id are generated. + * If not, the _feature_layter_id is added + */ +export default class FeatureDuplicatorPerLayer implements FeatureSource { + public readonly features: UIEventSource<{ feature: any; freshness: Date }[]>; + + + constructor(layers: { layerDef: LayerConfig }[], upstream: FeatureSource) { + let noPassthroughts = true; + for (const layer of layers) { + if (layer.layerDef.passAllFeatures) { + noPassthroughts = false; + break; + } + } + + this.features = upstream.features.map(features => { + const newFeatures: { feature: any, freshness: Date }[] = []; + if(features === undefined){ + return newFeatures; + } + + + for (const f of features) { + if (f.feature._matching_layer_id) { + // Already matched previously + // We simply add it + newFeatures.push(f); + return; + } + + for (const layer of layers) { + if (layer.layerDef.overpassTags.matchesProperties(f.feature.properties)) { + if (layer.layerDef.passAllFeatures) { + + // We copy the feature; the "properties" field is kept identical though! + // Keeping "properties" identical is needed, as it might break the 'allElementStorage' otherwise + const newFeature = { + geometry: f.feature.geometry, + id: f.feature.id, + type: f.feature.type, + properties: f.feature.properties, + _matching_layer_id : layer.layerDef.id + } + newFeatures.push({feature: newFeature, freshness: f.freshness}); + } else { + // If not 'passAllFeatures', we are done + f.feature._matching_layer_id = layer.layerDef.id; + newFeatures.push(f); + break; + } + } + } + } + return newFeatures; + + }) + + } + +} \ No newline at end of file diff --git a/Logic/FeatureSource/FeatureSourceMerger.ts b/Logic/FeatureSource/FeatureSourceMerger.ts index 1755aef..2ea08c4 100644 --- a/Logic/FeatureSource/FeatureSourceMerger.ts +++ b/Logic/FeatureSource/FeatureSourceMerger.ts @@ -18,7 +18,7 @@ export default class FeatureSourceMerger implements FeatureSource { let all = {}; // Mapping 'id' -> {feature, freshness} for (const source of this._sources) { for (const f of source.features.data) { - const id = f.feature.properties.id+f.feature.geometry.type; + const id = f.feature.properties.id+f.feature.geometry.type+f.feature._matching_layer_id; const oldV = all[id]; if(oldV === undefined){ all[id] = f; diff --git a/Logic/FeatureSource/FilteringFeatureSource.ts b/Logic/FeatureSource/FilteringFeatureSource.ts index f3317aa..843ca28 100644 --- a/Logic/FeatureSource/FilteringFeatureSource.ts +++ b/Logic/FeatureSource/FilteringFeatureSource.ts @@ -14,14 +14,13 @@ export default class FilteringFeatureSource implements FeatureSource { upstream: FeatureSource) { const layerDict = {}; - + const self = this; - + function update() { - console.log("UPdating...") const features: { feature: any, freshness: Date }[] = upstream.features.data; const newFeatures = features.filter(f => { - const layerId = f.feature.properties._matching_layer_id; + const layerId = f.feature._matching_layer_id; if (layerId === undefined) { console.error(f) throw "feature._matching_layer_id is undefined" @@ -37,16 +36,22 @@ export default class FilteringFeatureSource implements FeatureSource { }); self.features.setData(newFeatures); } + for (const layer of layers) { layerDict[layer.layerDef.id] = layer; - layer.isDisplayed.addCallback(update) + layer.isDisplayed.addCallback(() => { + console.log("Updating due to layer change") + update()}) } - upstream.features.addCallback(update); - location.map(l => l.zoom).addCallback(update); + upstream.features.addCallback(() => { + console.log("Updating due to upstream change") + update()}); + location.map(l => l.zoom).addCallback(() => { + console.log("UPdating due to zoom level change") + update();}); } - } \ No newline at end of file diff --git a/Logic/FeatureSource/NoOverlapSource.ts b/Logic/FeatureSource/NoOverlapSource.ts index 9dd6681..73d69de 100644 --- a/Logic/FeatureSource/NoOverlapSource.ts +++ b/Logic/FeatureSource/NoOverlapSource.ts @@ -45,7 +45,7 @@ export default class NoOverlapSource { partitions[layerId] = [] } for (const feature of features) { - partitions[feature.feature.properties._matching_layer_id].push(feature); + partitions[feature.feature._matching_layer_id].push(feature); } // With this partitioning in hand, we run over every layer and remove every underlying feature if needed diff --git a/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts b/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts index 170e434..66faccf 100644 --- a/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts +++ b/Logic/FeatureSource/WayHandlingApplyingFeatureSource.ts @@ -32,7 +32,7 @@ export default class WayHandlingApplyingFeatureSource implements FeatureSource { const newFeatures: { feature: any, freshness: Date }[] = []; for (const f of features) { const feat = f.feature; - const layerId = feat.properties._matching_layer_id; + const layerId = feat._matching_layer_id; const layer: LayerConfig = layerDict[layerId].layerDef; if (layer === undefined) { throw "No layer found with id " + layerId; @@ -50,6 +50,7 @@ export default class WayHandlingApplyingFeatureSource implements FeatureSource { } const centerPoint = GeoOperations.centerpoint(feat); + centerPoint._matching_layer_id = feat._matching_layer_id; newFeatures.push({feature: centerPoint, freshness: f.freshness}); if(layer.wayHandling === LayerConfig.WAYHANDLING_CENTER_AND_WAY){ diff --git a/Logic/FilteredLayer.ts b/Logic/FilteredLayer.ts deleted file mode 100644 index ed2f9b0..0000000 --- a/Logic/FilteredLayer.ts +++ /dev/null @@ -1,163 +0,0 @@ -import {TagsFilter, TagUtils} from "./Tags"; -import {UIEventSource} from "./UIEventSource"; -import * as L from "leaflet" -import {Layer} from "leaflet" -import {GeoOperations} from "./GeoOperations"; -import {UIElement} from "../UI/UIElement"; -import State from "../State"; -import LayerConfig from "../Customizations/JSON/LayerConfig"; -import Hash from "./Web/Hash"; -import LazyElement from "../UI/Base/LazyElement"; - -/*** - * - */ -export class FilteredLayer { - - public readonly name: string | UIElement; - public readonly isDisplayed: UIEventSource = new UIEventSource(true); - public readonly layerDef: LayerConfig; - - private readonly filters: TagsFilter; - private readonly _maxAllowedOverlap: number; - - /** The featurecollection from overpass - */ - private _dataFromOverpass: any[]; - /** - * The leaflet layer object which should be removed on rerendering - */ - private _geolayer; - - private _showOnPopup: (tags: UIEventSource, feature: any) => UIElement; - - - constructor( - layerDef: LayerConfig, - showOnPopup: ((tags: UIEventSource, feature: any) => UIElement) - ) { - this.layerDef = layerDef; - - this._showOnPopup = showOnPopup; - this.name = name; - this.filters = layerDef.overpassTags; - this._maxAllowedOverlap = layerDef.hideUnderlayingFeaturesMinPercentage; - - } - - /** - * 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 - */ - public SetApplicableData(features: any[]): any[] { - const leftoverFeatures = []; - const selfFeatures = []; - for (let feature of features) { - const tags = TagUtils.proprtiesToKV(feature.properties); - const matches = this.filters.matches(tags); - if (matches) { - selfFeatures.push(feature); - } - if (!matches || this.layerDef.passAllFeatures) { - leftoverFeatures.push(feature); - } - } - - this.RenderLayer(selfFeatures) - return leftoverFeatures; - } - - - private RenderLayer(features: any[]) { - - if (this._geolayer !== undefined && this._geolayer !== null) { - // Remove the old geojson layer from the map - we'll reshow all the elements later on anyway - State.state.leafletMap.data.removeLayer(this._geolayer); - } - - // We fetch all the data we have to show: - const data = { - type: "FeatureCollection", - features: features - } - - let self = this; - this._geolayer = L.geoJSON(data, { - style: feature => { - const tagsSource = State.state.allElements.getEventSourceFor(feature); - return self.layerDef.GenerateLeafletStyle(tagsSource, 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 tagSource = State.state.allElements.getEventSourceFor(feature); - - const style = self.layerDef.GenerateLeafletStyle(tagSource, self._showOnPopup !== undefined); - let marker; - if (style.icon === undefined) { - marker = L.circle(latLng, { - radius: 25, - color: style.color - }); - } else { - marker = L.marker(latLng, { - icon: L.divIcon({ - html: style.icon.html.Render(), - className: style.icon.className, - iconAnchor: style.icon.iconAnchor, - iconUrl: style.icon.iconUrl, - popupAnchor: style.icon.popupAnchor, - iconSize: style.icon.iconSize - }) - }); - } - 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); - - - const eventSource = State.state.allElements.getEventSourceFor(feature); - let uiElement: LazyElement = new LazyElement(() => 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... - uiElement.Activate(); - State.state.selectedElement.setData(feature); - }); - - if (feature.properties.id.replace(/\//g, "_") === Hash.Get().data) { - // This element is in the URL, so this is a share link - // We already open it - uiElement.Activate(); - popup.setContent(uiElement.Render()); - - const center = GeoOperations.centerpoint(feature).geometry.coordinates; - popup.setLatLng({lat: center[1], lng: center[0]}); - popup.openOn(State.state.leafletMap.data); - State.state.selectedElement.setData(feature); - uiElement.Update(); - } - - } - }); - - this._geolayer.addTo(State.state.leafletMap.data); - - } - - -} \ No newline at end of file diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index eb6c383..1b614e8 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -8,7 +8,7 @@ import Svg from "../../Svg"; import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; import Img from "../../UI/Base/Img"; -export class UserDetails { +export default class UserDetails { public loggedIn = false; public name = "Not logged in"; @@ -21,15 +21,15 @@ export class UserDetails { } export class OsmConnection { - + public auth; public userDetails: UIEventSource; _dryRun: boolean; public preferencesHandler: OsmPreferences; public changesetHandler: ChangesetHandler; - - private _onLoggedIn : ((userDetails: UserDetails) => void)[] = []; + + private _onLoggedIn: ((userDetails: UserDetails) => void)[] = []; constructor(dryRun: boolean, oauth_token: UIEventSource, // Used to keep multiple changesets open and to write to the correct changeset @@ -44,11 +44,11 @@ export class OsmConnection { } catch (e) { console.warn("Detecting standalone mode failed", e, ". Assuming in browser and not worrying furhter") } - + const iframeMode = window !== window.top; - if ( iframeMode || pwaStandAloneMode || !singlePage) { + if (iframeMode || pwaStandAloneMode || !singlePage) { // In standalone mode, we DON'T use single page login, as 'redirecting' opens a new window anyway... // Same for an iframe... this.auth = new osmAuth({ @@ -74,7 +74,7 @@ export class OsmConnection { this._dryRun = dryRun; this.preferencesHandler = new OsmPreferences(this.auth, this); - + this.changesetHandler = new ChangesetHandler(layoutName, dryRun, this, this.auth); if (oauth_token.data !== undefined) { console.log(oauth_token.data) @@ -86,7 +86,7 @@ export class OsmConnection { }, this.auth); oauth_token.setData(undefined); - + } if (this.auth.authenticated()) { this.AttemptLogin(); // Also updates the user badge @@ -100,7 +100,8 @@ export class OsmConnection { layout: LayoutConfig, allElements: ElementStorage, generateChangeXML: (csid: string) => string, - continuation: () => void = () => {}) { + continuation: () => void = () => { + }) { this.changesetHandler.UploadChangeset(layout, allElements, generateChangeXML, continuation); } @@ -112,10 +113,10 @@ export class OsmConnection { return this.preferencesHandler.GetLongPreference(key, prefix); } - public OnLoggedIn(action: (userDetails: UserDetails) => void){ + public OnLoggedIn(action: (userDetails: UserDetails) => void) { this._onLoggedIn.push(action); } - + public LogOut() { this.auth.logout(); this.userDetails.data.loggedIn = false; @@ -132,7 +133,7 @@ export class OsmConnection { method: 'GET', path: '/api/0.6/user/details' }, function (err, details) { - if(err != null){ + if (err != null) { console.log(err); return; } @@ -140,9 +141,9 @@ export class OsmConnection { if (details == null) { return; } - + self.CheckForMessagesContinuously(); - + // details is an XML DOM of user details let userInfo = details.getElementsByTagName("user")[0]; @@ -177,7 +178,7 @@ export class OsmConnection { action(self.userDetails.data); } self._onLoggedIn = []; - + }); } @@ -189,7 +190,7 @@ export class OsmConnection { console.log("Checking for messages") this.AttemptLogin(); } - }, 5 * 60 * 1000); + }, 5 * 60 * 1000); } diff --git a/Models/BaseLayer.ts b/Models/BaseLayer.ts index e5e1c2c..63c3abb 100644 --- a/Models/BaseLayer.ts +++ b/Models/BaseLayer.ts @@ -1,6 +1,6 @@ import {TileLayer} from "leaflet"; -export interface BaseLayer { +export default interface BaseLayer { id: string, name: string, layer: TileLayer, diff --git a/State.ts b/State.ts index 4b1c513..75329af 100644 --- a/State.ts +++ b/State.ts @@ -12,7 +12,7 @@ import LayoutConfig from "./Customizations/JSON/LayoutConfig"; import Hash from "./Logic/Web/Hash"; import {MangroveIdentity} from "./Logic/Web/MangroveReviews"; import InstalledThemes from "./Logic/Actors/InstalledThemes"; -import {BaseLayer} from "./Models/BaseLayer"; +import BaseLayer from "./Models/BaseLayer"; import Loc from "./Models/Loc"; import Constants from "./Models/Constants"; @@ -61,11 +61,9 @@ export default class State { public filteredLayers: UIEventSource<{ - readonly name: string | UIElement; readonly isDisplayed: UIEventSource, - readonly layerDef: LayerConfig; + readonly layerDef: LayerConfig; }[]> = new UIEventSource<{ - readonly name: string | UIElement; readonly isDisplayed: UIEventSource, readonly layerDef: LayerConfig; }[]>([]) @@ -114,7 +112,8 @@ export default class State { public layoutDefinition: string; public installedThemes: UIEventSource<{ layout: LayoutConfig; definition: string }[]>; - public layerControlIsOpened: UIEventSource = QueryParameters.GetQueryParameter("layer-control-toggle", "false", "Wether or not the layer control is shown") + public layerControlIsOpened: UIEventSource = + QueryParameters.GetQueryParameter("layer-control-toggle", "false", "Whether or not the layer control is shown") .map((str) => str !== "false", [], b => "" + b) 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 >${Constants.userJourney.mapCompleteHelpUnlock} changesets)`).map( @@ -153,8 +152,6 @@ export default class State { }); - - function featSw(key: string, deflt: (layout: LayoutConfig) => boolean, documentation: string): UIEventSource { const queryParameterSource = QueryParameters.GetQueryParameter(key, undefined, documentation); // I'm so sorry about someone trying to decipher this diff --git a/Svg.ts b/Svg.ts index 038f80b..e37eccb 100644 --- a/Svg.ts +++ b/Svg.ts @@ -89,12 +89,12 @@ export default class Svg { public static delete_icon_svg() { return new FixedUiElement(Svg.delete_icon);} public static delete_icon_ui() { return new FixedUiElement(Svg.delete_icon_img);} - public static direction = " image/svg+xml " + public static direction = " image/svg+xml " public static direction_img = Img.AsImageElement(Svg.direction) public static direction_svg() { return new FixedUiElement(Svg.direction);} public static direction_ui() { return new FixedUiElement(Svg.direction_img);} - public static direction_gradient = " image/svg+xml " + public static direction_gradient = " image/svg+xml " 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);} diff --git a/UI/Base/FeatureSwitched.ts b/UI/Base/FeatureSwitched.ts new file mode 100644 index 0000000..6ff095a --- /dev/null +++ b/UI/Base/FeatureSwitched.ts @@ -0,0 +1,22 @@ +import {UIElement} from "../UIElement"; +import {UIEventSource} from "../../Logic/UIEventSource"; + +export default class FeatureSwitched extends UIElement{ + private readonly _upstream: UIElement; + private readonly _swtch: UIEventSource; + + constructor(upstream :UIElement, + swtch: UIEventSource) { + super(swtch); + this._upstream = upstream; + this._swtch = swtch; + } + + InnerRender(): string { + if(this._swtch.data){ + return this._upstream.Render(); + } + return ""; + } + +} \ No newline at end of file diff --git a/UI/Misc/Attribution.ts b/UI/BigComponents/Attribution.ts similarity index 100% rename from UI/Misc/Attribution.ts rename to UI/BigComponents/Attribution.ts diff --git a/UI/BackgroundSelector.ts b/UI/BigComponents/BackgroundSelector.ts similarity index 77% rename from UI/BackgroundSelector.ts rename to UI/BigComponents/BackgroundSelector.ts index b9344cd..2f58245 100644 --- a/UI/BackgroundSelector.ts +++ b/UI/BigComponents/BackgroundSelector.ts @@ -1,9 +1,9 @@ -import {UIElement} from "./UIElement"; -import {DropDown} from "./Input/DropDown"; -import Translations from "./i18n/Translations"; -import State from "../State"; -import {UIEventSource} from "../Logic/UIEventSource"; -import {BaseLayer} from "../Models/BaseLayer"; +import {UIElement} from "../UIElement"; +import {DropDown} from "../Input/DropDown"; +import Translations from "../i18n/Translations"; +import State from "../../State"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import {BaseLayer} from "../../Models/BaseLayer"; export default class BackgroundSelector extends UIElement { diff --git a/UI/Basemap.ts b/UI/BigComponents/Basemap.ts similarity index 92% rename from UI/Basemap.ts rename to UI/BigComponents/Basemap.ts index 3f871e6..73047a5 100644 --- a/UI/Basemap.ts +++ b/UI/BigComponents/Basemap.ts @@ -1,8 +1,8 @@ import * as L from "leaflet" -import {UIEventSource} from "../Logic/UIEventSource"; -import Loc from "../Models/Loc"; -import {UIElement} from "./UIElement"; -import {BaseLayer} from "../Models/BaseLayer"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import Loc from "../../Models/Loc"; +import {UIElement} from "../UIElement"; +import BaseLayer from "../../Models/BaseLayer"; export class Basemap { diff --git a/UI/BigComponents/FullWelcomePaneWithTabs.ts b/UI/BigComponents/FullWelcomePaneWithTabs.ts new file mode 100644 index 0000000..8dd0d80 --- /dev/null +++ b/UI/BigComponents/FullWelcomePaneWithTabs.ts @@ -0,0 +1,80 @@ +import {UIElement} from "../UIElement"; +import State from "../../State"; +import WelcomeMessage from "./WelcomeMessage"; +import * as personal from "../../assets/themes/personalLayout/personalLayout.json"; +import PersonalLayersPanel from "./PersonalLayersPanel"; +import Svg from "../../Svg"; +import Translations from "../i18n/Translations"; +import ShareScreen from "./ShareScreen"; +import MoreScreen from "./MoreScreen"; +import {VariableUiElement} from "../Base/VariableUIElement"; +import Constants from "../../Models/Constants"; +import Combine from "../Base/Combine"; +import Locale from "../i18n/Locale"; +import {TabbedComponent} from "../Base/TabbedComponent"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; +import UserDetails from "../../Logic/Osm/OsmConnection"; + +export default class FullWelcomePaneWithTabs extends UIElement { + private readonly _layoutToUse: UIEventSource; + private readonly _userDetails: UIEventSource; + + private readonly _component: UIElement; + + constructor() { + super(State.state.layoutToUse); + this._layoutToUse = State.state.layoutToUse; + this._userDetails = State.state.osmConnection.userDetails; + + + const layoutToUse = this._layoutToUse.data; + let welcome: UIElement = new WelcomeMessage(); + if (layoutToUse.id === personal.id) { + welcome = new PersonalLayersPanel(); + } + const tabs = [ + {header: ``, content: welcome}, + { + header: Svg.osm_logo_img, + content: Translations.t.general.openStreetMapIntro as UIElement + }, + + ] + + if (State.state.featureSwitchShareScreen.data) { + tabs.push({header: Svg.share_img, content: new ShareScreen()}); + } + + if (State.state.featureSwitchMoreQuests.data) { + + tabs.push({ + header: Svg.add_img, + content: new MoreScreen() + }); + } + + + tabs.push({ + header: Svg.help, + content: new VariableUiElement(this._userDetails.map(userdetails => { + if (userdetails.csCount < Constants.userJourney.mapCompleteHelpUnlock) { + return "" + } + return new Combine([Translations.t.general.aboutMapcomplete, "
Version " + Constants.vNumber]).Render(); + }, [Locale.language])) + } + ); + + this._component = new TabbedComponent(tabs, State.state.welcomeMessageOpenedTab) + .ListenTo(this._userDetails); + + + } + + InnerRender(): string { + return this._component.Render(); + + } + +} \ No newline at end of file diff --git a/UI/BigComponents/LayerControlPanel.ts b/UI/BigComponents/LayerControlPanel.ts new file mode 100644 index 0000000..8b90df0 --- /dev/null +++ b/UI/BigComponents/LayerControlPanel.ts @@ -0,0 +1,33 @@ +import {UIElement} from "../UIElement"; +import State from "../../State"; +import BackgroundSelector from "./BackgroundSelector"; +import LayerSelection from "./LayerSelection"; +import Combine from "../Base/Combine"; + +export default class LayerControlPanel extends UIElement{ + private readonly _panel: UIElement; + + + constructor() { + super(); + let layerControlPanel: UIElement = undefined; + if (State.state.layoutToUse.data.enableBackgroundLayerSelection) { + layerControlPanel = new BackgroundSelector(); + layerControlPanel.SetStyle("margin:1em"); + layerControlPanel.onClick(() => { + }); + } + + if (State.state.filteredLayers.data.length > 1) { + const layerSelection = new LayerSelection(); + layerSelection.onClick(() => { }); + layerControlPanel = new Combine([layerSelection, "
", layerControlPanel]); + } + this._panel = layerControlPanel; + } + + InnerRender(): string { + return this._panel.Render(); + } + +} \ No newline at end of file diff --git a/UI/LayerSelection.ts b/UI/BigComponents/LayerSelection.ts similarity index 77% rename from UI/LayerSelection.ts rename to UI/BigComponents/LayerSelection.ts index 8406bc2..3766bdf 100644 --- a/UI/LayerSelection.ts +++ b/UI/BigComponents/LayerSelection.ts @@ -1,13 +1,13 @@ -import {UIElement} from "./UIElement"; -import CheckBox from "./Input/CheckBox"; -import Combine from "./Base/Combine"; -import State from "../State"; -import Translations from "./i18n/Translations"; -import {FixedUiElement} from "./Base/FixedUiElement"; -import {VariableUiElement} from "./Base/VariableUIElement"; -import {UIEventSource} from "../Logic/UIEventSource"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import {UIElement} from "../UIElement"; +import {VariableUiElement} from "../Base/VariableUIElement"; +import State from "../../State"; +import CheckBox from "../Input/CheckBox"; +import Combine from "../Base/Combine"; +import {FixedUiElement} from "../Base/FixedUiElement"; +import Translations from "../i18n/Translations"; -export class LayerSelection extends UIElement { +export default class LayerSelection extends UIElement { private readonly _checkboxes: UIElement[]; @@ -16,7 +16,9 @@ export class LayerSelection extends UIElement { this._checkboxes = []; for (const layer of State.state.filteredLayers.data) { - const leafletStyle = layer.layerDef.GenerateLeafletStyle(new UIEventSource({id: "node/-1"}), true) + const leafletStyle = layer.layerDef.GenerateLeafletStyle( + new UIEventSource({id: "node/-1"}), + false) const leafletHtml = leafletStyle.icon.html; const icon = new FixedUiElement(leafletHtml.Render()) diff --git a/UI/MoreScreen.ts b/UI/BigComponents/MoreScreen.ts similarity index 84% rename from UI/MoreScreen.ts rename to UI/BigComponents/MoreScreen.ts index 6a49ddb..7fe81eb 100644 --- a/UI/MoreScreen.ts +++ b/UI/BigComponents/MoreScreen.ts @@ -1,17 +1,17 @@ -import {UIElement} from "./UIElement"; -import {VerticalCombine} from "./Base/VerticalCombine"; -import Translations from "./i18n/Translations"; -import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; -import Combine from "./Base/Combine"; -import {SubtleButton} from "./Base/SubtleButton"; -import State from "../State"; -import {VariableUiElement} from "./Base/VariableUIElement"; -import Svg from "../Svg"; -import LayoutConfig from "../Customizations/JSON/LayoutConfig"; -import * as personal from "../assets/themes/personalLayout/personalLayout.json" -import Constants from "../Models/Constants"; +import {VerticalCombine} from "../Base/VerticalCombine"; +import {UIElement} from "../UIElement"; +import {VariableUiElement} from "../Base/VariableUIElement"; +import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; +import {AllKnownLayouts} from "../../Customizations/AllKnownLayouts"; +import Svg from "../../Svg"; +import State from "../../State"; +import Combine from "../Base/Combine"; +import {SubtleButton} from "../Base/SubtleButton"; +import Translations from "../i18n/Translations"; +import * as personal from "../../assets/themes/personalLayout/personalLayout.json" +import Constants from "../../Models/Constants"; -export class MoreScreen extends UIElement { +export default class MoreScreen extends UIElement { constructor() { diff --git a/UI/PersonalLayersPanel.ts b/UI/BigComponents/PersonalLayersPanel.ts similarity index 86% rename from UI/PersonalLayersPanel.ts rename to UI/BigComponents/PersonalLayersPanel.ts index 9cdac9a..f5d9c12 100644 --- a/UI/PersonalLayersPanel.ts +++ b/UI/BigComponents/PersonalLayersPanel.ts @@ -1,17 +1,16 @@ -import {UIElement} from "./UIElement"; -import State from "../State"; -import Translations from "../UI/i18n/Translations"; -import {AllKnownLayouts} from "../Customizations/AllKnownLayouts"; -import Combine from "../UI/Base/Combine"; -import CheckBox from "../UI/Input/CheckBox"; -import * as personal from "../assets/themes/personalLayout/personalLayout.json"; -import {SubtleButton} from "./Base/SubtleButton"; -import {FixedUiElement} from "./Base/FixedUiElement"; -import Svg from "../Svg"; -import LayoutConfig from "../Customizations/JSON/LayoutConfig"; -import {UIEventSource} from "../Logic/UIEventSource"; - -export class PersonalLayersPanel extends UIElement { +import {UIEventSource} from "../../Logic/UIEventSource"; +import {UIElement} from "../UIElement"; +import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; +import {AllKnownLayouts} from "../../Customizations/AllKnownLayouts"; +import Svg from "../../Svg"; +import State from "../../State"; +import Combine from "../Base/Combine"; +import CheckBox from "../Input/CheckBox"; +import {SubtleButton} from "../Base/SubtleButton"; +import {FixedUiElement} from "../Base/FixedUiElement"; +import Translations from "../i18n/Translations"; +import * as personal from "../../assets/themes/personalLayout/personalLayout.json" +export default class PersonalLayersPanel extends UIElement { private checkboxes: UIElement[] = []; constructor() { diff --git a/UI/SearchAndGo.ts b/UI/BigComponents/SearchAndGo.ts similarity index 69% rename from UI/SearchAndGo.ts rename to UI/BigComponents/SearchAndGo.ts index 437b95c..6603ef8 100644 --- a/UI/SearchAndGo.ts +++ b/UI/BigComponents/SearchAndGo.ts @@ -1,23 +1,22 @@ -import Locale from "./i18n/Locale"; -import {UIElement} from "./UIElement"; -import {VariableUiElement} from "./Base/VariableUIElement"; -import {TextField} from "./Input/TextField"; -import {Geocoding} from "../Logic/Osm/Geocoding"; -import Translations from "./i18n/Translations"; -import State from "../State"; +import Locale from "../i18n/Locale"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import {UIElement} from "../UIElement"; +import {Translation} from "../i18n/Translation"; +import {VariableUiElement} from "../Base/VariableUIElement"; +import Svg from "../../Svg"; +import State from "../../State"; +import {TextField} from "../Input/TextField"; +import {Geocoding} from "../../Logic/Osm/Geocoding"; +import Translations from "../i18n/Translations"; -import {UIEventSource} from "../Logic/UIEventSource"; -import Svg from "../Svg"; -import {Translation} from "./i18n/Translation"; - -export class SearchAndGo extends UIElement { +export default class SearchAndGo extends UIElement { private _placeholder = new UIEventSource(Translations.t.general.search.search) private _searchField = new TextField({ placeholder: new VariableUiElement( this._placeholder.map(uiElement => uiElement.InnerRender(), [Locale.language]) ), - value: new UIEventSource("") + value: new UIEventSource("") } ); @@ -39,25 +38,31 @@ export class SearchAndGo extends UIElement { } + InnerRender(): string { + return this._searchField.Render() + + this._goButton.Render(); + + } + // Triggered by 'enter' or onclick private RunSearch() { const searchString = this._searchField.GetValue().data; - if(searchString === undefined || searchString === ""){ + if (searchString === undefined || searchString === "") { return; } this._searchField.GetValue().setData(""); this._placeholder.setData(Translations.t.general.search.searching); const self = this; - Geocoding.Search(searchString, (result) => { + Geocoding.Search(searchString, (result) => { - console.log("Search result", result) + console.log("Search result", result) if (result.length == 0) { self._placeholder.setData(Translations.t.general.search.nothing); return; } const bb = result[0].boundingbox; - const bounds : [[number, number], [number, number]] = [ + const bounds: [[number, number], [number, number]] = [ [bb[0], bb[2]], [bb[1], bb[3]] ] @@ -71,11 +76,5 @@ export class SearchAndGo extends UIElement { } - InnerRender(): string { - return this._searchField.Render() + - this._goButton.Render(); - - } - } \ No newline at end of file diff --git a/UI/ShareButton.ts b/UI/BigComponents/ShareButton.ts similarity index 96% rename from UI/ShareButton.ts rename to UI/BigComponents/ShareButton.ts index 9fadc94..b4c2eba 100644 --- a/UI/ShareButton.ts +++ b/UI/BigComponents/ShareButton.ts @@ -1,4 +1,4 @@ -import {UIElement} from "./UIElement"; +import {UIElement} from "../UIElement"; export default class ShareButton extends UIElement{ private _embedded: UIElement; diff --git a/UI/ShareScreen.ts b/UI/BigComponents/ShareScreen.ts similarity index 90% rename from UI/ShareScreen.ts rename to UI/BigComponents/ShareScreen.ts index c30882f..3f61e43 100644 --- a/UI/ShareScreen.ts +++ b/UI/BigComponents/ShareScreen.ts @@ -1,21 +1,21 @@ -import {UIElement} from "./UIElement"; -import Translations from "./i18n/Translations"; -import {FixedUiElement} from "./Base/FixedUiElement"; -import Combine from "./Base/Combine"; -import {VariableUiElement} from "./Base/VariableUIElement"; -import CheckBox from "./Input/CheckBox"; -import {VerticalCombine} from "./Base/VerticalCombine"; -import State from "../State"; -import {FilteredLayer} from "../Logic/FilteredLayer"; -import {Utils} from "../Utils"; -import {UIEventSource} from "../Logic/UIEventSource"; -import {SubtleButton} from "./Base/SubtleButton"; -import Svg from "../Svg"; -import {Translation} from "./i18n/Translation"; -import LayoutConfig from "../Customizations/JSON/LayoutConfig"; -import Constants from "../Models/Constants"; +import {VerticalCombine} from "../Base/VerticalCombine"; +import {UIElement} from "../UIElement"; +import {VariableUiElement} from "../Base/VariableUIElement"; +import {Translation} from "../i18n/Translation"; +import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; +import Svg from "../../Svg"; +import Combine from "../Base/Combine"; +import {SubtleButton} from "../Base/SubtleButton"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import {Utils} from "../../Utils"; +import State from "../../State"; +import CheckBox from "../Input/CheckBox"; +import {FixedUiElement} from "../Base/FixedUiElement"; +import Translations from "../i18n/Translations"; +import Constants from "../../Models/Constants"; +import LayerConfig from "../../Customizations/JSON/LayerConfig"; -export class ShareScreen extends UIElement { +export default class ShareScreen extends UIElement { private readonly _options: UIElement; private readonly _iframeCode: UIElement; public iframe: UIEventSource; @@ -61,7 +61,7 @@ export class ShareScreen extends UIElement { }, [currentLocation])); - function fLayerToParam(flayer: FilteredLayer) { + function fLayerToParam(flayer: {isDisplayed: UIEventSource, layerDef: LayerConfig}) { if (flayer.isDisplayed.data) { return null; // Being displayed is the default } diff --git a/UI/SimpleAddUI.ts b/UI/BigComponents/SimpleAddUI.ts similarity index 93% rename from UI/SimpleAddUI.ts rename to UI/BigComponents/SimpleAddUI.ts index 70e4aaf..83309e3 100644 --- a/UI/SimpleAddUI.ts +++ b/UI/BigComponents/SimpleAddUI.ts @@ -1,20 +1,19 @@ -import {UIElement} from "./UIElement"; -import {Tag, TagUtils} from "../Logic/Tags"; -import Translations from "./i18n/Translations"; -import Combine from "./Base/Combine"; -import {SubtleButton} from "./Base/SubtleButton"; -import Locale from "./i18n/Locale"; -import State from "../State"; - -import {UIEventSource} from "../Logic/UIEventSource"; -import Svg from "../Svg"; -import {FixedUiElement} from "./Base/FixedUiElement"; -import Constants from "../Models/Constants"; - /** * Asks to add a feature at the last clicked location, at least if zoom is sufficient */ -export class SimpleAddUI extends UIElement { +import Locale from "../i18n/Locale"; +import {UIEventSource} from "../../Logic/UIEventSource"; +import {Tag, TagUtils} from "../../Logic/Tags"; +import {UIElement} from "../UIElement"; +import Svg from "../../Svg"; +import {SubtleButton} from "../Base/SubtleButton"; +import State from "../../State"; +import Combine from "../Base/Combine"; +import {FixedUiElement} from "../Base/FixedUiElement"; +import Translations from "../i18n/Translations"; +import Constants from "../../Models/Constants"; + +export default class SimpleAddUI extends UIElement { private readonly _addButtons: UIElement[]; private _loginButton : UIElement; diff --git a/UI/UserBadge.ts b/UI/BigComponents/UserBadge.ts similarity index 85% rename from UI/UserBadge.ts rename to UI/BigComponents/UserBadge.ts index b4b34a2..a1ad4b5 100644 --- a/UI/UserBadge.ts +++ b/UI/BigComponents/UserBadge.ts @@ -1,25 +1,25 @@ -import {UIElement} from "./UIElement"; -import {FixedUiElement} from "./Base/FixedUiElement"; -import {VariableUiElement} from "./Base/VariableUIElement"; -import Translations from "./i18n/Translations"; -import {UserDetails} from "../Logic/Osm/OsmConnection"; -import State from "../State"; -import {UIEventSource} from "../Logic/UIEventSource"; -import Combine from "./Base/Combine"; -import Svg from "../Svg"; -import Link from "./Base/Link"; -import LanguagePicker from "./LanguagePicker"; - /** * Handles and updates the user badge */ -export class UserBadge extends UIElement { +import {UIEventSource} from "../../Logic/UIEventSource"; +import {UIElement} from "../UIElement"; +import {VariableUiElement} from "../Base/VariableUIElement"; +import {UserDetails} from "../../Logic/Osm/OsmConnection"; +import Svg from "../../Svg"; +import State from "../../State"; +import Combine from "../Base/Combine"; +import {FixedUiElement} from "../Base/FixedUiElement"; +import LanguagePicker from "../LanguagePicker"; +import Translations from "../i18n/Translations"; +import Link from "../Base/Link"; + +export default class UserBadge extends UIElement { private _userDetails: UIEventSource; private _logout: UIElement; private _homeButton: UIElement; private _languagePicker: UIElement; - private _loginButton : UIElement; + private _loginButton: UIElement; constructor() { super(State.state.osmConnection.userDetails); @@ -94,7 +94,7 @@ export class UserBadge extends UIElement { const settings = new Link(Svg.gear_svg(), - `https://www.openstreetmap.org/user/${encodeURIComponent(user.name)}/account`, + `https://www.openstreetmap.org/user/${encodeURIComponent(user.name)}/account`, true) diff --git a/UI/WelcomeMessage.ts b/UI/BigComponents/WelcomeMessage.ts similarity index 80% rename from UI/WelcomeMessage.ts rename to UI/BigComponents/WelcomeMessage.ts index 2851236..de1179c 100644 --- a/UI/WelcomeMessage.ts +++ b/UI/BigComponents/WelcomeMessage.ts @@ -1,12 +1,12 @@ -import {UIElement} from "./UIElement"; -import Locale from "../UI/i18n/Locale"; -import State from "../State"; -import Translations from "./i18n/Translations"; -import Combine from "./Base/Combine"; -import LanguagePicker from "./LanguagePicker"; +import Locale from "../i18n/Locale"; +import {UIElement} from "../UIElement"; +import State from "../../State"; +import Combine from "../Base/Combine"; +import LanguagePicker from "../LanguagePicker"; +import Translations from "../i18n/Translations"; -export class WelcomeMessage extends UIElement { +export default class WelcomeMessage extends UIElement { private languagePicker: UIElement; private readonly description: UIElement; @@ -25,11 +25,6 @@ export class WelcomeMessage extends UIElement { "

", layout.title, "

", layout.description ]) - layout.descriptionTail - - - - this.plzLogIn = Translations.t.general.loginWithOpenStreetMap .onClick(() => { diff --git a/UI/CenterMessageBox.ts b/UI/CenterMessageBox.ts index 71e2fdf..66fe288 100644 --- a/UI/CenterMessageBox.ts +++ b/UI/CenterMessageBox.ts @@ -2,10 +2,9 @@ import {UIElement} from "./UIElement"; import Translations from "./i18n/Translations"; import State from "../State"; -export class CenterMessageBox extends UIElement { +export default class CenterMessageBox extends UIElement { - constructor( - ) { + constructor() { super(State.state.centerMessage); this.ListenTo(State.state.locationControl); @@ -19,14 +18,17 @@ export class CenterMessageBox extends UIElement { return {innerHtml: State.state.centerMessage.data, done: false}; } const lu = State.state.layerUpdater; - if(lu.retries.data > 0) { - return {innerHtml: Translations.t.centerMessage.retrying.Subs({count: ""+ lu.retries.data}).Render(), done: false}; + if (lu.retries.data > 0) { + return { + innerHtml: Translations.t.centerMessage.retrying.Subs({count: "" + lu.retries.data}).Render(), + done: false + }; } - + if (lu.runningQuery.data) { return {innerHtml: Translations.t.centerMessage.loadingData.Render(), done: false}; - - } + + } if (!lu.sufficientlyZoomed.data) { return {innerHtml: Translations.t.centerMessage.zoomIn.Render(), done: false}; } else { diff --git a/UI/FullScreenMessageBoxHandler.ts b/UI/FullScreenMessageBoxHandler.ts index 2451495..4b19508 100644 --- a/UI/FullScreenMessageBoxHandler.ts +++ b/UI/FullScreenMessageBoxHandler.ts @@ -6,7 +6,7 @@ import Combine from "./Base/Combine"; /** * Handles the full screen popup on mobile */ -export class FullScreenMessageBox extends UIElement { +export default class FullScreenMessageBox extends UIElement { private readonly returnToTheMap: UIElement; private _content: UIElement; diff --git a/UI/Popup/EditableTagRendering.ts b/UI/Popup/EditableTagRendering.ts index 0481eef..68d1452 100644 --- a/UI/Popup/EditableTagRendering.ts +++ b/UI/Popup/EditableTagRendering.ts @@ -9,8 +9,8 @@ import State from "../../State"; import Svg from "../../Svg"; export default class EditableTagRendering extends UIElement { - private _tags: UIEventSource; - private _configuration: TagRenderingConfig; + private readonly _tags: UIEventSource; + private readonly _configuration: TagRenderingConfig; private _editMode: UIEventSource = new UIEventSource(false); private _editButton: UIElement; diff --git a/UI/Popup/FeatureInfoBox.ts b/UI/Popup/FeatureInfoBox.ts index c714e5a..7a58c93 100644 --- a/UI/Popup/FeatureInfoBox.ts +++ b/UI/Popup/FeatureInfoBox.ts @@ -8,14 +8,14 @@ import TagRenderingAnswer from "./TagRenderingAnswer"; import State from "../../State"; import {FixedUiElement} from "../Base/FixedUiElement"; -export class FeatureInfoBox extends UIElement { +export default class FeatureInfoBox extends UIElement { private _tags: UIEventSource; private _layerConfig: LayerConfig; - private _title : UIElement; + private _title: UIElement; private _titleIcons: UIElement; private _renderings: UIElement[]; - private _questionBox : UIElement; + private _questionBox: UIElement; constructor( tags: UIEventSource, @@ -35,15 +35,15 @@ export class FeatureInfoBox extends UIElement { this._titleIcons = new Combine( layerConfig.titleIcons.map(icon => new TagRenderingAnswer(tags, icon))) .SetClass("featureinfobox-icons"); - - let questionBox : UIElement = undefined; + + let questionBox: UIElement = undefined; if (State.state.featureSwitchUserbadge.data) { questionBox = new QuestionBox(tags, layerConfig.tagRenderings); } - + let questionBoxIsUsed = false; this._renderings = layerConfig.tagRenderings.map(tr => { - if(tr.question === null){ + if (tr.question === null) { questionBoxIsUsed = true; // This is the question box! return questionBox; @@ -51,9 +51,9 @@ export class FeatureInfoBox extends UIElement { return new EditableTagRendering(tags, tr); }); this._renderings[0]?.SetClass("first-rendering"); - if(!questionBoxIsUsed){ - this._renderings.push(questionBox); - } + if (!questionBoxIsUsed) { + this._renderings.push(questionBox); + } } InnerRender(): string { diff --git a/UI/Popup/QuestionBox.ts b/UI/Popup/QuestionBox.ts index e6caecb..991d11a 100644 --- a/UI/Popup/QuestionBox.ts +++ b/UI/Popup/QuestionBox.ts @@ -9,9 +9,9 @@ import Translations from "../i18n/Translations"; * Generates all the questions, one by one */ export default class QuestionBox extends UIElement { - private _tags: UIEventSource; + private readonly _tags: UIEventSource; - private _tagRenderings: TagRenderingConfig[]; + private readonly _tagRenderings: TagRenderingConfig[]; private _tagRenderingQuestions: UIElement[]; private _skippedQuestions: UIEventSource = new UIEventSource([]) diff --git a/UI/Popup/SaveButton.ts b/UI/Popup/SaveButton.ts index 2886d9d..3b741a4 100644 --- a/UI/Popup/SaveButton.ts +++ b/UI/Popup/SaveButton.ts @@ -1,7 +1,7 @@ import {UIEventSource} from "../../Logic/UIEventSource"; import {UIElement} from "../UIElement"; import Translations from "../i18n/Translations"; -import {OsmConnection, UserDetails} from "../../Logic/Osm/OsmConnection"; +import UserDetails, {OsmConnection} from "../../Logic/Osm/OsmConnection"; export class SaveButton extends UIElement { diff --git a/UI/Popup/TagRenderingAnswer.ts b/UI/Popup/TagRenderingAnswer.ts index d5e062d..57ddf68 100644 --- a/UI/Popup/TagRenderingAnswer.ts +++ b/UI/Popup/TagRenderingAnswer.ts @@ -7,7 +7,7 @@ import {SubstitutedTranslation} from "../SpecialVisualizations"; * Displays the correct value for a known tagrendering */ export default class TagRenderingAnswer extends UIElement { - private _tags: UIEventSource; + private readonly _tags: UIEventSource; private _configuration: TagRenderingConfig; private _content: UIElement; diff --git a/UI/Popup/TagRenderingQuestion.ts b/UI/Popup/TagRenderingQuestion.ts index c6420f5..bfeb82e 100644 --- a/UI/Popup/TagRenderingQuestion.ts +++ b/UI/Popup/TagRenderingQuestion.ts @@ -25,7 +25,7 @@ import Constants from "../../Models/Constants"; * Note that the value _migh_ already be known, e.g. when selected or when changing the value */ export default class TagRenderingQuestion extends UIElement { - private _tags: UIEventSource; + private readonly _tags: UIEventSource; private _configuration: TagRenderingConfig; private _saveButton: UIElement; diff --git a/UI/ShowDataLayer.ts b/UI/ShowDataLayer.ts new file mode 100644 index 0000000..18d2986 --- /dev/null +++ b/UI/ShowDataLayer.ts @@ -0,0 +1,141 @@ +/** + * The data layer shows all the given geojson elements with the appropriate icon etc + */ +import {UIEventSource} from "../Logic/UIEventSource"; +import * as L from "leaflet" +import LayerConfig from "../Customizations/JSON/LayerConfig"; +import State from "../State"; +import LazyElement from "./Base/LazyElement"; +import Hash from "../Logic/Web/Hash"; +import {GeoOperations} from "../Logic/GeoOperations"; +import FeatureInfoBox from "./Popup/FeatureInfoBox"; + +export default class ShowDataLayer { + + private readonly _layerDict; + private readonly _leafletMap: UIEventSource; + + constructor(features: UIEventSource<{ feature: any, freshness: Date }[]>, + leafletMap: UIEventSource, + layers: { layerDef: LayerConfig, isDisplayed: UIEventSource }[]) { + this._leafletMap = leafletMap; + const self = this; + + let oldGeoLayer: L.Layer = undefined; + + this._layerDict = {}; + for (const layer of layers) { + this._layerDict[layer.layerDef.id] = layer.layerDef; + } + + function update() { + if (features.data === undefined) { + return; + } + if (leafletMap.data === undefined) { + return; + } + const mp = leafletMap.data; + + const feats = features.data.map(ff => ff.feature); + const geoLayer = self.CreateGeojsonLayer(feats); + + if (oldGeoLayer) { + mp.removeLayer(oldGeoLayer); + } + + geoLayer.addTo(mp); + oldGeoLayer = geoLayer; + } + + features.addCallbackAndRun(() => update()); + leafletMap.addCallback(() => update()); + + } + + + private createStyleFor(feature) { + const tagsSource = State.state.allElements.getEventSourceFor(feature); + // Every object is tied to exactly one layer + const layer = this._layerDict[feature._matching_layer_id]; + return layer.GenerateLeafletStyle(tagsSource, layer._showOnPopup !== undefined); + } + + private pointToLayer(feature, latLng): L.Layer { + // Leaflet cannot handle geojson points natively + // We have to convert them to the appropriate icon + // Click handling is done in the next step + + const tagSource = State.state.allElements.getEventSourceFor(feature); + const layer : LayerConfig = this._layerDict[feature._matching_layer_id]; + + const style = layer.GenerateLeafletStyle(tagSource, !(layer.title === undefined && (layer.tagRenderings ?? []).length === 0)); + return L.marker(latLng, { + icon: L.divIcon({ + html: style.icon.html.Render(), + className: style.icon.className, + iconAnchor: style.icon.iconAnchor, + iconUrl: style.icon.iconUrl, + popupAnchor: style.icon.popupAnchor, + iconSize: style.icon.iconSize + }) + }); + } + + private postProcessFeature(feature, leafletLayer: L.Layer){ + const layer : LayerConfig = this._layerDict[feature._matching_layer_id]; + if (layer.title === undefined && (layer.tagRenderings ?? []).length === 0) { + // No popup action defined -> Don't do anything + return; + } + + const popup = L.popup({ + autoPan: true, + closeOnEscapeKey: true, + }, leafletLayer); + + + const tags = State.state.allElements.getEventSourceFor(feature); + let uiElement: LazyElement = new LazyElement(() => new FeatureInfoBox(tags, layer)); + popup.setContent(uiElement.Render()); + leafletLayer.bindPopup(popup); + // We first render the UIelement (which'll still need an update later on...) + // But at least it'll be visible already + + + leafletLayer.on("click", (e) => { + // We set the element as selected... + uiElement.Activate(); + State.state.selectedElement.setData(feature); + }); + + + if (feature.properties.id.replace(/\//g, "_") === Hash.Get().data) { + // This element is in the URL, so this is a share link + // We already open it + uiElement.Activate(); + popup.setContent(uiElement.Render()); + + const center = GeoOperations.centerpoint(feature).geometry.coordinates; + popup.setLatLng({lat: center[1], lng: center[0]}); + popup.openOn(State.state.leafletMap.data); + State.state.selectedElement.setData(feature); + uiElement.Update(); + } + } + + private CreateGeojsonLayer(features: any[]): L.Layer { + const self = this; + const data = { + type: "FeatureCollection", + features: features + } + return L.geoJSON(data, { + style: feature => self.createStyleFor(feature), + pointToLayer: (feature, latLng) => self.pointToLayer(feature, latLng), + onEachFeature: (feature, leafletLayer) => self.postProcessFeature(feature, leafletLayer) + }); + + } + +} \ No newline at end of file diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index d973a85..8814242 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -9,7 +9,7 @@ import Locale from "../UI/i18n/Locale"; import {ImageUploadFlow} from "./Image/ImageUploadFlow"; import {Translation} from "./i18n/Translation"; -import ShareButton from "./ShareButton"; +import ShareButton from "./BigComponents/ShareButton"; import Svg from "../Svg"; import ReviewElement from "./Reviews/ReviewElement"; import MangroveReviews from "../Logic/Web/MangroveReviews"; diff --git a/assets/layers/direction/direction.json b/assets/layers/direction/direction.json index f153319..a22f428 100644 --- a/assets/layers/direction/direction.json +++ b/assets/layers/direction/direction.json @@ -5,7 +5,10 @@ }, "minzoom": 16, "overpassTags": { - "or": ["camera:direction~*","direction~*"] + "or": [ + "camera:direction~*", + "direction~*" + ] }, "doNotDownload": true, "passAllFeatures": true, @@ -14,7 +17,16 @@ "en": "This layer visualizes directions" }, "tagRenderings": [], - "icon": "./assets/svg/direction_gradient.svg", + "icon": { + "render": "direction_gradient:var(--catch-detail-color)", + "#": "For some weird reason, showing the icon in the layer control panel breaks the svg-gradient (because the svg gradient has a global color or smthng) - so we use a different icon without gradient", + "mappings": [ + { + "if": "id=node/-1", + "then": "direction:var(--catch-detail-color)" + } + ] + }, "rotation": { "render": "{camera:direction}deg", "mappings": [ @@ -24,7 +36,7 @@ } ] }, - "iconSize": "200,200,center", + "iconSize": "200,200,center", "color": "--catch-detail-color", "stroke": "0", "presets": [], diff --git a/assets/layers/surveillance_cameras/surveillance_cameras.json b/assets/layers/surveillance_cameras/surveillance_cameras.json index 5ae0be7..32c6847 100644 --- a/assets/layers/surveillance_cameras/surveillance_cameras.json +++ b/assets/layers/surveillance_cameras/surveillance_cameras.json @@ -335,6 +335,7 @@ ] }, "rotation": { + "#": "Note: {camera:direction} is substituted by a number, giving the string 'calc(123deg + 90deg)' ; it is this string that is used as css property, which interprets the calc", "render": "calc({camera:direction}deg + 90deg)", "mappings": [ { diff --git a/assets/svg/direction.svg b/assets/svg/direction.svg index e1eccac..cff71b6 100644 --- a/assets/svg/direction.svg +++ b/assets/svg/direction.svg @@ -5,11 +5,11 @@ 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" + width="100" height="100" - width="100"> + viewBox="0 0 100 100" + version="1.1" + id="svg8"> @@ -25,7 +25,7 @@ + id="path821" /> diff --git a/assets/svg/direction_gradient.svg b/assets/svg/direction_gradient.svg index 8ba4912..54af33b 100644 --- a/assets/svg/direction_gradient.svg +++ b/assets/svg/direction_gradient.svg @@ -6,48 +6,11 @@ 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)"> - - - - + version="1.1" + viewBox="0 0 100 100" + height="100" + width="100"> @@ -56,7 +19,6 @@ image/svg+xml - @@ -65,28 +27,27 @@ + id="innercolor" /> + id="outercolor" /> + cx="49.787739" + cy="53.828533" + fx="49.787739" + fy="53.828533" + r="28.883806" + gradientTransform="matrix(1.5439431,-0.01852438,0.02075364,1.7297431,-27.986574,-42.187244)" + gradientUnits="userSpaceOnUse" /> + d="M 50,50 21.042889,9.3993342 C 36.191421,-2.001434 60.726552,-3.6768009 78.8105,9.1490935 Z" + style="fill:url(#radialGradient828);fill-opacity:1;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />