>
export abstract class PureComponent extends Component
{
private unsubApp!: () => void
- private unsubState!: () => void
private reactionDisposers: IReactionDisposer[] = []
constructor(props: P, protected application: WebApplication) {
@@ -19,63 +17,34 @@ export abstract class PureComponent
void): void {
this.reactionDisposers.push(autorun(view))
}
- addAppStateObserver() {
- this.unsubState = this.application.getAppState().addObserver(async (eventName, data) => {
- this.onAppStateEvent(eventName, data)
- })
- }
-
- onAppStateEvent(_eventName: AppStateEvent, _data: unknown) {
- /** Optional override */
- }
-
addAppEventObserver() {
if (this.application.isStarted()) {
this.onAppStart().catch(console.error)
diff --git a/app/assets/javascripts/Components/AccountMenu/AccountMenu.tsx b/app/assets/javascripts/Components/AccountMenu/AccountMenu.tsx
new file mode 100644
index 000000000..0aec88f39
--- /dev/null
+++ b/app/assets/javascripts/Components/AccountMenu/AccountMenu.tsx
@@ -0,0 +1,79 @@
+import { observer } from 'mobx-react-lite'
+import { useCloseOnClickOutside } from '@/Hooks/useCloseOnClickOutside'
+import { ViewControllerManager } from '@/Services/ViewControllerManager'
+import { WebApplication } from '@/Application/Application'
+import { useCallback, useRef, FunctionComponent, KeyboardEventHandler } from 'react'
+import { ApplicationGroup } from '@/Application/ApplicationGroup'
+import { AccountMenuPane } from './AccountMenuPane'
+import MenuPaneSelector from './MenuPaneSelector'
+
+type Props = {
+ viewControllerManager: ViewControllerManager
+ application: WebApplication
+ onClickOutside: () => void
+ mainApplicationGroup: ApplicationGroup
+}
+
+const AccountMenu: FunctionComponent = ({
+ application,
+ viewControllerManager,
+ onClickOutside,
+ mainApplicationGroup,
+}) => {
+ const { currentPane, shouldAnimateCloseMenu } = viewControllerManager.accountMenuController
+
+ const closeAccountMenu = useCallback(() => {
+ viewControllerManager.accountMenuController.closeAccountMenu()
+ }, [viewControllerManager])
+
+ const setCurrentPane = useCallback(
+ (pane: AccountMenuPane) => {
+ viewControllerManager.accountMenuController.setCurrentPane(pane)
+ },
+ [viewControllerManager],
+ )
+
+ const ref = useRef(null)
+ useCloseOnClickOutside(ref, () => {
+ onClickOutside()
+ })
+
+ const handleKeyDown: KeyboardEventHandler = useCallback(
+ (event) => {
+ switch (event.key) {
+ case 'Escape':
+ if (currentPane === AccountMenuPane.GeneralMenu) {
+ closeAccountMenu()
+ } else if (currentPane === AccountMenuPane.ConfirmPassword) {
+ setCurrentPane(AccountMenuPane.Register)
+ } else {
+ setCurrentPane(AccountMenuPane.GeneralMenu)
+ }
+ break
+ }
+ },
+ [closeAccountMenu, currentPane, setCurrentPane],
+ )
+
+ return (
+
+ )
+}
+
+export default observer(AccountMenu)
diff --git a/app/assets/javascripts/Components/AccountMenu/AccountMenuPane.ts b/app/assets/javascripts/Components/AccountMenu/AccountMenuPane.ts
new file mode 100644
index 000000000..8e9c673a3
--- /dev/null
+++ b/app/assets/javascripts/Components/AccountMenu/AccountMenuPane.ts
@@ -0,0 +1,6 @@
+export enum AccountMenuPane {
+ GeneralMenu,
+ SignIn,
+ Register,
+ ConfirmPassword,
+}
diff --git a/app/assets/javascripts/Components/AccountMenu/AdvancedOptions.tsx b/app/assets/javascripts/Components/AccountMenu/AdvancedOptions.tsx
index 7aa70e5dc..474c79170 100644
--- a/app/assets/javascripts/Components/AccountMenu/AdvancedOptions.tsx
+++ b/app/assets/javascripts/Components/AccountMenu/AdvancedOptions.tsx
@@ -1,184 +1,190 @@
-import { WebApplication } from '@/UIModels/Application'
-import { AppState } from '@/UIModels/AppState'
+import { WebApplication } from '@/Application/Application'
+import { ViewControllerManager } from '@/Services/ViewControllerManager'
import { observer } from 'mobx-react-lite'
-import { FunctionComponent } from 'preact'
-import { useCallback, useEffect, useState } from 'preact/hooks'
-import { Checkbox } from '@/Components/Checkbox'
-import { DecoratedInput } from '@/Components/Input/DecoratedInput'
-import { Icon } from '@/Components/Icon'
+import { ChangeEventHandler, FunctionComponent, useCallback, useEffect, useState } from 'react'
+import Checkbox from '@/Components/Checkbox/Checkbox'
+import DecoratedInput from '@/Components/Input/DecoratedInput'
+import Icon from '@/Components/Icon/Icon'
type Props = {
application: WebApplication
- appState: AppState
+ viewControllerManager: ViewControllerManager
disabled?: boolean
onPrivateWorkspaceChange?: (isPrivate: boolean, identifier?: string) => void
onStrictSignInChange?: (isStrictSignIn: boolean) => void
}
-export const AdvancedOptions: FunctionComponent = observer(
- ({ appState, application, disabled = false, onPrivateWorkspaceChange, onStrictSignInChange, children }) => {
- const { server, setServer, enableServerOption, setEnableServerOption } = appState.accountMenu
- const [showAdvanced, setShowAdvanced] = useState(false)
+const AdvancedOptions: FunctionComponent = ({
+ viewControllerManager,
+ application,
+ disabled = false,
+ onPrivateWorkspaceChange,
+ onStrictSignInChange,
+ children,
+}) => {
+ const { server, setServer, enableServerOption, setEnableServerOption } = viewControllerManager.accountMenuController
+ const [showAdvanced, setShowAdvanced] = useState(false)
- const [isPrivateWorkspace, setIsPrivateWorkspace] = useState(false)
- const [privateWorkspaceName, setPrivateWorkspaceName] = useState('')
- const [privateWorkspaceUserphrase, setPrivateWorkspaceUserphrase] = useState('')
+ const [isPrivateWorkspace, setIsPrivateWorkspace] = useState(false)
+ const [privateWorkspaceName, setPrivateWorkspaceName] = useState('')
+ const [privateWorkspaceUserphrase, setPrivateWorkspaceUserphrase] = useState('')
- const [isStrictSignin, setIsStrictSignin] = useState(false)
+ const [isStrictSignin, setIsStrictSignin] = useState(false)
- useEffect(() => {
- const recomputePrivateWorkspaceIdentifier = async () => {
- const identifier = await application.computePrivateWorkspaceIdentifier(
- privateWorkspaceName,
- privateWorkspaceUserphrase,
- )
+ useEffect(() => {
+ const recomputePrivateWorkspaceIdentifier = async () => {
+ const identifier = await application.computePrivateWorkspaceIdentifier(
+ privateWorkspaceName,
+ privateWorkspaceUserphrase,
+ )
- if (!identifier) {
- if (privateWorkspaceName?.length > 0 && privateWorkspaceUserphrase?.length > 0) {
- application.alertService.alert('Unable to compute private workspace name.').catch(console.error)
- }
- return
+ if (!identifier) {
+ if (privateWorkspaceName?.length > 0 && privateWorkspaceUserphrase?.length > 0) {
+ application.alertService.alert('Unable to compute private workspace name.').catch(console.error)
}
- onPrivateWorkspaceChange?.(true, identifier)
+ return
}
+ onPrivateWorkspaceChange?.(true, identifier)
+ }
- if (privateWorkspaceName && privateWorkspaceUserphrase) {
- recomputePrivateWorkspaceIdentifier().catch(console.error)
+ if (privateWorkspaceName && privateWorkspaceUserphrase) {
+ recomputePrivateWorkspaceIdentifier().catch(console.error)
+ }
+ }, [privateWorkspaceName, privateWorkspaceUserphrase, application, onPrivateWorkspaceChange])
+
+ useEffect(() => {
+ onPrivateWorkspaceChange?.(isPrivateWorkspace)
+ }, [isPrivateWorkspace, onPrivateWorkspaceChange])
+
+ const handleIsPrivateWorkspaceChange = useCallback(() => {
+ setIsPrivateWorkspace(!isPrivateWorkspace)
+ }, [isPrivateWorkspace])
+
+ const handlePrivateWorkspaceNameChange = useCallback((name: string) => {
+ setPrivateWorkspaceName(name)
+ }, [])
+
+ const handlePrivateWorkspaceUserphraseChange = useCallback((userphrase: string) => {
+ setPrivateWorkspaceUserphrase(userphrase)
+ }, [])
+
+ const handleServerOptionChange: ChangeEventHandler = useCallback(
+ (e) => {
+ if (e.target instanceof HTMLInputElement) {
+ setEnableServerOption(e.target.checked)
}
- }, [privateWorkspaceName, privateWorkspaceUserphrase, application, onPrivateWorkspaceChange])
+ },
+ [setEnableServerOption],
+ )
- useEffect(() => {
- onPrivateWorkspaceChange?.(isPrivateWorkspace)
- }, [isPrivateWorkspace, onPrivateWorkspaceChange])
+ const handleSyncServerChange = useCallback(
+ (server: string) => {
+ setServer(server)
+ application.setCustomHost(server).catch(console.error)
+ },
+ [application, setServer],
+ )
- const handleIsPrivateWorkspaceChange = useCallback(() => {
- setIsPrivateWorkspace(!isPrivateWorkspace)
- }, [isPrivateWorkspace])
+ const handleStrictSigninChange = useCallback(() => {
+ const newValue = !isStrictSignin
+ setIsStrictSignin(newValue)
+ onStrictSignInChange?.(newValue)
+ }, [isStrictSignin, onStrictSignInChange])
- const handlePrivateWorkspaceNameChange = useCallback((name: string) => {
- setPrivateWorkspaceName(name)
- }, [])
+ const toggleShowAdvanced = useCallback(() => {
+ setShowAdvanced(!showAdvanced)
+ }, [showAdvanced])
- const handlePrivateWorkspaceUserphraseChange = useCallback((userphrase: string) => {
- setPrivateWorkspaceUserphrase(userphrase)
- }, [])
+ return (
+ <>
+
+ {showAdvanced ? (
+
+ {children}
- const handleServerOptionChange = useCallback(
- (e: Event) => {
- if (e.target instanceof HTMLInputElement) {
- setEnableServerOption(e.target.checked)
- }
- },
- [setEnableServerOption],
- )
-
- const handleSyncServerChange = useCallback(
- (server: string) => {
- setServer(server)
- application.setCustomHost(server).catch(console.error)
- },
- [application, setServer],
- )
-
- const handleStrictSigninChange = useCallback(() => {
- const newValue = !isStrictSignin
- setIsStrictSignin(newValue)
- onStrictSignInChange?.(newValue)
- }, [isStrictSignin, onStrictSignInChange])
-
- const toggleShowAdvanced = useCallback(() => {
- setShowAdvanced(!showAdvanced)
- }, [showAdvanced])
-
- return (
- <>
-