forked from MapComplete/MapComplete
		
	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…
	
	Add table
		Add a link
		
	
		Reference in a new issue