From 9dabe44d0fbd103feb4e2295cfdb9b922f996134 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Thu, 23 Nov 2023 13:44:54 +0100 Subject: [PATCH] Fix KeyboardShortcut menu, person upload picture (#2669) * Fix KeyboardShortcut menu, person upload picture * Fixes --- .../effect-components/PageChangeEffect.tsx | 30 +++++++++--- front/src/generated/graphql.tsx | 40 +++++++++++++++ .../command-menu/components/CommandMenu.tsx | 11 +++-- .../command-menu/hooks/useCommandMenu.ts | 13 ++++- .../favorites/components/Favorites.tsx | 12 +++-- .../files/graphql/queries/uploadImage.ts | 7 +++ .../components/KeyboardShortcutMenu.tsx | 12 ++++- .../components/RecordShowPage.tsx | 49 +++++++++++++++---- .../utils/filterAvailableFieldMetadataItem.ts | 4 ++ .../components/ShowPageSummaryCard.tsx | 9 ++-- .../record-table/hooks/useRecordTable.ts | 5 +- .../ui/utilities/hotkey/constants/index.ts | 2 +- .../hotkey/hooks/useSetHotkeyScope.ts | 2 +- .../testing/InitializeHotkeyStorybookHook.tsx | 2 +- .../metadata/field-metadata/person.ts | 4 +- .../standard-objects/person.ts | 1 + 16 files changed, 168 insertions(+), 35 deletions(-) create mode 100644 front/src/modules/files/graphql/queries/uploadImage.ts diff --git a/front/src/effect-components/PageChangeEffect.tsx b/front/src/effect-components/PageChangeEffect.tsx index f0ffd6e14..f90e5d101 100644 --- a/front/src/effect-components/PageChangeEffect.tsx +++ b/front/src/effect-components/PageChangeEffect.tsx @@ -111,19 +111,31 @@ export const PageChangeEffect = () => { switch (true) { case isMatchingLocation(AppPath.RecordTablePage): { - setHotkeyScope(TableHotkeyScope.Table, { goto: true }); + setHotkeyScope(TableHotkeyScope.Table, { + goto: true, + keyboardShortcutMenu: true, + }); break; } case isMatchingLocation(AppPath.RecordShowPage): { - setHotkeyScope(PageHotkeyScope.CompanyShowPage, { goto: true }); + setHotkeyScope(PageHotkeyScope.CompanyShowPage, { + goto: true, + keyboardShortcutMenu: true, + }); break; } case isMatchingLocation(AppPath.OpportunitiesPage): { - setHotkeyScope(PageHotkeyScope.OpportunitiesPage, { goto: true }); + setHotkeyScope(PageHotkeyScope.OpportunitiesPage, { + goto: true, + keyboardShortcutMenu: true, + }); break; } case isMatchingLocation(AppPath.TasksPage): { - setHotkeyScope(PageHotkeyScope.TaskPage, { goto: true }); + setHotkeyScope(PageHotkeyScope.TaskPage, { + goto: true, + keyboardShortcutMenu: true, + }); break; } @@ -148,14 +160,20 @@ export const PageChangeEffect = () => { break; } case isMatchingLocation(SettingsPath.ProfilePage, AppBasePath.Settings): { - setHotkeyScope(PageHotkeyScope.ProfilePage, { goto: true }); + setHotkeyScope(PageHotkeyScope.ProfilePage, { + goto: true, + keyboardShortcutMenu: true, + }); break; } case isMatchingLocation( SettingsPath.WorkspaceMembersPage, AppBasePath.Settings, ): { - setHotkeyScope(PageHotkeyScope.WorkspaceMemberPage, { goto: true }); + setHotkeyScope(PageHotkeyScope.WorkspaceMemberPage, { + goto: true, + keyboardShortcutMenu: true, + }); break; } } diff --git a/front/src/generated/graphql.tsx b/front/src/generated/graphql.tsx index 488940e60..f452892e2 100644 --- a/front/src/generated/graphql.tsx +++ b/front/src/generated/graphql.tsx @@ -646,6 +646,14 @@ export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>; export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, debugMode: boolean, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean }, telemetry: { __typename?: 'Telemetry', enabled: boolean, anonymizationEnabled: boolean }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null } } }; +export type UploadImageMutationVariables = Exact<{ + file: Scalars['Upload']; + fileFolder?: InputMaybe; +}>; + + +export type UploadImageMutation = { __typename?: 'Mutation', uploadImage: string }; + export type UserQueryFragmentFragment = { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canImpersonate: boolean, supportUserHash?: string | null, workspaceMember: { __typename?: 'UserWorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale: string, allowImpersonation: boolean, name: { __typename?: 'UserWorkspaceMemberName', firstName: string, lastName: string } }, defaultWorkspace: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, domainName?: string | null, inviteHash?: string | null, allowImpersonation: boolean } }; export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>; @@ -1076,6 +1084,38 @@ export function useGetClientConfigLazyQuery(baseOptions?: Apollo.LazyQueryHookOp export type GetClientConfigQueryHookResult = ReturnType; export type GetClientConfigLazyQueryHookResult = ReturnType; export type GetClientConfigQueryResult = Apollo.QueryResult; +export const UploadImageDocument = gql` + mutation uploadImage($file: Upload!, $fileFolder: FileFolder) { + uploadImage(file: $file, fileFolder: $fileFolder) +} + `; +export type UploadImageMutationFn = Apollo.MutationFunction; + +/** + * __useUploadImageMutation__ + * + * To run a mutation, you first call `useUploadImageMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUploadImageMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [uploadImageMutation, { data, loading, error }] = useUploadImageMutation({ + * variables: { + * file: // value for 'file' + * fileFolder: // value for 'fileFolder' + * }, + * }); + */ +export function useUploadImageMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UploadImageDocument, options); + } +export type UploadImageMutationHookResult = ReturnType; +export type UploadImageMutationResult = Apollo.MutationResult; +export type UploadImageMutationOptions = Apollo.BaseMutationOptions; export const DeleteUserAccountDocument = gql` mutation DeleteUserAccount { deleteUser { diff --git a/front/src/modules/command-menu/components/CommandMenu.tsx b/front/src/modules/command-menu/components/CommandMenu.tsx index 566fcd107..5b721f451 100644 --- a/front/src/modules/command-menu/components/CommandMenu.tsx +++ b/front/src/modules/command-menu/components/CommandMenu.tsx @@ -25,7 +25,8 @@ import { } from './CommandMenuStyles'; export const CommandMenu = () => { - const { openCommandMenu, closeCommandMenu } = useCommandMenu(); + const { openCommandMenu, closeCommandMenu, toggleCommandMenu } = + useCommandMenu(); const openActivityRightDrawer = useOpenActivityRightDrawer(); const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState); const [search, setSearch] = useState(''); @@ -35,7 +36,7 @@ export const CommandMenu = () => { 'ctrl+k,meta+k', () => { setSearch(''); - openCommandMenu(); + toggleCommandMenu(); }, AppHotkeyScope.CommandMenu, [openCommandMenu, setSearch], @@ -154,9 +155,11 @@ export const CommandMenu = () => { Icon={() => ( )} /> diff --git a/front/src/modules/command-menu/hooks/useCommandMenu.ts b/front/src/modules/command-menu/hooks/useCommandMenu.ts index 99277e2d7..609741fee 100644 --- a/front/src/modules/command-menu/hooks/useCommandMenu.ts +++ b/front/src/modules/command-menu/hooks/useCommandMenu.ts @@ -9,7 +9,9 @@ import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState'; import { Command } from '../types/Command'; export const useCommandMenu = () => { - const [, setIsCommandMenuOpened] = useRecoilState(isCommandMenuOpenedState); + const [isCommandMenuOpened, setIsCommandMenuOpened] = useRecoilState( + isCommandMenuOpenedState, + ); const setCommands = useSetRecoilState(commandMenuCommandsState); const { setHotkeyScopeAndMemorizePreviousScope, @@ -26,6 +28,14 @@ export const useCommandMenu = () => { goBackToPreviousHotkeyScope(); }; + const toggleCommandMenu = () => { + if (isCommandMenuOpened) { + closeCommandMenu(); + } else { + openCommandMenu(); + } + }; + const addToCommandMenu = (addCommand: Command[]) => { setCommands((prev) => [...prev, ...addCommand]); }; @@ -37,6 +47,7 @@ export const useCommandMenu = () => { return { openCommandMenu, closeCommandMenu, + toggleCommandMenu, addToCommandMenu, setToIntitialCommandMenu, }; diff --git a/front/src/modules/favorites/components/Favorites.tsx b/front/src/modules/favorites/components/Favorites.tsx index e66db6cb0..0e892990e 100644 --- a/front/src/modules/favorites/components/Favorites.tsx +++ b/front/src/modules/favorites/components/Favorites.tsx @@ -30,8 +30,14 @@ export const Favorites = () => { draggableItems={ <> {favorites.map((favorite, index) => { - const { id, labelIdentifier, avatarUrl, avatarType, link } = - favorite; + const { + id, + labelIdentifier, + avatarUrl, + avatarType, + link, + recordId, + } = favorite; return ( { label={labelIdentifier} Icon={() => ( { - const { toggleKeyboardShortcutMenu } = useKeyboardShortcutMenu(); + const { toggleKeyboardShortcutMenu, closeKeyboardShortcutMenu } = + useKeyboardShortcutMenu(); const isKeyboardShortcutMenuOpened = useRecoilValue( isKeyboardShortcutMenuOpenedState, ); @@ -28,6 +29,15 @@ export const KeyboardShortcutMenu = () => { [toggleKeyboardShortcutMenu], ); + useScopedHotkeys( + 'Esc', + () => { + closeKeyboardShortcutMenu(); + }, + AppHotkeyScope.KeyboardShortcutMenu, + [toggleKeyboardShortcutMenu], + ); + return ( isKeyboardShortcutMenuOpened && ( diff --git a/front/src/modules/object-record/components/RecordShowPage.tsx b/front/src/modules/object-record/components/RecordShowPage.tsx index 69ce641e5..63e648250 100644 --- a/front/src/modules/object-record/components/RecordShowPage.tsx +++ b/front/src/modules/object-record/components/RecordShowPage.tsx @@ -1,5 +1,4 @@ import { useParams } from 'react-router-dom'; -import { DateTime } from 'luxon'; import { useRecoilState } from 'recoil'; import { CompanyTeam } from '@/companies/components/CompanyTeam'; @@ -26,6 +25,7 @@ import { PropertyBox } from '@/ui/object/record-inline-cell/property-box/compone import { InlineCellHotkeyScope } from '@/ui/object/record-inline-cell/types/InlineCellHotkeyScope'; import { PageTitle } from '@/ui/utilities/page-title/PageTitle'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; +import { FileFolder, useUploadImageMutation } from '~/generated/graphql'; import { getLogoUrlFromDomainName } from '~/utils'; import { useFindOneObjectRecord } from '../hooks/useFindOneObjectRecord'; @@ -59,11 +59,12 @@ export const RecordShowPage = () => { }, }); - const useUpdateOneObjectMutation: () => [(params: any) => any, any] = () => { - const { updateOneObject } = useUpdateOneObjectRecord({ - objectNameSingular, - }); + const [uploadImage] = useUploadImageMutation(); + const { updateOneObject } = useUpdateOneObjectRecord({ + objectNameSingular, + }); + const useUpdateOneObjectMutation: () => [(params: any) => any, any] = () => { const updateEntity = ({ variables, }: { @@ -126,6 +127,35 @@ export const RecordShowPage = () => { objectMetadataItem?.nameSingular ?? '', ); + const onUploadPicture = async (file: File) => { + if (objectNameSingular !== 'person') { + return; + } + + const result = await uploadImage({ + variables: { + file, + fileFolder: FileFolder.PersonPicture, + }, + }); + + const avatarUrl = result?.data?.uploadImage; + + if (!avatarUrl) { + return; + } + if (!updateOneObject) { + return; + } + + await updateOneObject({ + idToUpdate: object?.id, + input: { + avatarUrl, + }, + }); + }; + return ( @@ -156,15 +186,16 @@ export const RecordShowPage = () => { title={recordIdentifiers?.name ?? 'No name'} date={object.createdAt ?? ''} renderTitleEditComponent={() => <>} - avatarType="squared" + avatarType={recordIdentifiers?.avatarType ?? 'rounded'} + onUploadPicture={ + objectNameSingular === 'person' ? onUploadPicture : undefined + } /> {objectMetadataItem && [...objectMetadataItem.fields] .sort((a, b) => - DateTime.fromISO(a.createdAt) - .diff(DateTime.fromISO(b.createdAt)) - .toMillis(), + a.name === 'name' ? -1 : a.name.localeCompare(b.name), ) .filter(filterAvailableFieldMetadataItem) .map((metadataField, index) => { diff --git a/front/src/modules/object-record/utils/filterAvailableFieldMetadataItem.ts b/front/src/modules/object-record/utils/filterAvailableFieldMetadataItem.ts index ab37057b5..a20f52d8c 100644 --- a/front/src/modules/object-record/utils/filterAvailableFieldMetadataItem.ts +++ b/front/src/modules/object-record/utils/filterAvailableFieldMetadataItem.ts @@ -17,5 +17,9 @@ export const filterAvailableFieldMetadataItem = ( return false; } + if (fieldMetadataItem.isSystem) { + return false; + } + return true; }; diff --git a/front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx b/front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx index 8d25c3740..0ee926246 100644 --- a/front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx +++ b/front/src/modules/ui/layout/show-page/components/ShowPageSummaryCard.tsx @@ -92,17 +92,16 @@ export const ShowPageSummaryCard = ({ if (e.target.files) onUploadPicture?.(e.target.files[0]); }; - // Todo - add back in when we have the ability to upload a picture - // const handleAvatarClick = () => { - // inputFileRef?.current?.click?.(); - // }; + const handleAvatarClick = () => { + inputFileRef?.current?.click?.(); + }; return ( { useScopedHotkeys( [Key.Escape], () => { - setHotkeyScope(TableHotkeyScope.Table, { goto: true }); + setHotkeyScope(TableHotkeyScope.Table, { + goto: true, + keyboardShortcutMenu: true, + }); disableSoftFocus(); }, TableHotkeyScope.TableSoftFocus, diff --git a/front/src/modules/ui/utilities/hotkey/constants/index.ts b/front/src/modules/ui/utilities/hotkey/constants/index.ts index 4b152d6c5..435b9ee3c 100644 --- a/front/src/modules/ui/utilities/hotkey/constants/index.ts +++ b/front/src/modules/ui/utilities/hotkey/constants/index.ts @@ -5,7 +5,7 @@ import { HotkeyScope } from '../types/HotkeyScope'; export const DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES: CustomHotkeyScopes = { commandMenu: true, goto: false, - keyboardShortcutMenu: true, + keyboardShortcutMenu: false, }; export const INITIAL_HOTKEYS_SCOPE: HotkeyScope = { diff --git a/front/src/modules/ui/utilities/hotkey/hooks/useSetHotkeyScope.ts b/front/src/modules/ui/utilities/hotkey/hooks/useSetHotkeyScope.ts index b378f2b9e..beaf9715c 100644 --- a/front/src/modules/ui/utilities/hotkey/hooks/useSetHotkeyScope.ts +++ b/front/src/modules/ui/utilities/hotkey/hooks/useSetHotkeyScope.ts @@ -55,7 +55,7 @@ export const useSetHotkeyScope = () => customScopes: { commandMenu: customScopes?.commandMenu ?? true, goto: customScopes?.goto ?? false, - keyboardShortcutMenu: customScopes?.keyboardShortcutMenu ?? true, + keyboardShortcutMenu: customScopes?.keyboardShortcutMenu ?? false, }, }; diff --git a/front/src/testing/InitializeHotkeyStorybookHook.tsx b/front/src/testing/InitializeHotkeyStorybookHook.tsx index 4dc315628..71653b8e1 100644 --- a/front/src/testing/InitializeHotkeyStorybookHook.tsx +++ b/front/src/testing/InitializeHotkeyStorybookHook.tsx @@ -10,7 +10,7 @@ export const InitializeHotkeyStorybookHookEffect = () => { setHotkeyScope(AppHotkeyScope.App, { commandMenu: true, goto: false, - keyboardShortcutMenu: true, + keyboardShortcutMenu: false, }); }, [setHotkeyScope]); diff --git a/server/src/database/typeorm-seeds/metadata/field-metadata/person.ts b/server/src/database/typeorm-seeds/metadata/field-metadata/person.ts index 9d81e0362..a0fbc51f9 100644 --- a/server/src/database/typeorm-seeds/metadata/field-metadata/person.ts +++ b/server/src/database/typeorm-seeds/metadata/field-metadata/person.ts @@ -183,7 +183,7 @@ export const seedPersonFieldMetadata = async ( url: 'xLinkUrl', }, description: 'Contact’s X/Twitter account', - icon: 'IconUser', + icon: 'IconBrandX', isNullable: true, isSystem: false, defaultValue: undefined, @@ -257,7 +257,7 @@ export const seedPersonFieldMetadata = async ( description: 'Contact’s avatar', icon: 'IconFileUpload', isNullable: true, - isSystem: false, + isSystem: true, defaultValue: undefined, }, diff --git a/server/src/workspace/workspace-manager/standard-objects/person.ts b/server/src/workspace/workspace-manager/standard-objects/person.ts index c6376c0f1..bd4f0efc6 100644 --- a/server/src/workspace/workspace-manager/standard-objects/person.ts +++ b/server/src/workspace/workspace-manager/standard-objects/person.ts @@ -117,6 +117,7 @@ const personMetadata = { description: 'Contact’s avatar', icon: 'IconFileUpload', isNullable: true, + isSystem: true, }, // Relations {