UX+Refactoring: use side-drawer for menu, reorder menu structure

This commit is contained in:
Pieter Vander Vennet 2024-08-29 02:46:51 +02:00
parent 8465b59c7f
commit 124e816abe
25 changed files with 645 additions and 1059 deletions

View file

@ -13,65 +13,40 @@
import type { MapProperties } from "../Models/MapProperties"
import Geosearch from "./BigComponents/Geosearch.svelte"
import Translations from "./i18n/Translations"
import usersettings from "../assets/generated/layers/usersettings.json"
import {
CogIcon,
EyeIcon,
HeartIcon,
MenuIcon,
XCircleIcon,
MenuIcon
} from "@rgossiaux/svelte-heroicons/solid"
import Tr from "./Base/Tr.svelte"
import CommunityIndexView from "./BigComponents/CommunityIndexView.svelte"
import FloatOver from "./Base/FloatOver.svelte"
import Constants from "../Models/Constants"
import TabbedGroup from "./Base/TabbedGroup.svelte"
import UserRelatedState from "../Logic/State/UserRelatedState"
import LoginToggle from "./Base/LoginToggle.svelte"
import LoginButton from "./Base/LoginButton.svelte"
import CopyrightPanel from "./BigComponents/CopyrightPanel.svelte"
import DownloadPanel from "./DownloadFlow/DownloadPanel.svelte"
import ModalRight from "./Base/ModalRight.svelte"
import LevelSelector from "./BigComponents/LevelSelector.svelte"
import ThemeIntroPanel from "./BigComponents/ThemeIntroPanel.svelte"
import type { RasterLayerPolygon } from "../Models/RasterLayers"
import { AvailableRasterLayers } from "../Models/RasterLayers"
import RasterLayerOverview from "./Map/RasterLayerOverview.svelte"
import IfHidden from "./Base/IfHidden.svelte"
import { onDestroy } from "svelte"
import OpenBackgroundSelectorButton from "./BigComponents/OpenBackgroundSelectorButton.svelte"
import StateIndicator from "./BigComponents/StateIndicator.svelte"
import ShareScreen from "./BigComponents/ShareScreen.svelte"
import UploadingImageCounter from "./Image/UploadingImageCounter.svelte"
import PendingChangesIndicator from "./BigComponents/PendingChangesIndicator.svelte"
import Cross from "../assets/svg/Cross.svelte"
import LanguagePicker from "./InputElement/LanguagePicker.svelte"
import Min from "../assets/svg/Min.svelte"
import Plus from "../assets/svg/Plus.svelte"
import Filter from "../assets/svg/Filter.svelte"
import Community from "../assets/svg/Community.svelte"
import Favourites from "./Favourites/Favourites.svelte"
import ImageOperations from "./Image/ImageOperations.svelte"
import VisualFeedbackPanel from "./BigComponents/VisualFeedbackPanel.svelte"
import { Orientation } from "../Sensors/Orientation"
import GeolocationIndicator from "./BigComponents/GeolocationIndicator.svelte"
import Compass_arrow from "../assets/svg/Compass_arrow.svelte"
import ReverseGeocoding from "./BigComponents/ReverseGeocoding.svelte"
import FilterPanel from "./BigComponents/FilterPanel.svelte"
import PrivacyPolicy from "./BigComponents/PrivacyPolicy.svelte"
import { BBox } from "../Logic/BBox"
import ReviewsOverview from "./Reviews/ReviewsOverview.svelte"
import ExtraLinkButton from "./BigComponents/ExtraLinkButton.svelte"
import CloseAnimation from "./Base/CloseAnimation.svelte"
import { LastClickFeatureSource } from "../Logic/FeatureSource/Sources/LastClickFeatureSource"
import ArrowDownTray from "@babeard/svelte-heroicons/mini/ArrowDownTray"
import Share from "@babeard/svelte-heroicons/solid/Share"
import ChevronRight from "@babeard/svelte-heroicons/solid/ChevronRight"
import Marker from "./Map/Marker.svelte"
import AboutMapComplete from "./BigComponents/AboutMapComplete.svelte"
import HotkeyTable from "./BigComponents/HotkeyTable.svelte"
import SelectedElementPanel from "./Base/SelectedElementPanel.svelte"
import type { LayerConfigJson } from "../Models/ThemeConfig/Json/LayerConfigJson"
import MenuDrawer from "./BigComponents/MenuDrawer.svelte"
import DrawerLeft from "./Base/DrawerLeft.svelte"
export let state: ThemeViewState
let layout = state.layout
@ -120,7 +95,6 @@
let visualFeedback = state.visualFeedback
let viewport: UIEventSource<HTMLDivElement> = new UIEventSource<HTMLDivElement>(undefined)
let mapproperties: MapProperties = state.mapProperties
let usersettingslayer = new LayerConfig(<LayerConfigJson>usersettings, "usersettings", true)
state.mapProperties.installCustomKeyboardHandler(viewport)
let canZoomIn = mapproperties.maxzoom.map(
(mz) => mapproperties.zoom.data < mz,
@ -144,7 +118,7 @@
const bottomRight = mlmap.unproject([rect.right, rect.bottom])
const bbox = new BBox([
[topLeft.lng, topLeft.lat],
[bottomRight.lng, bottomRight.lat],
[bottomRight.lng, bottomRight.lat]
])
state.visualFeedbackViewportBounds.setData(bbox)
}
@ -156,7 +130,6 @@
updateViewport()
})
let featureSwitches: FeatureSwitchState = state.featureSwitches
let availableLayers = state.availableLayers
let currentViewLayer: LayerConfig = layout.layers.find((l) => l.id === "current_view")
let rasterLayer: Store<RasterLayerPolygon> = state.mapProperties.rasterLayer
let rasterLayerName =
@ -168,8 +141,13 @@
})
)
let previewedImage = state.previewedImage
let addNewFeatureMode = state.userRelatedState.addNewFeatureMode
let gpsAvailable = state.geolocation.geolocationState.gpsAvailable
let gpsButtonAriaLabel = state.geolocation.geolocationState.gpsStateExplanation
let debug = state.featureSwitches.featureSwitchIsDebugging
debug.addCallbackAndRun((dbg) => {
if (dbg) {
document.body.classList.add("debug")
@ -190,26 +168,7 @@
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)
let addNewFeatureMode = state.userRelatedState.addNewFeatureMode
let gpsAvailable = state.geolocation.geolocationState.gpsAvailable
let gpsButtonAriaLabel = state.geolocation.geolocationState.gpsStateExplanation
</script>
<main>
@ -255,9 +214,8 @@
<div class="float-left m-1 flex flex-col sm:mt-2">
<If condition={state.featureSwitches.featureSwitchWelcomeMessage}>
<MapControlButton
on:click={() => state.guistate.themeIsOpened.setData(true)}
on:click={() => state.guistate.pageStates.about_theme.set(true)}
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"
@ -272,9 +230,8 @@
<MapControlButton
arialabel={Translations.t.general.labels.menu}
on:click={() => state.guistate.menuIsOpened.setData(true)}
on:click={() => {console.log("Opening...."); state.guistate.menuIsOpened.setData(true)}}
on:keydown={forwardEventToMap}
htmlElem={openMenuButton}
>
<MenuIcon class="h-8 w-8 cursor-pointer" />
</MapControlButton>
@ -285,7 +242,6 @@
state.selectCurrentView()
}}
on:keydown={forwardEventToMap}
htmlElem={openCurrentViewLayerButton}
>
<div class="h-8 w-8 cursor-pointer">
<ToSvelte construct={() => currentViewLayer.defaultIcon()} />
@ -322,7 +278,6 @@
<button
class="low-interaction pointer-events-auto w-fit"
class:disabled={$currentZoom < Constants.minZoomLevelToAddNewPoint}
bind:this={_openNewElementButton}
on:click={() => {
state.openNewDialog()
}}
@ -346,7 +301,6 @@
arialabel={Translations.t.general.labels.filter}
on:click={() => state.guistate.openFilterView()}
on:keydown={forwardEventToMap}
htmlElem={openFilterButton}
>
<Filter class="h-6 w-6" />
</MapControlButton>
@ -355,25 +309,17 @@
<OpenBackgroundSelectorButton
hideTooltip={true}
{state}
htmlElem={openBackgroundButton}
/>
</If>
<a
class="bg-black-transparent pointer-events-auto ml-1 h-fit max-h-12 cursor-pointer self-end self-center overflow-hidden rounded-2xl px-1 text-white opacity-50 hover:opacity-100"
on:click={() => {
if (featureSwitches.featureSwitchWelcomeMessage.data) {
state.guistate.themeViewTab.setData("copyright")
state.guistate.themeIsOpened.setData(true)
} else {
state.guistate.copyrightPanelIsOpened.setData(true)
}
}}
<button
class="as-link bg-black-transparent pointer-events-auto ml-1 h-fit max-h-12 cursor-pointer self-end self-center overflow-hidden rounded-2xl px-1 text-white opacity-50 hover:opacity-100"
on:click={() => {state.guistate.pageStates.copyright.set(true)}}
>
© <span class="hidden sm:inline sm:pr-2">
OpenStreetMap
<span class="hidden w-24 md:inline md:pr-2">, {rasterLayerName}</span>
</span>
</a>
</button>
</div>
</div>
@ -443,6 +389,11 @@
<svelte:fragment slot="error" />
</LoginToggle>
<DrawerLeft shown={state.guistate.menuIsOpened}>
<MenuDrawer onlyLink={true} {state} />
</DrawerLeft>
<MenuDrawer onlyLink={false} {state} />
{#if $selectedElement !== undefined && $selectedLayer !== undefined && !$selectedLayer.popupInFloatover}
<!-- right modal with the selected element view -->
<ModalRight
@ -485,225 +436,5 @@
</FloatOver>
</If>
<!-- big theme menu -->
<If condition={state.guistate.themeIsOpened}>
<FloatOver on:close={() => state.guistate.themeIsOpened.setData(false)}>
<span slot="close-button"><!-- Disable the close button --></span>
<TabbedGroup
condition1={state.featureSwitches.featureSwitchEnableExport}
tab={state.guistate.themeViewTabIndex}
>
<div slot="post-tablist">
<XCircleIcon
class="mr-2 h-8 w-8"
on:click={() => state.guistate.themeIsOpened.setData(false)}
/>
</div>
<div class="flex" slot="title0">
<Marker icons={layout.icon} size="h-4 w-4" />
<Tr t={layout.title} />
</div>
<div class="m-4 h-full" slot="content0">
<ThemeIntroPanel {state} />
</div>
<div class="flex" slot="title1">
<If condition={state.featureSwitches.featureSwitchEnableExport}>
<ArrowDownTray class="h-4 w-4" />
<Tr t={Translations.t.general.download.title} />
</If>
</div>
<div class="m-4" slot="content1">
<DownloadPanel {state} />
</div>
<div slot="title2">
<Tr t={Translations.t.general.attribution.title} />
</div>
<div slot="content2" class="m-2 flex flex-col">
<CopyrightPanel {state} />
</div>
<div class="flex" slot="title3">
<Share class="h-4 w-4" />
<Tr t={Translations.t.general.sharescreen.title} />
</div>
<div class="m-2" slot="content3">
<ShareScreen {state} />
</div>
</TabbedGroup>
</FloatOver>
</If>
<!-- Filterpane -->
<If condition={state.guistate.filtersPanelIsOpened}>
<FloatOver on:close={() => state.guistate.filtersPanelIsOpened.setData(false)}>
<FilterPanel {state} />
</FloatOver>
</If>
<!-- background layer selector -->
<IfHidden condition={state.guistate.backgroundLayerSelectionIsOpened}>
<FloatOver
on:close={() => {
state.guistate.backgroundLayerSelectionIsOpened.setData(false)
}}
>
<RasterLayerOverview
{availableLayers}
map={state.map}
mapproperties={state.mapProperties}
userstate={state.userRelatedState}
visible={state.guistate.backgroundLayerSelectionIsOpened}
/>
</FloatOver>
</IfHidden>
<!-- Menu page -->
<If condition={state.guistate.menuIsOpened}>
<FloatOver on:close={() => state.guistate.menuIsOpened.setData(false)}>
<span slot="close-button"><!-- Hide the default close button --></span>
<TabbedGroup
condition1={featureSwitches.featureSwitchEnableLogin}
condition2={state.featureSwitches.featureSwitchCommunityIndex}
tab={state.guistate.menuViewTabIndex}
>
<div slot="post-tablist">
<XCircleIcon
class="mr-2 h-8 w-8"
on:click={() => state.guistate.menuIsOpened.setData(false)}
/>
</div>
<div class="flex" slot="title0">
<Tr t={Translations.t.general.menu.aboutMapComplete} />
</div>
<div slot="content0" class="flex flex-col">
<AboutMapComplete {state} />
<div class="m-2 flex flex-col">
<HotkeyTable />
</div>
</div>
<div class="flex" slot="title1">
<CogIcon class="h-6 w-6" />
<Tr t={UserRelatedState.usersettingsConfig.title.GetRenderValue({})} />
</div>
<div class="links-as-button py-8" slot="content1">
<!-- All shown components are set by 'usersettings.json', which happily uses some special visualisations created specifically for it -->
<LoginToggle {state}>
<div class="flex flex-col" slot="not-logged-in">
<LanguagePicker availableLanguages={layout.language} />
<Tr cls="alert" t={Translations.t.userinfo.notLoggedIn} />
<LoginButton clss="primary" osmConnection={state.osmConnection} />
</div>
<SelectedElementView
highlightedRendering={state.guistate.highlightedUserSetting}
layer={usersettingslayer}
selectedElement={{
type: "Feature",
properties: { id: "settings" },
geometry: { type: "Point", coordinates: [0, 0] },
}}
{state}
tags={state.userRelatedState.preferencesAsTags}
/>
</LoginToggle>
</div>
<div class="flex" slot="title2">
<HeartIcon class="h-6 w-6" />
<Tr t={Translations.t.favouritePoi.tab} />
</div>
<div class="m-2 flex flex-col" slot="content2">
<h3>
<Tr t={Translations.t.favouritePoi.title} />
</h3>
<Favourites {state} />
<h3>
<Tr t={Translations.t.reviews.your_reviews} />
</h3>
<ReviewsOverview {state} />
</div>
</TabbedGroup>
</FloatOver>
</If>
<!-- Privacy policy -->
<If condition={state.guistate.privacyPanelIsOpened}>
<FloatOver on:close={() => state.guistate.privacyPanelIsOpened.setData(false)}>
<div class="flex h-full flex-col overflow-hidden">
<h2 class="low-interaction m-0 flex items-center p-4 drop-shadow-md">
<EyeIcon class="w-6 pr-2" />
<Tr t={Translations.t.privacy.title} />
</h2>
<div class="overflow-auto p-4">
<PrivacyPolicy {state} />
</div>
</div>
</FloatOver>
</If>
<!-- Attribution, copyright and about MapComplete (no menu case) -->
<If condition={state.guistate.copyrightPanelIsOpened}>
<FloatOver on:close={() => state.guistate.copyrightPanelIsOpened.setData(false)}>
<div class="flex h-full flex-col overflow-hidden">
<h1 class="low-interaction m-0 flex items-center p-4 drop-shadow-md">
<Tr t={Translations.t.general.attribution.title} />
</h1>
<div class="overflow-auto p-4">
<h2>
<Tr t={Translations.t.general.menu.aboutMapComplete} />
</h2>
<AboutMapComplete {state} />
<CopyrightPanel {state} />
</div>
</div>
</FloatOver>
</If>
<!-- Community index -->
<If condition={state.guistate.communityIndexPanelIsOpened}>
<FloatOver on:close={() => state.guistate.communityIndexPanelIsOpened.setData(false)}>
<div class="flex h-full flex-col overflow-hidden">
<h2 class="low-interaction m-0 flex items-center p-4">
<Community class="h-6 w-6" />
<Tr t={Translations.t.communityIndex.title} />
</h2>
<div class="overflow-auto p-4">
<CommunityIndexView location={state.mapProperties.location} />
</div>
</div>
</FloatOver>
</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) => 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"
/>
</main>