From 98866b4a5775648316a00c5bccce4b30fc91a255 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Tue, 3 Jan 2023 02:24:03 +0100 Subject: [PATCH] User flow improvements for the theme introduction panel --- Logic/Osm/OsmConnection.ts | 3 +- Logic/UIEventSource.ts | 6 +- UI/BigComponents/FullWelcomePaneWithTabs.ts | 17 ++-- UI/BigComponents/ThemeIntroductionPanel.ts | 17 +++- UI/BigComponents/UserInformation.ts | 17 ++-- UI/DefaultGUI.ts | 14 +++- UI/DefaultGuiState.ts | 12 ++- UI/LoggedInUserIndicator.ts | 42 ++++++++++ css/index-tailwind-output.css | 88 ++++++++++++--------- langs/en.json | 2 +- 10 files changed, 156 insertions(+), 62 deletions(-) create mode 100644 UI/LoggedInUserIndicator.ts diff --git a/Logic/Osm/OsmConnection.ts b/Logic/Osm/OsmConnection.ts index 692421b3f..6e294d2bc 100644 --- a/Logic/Osm/OsmConnection.ts +++ b/Logic/Osm/OsmConnection.ts @@ -62,7 +62,7 @@ export class OsmConnection { private readonly _singlePage: boolean private isChecking = false - constructor(options: { + constructor(options?: { dryRun?: UIEventSource fakeUser?: false | boolean oauth_token?: UIEventSource @@ -71,6 +71,7 @@ export class OsmConnection { osmConfiguration?: "osm" | "osm-test" attemptLogin?: true | boolean }) { + options = options ?? {} this.fakeUser = options.fakeUser ?? false this._singlePage = options.singlePage ?? true this._oauth_config = diff --git a/Logic/UIEventSource.ts b/Logic/UIEventSource.ts index 0ee8b5967..7db59652f 100644 --- a/Logic/UIEventSource.ts +++ b/Logic/UIEventSource.ts @@ -230,10 +230,12 @@ export abstract class Store { const newSource = new UIEventSource(this.data) + const self = this this.addCallback((latestData) => { window.setTimeout(() => { - if (this.data == latestData) { - // compare by reference + if (self.data == latestData) { + // compare by reference. + // Note that 'latestData' and 'self.data' are both from the same UIEVentSource, but both are dereferenced at a different time newSource.setData(latestData) } }, millisToStabilize) diff --git a/UI/BigComponents/FullWelcomePaneWithTabs.ts b/UI/BigComponents/FullWelcomePaneWithTabs.ts index f21e01c29..e54b2e29a 100644 --- a/UI/BigComponents/FullWelcomePaneWithTabs.ts +++ b/UI/BigComponents/FullWelcomePaneWithTabs.ts @@ -37,12 +37,13 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { featurePipeline: FeaturePipeline backgroundLayer: UIEventSource filteredLayers: UIEventSource - } & UserRelatedState + } & UserRelatedState, + guistate?: { userInfoIsOpened: UIEventSource } ) { const layoutToUse = state.layoutToUse super( () => layoutToUse.title.Clone(), - () => FullWelcomePaneWithTabs.GenerateContents(state, currentTab, isShown), + () => FullWelcomePaneWithTabs.GenerateContents(state, currentTab, isShown, guistate), "welcome", isShown ) @@ -60,12 +61,13 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { filteredLayers: UIEventSource } & UserRelatedState, isShown: UIEventSource, - currentTab: UIEventSource + currentTab: UIEventSource, + guistate?: { userInfoIsOpened: UIEventSource } ): { header: string | BaseUIElement; content: BaseUIElement }[] { const tabs: { header: string | BaseUIElement; content: BaseUIElement }[] = [ { header: ``, - content: new ThemeIntroductionPanel(isShown, currentTab, state), + content: new ThemeIntroductionPanel(isShown, currentTab, state, guistate), }, ] @@ -113,11 +115,12 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen { filteredLayers: UIEventSource } & UserRelatedState, currentTab: UIEventSource, - isShown: UIEventSource + isShown: UIEventSource, + guistate?: { userInfoIsOpened: UIEventSource } ) { - const tabs = FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown, currentTab) + const tabs = FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown, currentTab, guistate) const tabsWithAboutMc = [ - ...FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown, currentTab), + ...FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown, currentTab, guistate), ] tabsWithAboutMc.push({ diff --git a/UI/BigComponents/ThemeIntroductionPanel.ts b/UI/BigComponents/ThemeIntroductionPanel.ts index 75b4fd1e2..09669d693 100644 --- a/UI/BigComponents/ThemeIntroductionPanel.ts +++ b/UI/BigComponents/ThemeIntroductionPanel.ts @@ -9,6 +9,7 @@ import Svg from "../../Svg" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" import { OsmConnection } from "../../Logic/Osm/OsmConnection" import FullWelcomePaneWithTabs from "./FullWelcomePaneWithTabs" +import LoggedInUserIndicator from "../LoggedInUserIndicator" export default class ThemeIntroductionPanel extends Combine { constructor( @@ -20,7 +21,8 @@ export default class ThemeIntroductionPanel extends Combine { featureSwitchUserbadge: UIEventSource layoutToUse: LayoutConfig osmConnection: OsmConnection - } + }, + guistate?: { userInfoIsOpened: UIEventSource } ) { const t = Translations.t.general const layout = state.layoutToUse @@ -36,9 +38,18 @@ export default class ThemeIntroductionPanel extends Combine { }) .SetClass("only-on-mobile") + const loggedInUserInfo = new LoggedInUserIndicator(state.osmConnection, { + firstLine: Translations.t.general.welcomeBack.Clone(), + }) + if (guistate?.userInfoIsOpened) { + loggedInUserInfo.onClick(() => { + guistate.userInfoIsOpened.setData(true) + }) + } + const loginStatus = new Toggle( new LoginToggle( - undefined, + loggedInUserInfo, new Combine([ Translations.t.general.loginWithOpenStreetMap.SetClass("text-xl font-bold"), Translations.t.general.loginOnlyNeededToEdit.Clone().SetClass("font-bold"), @@ -60,7 +71,7 @@ export default class ThemeIntroductionPanel extends Combine { ]).SetClass("flex flex-col mt-2"), toTheMap, - loginStatus.SetClass("block"), + loginStatus.SetClass("block mt-6 pt-2 md:border-t-2 border-dotted border-gray-400"), layout.descriptionTail?.Clone().SetClass("block mt-4"), languagePicker?.SetClass("block mt-4"), diff --git a/UI/BigComponents/UserInformation.ts b/UI/BigComponents/UserInformation.ts index 5f70fc228..25523b32d 100644 --- a/UI/BigComponents/UserInformation.ts +++ b/UI/BigComponents/UserInformation.ts @@ -140,12 +140,17 @@ class UserInformationMainPanel extends Combine { } export default class UserInformationPanel extends ScrollableFullScreen { - constructor(state: { - layoutToUse: LayoutConfig - osmConnection: OsmConnection - locationControl: UIEventSource - }) { - const isOpened = new UIEventSource(false) + constructor( + state: { + layoutToUse: LayoutConfig + osmConnection: OsmConnection + locationControl: UIEventSource + }, + options?: { + isOpened?: UIEventSource + } + ) { + const isOpened = options?.isOpened ?? new UIEventSource(false) super( () => { return new VariableUiElement( diff --git a/UI/DefaultGUI.ts b/UI/DefaultGUI.ts index 7497da269..b0f319ff8 100644 --- a/UI/DefaultGUI.ts +++ b/UI/DefaultGUI.ts @@ -33,7 +33,6 @@ import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler" import { GeoLocationState } from "../Logic/State/GeoLocationState" import Hotkeys from "./Base/Hotkeys" import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers" -import { Translation } from "./i18n/Translation" /** * The default MapComplete GUI initializer @@ -205,7 +204,9 @@ export default class DefaultGUI { const self = this new Combine([ Toggle.If(state.featureSwitchUserbadge, () => { - const userInfo = new UserInformationPanel(state) + const userInfo = new UserInformationPanel(state, { + isOpened: guiState.userInfoIsOpened, + }) const mapControl = new MapControlButton( new VariableUiElement( @@ -219,7 +220,7 @@ export default class DefaultGUI { { dontStyle: true, } - ).onClick(() => userInfo.Activate()) + ).onClick(() => guiState.userInfoIsOpened.setData(true)) return new LoginToggle( mapControl, @@ -292,7 +293,12 @@ export default class DefaultGUI { private InitWelcomeMessage(): BaseUIElement { const isOpened = this.guiState.welcomeMessageIsOpened - new FullWelcomePaneWithTabs(isOpened, this.guiState.welcomeMessageOpenedTab, this.state) + new FullWelcomePaneWithTabs( + isOpened, + this.guiState.welcomeMessageOpenedTab, + this.state, + this.guiState + ) // ?-Button on Desktop, opens panel with close-X. const help = new MapControlButton(Svg.help_svg()) diff --git a/UI/DefaultGuiState.ts b/UI/DefaultGuiState.ts index ccdfe323e..43c2c94de 100644 --- a/UI/DefaultGuiState.ts +++ b/UI/DefaultGuiState.ts @@ -9,6 +9,7 @@ export class DefaultGuiState { public readonly filterViewIsOpened: UIEventSource public readonly copyrightViewIsOpened: UIEventSource public readonly currentViewControlIsOpened: UIEventSource + public readonly userInfoIsOpened: UIEventSource public readonly welcomeMessageOpenedTab: UIEventSource public readonly allFullScreenStates: UIEventSource[] = [] @@ -43,8 +44,14 @@ export class DefaultGuiState { this.currentViewControlIsOpened = QueryParameters.GetBooleanQueryParameter( "currentview-toggle", false, - "Whether or not the current view box is shown" + "Whether or not the current view box is shown (metalayer showing current view, allows to do calculate stats for all in view)" ) + this.userInfoIsOpened = QueryParameters.GetBooleanQueryParameter( + "userinfo-toggle", + false, + "Whether or not the user info is shown" + ) + const states = { download: this.downloadControlIsOpened, filters: this.filterViewIsOpened, @@ -66,7 +73,8 @@ export class DefaultGuiState { this.filterViewIsOpened, this.copyrightViewIsOpened, this.welcomeMessageIsOpened, - this.currentViewControlIsOpened + this.currentViewControlIsOpened, + this.userInfoIsOpened ) for (let i = 0; i < this.allFullScreenStates.length; i++) { diff --git a/UI/LoggedInUserIndicator.ts b/UI/LoggedInUserIndicator.ts new file mode 100644 index 000000000..4d0ca0ac4 --- /dev/null +++ b/UI/LoggedInUserIndicator.ts @@ -0,0 +1,42 @@ +import { VariableUiElement } from "./Base/VariableUIElement" +import { OsmConnection } from "../Logic/Osm/OsmConnection" +import Svg from "../Svg" +import Img from "./Base/Img" +import Combine from "./Base/Combine" +import { FixedUiElement } from "./Base/FixedUiElement" +import BaseUIElement from "./BaseUIElement" + +export default class LoggedInUserIndicator extends VariableUiElement { + constructor( + osmConnection: OsmConnection, + options?: { + size?: "small" | "medium" | "large" + firstLine?: BaseUIElement + } + ) { + options = options ?? {} + let size = "w-8 h-8 mr-2" + if (options.size == "medium") { + size = "w-16 h-16 mr-4" + } else if (options.size == "large") { + size = "w-32 h-32 mr-6" + } + super( + osmConnection.userDetails.mapD((ud) => { + let img = Svg.person_svg().SetClass( + "rounded-full border border-black overflow-hidden" + ) + if (ud.img) { + img = new Img(ud.img) + } + let contents: BaseUIElement = new FixedUiElement(ud.name).SetClass("font-bold") + if (options?.firstLine) { + contents = new Combine([options.firstLine, contents]).SetClass("flex flex-col") + } + return new Combine([img.SetClass("rounded-full " + size), contents]).SetClass( + "flex items-center" + ) + }) + ) + } +} diff --git a/css/index-tailwind-output.css b/css/index-tailwind-output.css index e76ec2439..e67dc4498 100644 --- a/css/index-tailwind-output.css +++ b/css/index-tailwind-output.css @@ -624,6 +624,10 @@ video { position: relative; } +.\!relative { + position: relative !important; +} + .sticky { position: -webkit-sticky; position: sticky; @@ -807,18 +811,22 @@ video { margin-top: 0.25rem; } -.mt-4 { - margin-top: 1rem; -} - -.ml-3 { - margin-left: 0.75rem; -} - .mr-2 { margin-right: 0.5rem; } +.mr-4 { + margin-right: 1rem; +} + +.mr-6 { + margin-right: 1.5rem; +} + +.mt-4 { + margin-top: 1rem; +} + .ml-4 { margin-left: 1rem; } @@ -827,10 +835,6 @@ video { margin-bottom: 6rem; } -.mr-4 { - margin-right: 1rem; -} - .mb-2 { margin-bottom: 0.5rem; } @@ -839,6 +843,10 @@ video { margin-left: 0.5rem; } +.ml-3 { + margin-left: 0.75rem; +} + .ml-12 { margin-left: 3rem; } @@ -960,6 +968,18 @@ video { height: 16rem; } +.h-8 { + height: 2rem; +} + +.h-16 { + height: 4rem; +} + +.h-32 { + height: 8rem; +} + .h-10 { height: 2.5rem; } @@ -988,22 +1008,10 @@ video { height: 1.5rem; } -.h-8 { - height: 2rem; -} - -.h-32 { - height: 8rem; -} - .h-96 { height: 24rem; } -.h-16 { - height: 4rem; -} - .h-0 { height: 0px; } @@ -1048,6 +1056,18 @@ video { width: 1.5rem; } +.w-8 { + width: 2rem; +} + +.w-16 { + width: 4rem; +} + +.w-32 { + width: 8rem; +} + .w-10 { width: 2.5rem; } @@ -1072,10 +1092,6 @@ video { width: 2.75rem; } -.w-8 { - width: 2rem; -} - .w-min { width: -webkit-min-content; width: min-content; @@ -1090,14 +1106,6 @@ video { width: 24rem; } -.w-32 { - width: 8rem; -} - -.w-16 { - width: 4rem; -} - .w-auto { width: auto; } @@ -1374,6 +1382,10 @@ video { border-bottom-width: 1px; } +.border-dotted { + border-style: dotted; +} + .border-black { --tw-border-opacity: 1; border-color: rgb(0 0 0 / var(--tw-border-opacity)); @@ -2738,6 +2750,10 @@ input { border-radius: 0.75rem; } + .md\:border-t-2 { + border-top-width: 2px; + } + .md\:p-1 { padding: 0.25rem; } diff --git a/langs/en.json b/langs/en.json index 0d63cc6f8..a67879fcf 100644 --- a/langs/en.json +++ b/langs/en.json @@ -327,7 +327,7 @@ "tuesday": "Tuesday", "wednesday": "Wednesday" }, - "welcomeBack": "You are logged in, welcome back!", + "welcomeBack": "Welcome back!", "welcomeExplanation": { "addNew": "Tap the map to add a new POI.", "browseMoreMaps": "Discover more maps",