Further refactoring fullscreenelement: removal of hash handling from showDataLayer

This commit is contained in:
pietervdvn 2021-01-27 01:14:16 +01:00
parent e2e48344d6
commit 593ac5381a
7 changed files with 125 additions and 114 deletions

View file

@ -33,6 +33,8 @@ import LayerConfig from "./Customizations/JSON/LayerConfig";
import ShowDataLayer from "./UI/ShowDataLayer"; import ShowDataLayer from "./UI/ShowDataLayer";
import Hash from "./Logic/Web/Hash"; import Hash from "./Logic/Web/Hash";
import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline"; import FeaturePipeline from "./Logic/FeatureSource/FeaturePipeline";
import HashHandler from "./Logic/Actors/SelectedFeatureHandler";
import SelectedFeatureHandler from "./Logic/Actors/SelectedFeatureHandler";
export class InitUiElements { export class InitUiElements {
@ -231,7 +233,7 @@ export class InitUiElements {
checkbox.isEnabled.setData(false); checkbox.isEnabled.setData(false);
}) })
State.state.selectedElement.addCallback(selected => { State.state.selectedElement.addCallbackAndRun(selected => {
if (selected !== undefined) { if (selected !== undefined) {
checkbox.isEnabled.setData(false); checkbox.isEnabled.setData(false);
} }
@ -258,6 +260,11 @@ export class InitUiElements {
checkbox.isEnabled.setData(false); checkbox.isEnabled.setData(false);
}); });
State.state.selectedElement.addCallbackAndRun(feature => {
if(feature !== undefined){
checkbox.isEnabled.setData(false);
}
})
}); });
} }
@ -341,6 +348,8 @@ export class InitUiElements {
new ShowDataLayer(source.features, State.state.leafletMap, new ShowDataLayer(source.features, State.state.leafletMap,
State.state.layoutToUse.data); State.state.layoutToUse.data);
new SelectedFeatureHandler(Hash.hash, State.state.selectedElement, source);
} }

View file

@ -1,19 +0,0 @@
import {UIEventSource} from "../UIEventSource";
import {UIElement} from "../../UI/UIElement";
export default class HistoryHandling {
constructor(hash: UIEventSource<string>, fullscreenMessage: UIEventSource<{ content: UIElement, hashText: string }>) {
hash.addCallback(h => {
if (h === undefined || h === "") {
fullscreenMessage.setData(undefined);
}
})
fullscreenMessage.addCallback(fs => {
hash.setData(fs?.hashText);
})
}
}

View file

@ -0,0 +1,51 @@
import {UIEventSource} from "../UIEventSource";
import {UIElement} from "../../UI/UIElement";
import FeatureSource from "../FeatureSource/FeatureSource";
/**
* Makes sure the hash shows the selected element and vice-versa
*/
export default class SelectedFeatureHandler {
private readonly _featureSource: FeatureSource;
private readonly _hash: UIEventSource<string>;
private readonly _selectedFeature: UIEventSource<any>;
constructor(hash: UIEventSource<string>,
selectedFeature: UIEventSource<any>,
featureSource: FeatureSource) {
this._hash = hash;
this._selectedFeature = selectedFeature;
this._featureSource = featureSource;
const self = this;
hash.addCallback(h => {
if (h === undefined || h === "") {
selectedFeature.setData(undefined);
}else{
self.selectFeature();
}
})
featureSource.features.addCallback(_ => self.selectFeature());
selectedFeature.addCallback(feature => {
hash.setData(feature?.properties?.id ?? "");
})
this.selectFeature();
}
private selectFeature(){
const features = this._featureSource?.features?.data;
if(features === undefined){
return;
}
for (const feature of features) {
const id = feature.feature?.properties?.id;
if(id === this._hash.data){
this._selectedFeature.setData(feature.feature);
}
}
}
}

129
State.ts
View file

