feat: add workspace switcher to lock screen (#969)

This commit is contained in:
Aman Harwara
2022-04-13 22:20:56 +05:30
committed by GitHub
parent 8e467f9e6d
commit 9ba7b875a8
15 changed files with 163 additions and 13 deletions

View File

@@ -10,6 +10,7 @@ type Props = {
onClick: () => void onClick: () => void
onDelete: () => void onDelete: () => void
renameDescriptor: (label: string) => void renameDescriptor: (label: string) => void
hideOptions: boolean
} }
export const WorkspaceMenuItem: FunctionComponent<Props> = ({ export const WorkspaceMenuItem: FunctionComponent<Props> = ({
@@ -17,6 +18,7 @@ export const WorkspaceMenuItem: FunctionComponent<Props> = ({
onClick, onClick,
onDelete, onDelete,
renameDescriptor, renameDescriptor,
hideOptions,
}) => { }) => {
const [isRenaming, setIsRenaming] = useState(false) const [isRenaming, setIsRenaming] = useState(false)
const inputRef = useRef<HTMLInputElement>(null) const inputRef = useRef<HTMLInputElement>(null)
@@ -58,7 +60,7 @@ export const WorkspaceMenuItem: FunctionComponent<Props> = ({
) : ( ) : (
<div>{descriptor.label}</div> <div>{descriptor.label}</div>
)} )}
{descriptor.primary && ( {descriptor.primary && !hideOptions && (
<div> <div>
<button <button
className="w-5 h-5 p-0 mr-3 border-0 bg-transparent hover:bg-contrast cursor-pointer" className="w-5 h-5 p-0 mr-3 border-0 bg-transparent hover:bg-contrast cursor-pointer"

View File

@@ -13,10 +13,11 @@ type Props = {
mainApplicationGroup: ApplicationGroup mainApplicationGroup: ApplicationGroup
appState: AppState appState: AppState
isOpen: boolean isOpen: boolean
hideWorkspaceOptions?: boolean
} }
export const WorkspaceSwitcherMenu: FunctionComponent<Props> = observer( export const WorkspaceSwitcherMenu: FunctionComponent<Props> = observer(
({ mainApplicationGroup, appState, isOpen }) => { ({ mainApplicationGroup, appState, isOpen, hideWorkspaceOptions = false }) => {
const [applicationDescriptors, setApplicationDescriptors] = useState<ApplicationDescriptor[]>( const [applicationDescriptors, setApplicationDescriptors] = useState<ApplicationDescriptor[]>(
[], [],
) )
@@ -37,6 +38,7 @@ export const WorkspaceSwitcherMenu: FunctionComponent<Props> = observer(
{applicationDescriptors.map((descriptor) => ( {applicationDescriptors.map((descriptor) => (
<WorkspaceMenuItem <WorkspaceMenuItem
descriptor={descriptor} descriptor={descriptor}
hideOptions={hideWorkspaceOptions}
onDelete={() => { onDelete={() => {
appState.accountMenu.setSigningOut(true) appState.accountMenu.setSigningOut(true)
}} }}

View File

@@ -193,6 +193,8 @@ export class ApplicationView extends PureComponent<Props, State> {
<ChallengeModal <ChallengeModal
key={challenge.id} key={challenge.id}
application={this.application} application={this.application}
appState={this.appState}
mainApplicationGroup={this.props.mainApplicationGroup}
challenge={challenge} challenge={challenge}
onDismiss={this.removeChallenge} onDismiss={this.removeChallenge}
/> />

View File

@@ -14,6 +14,9 @@ import { useCallback, useEffect, useState } from 'preact/hooks'
import { Button } from '@/Components/Button/Button' import { Button } from '@/Components/Button/Button'
import { Icon } from '@/Components/Icon' import { Icon } from '@/Components/Icon'
import { ChallengeModalPrompt } from './ChallengePrompt' import { ChallengeModalPrompt } from './ChallengePrompt'
import { LockscreenWorkspaceSwitcher } from './LockscreenWorkspaceSwitcher'
import { ApplicationGroup } from '@/UIModels/ApplicationGroup'
import { AppState } from '@/UIModels/AppState'
type InputValue = { type InputValue = {
prompt: ChallengePrompt prompt: ChallengePrompt
@@ -25,6 +28,8 @@ export type ChallengeModalValues = Record<ChallengePrompt['id'], InputValue>
type Props = { type Props = {
application: WebApplication application: WebApplication
appState: AppState
mainApplicationGroup: ApplicationGroup
challenge: Challenge challenge: Challenge
onDismiss: (challenge: Challenge) => Promise<void> onDismiss: (challenge: Challenge) => Promise<void>
} }
@@ -48,7 +53,13 @@ const validateValues = (
return undefined 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, setValues] = useState<ChallengeModalValues>(() => {
const values = {} as ChallengeModalValues const values = {} as ChallengeModalValues
for (const prompt of challenge.prompts) { for (const prompt of challenge.prompts) {
@@ -68,6 +79,7 @@ export const ChallengeModal: FunctionComponent<Props> = ({ application, challeng
ChallengeReason.ApplicationUnlock, ChallengeReason.ApplicationUnlock,
ChallengeReason.Migration, ChallengeReason.Migration,
].includes(challenge.reason) ].includes(challenge.reason)
const shouldShowWorkspaceSwitcher = challenge.reason === ChallengeReason.ApplicationUnlock
const submit = async () => { const submit = async () => {
const validatedValues = validateValues(values, challenge.prompts) const validatedValues = validateValues(values, challenge.prompts)
@@ -252,6 +264,12 @@ export const ChallengeModal: FunctionComponent<Props> = ({ application, challeng
Forgot passcode? Forgot passcode?
</Button> </Button>
)} )}
{shouldShowWorkspaceSwitcher && (
<LockscreenWorkspaceSwitcher
mainApplicationGroup={mainApplicationGroup}
appState={appState}
/>
)}
</DialogContent> </DialogContent>
</DialogOverlay> </DialogOverlay>
) )

View File

@@ -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>
)
}

View File

@@ -6,7 +6,12 @@ import { ApplicationEvent, PrefKey } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'preact' import { FunctionComponent } from 'preact'
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks' 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 = { type Props = {
application: WebApplication application: WebApplication

View File

@@ -10,7 +10,12 @@ import { NoAccountWarning } from '@/Components/NoAccountWarning'
import { NotesList } from '@/Components/NotesList' import { NotesList } from '@/Components/NotesList'
import { NotesListOptionsMenu } from '@/Components/NotesList/NotesListOptionsMenu' import { NotesListOptionsMenu } from '@/Components/NotesList/NotesListOptionsMenu'
import { SearchOptions } from '@/Components/SearchOptions' 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 { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure'
import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur' import { useCloseOnBlur } from '@/Hooks/useCloseOnBlur'

View File

@@ -1,5 +1,8 @@
import { FunctionalComponent } from 'preact' 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 { OfflineSubscription } from '@/Components/Preferences/Panes/Account/OfflineSubscription'
import { WebApplication } from '@/UIModels/Application' import { WebApplication } from '@/UIModels/Application'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'

View File

@@ -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 { WebApplication } from '@/UIModels/Application'
import { SubscriptionInformation } from './SubscriptionInformation' import { SubscriptionInformation } from './SubscriptionInformation'
import { NoSubscription } from './NoSubscription' import { NoSubscription } from './NoSubscription'

View File

@@ -17,7 +17,13 @@ import { JSXInternal } from 'preact/src/jsx'
import TargetedEvent = JSXInternal.TargetedEvent import TargetedEvent = JSXInternal.TargetedEvent
import { AppState } from '@/UIModels/AppState' import { AppState } from '@/UIModels/AppState'
import { observer } from 'mobx-react-lite' 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' import { Button } from '@/Components/Button/Button'
type Props = { type Props = {

View File

@@ -3,7 +3,13 @@ import { STRING_FAILED_TO_UPDATE_USER_SETTING } from '@/Strings'
import { useCallback, useEffect, useState } from 'preact/hooks' import { useCallback, useEffect, useState } from 'preact/hooks'
import { WebApplication } from '@/UIModels/Application' import { WebApplication } from '@/UIModels/Application'
import { observer } from 'mobx-react-lite' 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 { Dropdown, DropdownItem } from '@/Components/Dropdown'
import { Switch } from '@/Components/Switch' import { Switch } from '@/Components/Switch'
import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator' import { HorizontalSeparator } from '@/Components/Shared/HorizontalSeparator'

View File

@@ -1,7 +1,12 @@
import { DisplayStringForContentType, SNComponent } from '@standardnotes/snjs' import { DisplayStringForContentType, SNComponent } from '@standardnotes/snjs'
import { Button } from '@/Components/Button/Button' import { Button } from '@/Components/Button/Button'
import { FunctionComponent } from 'preact' 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<{ export const ConfirmCustomExtension: FunctionComponent<{
component: SNComponent component: SNComponent

View File

@@ -1,6 +1,10 @@
import { FunctionComponent } from 'preact' import { FunctionComponent } from 'preact'
import { SNComponent } from '@standardnotes/snjs' 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 { Switch } from '@/Components/Switch'
import { WebApplication } from '@/UIModels/Application' import { WebApplication } from '@/UIModels/Application'
import { useState } from 'preact/hooks' import { useState } from 'preact/hooks'

View File

@@ -3,7 +3,12 @@ import { STRING_E2E_ENABLED, STRING_ENC_NOT_ENABLED, STRING_LOCAL_ENC_ENABLED }
import { AppState } from '@/UIModels/AppState' import { AppState } from '@/UIModels/AppState'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import { ComponentChild, FunctionComponent } from 'preact' 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}` const formatCount = (count: number, itemType: string) => `${count} / ${count} ${itemType}`

View File

@@ -1,5 +1,10 @@
import { FunctionComponent } from 'preact' 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 { Switch } from '@/Components/Switch'
import { observer } from 'mobx-react-lite' import { observer } from 'mobx-react-lite'
import { is2FAActivation, is2FADisabled, TwoFactorAuth } from './TwoFactorAuth' import { is2FAActivation, is2FADisabled, TwoFactorAuth } from './TwoFactorAuth'