| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  | import ThemeViewState from "../../Models/ThemeViewState" | 
					
						
							|  |  |  | import Hash from "./Hash" | 
					
						
							| 
									
										
										
										
											2023-08-10 15:37:44 +02:00
										 |  |  | import { MenuState } from "../../Models/MenuState" | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 18:04:25 +02:00
										 |  |  | export default class ThemeViewStateHashActor { | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |     private readonly _state: ThemeViewState | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-10 15:37:44 +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", | 
					
						
							|  |  |  |         "- The base64-encoded JSON-file specifying a custom theme (only when loading)", | 
					
						
							|  |  |  |         "", | 
					
						
							|  |  |  |         "### Possible hashes to open a menu", | 
					
						
							|  |  |  |         "", | 
					
						
							|  |  |  |         "The possible hashes are:", | 
					
						
							|  |  |  |         "", | 
					
						
							|  |  |  |         MenuState._menuviewTabs.map((tab) => "`menu:" + tab + "`").join(","), | 
					
						
							| 
									
										
										
										
											2024-08-14 13:53:56 +02:00
										 |  |  |         MenuState._themeviewTabs.map((tab) => "`theme-menu:" + tab + "`").join(","), | 
					
						
							| 
									
										
										
										
											2023-08-10 15:37:44 +02:00
										 |  |  |     ] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  |     /** | 
					
						
							| 
									
										
										
										
											2023-06-07 18:04:25 +02:00
										 |  |  |      * 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. | 
					
						
							| 
									
										
										
										
											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 | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * @param state | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     constructor(state: ThemeViewState) { | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |         this._state = state | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 18:04:25 +02:00
										 |  |  |         // 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
 | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |             state.indexedFeatures.featuresById.addCallbackAndRunD((_) => { | 
					
						
							|  |  |  |                 let unregister = this.loadSelectedElementFromHash(hash) | 
					
						
							| 
									
										
										
										
											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 unregister | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 18:04:25 +02:00
										 |  |  |         // Register a hash change listener to correctly handle the back button
 | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |         Hash.hash.addCallback((hash) => { | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  |             if (!!hash) { | 
					
						
							|  |  |  |                 // There is still a hash
 | 
					
						
							|  |  |  |                 // We _only_ have to (at most) close the overlays in this case
 | 
					
						
							| 
									
										
										
										
											2024-01-11 04:00:56 +01:00
										 |  |  |                 if (state.previewedImage.data) { | 
					
						
							|  |  |  |                     state.previewedImage.setData(undefined) | 
					
						
							|  |  |  |                     return | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  |                 const parts = hash.split(";") | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                 if (parts.indexOf("background") < 0) { | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  |                     state.guistate.backgroundLayerSelectionIsOpened.setData(false) | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 this.loadSelectedElementFromHash(hash) | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 this.back() | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-07 18:04:25 +02:00
										 |  |  |         // At last, register callbacks on the state to update the hash when they change.
 | 
					
						
							|  |  |  |         // Note: these should use 'addCallback', not 'addCallbackAndRun'
 | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |         state.selectedElement.addCallback((_) => this.setHash()) | 
					
						
							|  |  |  |         state.guistate.allToggles.forEach(({ toggle, submenu }) => { | 
					
						
							|  |  |  |             submenu?.addCallback((_) => this.setHash()) | 
					
						
							|  |  |  |             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 | 
					
						
							|  |  |  |      * @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 | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         console.log( | 
					
						
							|  |  |  |             "Setting selected element based on hash", | 
					
						
							|  |  |  |             hash, | 
					
						
							|  |  |  |             "; found", | 
					
						
							| 
									
										
										
										
											2024-08-24 12:15:20 +02:00
										 |  |  |             found | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  |         ) | 
					
						
							|  |  |  |         selectedElement.setData(found) | 
					
						
							|  |  |  |         return true | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private loadStateFromHash(hash: string) { | 
					
						
							|  |  |  |         const state = this._state | 
					
						
							| 
									
										
										
										
											2024-08-02 13:33:29 +02:00
										 |  |  |         for (const superpart of hash.split(";")) { | 
					
						
							|  |  |  |             const parts = superpart.at(-1)?.split(":") ?? [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             outer: for (const { toggle, name, submenu } of state.guistate.allToggles) { | 
					
						
							|  |  |  |                 for (const part of parts) { | 
					
						
							|  |  |  |                     if (part.indexOf(":") < 0) { | 
					
						
							|  |  |  |                         if (part === name) { | 
					
						
							|  |  |  |                             toggle.setData(true) | 
					
						
							|  |  |  |                             continue outer | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         continue | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     const [main, submenuValue] = part.split(":") | 
					
						
							|  |  |  |                     if (part !== main) { | 
					
						
							|  |  |  |                         continue | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  |                     toggle.setData(true) | 
					
						
							| 
									
										
										
										
											2024-08-02 13:33:29 +02:00
										 |  |  |                     submenu?.setData(submenuValue) | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  |                     continue outer | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-08-02 13:33:29 +02:00
										 |  |  |                 // If we arrive here, the loop above has not found any match
 | 
					
						
							|  |  |  |                 toggle.setData(false) | 
					
						
							|  |  |  |             } | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private setHash() { | 
					
						
							|  |  |  |         const s = this._state | 
					
						
							|  |  |  |         let h = "" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |         for (const { toggle, showOverOthers, name, submenu } of s.guistate.allToggles) { | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  |             if (showOverOthers || !toggle.data) { | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |             h = name | 
					
						
							|  |  |  |             if (submenu?.data) { | 
					
						
							|  |  |  |                 h += ":" + submenu.data | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (s.selectedElement.data !== undefined) { | 
					
						
							|  |  |  |             h = s.selectedElement.data.properties.id | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |         for (const { toggle, showOverOthers, name, submenu } of s.guistate.allToggles) { | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  |             if (!showOverOthers || !toggle.data) { | 
					
						
							| 
									
										
										
										
											2023-06-14 20:39:36 +02:00
										 |  |  |                 continue | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  |             } | 
					
						
							|  |  |  |             if (h) { | 
					
						
							|  |  |  |                 h += ";" + name | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 h = name | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (submenu?.data) { | 
					
						
							|  |  |  |                 h += ":" + submenu.data | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         Hash.hash.setData(h) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     private back() { | 
					
						
							|  |  |  |         const state = this._state | 
					
						
							| 
									
										
										
										
											2024-01-11 04:00:56 +01:00
										 |  |  |         if (state.previewedImage.data) { | 
					
						
							|  |  |  |             state.previewedImage.setData(undefined) | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2023-06-07 02:42:49 +02:00
										 |  |  |         // history.pushState(null, null, window.location.pathname);
 | 
					
						
							|  |  |  |         if (state.selectedElement.data) { | 
					
						
							|  |  |  |             state.selectedElement.setData(undefined) | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (state.guistate.closeAll()) { | 
					
						
							|  |  |  |             return | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |