MapComplete/src/Logic/Web/ThemeViewStateHashActor.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

171 lines
5.8 KiB
TypeScript
Raw Normal View History

import Hash from "./Hash"
import { MenuState } from "../../Models/MenuState"
2025-01-12 01:53:58 +01:00
import { AndroidPolyfill } from "./AndroidPolyfill"
import { IndexedFeatureSource } from "../FeatureSource/FeatureSource"
import { Feature } from "geojson"
2025-01-23 15:13:00 +01:00
import { Store, UIEventSource } from "../UIEventSource"
2023-06-07 02:42:49 +02:00
2023-06-07 18:04:25 +02:00
export default class ThemeViewStateHashActor {
private readonly _state: {
2025-01-28 15:42:34 +01:00
indexedFeatures: IndexedFeatureSource
selectedElement: UIEventSource<Feature>
guistate: MenuState
}
2024-08-29 03:53:54 +02:00
private isUpdatingHash = false
2023-06-07 02:42:49 +02:00
public static readonly documentation = [
"The URL-hash can contain multiple values:",
"",
"- The id of the currently selected object, e.g. `node/1234`",
"- The currently opened menu view",
"",
"### Possible hashes to open a menu",
"",
"The possible hashes are:",
"",
2025-01-28 15:42:34 +01:00
MenuState.pageNames.map((tab) => "`" + tab + "`").join(","),
]
2023-06-07 02:42:49 +02:00
/**
* Converts the hash to the appropriate theme-view state and, vice versa, sets the hash.
2023-06-07 18:04:25 +02:00
*
2025-01-12 01:53:58 +01:00
* As the navigator-back-button changes the hash first, this class thus also handles the (browser) 'back'-button events.
2023-06-07 02:42:49 +02:00
*
* 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
*
*/
constructor(state: {
2025-02-10 02:04:58 +01:00
featureSwitches: { featureSwitchBackToThemeOverview: Store<boolean> }
indexedFeatures: IndexedFeatureSource
selectedElement: UIEventSource<Feature>
guistate: MenuState
}) {
this._state = state
2025-01-12 01:53:58 +01:00
AndroidPolyfill.onBackButton(() => this.back(), {
2025-02-10 02:04:58 +01:00
returnToIndex: state.featureSwitches.featureSwitchBackToThemeOverview,
2025-01-12 01:53:58 +01:00
})
2023-06-07 02:42:49 +02:00
const hashOnLoad = Hash.hash.data
const containsMenu = this.loadStateFromHash(hashOnLoad)
2023-06-07 18:04:25 +02:00
// First of all, try to recover the selected element
2024-08-29 12:28:59 +02:00
if (!containsMenu && hashOnLoad?.length > 0) {
state.indexedFeatures.featuresById.addCallbackAndRunD(() => {
2023-06-07 18:04:25 +02:00
// once that we have found a matching element, we can be sure the indexedFeaturesource was popuplated and that the job is done
return this.loadSelectedElementFromHash(hashOnLoad)
2023-06-07 18:04:25 +02:00
})
}
2023-06-07 02:42:49 +02:00
// 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())
2023-06-07 18:04:25 +02:00
// Register a hash change listener to correctly handle the back button
Hash.hash.addCallback((hash) => {
2024-09-02 12:48:15 +02:00
if (this.isUpdatingHash) {
2024-08-29 03:53:54 +02:00
return
}
if (!hash) {
2023-06-07 02:42:49 +02:00
this.back()
2024-08-29 03:53:54 +02:00
} else {
if (!this.loadStateFromHash(hash)) {
this.loadSelectedElementFromHash(hash)
}
2023-06-07 02:42:49 +02:00
}
})
for (const key in state.guistate.pageStates) {
const toggle = state.guistate.pageStates[key]
toggle.addCallback(() => this.setHash())
}
2023-06-07 02:42:49 +02:00
2023-06-07 18:04:25 +02:00
// When all is done, set the hash. This must happen last to give the code above correct info
this.setHash()
2023-06-07 02:42:49 +02:00
}
/**
* Selects the appropriate element
* Returns true if this method can be unregistered for the first run
*/
private loadSelectedElementFromHash(hash: string): boolean {
const state = this._state
const selectedElement = state.selectedElement
// 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.startsWith("last_click")) {
2023-06-07 02:42:49 +02:00
return true
}
2025-05-08 11:44:03 +02:00
console.log(
"Setting selected element based on hash",
hash,
"; found",
found,
"current:",
selectedElement.data?.properties?.id
)
2023-06-07 02:42:49 +02:00
selectedElement.setData(found)
return true
}
private loadStateFromHash(hash: string): boolean {
for (const page in this._state.guistate.pageStates) {
if (page === hash) {
const toggle = this._state.guistate.pageStates[page]
toggle.set(true)
console.log("Loading menu view from hash:", page)
return true
}
2023-06-07 02:42:49 +02:00
}
return false
2023-06-07 02:42:49 +02:00
}
/**
* Sets the hash based on:
*
* 1. Selected element ID
* 2. A selected 'page' from the menu
*
*/
private setHash(): void {
// Important ! This function is called by 'addCallback' and might thus never return 'true' or it will be unregistered
2024-08-29 03:53:54 +02:00
this.isUpdatingHash = true
try {
const selectedElement = this._state.selectedElement.data
if (selectedElement) {
Hash.hash.set(selectedElement.properties.id)
return
2023-06-07 02:42:49 +02:00
}
2024-08-29 03:53:54 +02:00
for (const page in this._state.guistate.pageStates) {
const toggle = this._state.guistate.pageStates[page]
if (toggle.data) {
Hash.hash.set(page)
return
2024-08-29 03:53:54 +02:00
}
}
Hash.hash.set(undefined)
return
2024-08-29 03:53:54 +02:00
} finally {
this.isUpdatingHash = false
2023-06-07 02:42:49 +02:00
}
}
2025-01-12 01:53:58 +01:00
/**
* Returns 'true' if an action was taken
* @private
*/
2025-01-23 13:41:06 +01:00
private back(): boolean {
2023-06-07 02:42:49 +02:00
const state = this._state
2025-01-23 13:41:06 +01:00
return state.guistate.closeAll()
2023-06-07 02:42:49 +02:00
}
}