feat: add workspace switcher to lock screen (#969)
This commit is contained in:
@@ -10,6 +10,7 @@ type Props = {
|
||||
onClick: () => void
|
||||
onDelete: () => void
|
||||
renameDescriptor: (label: string) => void
|
||||
hideOptions: boolean
|
||||
}
|
||||
|
||||
export const WorkspaceMenuItem: FunctionComponent<Props> = ({
|
||||
@@ -17,6 +18,7 @@ export const WorkspaceMenuItem: FunctionComponent<Props> = ({
|
||||
onClick,
|
||||
onDelete,
|
||||
renameDescriptor,
|
||||
hideOptions,
|
||||
}) => {
|
||||
const [isRenaming, setIsRenaming] = useState(false)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
@@ -58,7 +60,7 @@ export const WorkspaceMenuItem: FunctionComponent<Props> = ({
|
||||
) : (
|
||||
<div>{descriptor.label}</div>
|
||||
)}
|
||||
{descriptor.primary && (
|
||||
{descriptor.primary && !hideOptions && (
|
||||
<div>
|
||||
<button
|
||||
className="w-5 h-5 p-0 mr-3 border-0 bg-transparent hover:bg-contrast cursor-pointer"
|
||||
|
||||
@@ -13,10 +13,11 @@ type Props = {
|
||||
mainApplicationGroup: ApplicationGroup
|
||||
appState: AppState
|
||||
isOpen: boolean
|
||||
hideWorkspaceOptions?: boolean
|
||||
}
|
||||
|
||||
export const WorkspaceSwitcherMenu: FunctionComponent<Props> = observer(
|
||||
({ mainApplicationGroup, appState, isOpen }) => {
|
||||
({ mainApplicationGroup, appState, isOpen, hideWorkspaceOptions = false }) => {
|
||||
const [applicationDescriptors, setApplicationDescriptors] = useState<ApplicationDescriptor[]>(
|
||||
[],
|
||||
)
|
||||
@@ -37,6 +38,7 @@ export const WorkspaceSwitcherMenu: FunctionComponent<Props> = observer(
|
||||
{applicationDescriptors.map((descriptor) => (
|
||||
<WorkspaceMenuItem
|
||||
descriptor={descriptor}
|
||||
hideOptions={hideWorkspaceOptions}
|
||||
onDelete={() => {
|
||||
appState.accountMenu.setSigningOut(true)
|
||||
}}
|
||||
|
||||
@@ -193,6 +193,8 @@ export class ApplicationView extends PureComponent<Props, State> {
|
||||
<ChallengeModal
|
||||
key={challenge.id}
|
||||
application={this.application}
|
||||
appState={this.appState}
|
||||
mainApplicationGroup={this.props.mainApplicationGroup}
|
||||
challenge={challenge}
|
||||
onDismiss={this.removeChallenge}
|
||||
/>
|
||||
|
||||
@@ -14,6 +14,9 @@ import { useCallback, useEffect, useState } from 'preact/hooks'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { Icon } from '@/Components/Icon'
|
||||
import { ChallengeModalPrompt } from './ChallengePrompt'
|
||||
import { LockscreenWorkspaceSwitcher } from './LockscreenWorkspaceSwitcher'
|
||||
import { ApplicationGroup } from '@/UIModels/ApplicationGroup'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
|
||||
type InputValue = {
|
||||
prompt: ChallengePrompt
|
||||
@@ -25,6 +28,8 @@ export type ChallengeModalValues = Record<ChallengePrompt['id'], InputValue>
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
appState: AppState
|
||||
mainApplicationGroup: ApplicationGroup
|
||||
challenge: Challenge
|
||||
onDismiss: (challenge: Challenge) => Promise<void>
|
||||
}
|
||||
@@ -48,7 +53,13 @@ const validateValues = (
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const ChallengeModal: FunctionComponent<Props> = ({ application, challenge, onDismiss }) => {
|
||||
export const ChallengeModal: FunctionComponent<Props> = ({
|
||||
application,
|
||||
appState,
|
||||
mainApplicationGroup,
|
||||
challenge,
|
||||
onDismiss,
|
||||
}) => {
|
||||
const [values, setValues] = useState<ChallengeModalValues>(() => {
|
||||
const values = {} as ChallengeModalValues
|
||||
for (const prompt of challenge.prompts) {
|
||||
@@ -68,6 +79,7 @@ export const ChallengeModal: FunctionComponent<Props> = ({ application, challeng
|
||||
ChallengeReason.ApplicationUnlock,
|
||||
ChallengeReason.Migration,
|
||||
].includes(challenge.reason)
|
||||
const shouldShowWorkspaceSwitcher = challenge.reason === ChallengeReason.ApplicationUnlock
|
||||
|
||||
const submit = async () => {
|
||||
const validatedValues = validateValues(values, challenge.prompts)
|
||||
@@ -252,6 +264,12 @@ export const ChallengeModal: FunctionComponent<Props> = ({ application, challeng
|
||||
Forgot passcode?
|
||||
</Button>
|
||||
)}
|
||||
{shouldShowWorkspaceSwitcher && (
|
||||
<LockscreenWorkspaceSwitcher
|
||||
mainApplicationGroup={mainApplicationGroup}
|
||||
appState={appState}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
</DialogOverlay>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { ApplicationGroup } from '@/UIModels/ApplicationGroup'
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { calculateSubmenuStyle, SubmenuStyle } from '@/Utils/CalculateSubmenuStyle'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { useEffect, useRef, useState } from 'preact/hooks'
|
||||
import { WorkspaceSwitcherMenu } from '@/Components/AccountMenu/WorkspaceSwitcher/WorkspaceSwitcherMenu'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { Icon } from '@/Components/Icon'
|
||||
import { useCloseOnClickOutside } from '@/Hooks/useCloseOnClickOutside'
|
||||
|
||||
type Props = {
|
||||
mainApplicationGroup: ApplicationGroup
|
||||
appState: AppState
|
||||
}
|
||||
|
||||
export const LockscreenWorkspaceSwitcher: FunctionComponent<Props> = ({
|
||||
mainApplicationGroup,
|
||||
appState,
|
||||
}) => {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
const menuRef = useRef<HTMLDivElement>(null)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [menuStyle, setMenuStyle] = useState<SubmenuStyle>()
|
||||
|
||||
useCloseOnClickOutside(containerRef, () => setIsOpen(false))
|
||||
|
||||
const toggleMenu = () => {
|
||||
if (!isOpen) {
|
||||
const menuPosition = calculateSubmenuStyle(buttonRef.current)
|
||||
if (menuPosition) {
|
||||
setMenuStyle(menuPosition)
|
||||
}
|
||||
}
|
||||
|
||||
setIsOpen(!isOpen)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
const timeToWaitBeforeCheckingMenuCollision = 0
|
||||
setTimeout(() => {
|
||||
const newMenuPosition = calculateSubmenuStyle(buttonRef.current, menuRef.current)
|
||||
|
||||
if (newMenuPosition) {
|
||||
setMenuStyle(newMenuPosition)
|
||||
}
|
||||
}, timeToWaitBeforeCheckingMenuCollision)
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
<Button
|
||||
ref={buttonRef}
|
||||
onClick={toggleMenu}
|
||||
className="flex items-center justify-center min-w-76 mt-2"
|
||||
>
|
||||
<Icon type="user-switch" className="color-neutral mr-2" />
|
||||
Switch workspace
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<div
|
||||
ref={menuRef}
|
||||
className="sn-dropdown max-h-120 min-w-68 py-2 fixed overflow-y-auto"
|
||||
style={menuStyle}
|
||||
>
|
||||
<WorkspaceSwitcherMenu
|
||||
mainApplicationGroup={mainApplicationGroup}
|
||||
appState={appState}
|
||||
isOpen={isOpen}
|
||||
hideWorkspaceOptions={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -6,7 +6,12 @@ import { ApplicationEvent, PrefKey } from '@standardnotes/snjs'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks'
|
||||
import { PanelSide, ResizeFinishCallback, PanelResizer, PanelResizeType } from '@/Components/PanelResizer'
|
||||
import {
|
||||
PanelSide,
|
||||
ResizeFinishCallback,
|
||||
PanelResizer,
|
||||
PanelResizeType,
|
||||
} from '@/Components/PanelResizer'
|
||||
|
||||
type Props = {
|
||||
application: WebApplication
|
||||
|
||||
@@ -10,7 +10,12 @@ import { NoAccountWarning } from '@/Components/NoAccountWarning'
|
||||
import { NotesList } from '@/Components/NotesList'
|
||||
import { NotesListOptionsMenu } from '@/Components/NotesList/NotesListOptionsMenu'
|
||||
import { SearchOptions } from '@/Components/SearchOptions'
|
||||
import { PanelSide, ResizeFinishCallback, PanelResizer, PanelResizeType } from '@/Components/PanelResizer'
|
||||
import {
|
||||
PanelSide,
|
||||
ResizeFinishCallback,
|
||||
PanelResizer,
|
||||
PanelResizeType,
|
||||
} from '@/Components/PanelResizer'
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
|
||||
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { FunctionalComponent } from 'preact'
|
||||
import { PreferencesGroup, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { OfflineSubscription } from '@/Components/Preferences/Panes/Account/OfflineSubscription'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { PreferencesGroup, PreferencesSegment, Title } from '@/Components/Preferences/PreferencesComponents'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Title,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { SubscriptionInformation } from './SubscriptionInformation'
|
||||
import { NoSubscription } from './NoSubscription'
|
||||
|
||||
@@ -17,7 +17,13 @@ import { JSXInternal } from 'preact/src/jsx'
|
||||
import TargetedEvent = JSXInternal.TargetedEvent
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { PreferencesGroup, PreferencesSegment, Title, Text, Subtitle } from '@/Components/Preferences/PreferencesComponents'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Title,
|
||||
Text,
|
||||
Subtitle,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -3,7 +3,13 @@ import { STRING_FAILED_TO_UPDATE_USER_SETTING } from '@/Strings'
|
||||
import { useCallback, useEffect, useState } from 'preact/hooks'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { PreferencesGroup, PreferencesSegment, Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Subtitle,
|
||||
Text,
|
||||
Title,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Dropdown, DropdownItem } from '@/Components/Dropdown'
|
||||
import { Switch } from '@/Components/Switch'
|
||||
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { DisplayStringForContentType, SNComponent } from '@standardnotes/snjs'
|
||||
import { Button } from '@/Components/Button/Button'
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { Title, Text, Subtitle, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
|
||||
import {
|
||||
Title,
|
||||
Text,
|
||||
Subtitle,
|
||||
PreferencesSegment,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
|
||||
export const ConfirmCustomExtension: FunctionComponent<{
|
||||
component: SNComponent
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { SNComponent } from '@standardnotes/snjs'
|
||||
import { PreferencesSegment, SubtitleLight, Title } from '@/Components/Preferences/PreferencesComponents'
|
||||
import {
|
||||
PreferencesSegment,
|
||||
SubtitleLight,
|
||||
Title,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Switch } from '@/Components/Switch'
|
||||
import { WebApplication } from '@/UIModels/Application'
|
||||
import { useState } from 'preact/hooks'
|
||||
|
||||
@@ -3,7 +3,12 @@ import { STRING_E2E_ENABLED, STRING_ENC_NOT_ENABLED, STRING_LOCAL_ENC_ENABLED }
|
||||
import { AppState } from '@/UIModels/AppState'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { ComponentChild, FunctionComponent } from 'preact'
|
||||
import { PreferencesGroup, PreferencesSegment, Text, Title } from '@/Components/Preferences/PreferencesComponents'
|
||||
import {
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
Text,
|
||||
Title,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
|
||||
const formatCount = (count: number, itemType: string) => `${count} / ${count} ${itemType}`
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { FunctionComponent } from 'preact'
|
||||
import { Title, Text, PreferencesGroup, PreferencesSegment } from '@/Components/Preferences/PreferencesComponents'
|
||||
import {
|
||||
Title,
|
||||
Text,
|
||||
PreferencesGroup,
|
||||
PreferencesSegment,
|
||||
} from '@/Components/Preferences/PreferencesComponents'
|
||||
import { Switch } from '@/Components/Switch'
|
||||
import { observer } from 'mobx-react-lite'
|
||||
import { is2FAActivation, is2FADisabled, TwoFactorAuth } from './TwoFactorAuth'
|
||||
|
||||
Reference in New Issue
Block a user