Fix KeyboardShortcut menu, person upload picture (#2669)

* Fix KeyboardShortcut menu, person upload picture

* Fixes
This commit is contained in:
Charles Bochet
2023-11-23 13:44:54 +01:00
committed by GitHub
parent 9c4f402102
commit 9dabe44d0f
16 changed files with 168 additions and 35 deletions

View File

@ -111,19 +111,31 @@ export const PageChangeEffect = () => {
switch (true) { switch (true) {
case isMatchingLocation(AppPath.RecordTablePage): { case isMatchingLocation(AppPath.RecordTablePage): {
setHotkeyScope(TableHotkeyScope.Table, { goto: true }); setHotkeyScope(TableHotkeyScope.Table, {
goto: true,
keyboardShortcutMenu: true,
});
break; break;
} }
case isMatchingLocation(AppPath.RecordShowPage): { case isMatchingLocation(AppPath.RecordShowPage): {
setHotkeyScope(PageHotkeyScope.CompanyShowPage, { goto: true }); setHotkeyScope(PageHotkeyScope.CompanyShowPage, {
goto: true,
keyboardShortcutMenu: true,
});
break; break;
} }
case isMatchingLocation(AppPath.OpportunitiesPage): { case isMatchingLocation(AppPath.OpportunitiesPage): {
setHotkeyScope(PageHotkeyScope.OpportunitiesPage, { goto: true }); setHotkeyScope(PageHotkeyScope.OpportunitiesPage, {
goto: true,
keyboardShortcutMenu: true,
});
break; break;
} }
case isMatchingLocation(AppPath.TasksPage): { case isMatchingLocation(AppPath.TasksPage): {
setHotkeyScope(PageHotkeyScope.TaskPage, { goto: true }); setHotkeyScope(PageHotkeyScope.TaskPage, {
goto: true,
keyboardShortcutMenu: true,
});
break; break;
} }
@ -148,14 +160,20 @@ export const PageChangeEffect = () => {
break; break;
} }
case isMatchingLocation(SettingsPath.ProfilePage, AppBasePath.Settings): { case isMatchingLocation(SettingsPath.ProfilePage, AppBasePath.Settings): {
setHotkeyScope(PageHotkeyScope.ProfilePage, { goto: true }); setHotkeyScope(PageHotkeyScope.ProfilePage, {
goto: true,
keyboardShortcutMenu: true,
});
break; break;
} }
case isMatchingLocation( case isMatchingLocation(
SettingsPath.WorkspaceMembersPage, SettingsPath.WorkspaceMembersPage,
AppBasePath.Settings, AppBasePath.Settings,
): { ): {
setHotkeyScope(PageHotkeyScope.WorkspaceMemberPage, { goto: true }); setHotkeyScope(PageHotkeyScope.WorkspaceMemberPage, {
goto: true,
keyboardShortcutMenu: true,
});
break; break;
} }
} }

View File

@ -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 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<FileFolder>;
}>;
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 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; }>; export type DeleteUserAccountMutationVariables = Exact<{ [key: string]: never; }>;
@ -1076,6 +1084,38 @@ export function useGetClientConfigLazyQuery(baseOptions?: Apollo.LazyQueryHookOp
export type GetClientConfigQueryHookResult = ReturnType<typeof useGetClientConfigQuery>; export type GetClientConfigQueryHookResult = ReturnType<typeof useGetClientConfigQuery>;
export type GetClientConfigLazyQueryHookResult = ReturnType<typeof useGetClientConfigLazyQuery>; export type GetClientConfigLazyQueryHookResult = ReturnType<typeof useGetClientConfigLazyQuery>;
export type GetClientConfigQueryResult = Apollo.QueryResult<GetClientConfigQuery, GetClientConfigQueryVariables>; export type GetClientConfigQueryResult = Apollo.QueryResult<GetClientConfigQuery, GetClientConfigQueryVariables>;
export const UploadImageDocument = gql`
mutation uploadImage($file: Upload!, $fileFolder: FileFolder) {
uploadImage(file: $file, fileFolder: $fileFolder)
}
`;
export type UploadImageMutationFn = Apollo.MutationFunction<UploadImageMutation, UploadImageMutationVariables>;
/**
* __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<UploadImageMutation, UploadImageMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<UploadImageMutation, UploadImageMutationVariables>(UploadImageDocument, options);
}
export type UploadImageMutationHookResult = ReturnType<typeof useUploadImageMutation>;
export type UploadImageMutationResult = Apollo.MutationResult<UploadImageMutation>;
export type UploadImageMutationOptions = Apollo.BaseMutationOptions<UploadImageMutation, UploadImageMutationVariables>;
export const DeleteUserAccountDocument = gql` export const DeleteUserAccountDocument = gql`
mutation DeleteUserAccount { mutation DeleteUserAccount {
deleteUser { deleteUser {

View File

@ -25,7 +25,8 @@ import {
} from './CommandMenuStyles'; } from './CommandMenuStyles';
export const CommandMenu = () => { export const CommandMenu = () => {
const { openCommandMenu, closeCommandMenu } = useCommandMenu(); const { openCommandMenu, closeCommandMenu, toggleCommandMenu } =
useCommandMenu();
const openActivityRightDrawer = useOpenActivityRightDrawer(); const openActivityRightDrawer = useOpenActivityRightDrawer();
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState); const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
const [search, setSearch] = useState(''); const [search, setSearch] = useState('');
@ -35,7 +36,7 @@ export const CommandMenu = () => {
'ctrl+k,meta+k', 'ctrl+k,meta+k',
() => { () => {
setSearch(''); setSearch('');
openCommandMenu(); toggleCommandMenu();
}, },
AppHotkeyScope.CommandMenu, AppHotkeyScope.CommandMenu,
[openCommandMenu, setSearch], [openCommandMenu, setSearch],
@ -154,9 +155,11 @@ export const CommandMenu = () => {
Icon={() => ( Icon={() => (
<Avatar <Avatar
type="rounded" type="rounded"
avatarUrl={null} avatarUrl={person.avatarUrl}
colorId={person.id} colorId={person.id}
placeholder={person.displayName} placeholder={
person.name?.firstName + ' ' + person.name?.lastName
}
/> />
)} )}
/> />

View File

@ -9,7 +9,9 @@ import { isCommandMenuOpenedState } from '../states/isCommandMenuOpenedState';
import { Command } from '../types/Command'; import { Command } from '../types/Command';
export const useCommandMenu = () => { export const useCommandMenu = () => {
const [, setIsCommandMenuOpened] = useRecoilState(isCommandMenuOpenedState); const [isCommandMenuOpened, setIsCommandMenuOpened] = useRecoilState(
isCommandMenuOpenedState,
);
const setCommands = useSetRecoilState(commandMenuCommandsState); const setCommands = useSetRecoilState(commandMenuCommandsState);
const { const {
setHotkeyScopeAndMemorizePreviousScope, setHotkeyScopeAndMemorizePreviousScope,
@ -26,6 +28,14 @@ export const useCommandMenu = () => {
goBackToPreviousHotkeyScope(); goBackToPreviousHotkeyScope();
}; };
const toggleCommandMenu = () => {
if (isCommandMenuOpened) {
closeCommandMenu();
} else {
openCommandMenu();
}
};
const addToCommandMenu = (addCommand: Command[]) => { const addToCommandMenu = (addCommand: Command[]) => {
setCommands((prev) => [...prev, ...addCommand]); setCommands((prev) => [...prev, ...addCommand]);
}; };
@ -37,6 +47,7 @@ export const useCommandMenu = () => {
return { return {
openCommandMenu, openCommandMenu,
closeCommandMenu, closeCommandMenu,
toggleCommandMenu,
addToCommandMenu, addToCommandMenu,
setToIntitialCommandMenu, setToIntitialCommandMenu,
}; };

View File

@ -30,8 +30,14 @@ export const Favorites = () => {
draggableItems={ draggableItems={
<> <>
{favorites.map((favorite, index) => { {favorites.map((favorite, index) => {
const { id, labelIdentifier, avatarUrl, avatarType, link } = const {
favorite; id,
labelIdentifier,
avatarUrl,
avatarType,
link,
recordId,
} = favorite;
return ( return (
<DraggableItem <DraggableItem
@ -44,7 +50,7 @@ export const Favorites = () => {
label={labelIdentifier} label={labelIdentifier}
Icon={() => ( Icon={() => (
<Avatar <Avatar
colorId={id} colorId={recordId}
avatarUrl={avatarUrl} avatarUrl={avatarUrl}
type={avatarType} type={avatarType}
placeholder={labelIdentifier} placeholder={labelIdentifier}

View File

@ -0,0 +1,7 @@
import { gql } from '@apollo/client';
export const UPLOAD_IMAGE = gql`
mutation uploadImage($file: Upload!, $fileFolder: FileFolder) {
uploadImage(file: $file, fileFolder: $fileFolder)
}
`;

View File

@ -15,7 +15,8 @@ import { KeyboardMenuGroup } from './KeyboardShortcutMenuGroup';
import { KeyboardMenuItem } from './KeyboardShortcutMenuItem'; import { KeyboardMenuItem } from './KeyboardShortcutMenuItem';
export const KeyboardShortcutMenu = () => { export const KeyboardShortcutMenu = () => {
const { toggleKeyboardShortcutMenu } = useKeyboardShortcutMenu(); const { toggleKeyboardShortcutMenu, closeKeyboardShortcutMenu } =
useKeyboardShortcutMenu();
const isKeyboardShortcutMenuOpened = useRecoilValue( const isKeyboardShortcutMenuOpened = useRecoilValue(
isKeyboardShortcutMenuOpenedState, isKeyboardShortcutMenuOpenedState,
); );
@ -28,6 +29,15 @@ export const KeyboardShortcutMenu = () => {
[toggleKeyboardShortcutMenu], [toggleKeyboardShortcutMenu],
); );
useScopedHotkeys(
'Esc',
() => {
closeKeyboardShortcutMenu();
},
AppHotkeyScope.KeyboardShortcutMenu,
[toggleKeyboardShortcutMenu],
);
return ( return (
isKeyboardShortcutMenuOpened && ( isKeyboardShortcutMenuOpened && (
<KeyboardMenuDialog onClose={toggleKeyboardShortcutMenu}> <KeyboardMenuDialog onClose={toggleKeyboardShortcutMenu}>

View File

@ -1,5 +1,4 @@
import { useParams } from 'react-router-dom'; import { useParams } from 'react-router-dom';
import { DateTime } from 'luxon';
import { useRecoilState } from 'recoil'; import { useRecoilState } from 'recoil';
import { CompanyTeam } from '@/companies/components/CompanyTeam'; 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 { InlineCellHotkeyScope } from '@/ui/object/record-inline-cell/types/InlineCellHotkeyScope';
import { PageTitle } from '@/ui/utilities/page-title/PageTitle'; import { PageTitle } from '@/ui/utilities/page-title/PageTitle';
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope'; import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
import { FileFolder, useUploadImageMutation } from '~/generated/graphql';
import { getLogoUrlFromDomainName } from '~/utils'; import { getLogoUrlFromDomainName } from '~/utils';
import { useFindOneObjectRecord } from '../hooks/useFindOneObjectRecord'; import { useFindOneObjectRecord } from '../hooks/useFindOneObjectRecord';
@ -59,11 +59,12 @@ export const RecordShowPage = () => {
}, },
}); });
const useUpdateOneObjectMutation: () => [(params: any) => any, any] = () => { const [uploadImage] = useUploadImageMutation();
const { updateOneObject } = useUpdateOneObjectRecord({ const { updateOneObject } = useUpdateOneObjectRecord({
objectNameSingular, objectNameSingular,
}); });
const useUpdateOneObjectMutation: () => [(params: any) => any, any] = () => {
const updateEntity = ({ const updateEntity = ({
variables, variables,
}: { }: {
@ -126,6 +127,35 @@ export const RecordShowPage = () => {
objectMetadataItem?.nameSingular ?? '', 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 ( return (
<PageContainer> <PageContainer>
<PageTitle title={pageName} /> <PageTitle title={pageName} />
@ -156,15 +186,16 @@ export const RecordShowPage = () => {
title={recordIdentifiers?.name ?? 'No name'} title={recordIdentifiers?.name ?? 'No name'}
date={object.createdAt ?? ''} date={object.createdAt ?? ''}
renderTitleEditComponent={() => <></>} renderTitleEditComponent={() => <></>}
avatarType="squared" avatarType={recordIdentifiers?.avatarType ?? 'rounded'}
onUploadPicture={
objectNameSingular === 'person' ? onUploadPicture : undefined
}
/> />
<PropertyBox extraPadding={true}> <PropertyBox extraPadding={true}>
{objectMetadataItem && {objectMetadataItem &&
[...objectMetadataItem.fields] [...objectMetadataItem.fields]
.sort((a, b) => .sort((a, b) =>
DateTime.fromISO(a.createdAt) a.name === 'name' ? -1 : a.name.localeCompare(b.name),
.diff(DateTime.fromISO(b.createdAt))
.toMillis(),
) )
.filter(filterAvailableFieldMetadataItem) .filter(filterAvailableFieldMetadataItem)
.map((metadataField, index) => { .map((metadataField, index) => {

View File

@ -17,5 +17,9 @@ export const filterAvailableFieldMetadataItem = (
return false; return false;
} }
if (fieldMetadataItem.isSystem) {
return false;
}
return true; return true;
}; };

View File

@ -92,17 +92,16 @@ export const ShowPageSummaryCard = ({
if (e.target.files) onUploadPicture?.(e.target.files[0]); if (e.target.files) onUploadPicture?.(e.target.files[0]);
}; };
// Todo - add back in when we have the ability to upload a picture const handleAvatarClick = () => {
// const handleAvatarClick = () => { inputFileRef?.current?.click?.();
// inputFileRef?.current?.click?.(); };
// };
return ( return (
<StyledShowPageSummaryCard> <StyledShowPageSummaryCard>
<StyledAvatarWrapper> <StyledAvatarWrapper>
<Avatar <Avatar
avatarUrl={logoOrAvatar} avatarUrl={logoOrAvatar}
// onClick={onUploadPicture ? handleAvatarClick : undefined} onClick={onUploadPicture ? handleAvatarClick : undefined}
size="xl" size="xl"
colorId={id} colorId={id}
placeholder={title} placeholder={title}

View File

@ -284,7 +284,10 @@ export const useRecordTable = (props?: useRecordTableProps) => {
useScopedHotkeys( useScopedHotkeys(
[Key.Escape], [Key.Escape],
() => { () => {
setHotkeyScope(TableHotkeyScope.Table, { goto: true }); setHotkeyScope(TableHotkeyScope.Table, {
goto: true,
keyboardShortcutMenu: true,
});
disableSoftFocus(); disableSoftFocus();
}, },
TableHotkeyScope.TableSoftFocus, TableHotkeyScope.TableSoftFocus,

View File

@ -5,7 +5,7 @@ import { HotkeyScope } from '../types/HotkeyScope';
export const DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES: CustomHotkeyScopes = { export const DEFAULT_HOTKEYS_SCOPE_CUSTOM_SCOPES: CustomHotkeyScopes = {
commandMenu: true, commandMenu: true,
goto: false, goto: false,
keyboardShortcutMenu: true, keyboardShortcutMenu: false,
}; };
export const INITIAL_HOTKEYS_SCOPE: HotkeyScope = { export const INITIAL_HOTKEYS_SCOPE: HotkeyScope = {

View File

@ -55,7 +55,7 @@ export const useSetHotkeyScope = () =>
customScopes: { customScopes: {
commandMenu: customScopes?.commandMenu ?? true, commandMenu: customScopes?.commandMenu ?? true,
goto: customScopes?.goto ?? false, goto: customScopes?.goto ?? false,
keyboardShortcutMenu: customScopes?.keyboardShortcutMenu ?? true, keyboardShortcutMenu: customScopes?.keyboardShortcutMenu ?? false,
}, },
}; };

View File

@ -10,7 +10,7 @@ export const InitializeHotkeyStorybookHookEffect = () => {
setHotkeyScope(AppHotkeyScope.App, { setHotkeyScope(AppHotkeyScope.App, {
commandMenu: true, commandMenu: true,
goto: false, goto: false,
keyboardShortcutMenu: true, keyboardShortcutMenu: false,
}); });
}, [setHotkeyScope]); }, [setHotkeyScope]);

View File

@ -183,7 +183,7 @@ export const seedPersonFieldMetadata = async (
url: 'xLinkUrl', url: 'xLinkUrl',
}, },
description: 'Contacts X/Twitter account', description: 'Contacts X/Twitter account',
icon: 'IconUser', icon: 'IconBrandX',
isNullable: true, isNullable: true,
isSystem: false, isSystem: false,
defaultValue: undefined, defaultValue: undefined,
@ -257,7 +257,7 @@ export const seedPersonFieldMetadata = async (
description: 'Contacts avatar', description: 'Contacts avatar',
icon: 'IconFileUpload', icon: 'IconFileUpload',
isNullable: true, isNullable: true,
isSystem: false, isSystem: true,
defaultValue: undefined, defaultValue: undefined,
}, },

View File

@ -117,6 +117,7 @@ const personMetadata = {
description: 'Contacts avatar', description: 'Contacts avatar',
icon: 'IconFileUpload', icon: 'IconFileUpload',
isNullable: true, isNullable: true,
isSystem: true,
}, },
// Relations // Relations
{ {