forked from MapComplete/MapComplete
User flow improvements for the theme introduction panel
This commit is contained in:
parent
b4f739e506
commit
98866b4a57
10 changed files with 156 additions and 62 deletions
|
@ -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 =
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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++) {
|
||||
|
|
42
UI/LoggedInUserIndicator.ts
Normal file
42
UI/LoggedInUserIndicator.ts
Normal 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"
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue