diff --git a/InitUiElements.ts b/InitUiElements.ts index b9238ec..8d9b2ed 100644 --- a/InitUiElements.ts +++ b/InitUiElements.ts @@ -7,7 +7,7 @@ import Combine from "./UI/Base/Combine"; import {UIElement} from "./UI/UIElement"; import {MoreScreen} from "./UI/MoreScreen"; import {FilteredLayer} from "./Logic/FilteredLayer"; -import {Basemap} from "./Logic/Leaflet/Basemap"; +import {Basemap} from "./UI/Basemap"; import State from "./State"; import {WelcomeMessage} from "./UI/WelcomeMessage"; import {LayerSelection} from "./UI/LayerSelection"; @@ -17,7 +17,7 @@ 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/Leaflet/StrayClickHandler"; +import {StrayClickHandler} from "./Logic/Actors/StrayClickHandler"; import {SimpleAddUI} from "./UI/SimpleAddUI"; import {CenterMessageBox} from "./UI/CenterMessageBox"; import {AllKnownLayouts} from "./Customizations/AllKnownLayouts"; @@ -25,11 +25,10 @@ import {TagUtils} from "./Logic/Tags"; import {UserBadge} from "./UI/UserBadge"; import {SearchAndGo} from "./UI/SearchAndGo"; import {FullScreenMessageBox} from "./UI/FullScreenMessageBoxHandler"; -import {GeoLocationHandler} from "./Logic/Leaflet/GeoLocationHandler"; +import {GeoLocationHandler} from "./Logic/Actors/GeoLocationHandler"; import {LocalStorageSource} from "./Logic/Web/LocalStorageSource"; import {Utils} from "./Utils"; import BackgroundSelector from "./UI/BackgroundSelector"; -import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; import {FeatureInfoBox} from "./UI/Popup/FeatureInfoBox"; import Svg from "./Svg"; import Link from "./UI/Base/Link"; @@ -44,40 +43,6 @@ import Constants from "./Models/Constants"; export class InitUiElements { - private static setupAllLayerElements() { - - // ------------- Setup the layers ------------------------------- - - InitUiElements.InitLayers(); - InitUiElements.InitLayerSelection(); - - - // ------------------ Setup various other UI elements ------------ - - - InitUiElements.OnlyIf(State.state.featureSwitchAddNew, () => { - - let presetCount = 0; - for (const layer of State.state.filteredLayers.data) { - for (const preset of layer.layerDef.presets) { - presetCount++; - } - } - if (presetCount == 0) { - return; - } - - - new StrayClickHandler(() => { - return new SimpleAddUI(); - } - ); - }); - - new CenterMessageBox().AttachTo("centermessage"); - - } - static InitAll(layoutToUse: LayoutConfig, layoutFromBase64: string, testing: UIEventSource, layoutName: string, layoutDefinition: string = "") { if (layoutToUse === undefined) { @@ -88,8 +53,10 @@ export class InitUiElements { } console.log("Using layout: ", layoutToUse.id, "LayoutFromBase64 is ", layoutFromBase64); + + State.state = new State(layoutToUse); - + // This 'leaks' the global state via the window object, useful for debugging // @ts-ignore window.mapcomplete_state = State.state; @@ -116,7 +83,7 @@ export class InitUiElements { InitUiElements.setupAllLayerElements(); if (layoutToUse.customCss !== undefined) { - Utils.LoadCustomCss(layoutToUse.customCss); + Utils.LoadCustomCss(layoutToUse.customCss); } function updateFavs() { @@ -163,7 +130,7 @@ export class InitUiElements { if (feature === undefined) { State.state.fullScreenMessage.setData(undefined); } - if (feature?.properties === undefined) { + if (feature?.properties === undefined) { return; } const data = feature.properties; @@ -176,11 +143,11 @@ export class InitUiElements { if (!applicable) { continue; } - - if((layer.title ?? null) === null && layer.tagRenderings.length === 0){ + + if ((layer.title ?? null) === null && layer.tagRenderings.length === 0) { continue; } - + // This layer is the layer that gives the questions const featureBox = new FeatureInfoBox( State.state.allElements.getEventSourceById(data.id), @@ -231,15 +198,19 @@ export class InitUiElements { iconAnchor: [15, 15] }); const marker = L.marker([home.lat, home.lon], {icon: icon}) - marker.addTo(State.state.bm.map) + marker.addTo(State.state.leafletMap.data) }); - new GeoLocationHandler() + 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;`) .AttachTo("geolocate-button"); State.state.locationControl.ping(); } - + static LoadLayoutFromHash(userLayoutParam: UIEventSource) { try { let hash = location.hash.substr(1); @@ -282,55 +253,6 @@ export class InitUiElements { } - - 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); - - } - - static InitWelcomeMessage() { const fullOptions = this.CreateWelcomePane(); @@ -373,25 +295,6 @@ export class InitUiElements { } - - 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; - } static InitLayerSelection() { InitUiElements.OnlyIf(State.state.featureSwitchLayers, () => { @@ -435,37 +338,27 @@ export class InitUiElements { }); } + static InitBaseMap() { - const bm = new Basemap("leafletDiv", - State.state.locationControl, + + const attr = new Attribution(State.state.locationControl, State.state.osmConnection.userDetails, State.state.layoutToUse, State.state.leafletMap); + const bm = new Basemap("leafletDiv", + State.state.locationControl, State.state.backgroundLayer, - new Attribution(State.state.locationControl, State.state.osmConnection.userDetails, State.state.layoutToUse, State.state.bm) + State.state.LastClickLocation, + attr ); - State.state.bm = bm; + State.state.leafletMap.setData(bm.map); + bm.map.on("popupclose", () => { State.state.selectedElement.setData(undefined) }) + + State.state.layerUpdater = new UpdateFromOverpass(State.state); - State.state.availableBackgroundLayers = new AvailableBaseLayers(State.state.locationControl, State.state.backgroundLayer).availableEditorLayers; - const queryParam = QueryParameters.GetQueryParameter("background", State.state.layoutToUse.data.defaultBackgroundId, "The id of the background layer to start with"); - - queryParam.addCallbackAndRun((selectedId: string) => { - const available = State.state.availableBackgroundLayers.data; - for (const layer of available) { - if (layer.id === selectedId) { - State.state.backgroundLayer.setData(layer); - } - } - }) - - State.state.backgroundLayer.addCallbackAndRun(currentLayer => { - queryParam.setData(currentLayer.id); - }); - } - static InitLayers() { const flayers: FilteredLayer[] = [] @@ -496,4 +389,111 @@ export class InitUiElements { State.state.filteredLayers.setData(flayers); } + private static setupAllLayerElements() { + + // ------------- Setup the layers ------------------------------- + + InitUiElements.InitLayers(); + InitUiElements.InitLayerSelection(); + + + // ------------------ Setup various other UI elements ------------ + + + InitUiElements.OnlyIf(State.state.featureSwitchAddNew, () => { + + let presetCount = 0; + for (const layer of State.state.filteredLayers.data) { + for (const preset of layer.layerDef.presets) { + presetCount++; + } + } + if (presetCount == 0) { + return; + } + + + new StrayClickHandler( + State.state.LastClickLocation, + State.state.selectedElement, + State.state.filteredLayers, + State.state.leafletMap, + State.state.fullScreenMessage, + () => { + return new SimpleAddUI(); + } + ); + }); + + 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/AvailableBaseLayers.ts b/Logic/Actors/AvailableBaseLayers.ts index df28611..59130bc 100644 --- a/Logic/Actors/AvailableBaseLayers.ts +++ b/Logic/Actors/AvailableBaseLayers.ts @@ -1,10 +1,9 @@ import * as editorlayerindex from "../../assets/editor-layer-index.json" +import {BaseLayer} from "../../Models/BaseLayer"; +import * as L from "leaflet"; +import * as X from "leaflet-providers"; import {UIEventSource} from "../UIEventSource"; import {GeoOperations} from "../GeoOperations"; -import {Basemap} from "../Leaflet/Basemap"; -import {BaseLayer} from "../../Models/BaseLayer"; -import * as X from "leaflet-providers"; -import * as L from "leaflet"; import {TileLayer} from "leaflet"; import {Utils} from "../../Utils"; @@ -32,12 +31,16 @@ export default class AvailableBaseLayers { public static layerOverview = AvailableBaseLayers.LoadRasterIndex().concat(AvailableBaseLayers.LoadProviderIndex()); public availableEditorLayers: UIEventSource; - constructor(location: UIEventSource<{ lat: number, lon: number, zoom: number }>, - currentBackgroundLayer: UIEventSource) { + constructor(location: UIEventSource<{ lat: number, lon: number, zoom: number }>) { const self = this; this.availableEditorLayers = location.map( (currentLocation) => { + + if(currentLocation === undefined){ + return AvailableBaseLayers.layerOverview; + } + const currentLayers = self.availableEditorLayers?.data; const newLayers = AvailableBaseLayers.AvailableLayersAt(currentLocation?.lon, currentLocation?.lat); @@ -57,36 +60,15 @@ export default class AvailableBaseLayers { }); - // Change the baselayer back to OSM if we go out of the current range of the layer - this.availableEditorLayers.addCallbackAndRun(availableLayers => { - const currentLayer = currentBackgroundLayer.data.id; - for (const availableLayer of availableLayers) { - if (availableLayer.id === currentLayer) { - - if (availableLayer.max_zoom < location.data.zoom) { - break; - } - - if (availableLayer.min_zoom > location.data.zoom) { - break; - } - return; // All good - the current layer still works! - } - } - // Oops, we panned out of range for this layer! - console.log("AvailableBaseLayers-actor: detected that the current bounds aren't sufficient anymore - reverting to OSM standard") - layerControl.setData(AvailableBaseLayers.osmCarto); - - }); - - + } private static AvailableLayersAt(lon: number, lat: number): BaseLayer[] { const availableLayers = [AvailableBaseLayers.osmCarto] const globalLayers = []; - for (const i in AvailableBaseLayers.layerOverview) { - const layer = AvailableBaseLayers.layerOverview[i]; + for (const layerOverviewItem of AvailableBaseLayers.layerOverview) { + const layer = layerOverviewItem; + if (layer.feature?.geometry === undefined || layer.feature?.geometry === null) { globalLayers.push(layer); continue; diff --git a/Logic/Leaflet/GeoLocationHandler.ts b/Logic/Actors/GeoLocationHandler.ts similarity index 77% rename from Logic/Leaflet/GeoLocationHandler.ts rename to Logic/Actors/GeoLocationHandler.ts index e8a96af..c8216ca 100644 --- a/Logic/Leaflet/GeoLocationHandler.ts +++ b/Logic/Actors/GeoLocationHandler.ts @@ -1,9 +1,7 @@ import * as L from "leaflet"; import {UIEventSource} from "../UIEventSource"; import {UIElement} from "../../UI/UIElement"; -import State from "../../State"; import {Utils} from "../../Utils"; -import {Basemap} from "./Basemap"; import Svg from "../../Svg"; import {Img} from "../../UI/Img"; @@ -11,12 +9,20 @@ export class GeoLocationHandler extends UIElement { private readonly _isActive: UIEventSource = new UIEventSource(false); private readonly _permission: UIEventSource = new UIEventSource(""); - private _marker: any; + private _marker: L.Marker; private readonly _hasLocation: UIEventSource; + private readonly _currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>; + private readonly _leafletMap: UIEventSource; + private readonly _featureSwitch: UIEventSource; - constructor() { + constructor(currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>, + leafletMap: UIEventSource, + featureSwitch: UIEventSource) { super(undefined); - this._hasLocation = State.state.currentGPSLocation.map((location) => location !== undefined); + this._currentGPSLocation = currentGPSLocation; + this._leafletMap = leafletMap; + this._featureSwitch = featureSwitch; + this._hasLocation = currentGPSLocation.map((location) => location !== undefined); var self = this; import("../../vendor/Leaflet.AccuratePosition.js").then(() => { self.init(); @@ -33,11 +39,11 @@ export class GeoLocationHandler extends UIElement { function onAccuratePositionProgress(e) { - State.state.currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy}); + self._currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy}); } function onAccuratePositionFound(e) { - State.state.currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy}); + self._currentGPSLocation.setData({latlng: e.latlng, accuracy: e.accuracy}); } function onAccuratePositionError(e) { @@ -45,15 +51,13 @@ export class GeoLocationHandler extends UIElement { } - const bm : Basemap = State.state.bm; - const map = bm.map; + const map = this._leafletMap.data; map.on('accuratepositionprogress', onAccuratePositionProgress); map.on('accuratepositionfound', onAccuratePositionFound); map.on('accuratepositionerror', onAccuratePositionError); - - State.state.currentGPSLocation.addCallback((location) => { + this._currentGPSLocation.addCallback((location) => { const color = getComputedStyle(document.body).getPropertyValue("--catch-detail-color") const icon = L.icon( @@ -62,7 +66,7 @@ export class GeoLocationHandler extends UIElement { 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}); newMarker.addTo(map); @@ -88,10 +92,10 @@ export class GeoLocationHandler extends UIElement { } InnerRender(): string { - if(!State.state.featureSwitchGeolocation.data){ + if (!this._featureSwitch.data) { return ""; } - + if (this._hasLocation.data) { return Svg.crosshair_blue_img; } @@ -102,44 +106,6 @@ export class GeoLocationHandler extends UIElement { return Svg.crosshair_img; } - - private StartGeolocating(zoomlevel = 19) { - const self = this; - const map = State.state.bm.map; - if (self._permission.data === "denied") { - return ""; - } - if (State.state.currentGPSLocation.data !== undefined) { - State.state.bm.map.setView( - State.state.currentGPSLocation.data.latlng, zoomlevel - ); - } - - - console.log("Searching location using GPS") - map.findAccuratePosition({ - maxWait: 10000, // defaults to 10000 - desiredAccuracy: 50 // defaults to 20 - }); - - - if (!self._isActive.data) { - self._isActive.setData(true); - Utils.DoEvery(60000, () => { - - if (document.visibilityState !== "visible") { - console.log("Not starting gps: document not visible") - return; - } - - map.findAccuratePosition({ - maxWait: 10000, // defaults to 10000 - desiredAccuracy: 50 // defaults to 20 - }); - }) - } - } - InnerUpdate(htmlElement: HTMLElement) { super.InnerUpdate(htmlElement); @@ -156,4 +122,41 @@ export class GeoLocationHandler extends UIElement { } + private StartGeolocating(zoomlevel = 19) { + const self = this; + const map : any = this._leafletMap.data; + if (self._permission.data === "denied") { + return ""; + } + if (this._currentGPSLocation.data !== undefined) { + this._leafletMap.data.setView( + this._currentGPSLocation.data.latlng, zoomlevel + ); + } + + + console.log("Searching location using GPS") + map.findAccuratePosition({ + maxWait: 10000, // defaults to 10000 + desiredAccuracy: 50 // defaults to 20 + }); + + + if (!self._isActive.data) { + self._isActive.setData(true); + Utils.DoEvery(60000, () => { + + if (document.visibilityState !== "visible") { + console.log("Not starting gps: document not visible") + return; + } + + map.findAccuratePosition({ + maxWait: 10000, // defaults to 10000 + desiredAccuracy: 50 // defaults to 20 + }); + }) + } + } + } \ No newline at end of file diff --git a/Logic/Actors/LayerResetter.ts b/Logic/Actors/LayerResetter.ts new file mode 100644 index 0000000..a38ac2b --- /dev/null +++ b/Logic/Actors/LayerResetter.ts @@ -0,0 +1,44 @@ +import {UIEventSource} from "../UIEventSource"; +import {BaseLayer} from "../../Models/BaseLayer"; +import AvailableBaseLayers from "./AvailableBaseLayers"; +import Loc from "../../Models/Loc"; + +/** + * Sets the current background layer to a layer that is actually available + */ +export default class LayerResetter { + + constructor( currentBackgroundLayer: UIEventSource, + location: UIEventSource, + availableLayers: UIEventSource, + defaultLayerId: UIEventSource = undefined) { + defaultLayerId = defaultLayerId ?? new UIEventSource(AvailableBaseLayers.osmCarto.id); + + // Change the baselayer back to OSM if we go out of the current range of the layer + availableLayers.addCallbackAndRun(availableLayers => { + let defaultLayer = undefined; + const currentLayer = currentBackgroundLayer.data.id; + for (const availableLayer of availableLayers) { + if (availableLayer.id === currentLayer) { + + if (availableLayer.max_zoom < location.data.zoom) { + break; + } + + if (availableLayer.min_zoom > location.data.zoom) { + break; + } + if(availableLayer.id === defaultLayerId.data){ + defaultLayer = availableLayer; + } + return; // All good - the current layer still works! + } + } + // Oops, we panned out of range for this layer! + console.log("AvailableBaseLayers-actor: detected that the current bounds aren't sufficient anymore - reverting to OSM standard") + currentBackgroundLayer.setData(defaultLayer ?? AvailableBaseLayers.osmCarto); + }); + + } + +} \ No newline at end of file diff --git a/Logic/Leaflet/StrayClickHandler.ts b/Logic/Actors/StrayClickHandler.ts similarity index 62% rename from Logic/Leaflet/StrayClickHandler.ts rename to Logic/Actors/StrayClickHandler.ts index b8ab83d..d499cc7 100644 --- a/Logic/Leaflet/StrayClickHandler.ts +++ b/Logic/Actors/StrayClickHandler.ts @@ -1,8 +1,9 @@ import * as L from "leaflet"; import {UIElement} from "../../UI/UIElement"; -import State from "../../State"; import {Img} from "../../UI/Img"; import Svg from "../../Svg"; +import {UIEventSource} from "../UIEventSource"; +import {FilteredLayer} from "../FilteredLayer"; /** * The stray-click-hanlders adds a marker to the map if no feature was clicked. @@ -13,25 +14,29 @@ export class StrayClickHandler { private _uiToShow: (() => UIElement); constructor( + lastClickLocation: UIEventSource<{ lat: number, lon:number }>, + selectedElement: UIEventSource, + filteredLayers: UIEventSource, + leafletMap: UIEventSource, + fullscreenMessage: UIEventSource, uiToShow: (() => UIElement)) { this._uiToShow = uiToShow; const self = this; - const map = State.state.bm.map; - State.state.filteredLayers.data.forEach((filteredLayer) => { + filteredLayers.data.forEach((filteredLayer) => { filteredLayer.isDisplayed.addCallback(isEnabled => { - if(isEnabled && self._lastMarker){ + 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... - map.removeLayer(self._lastMarker); + leafletMap.data.removeLayer(self._lastMarker); } }) }) - State.state.bm.LastClickLocation.addCallback(function (lastClick) { - State.state.selectedElement.setData(undefined); + lastClickLocation.addCallback(function (lastClick) { + selectedElement.setData(undefined); if (self._lastMarker !== undefined) { - map.removeLayer(self._lastMarker); + leafletMap.data?.removeLayer(self._lastMarker); } self._lastMarker = L.marker([lastClick.lat, lastClick.lon], { icon: L.icon({ @@ -43,18 +48,18 @@ export class StrayClickHandler { }); const uiElement = uiToShow(); const popup = L.popup().setContent(uiElement.Render()); - self._lastMarker.addTo(map); + self._lastMarker.addTo(leafletMap.data); self._lastMarker.bindPopup(popup); self._lastMarker.on("click", () => { - State.state.fullScreenMessage.setData(self._uiToShow()); + fullscreenMessage.setData(self._uiToShow()); uiElement.Update(); }); }); - State.state.selectedElement.addCallback(() => { + selectedElement.addCallback(() => { if (self._lastMarker !== undefined) { - map.removeLayer(self._lastMarker); + leafletMap.data.removeLayer(self._lastMarker); this._lastMarker = undefined; } }) diff --git a/Logic/ElementStorage.ts b/Logic/ElementStorage.ts index 74a236a..c127d2c 100644 --- a/Logic/ElementStorage.ts +++ b/Logic/ElementStorage.ts @@ -1,5 +1,5 @@ /** - * Keeps track of a dictionary 'elementID' -> element + * Keeps track of a dictionary 'elementID' -> UIEventSource */ import {UIEventSource} from "./UIEventSource"; diff --git a/Logic/FilteredLayer.ts b/Logic/FilteredLayer.ts index efd32ec..c143712 100644 --- a/Logic/FilteredLayer.ts +++ b/Logic/FilteredLayer.ts @@ -58,7 +58,7 @@ export class FilteredLayer { [State.state.locationControl] ); this.combinedIsDisplayed.addCallback(function (isDisplayed) { - const map = State.state.bm.map; + const map = State.state.leafletMap.data; if (self._geolayer !== undefined && self._geolayer !== null) { if (isDisplayed) { self._geolayer.addTo(map); @@ -116,7 +116,7 @@ export class FilteredLayer { 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.bm.map.removeLayer(this._geolayer); + State.state.leafletMap.data.removeLayer(this._geolayer); } // We fetch all the data we have to show: @@ -200,7 +200,7 @@ export class FilteredLayer { const center = GeoOperations.centerpoint(feature).geometry.coordinates; popup.setLatLng({lat: center[1], lng: center[0]}); - popup.openOn(State.state.bm.map); + popup.openOn(State.state.leafletMap.data); State.state.selectedElement.setData(feature); uiElement.Update(); } @@ -209,7 +209,7 @@ export class FilteredLayer { }); if (this.combinedIsDisplayed.data) { - this._geolayer.addTo(State.state.bm.map); + this._geolayer.addTo(State.state.leafletMap.data); } } diff --git a/Logic/Osm/Geocoding.ts b/Logic/Osm/Geocoding.ts index 68934b4..5629fa1 100644 --- a/Logic/Osm/Geocoding.ts +++ b/Logic/Osm/Geocoding.ts @@ -8,7 +8,7 @@ export class Geocoding { static Search(query: string, handleResult: ((places: { display_name: string, lat: number, lon: number, boundingbox: number[] }[]) => void), onFail: (() => void)) { - const b = State.state.bm.map.getBounds(); + const b = State.state.leafletMap.data.getBounds(); console.log(b); $.getJSON( Geocoding.host + "format=json&limit=1&viewbox=" + diff --git a/Logic/UpdateFromOverpass.ts b/Logic/UpdateFromOverpass.ts index 8762b30..429708c 100644 --- a/Logic/UpdateFromOverpass.ts +++ b/Logic/UpdateFromOverpass.ts @@ -162,7 +162,7 @@ export class UpdateFromOverpass { return; } - const bounds = state.bm.map.getBounds(); + const bounds = state.leafletMap.data.getBounds(); const diff = state.layoutToUse.data.widenFactor; @@ -196,7 +196,7 @@ export class UpdateFromOverpass { return false; } - const b = state.bm.map.getBounds(); + const b = state.leafletMap.data.getBounds(); return b.getSouth() >= bounds.south && b.getNorth() <= bounds.north && b.getEast() <= bounds.east && diff --git a/State.ts b/State.ts index c13e747..65b3d76 100644 --- a/State.ts +++ b/State.ts @@ -18,6 +18,8 @@ import {BaseLayer} from "./Models/BaseLayer"; import Loc from "./Models/Loc"; import Constants from "./Models/Constants"; import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers"; +import * as L from "leaflet" +import LayerResetter from "./Logic/Actors/LayerResetter"; /** * Contains the global state: a bunch of UI-event sources @@ -41,9 +43,9 @@ export default class State { */ public changes: Changes; /** - THe basemap with leaflet instance + The leaflet instance of the big basemap */ - public bm; + public leafletMap = new UIEventSource(undefined); /** * Background layer id */ @@ -91,8 +93,11 @@ export default class State { * The map location: currently centered lat, lon and zoom */ public readonly locationControl = new UIEventSource(undefined); - public readonly backgroundLayer = new UIEventSource(AvailableBaseLayers.osmCarto); - + public readonly backgroundLayer; + /* Last location where a click was registered + */ + public readonly LastClickLocation: UIEventSource<{ lat: number, lon: number }> = new UIEventSource<{ lat: number, lon: number }>(undefined) + /** * The location as delivered by the GPS */ @@ -114,23 +119,12 @@ export default class State { const self = this; this.layoutToUse.setData(layoutToUse); - function asFloat(source: UIEventSource): UIEventSource { - return source.map(str => { - let parsed = parseFloat(str); - return isNaN(parsed) ? undefined : parsed; - }, [], fl => { - if (fl === undefined || isNaN(fl)) { - return undefined; - } - return ("" + fl).substr(0, 8); - }) - } - const zoom = asFloat( + const zoom = State.asFloat( QueryParameters.GetQueryParameter("z", "" + layoutToUse.startZoom, "The initial/current zoom level") .syncWith(LocalStorageSource.Get("zoom"))); - const lat = asFloat(QueryParameters.GetQueryParameter("lat", "" + layoutToUse.startLat, "The initial/current latitude") + const lat = State.asFloat(QueryParameters.GetQueryParameter("lat", "" + layoutToUse.startLat, "The initial/current latitude") .syncWith(LocalStorageSource.Get("lat"))); - const lon = asFloat(QueryParameters.GetQueryParameter("lon", "" + layoutToUse.startLon, "The initial/current longitude of the app") + const lon = State.asFloat(QueryParameters.GetQueryParameter("lon", "" + layoutToUse.startLon, "The initial/current longitude of the app") .syncWith(LocalStorageSource.Get("lon"))); @@ -153,11 +147,35 @@ export default class State { }); + this.availableBackgroundLayers = new AvailableBaseLayers(this.locationControl).availableEditorLayers; + this.backgroundLayer = QueryParameters.GetQueryParameter("background", + this.layoutToUse.data.defaultBackgroundId ?? AvailableBaseLayers.osmCarto.id, + "The id of the background layer to start with") + .map((selectedId: string) => { + console.log("SELECTED ID", selectedId) + const available = self.availableBackgroundLayers.data; + for (const layer of available) { + if (layer.id === selectedId) { + return layer; + } + } + return AvailableBaseLayers.osmCarto; + }, [], layer => layer.id); + + + new LayerResetter( + this.backgroundLayer,this.locationControl, + this.availableBackgroundLayers, this.layoutToUse.map((layout : LayoutConfig)=> layout.defaultBackgroundId)); + + + + + 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 - // It takes the current layout, extracts the default value for this query paramter. A query parameter event source is then retreived and flattened + // It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened return UIEventSource.flatten( self.layoutToUse.map((layout) => { const defaultValue = deflt(layout); @@ -186,6 +204,9 @@ export default class State { this.featureSwitchGeolocation = featSw("fs-geolocation", (layoutToUse) => layoutToUse?.enableGeolocation ?? true, "Disables/Enables the geolocation button"); + + + const testParam = QueryParameters.GetQueryParameter("test", "false", "If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org").data; this.osmConnection = new OsmConnection( @@ -264,4 +285,16 @@ export default class State { } + private static asFloat(source: UIEventSource): UIEventSource { + return source.map(str => { + let parsed = parseFloat(str); + return isNaN(parsed) ? undefined : parsed; + }, [], fl => { + if (fl === undefined || isNaN(fl)) { + return undefined; + } + return ("" + fl).substr(0, 8); + }) + } + } diff --git a/Logic/Leaflet/Basemap.ts b/UI/Basemap.ts similarity index 74% rename from Logic/Leaflet/Basemap.ts rename to UI/Basemap.ts index a9c0c09..3f871e6 100644 --- a/Logic/Leaflet/Basemap.ts +++ b/UI/Basemap.ts @@ -1,27 +1,23 @@ import * as L from "leaflet" -import {UIEventSource} from "../UIEventSource"; -import {UIElement} from "../../UI/UIElement"; -import {BaseLayer} from "../../Models/BaseLayer"; -import AvailableBaseLayers from "../Actors/AvailableBaseLayers"; -import Loc from "../../Models/Loc"; +import {UIEventSource} from "../Logic/UIEventSource"; +import Loc from "../Models/Loc"; +import {UIElement} from "./UIElement"; +import {BaseLayer} from "../Models/BaseLayer"; export class Basemap { - // @ts-ignore - public readonly map: Map; - - public readonly LastClickLocation: UIEventSource<{ lat: number, lon: number }> = new UIEventSource<{ lat: number, lon: number }>(undefined) - + public readonly map: L.Map; constructor(leafletElementId: string, location: UIEventSource, currentLayer: UIEventSource, + lastClickLocation: UIEventSource<{ lat: number, lon: number }>, extraAttribution: UIElement) { this.map = L.map(leafletElementId, { center: [location.data.lat ?? 0, location.data.lon ?? 0], zoom: location.data.zoom ?? 2, - layers: [AvailableBaseLayers.osmCarto.layer], + layers: [currentLayer.data.layer], }); L.control.scale( @@ -64,11 +60,14 @@ export class Basemap { }); this.map.on("click", function (e) { - self.LastClickLocation.setData({lat: e.latlng.lat, lon: e.latlng.lng}) + // @ts-ignore + lastClickLocation.setData({lat: e.latlng.lat, lon: e.latlng.lng}) }); this.map.on("contextmenu", function (e) { - self.LastClickLocation.setData({lat: e.latlng.lat, lon: e.latlng.lng}); + // @ts-ignore + lastClickLocation.setData({lat: e.latlng.lat, lon: e.latlng.lng}); + // @ts-ignore e.preventDefault(); }); diff --git a/UI/Input/DirectionInput.ts b/UI/Input/DirectionInput.ts index 3fe8344..83b18a1 100644 --- a/UI/Input/DirectionInput.ts +++ b/UI/Input/DirectionInput.ts @@ -2,10 +2,7 @@ import {InputElement} from "./InputElement"; import {UIEventSource} from "../../Logic/UIEventSource"; import Combine from "../Base/Combine"; import Svg from "../../Svg"; -import * as L from "leaflet" -import * as X from "leaflet-providers" -import {Basemap} from "../../Logic/Leaflet/Basemap"; -import State from "../../State"; + /** * Selects a direction in degrees diff --git a/UI/Misc/Attribution.ts b/UI/Misc/Attribution.ts index 5c954ba..ccf0e3c 100644 --- a/UI/Misc/Attribution.ts +++ b/UI/Misc/Attribution.ts @@ -1,30 +1,30 @@ import {UIElement} from "../UIElement"; import Link from "../Base/Link"; import Svg from "../../Svg"; -import {Basemap} from "../../Logic/Leaflet/Basemap"; import Combine from "../Base/Combine"; import {UIEventSource} from "../../Logic/UIEventSource"; import {UserDetails} from "../../Logic/Osm/OsmConnection"; import Constants from "../../Models/Constants"; import LayoutConfig from "../../Customizations/JSON/LayoutConfig"; import Loc from "../../Models/Loc"; +import * as L from "leaflet" export default class Attribution extends UIElement { private readonly _location: UIEventSource; private readonly _layoutToUse: UIEventSource; private readonly _userDetails: UIEventSource; - private readonly _basemap: Basemap; + private readonly _leafletMap: UIEventSource; constructor(location: UIEventSource, userDetails: UIEventSource, layoutToUse: UIEventSource, - basemap: Basemap) { + leafletMap: UIEventSource) { super(location); this._layoutToUse = layoutToUse; this.ListenTo(layoutToUse); this._userDetails = userDetails; - this._basemap = basemap; + this._leafletMap = leafletMap; this.ListenTo(userDetails); this._location = location; this.SetClass("map-attribution"); @@ -47,9 +47,9 @@ export default class Attribution extends UIElement { } let editWithJosm: (UIElement | string) = "" if (location !== undefined && - this._basemap !== undefined && + this._leafletMap.data !== undefined && userDetails.csCount >= Constants.userJourney.tagsVisibleAndWikiLinked) { - const bounds = this._basemap.map.getBounds(); + const bounds = this._leafletMap.data.getBounds(); const top = bounds.getNorth(); const bottom = bounds.getSouth(); const right = bounds.getEast(); diff --git a/UI/SearchAndGo.ts b/UI/SearchAndGo.ts index 3db4f88..289ee71 100644 --- a/UI/SearchAndGo.ts +++ b/UI/SearchAndGo.ts @@ -61,7 +61,7 @@ export class SearchAndGo extends UIElement { [bb[0], bb[2]], [bb[1], bb[3]] ] - State.state.bm.map.fitBounds(bounds); + State.state.leafletMap.data.fitBounds(bounds); self._placeholder.setData(Translations.t.general.search.search); }, () => { diff --git a/UI/SimpleAddUI.ts b/UI/SimpleAddUI.ts index 2946080..c8a92be 100644 --- a/UI/SimpleAddUI.ts +++ b/UI/SimpleAddUI.ts @@ -115,7 +115,7 @@ export class SimpleAddUI extends UIElement { private CreatePoint(tags: Tag[], layerToAddTo: FilteredLayer) { return () => { - const loc = State.state.bm.LastClickLocation.data; + const loc = State.state.LastClickLocation.data; let feature = State.state.changes.createElement(tags, loc.lat, loc.lon); State.state.selectedElement.setData(feature); layerToAddTo.AddNewElement(feature); diff --git a/UI/UserBadge.ts b/UI/UserBadge.ts index ff1c15d..b4b34a2 100644 --- a/UI/UserBadge.ts +++ b/UI/UserBadge.ts @@ -59,7 +59,7 @@ export class UserBadge extends UIElement { if (home === undefined) { return; } - State.state.bm.map.setView([home.lat, home.lon], 16); + State.state.leafletMap.data.setView([home.lat, home.lon], 16); }); } diff --git a/index.ts b/index.ts index f55db7c..168ca1b 100644 --- a/index.ts +++ b/index.ts @@ -6,6 +6,7 @@ import {UIEventSource} from "./Logic/UIEventSource"; import * as $ from "jquery"; import LayoutConfig from "./Customizations/JSON/LayoutConfig"; import {Utils} from "./Utils"; +import {Overpass} from "./Logic/Osm/Overpass"; let defaultLayout = "bookcases" // --------------------- Special actions based on the parameters ----------------- @@ -17,19 +18,9 @@ if (location.href.startsWith("http://buurtnatuur.be")) { if (location.href.indexOf("buurtnatuur.be") >= 0) { - // Reload the https version. This is important for the 'locate me' button defaultLayout = "buurtnatuur" } - -if (location.href.indexOf("buurtnatuur.be") >= 0) { - defaultLayout = "buurtnatuur" -} - -if(location.href.indexOf("pietervdvn.github.io") >= 0){ - defaultLayout = "bookcases" -} - const customCssQP = QueryParameters.GetQueryParameter("custom-css", "", "If specified, the custom css from the given link will be loaded additionaly"); if(customCssQP.data !== undefined && customCssQP.data !== ""){ Utils.LoadCustomCss(customCssQP.data); @@ -43,7 +34,7 @@ if (location.hostname === "localhost" || location.hostname === "127.0.0.1") { testing.setData(testing.data ?? "true") // If you have a testfile somewhere, enable this to spoof overpass // This should be hosted independantly, e.g. with `cd assets; webfsd -p 8080` + a CORS plugin to disable cors rules - //Overpass.testUrl = "http://127.0.0.1:8080/streetwidths.geojson"; + // Overpass.testUrl = "http://127.0.0.1:8080/streetwidths.geojson"; } else { testing = QueryParameters.GetQueryParameter("test", "false"); } @@ -78,7 +69,7 @@ if (layoutFromBase64.startsWith("wiki:")) { $.ajax({ url: url, success: function (data) { - // Hacky McHackFace has been working here. This probably break in the future + // Hacky McHackFace has been working here. This'll probably break in the future const startTrigger = "
"; const start = data.indexOf(startTrigger); data = data.substr(start, @@ -113,4 +104,3 @@ if (layoutFromBase64.startsWith("wiki:")) { window.addEventListener('contextmenu', function (e) { // Not compatible with IE < 9 e.preventDefault(); }, false); -// console.log(QueryParameters.GenerateQueryParameterDocs())