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 readonly _singlePage: boolean
 | 
				
			||||||
    private isChecking = false
 | 
					    private isChecking = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(options: {
 | 
					    constructor(options?: {
 | 
				
			||||||
        dryRun?: UIEventSource<boolean>
 | 
					        dryRun?: UIEventSource<boolean>
 | 
				
			||||||
        fakeUser?: false | boolean
 | 
					        fakeUser?: false | boolean
 | 
				
			||||||
        oauth_token?: UIEventSource<string>
 | 
					        oauth_token?: UIEventSource<string>
 | 
				
			||||||
| 
						 | 
					@ -71,6 +71,7 @@ export class OsmConnection {
 | 
				
			||||||
        osmConfiguration?: "osm" | "osm-test"
 | 
					        osmConfiguration?: "osm" | "osm-test"
 | 
				
			||||||
        attemptLogin?: true | boolean
 | 
					        attemptLogin?: true | boolean
 | 
				
			||||||
    }) {
 | 
					    }) {
 | 
				
			||||||
 | 
					        options = options ?? {}
 | 
				
			||||||
        this.fakeUser = options.fakeUser ?? false
 | 
					        this.fakeUser = options.fakeUser ?? false
 | 
				
			||||||
        this._singlePage = options.singlePage ?? true
 | 
					        this._singlePage = options.singlePage ?? true
 | 
				
			||||||
        this._oauth_config =
 | 
					        this._oauth_config =
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -230,10 +230,12 @@ export abstract class Store<T> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const newSource = new UIEventSource<T>(this.data)
 | 
					        const newSource = new UIEventSource<T>(this.data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const self = this
 | 
				
			||||||
        this.addCallback((latestData) => {
 | 
					        this.addCallback((latestData) => {
 | 
				
			||||||
            window.setTimeout(() => {
 | 
					            window.setTimeout(() => {
 | 
				
			||||||
                if (this.data == latestData) {
 | 
					                if (self.data == latestData) {
 | 
				
			||||||
                    // compare by reference
 | 
					                    // 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)
 | 
					                    newSource.setData(latestData)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }, millisToStabilize)
 | 
					            }, millisToStabilize)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,12 +37,13 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
 | 
				
			||||||
            featurePipeline: FeaturePipeline
 | 
					            featurePipeline: FeaturePipeline
 | 
				
			||||||
            backgroundLayer: UIEventSource<BaseLayer>
 | 
					            backgroundLayer: UIEventSource<BaseLayer>
 | 
				
			||||||
            filteredLayers: UIEventSource<FilteredLayer[]>
 | 
					            filteredLayers: UIEventSource<FilteredLayer[]>
 | 
				
			||||||
        } & UserRelatedState
 | 
					        } & UserRelatedState,
 | 
				
			||||||
 | 
					        guistate?: { userInfoIsOpened: UIEventSource<boolean> }
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        const layoutToUse = state.layoutToUse
 | 
					        const layoutToUse = state.layoutToUse
 | 
				
			||||||
        super(
 | 
					        super(
 | 
				
			||||||
            () => layoutToUse.title.Clone(),
 | 
					            () => layoutToUse.title.Clone(),
 | 
				
			||||||
            () => FullWelcomePaneWithTabs.GenerateContents(state, currentTab, isShown),
 | 
					            () => FullWelcomePaneWithTabs.GenerateContents(state, currentTab, isShown, guistate),
 | 
				
			||||||
            "welcome",
 | 
					            "welcome",
 | 
				
			||||||
            isShown
 | 
					            isShown
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
| 
						 | 
					@ -60,12 +61,13 @@ export default class FullWelcomePaneWithTabs extends ScrollableFullScreen {
 | 
				
			||||||
            filteredLayers: UIEventSource<FilteredLayer[]>
 | 
					            filteredLayers: UIEventSource<FilteredLayer[]>
 | 
				
			||||||
        } & UserRelatedState,
 | 
					        } & UserRelatedState,
 | 
				
			||||||
        isShown: UIEventSource<boolean>,
 | 
					        isShown: UIEventSource<boolean>,
 | 
				
			||||||
        currentTab: UIEventSource<number>
 | 
					        currentTab: UIEventSource<number>,
 | 
				
			||||||
 | 
					        guistate?: { userInfoIsOpened: UIEventSource<boolean> }
 | 
				
			||||||
    ): { header: string | BaseUIElement; content: BaseUIElement }[] {
 | 
					    ): { header: string | BaseUIElement; content: BaseUIElement }[] {
 | 
				
			||||||
        const tabs: { header: string | BaseUIElement; content: BaseUIElement }[] = [
 | 
					        const tabs: { header: string | BaseUIElement; content: BaseUIElement }[] = [
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                header: `<img src='${state.layoutToUse.icon}'>`,
 | 
					                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[]>
 | 
					            filteredLayers: UIEventSource<FilteredLayer[]>
 | 
				
			||||||
        } & UserRelatedState,
 | 
					        } & UserRelatedState,
 | 
				
			||||||
        currentTab: UIEventSource<number>,
 | 
					        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 = [
 | 
					        const tabsWithAboutMc = [
 | 
				
			||||||
            ...FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown, currentTab),
 | 
					            ...FullWelcomePaneWithTabs.ConstructBaseTabs(state, isShown, currentTab, guistate),
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        tabsWithAboutMc.push({
 | 
					        tabsWithAboutMc.push({
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@ import Svg from "../../Svg"
 | 
				
			||||||
import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
 | 
					import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig"
 | 
				
			||||||
import { OsmConnection } from "../../Logic/Osm/OsmConnection"
 | 
					import { OsmConnection } from "../../Logic/Osm/OsmConnection"
 | 
				
			||||||
import FullWelcomePaneWithTabs from "./FullWelcomePaneWithTabs"
 | 
					import FullWelcomePaneWithTabs from "./FullWelcomePaneWithTabs"
 | 
				
			||||||
 | 
					import LoggedInUserIndicator from "../LoggedInUserIndicator"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class ThemeIntroductionPanel extends Combine {
 | 
					export default class ThemeIntroductionPanel extends Combine {
 | 
				
			||||||
    constructor(
 | 
					    constructor(
 | 
				
			||||||
| 
						 | 
					@ -20,7 +21,8 @@ export default class ThemeIntroductionPanel extends Combine {
 | 
				
			||||||
            featureSwitchUserbadge: UIEventSource<boolean>
 | 
					            featureSwitchUserbadge: UIEventSource<boolean>
 | 
				
			||||||
            layoutToUse: LayoutConfig
 | 
					            layoutToUse: LayoutConfig
 | 
				
			||||||
            osmConnection: OsmConnection
 | 
					            osmConnection: OsmConnection
 | 
				
			||||||
        }
 | 
					        },
 | 
				
			||||||
 | 
					        guistate?: { userInfoIsOpened: UIEventSource<boolean> }
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        const t = Translations.t.general
 | 
					        const t = Translations.t.general
 | 
				
			||||||
        const layout = state.layoutToUse
 | 
					        const layout = state.layoutToUse
 | 
				
			||||||
| 
						 | 
					@ -36,9 +38,18 @@ export default class ThemeIntroductionPanel extends Combine {
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .SetClass("only-on-mobile")
 | 
					            .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(
 | 
					        const loginStatus = new Toggle(
 | 
				
			||||||
            new LoginToggle(
 | 
					            new LoginToggle(
 | 
				
			||||||
                undefined,
 | 
					                loggedInUserInfo,
 | 
				
			||||||
                new Combine([
 | 
					                new Combine([
 | 
				
			||||||
                    Translations.t.general.loginWithOpenStreetMap.SetClass("text-xl font-bold"),
 | 
					                    Translations.t.general.loginWithOpenStreetMap.SetClass("text-xl font-bold"),
 | 
				
			||||||
                    Translations.t.general.loginOnlyNeededToEdit.Clone().SetClass("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"),
 | 
					            ]).SetClass("flex flex-col mt-2"),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            toTheMap,
 | 
					            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"),
 | 
					            layout.descriptionTail?.Clone().SetClass("block mt-4"),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            languagePicker?.SetClass("block mt-4"),
 | 
					            languagePicker?.SetClass("block mt-4"),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -140,12 +140,17 @@ class UserInformationMainPanel extends Combine {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class UserInformationPanel extends ScrollableFullScreen {
 | 
					export default class UserInformationPanel extends ScrollableFullScreen {
 | 
				
			||||||
    constructor(state: {
 | 
					    constructor(
 | 
				
			||||||
        layoutToUse: LayoutConfig
 | 
					        state: {
 | 
				
			||||||
        osmConnection: OsmConnection
 | 
					            layoutToUse: LayoutConfig
 | 
				
			||||||
        locationControl: UIEventSource<Loc>
 | 
					            osmConnection: OsmConnection
 | 
				
			||||||
    }) {
 | 
					            locationControl: UIEventSource<Loc>
 | 
				
			||||||
        const isOpened = new UIEventSource<boolean>(false)
 | 
					        },
 | 
				
			||||||
 | 
					        options?: {
 | 
				
			||||||
 | 
					            isOpened?: UIEventSource<boolean>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        const isOpened = options?.isOpened ?? new UIEventSource<boolean>(false)
 | 
				
			||||||
        super(
 | 
					        super(
 | 
				
			||||||
            () => {
 | 
					            () => {
 | 
				
			||||||
                return new VariableUiElement(
 | 
					                return new VariableUiElement(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,7 +33,6 @@ import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"
 | 
				
			||||||
import { GeoLocationState } from "../Logic/State/GeoLocationState"
 | 
					import { GeoLocationState } from "../Logic/State/GeoLocationState"
 | 
				
			||||||
import Hotkeys from "./Base/Hotkeys"
 | 
					import Hotkeys from "./Base/Hotkeys"
 | 
				
			||||||
import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers"
 | 
					import AvailableBaseLayers from "../Logic/Actors/AvailableBaseLayers"
 | 
				
			||||||
import { Translation } from "./i18n/Translation"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * The default MapComplete GUI initializer
 | 
					 * The default MapComplete GUI initializer
 | 
				
			||||||
| 
						 | 
					@ -205,7 +204,9 @@ export default class DefaultGUI {
 | 
				
			||||||
        const self = this
 | 
					        const self = this
 | 
				
			||||||
        new Combine([
 | 
					        new Combine([
 | 
				
			||||||
            Toggle.If(state.featureSwitchUserbadge, () => {
 | 
					            Toggle.If(state.featureSwitchUserbadge, () => {
 | 
				
			||||||
                const userInfo = new UserInformationPanel(state)
 | 
					                const userInfo = new UserInformationPanel(state, {
 | 
				
			||||||
 | 
					                    isOpened: guiState.userInfoIsOpened,
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const mapControl = new MapControlButton(
 | 
					                const mapControl = new MapControlButton(
 | 
				
			||||||
                    new VariableUiElement(
 | 
					                    new VariableUiElement(
 | 
				
			||||||
| 
						 | 
					@ -219,7 +220,7 @@ export default class DefaultGUI {
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        dontStyle: true,
 | 
					                        dontStyle: true,
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                ).onClick(() => userInfo.Activate())
 | 
					                ).onClick(() => guiState.userInfoIsOpened.setData(true))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                return new LoginToggle(
 | 
					                return new LoginToggle(
 | 
				
			||||||
                    mapControl,
 | 
					                    mapControl,
 | 
				
			||||||
| 
						 | 
					@ -292,7 +293,12 @@ export default class DefaultGUI {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private InitWelcomeMessage(): BaseUIElement {
 | 
					    private InitWelcomeMessage(): BaseUIElement {
 | 
				
			||||||
        const isOpened = this.guiState.welcomeMessageIsOpened
 | 
					        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.
 | 
					        // ?-Button on Desktop, opens panel with close-X.
 | 
				
			||||||
        const help = new MapControlButton(Svg.help_svg())
 | 
					        const help = new MapControlButton(Svg.help_svg())
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,7 @@ export class DefaultGuiState {
 | 
				
			||||||
    public readonly filterViewIsOpened: UIEventSource<boolean>
 | 
					    public readonly filterViewIsOpened: UIEventSource<boolean>
 | 
				
			||||||
    public readonly copyrightViewIsOpened: UIEventSource<boolean>
 | 
					    public readonly copyrightViewIsOpened: UIEventSource<boolean>
 | 
				
			||||||
    public readonly currentViewControlIsOpened: UIEventSource<boolean>
 | 
					    public readonly currentViewControlIsOpened: UIEventSource<boolean>
 | 
				
			||||||
 | 
					    public readonly userInfoIsOpened: UIEventSource<boolean>
 | 
				
			||||||
    public readonly welcomeMessageOpenedTab: UIEventSource<number>
 | 
					    public readonly welcomeMessageOpenedTab: UIEventSource<number>
 | 
				
			||||||
    public readonly allFullScreenStates: UIEventSource<boolean>[] = []
 | 
					    public readonly allFullScreenStates: UIEventSource<boolean>[] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,8 +44,14 @@ export class DefaultGuiState {
 | 
				
			||||||
        this.currentViewControlIsOpened = QueryParameters.GetBooleanQueryParameter(
 | 
					        this.currentViewControlIsOpened = QueryParameters.GetBooleanQueryParameter(
 | 
				
			||||||
            "currentview-toggle",
 | 
					            "currentview-toggle",
 | 
				
			||||||
            false,
 | 
					            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 = {
 | 
					        const states = {
 | 
				
			||||||
            download: this.downloadControlIsOpened,
 | 
					            download: this.downloadControlIsOpened,
 | 
				
			||||||
            filters: this.filterViewIsOpened,
 | 
					            filters: this.filterViewIsOpened,
 | 
				
			||||||
| 
						 | 
					@ -66,7 +73,8 @@ export class DefaultGuiState {
 | 
				
			||||||
            this.filterViewIsOpened,
 | 
					            this.filterViewIsOpened,
 | 
				
			||||||
            this.copyrightViewIsOpened,
 | 
					            this.copyrightViewIsOpened,
 | 
				
			||||||
            this.welcomeMessageIsOpened,
 | 
					            this.welcomeMessageIsOpened,
 | 
				
			||||||
            this.currentViewControlIsOpened
 | 
					            this.currentViewControlIsOpened,
 | 
				
			||||||
 | 
					            this.userInfoIsOpened
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (let i = 0; i < this.allFullScreenStates.length; i++) {
 | 
					        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;
 | 
					  position: relative;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.\!relative {
 | 
				
			||||||
 | 
					  position: relative !important;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.sticky {
 | 
					.sticky {
 | 
				
			||||||
  position: -webkit-sticky;
 | 
					  position: -webkit-sticky;
 | 
				
			||||||
  position: sticky;
 | 
					  position: sticky;
 | 
				
			||||||
| 
						 | 
					@ -807,18 +811,22 @@ video {
 | 
				
			||||||
  margin-top: 0.25rem;
 | 
					  margin-top: 0.25rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.mt-4 {
 | 
					 | 
				
			||||||
  margin-top: 1rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.ml-3 {
 | 
					 | 
				
			||||||
  margin-left: 0.75rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.mr-2 {
 | 
					.mr-2 {
 | 
				
			||||||
  margin-right: 0.5rem;
 | 
					  margin-right: 0.5rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mr-4 {
 | 
				
			||||||
 | 
					  margin-right: 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mr-6 {
 | 
				
			||||||
 | 
					  margin-right: 1.5rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mt-4 {
 | 
				
			||||||
 | 
					  margin-top: 1rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.ml-4 {
 | 
					.ml-4 {
 | 
				
			||||||
  margin-left: 1rem;
 | 
					  margin-left: 1rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -827,10 +835,6 @@ video {
 | 
				
			||||||
  margin-bottom: 6rem;
 | 
					  margin-bottom: 6rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.mr-4 {
 | 
					 | 
				
			||||||
  margin-right: 1rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.mb-2 {
 | 
					.mb-2 {
 | 
				
			||||||
  margin-bottom: 0.5rem;
 | 
					  margin-bottom: 0.5rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -839,6 +843,10 @@ video {
 | 
				
			||||||
  margin-left: 0.5rem;
 | 
					  margin-left: 0.5rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.ml-3 {
 | 
				
			||||||
 | 
					  margin-left: 0.75rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.ml-12 {
 | 
					.ml-12 {
 | 
				
			||||||
  margin-left: 3rem;
 | 
					  margin-left: 3rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -960,6 +968,18 @@ video {
 | 
				
			||||||
  height: 16rem;
 | 
					  height: 16rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.h-8 {
 | 
				
			||||||
 | 
					  height: 2rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.h-16 {
 | 
				
			||||||
 | 
					  height: 4rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.h-32 {
 | 
				
			||||||
 | 
					  height: 8rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.h-10 {
 | 
					.h-10 {
 | 
				
			||||||
  height: 2.5rem;
 | 
					  height: 2.5rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -988,22 +1008,10 @@ video {
 | 
				
			||||||
  height: 1.5rem;
 | 
					  height: 1.5rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.h-8 {
 | 
					 | 
				
			||||||
  height: 2rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.h-32 {
 | 
					 | 
				
			||||||
  height: 8rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.h-96 {
 | 
					.h-96 {
 | 
				
			||||||
  height: 24rem;
 | 
					  height: 24rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.h-16 {
 | 
					 | 
				
			||||||
  height: 4rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.h-0 {
 | 
					.h-0 {
 | 
				
			||||||
  height: 0px;
 | 
					  height: 0px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1048,6 +1056,18 @@ video {
 | 
				
			||||||
  width: 1.5rem;
 | 
					  width: 1.5rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.w-8 {
 | 
				
			||||||
 | 
					  width: 2rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.w-16 {
 | 
				
			||||||
 | 
					  width: 4rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.w-32 {
 | 
				
			||||||
 | 
					  width: 8rem;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.w-10 {
 | 
					.w-10 {
 | 
				
			||||||
  width: 2.5rem;
 | 
					  width: 2.5rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1072,10 +1092,6 @@ video {
 | 
				
			||||||
  width: 2.75rem;
 | 
					  width: 2.75rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.w-8 {
 | 
					 | 
				
			||||||
  width: 2rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.w-min {
 | 
					.w-min {
 | 
				
			||||||
  width: -webkit-min-content;
 | 
					  width: -webkit-min-content;
 | 
				
			||||||
  width: min-content;
 | 
					  width: min-content;
 | 
				
			||||||
| 
						 | 
					@ -1090,14 +1106,6 @@ video {
 | 
				
			||||||
  width: 24rem;
 | 
					  width: 24rem;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.w-32 {
 | 
					 | 
				
			||||||
  width: 8rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.w-16 {
 | 
					 | 
				
			||||||
  width: 4rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.w-auto {
 | 
					.w-auto {
 | 
				
			||||||
  width: auto;
 | 
					  width: auto;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1374,6 +1382,10 @@ video {
 | 
				
			||||||
  border-bottom-width: 1px;
 | 
					  border-bottom-width: 1px;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.border-dotted {
 | 
				
			||||||
 | 
					  border-style: dotted;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.border-black {
 | 
					.border-black {
 | 
				
			||||||
  --tw-border-opacity: 1;
 | 
					  --tw-border-opacity: 1;
 | 
				
			||||||
  border-color: rgb(0 0 0 / var(--tw-border-opacity));
 | 
					  border-color: rgb(0 0 0 / var(--tw-border-opacity));
 | 
				
			||||||
| 
						 | 
					@ -2738,6 +2750,10 @@ input {
 | 
				
			||||||
    border-radius: 0.75rem;
 | 
					    border-radius: 0.75rem;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .md\:border-t-2 {
 | 
				
			||||||
 | 
					    border-top-width: 2px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .md\:p-1 {
 | 
					  .md\:p-1 {
 | 
				
			||||||
    padding: 0.25rem;
 | 
					    padding: 0.25rem;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -327,7 +327,7 @@
 | 
				
			||||||
            "tuesday": "Tuesday",
 | 
					            "tuesday": "Tuesday",
 | 
				
			||||||
            "wednesday": "Wednesday"
 | 
					            "wednesday": "Wednesday"
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "welcomeBack": "You are logged in, welcome back!",
 | 
					        "welcomeBack": "Welcome back!",
 | 
				
			||||||
        "welcomeExplanation": {
 | 
					        "welcomeExplanation": {
 | 
				
			||||||
            "addNew": "Tap the map to add a new POI.",
 | 
					            "addNew": "Tap the map to add a new POI.",
 | 
				
			||||||
            "browseMoreMaps": "Discover more maps",
 | 
					            "browseMoreMaps": "Discover more maps",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue