diff --git a/Logic/Web/Hash.ts b/Logic/Web/Hash.ts index f89e61630..6c3174661 100644 --- a/Logic/Web/Hash.ts +++ b/Logic/Web/Hash.ts @@ -47,14 +47,6 @@ export default class Hash { hash.setData(newValue) } - window.addEventListener("popstate", (_) => { - let newValue = window.location.hash.substr(1) - if (newValue === "") { - newValue = undefined - } - hash.setData(newValue) - }) - return hash } } diff --git a/Logic/Web/NavigatorBackButtonHandler.ts b/Logic/Web/NavigatorBackButtonHandler.ts new file mode 100644 index 000000000..244d19232 --- /dev/null +++ b/Logic/Web/NavigatorBackButtonHandler.ts @@ -0,0 +1,171 @@ +import ThemeViewState from "../../Models/ThemeViewState"; +import Hash from "./Hash"; + +export default class NavigatorBackButtonHandler { + private readonly _state: ThemeViewState; + + /** + * 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; + + + Hash.hash.addCallback(hash => { + console.log("Current hash", 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() + } + }) + + state.selectedElement.addCallbackAndRun(_ => this.setHash()) + state.guistate.allToggles.forEach(({toggle, submenu}) => { + submenu?.addCallback(_ => this.setHash()) + toggle.addCallback(_ => this.setHash()); + }) + + 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 + }) + } + } + + /** + * 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 + } + } + +} diff --git a/Models/MenuState.ts b/Models/MenuState.ts index 476006f26..c1ec9b4e7 100644 --- a/Models/MenuState.ts +++ b/Models/MenuState.ts @@ -1,8 +1,8 @@ import LayerConfig from "./ThemeConfig/LayerConfig" -import { UIEventSource } from "../Logic/UIEventSource" +import {UIEventSource} from "../Logic/UIEventSource" import UserRelatedState from "../Logic/State/UserRelatedState" -import { Utils } from "../Utils" -import { LocalStorageSource } from "../Logic/Web/LocalStorageSource" +import {Utils} from "../Utils" +import {LocalStorageSource} from "../Logic/Web/LocalStorageSource" export type ThemeViewTabStates = typeof MenuState._themeviewTabs[number] export type MenuViewTabStates = typeof MenuState._menuviewTabs[number] @@ -14,22 +14,28 @@ export type MenuViewTabStates = typeof MenuState._menuviewTabs[number] * Some convenience methods are provided for this as well */ export class MenuState { - public static readonly _themeviewTabs = ["intro", "filters", "download", "copyright"] as const + public static readonly _themeviewTabs = ["intro", "filters", "download", "copyright","share"] as const + public static readonly _menuviewTabs = ["about", "settings", "community", "privacy","advanced"] as const public readonly themeIsOpened: UIEventSource public readonly themeViewTabIndex: UIEventSource public readonly themeViewTab: UIEventSource - - public static readonly _menuviewTabs = ["about", "settings", "community", "privacy"] as const public readonly menuIsOpened: UIEventSource public readonly menuViewTabIndex: UIEventSource public readonly menuViewTab: UIEventSource public readonly backgroundLayerSelectionIsOpened: UIEventSource = new UIEventSource(false) + public readonly allToggles: { + toggle: UIEventSource, name: string, + submenu?: UIEventSource, + showOverOthers?: boolean + }[] + public readonly highlightedLayerInFilters: UIEventSource = new UIEventSource( undefined ) public highlightedUserSetting: UIEventSource = new UIEventSource(undefined) + constructor(themeid: string = "") { if (themeid) { themeid += "-" @@ -49,6 +55,7 @@ export class MenuState { [], (str) => MenuState._menuviewTabs.indexOf(str) ) + this.menuViewTab.addCallbackAndRunD(s => console.trace("Menu view tab state is", s, this.menuIsOpened.data)) this.menuIsOpened.addCallbackAndRun((isOpen) => { if (!isOpen) { this.highlightedUserSetting.setData(undefined) @@ -70,7 +77,23 @@ export class MenuState { this.menuIsOpened.setData(false) } }) + + this.allToggles = [ + { + toggle: this.menuIsOpened, + name: "menu", + submenu: this.menuViewTab + }, { + toggle: this.themeIsOpened, + name: "theme-menu", + submenu: this.themeViewTab + }, { + toggle: this.backgroundLayerSelectionIsOpened, + name: "background", + showOverOthers: true + }] } + public openFilterView(highlightLayer?: LayerConfig | string) { this.themeIsOpened.setData(true) this.themeViewTab.setData("filters") @@ -101,10 +124,15 @@ export class MenuState { this.highlightedUserSetting.setData(highlightTagRendering) } - public closeAll() { - this.menuIsOpened.setData(false) - this.themeIsOpened.setData(false) - this.backgroundLayerSelectionIsOpened.setData(false) + /** + * Close all floatOvers. + * Returns 'true' if at least one menu was opened + */ + public closeAll(): boolean { + const toggles = [this.menuIsOpened, this.themeIsOpened, this.backgroundLayerSelectionIsOpened] + const somethingIsOpen = toggles.some(t => t.data) + toggles.forEach(t => t.setData(false)) + return somethingIsOpen } } diff --git a/Models/ThemeViewState.ts b/Models/ThemeViewState.ts index 2ef87de87..f4e0ba707 100644 --- a/Models/ThemeViewState.ts +++ b/Models/ThemeViewState.ts @@ -44,8 +44,8 @@ import {Utils} from "../Utils" import {EliCategory} from "./RasterLayerProperties" import BackgroundLayerResetter from "../Logic/Actors/BackgroundLayerResetter" import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveFeatureSourceToLocalStorage" -import Hash from "../Logic/Web/Hash" import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" +import NavigatorBackButtonHandler from "../Logic/Web/NavigatorBackButtonHandler"; /** * @@ -523,43 +523,6 @@ export default class ThemeViewState implements SpecialVisualizationState { * Setup various services for which no reference are needed */ private initActors() { - { - // Set the hash based on the selected element... - this.selectedElement.addCallback((selected) => { - Hash.hash.setData(selected?.properties?.id) - }) - // ... search and select an element based on the hash - Hash.hash.mapD( - (hash) => { - if (this.selectedElement.data?.properties?.id === hash) { - // We already have the correct hash - return - } - - const found = this.indexedFeatures.featuresById.data?.get(hash) - if (!found) { - return - } - if (found.properties.id === "last_click") { - return - } - const layer = this.layout.getMatchingLayer(found.properties) - console.log( - "Setting selected element based on hash", - hash, - "; found", - found, - "got matching layer", - layer.id, - "" - ) - this.selectedElement.setData(found) - this.selectedLayer.setData(layer) - }, - [this.indexedFeatures.featuresById.stabilized(250)] - ) - } - { // Unselect the selected element if it is panned out of view this.mapProperties.bounds.stabilized(250).addCallbackD((bounds) => { @@ -582,6 +545,7 @@ export default class ThemeViewState implements SpecialVisualizationState { } }) } + new NavigatorBackButtonHandler(this) new MetaTagging(this) new TitleHandler(this.selectedElement, this.selectedLayer, this.featureProperties, this) new ChangeToElementsActor(this.changes, this.featureProperties) diff --git a/UI/Base/TableOfContents.ts b/UI/Base/TableOfContents.ts index 7c93ca68e..12dac51b1 100644 --- a/UI/Base/TableOfContents.ts +++ b/UI/Base/TableOfContents.ts @@ -4,7 +4,6 @@ import { Translation } from "../i18n/Translation" import { FixedUiElement } from "./FixedUiElement" import Title from "./Title" import List from "./List" -import Hash from "../../Logic/Web/Hash" import Link from "./Link" import { Utils } from "../../Utils" @@ -43,13 +42,6 @@ export default class TableOfContents extends Combine { const vis = new Link(content, "#" + title.id) - Hash.hash.addCallbackAndRun((h) => { - if (h === title.id) { - vis.SetClass("font-bold") - } else { - vis.RemoveClass("font-bold") - } - }) els.push({ level: title.level, content: vis }) } const minLevel = Math.min(...els.map((e) => e.level)) diff --git a/UI/SpecialVisualizations.ts b/UI/SpecialVisualizations.ts index 6977f3ee7..1f20fab08 100644 --- a/UI/SpecialVisualizations.ts +++ b/UI/SpecialVisualizations.ts @@ -30,7 +30,6 @@ import OpeningHoursVisualization from "./OpeningHours/OpeningHoursVisualization" import LiveQueryHandler from "../Logic/Web/LiveQueryHandler" import {SubtleButton} from "./Base/SubtleButton" import Svg from "../Svg" -import Hash from "../Logic/Web/Hash" import NoteCommentElement from "./Popup/NoteCommentElement" import ImgurUploader from "../Logic/ImageProviders/ImgurUploader" import FileSelectorButton from "./Input/FileSelectorButton" @@ -913,7 +912,7 @@ export default class SpecialVisualizations { Translations.t.general.removeLocationHistory ).onClick(() => { state.historicalUserLocations.features.setData([]) - Hash.hash.setData(undefined) + state.selectedElement.setData(undefined) }) }, }, diff --git a/Utils/pngMapCreator.ts b/Utils/pngMapCreator.ts index 30267ba96..b93d5c82b 100644 --- a/Utils/pngMapCreator.ts +++ b/Utils/pngMapCreator.ts @@ -74,9 +74,8 @@ export class PngMapCreator { // Some extra buffer... setState("One second pause to make sure all images are loaded...") await Utils.waitFor(1000) - const dpiFactor = 1 - setState("Exporting png (" + this._options.width + "mm * " + this._options.height + "mm , dpiFactor:" + dpiFactor + ", maplibre-canvas-pixelratio: " + pixelRatio + ")") - return await mla.exportAsPng(dpiFactor) + setState("Exporting png (" + this._options.width + "mm * " + this._options.height + "mm , maplibre-canvas-pixelratio: " + pixelRatio + ")") + return await mla.exportAsPng(pixelRatio) } finally { div.parentElement.removeChild(div) } diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index 8a0cb8c34..974249f39 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -695,14 +695,6 @@ video { top: 14rem; } -.top-2 { - top: 0.5rem; -} - -.right-3 { - right: 0.75rem; -} - .top-0 { top: 0px; } @@ -711,6 +703,14 @@ video { left: 0px; } +.top-2 { + top: 0.5rem; +} + +.right-3 { + right: 0.75rem; +} + .bottom-0 { bottom: 0px; } @@ -909,14 +909,14 @@ video { margin-top: 2rem; } -.ml-3 { - margin-left: 0.75rem; -} - .mb-8 { margin-bottom: 2rem; } +.ml-3 { + margin-left: 0.75rem; +} + .-ml-6 { margin-left: -1.5rem; } @@ -1137,10 +1137,6 @@ video { width: 50%; } -.w-96 { - width: 24rem; -} - .w-10 { width: 2.5rem; } @@ -1586,6 +1582,10 @@ video { padding-left: 0.5rem; } +.pr-4 { + padding-right: 1rem; +} + .pt-0\.5 { padding-top: 0.125rem; } @@ -1606,10 +1606,6 @@ video { padding-right: 0.75rem; } -.pr-4 { - padding-right: 1rem; -} - .pl-3 { padding-left: 0.75rem; }