Further refactoring
This commit is contained in:
parent
e4a2fd1daf
commit
7a7b34b0fa
17 changed files with 350 additions and 297 deletions
|
@ -7,7 +7,7 @@ import Combine from "./UI/Base/Combine";
|
||||||
import {UIElement} from "./UI/UIElement";
|
import {UIElement} from "./UI/UIElement";
|
||||||
import {MoreScreen} from "./UI/MoreScreen";
|
import {MoreScreen} from "./UI/MoreScreen";
|
||||||
import {FilteredLayer} from "./Logic/FilteredLayer";
|
import {FilteredLayer} from "./Logic/FilteredLayer";
|
||||||
import {Basemap} from "./Logic/Leaflet/Basemap";
|
import {Basemap} from "./UI/Basemap";
|
||||||
import State from "./State";
|
import State from "./State";
|
||||||
import {WelcomeMessage} from "./UI/WelcomeMessage";
|
import {WelcomeMessage} from "./UI/WelcomeMessage";
|
||||||
import {LayerSelection} from "./UI/LayerSelection";
|
import {LayerSelection} from "./UI/LayerSelection";
|
||||||
|
@ -17,7 +17,7 @@ import {UIEventSource} from "./Logic/UIEventSource";
|
||||||
import {QueryParameters} from "./Logic/Web/QueryParameters";
|
import {QueryParameters} from "./Logic/Web/QueryParameters";
|
||||||
import {PersonalLayersPanel} from "./UI/PersonalLayersPanel";
|
import {PersonalLayersPanel} from "./UI/PersonalLayersPanel";
|
||||||
import Locale from "./UI/i18n/Locale";
|
import Locale from "./UI/i18n/Locale";
|
||||||
import {StrayClickHandler} from "./Logic/Leaflet/StrayClickHandler";
|
import {StrayClickHandler} from "./Logic/Actors/StrayClickHandler";
|
||||||
import {SimpleAddUI} from "./UI/SimpleAddUI";
|
import {SimpleAddUI} from "./UI/SimpleAddUI";
|
||||||
import {CenterMessageBox} from "./UI/CenterMessageBox";
|
import {CenterMessageBox} from "./UI/CenterMessageBox";
|
||||||
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
|
import {AllKnownLayouts} from "./Customizations/AllKnownLayouts";
|
||||||
|
@ -25,11 +25,10 @@ import {TagUtils} from "./Logic/Tags";
|
||||||
import {UserBadge} from "./UI/UserBadge";
|
import {UserBadge} from "./UI/UserBadge";
|
||||||
import {SearchAndGo} from "./UI/SearchAndGo";
|
import {SearchAndGo} from "./UI/SearchAndGo";
|
||||||
import {FullScreenMessageBox} from "./UI/FullScreenMessageBoxHandler";
|
import {FullScreenMessageBox} from "./UI/FullScreenMessageBoxHandler";
|
||||||
import {GeoLocationHandler} from "./Logic/Leaflet/GeoLocationHandler";
|
import {GeoLocationHandler} from "./Logic/Actors/GeoLocationHandler";
|
||||||
import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
|
import {LocalStorageSource} from "./Logic/Web/LocalStorageSource";
|
||||||
import {Utils} from "./Utils";
|
import {Utils} from "./Utils";
|
||||||
import BackgroundSelector from "./UI/BackgroundSelector";
|
import BackgroundSelector from "./UI/BackgroundSelector";
|
||||||
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
|
|
||||||
import {FeatureInfoBox} from "./UI/Popup/FeatureInfoBox";
|
import {FeatureInfoBox} from "./UI/Popup/FeatureInfoBox";
|
||||||
import Svg from "./Svg";
|
import Svg from "./Svg";
|
||||||
import Link from "./UI/Base/Link";
|
import Link from "./UI/Base/Link";
|
||||||
|
@ -44,40 +43,6 @@ import Constants from "./Models/Constants";
|
||||||
export class InitUiElements {
|
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<string>, layoutName: string,
|
static InitAll(layoutToUse: LayoutConfig, layoutFromBase64: string, testing: UIEventSource<string>, layoutName: string,
|
||||||
layoutDefinition: string = "") {
|
layoutDefinition: string = "") {
|
||||||
if (layoutToUse === undefined) {
|
if (layoutToUse === undefined) {
|
||||||
|
@ -88,8 +53,10 @@ export class InitUiElements {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Using layout: ", layoutToUse.id, "LayoutFromBase64 is ", layoutFromBase64);
|
console.log("Using layout: ", layoutToUse.id, "LayoutFromBase64 is ", layoutFromBase64);
|
||||||
|
|
||||||
|
|
||||||
State.state = new State(layoutToUse);
|
State.state = new State(layoutToUse);
|
||||||
|
|
||||||
// This 'leaks' the global state via the window object, useful for debugging
|
// This 'leaks' the global state via the window object, useful for debugging
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.mapcomplete_state = State.state;
|
window.mapcomplete_state = State.state;
|
||||||
|
@ -116,7 +83,7 @@ export class InitUiElements {
|
||||||
InitUiElements.setupAllLayerElements();
|
InitUiElements.setupAllLayerElements();
|
||||||
|
|
||||||
if (layoutToUse.customCss !== undefined) {
|
if (layoutToUse.customCss !== undefined) {
|
||||||
Utils.LoadCustomCss(layoutToUse.customCss);
|
Utils.LoadCustomCss(layoutToUse.customCss);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFavs() {
|
function updateFavs() {
|
||||||
|
@ -163,7 +130,7 @@ export class InitUiElements {
|
||||||
if (feature === undefined) {
|
if (feature === undefined) {
|
||||||
State.state.fullScreenMessage.setData(undefined);
|
State.state.fullScreenMessage.setData(undefined);
|
||||||
}
|
}
|
||||||
if (feature?.properties === undefined) {
|
if (feature?.properties === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = feature.properties;
|
const data = feature.properties;
|
||||||
|
@ -176,11 +143,11 @@ export class InitUiElements {
|
||||||
if (!applicable) {
|
if (!applicable) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if((layer.title ?? null) === null && layer.tagRenderings.length === 0){
|
if ((layer.title ?? null) === null && layer.tagRenderings.length === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This layer is the layer that gives the questions
|
// This layer is the layer that gives the questions
|
||||||
const featureBox = new FeatureInfoBox(
|
const featureBox = new FeatureInfoBox(
|
||||||
State.state.allElements.getEventSourceById(data.id),
|
State.state.allElements.getEventSourceById(data.id),
|
||||||
|
@ -231,15 +198,19 @@ export class InitUiElements {
|
||||||
iconAnchor: [15, 15]
|
iconAnchor: [15, 15]
|
||||||
});
|
});
|
||||||
const marker = L.marker([home.lat, home.lon], {icon: icon})
|
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;`)
|
.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");
|
.AttachTo("geolocate-button");
|
||||||
State.state.locationControl.ping();
|
State.state.locationControl.ping();
|
||||||
}
|
}
|
||||||
|
|
||||||
static LoadLayoutFromHash(userLayoutParam: UIEventSource<string>) {
|
static LoadLayoutFromHash(userLayoutParam: UIEventSource<string>) {
|
||||||
try {
|
try {
|
||||||
let hash = location.hash.substr(1);
|
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: `<img src='${layoutToUse.icon}'>`, 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, "<br/>Version "+Constants.vNumber]).Render();
|
|
||||||
}, [Locale.language]))
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
return new TabbedComponent(tabs, State.state.welcomeMessageOpenedTab)
|
|
||||||
.ListenTo(State.state.osmConnection.userDetails);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static InitWelcomeMessage() {
|
static InitWelcomeMessage() {
|
||||||
|
|
||||||
const fullOptions = this.CreateWelcomePane();
|
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, "<br/>", layerControlPanel]);
|
|
||||||
}
|
|
||||||
return layerControlPanel;
|
|
||||||
}
|
|
||||||
|
|
||||||
static InitLayerSelection() {
|
static InitLayerSelection() {
|
||||||
InitUiElements.OnlyIf(State.state.featureSwitchLayers, () => {
|
InitUiElements.OnlyIf(State.state.featureSwitchLayers, () => {
|
||||||
|
@ -435,37 +338,27 @@ export class InitUiElements {
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static InitBaseMap() {
|
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,
|
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", () => {
|
bm.map.on("popupclose", () => {
|
||||||
State.state.selectedElement.setData(undefined)
|
State.state.selectedElement.setData(undefined)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
State.state.layerUpdater = new UpdateFromOverpass(State.state);
|
State.state.layerUpdater = new UpdateFromOverpass(State.state);
|
||||||
|
|
||||||
State.state.availableBackgroundLayers = new AvailableBaseLayers(State.state.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() {
|
static InitLayers() {
|
||||||
|
|
||||||
const flayers: FilteredLayer[] = []
|
const flayers: FilteredLayer[] = []
|
||||||
|
@ -496,4 +389,111 @@ export class InitUiElements {
|
||||||
State.state.filteredLayers.setData(flayers);
|
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: `<img src='${layoutToUse.icon}'>`, 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, "<br/>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, "<br/>", layerControlPanel]);
|
||||||
|
}
|
||||||
|
return layerControlPanel;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,10 +1,9 @@
|
||||||
import * as editorlayerindex from "../../assets/editor-layer-index.json"
|
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 {UIEventSource} from "../UIEventSource";
|
||||||
import {GeoOperations} from "../GeoOperations";
|
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 {TileLayer} from "leaflet";
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
|
|
||||||
|
@ -32,12 +31,16 @@ export default class AvailableBaseLayers {
|
||||||
public static layerOverview = AvailableBaseLayers.LoadRasterIndex().concat(AvailableBaseLayers.LoadProviderIndex());
|
public static layerOverview = AvailableBaseLayers.LoadRasterIndex().concat(AvailableBaseLayers.LoadProviderIndex());
|
||||||
public availableEditorLayers: UIEventSource<BaseLayer[]>;
|
public availableEditorLayers: UIEventSource<BaseLayer[]>;
|
||||||
|
|
||||||
constructor(location: UIEventSource<{ lat: number, lon: number, zoom: number }>,
|
constructor(location: UIEventSource<{ lat: number, lon: number, zoom: number }>) {
|
||||||
currentBackgroundLayer: UIEventSource<BaseLayer>) {
|
|
||||||
const self = this;
|
const self = this;
|
||||||
this.availableEditorLayers =
|
this.availableEditorLayers =
|
||||||
location.map(
|
location.map(
|
||||||
(currentLocation) => {
|
(currentLocation) => {
|
||||||
|
|
||||||
|
if(currentLocation === undefined){
|
||||||
|
return AvailableBaseLayers.layerOverview;
|
||||||
|
}
|
||||||
|
|
||||||
const currentLayers = self.availableEditorLayers?.data;
|
const currentLayers = self.availableEditorLayers?.data;
|
||||||
const newLayers = AvailableBaseLayers.AvailableLayersAt(currentLocation?.lon, currentLocation?.lat);
|
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[] {
|
private static AvailableLayersAt(lon: number, lat: number): BaseLayer[] {
|
||||||
const availableLayers = [AvailableBaseLayers.osmCarto]
|
const availableLayers = [AvailableBaseLayers.osmCarto]
|
||||||
const globalLayers = [];
|
const globalLayers = [];
|
||||||
for (const i in AvailableBaseLayers.layerOverview) {
|
for (const layerOverviewItem of AvailableBaseLayers.layerOverview) {
|
||||||
const layer = AvailableBaseLayers.layerOverview[i];
|
const layer = layerOverviewItem;
|
||||||
|
|
||||||
if (layer.feature?.geometry === undefined || layer.feature?.geometry === null) {
|
if (layer.feature?.geometry === undefined || layer.feature?.geometry === null) {
|
||||||
globalLayers.push(layer);
|
globalLayers.push(layer);
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import * as L from "leaflet";
|
import * as L from "leaflet";
|
||||||
import {UIEventSource} from "../UIEventSource";
|
import {UIEventSource} from "../UIEventSource";
|
||||||
import {UIElement} from "../../UI/UIElement";
|
import {UIElement} from "../../UI/UIElement";
|
||||||
import State from "../../State";
|
|
||||||
import {Utils} from "../../Utils";
|
import {Utils} from "../../Utils";
|
||||||
import {Basemap} from "./Basemap";
|
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
import {Img} from "../../UI/Img";
|
import {Img} from "../../UI/Img";
|
||||||
|
|
||||||
|
@ -11,12 +9,20 @@ export class GeoLocationHandler extends UIElement {
|
||||||
|
|
||||||
private readonly _isActive: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
private readonly _isActive: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||||
private readonly _permission: UIEventSource<string> = new UIEventSource<string>("");
|
private readonly _permission: UIEventSource<string> = new UIEventSource<string>("");
|
||||||
private _marker: any;
|
private _marker: L.Marker;
|
||||||
private readonly _hasLocation: UIEventSource<boolean>;
|
private readonly _hasLocation: UIEventSource<boolean>;
|
||||||
|
private readonly _currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>;
|
||||||
|
private readonly _leafletMap: UIEventSource<L.Map>;
|
||||||
|
private readonly _featureSwitch: UIEventSource<boolean>;
|
||||||
|
|
||||||
constructor() {
|
constructor(currentGPSLocation: UIEventSource<{ latlng: any; accuracy: number }>,
|
||||||
|
leafletMap: UIEventSource<L.Map>,
|
||||||
|
featureSwitch: UIEventSource<boolean>) {
|
||||||
super(undefined);
|
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;
|
var self = this;
|
||||||
import("../../vendor/Leaflet.AccuratePosition.js").then(() => {
|
import("../../vendor/Leaflet.AccuratePosition.js").then(() => {
|
||||||
self.init();
|
self.init();
|
||||||
|
@ -33,11 +39,11 @@ export class GeoLocationHandler extends UIElement {
|
||||||
|
|
||||||
|
|
||||||
function onAccuratePositionProgress(e) {
|
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) {
|
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) {
|
function onAccuratePositionError(e) {
|
||||||
|
@ -45,15 +51,13 @@ export class GeoLocationHandler extends UIElement {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const bm : Basemap = State.state.bm;
|
const map = this._leafletMap.data;
|
||||||
const map = bm.map;
|
|
||||||
map.on('accuratepositionprogress', onAccuratePositionProgress);
|
map.on('accuratepositionprogress', onAccuratePositionProgress);
|
||||||
map.on('accuratepositionfound', onAccuratePositionFound);
|
map.on('accuratepositionfound', onAccuratePositionFound);
|
||||||
map.on('accuratepositionerror', onAccuratePositionError);
|
map.on('accuratepositionerror', onAccuratePositionError);
|
||||||
|
|
||||||
|
|
||||||
|
this._currentGPSLocation.addCallback((location) => {
|
||||||
State.state.currentGPSLocation.addCallback((location) => {
|
|
||||||
|
|
||||||
const color = getComputedStyle(document.body).getPropertyValue("--catch-detail-color")
|
const color = getComputedStyle(document.body).getPropertyValue("--catch-detail-color")
|
||||||
const icon = L.icon(
|
const icon = L.icon(
|
||||||
|
@ -62,7 +66,7 @@ export class GeoLocationHandler extends UIElement {
|
||||||
iconSize: [40, 40], // size of the icon
|
iconSize: [40, 40], // size of the icon
|
||||||
iconAnchor: [20, 20], // point of the icon which will correspond to marker's location
|
iconAnchor: [20, 20], // point of the icon which will correspond to marker's location
|
||||||
})
|
})
|
||||||
|
|
||||||
const newMarker = L.marker(location.latlng, {icon: icon});
|
const newMarker = L.marker(location.latlng, {icon: icon});
|
||||||
newMarker.addTo(map);
|
newMarker.addTo(map);
|
||||||
|
|
||||||
|
@ -88,10 +92,10 @@ export class GeoLocationHandler extends UIElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
InnerRender(): string {
|
InnerRender(): string {
|
||||||
if(!State.state.featureSwitchGeolocation.data){
|
if (!this._featureSwitch.data) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._hasLocation.data) {
|
if (this._hasLocation.data) {
|
||||||
return Svg.crosshair_blue_img;
|
return Svg.crosshair_blue_img;
|
||||||
}
|
}
|
||||||
|
@ -102,44 +106,6 @@ export class GeoLocationHandler extends UIElement {
|
||||||
return Svg.crosshair_img;
|
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) {
|
InnerUpdate(htmlElement: HTMLElement) {
|
||||||
super.InnerUpdate(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
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
44
Logic/Actors/LayerResetter.ts
Normal file
44
Logic/Actors/LayerResetter.ts
Normal file
|
@ -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<BaseLayer>,
|
||||||
|
location: UIEventSource<Loc>,
|
||||||
|
availableLayers: UIEventSource<BaseLayer[]>,
|
||||||
|
defaultLayerId: UIEventSource<string> = undefined) {
|
||||||
|
defaultLayerId = defaultLayerId ?? new UIEventSource<string>(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);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,8 +1,9 @@
|
||||||
import * as L from "leaflet";
|
import * as L from "leaflet";
|
||||||
import {UIElement} from "../../UI/UIElement";
|
import {UIElement} from "../../UI/UIElement";
|
||||||
import State from "../../State";
|
|
||||||
import {Img} from "../../UI/Img";
|
import {Img} from "../../UI/Img";
|
||||||
import Svg from "../../Svg";
|
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.
|
* 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);
|
private _uiToShow: (() => UIElement);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
lastClickLocation: UIEventSource<{ lat: number, lon:number }>,
|
||||||
|
selectedElement: UIEventSource<string>,
|
||||||
|
filteredLayers: UIEventSource<FilteredLayer[]>,
|
||||||
|
leafletMap: UIEventSource<L.Map>,
|
||||||
|
fullscreenMessage: UIEventSource<UIElement>,
|
||||||
uiToShow: (() => UIElement)) {
|
uiToShow: (() => UIElement)) {
|
||||||
this._uiToShow = uiToShow;
|
this._uiToShow = uiToShow;
|
||||||
const self = this;
|
const self = this;
|
||||||
const map = State.state.bm.map;
|
filteredLayers.data.forEach((filteredLayer) => {
|
||||||
State.state.filteredLayers.data.forEach((filteredLayer) => {
|
|
||||||
filteredLayer.isDisplayed.addCallback(isEnabled => {
|
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
|
// 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...
|
// 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) {
|
lastClickLocation.addCallback(function (lastClick) {
|
||||||
State.state.selectedElement.setData(undefined);
|
selectedElement.setData(undefined);
|
||||||
|
|
||||||
if (self._lastMarker !== undefined) {
|
if (self._lastMarker !== undefined) {
|
||||||
map.removeLayer(self._lastMarker);
|
leafletMap.data?.removeLayer(self._lastMarker);
|
||||||
}
|
}
|
||||||
self._lastMarker = L.marker([lastClick.lat, lastClick.lon], {
|
self._lastMarker = L.marker([lastClick.lat, lastClick.lon], {
|
||||||
icon: L.icon({
|
icon: L.icon({
|
||||||
|
@ -43,18 +48,18 @@ export class StrayClickHandler {
|
||||||
});
|
});
|
||||||
const uiElement = uiToShow();
|
const uiElement = uiToShow();
|
||||||
const popup = L.popup().setContent(uiElement.Render());
|
const popup = L.popup().setContent(uiElement.Render());
|
||||||
self._lastMarker.addTo(map);
|
self._lastMarker.addTo(leafletMap.data);
|
||||||
self._lastMarker.bindPopup(popup);
|
self._lastMarker.bindPopup(popup);
|
||||||
|
|
||||||
self._lastMarker.on("click", () => {
|
self._lastMarker.on("click", () => {
|
||||||
State.state.fullScreenMessage.setData(self._uiToShow());
|
fullscreenMessage.setData(self._uiToShow());
|
||||||
uiElement.Update();
|
uiElement.Update();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
State.state.selectedElement.addCallback(() => {
|
selectedElement.addCallback(() => {
|
||||||
if (self._lastMarker !== undefined) {
|
if (self._lastMarker !== undefined) {
|
||||||
map.removeLayer(self._lastMarker);
|
leafletMap.data.removeLayer(self._lastMarker);
|
||||||
this._lastMarker = undefined;
|
this._lastMarker = undefined;
|
||||||
}
|
}
|
||||||
})
|
})
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Keeps track of a dictionary 'elementID' -> element
|
* Keeps track of a dictionary 'elementID' -> UIEventSource<tags>
|
||||||
*/
|
*/
|
||||||
import {UIEventSource} from "./UIEventSource";
|
import {UIEventSource} from "./UIEventSource";
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ export class FilteredLayer {
|
||||||
[State.state.locationControl]
|
[State.state.locationControl]
|
||||||
);
|
);
|
||||||
this.combinedIsDisplayed.addCallback(function (isDisplayed) {
|
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 (self._geolayer !== undefined && self._geolayer !== null) {
|
||||||
if (isDisplayed) {
|
if (isDisplayed) {
|
||||||
self._geolayer.addTo(map);
|
self._geolayer.addTo(map);
|
||||||
|
@ -116,7 +116,7 @@ export class FilteredLayer {
|
||||||
|
|
||||||
if (this._geolayer !== undefined && this._geolayer !== null) {
|
if (this._geolayer !== undefined && this._geolayer !== null) {
|
||||||
// Remove the old geojson layer from the map - we'll reshow all the elements later on anyway
|
// Remove the old geojson layer from the map - we'll reshow all the elements later on anyway
|
||||||
State.state.bm.map.removeLayer(this._geolayer);
|
State.state.leafletMap.data.removeLayer(this._geolayer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We fetch all the data we have to show:
|
// We fetch all the data we have to show:
|
||||||
|
@ -200,7 +200,7 @@ export class FilteredLayer {
|
||||||
|
|
||||||
const center = GeoOperations.centerpoint(feature).geometry.coordinates;
|
const center = GeoOperations.centerpoint(feature).geometry.coordinates;
|
||||||
popup.setLatLng({lat: center[1], lng: center[0]});
|
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);
|
State.state.selectedElement.setData(feature);
|
||||||
uiElement.Update();
|
uiElement.Update();
|
||||||
}
|
}
|
||||||
|
@ -209,7 +209,7 @@ export class FilteredLayer {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.combinedIsDisplayed.data) {
|
if (this.combinedIsDisplayed.data) {
|
||||||
this._geolayer.addTo(State.state.bm.map);
|
this._geolayer.addTo(State.state.leafletMap.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ export class Geocoding {
|
||||||
static Search(query: string,
|
static Search(query: string,
|
||||||
handleResult: ((places: { display_name: string, lat: number, lon: number, boundingbox: number[] }[]) => void),
|
handleResult: ((places: { display_name: string, lat: number, lon: number, boundingbox: number[] }[]) => void),
|
||||||
onFail: (() => void)) {
|
onFail: (() => void)) {
|
||||||
const b = State.state.bm.map.getBounds();
|
const b = State.state.leafletMap.data.getBounds();
|
||||||
console.log(b);
|
console.log(b);
|
||||||
$.getJSON(
|
$.getJSON(
|
||||||
Geocoding.host + "format=json&limit=1&viewbox=" +
|
Geocoding.host + "format=json&limit=1&viewbox=" +
|
||||||
|
|
|
@ -162,7 +162,7 @@ export class UpdateFromOverpass {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bounds = state.bm.map.getBounds();
|
const bounds = state.leafletMap.data.getBounds();
|
||||||
|
|
||||||
const diff = state.layoutToUse.data.widenFactor;
|
const diff = state.layoutToUse.data.widenFactor;
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ export class UpdateFromOverpass {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const b = state.bm.map.getBounds();
|
const b = state.leafletMap.data.getBounds();
|
||||||
return b.getSouth() >= bounds.south &&
|
return b.getSouth() >= bounds.south &&
|
||||||
b.getNorth() <= bounds.north &&
|
b.getNorth() <= bounds.north &&
|
||||||
b.getEast() <= bounds.east &&
|
b.getEast() <= bounds.east &&
|
||||||
|
|
71
State.ts
71
State.ts
|
@ -18,6 +18,8 @@ import {BaseLayer} from "./Models/BaseLayer";
|
||||||
import Loc from "./Models/Loc";
|
import Loc from "./Models/Loc";
|
||||||
import Constants from "./Models/Constants";
|
import Constants from "./Models/Constants";
|
||||||
import AvailableBaseLayers from "./Logic/Actors/AvailableBaseLayers";
|
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
|
* Contains the global state: a bunch of UI-event sources
|
||||||
|
@ -41,9 +43,9 @@ export default class State {
|
||||||
*/
|
*/
|
||||||
public changes: Changes;
|
public changes: Changes;
|
||||||
/**
|
/**
|
||||||
THe basemap with leaflet instance
|
The leaflet instance of the big basemap
|
||||||
*/
|
*/
|
||||||
public bm;
|
public leafletMap = new UIEventSource<L.Map>(undefined);
|
||||||
/**
|
/**
|
||||||
* Background layer id
|
* Background layer id
|
||||||
*/
|
*/
|
||||||
|
@ -91,8 +93,11 @@ export default class State {
|
||||||
* The map location: currently centered lat, lon and zoom
|
* The map location: currently centered lat, lon and zoom
|
||||||
*/
|
*/
|
||||||
public readonly locationControl = new UIEventSource<Loc>(undefined);
|
public readonly locationControl = new UIEventSource<Loc>(undefined);
|
||||||
public readonly backgroundLayer = new UIEventSource<BaseLayer>(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
|
* The location as delivered by the GPS
|
||||||
*/
|
*/
|
||||||
|
@ -114,23 +119,12 @@ export default class State {
|
||||||
const self = this;
|
const self = this;
|
||||||
this.layoutToUse.setData(layoutToUse);
|
this.layoutToUse.setData(layoutToUse);
|
||||||
|
|
||||||
function asFloat(source: UIEventSource<string>): UIEventSource<number> {
|
const zoom = State.asFloat(
|
||||||
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(
|
|
||||||
QueryParameters.GetQueryParameter("z", "" + layoutToUse.startZoom, "The initial/current zoom level")
|
QueryParameters.GetQueryParameter("z", "" + layoutToUse.startZoom, "The initial/current zoom level")
|
||||||
.syncWith(LocalStorageSource.Get("zoom")));
|
.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")));
|
.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")));
|
.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<boolean> {
|
function featSw(key: string, deflt: (layout: LayoutConfig) => boolean, documentation: string): UIEventSource<boolean> {
|
||||||
const queryParameterSource = QueryParameters.GetQueryParameter(key, undefined, documentation);
|
const queryParameterSource = QueryParameters.GetQueryParameter(key, undefined, documentation);
|
||||||
// I'm so sorry about someone trying to decipher this
|
// I'm so sorry about someone trying to decipher this
|
||||||
|
|
||||||
// It takes the current layout, extracts the default value for this query paramter. A query parameter event source is then retreived and flattened
|
// It takes the current layout, extracts the default value for this query parameter. A query parameter event source is then retrieved and flattened
|
||||||
return UIEventSource.flatten(
|
return UIEventSource.flatten(
|
||||||
self.layoutToUse.map((layout) => {
|
self.layoutToUse.map((layout) => {
|
||||||
const defaultValue = deflt(layout);
|
const defaultValue = deflt(layout);
|
||||||
|
@ -186,6 +204,9 @@ export default class State {
|
||||||
this.featureSwitchGeolocation = featSw("fs-geolocation", (layoutToUse) => layoutToUse?.enableGeolocation ?? true,
|
this.featureSwitchGeolocation = featSw("fs-geolocation", (layoutToUse) => layoutToUse?.enableGeolocation ?? true,
|
||||||
"Disables/Enables the geolocation button");
|
"Disables/Enables the geolocation button");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const testParam = QueryParameters.GetQueryParameter("test", "false",
|
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;
|
"If true, 'dryrun' mode is activated. The app will behave as normal, except that changes to OSM will be printed onto the console instead of actually uploaded to osm.org").data;
|
||||||
this.osmConnection = new OsmConnection(
|
this.osmConnection = new OsmConnection(
|
||||||
|
@ -264,4 +285,16 @@ export default class State {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static asFloat(source: UIEventSource<string>): UIEventSource<number> {
|
||||||
|
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);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,23 @@
|
||||||
import * as L from "leaflet"
|
import * as L from "leaflet"
|
||||||
import {UIEventSource} from "../UIEventSource";
|
import {UIEventSource} from "../Logic/UIEventSource";
|
||||||
import {UIElement} from "../../UI/UIElement";
|
import Loc from "../Models/Loc";
|
||||||
import {BaseLayer} from "../../Models/BaseLayer";
|
import {UIElement} from "./UIElement";
|
||||||
import AvailableBaseLayers from "../Actors/AvailableBaseLayers";
|
import {BaseLayer} from "../Models/BaseLayer";
|
||||||
import Loc from "../../Models/Loc";
|
|
||||||
|
|
||||||
export class Basemap {
|
export class Basemap {
|
||||||
|
|
||||||
|
|
||||||
// @ts-ignore
|
public readonly map: L.Map;
|
||||||
public readonly map: Map;
|
|
||||||
|
|
||||||
public readonly LastClickLocation: UIEventSource<{ lat: number, lon: number }> = new UIEventSource<{ lat: number, lon: number }>(undefined)
|
|
||||||
|
|
||||||
|
|
||||||
constructor(leafletElementId: string,
|
constructor(leafletElementId: string,
|
||||||
location: UIEventSource<Loc>,
|
location: UIEventSource<Loc>,
|
||||||
currentLayer: UIEventSource<BaseLayer>,
|
currentLayer: UIEventSource<BaseLayer>,
|
||||||
|
lastClickLocation: UIEventSource<{ lat: number, lon: number }>,
|
||||||
extraAttribution: UIElement) {
|
extraAttribution: UIElement) {
|
||||||
this.map = L.map(leafletElementId, {
|
this.map = L.map(leafletElementId, {
|
||||||
center: [location.data.lat ?? 0, location.data.lon ?? 0],
|
center: [location.data.lat ?? 0, location.data.lon ?? 0],
|
||||||
zoom: location.data.zoom ?? 2,
|
zoom: location.data.zoom ?? 2,
|
||||||
layers: [AvailableBaseLayers.osmCarto.layer],
|
layers: [currentLayer.data.layer],
|
||||||
});
|
});
|
||||||
|
|
||||||
L.control.scale(
|
L.control.scale(
|
||||||
|
@ -64,11 +60,14 @@ export class Basemap {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.map.on("click", function (e) {
|
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) {
|
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();
|
e.preventDefault();
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,10 +2,7 @@ import {InputElement} from "./InputElement";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
import * as L from "leaflet"
|
|
||||||
import * as X from "leaflet-providers"
|
|
||||||
import {Basemap} from "../../Logic/Leaflet/Basemap";
|
|
||||||
import State from "../../State";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selects a direction in degrees
|
* Selects a direction in degrees
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
import {UIElement} from "../UIElement";
|
import {UIElement} from "../UIElement";
|
||||||
import Link from "../Base/Link";
|
import Link from "../Base/Link";
|
||||||
import Svg from "../../Svg";
|
import Svg from "../../Svg";
|
||||||
import {Basemap} from "../../Logic/Leaflet/Basemap";
|
|
||||||
import Combine from "../Base/Combine";
|
import Combine from "../Base/Combine";
|
||||||
import {UIEventSource} from "../../Logic/UIEventSource";
|
import {UIEventSource} from "../../Logic/UIEventSource";
|
||||||
import {UserDetails} from "../../Logic/Osm/OsmConnection";
|
import {UserDetails} from "../../Logic/Osm/OsmConnection";
|
||||||
import Constants from "../../Models/Constants";
|
import Constants from "../../Models/Constants";
|
||||||
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
import LayoutConfig from "../../Customizations/JSON/LayoutConfig";
|
||||||
import Loc from "../../Models/Loc";
|
import Loc from "../../Models/Loc";
|
||||||
|
import * as L from "leaflet"
|
||||||
|
|
||||||
export default class Attribution extends UIElement {
|
export default class Attribution extends UIElement {
|
||||||
|
|
||||||
private readonly _location: UIEventSource<Loc>;
|
private readonly _location: UIEventSource<Loc>;
|
||||||
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
|
private readonly _layoutToUse: UIEventSource<LayoutConfig>;
|
||||||
private readonly _userDetails: UIEventSource<UserDetails>;
|
private readonly _userDetails: UIEventSource<UserDetails>;
|
||||||
private readonly _basemap: Basemap;
|
private readonly _leafletMap: UIEventSource<L.Map>;
|
||||||
|
|
||||||
constructor(location: UIEventSource<Loc>,
|
constructor(location: UIEventSource<Loc>,
|
||||||
userDetails: UIEventSource<UserDetails>,
|
userDetails: UIEventSource<UserDetails>,
|
||||||
layoutToUse: UIEventSource<LayoutConfig>,
|
layoutToUse: UIEventSource<LayoutConfig>,
|
||||||
basemap: Basemap) {
|
leafletMap: UIEventSource<L.Map>) {
|
||||||
super(location);
|
super(location);
|
||||||
this._layoutToUse = layoutToUse;
|
this._layoutToUse = layoutToUse;
|
||||||
this.ListenTo(layoutToUse);
|
this.ListenTo(layoutToUse);
|
||||||
this._userDetails = userDetails;
|
this._userDetails = userDetails;
|
||||||
this._basemap = basemap;
|
this._leafletMap = leafletMap;
|
||||||
this.ListenTo(userDetails);
|
this.ListenTo(userDetails);
|
||||||
this._location = location;
|
this._location = location;
|
||||||
this.SetClass("map-attribution");
|
this.SetClass("map-attribution");
|
||||||
|
@ -47,9 +47,9 @@ export default class Attribution extends UIElement {
|
||||||
}
|
}
|
||||||
let editWithJosm: (UIElement | string) = ""
|
let editWithJosm: (UIElement | string) = ""
|
||||||
if (location !== undefined &&
|
if (location !== undefined &&
|
||||||
this._basemap !== undefined &&
|
this._leafletMap.data !== undefined &&
|
||||||
userDetails.csCount >= Constants.userJourney.tagsVisibleAndWikiLinked) {
|
userDetails.csCount >= Constants.userJourney.tagsVisibleAndWikiLinked) {
|
||||||
const bounds = this._basemap.map.getBounds();
|
const bounds = this._leafletMap.data.getBounds();
|
||||||
const top = bounds.getNorth();
|
const top = bounds.getNorth();
|
||||||
const bottom = bounds.getSouth();
|
const bottom = bounds.getSouth();
|
||||||
const right = bounds.getEast();
|
const right = bounds.getEast();
|
||||||
|
|
|
@ -61,7 +61,7 @@ export class SearchAndGo extends UIElement {
|
||||||
[bb[0], bb[2]],
|
[bb[0], bb[2]],
|
||||||
[bb[1], bb[3]]
|
[bb[1], bb[3]]
|
||||||
]
|
]
|
||||||
State.state.bm.map.fitBounds(bounds);
|
State.state.leafletMap.data.fitBounds(bounds);
|
||||||
self._placeholder.setData(Translations.t.general.search.search);
|
self._placeholder.setData(Translations.t.general.search.search);
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
|
|
|
@ -115,7 +115,7 @@ export class SimpleAddUI extends UIElement {
|
||||||
private CreatePoint(tags: Tag[], layerToAddTo: FilteredLayer) {
|
private CreatePoint(tags: Tag[], layerToAddTo: FilteredLayer) {
|
||||||
return () => {
|
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);
|
let feature = State.state.changes.createElement(tags, loc.lat, loc.lon);
|
||||||
State.state.selectedElement.setData(feature);
|
State.state.selectedElement.setData(feature);
|
||||||
layerToAddTo.AddNewElement(feature);
|
layerToAddTo.AddNewElement(feature);
|
||||||
|
|
|
@ -59,7 +59,7 @@ export class UserBadge extends UIElement {
|
||||||
if (home === undefined) {
|
if (home === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
State.state.bm.map.setView([home.lat, home.lon], 16);
|
State.state.leafletMap.data.setView([home.lat, home.lon], 16);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
16
index.ts
16
index.ts
|
@ -6,6 +6,7 @@ import {UIEventSource} from "./Logic/UIEventSource";
|
||||||
import * as $ from "jquery";
|
import * as $ from "jquery";
|
||||||
import LayoutConfig from "./Customizations/JSON/LayoutConfig";
|
import LayoutConfig from "./Customizations/JSON/LayoutConfig";
|
||||||
import {Utils} from "./Utils";
|
import {Utils} from "./Utils";
|
||||||
|
import {Overpass} from "./Logic/Osm/Overpass";
|
||||||
|
|
||||||
let defaultLayout = "bookcases"
|
let defaultLayout = "bookcases"
|
||||||
// --------------------- Special actions based on the parameters -----------------
|
// --------------------- Special actions based on the parameters -----------------
|
||||||
|
@ -17,19 +18,9 @@ if (location.href.startsWith("http://buurtnatuur.be")) {
|
||||||
|
|
||||||
|
|
||||||
if (location.href.indexOf("buurtnatuur.be") >= 0) {
|
if (location.href.indexOf("buurtnatuur.be") >= 0) {
|
||||||
// Reload the https version. This is important for the 'locate me' button
|
|
||||||
defaultLayout = "buurtnatuur"
|
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");
|
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 !== ""){
|
if(customCssQP.data !== undefined && customCssQP.data !== ""){
|
||||||
Utils.LoadCustomCss(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")
|
testing.setData(testing.data ?? "true")
|
||||||
// If you have a testfile somewhere, enable this to spoof overpass
|
// 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
|
// 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 {
|
} else {
|
||||||
testing = QueryParameters.GetQueryParameter("test", "false");
|
testing = QueryParameters.GetQueryParameter("test", "false");
|
||||||
}
|
}
|
||||||
|
@ -78,7 +69,7 @@ if (layoutFromBase64.startsWith("wiki:")) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: url,
|
url: url,
|
||||||
success: function (data) {
|
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 = "<div class=\"mw-parser-output\">";
|
const startTrigger = "<div class=\"mw-parser-output\">";
|
||||||
const start = data.indexOf(startTrigger);
|
const start = data.indexOf(startTrigger);
|
||||||
data = data.substr(start,
|
data = data.substr(start,
|
||||||
|
@ -113,4 +104,3 @@ if (layoutFromBase64.startsWith("wiki:")) {
|
||||||
window.addEventListener('contextmenu', function (e) { // Not compatible with IE < 9
|
window.addEventListener('contextmenu', function (e) { // Not compatible with IE < 9
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}, false);
|
}, false);
|
||||||
// console.log(QueryParameters.GenerateQueryParameterDocs())
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue