mirror of
https://github.com/standardnotes/app
synced 2026-07-14 00:03:20 -04:00
* wip: command palette * use code instead of key * show recent items above commands * refactor * fix command * add placeholder * Tab/Shift-Tab to switch tabs * Fix test * Add menu item to general account menu * if shortcut_id is available, use that as the id * make toggle fn more stable * small naming changes * fix name * Close open modals and popovers when opening command palette * use stable ids + make sure selectedNotesCount only changes when the count actually changes * display all commands, even ones in recents list
240 lines
9.1 KiB
TypeScript
240 lines
9.1 KiB
TypeScript
import { observer } from 'mobx-react-lite'
|
||
import Icon from '@/Components/Icon/Icon'
|
||
import { SyncQueueStrategy } from '@standardnotes/snjs'
|
||
import { STRING_GENERIC_SYNC_ERROR } from '@/Constants/Strings'
|
||
import { useCallback, useMemo, useState, FunctionComponent } from 'react'
|
||
import { AccountMenuPane } from './AccountMenuPane'
|
||
import Menu from '@/Components/Menu/Menu'
|
||
import MenuItem from '@/Components/Menu/MenuItem'
|
||
import WorkspaceSwitcherOption from './WorkspaceSwitcher/WorkspaceSwitcherOption'
|
||
import { WebApplicationGroup } from '@/Application/WebApplicationGroup'
|
||
import { formatLastSyncDate } from '@/Utils/DateUtils'
|
||
import Spinner from '@/Components/Spinner/Spinner'
|
||
import { MenuItemIconSize } from '@/Constants/TailwindClassNames'
|
||
import { useApplication } from '../ApplicationProvider'
|
||
import MenuSection from '../Menu/MenuSection'
|
||
import { TOGGLE_COMMAND_PALETTE, TOGGLE_KEYBOARD_SHORTCUTS_MODAL, isMobilePlatform } from '@standardnotes/ui-services'
|
||
import { KeyboardShortcutIndicator } from '../KeyboardShortcutIndicator/KeyboardShortcutIndicator'
|
||
|
||
type Props = {
|
||
mainApplicationGroup: WebApplicationGroup
|
||
setMenuPane: (pane: AccountMenuPane) => void
|
||
closeMenu: () => void
|
||
}
|
||
|
||
const iconClassName = `text-neutral mr-2 ${MenuItemIconSize}`
|
||
|
||
const GeneralAccountMenu: FunctionComponent<Props> = ({ setMenuPane, closeMenu, mainApplicationGroup }) => {
|
||
const application = useApplication()
|
||
|
||
const [isSyncingInProgress, setIsSyncingInProgress] = useState(false)
|
||
const [lastSyncDate, setLastSyncDate] = useState(formatLastSyncDate(application.sync.getLastSyncDate() as Date))
|
||
|
||
const doSynchronization = useCallback(async () => {
|
||
setIsSyncingInProgress(true)
|
||
|
||
application.sync
|
||
.sync({
|
||
queueStrategy: SyncQueueStrategy.ForceSpawnNew,
|
||
checkIntegrity: true,
|
||
})
|
||
.then((res) => {
|
||
if (res && (res as any).error) {
|
||
throw new Error()
|
||
} else {
|
||
setLastSyncDate(formatLastSyncDate(application.sync.getLastSyncDate() as Date))
|
||
}
|
||
})
|
||
.catch(() => {
|
||
application.alerts.alert(STRING_GENERIC_SYNC_ERROR).catch(console.error)
|
||
})
|
||
.finally(() => {
|
||
setIsSyncingInProgress(false)
|
||
})
|
||
}, [application])
|
||
|
||
const user = useMemo(() => application.sessions.getUser(), [application])
|
||
|
||
const openPreferences = useCallback(() => {
|
||
application.accountMenuController.closeAccountMenu()
|
||
application.preferencesController.setCurrentPane('account')
|
||
application.preferencesController.openPreferences()
|
||
}, [application])
|
||
|
||
const openHelp = useCallback(() => {
|
||
application.accountMenuController.closeAccountMenu()
|
||
application.preferencesController.setCurrentPane('help-feedback')
|
||
application.preferencesController.openPreferences()
|
||
}, [application])
|
||
|
||
const openEmail = useCallback(() => {
|
||
const subject = 'Standard Notes Feedback'
|
||
|
||
const body = `App Version: ${application.version}`
|
||
|
||
application.device.openUrl(
|
||
`mailto:[email protected]?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`,
|
||
)
|
||
}, [application.device, application.version])
|
||
|
||
const signOut = useCallback(() => {
|
||
application.accountMenuController.setSigningOut(true)
|
||
}, [application])
|
||
|
||
const activateRegisterPane = useCallback(() => {
|
||
setMenuPane(AccountMenuPane.Register)
|
||
}, [setMenuPane])
|
||
|
||
const activateSignInPane = useCallback(() => {
|
||
setMenuPane(AccountMenuPane.SignIn)
|
||
}, [setMenuPane])
|
||
|
||
const CREATE_ACCOUNT_INDEX = 1
|
||
const SWITCHER_INDEX = 0
|
||
|
||
const keyboardShortcutsHelpShortcut = useMemo(() => {
|
||
return application.keyboardService.keyboardShortcutForCommand(TOGGLE_KEYBOARD_SHORTCUTS_MODAL)
|
||
}, [application.keyboardService])
|
||
const commandPaletteShortcut = useMemo(() => {
|
||
return application.keyboardService.keyboardShortcutForCommand(TOGGLE_COMMAND_PALETTE)
|
||
}, [application.keyboardService])
|
||
|
||
return (
|
||
<>
|
||
<div className="mb-1 mt-1 hidden items-center justify-between px-4 md:flex md:px-3">
|
||
<div className="text-lg font-bold lg:text-base">Account</div>
|
||
<div className="flex cursor-pointer" onClick={closeMenu}>
|
||
<Icon type="close" className="text-neutral" />
|
||
</div>
|
||
</div>
|
||
{user ? (
|
||
<>
|
||
<div className="mb-3 px-4 text-lg text-foreground md:px-3 lg:text-sm">
|
||
<div>You're signed in as:</div>
|
||
<div className="wrap my-0.5 font-bold">{user.email}</div>
|
||
<span className="text-neutral">{application.getHost.execute().getValue()}</span>
|
||
</div>
|
||
<div className="mb-2 flex items-start justify-between px-4 text-mobile-menu-item md:px-3 md:text-tablet-menu-item lg:text-menu-item">
|
||
{isSyncingInProgress ? (
|
||
<div className="flex items-center font-semibold text-info">
|
||
<Spinner className="mr-2 h-5 w-5" />
|
||
Syncing...
|
||
</div>
|
||
) : (
|
||
<div className="flex items-start">
|
||
<Icon type="check-circle" className={`mr-2 text-success ${MenuItemIconSize}`} />
|
||
<div>
|
||
<div className="font-semibold text-success">Last synced:</div>
|
||
<div className="text-text">{lastSyncDate}</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
<div className="flex cursor-pointer text-passive-1" onClick={doSynchronization}>
|
||
<Icon type="sync" className={`${MenuItemIconSize}`} />
|
||
</div>
|
||
</div>
|
||
</>
|
||
) : (
|
||
<>
|
||
<div className="mb-1 px-4 md:px-3">
|
||
<div className="mb-3 text-base text-foreground lg:text-sm">
|
||
You’re offline. Sign in to sync your notes and preferences across all your devices and enable end-to-end
|
||
encryption.
|
||
</div>
|
||
<div className="flex items-center text-passive-1">
|
||
<Icon type="cloud-off" className={`mr-2 ${MenuItemIconSize}`} />
|
||
<span className="text-lg font-semibold lg:text-sm">Offline</span>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)}
|
||
<Menu
|
||
a11yLabel="General account menu"
|
||
closeMenu={closeMenu}
|
||
initialFocus={!application.hasAccount() ? CREATE_ACCOUNT_INDEX : SWITCHER_INDEX}
|
||
>
|
||
<MenuSection className="md:border-t md:pt-2">
|
||
<WorkspaceSwitcherOption mainApplicationGroup={mainApplicationGroup} />
|
||
</MenuSection>
|
||
<MenuSection>
|
||
{user ? (
|
||
<MenuItem onClick={openPreferences}>
|
||
<Icon type="user" className={iconClassName} />
|
||
Account settings
|
||
</MenuItem>
|
||
) : (
|
||
<>
|
||
<MenuItem onClick={activateRegisterPane}>
|
||
<Icon type="user" className={iconClassName} />
|
||
Create free account
|
||
</MenuItem>
|
||
<MenuItem onClick={activateSignInPane}>
|
||
<Icon type="signIn" className={iconClassName} />
|
||
Sign in
|
||
</MenuItem>
|
||
</>
|
||
)}
|
||
<MenuItem
|
||
onClick={() => {
|
||
application.importModalController.setIsVisible(true)
|
||
application.accountMenuController.closeAccountMenu()
|
||
}}
|
||
>
|
||
<Icon type="archive" className={iconClassName} />
|
||
Import
|
||
</MenuItem>
|
||
{application.isNativeMobileWeb() && (
|
||
<MenuItem onClick={openEmail}>
|
||
<Icon type="email-filled" className={iconClassName} />
|
||
Email us
|
||
</MenuItem>
|
||
)}
|
||
<MenuItem className="justify-between" onClick={openHelp}>
|
||
<div className="flex items-center">
|
||
<Icon type="help" className={iconClassName} />
|
||
Help & feedback
|
||
</div>
|
||
<span className="text-neutral">v{application.version}</span>
|
||
</MenuItem>
|
||
{!isMobilePlatform(application.platform) && (
|
||
<>
|
||
<MenuItem
|
||
onClick={() => {
|
||
application.keyboardService.triggerCommand(TOGGLE_KEYBOARD_SHORTCUTS_MODAL)
|
||
}}
|
||
>
|
||
<Icon type="keyboard" className={iconClassName} />
|
||
Keyboard shortcuts
|
||
{keyboardShortcutsHelpShortcut && (
|
||
<KeyboardShortcutIndicator shortcut={keyboardShortcutsHelpShortcut} className="ml-auto" />
|
||
)}
|
||
</MenuItem>
|
||
<MenuItem
|
||
onClick={() => {
|
||
application.keyboardService.triggerCommand(TOGGLE_COMMAND_PALETTE)
|
||
}}
|
||
>
|
||
<Icon type="info" className={iconClassName} />
|
||
Command palette
|
||
{commandPaletteShortcut && (
|
||
<KeyboardShortcutIndicator shortcut={commandPaletteShortcut} className="ml-auto" />
|
||
)}
|
||
</MenuItem>
|
||
</>
|
||
)}
|
||
</MenuSection>
|
||
{user ? (
|
||
<MenuSection>
|
||
<MenuItem onClick={signOut}>
|
||
<Icon type="signOut" className={iconClassName} />
|
||
Sign out workspace
|
||
</MenuItem>
|
||
</MenuSection>
|
||
) : null}
|
||
</Menu>
|
||
</>
|
||
)
|
||
}
|
||
|
||
export default observer(GeneralAccountMenu)
|