UX: add fade-out animation to menus to show where they can be found again
This commit is contained in:
parent
733c2c7d14
commit
2bd3806f9a
9 changed files with 107 additions and 21 deletions
|
@ -781,12 +781,12 @@ video {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-4 {
|
.m-8 {
|
||||||
margin: 1rem;
|
margin: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-2 {
|
.m-4 {
|
||||||
margin: 0.5rem;
|
margin: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-3 {
|
.m-3 {
|
||||||
|
@ -797,8 +797,8 @@ video {
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-8 {
|
.m-2 {
|
||||||
margin: 2rem;
|
margin: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-1 {
|
.m-1 {
|
||||||
|
@ -1806,14 +1806,14 @@ video {
|
||||||
background-repeat: repeat;
|
background-repeat: repeat;
|
||||||
}
|
}
|
||||||
|
|
||||||
.p-4 {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-8 {
|
.p-8 {
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.p-4 {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.p-0 {
|
.p-0 {
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ export class LastClickFeatureSource {
|
||||||
private i: number = 0
|
private i: number = 0
|
||||||
private readonly hasPresets: boolean
|
private readonly hasPresets: boolean
|
||||||
private readonly hasNoteLayer: boolean
|
private readonly hasNoteLayer: boolean
|
||||||
|
public static readonly newPointElementId= "new_point_dialog"
|
||||||
|
|
||||||
constructor(layout: LayoutConfig) {
|
constructor(layout: LayoutConfig) {
|
||||||
this.hasNoteLayer = layout.hasNoteLayer()
|
this.hasNoteLayer = layout.hasNoteLayer()
|
||||||
|
@ -46,7 +47,7 @@ export class LastClickFeatureSource {
|
||||||
|
|
||||||
public createFeature(lon: number, lat: number): Feature<Point, OsmTags> {
|
public createFeature(lon: number, lat: number): Feature<Point, OsmTags> {
|
||||||
const properties: OsmTags = {
|
const properties: OsmTags = {
|
||||||
id: "new_point_dialog",
|
id: LastClickFeatureSource.newPointElementId,
|
||||||
has_note_layer: this.hasNoteLayer ? "yes" : "no",
|
has_note_layer: this.hasNoteLayer ? "yes" : "no",
|
||||||
has_presets: this.hasPresets ? "yes" : "no",
|
has_presets: this.hasPresets ? "yes" : "no",
|
||||||
renderings: this.renderings.join(""),
|
renderings: this.renderings.join(""),
|
||||||
|
|
49
src/UI/Base/CloseAnimation.svelte
Normal file
49
src/UI/Base/CloseAnimation.svelte
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { Store } from "../../Logic/UIEventSource"
|
||||||
|
import { onDestroy, onMount } from "svelte"
|
||||||
|
|
||||||
|
let elem: HTMLElement
|
||||||
|
let targetOuter: HTMLElement
|
||||||
|
export let isOpened: Store<boolean>
|
||||||
|
export let moveTo: Store<HTMLElement>
|
||||||
|
|
||||||
|
export let debug : string
|
||||||
|
function copySizeOf(htmlElem: HTMLElement) {
|
||||||
|
const target = htmlElem.getBoundingClientRect()
|
||||||
|
elem.style.left = target.x + "px"
|
||||||
|
elem.style.top = target.y + "px"
|
||||||
|
elem.style.width = target.width + "px"
|
||||||
|
elem.style.height = target.height + "px"
|
||||||
|
}
|
||||||
|
|
||||||
|
function animate(opened: boolean) {
|
||||||
|
const moveToElem = moveTo.data
|
||||||
|
console.log("Animating", debug," to", opened)
|
||||||
|
if (opened) {
|
||||||
|
copySizeOf(targetOuter)
|
||||||
|
elem.style.background = "var(--background-color)"
|
||||||
|
} else if (moveToElem !== undefined) {
|
||||||
|
copySizeOf(moveToElem)
|
||||||
|
elem.style.background = "#ffffff00"
|
||||||
|
} else {
|
||||||
|
elem.style.left = "0px"
|
||||||
|
elem.style.top = "0px"
|
||||||
|
elem.style.background = "#ffffff00"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDestroy(isOpened.addCallback(opened => animate(opened)))
|
||||||
|
onMount(() => animate(isOpened.data))
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<div class={"absolute bottom-0 right-0 h-full w-screen p-4 md:p-6 pointer-events-none invisible"}>
|
||||||
|
<div class="content h-full" bind:this={targetOuter} style="background: red" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div bind:this={elem} class="pointer-events-none absolute bottom-0 right-0 low-interaction rounded-2xl"
|
||||||
|
style="transition: all 0.65s ease-out, background-color 1.8s ease-out; background: var(--background-color);">
|
||||||
|
<!-- Classes should be the same as the 'floatoaver' -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
*/
|
*/
|
||||||
const dispatch = createEventDispatcher<{ close }>()
|
const dispatch = createEventDispatcher<{ close }>()
|
||||||
|
|
||||||
export let extraClasses = "p-4 md:p-6"
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Draw the background over the total screen -->
|
<!-- Draw the background over the total screen -->
|
||||||
|
@ -24,7 +23,7 @@
|
||||||
/>
|
/>
|
||||||
<!-- draw a _second_ absolute div, placed using 'bottom' which will be above the navigation bar on mobile browsers -->
|
<!-- draw a _second_ absolute div, placed using 'bottom' which will be above the navigation bar on mobile browsers -->
|
||||||
<div
|
<div
|
||||||
class={twMerge("absolute bottom-0 right-0 h-full w-screen", extraClasses)}
|
class={"absolute bottom-0 right-0 h-full w-screen p-4 md:p-6"}
|
||||||
style="z-index: 21"
|
style="z-index: 21"
|
||||||
use:trapFocus
|
use:trapFocus
|
||||||
>
|
>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
import { twJoin } from "tailwind-merge"
|
import { twJoin } from "tailwind-merge"
|
||||||
import { Translation } from "../i18n/Translation"
|
import { Translation } from "../i18n/Translation"
|
||||||
import { ariaLabel } from "../../Utils/ariaLabel"
|
import { ariaLabel } from "../../Utils/ariaLabel"
|
||||||
import { ImmutableStore, Store } from "../../Logic/UIEventSource"
|
import { ImmutableStore, Store, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A round button with an icon and possible a small text, which hovers above the map
|
* A round button with an icon and possible a small text, which hovers above the map
|
||||||
|
@ -12,9 +12,15 @@
|
||||||
export let cls = "m-0.5 p-0.5 sm:p-1 md:m-1"
|
export let cls = "m-0.5 p-0.5 sm:p-1 md:m-1"
|
||||||
export let enabled: Store<boolean> = new ImmutableStore(true)
|
export let enabled: Store<boolean> = new ImmutableStore(true)
|
||||||
export let arialabel: Translation = undefined
|
export let arialabel: Translation = undefined
|
||||||
|
export let htmlElem: UIEventSource<HTMLElement> = undefined
|
||||||
|
let _htmlElem : HTMLElement
|
||||||
|
$: {
|
||||||
|
htmlElem?.setData(_htmlElem)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
bind:this={_htmlElem}
|
||||||
on:click={(e) => dispatch("click", e)}
|
on:click={(e) => dispatch("click", e)}
|
||||||
on:keydown
|
on:keydown
|
||||||
use:ariaLabel={arialabel}
|
use:ariaLabel={arialabel}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
* Even though the component is very small, it gets its own class as it is often reused
|
* Even though the component is very small, it gets its own class as it is often reused
|
||||||
*/
|
*/
|
||||||
import { Square3Stack3dIcon } from "@babeard/svelte-heroicons/solid"
|
import { Square3Stack3dIcon } from "@babeard/svelte-heroicons/solid"
|
||||||
import type { SpecialVisualizationState } from "../SpecialVisualization"
|
|
||||||
import Translations from "../i18n/Translations"
|
import Translations from "../i18n/Translations"
|
||||||
import MapControlButton from "../Base/MapControlButton.svelte"
|
import MapControlButton from "../Base/MapControlButton.svelte"
|
||||||
import Tr from "../Base/Tr.svelte"
|
import Tr from "../Base/Tr.svelte"
|
||||||
|
@ -16,11 +15,13 @@
|
||||||
export let state: ThemeViewState
|
export let state: ThemeViewState
|
||||||
export let map: Store<MlMap> = undefined
|
export let map: Store<MlMap> = undefined
|
||||||
export let hideTooltip = false
|
export let hideTooltip = false
|
||||||
|
export let htmlElem : UIEventSource<HTMLElement> = undefined
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<MapControlButton
|
<MapControlButton
|
||||||
arialabel={Translations.t.general.labels.background}
|
arialabel={Translations.t.general.labels.background}
|
||||||
on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}
|
on:click={() => state.guistate.backgroundLayerSelectionIsOpened.setData(true)}
|
||||||
|
{htmlElem}
|
||||||
>
|
>
|
||||||
<StyleLoadingIndicator map={map ?? state.map} rasterLayer={state.mapProperties.rasterLayer}>
|
<StyleLoadingIndicator map={map ?? state.map} rasterLayer={state.mapProperties.rasterLayer}>
|
||||||
<Square3Stack3dIcon class="h-6 w-6" />
|
<Square3Stack3dIcon class="h-6 w-6" />
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Loading from "../Base/Loading.svelte"
|
import Loading from "../Base/Loading.svelte"
|
||||||
import { Stores, UIEventSource } from "../../Logic/UIEventSource"
|
import { Store, Stores, UIEventSource } from "../../Logic/UIEventSource"
|
||||||
import { Map as MlMap } from "maplibre-gl"
|
import { Map as MlMap } from "maplibre-gl"
|
||||||
import { onDestroy } from "svelte"
|
import { onDestroy } from "svelte"
|
||||||
|
|
||||||
let isLoading = false
|
let isLoading = false
|
||||||
export let map: UIEventSource<MlMap>
|
export let map: Store<MlMap>
|
||||||
/**
|
/**
|
||||||
* Optional. Only used for the 'global' change indicator so that it won't spin on pan/zoom but only when a change _actually_ occured
|
* Optional. Only used for the 'global' change indicator so that it won't spin on pan/zoom but only when a change _actually_ occured
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -73,8 +73,8 @@
|
||||||
import { BBox } from "../Logic/BBox"
|
import { BBox } from "../Logic/BBox"
|
||||||
import ReviewsOverview from "./Reviews/ReviewsOverview.svelte"
|
import ReviewsOverview from "./Reviews/ReviewsOverview.svelte"
|
||||||
import ExtraLinkButton from "./BigComponents/ExtraLinkButton.svelte"
|
import ExtraLinkButton from "./BigComponents/ExtraLinkButton.svelte"
|
||||||
import Locale from "./i18n/Locale"
|
import CloseAnimation from "./Base/CloseAnimation.svelte"
|
||||||
import LanguageUtils from "../Utils/LanguageUtils"
|
import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"
|
||||||
|
|
||||||
export let state: ThemeViewState
|
export let state: ThemeViewState
|
||||||
let layout = state.layout
|
let layout = state.layout
|
||||||
|
@ -153,7 +153,7 @@
|
||||||
})
|
})
|
||||||
let featureSwitches: FeatureSwitchState = state.featureSwitches
|
let featureSwitches: FeatureSwitchState = state.featureSwitches
|
||||||
let availableLayers = state.availableLayers
|
let availableLayers = state.availableLayers
|
||||||
let currentViewLayer = layout.layers.find((l) => l.id === "current_view")
|
let currentViewLayer: LayerConfig = layout.layers.find((l) => l.id === "current_view")
|
||||||
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer
|
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer
|
||||||
let rasterLayerName =
|
let rasterLayerName =
|
||||||
rasterLayer.data?.properties?.name ??
|
rasterLayer.data?.properties?.name ??
|
||||||
|
@ -185,6 +185,22 @@
|
||||||
const animation = mlmap.keyboard?.keydown(e)
|
const animation = mlmap.keyboard?.keydown(e)
|
||||||
animation?.cameraAnimation(mlmap)
|
animation?.cameraAnimation(mlmap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needed for the animations
|
||||||
|
*/
|
||||||
|
let openMapButton : UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||||
|
let openMenuButton : UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||||
|
let openCurrentViewLayerButton : UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||||
|
let _openNewElementButton: HTMLButtonElement
|
||||||
|
let openNewElementButton : UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||||
|
|
||||||
|
$: {
|
||||||
|
openNewElementButton.setData(_openNewElementButton)
|
||||||
|
}
|
||||||
|
let openFilterButton : UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||||
|
let openBackgroundButton : UIEventSource<HTMLElement> = new UIEventSource<HTMLElement>(undefined)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
|
<div class="absolute top-0 left-0 h-screen w-screen overflow-hidden">
|
||||||
|
@ -230,6 +246,7 @@
|
||||||
<MapControlButton
|
<MapControlButton
|
||||||
on:click={() => state.guistate.themeIsOpened.setData(true)}
|
on:click={() => state.guistate.themeIsOpened.setData(true)}
|
||||||
on:keydown={forwardEventToMap}
|
on:keydown={forwardEventToMap}
|
||||||
|
htmlElem={openMapButton}
|
||||||
>
|
>
|
||||||
<div class="m-0.5 mx-1 flex cursor-pointer items-center max-[480px]:w-full sm:mx-1 md:mx-2">
|
<div class="m-0.5 mx-1 flex cursor-pointer items-center max-[480px]:w-full sm:mx-1 md:mx-2">
|
||||||
<img
|
<img
|
||||||
|
@ -247,6 +264,7 @@
|
||||||
arialabel={Translations.t.general.labels.menu}
|
arialabel={Translations.t.general.labels.menu}
|
||||||
on:click={() => state.guistate.menuIsOpened.setData(true)}
|
on:click={() => state.guistate.menuIsOpened.setData(true)}
|
||||||
on:keydown={forwardEventToMap}
|
on:keydown={forwardEventToMap}
|
||||||
|
htmlElem={openMenuButton}
|
||||||
>
|
>
|
||||||
<MenuIcon class="h-8 w-8 cursor-pointer" />
|
<MenuIcon class="h-8 w-8 cursor-pointer" />
|
||||||
</MapControlButton>
|
</MapControlButton>
|
||||||
|
@ -256,6 +274,7 @@
|
||||||
state.selectedElement.setData(state.currentView.features?.data?.[0])
|
state.selectedElement.setData(state.currentView.features?.data?.[0])
|
||||||
}}
|
}}
|
||||||
on:keydown={forwardEventToMap}
|
on:keydown={forwardEventToMap}
|
||||||
|
htmlElem={openCurrentViewLayerButton}
|
||||||
>
|
>
|
||||||
<ToSvelte
|
<ToSvelte
|
||||||
construct={() => currentViewLayer.defaultIcon().SetClass("w-8 h-8 cursor-pointer")}
|
construct={() => currentViewLayer.defaultIcon().SetClass("w-8 h-8 cursor-pointer")}
|
||||||
|
@ -289,6 +308,7 @@
|
||||||
<button
|
<button
|
||||||
class="pointer-events-auto w-fit low-interaction"
|
class="pointer-events-auto w-fit low-interaction"
|
||||||
class:disabled={$currentZoom < Constants.minZoomLevelToAddNewPoint}
|
class:disabled={$currentZoom < Constants.minZoomLevelToAddNewPoint}
|
||||||
|
bind:this={_openNewElementButton}
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
state.openNewDialog()
|
state.openNewDialog()
|
||||||
}}
|
}}
|
||||||
|
@ -312,12 +332,13 @@
|
||||||
arialabel={Translations.t.general.labels.filter}
|
arialabel={Translations.t.general.labels.filter}
|
||||||
on:click={() => state.guistate.openFilterView()}
|
on:click={() => state.guistate.openFilterView()}
|
||||||
on:keydown={forwardEventToMap}
|
on:keydown={forwardEventToMap}
|
||||||
|
htmlElem={openFilterButton}
|
||||||
>
|
>
|
||||||
<Filter class="h-6 w-6" />
|
<Filter class="h-6 w-6" />
|
||||||
</MapControlButton>
|
</MapControlButton>
|
||||||
</If>
|
</If>
|
||||||
<If condition={state.featureSwitches.featureSwitchBackgroundSelection}>
|
<If condition={state.featureSwitches.featureSwitchBackgroundSelection}>
|
||||||
<OpenBackgroundSelectorButton hideTooltip={true} {state} />
|
<OpenBackgroundSelectorButton hideTooltip={true} {state} htmlElem={openBackgroundButton} />
|
||||||
</If>
|
</If>
|
||||||
<a
|
<a
|
||||||
class="bg-black-transparent pointer-events-auto h-fit max-h-12 cursor-pointer self-end overflow-hidden rounded-2xl pl-1 pr-2 text-white opacity-50 hover:opacity-100"
|
class="bg-black-transparent pointer-events-auto h-fit max-h-12 cursor-pointer self-end overflow-hidden rounded-2xl pl-1 pr-2 text-white opacity-50 hover:opacity-100"
|
||||||
|
@ -659,3 +680,11 @@
|
||||||
</div>
|
</div>
|
||||||
</FloatOver>
|
</FloatOver>
|
||||||
</If>
|
</If>
|
||||||
|
|
||||||
|
|
||||||
|
<CloseAnimation isOpened={state.guistate.themeIsOpened} moveTo={openMapButton} debug="theme"/>
|
||||||
|
<CloseAnimation isOpened={state.guistate.menuIsOpened} moveTo={openMenuButton} debug="menu"/>
|
||||||
|
<CloseAnimation isOpened={selectedLayer.map(sl => (sl !== undefined && sl === currentViewLayer))} moveTo={openCurrentViewLayerButton} debug="currentViewLayer"/>
|
||||||
|
<CloseAnimation isOpened={selectedElement.map(sl =>{ console.log("SE is", sl); return sl !== undefined && sl?.properties?.id === LastClickFeatureSource.newPointElementId })} moveTo={openNewElementButton} debug="newElement"/>
|
||||||
|
<CloseAnimation isOpened={state.guistate.filtersPanelIsOpened} moveTo={openFilterButton} debug="filter"/>
|
||||||
|
<CloseAnimation isOpened={state.guistate.backgroundLayerSelectionIsOpened} moveTo={openBackgroundButton} debug="bg"/>
|
||||||
|
|
Loading…
Reference in a new issue