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 (
-
- {isVaultLockable &&
}
- {isAdmin &&
}
- {!isAdmin && vault.isSharedVaultListing() &&
}
- {isAdmin ? (
+ {canShowLockOption &&
}
+ {isCurrentUserAdmin &&
}
+ {!isCurrentUserAdmin && vault.isSharedVaultListing() &&
}
+ {isCurrentUserAdmin ? (
vault.isSharedVaultListing() ? (
) : application.hasAccount() ? (
diff --git a/packages/web/src/javascripts/Components/VaultSelectionMenu/ManyVaultSelectionMenu.tsx b/packages/web/src/javascripts/Components/VaultSelectionMenu/ManyVaultSelectionMenu.tsx
index 86332a978..221fc7c86 100644
--- a/packages/web/src/javascripts/Components/VaultSelectionMenu/ManyVaultSelectionMenu.tsx
+++ b/packages/web/src/javascripts/Components/VaultSelectionMenu/ManyVaultSelectionMenu.tsx
@@ -1,10 +1,11 @@
import { FunctionComponent, useCallback, useEffect, useState } from 'react'
import Menu from '../Menu/Menu'
import { useApplication } from '../ApplicationProvider'
-import MenuSwitchButtonItem from '../Menu/MenuSwitchButtonItem'
-import Icon from '../Icon/Icon'
import { ContentType, VaultListingInterface } from '@standardnotes/snjs'
import { observer } from 'mobx-react-lite'
+import VaultSelectMenuItemWithOptions from './MenuItemWithVaultOption'
+import Icon from '../Icon/Icon'
+import MenuSwitchButtonItem from '../Menu/MenuSwitchButtonItem'
const ManyVaultSelectionMenu: FunctionComponent = () => {
const application = useApplication()
@@ -38,19 +39,22 @@ const ManyVaultSelectionMenu: FunctionComponent = () => {
)
diff --git a/packages/web/src/javascripts/Components/VaultSelectionMenu/MenuItemWithVaultOption.tsx b/packages/web/src/javascripts/Components/VaultSelectionMenu/MenuItemWithVaultOption.tsx
new file mode 100644
index 000000000..6ec7d856b
--- /dev/null
+++ b/packages/web/src/javascripts/Components/VaultSelectionMenu/MenuItemWithVaultOption.tsx
@@ -0,0 +1,49 @@
+import { VaultListingInterface, classNames } from '@standardnotes/snjs'
+import { useState, useRef } from 'react'
+import Icon from '../Icon/Icon'
+import VaultOptionsMenu from './VaultOptionsMenu'
+import Popover from '../Popover/Popover'
+
+const VaultSelectMenuItemWithOptions = ({
+ vault,
+ children,
+}: {
+ vault: VaultListingInterface
+ children: React.ReactNode
+}) => {
+ const [isOptionsMenuOpen, setIsOptionsMenuOpen] = useState(false)
+ const optionsButtonRef = useRef
(null)
+
+ const toggleOptionsMenu = () => {
+ setIsOptionsMenuOpen((open) => !open)
+ }
+
+ return (
+
+ {children}
+
+
+
+
+
+ )
+}
+
+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 = () => {
)
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 (
+ <>
+
+ 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,
+ }
+}