From f82974633bb91fa5016a9546941b9ed3e82e814b Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Tue, 8 Aug 2023 19:00:04 +0530 Subject: [PATCH] chore: allow setting custom icon to vault --- .../Syncable/VaultListing/VaultListing.ts | 5 + .../VaultListing/VaultListingContent.ts | 2 + .../VaultListing/VaultListingInterface.ts | 2 + .../VaultListing/VaultListingMutator.ts | 5 + .../src/Domain/Vault/UseCase/CreateVault.ts | 6 ++ .../services/src/Domain/Vault/VaultService.ts | 17 +++- .../src/Domain/Vault/VaultServiceInterface.ts | 13 ++- .../Components/Icon/IconPicker.tsx | 72 ++++++++++----- .../Components/Modal/ModalOverlay.tsx | 1 + .../Panes/Vaults/Vaults/VaultItem.tsx | 2 +- .../Vaults/VaultModal/EditVaultModal.tsx | 91 ++++++++++++++++--- .../ManyVaultSelectionMenu.tsx | 2 +- .../Vaults/AddToVaultMenuOption.tsx | 2 +- .../Components/Vaults/VaultNameBadge.tsx | 2 +- 14 files changed, 174 insertions(+), 48 deletions(-) diff --git a/packages/models/src/Domain/Syncable/VaultListing/VaultListing.ts b/packages/models/src/Domain/Syncable/VaultListing/VaultListing.ts index cc218b4f6..f36e3c11e 100644 --- a/packages/models/src/Domain/Syncable/VaultListing/VaultListing.ts +++ b/packages/models/src/Domain/Syncable/VaultListing/VaultListing.ts @@ -8,6 +8,9 @@ import { VaultListingContent } from './VaultListingContent' import { KeySystemRootKeyStorageMode } from '../KeySystemRootKey/KeySystemRootKeyStorageMode' import { VaultListingSharingInfo } from './VaultListingSharingInfo' import { KeySystemIdentifier } from '../KeySystemRootKey/KeySystemIdentifier' +import { EmojiString, IconType } from '../../Utilities/Icon/IconType' + +export const DefaultVaultIconName: IconType = 'safe-square' export class VaultListing extends DecryptedItem implements VaultListingInterface { systemIdentifier: KeySystemIdentifier @@ -17,6 +20,7 @@ export class VaultListing extends DecryptedItem implements name: string description?: string + iconString: IconType | EmojiString sharing?: VaultListingSharingInfo @@ -30,6 +34,7 @@ export class VaultListing extends DecryptedItem implements this.name = payload.content.name this.description = payload.content.description + this.iconString = payload.content.iconString || DefaultVaultIconName this.sharing = payload.content.sharing } diff --git a/packages/models/src/Domain/Syncable/VaultListing/VaultListingContent.ts b/packages/models/src/Domain/Syncable/VaultListing/VaultListingContent.ts index e6022acda..99345a622 100644 --- a/packages/models/src/Domain/Syncable/VaultListing/VaultListingContent.ts +++ b/packages/models/src/Domain/Syncable/VaultListing/VaultListingContent.ts @@ -3,6 +3,7 @@ import { KeySystemIdentifier } from '../KeySystemRootKey/KeySystemIdentifier' import { KeySystemRootKeyParamsInterface } from '../../Local/KeyParams/KeySystemRootKeyParamsInterface' import { KeySystemRootKeyStorageMode } from '../KeySystemRootKey/KeySystemRootKeyStorageMode' import { VaultListingSharingInfo } from './VaultListingSharingInfo' +import { EmojiString, IconType } from '../../Utilities/Icon/IconType' export interface VaultListingContentSpecialized extends SpecializedContent { systemIdentifier: KeySystemIdentifier @@ -12,6 +13,7 @@ export interface VaultListingContentSpecialized extends SpecializedContent { name: string description?: string + iconString: IconType | EmojiString sharing?: VaultListingSharingInfo } diff --git a/packages/models/src/Domain/Syncable/VaultListing/VaultListingInterface.ts b/packages/models/src/Domain/Syncable/VaultListing/VaultListingInterface.ts index a92d03043..2a624d4cf 100644 --- a/packages/models/src/Domain/Syncable/VaultListing/VaultListingInterface.ts +++ b/packages/models/src/Domain/Syncable/VaultListing/VaultListingInterface.ts @@ -5,6 +5,7 @@ import { KeySystemRootKeyStorageMode } from '../KeySystemRootKey/KeySystemRootKe import { VaultListingSharingInfo } from './VaultListingSharingInfo' import { VaultListingContent } from './VaultListingContent' import { DecryptedItemInterface } from '../../Abstract/Item' +import { EmojiString, IconType } from '../../Utilities/Icon/IconType' export interface VaultListingInterface extends DecryptedItemInterface { systemIdentifier: KeySystemIdentifier @@ -14,6 +15,7 @@ export interface VaultListingInterface extends DecryptedItemInterface { @@ -44,6 +47,7 @@ export class CreateVault { keySystemIdentifier, vaultName: dto.vaultName, vaultDescription: dto.vaultDescription, + vaultIcon: dto.vaultIcon, passwordType: dto.userInputtedPassword ? KeySystemPasswordType.UserInputted : KeySystemPasswordType.Randomized, rootKeyParams: rootKey.keyParams, storage: dto.storagePreference, @@ -58,6 +62,7 @@ export class CreateVault { keySystemIdentifier: string vaultName: string vaultDescription?: string + vaultIcon: IconType | EmojiString passwordType: KeySystemPasswordType rootKeyParams: KeySystemRootKeyParamsInterface storage: KeySystemRootKeyStorageMode @@ -68,6 +73,7 @@ export class CreateVault { keyStorageMode: dto.storage, name: dto.vaultName, description: dto.vaultDescription, + iconString: dto.vaultIcon, } return this.mutator.createItem(ContentType.TYPES.VaultListing, FillItemContentSpecialized(content), true) diff --git a/packages/services/src/Domain/Vault/VaultService.ts b/packages/services/src/Domain/Vault/VaultService.ts index f24915e5e..14e178435 100644 --- a/packages/services/src/Domain/Vault/VaultService.ts +++ b/packages/services/src/Domain/Vault/VaultService.ts @@ -4,7 +4,9 @@ import { SendVaultDataChangedMessage } from './../SharedVaults/UseCase/SendVault import { isClientDisplayableError } from '@standardnotes/responses' import { DecryptedItemInterface, + EmojiString, FileItem, + IconType, KeySystemIdentifier, KeySystemRootKeyStorageMode, SharedVaultListingInterface, @@ -97,10 +99,15 @@ export class VaultService return vault } - async createRandomizedVault(dto: { name: string; description?: string }): Promise { + async createRandomizedVault(dto: { + name: string + description?: string + iconString: IconType | EmojiString + }): Promise { return this.createVaultWithParameters({ name: dto.name, description: dto.description, + iconString: dto.iconString, userInputtedPassword: undefined, storagePreference: KeySystemRootKeyStorageMode.Synced, }) @@ -109,6 +116,7 @@ export class VaultService async createUserInputtedPasswordVault(dto: { name: string description?: string + iconString: IconType | EmojiString userInputtedPassword: string storagePreference: KeySystemRootKeyStorageMode }): Promise { @@ -118,12 +126,14 @@ export class VaultService private async createVaultWithParameters(dto: { name: string description?: string + iconString: IconType | EmojiString userInputtedPassword: string | undefined storagePreference: KeySystemRootKeyStorageMode }): Promise { const result = await this._createVault.execute({ vaultName: dto.name, vaultDescription: dto.description, + vaultIcon: dto.iconString, userInputtedPassword: dto.userInputtedPassword, storagePreference: dto.storagePreference, }) @@ -188,13 +198,14 @@ export class VaultService return true } - async changeVaultNameAndDescription( + async changeVaultMetadata( vault: VaultListingInterface, - params: { name: string; description?: string }, + params: { name: string; description?: string; iconString: IconType | EmojiString }, ): Promise { const updatedVault = await this.mutator.changeItem(vault, (mutator) => { mutator.name = params.name mutator.description = params.description + mutator.iconString = params.iconString }) await this.sync.sync() diff --git a/packages/services/src/Domain/Vault/VaultServiceInterface.ts b/packages/services/src/Domain/Vault/VaultServiceInterface.ts index ec8495c2b..1ad608e29 100644 --- a/packages/services/src/Domain/Vault/VaultServiceInterface.ts +++ b/packages/services/src/Domain/Vault/VaultServiceInterface.ts @@ -1,5 +1,7 @@ import { DecryptedItemInterface, + EmojiString, + IconType, KeySystemIdentifier, KeySystemRootKeyStorageMode, SharedVaultListingInterface, @@ -12,10 +14,15 @@ import { Result } from '@standardnotes/domain-core' export interface VaultServiceInterface extends AbstractService { - createRandomizedVault(dto: { name: string; description?: string }): Promise + createRandomizedVault(dto: { + name: string + description?: string + iconString: IconType | EmojiString + }): Promise createUserInputtedPasswordVault(dto: { name: string description?: string + iconString: IconType | EmojiString userInputtedPassword: string storagePreference: KeySystemRootKeyStorageMode }): Promise @@ -32,9 +39,9 @@ export interface VaultServiceInterface isItemInVault(item: DecryptedItemInterface): boolean getItemVault(item: DecryptedItemInterface): VaultListingInterface | undefined - changeVaultNameAndDescription( + changeVaultMetadata( vault: VaultListingInterface, - params: { name: string; description: string }, + params: { name: string; description: string; iconString: IconType | EmojiString }, ): Promise rotateVaultRootKey(vault: VaultListingInterface, vaultPassword?: string): Promise diff --git a/packages/web/src/javascripts/Components/Icon/IconPicker.tsx b/packages/web/src/javascripts/Components/Icon/IconPicker.tsx index d591657ca..7bb9a6bac 100644 --- a/packages/web/src/javascripts/Components/Icon/IconPicker.tsx +++ b/packages/web/src/javascripts/Components/Icon/IconPicker.tsx @@ -1,6 +1,6 @@ import { classNames } from '@standardnotes/utils' import { EmojiString, Platform, VectorIconNameOrEmoji } from '@standardnotes/snjs' -import { FunctionComponent, useMemo, useRef, useState } from 'react' +import { ForwardedRef, forwardRef, useCallback, useMemo, useRef, useState } from 'react' import Dropdown from '../Dropdown/Dropdown' import { DropdownItem } from '../Dropdown/DropdownItem' import { getEmojiLength } from './EmojiLength' @@ -17,6 +17,39 @@ type Props = { className?: string } +const TabButton = forwardRef( + ( + { + type, + label, + currentType, + selectTab, + }: { + label: string + type: IconPickerType | 'reset' + currentType: IconPickerType + selectTab: (type: IconPickerType | 'reset') => void + }, + ref: ForwardedRef, + ) => { + const isSelected = currentType === type + + return ( + + ) + }, +) + const IconPicker = ({ selectedValue, onIconChange, platform, className, useIconGrid, iconGridClassName }: Props) => { const iconKeys = useMemo(() => Object.keys(IconNameToSvgMapping), []) @@ -51,26 +84,6 @@ const IconPicker = ({ selectedValue, onIconChange, platform, className, useIconG } } - const TabButton: FunctionComponent<{ - label: string - type: IconPickerType | 'reset' - }> = ({ type, label }) => { - const isSelected = currentType === type - - return ( - - ) - } - const handleIconChange = (value: string) => { onIconChange(value) } @@ -88,12 +101,20 @@ const IconPicker = ({ selectedValue, onIconChange, platform, className, useIconG } } + const focusOnMount = useCallback((element: HTMLButtonElement | null) => { + if (element) { + setTimeout(() => { + element.focus() + }) + } + }, []) + return (
- - - + + +
{currentType === 'icon' && @@ -104,12 +125,13 @@ const IconPicker = ({ selectedValue, onIconChange, platform, className, useIconG iconGridClassName, )} > - {iconKeys.map((iconName) => ( + {iconKeys.map((iconName, index) => ( diff --git a/packages/web/src/javascripts/Components/Modal/ModalOverlay.tsx b/packages/web/src/javascripts/Components/Modal/ModalOverlay.tsx index 32061537d..7a9de55c5 100644 --- a/packages/web/src/javascripts/Components/Modal/ModalOverlay.tsx +++ b/packages/web/src/javascripts/Components/Modal/ModalOverlay.tsx @@ -48,6 +48,7 @@ const ModalOverlay = forwardRef( modal={false} portal={true} preventBodyScroll={true} + hideOnInteractOutside={false} {...props} > {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 0b82a43a8..ae9b5b187 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 @@ -108,7 +108,7 @@ const VaultItem = ({ vault }: Props) => {
- +
{vault.name} {vault.description && ( diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultModal/EditVaultModal.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultModal/EditVaultModal.tsx index 9efb89775..0b7534a75 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultModal/EditVaultModal.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Vaults/Vaults/VaultModal/EditVaultModal.tsx @@ -9,6 +9,7 @@ import { SharedVaultInviteServerHash, SharedVaultUserServerHash, VaultListingInterface, + VectorIconNameOrEmoji, isClientDisplayableError, } from '@standardnotes/snjs' import { VaultModalMembers } from './VaultModalMembers' @@ -16,6 +17,11 @@ import { VaultModalInvites } from './VaultModalInvites' import { PasswordTypePreference } from './PasswordTypePreference' import { KeyStoragePreference } from './KeyStoragePreference' import useItem from '@/Hooks/useItem' +import Button from '@/Components/Button/Button' +import Icon from '@/Components/Icon/Icon' +import StyledTooltip from '@/Components/StyledTooltip/StyledTooltip' +import Popover from '@/Components/Popover/Popover' +import IconPicker from '@/Components/Icon/IconPicker' type Props = { existingVaultUuid?: string @@ -29,6 +35,7 @@ const EditVaultModal: FunctionComponent = ({ onCloseDialog, existingVault const [name, setName] = useState('') const [description, setDescription] = useState('') + const [iconString, setIconString] = useState('safe-square') const [members, setMembers] = useState([]) const [invites, setInvites] = useState([]) const [isAdmin, setIsAdmin] = useState(true) @@ -43,6 +50,7 @@ const EditVaultModal: FunctionComponent = ({ onCloseDialog, existingVault if (existingVault) { setName(existingVault.name ?? '') setDescription(existingVault.description ?? '') + setIconString(existingVault.iconString) setPasswordType(existingVault.rootKeyParams.passwordType) setKeyStorageMode(existingVault.keyStorageMode) } @@ -85,10 +93,11 @@ const EditVaultModal: FunctionComponent = ({ onCloseDialog, existingVault return } - if (vault.name !== name || vault.description !== description) { - await application.vaults.changeVaultNameAndDescription(vault, { + if (vault.name !== name || vault.description !== description || vault.iconString !== iconString) { + await application.vaults.changeVaultMetadata(vault, { name: name, description: description, + iconString: iconString, }) } @@ -125,7 +134,16 @@ const EditVaultModal: FunctionComponent = ({ onCloseDialog, existingVault handleDialogClose() }, - [application.vaults, customPassword, description, handleDialogClose, keyStorageMode, name, passwordType], + [ + application.vaults, + customPassword, + description, + handleDialogClose, + iconString, + keyStorageMode, + name, + passwordType, + ], ) const createNewVault = useCallback(async () => { @@ -141,6 +159,7 @@ const EditVaultModal: FunctionComponent = ({ onCloseDialog, existingVault await application.vaults.createUserInputtedPasswordVault({ name, description, + iconString: iconString, storagePreference: keyStorageMode, userInputtedPassword: customPassword, }) @@ -148,11 +167,21 @@ const EditVaultModal: FunctionComponent = ({ onCloseDialog, existingVault await application.vaults.createRandomizedVault({ name, description, + iconString: iconString, }) } handleDialogClose() - }, [application.vaults, customPassword, description, handleDialogClose, keyStorageMode, name, passwordType]) + }, [ + application.vaults, + customPassword, + description, + handleDialogClose, + iconString, + keyStorageMode, + name, + passwordType, + ]) const handleSubmit = useCallback(async () => { if (isSubmitting) { @@ -186,6 +215,12 @@ const EditVaultModal: FunctionComponent = ({ onCloseDialog, existingVault [existingVault, handleDialogClose, handleSubmit, isSubmitting], ) + const [shouldShowIconPicker, setShouldShowIconPicker] = useState(false) + const iconPickerButtonRef = useRef(null) + const toggleIconPicker = useCallback(() => { + setShouldShowIconPicker((shouldShow) => !shouldShow) + }, []) + if (existingVault && application.vaultLocks.isVaultLocked(existingVault)) { return
Vault is locked.
} @@ -198,15 +233,45 @@ const EditVaultModal: FunctionComponent = ({ onCloseDialog, existingVault
Vault Info
The vault name and description are end-to-end encrypted.
- { - setName(value) - }} - /> +
+ + + + +
+ { + setIconString(value ?? 'safe-square') + toggleIconPicker() + }} + platform={application.platform} + useIconGrid={true} + /> +
+
+ { + setName(value) + }} + /> +
{ checked={isVaultVisible(vault)} key={vault.uuid} > - +
{vault.name} {application.vaultLocks.isVaultLocked(vault) && } diff --git a/packages/web/src/javascripts/Components/Vaults/AddToVaultMenuOption.tsx b/packages/web/src/javascripts/Components/Vaults/AddToVaultMenuOption.tsx index ff4eb686b..30a57fe7e 100644 --- a/packages/web/src/javascripts/Components/Vaults/AddToVaultMenuOption.tsx +++ b/packages/web/src/javascripts/Components/Vaults/AddToVaultMenuOption.tsx @@ -90,7 +90,7 @@ const VaultMenu = ({ items }: { items: DecryptedItemInterface[] }) => { )} > diff --git a/packages/web/src/javascripts/Components/Vaults/VaultNameBadge.tsx b/packages/web/src/javascripts/Components/Vaults/VaultNameBadge.tsx index 8b9b56279..437aeb47e 100644 --- a/packages/web/src/javascripts/Components/Vaults/VaultNameBadge.tsx +++ b/packages/web/src/javascripts/Components/Vaults/VaultNameBadge.tsx @@ -9,7 +9,7 @@ type Props = { const VaultNameBadge: FunctionComponent = ({ vault }) => { return (
- + {vault.name}
)