@ -30,6 +30,8 @@ export default class State {
public static runningFromConsole: boolean = false; public static runningFromConsole: boolean = false;
public readonly layoutToUse = new UIEventSource<LayoutConfig>(undefined); public readonly layoutToUse = new UIEventSource<LayoutConfig>(undefined);
/** /**
@ -74,11 +76,11 @@ export default class State {
public readonly centerMessage = new UIEventSource<string>(""); public readonly centerMessage = new UIEventSource<string>("");
/** /**
The latest element that was selected - used to generate the right UI at the right place The latest element that was selected
*/ */
public readonly selectedElement = new UIEventSource<any>(undefined) public readonly selectedElement = new UIEventSource<any>(undefined)
publ
public readonly featureSwitchUserbadge: UIEventSource<boolean>; public readonly featureSwitchUserbadge: UIEventSource<boolean>;
public readonly featureSwitchSearch: UIEventSource<boolean>; public readonly featureSwitchSearch: UIEventSource<boolean>;
public readonly featureSwitchLayers: UIEventSource<boolean>; public readonly featureSwitchLayers: UIEventSource<boolean>;
@ -88,6 +90,7 @@ export default class State {
public readonly featureSwitchMoreQuests: UIEventSource<boolean>; public readonly featureSwitchMoreQuests: UIEventSource<boolean>;
public readonly featureSwitchShareScreen: UIEventSource<boolean>; public readonly featureSwitchShareScreen: UIEventSource<boolean>;
public readonly featureSwitchGeolocation: UIEventSource<boolean>; public readonly featureSwitchGeolocation: UIEventSource<boolean>;
public readonly featureSwitchIsTesting: UIEventSource<boolean>;
/** /**
@ -119,36 +122,39 @@ export default class State {
constructor(layoutToUse: LayoutConfig) { constructor(layoutToUse: LayoutConfig) {
const self = this; const self = this;
this.layoutToUse.setData(layoutToUse); this.layoutToUse.setData(layoutToUse);
const zoom = State.asFloat( // -- Location control initialization
QueryParameters.GetQueryParameter("z", "" +(layoutToUse?.startZoom ?? 1), "The initial/current zoom level") { const zoom = State.asFloat(
.syncWith(LocalStorageSource.Get("zoom"))); QueryParameters.GetQueryParameter("z", "" + (layoutToUse?.startZoom ?? 1), "The initial/current zoom level")
const lat = State.asFloat(QueryParameters.GetQueryParameter("lat", "" + (layoutToUse?.startLat ?? 0), "The initial/current latitude") .syncWith(LocalStorageSource.Get("zoom")));
.syncWith(LocalStorageSource.Get("lat"))); const lat = State.asFloat(QueryParameters.GetQueryParameter("lat", "" + (layoutToUse?.startLat ?? 0), "The initial/current latitude")
const lon = State.asFloat(QueryParameters.GetQueryParameter("lon", "" + (layoutToUse?.startLon ?? 0), "The initial/current longitude of the app") .syncWith(LocalStorageSource.Get("lat")));
.syncWith(LocalStorageSource.Get("lon"))); const lon = State.asFloat(QueryParameters.GetQueryParameter("lon", "" + (layoutToUse?.startLon ?? 0), "The initial/current longitude of the app")
.syncWith(LocalStorageSource.Get("lon")));
this.locationControl = new UIEventSource<Loc>({ this.locationControl = new UIEventSource<Loc>({
zoom: Utils.asFloat(zoom.data), zoom: Utils.asFloat(zoom.data),
lat: Utils.asFloat(lat.data), lat: Utils.asFloat(lat.data),
lon: Utils.asFloat(lon.data), lon: Utils.asFloat(lon.data),
}).addCallback((latlonz) => { }).addCallback((latlonz) => {
zoom.setData(latlonz.zoom); zoom.setData(latlonz.zoom);
lat.setData(latlonz.lat); lat.setData(latlonz.lat);
lon.setData(latlonz.lon); lon.setData(latlonz.lon);
}); });
this.layoutToUse.addCallback(layoutToUse => {
const lcd = self.locationControl.data;
lcd.zoom = lcd.zoom ?? layoutToUse?.startZoom;
lcd.lat = lcd.lat ?? layoutToUse?.startLat;
lcd.lon = lcd.lon ?? layoutToUse?.startLon;
self.locationControl.ping();
});
this.layoutToUse.addCallback(layoutToUse => {
const lcd = self.locationControl.data;
lcd.zoom = lcd.zoom ?? layoutToUse?.startZoom;
lcd.lat = lcd.lat ?? layoutToUse?.startLat;
lcd.lon = lcd.lon ?? layoutToUse?.startLon;
self.locationControl.ping();
});
}
// Helper function to initialize feature switches
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
@ -162,60 +168,52 @@ export default class State {
}), [queryParameterSource]); }), [queryParameterSource]);
} }
// Feature switch initialization - not as a function as the UIEventSources are readonly
this.featureSwitchUserbadge = featSw("fs-userbadge", (layoutToUse) => layoutToUse?.enableUserBadge ?? true, {
"Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode.");
this.featureSwitchSearch = featSw("fs-search", (layoutToUse) => layoutToUse?.enableSearch ?? true, this.featureSwitchUserbadge = featSw("fs-userbadge", (layoutToUse) => layoutToUse?.enableUserBadge ?? true,
"Disables/Enables the search bar"); "Disables/Enables the user information pill (userbadge) at the top left. Disabling this disables logging in and thus disables editing all together, effectively putting MapComplete into read-only mode.");
this.featureSwitchLayers = featSw("fs-layers", (layoutToUse) => layoutToUse?.enableLayers ?? true, this.featureSwitchSearch = featSw("fs-search", (layoutToUse) => layoutToUse?.enableSearch ?? true,
"Disables/Enables the layer control"); "Disables/Enables the search bar");
this.featureSwitchAddNew = featSw("fs-add-new", (layoutToUse) => layoutToUse?.enableAddNewPoints ?? true, this.featureSwitchLayers = featSw("fs-layers", (layoutToUse) => layoutToUse?.enableLayers ?? true,
"Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)"); "Disables/Enables the layer control");
this.featureSwitchWelcomeMessage = featSw("fs-welcome-message", () => true, this.featureSwitchAddNew = featSw("fs-add-new", (layoutToUse) => layoutToUse?.enableAddNewPoints ?? true,
"Disables/enables the help menu or welcome message"); "Disables/Enables the 'add new feature'-popup. (A theme without presets might not have it in the first place)");
this.featureSwitchIframe = featSw("fs-iframe", () => false, this.featureSwitchWelcomeMessage = featSw("fs-welcome-message", () => true,
"Disables/Enables the iframe-popup"); "Disables/enables the help menu or welcome message");
this.featureSwitchMoreQuests = featSw("fs-more-quests", (layoutToUse) => layoutToUse?.enableMoreQuests ?? true, this.featureSwitchIframe = featSw("fs-iframe", () => false,
"Disables/Enables the 'More Quests'-tab in the welcome message"); "Disables/Enables the iframe-popup");
this.featureSwitchShareScreen = featSw("fs-share-screen", (layoutToUse) => layoutToUse?.enableShareScreen ?? true, this.featureSwitchMoreQuests = featSw("fs-more-quests", (layoutToUse) => layoutToUse?.enableMoreQuests ?? true,
"Disables/Enables the 'Share-screen'-tab in the welcome message"); "Disables/Enables the 'More Quests'-tab in the welcome message");
this.featureSwitchGeolocation = featSw("fs-geolocation", (layoutToUse) => layoutToUse?.enableGeolocation ?? true, this.featureSwitchShareScreen = featSw("fs-share-screen", (layoutToUse) => layoutToUse?.enableShareScreen ?? true,
"Disables/Enables the geolocation button"); "Disables/Enables the 'Share-screen'-tab in the welcome message");
this.featureSwitchGeolocation = featSw("fs-geolocation", (layoutToUse) => layoutToUse?.enableGeolocation ?? true,
"Disables/Enables the geolocation button");
const testParam = QueryParameters.GetQueryParameter("test", "false", this.featureSwitchIsTesting = 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")
.map(str => str === "true",[], b => ""+b);
}
this.osmConnection = new OsmConnection( this.osmConnection = new OsmConnection(
testParam === "true", this.featureSwitchIsTesting.data,
QueryParameters.GetQueryParameter("oauth_token", undefined, QueryParameters.GetQueryParameter("oauth_token", undefined,
"Used to complete the login"), "Used to complete the login"),
layoutToUse?.id, layoutToUse?.id,
true true
); );
this.allElements = new ElementStorage();
this.changes = new Changes();
this.mangroveIdentity = new MangroveIdentity( this.mangroveIdentity = new MangroveIdentity(
this.osmConnection.GetLongPreference("identity", "mangrove") this.osmConnection.GetLongPreference("identity", "mangrove")
); );
const h = Hash.hash;
this.selectedElement.addCallback(selected => {
if (selected === undefined) {
h.setData("");
} else {
h.setData(selected.id.replace("/","_"))
}
}
)
h.addCallbackAndRun(hash => {
if (hash === undefined || hash === "") {
self.selectedElement.setData(undefined);
}
})
this.installedThemes = new InstalledThemes(this.osmConnection).installedThemes; this.installedThemes = new InstalledThemes(this.osmConnection).installedThemes;
@ -243,8 +241,6 @@ export default class State {
new TitleHandler(this.layoutToUse, this.selectedElement, this.allElements); new TitleHandler(this.layoutToUse, this.selectedElement, this.allElements);
this.allElements = new ElementStorage();
this.changes = new Changes();
} }
private static asFloat(source: UIEventSource<string>): UIEventSource<number> { private static asFloat(source: UIEventSource<string>): UIEventSource<number> {
@ -258,5 +254,6 @@ export default class State {
return ("" + fl).substr(0, 8); return ("" + fl).substr(0, 8);
}) })
} }
} }

View file

@ -13,7 +13,6 @@ export default class LazyElement<T extends UIElement> extends UIElement {
this.dumbMode = false; this.dumbMode = false;
const self = this; const self = this;
this.Activate = (onElement?: (element: T) => void) => { this.Activate = (onElement?: (element: T) => void) => {
console.log("ACTIVATED")
if (this._content === undefined) { if (this._content === undefined) {
self._content = content(); self._content = content();
} }

View file

@ -21,7 +21,6 @@ export default class ScrollableFullScreen extends UIElement {
Svg.close_svg().SetClass("hidden sm:block") Svg.close_svg().SetClass("hidden sm:block")
]) ])
.onClick(() => { .onClick(() => {
console.log("Clicked back!");
ScrollableFullScreen.RestoreLeaflet(); ScrollableFullScreen.RestoreLeaflet();
if (onClose !== undefined) { if (onClose !== undefined) {
onClose(); onClose();
@ -107,7 +106,6 @@ export default class ScrollableFullScreen extends UIElement {
} }
public static RestoreLeaflet() { public static RestoreLeaflet() {
console.log("Restoring")
const noTransf = document.getElementsByClassName("scrollable-fullscreen-no-transform"); const noTransf = document.getElementsByClassName("scrollable-fullscreen-no-transform");
for (let i = 0; i < noTransf.length; ++i) { for (let i = 0; i < noTransf.length; ++i) {
noTransf[i].classList.remove("no-transform"); noTransf[i].classList.remove("no-transform");
@ -136,13 +134,11 @@ export default class ScrollableFullScreen extends UIElement {
} }
protected InnerUpdate(htmlElement: HTMLElement) { protected InnerUpdate(htmlElement: HTMLElement) {
console.log("Inner updating scrollale", this.id)
this.PrepFullscreen(htmlElement) this.PrepFullscreen(htmlElement)
super.InnerUpdate(htmlElement); super.InnerUpdate(htmlElement);
} }
Update() { Update() {
console.log("Updating scrollable", this.id)
super.Update(); super.Update();
} }

View file

@ -72,18 +72,9 @@ export default class ShowDataLayer {
action(); action();
} }
}); });
Hash.hash.addCallbackAndRun(id => {
// This is a bit of an edge case: if the hash becomes an id to search, we have to show the corresponding popup
if (State.state.selectedElement !== undefined) {
return; // Something is already selected, we don't have to apply this fix
}
const action = self._onSelectedTrigger[id];
if (action) {
action();
}
})
update(); update();
} }
@ -167,19 +158,6 @@ export default class ShowDataLayer {
State.state.selectedElement.setData(feature); State.state.selectedElement.setData(feature);
} }
this._onSelectedTrigger[feature.properties.id.replace("/", "_")] = this._onSelectedTrigger[id]; this._onSelectedTrigger[feature.properties.id.replace("/", "_")] = this._onSelectedTrigger[id];
if (feature.properties.id.replace(/\//g, "_") === Hash.hash.data && State.state.selectedElement.data === undefined) {
// This element is in the URL, so this is a share link
// We open the relevant popup straight away
console.log("Opening the popup due to sharelink")
uiElement.Activate( );
popup.setContent(uiElement.Render());
const center = GeoOperations.centerpoint(feature).geometry.coordinates;
popup.setLatLng({lat: center[1], lng: center[0]});
popup.openOn(State.state.leafletMap.data);
State.state.selectedElement.setData(feature);
uiElement.Update();
}
} }
private CreateGeojsonLayer(features: any[]): L.Layer { private CreateGeojsonLayer(features: any[]): L.Layer {