mapcomplete/Logic/Web/ThemeViewStateHashActor.ts

180 lines
5.8 KiB
TypeScript

import ThemeViewState from "../../Models/ThemeViewState";
import Hash from "./Hash";
export default class ThemeViewStateHashActor {
private readonly _state: ThemeViewState;
/**
* Converts the hash to the appropriate themeview state and, vice versa, sets the hash.
*
* As the navigator-back-button changes the hash first, this class thus also handles the 'back'-button events.
*
* Note that there is no "real" way to intercept the back button, we can only detect the removal of the hash.
* As such, we use a change in the hash to close the appropriate windows
*
* @param state
*/
constructor(state: ThemeViewState) {
this._state = state;
// First of all, try to recover the selected element
if (Hash.hash.data) {
const hash = Hash.hash.data
this.loadStateFromHash(hash)
Hash.hash.setData(hash) // reapply the previous hash
state.indexedFeatures.featuresById.addCallbackAndRunD(_ => {
let unregister = this.loadSelectedElementFromHash(hash);
// once that we have found a matching element, we can be sure the indexedFeaturesource was popuplated and that the job is done
return unregister
})
}
// Register a hash change listener to correctly handle the back button
Hash.hash.addCallback(hash => {
if (!!hash) {
// There is still a hash
// We _only_ have to (at most) close the overlays in this case
const parts = hash.split(";")
if(parts.indexOf("background") < 0){
state.guistate.backgroundLayerSelectionIsOpened.setData(false)
}
this.loadSelectedElementFromHash(hash)
} else {
this.back()
}
})
// At last, register callbacks on the state to update the hash when they change.
// Note: these should use 'addCallback', not 'addCallbackAndRun'
state.selectedElement.addCallback(_ => this.setHash())
state.guistate.allToggles.forEach(({toggle, submenu}) => {
submenu?.addCallback(_ => this.setHash())
toggle.addCallback(_ => this.setHash());
})
// When all is done, set the hash. This must happen last to give the code above correct info
this.setHash()
}
/**
* Selects the appropriate element
* Returns true if this method can be unregistered for the first run
* @param hash
* @private
*/
private loadSelectedElementFromHash(hash: string): boolean {
const state = this._state
const selectedElement = state.selectedElement
// state.indexedFeatures.featuresById.stabilized(250)
hash = hash.split(";")[0] // The 'selectedElement' is always the _first_ item in the hash (if any)
// Set the hash based on the selected element...
// ... search and select an element based on the hash
if (selectedElement.data?.properties?.id === hash) {
// We already have the correct hash
return true
}
const found = state.indexedFeatures.featuresById.data?.get(hash)
if (!found) {
return false
}
if (found.properties.id === "last_click") {
return true
}
const layer = this._state.layout.getMatchingLayer(found.properties)
console.log(
"Setting selected element based on hash",
hash,
"; found",
found,
"got matching layer",
layer.id,
""
)
selectedElement.setData(found)
state.selectedLayer.setData(layer)
return true
}
private loadStateFromHash(hash: string) {
const state = this._state
const parts = hash.split(";")
outer: for (const {toggle, name, showOverOthers, submenu} of state.guistate.allToggles) {
for (const part of parts) {
if (part === name) {
toggle.setData(true)
continue outer
}
if (part.indexOf(":") < 0) {
continue
}
const [main, submenuValue] = part.split(":")
if (part !== main) {
continue
}
toggle.setData(true)
submenu?.setData(submenuValue)
continue outer
}
// If we arrive here, the loop above has not found any match
toggle.setData(false)
}
}
private setHash() {
const s = this._state
let h = ""
for (const {toggle, showOverOthers, name, submenu} of s.guistate.allToggles) {
if (showOverOthers || !toggle.data) {
continue;
}
h = name
if (submenu?.data) {
h += ":" + submenu.data
}
}
if (s.selectedElement.data !== undefined) {
h = s.selectedElement.data.properties.id
}
for (const {toggle, showOverOthers, name, submenu} of s.guistate.allToggles) {
if (!showOverOthers || !toggle.data) {
continue;
}
if (h) {
h += ";" + name
} else {
h = name
}
if (submenu?.data) {
h += ":" + submenu.data
}
}
Hash.hash.setData(h)
}
private back() {
console.log("Got a back event")
const state = this._state
// history.pushState(null, null, window.location.pathname);
if (state.selectedElement.data) {
state.selectedElement.setData(undefined)
return
}
if (state.guistate.closeAll()) {
return
}
}
}