User flow improvements for the theme introduction panel

This commit is contained in:
Pieter Vander Vennet 2023-01-03 02:24:03 +01:00
parent b4f739e506
commit 98866b4a57
10 changed files with 156 additions and 62 deletions

View file

@ -62,7 +62,7 @@ export class OsmConnection {
private readonly _singlePage: boolean
private isChecking = false
constructor(options: {
constructor(options?: {
dryRun?: UIEventSource<boolean>
fakeUser?: false | boolean
oauth_token?: UIEventSource<string>
@ -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 =

View file

@ -230,10 +230,12 @@ export abstract class Store<T> {
const newSource = new UIEventSource<T>(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)

View file

@ -37,12 +37,13 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
featurePipeline: FeaturePipeline
backgroundLayer: UIEventSource<BaseLayer>
filteredLayers: UIEventSource<FilteredLayer[]>
} & UserRelatedState
} & UserRelatedState,
guistate?: { userInfoIsOpened: UIEventSource<boolean> }
) {
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<FilteredLayer[]>
} & UserRelatedState,
isShown: UIEventSource<boolean>,
currentTab: UIEventSource<number>
currentTab: UIEventSource<number>,
guistate?: { userInfoIsOpened: UIEventSource<boolean> }
): { header: string | BaseUIElement; content: BaseUIElement }[] {
const tabs: { header: string | BaseUIElement; content: BaseUIElement }[] = [
{
header: `<img src='${state.layoutToUse.icon}'>`,
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<FilteredLayer[]>
} & UserRelatedState,
currentTab: UIEventSource<number>,
isShown: UIEventSource<boolean>
isShown: UIEventSource<boolean>,
guistate?: { userInfoIsOpened: UIEventSource<boolean> }
) {
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({

View file

@ -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<boolean>
layoutToUse: LayoutConfig
osmConnection: OsmConnection
}
},
guistate?: { userInfoIsOpened: UIEventSource<boolean> }
) {
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"),

View file

@ -140,12 +140,17 @@ class UserInformationMainPanel extends Combine {
}
export default class UserInformationPanel extends ScrollableFullScreen {
constructor(state: {
layoutToUse: LayoutConfig
osmConnection: OsmConnection
locationControl: UIEventSource<Loc>
}) {
const isOpened = new UIEventSource<boolean>(false)
constructor(
state: {
layoutToUse: LayoutConfig
osmConnection: OsmConnection
locationControl: UIEventSource<Loc>
},
options?: {
isOpened?: UIEventSource<boolean>
}
) {
const isOpened = options?.isOpened ?? new UIEventSource<boolean>(false)
super(
() => {
return new VariableUiElement(

View file

@ -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())

View file

@ -9,6 +9,7 @@ export class DefaultGuiState {
public readonly filterViewIsOpened: UIEventSource<boolean>
public readonly copyrightViewIsOpened: UIEventSource<boolean>
public readonly currentViewControlIsOpened: UIEventSource<boolean>
public readonly userInfoIsOpened: UIEventSource<boolean>
public readonly welcomeMessageOpenedTab: UIEventSource<number>
public readonly allFullScreenStates: UIEventSource<boolean>[] = []
@ -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++) {

View file

@ -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"
)
})
)
}
}

View file

@ -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;
}

View file

@ -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",