From c3996be0faac313a4039b4630ebe345cd5f6abee Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Thu, 14 Sep 2023 22:02:40 +0530 Subject: [PATCH] chore: add options menu to vault selection menu items (#2497) --- .../Components/Menu/MenuListItem.tsx | 2 +- .../Panes/Vaults/Vaults/VaultItem.tsx | 45 +++----------- .../ManyVaultSelectionMenu.tsx | 34 ++++++----- .../MenuItemWithVaultOption.tsx | 49 ++++++++++++++++ .../SingleVaultSelectionMenu.tsx | 14 +++-- .../VaultSelectionMenu/VaultOptionsMenu.tsx | 48 +++++++++++++++ .../web/src/javascripts/Hooks/useVault.ts | 58 +++++++++++++++++++ 7 files changed, 193 insertions(+), 57 deletions(-) create mode 100644 packages/web/src/javascripts/Components/VaultSelectionMenu/MenuItemWithVaultOption.tsx create mode 100644 packages/web/src/javascripts/Components/VaultSelectionMenu/VaultOptionsMenu.tsx create mode 100644 packages/web/src/javascripts/Hooks/useVault.ts diff --git a/packages/web/src/javascripts/Components/Menu/MenuListItem.tsx b/packages/web/src/javascripts/Components/Menu/MenuListItem.tsx index 834dbbd59..62a8ca366 100644 --- a/packages/web/src/javascripts/Components/Menu/MenuListItem.tsx +++ b/packages/web/src/javascripts/Components/Menu/MenuListItem.tsx @@ -2,7 +2,7 @@ import { ReactNode } from 'react' const MenuListItem = ({ children }: { children: ReactNode }) => { return ( -
  • +
  • {children}
  • ) diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultItem.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultItem.tsx index d44ac11d0..032f6b392 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultItem.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultItem.tsx @@ -1,6 +1,6 @@ import { formatSizeToReadableString } from '@standardnotes/filepicker' -import { ButtonType, VaultListingInterface, VaultLockServiceEvent, isClientDisplayableError } from '@standardnotes/snjs' -import { useCallback, useEffect, useState } from 'react' +import { ButtonType, VaultListingInterface, isClientDisplayableError } from '@standardnotes/snjs' +import { useCallback, useState } from 'react' import { useApplication } from '@/Components/ApplicationProvider' import Button from '@/Components/Button/Button' @@ -8,6 +8,7 @@ import Icon from '@/Components/Icon/Icon' import ModalOverlay from '@/Components/Modal/ModalOverlay' import ContactInviteModal from '../Invites/ContactInviteModal' import EditVaultModal from './VaultModal/EditVaultModal' +import { useVault } from '@/Hooks/useVault' type Props = { vault: VaultListingInterface @@ -22,29 +23,7 @@ const VaultItem = ({ vault }: Props) => { const [isVaultModalOpen, setIsVaultModalOpen] = useState(false) const closeVaultModal = () => setIsVaultModalOpen(false) - const isVaultLockable = application.vaultLocks.isVaultLockable(vault) - const [isVaultLocked, setIsVaultLocked] = useState(() => application.vaultLocks.isVaultLocked(vault)) - useEffect(() => { - return application.vaultLocks.addEventObserver((event) => { - if (event === VaultLockServiceEvent.VaultLocked || event === VaultLockServiceEvent.VaultUnlocked) { - setIsVaultLocked(application.vaultLocks.isVaultLocked(vault)) - } - }) - }, [application.vaultLocks, vault]) - - const toggleLock = useCallback(async () => { - if (!isVaultLockable) { - return - } - - if (isVaultLocked) { - application.vaultDisplayService.unlockVault(vault).catch(console.error) - } else { - application.vaultLocks.lockNonPersistentVault(vault).catch(console.error) - } - }, [application.vaultDisplayService, application.vaultLocks, isVaultLockable, isVaultLocked, vault]) - - const isAdmin = !vault.isSharedVaultListing() ? true : application.vaultUsers.isCurrentUserSharedVaultAdmin(vault) + const { isCurrentUserAdmin, isLocked, canShowLockOption, toggleLock, ensureVaultIsUnlocked } = useVault(vault) const deleteVault = useCallback(async () => { const confirm = await application.alerts.confirm( @@ -103,14 +82,6 @@ const VaultItem = ({ vault }: Props) => { await application.sharedVaults.convertVaultToSharedVault(vault) }, [application.sharedVaults, vault]) - const ensureVaultIsUnlocked = useCallback(async () => { - if (!application.vaultLocks.isVaultLocked(vault)) { - return true - } - const unlocked = await application.vaultDisplayService.unlockVault(vault) - return unlocked - }, [application, vault]) - const openEditModal = useCallback(async () => { if (!(await ensureVaultIsUnlocked())) { return @@ -150,10 +121,10 @@ const VaultItem = ({ vault }: Props) => { )}
    + + + +
    + ) +} + +export default VaultSelectMenuItemWithOptions diff --git a/packages/web/src/javascripts/Components/VaultSelectionMenu/SingleVaultSelectionMenu.tsx b/packages/web/src/javascripts/Components/VaultSelectionMenu/SingleVaultSelectionMenu.tsx index b299e117b..2d0bf24a6 100644 --- a/packages/web/src/javascripts/Components/VaultSelectionMenu/SingleVaultSelectionMenu.tsx +++ b/packages/web/src/javascripts/Components/VaultSelectionMenu/SingleVaultSelectionMenu.tsx @@ -5,6 +5,7 @@ import { ContentType, VaultListingInterface } from '@standardnotes/snjs' import MenuRadioButtonItem from '../Menu/MenuRadioButtonItem' import { observer } from 'mobx-react-lite' import Icon from '../Icon/Icon' +import VaultSelectMenuItemWithOptions from './MenuItemWithVaultOption' const SingleVaultSelectionMenu: FunctionComponent = () => { const application = useApplication() @@ -34,12 +35,17 @@ const SingleVaultSelectionMenu: FunctionComponent = () => { {!vaults.length &&
    No vaults found
    } {vaults.map((vault) => ( - selectVault(vault)}> -
    + + selectVault(vault)} + > {vault.name} {application.vaultLocks.isVaultLocked(vault) && } -
    -
    + + ))}
    ) diff --git a/packages/web/src/javascripts/Components/VaultSelectionMenu/VaultOptionsMenu.tsx b/packages/web/src/javascripts/Components/VaultSelectionMenu/VaultOptionsMenu.tsx new file mode 100644 index 000000000..fbe9383a4 --- /dev/null +++ b/packages/web/src/javascripts/Components/VaultSelectionMenu/VaultOptionsMenu.tsx @@ -0,0 +1,48 @@ +import { VaultListingInterface } from '@standardnotes/snjs' +import Menu from '../Menu/Menu' +import MenuItem from '../Menu/MenuItem' +import Icon from '../Icon/Icon' +import { useState, useCallback } from 'react' +import EditVaultModal from '../Preferences/Panes/Vaults/Vaults/VaultModal/EditVaultModal' +import { useVault } from '@/Hooks/useVault' + +type Props = { + vault: VaultListingInterface +} + +const VaultOptionsMenu = ({ vault }: Props) => { + const { canShowLockOption, isLocked, toggleLock, ensureVaultIsUnlocked } = useVault(vault) + + const [isVaultModalOpen, setIsVaultModalOpen] = useState(false) + const openEditModal = useCallback(async () => { + if (!(await ensureVaultIsUnlocked())) { + return + } + + setIsVaultModalOpen(true) + }, [ensureVaultIsUnlocked]) + + return ( + <> + + + + Edit vault + + {canShowLockOption && ( + + + {isLocked ? 'Unlock' : 'Lock'} vault + + )} + + setIsVaultModalOpen(false)} + /> + + ) +} + +export default VaultOptionsMenu diff --git a/packages/web/src/javascripts/Hooks/useVault.ts b/packages/web/src/javascripts/Hooks/useVault.ts new file mode 100644 index 000000000..b7c724c76 --- /dev/null +++ b/packages/web/src/javascripts/Hooks/useVault.ts @@ -0,0 +1,58 @@ +import { useApplication } from '@/Components/ApplicationProvider' +import { + KeySystemPasswordType, + KeySystemRootKeyStorageMode, + VaultListingInterface, + VaultLockServiceEvent, +} from '@standardnotes/snjs' +import { useState, useEffect, useCallback } from 'react' + +export const useVault = (vault: VaultListingInterface) => { + const application = useApplication() + + const canShowLockOption = + vault.keyPasswordType === KeySystemPasswordType.UserInputted && + vault.keyStorageMode === KeySystemRootKeyStorageMode.Ephemeral + + const [isLocked, setIsLocked] = useState(() => application.vaultLocks.isVaultLocked(vault)) + + useEffect(() => { + return application.vaultLocks.addEventObserver((event) => { + if (event === VaultLockServiceEvent.VaultLocked || event === VaultLockServiceEvent.VaultUnlocked) { + setIsLocked(application.vaultLocks.isVaultLocked(vault)) + } + }) + }, [application.vaultLocks, vault]) + + const toggleLock = useCallback(async () => { + if (!canShowLockOption) { + return + } + + if (isLocked) { + application.vaultDisplayService.unlockVault(vault).catch(console.error) + } else { + application.vaultLocks.lockNonPersistentVault(vault).catch(console.error) + } + }, [application.vaultDisplayService, application.vaultLocks, canShowLockOption, isLocked, vault]) + + const isCurrentUserAdmin = !vault.isSharedVaultListing() + ? true + : application.vaultUsers.isCurrentUserSharedVaultAdmin(vault) + + const ensureVaultIsUnlocked = useCallback(async () => { + if (!application.vaultLocks.isVaultLocked(vault)) { + return true + } + const unlocked = await application.vaultDisplayService.unlockVault(vault) + return unlocked + }, [application, vault]) + + return { + canShowLockOption, + isLocked, + toggleLock, + ensureVaultIsUnlocked, + isCurrentUserAdmin, + } +}