Files
standardnotes-app-web/packages/web/src/javascripts/Components/Vaults/AddToVaultMenuOption.tsx

156 lines
5.0 KiB
TypeScript

import { observer } from 'mobx-react-lite'
import { FunctionComponent, useCallback, useRef, useState } from 'react'
import Icon from '@/Components/Icon/Icon'
import { KeyboardKey } from '@standardnotes/ui-services'
import Popover from '../Popover/Popover'
import { classNames, DecryptedItemInterface, VaultListingInterface } from '@standardnotes/snjs'
import { useApplication } from '../ApplicationProvider'
import MenuItem from '../Menu/MenuItem'
import Menu from '../Menu/Menu'
import { featureTrunkVaultsEnabled } from '@/FeatureTrunk'
type Props = {
iconClassName: string
items: DecryptedItemInterface[]
}
const AddToVaultMenuOption: FunctionComponent<Props> = ({ iconClassName, items }) => {
const application = useApplication()
const menuContainerRef = useRef<HTMLDivElement>(null)
const buttonRef = useRef<HTMLButtonElement>(null)
const vaults = application.vaults.getVaults()
const [isSubMenuOpen, setIsSubMenuOpen] = useState(false)
const toggleSubMenu = useCallback(() => {
setIsSubMenuOpen((isOpen) => !isOpen)
}, [])
const addItemsToVault = useCallback(
async (vault: VaultListingInterface) => {
if (application.vaults.isVaultLocked(vault)) {
const unlocked = await application.vaultDisplayService.unlockVault(vault)
if (!unlocked) {
return
}
}
for (const item of items) {
await application.vaults.moveItemToVault(vault, item)
}
},
[application.vaultDisplayService, application.vaults, items],
)
const removeItemsFromVault = useCallback(async () => {
for (const item of items) {
const vault = application.vaults.getItemVault(item)
if (!vault) {
continue
}
if (application.vaults.isVaultLocked(vault)) {
const unlocked = await application.vaultDisplayService.unlockVault(vault)
if (!unlocked) {
return
}
}
await application.vaults.removeItemFromVault(item)
}
}, [application.vaultDisplayService, application.vaults, items])
const doesVaultContainItems = (vault: VaultListingInterface) => {
return items.every((item) => item.key_system_identifier === vault.systemIdentifier)
}
const doSomeItemsBelongToVault = items.some((item) => application.vaults.isItemInVault(item))
const singleItemVault = items.length === 1 ? application.vaults.getItemVault(items[0]) : undefined
if (!featureTrunkVaultsEnabled()) {
return null
}
return (
<div ref={menuContainerRef}>
<MenuItem
className="justify-between"
onClick={toggleSubMenu}
onKeyDown={(event) => {
if (event.key === KeyboardKey.Escape) {
setIsSubMenuOpen(false)
}
}}
ref={buttonRef}
>
<div className="flex items-center">
<Icon type="safe-square" className={iconClassName} />
Move to vault
</div>
<Icon type="chevron-right" className="text-neutral" />
</MenuItem>
<Popover
title="Move to vault"
togglePopover={toggleSubMenu}
anchorElement={buttonRef.current}
open={isSubMenuOpen}
side="right"
align="start"
className="py-2"
overrideZIndex="z-modal"
>
<Menu a11yLabel="Vault selection menu" isOpen={isSubMenuOpen}>
{doSomeItemsBelongToVault && (
<MenuItem
onClick={() => {
void removeItemsFromVault()
}}
>
<span className="flex overflow-hidden overflow-ellipsis whitespace-nowrap">
<Icon type="close" className="mr-2 text-neutral" />
<div className="flex w-full items-center gap-1">
Move out of {singleItemVault ? singleItemVault.name : 'vaults'}
</div>
</span>
</MenuItem>
)}
{vaults.map((vault) => {
if (singleItemVault) {
return null
}
return (
<MenuItem
key={vault.uuid}
onClick={() => {
doesVaultContainItems(vault) ? void removeItemsFromVault() : void addItemsToVault(vault)
}}
>
<span
className={classNames(
'flex overflow-ellipsis whitespace-nowrap',
doesVaultContainItems(vault) ? 'font-bold' : '',
)}
>
<Icon
type="safe-square"
size="large"
className={`mr-2 text-neutral ${doesVaultContainItems(vault) ? 'text-info' : ''}`}
/>
<div className="flex w-full items-center">
{vault.name}
{application.vaults.isVaultLocked(vault) && <Icon className="ml-1" type="lock" size={'small'} />}
</div>
</span>
</MenuItem>
)
})}
</Menu>
</Popover>
</div>
)
}
export default observer(AddToVaultMenuOption)