271 remove is command menu v2 enabled (#10809)
Closes https://github.com/twentyhq/core-team-issues/issues/271 This PR - Removes the feature flag IS_COMMAND_MENU_V2_ENABLED - Removes all old Right drawer components - Removes the Action menu bar - Removes unused Copilot page
This commit is contained in:
@ -27,10 +27,6 @@ export const RecordActionMenuEntriesSetter = () => {
|
||||
FeatureFlagKey.IsWorkflowEnabled,
|
||||
);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
if (!isDefined(contextStoreCurrentObjectMetadataItem)) {
|
||||
return null;
|
||||
}
|
||||
@ -40,10 +36,7 @@ export const RecordActionMenuEntriesSetter = () => {
|
||||
contextStoreTargetedRecordsRule,
|
||||
);
|
||||
|
||||
const actionConfig = getActionConfig(
|
||||
contextStoreCurrentObjectMetadataItem,
|
||||
isCommandMenuV2Enabled,
|
||||
);
|
||||
const actionConfig = getActionConfig(contextStoreCurrentObjectMetadataItem);
|
||||
|
||||
const actionsToRegister = isDefined(viewType)
|
||||
? Object.values(actionConfig ?? {}).filter((action) =>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook';
|
||||
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||
import { useDeleteFavorite } from '@/favorites/hooks/useDeleteFavorite';
|
||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
@ -8,10 +7,9 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isNull } from '@sniptt/guards';
|
||||
import { useCallback, useContext, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { getOsControlSymbol } from 'twenty-ui';
|
||||
@ -39,8 +37,6 @@ export const useDeleteSingleRecordAction: ActionHookWithObjectMetadataItem = ({
|
||||
const { sortedFavorites: favorites } = useFavorites();
|
||||
const { deleteFavorite } = useDeleteFavorite();
|
||||
|
||||
const { closeRightDrawer } = useRightDrawer();
|
||||
|
||||
const handleDeleteClick = useCallback(async () => {
|
||||
resetTableRowSelection();
|
||||
|
||||
@ -63,8 +59,6 @@ export const useDeleteSingleRecordAction: ActionHookWithObjectMetadataItem = ({
|
||||
|
||||
const isRemoteObject = objectMetadataItem.isRemote;
|
||||
|
||||
const { isInRightDrawer } = useContext(ActionMenuContext);
|
||||
|
||||
const shouldBeRegistered =
|
||||
!isRemoteObject &&
|
||||
isNull(selectedRecord?.deletedAt) &&
|
||||
@ -91,9 +85,6 @@ export const useDeleteSingleRecordAction: ActionHookWithObjectMetadataItem = ({
|
||||
subtitle={t`Are you sure you want to delete this record? It can be recovered from the Command menu (${osControlSymbol} + K).`}
|
||||
onConfirmClick={() => {
|
||||
handleDeleteClick();
|
||||
if (isInRightDrawer) {
|
||||
closeRightDrawer({ emitCloseEvent: false });
|
||||
}
|
||||
}}
|
||||
confirmButtonText={'Delete Record'}
|
||||
/>
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook';
|
||||
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||
import { useDestroyOneRecord } from '@/object-record/hooks/useDestroyOneRecord';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { useCallback, useContext, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
@ -35,8 +33,6 @@ export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({
|
||||
|
||||
const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
|
||||
|
||||
const { closeRightDrawer } = useRightDrawer();
|
||||
|
||||
const handleDeleteClick = useCallback(async () => {
|
||||
resetTableRowSelection();
|
||||
|
||||
@ -54,8 +50,6 @@ export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({
|
||||
|
||||
const isRemoteObject = objectMetadataItem.isRemote;
|
||||
|
||||
const { isInRightDrawer } = useContext(ActionMenuContext);
|
||||
|
||||
const shouldBeRegistered =
|
||||
!hasObjectReadOnlyPermission &&
|
||||
!isRemoteObject &&
|
||||
@ -82,9 +76,6 @@ export const useDestroySingleRecordAction: ActionHookWithObjectMetadataItem = ({
|
||||
}
|
||||
onConfirmClick={async () => {
|
||||
await handleDeleteClick();
|
||||
if (isInRightDrawer) {
|
||||
closeRightDrawer();
|
||||
}
|
||||
}}
|
||||
confirmButtonText={'Permanently Destroy Record'}
|
||||
/>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { useSelectedRecordIdOrThrow } from '@/action-menu/actions/record-actions/single-record/hooks/useSelectedRecordIdOrThrow';
|
||||
import { ActionHookWithObjectMetadataItem } from '@/action-menu/actions/types/ActionHook';
|
||||
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
|
||||
import { useRestoreManyRecords } from '@/object-record/hooks/useRestoreManyRecords';
|
||||
@ -9,9 +8,8 @@ import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTabl
|
||||
import { isSoftDeleteFilterActiveComponentState } from '@/object-record/record-table/states/isSoftDeleteFilterActiveComponentState';
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useCallback, useContext, useState } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
@ -35,8 +33,6 @@ export const useRestoreSingleRecordAction: ActionHookWithObjectMetadataItem = ({
|
||||
|
||||
const selectedRecord = useRecoilValue(recordStoreFamilyState(recordId));
|
||||
|
||||
const { closeRightDrawer } = useRightDrawer();
|
||||
|
||||
const handleRestoreClick = useCallback(async () => {
|
||||
resetTableRowSelection();
|
||||
|
||||
@ -55,8 +51,6 @@ export const useRestoreSingleRecordAction: ActionHookWithObjectMetadataItem = ({
|
||||
useRecoilComponentValueV2(contextStoreCurrentViewTypeComponentState) ===
|
||||
ContextStoreViewType.ShowPage;
|
||||
|
||||
const { isInRightDrawer } = useContext(ActionMenuContext);
|
||||
|
||||
const shouldBeRegistered =
|
||||
!isRemoteObject &&
|
||||
isDefined(selectedRecord?.deletedAt) &&
|
||||
@ -73,9 +67,6 @@ export const useRestoreSingleRecordAction: ActionHookWithObjectMetadataItem = ({
|
||||
|
||||
const handleConfirmClick = () => {
|
||||
handleRestoreClick();
|
||||
if (isInRightDrawer) {
|
||||
closeRightDrawer({ emitCloseEvent: false });
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { DEFAULT_ACTIONS_CONFIG_V1 } from '@/action-menu/actions/record-actions/constants/DefaultActionsConfigV1';
|
||||
import { DEFAULT_ACTIONS_CONFIG_V2 } from '@/action-menu/actions/record-actions/constants/DefaultActionsConfigV2';
|
||||
import { WORKFLOW_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowActionsConfig';
|
||||
import { WORKFLOW_RUNS_ACTIONS_CONFIG } from '@/action-menu/actions/record-actions/constants/WorkflowRunsActionsConfig';
|
||||
@ -6,10 +5,7 @@ import { WORKFLOW_VERSIONS_ACTIONS_CONFIG } from '@/action-menu/actions/record-a
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
|
||||
export const getActionConfig = (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
isCommandMenuV2Enabled: boolean,
|
||||
) => {
|
||||
export const getActionConfig = (objectMetadataItem: ObjectMetadataItem) => {
|
||||
switch (objectMetadataItem.nameSingular) {
|
||||
case CoreObjectNameSingular.Workflow:
|
||||
return WORKFLOW_ACTIONS_CONFIG;
|
||||
@ -18,8 +14,6 @@ export const getActionConfig = (
|
||||
case CoreObjectNameSingular.WorkflowRun:
|
||||
return WORKFLOW_RUNS_ACTIONS_CONFIG;
|
||||
default:
|
||||
return isCommandMenuV2Enabled
|
||||
? DEFAULT_ACTIONS_CONFIG_V2
|
||||
: DEFAULT_ACTIONS_CONFIG_V1;
|
||||
return DEFAULT_ACTIONS_CONFIG_V2;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { Button, getOsControlSymbol } from 'twenty-ui';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
export const CmdEnterActionButton = ({
|
||||
title,
|
||||
@ -15,15 +12,10 @@ export const CmdEnterActionButton = ({
|
||||
onClick: () => void;
|
||||
disabled?: boolean;
|
||||
}) => {
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
useScopedHotkeys(
|
||||
[`${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`],
|
||||
() => onClick(),
|
||||
isCommandMenuV2Enabled
|
||||
? AppHotkeyScope.CommandMenuOpen
|
||||
: RightDrawerHotkeyScope.RightDrawer,
|
||||
AppHotkeyScope.CommandMenuOpen,
|
||||
[onClick],
|
||||
);
|
||||
|
||||
|
||||
@ -3,10 +3,8 @@ import { MultipleRecordsActionKeys } from '@/action-menu/actions/record-actions/
|
||||
import { RecordAgnosticActionMenuEntriesSetter } from '@/action-menu/actions/record-agnostic-actions/components/RecordAgnosticActionMenuEntriesSetter';
|
||||
import { RunWorkflowRecordAgnosticActionMenuEntriesSetter } from '@/action-menu/actions/record-agnostic-actions/components/RunWorkflowRecordAgnosticActionMenuEntriesSetter';
|
||||
import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMenuConfirmationModals';
|
||||
import { RecordIndexActionMenuBar } from '@/action-menu/components/RecordIndexActionMenuBar';
|
||||
import { RecordIndexActionMenuButtons } from '@/action-menu/components/RecordIndexActionMenuButtons';
|
||||
import { RecordIndexActionMenuDropdown } from '@/action-menu/components/RecordIndexActionMenuDropdown';
|
||||
import { RecordIndexActionMenuEffect } from '@/action-menu/components/RecordIndexActionMenuEffect';
|
||||
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||
import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
|
||||
import { isRecordIndexLoadMoreLockedComponentState } from '@/object-record/record-index/states/isRecordIndexLoadMoreLockedComponentState';
|
||||
@ -21,10 +19,6 @@ export const RecordIndexActionMenu = ({ indexId }: { indexId: string }) => {
|
||||
contextStoreCurrentObjectMetadataItemComponentState,
|
||||
);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const isWorkflowEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsWorkflowEnabled,
|
||||
);
|
||||
@ -54,14 +48,9 @@ export const RecordIndexActionMenu = ({ indexId }: { indexId: string }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
{isCommandMenuV2Enabled ? (
|
||||
<>{!isMobile && <RecordIndexActionMenuButtons />}</>
|
||||
) : (
|
||||
<RecordIndexActionMenuBar />
|
||||
)}
|
||||
{!isMobile && <RecordIndexActionMenuButtons />}
|
||||
<RecordIndexActionMenuDropdown />
|
||||
<ActionMenuConfirmationModals />
|
||||
<RecordIndexActionMenuEffect />
|
||||
<RecordActionMenuEntriesSetter />
|
||||
<RecordAgnosticActionMenuEntriesSetter />
|
||||
{isWorkflowEnabled && (
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
import { RecordIndexActionMenuBarAllActionsButton } from '@/action-menu/components/RecordIndexActionMenuBarAllActionsButton';
|
||||
import { RecordIndexActionMenuBarEntry } from '@/action-menu/components/RecordIndexActionMenuBarEntry';
|
||||
import { actionMenuEntriesComponentSelector } from '@/action-menu/states/actionMenuEntriesComponentSelector';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { ActionBarHotkeyScope } from '@/action-menu/types/ActionBarHotKeyScope';
|
||||
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { BottomBar } from '@/ui/layout/bottom-bar/components/BottomBar';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledLabel = styled.div`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export const RecordIndexActionMenuBar = () => {
|
||||
const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
|
||||
contextStoreNumberOfSelectedRecordsComponentState,
|
||||
);
|
||||
|
||||
const actionMenuId = useAvailableComponentInstanceIdOrThrow(
|
||||
ActionMenuComponentInstanceContext,
|
||||
);
|
||||
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
actionMenuEntriesComponentSelector,
|
||||
);
|
||||
|
||||
const pinnedEntries = actionMenuEntries.filter((entry) => entry.isPinned);
|
||||
if (contextStoreNumberOfSelectedRecords === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<BottomBar
|
||||
bottomBarId={getActionBarIdFromActionMenuId(actionMenuId)}
|
||||
bottomBarHotkeyScopeFromParent={{ scope: ActionBarHotkeyScope.ActionBar }}
|
||||
>
|
||||
<StyledLabel>{contextStoreNumberOfSelectedRecords} selected:</StyledLabel>
|
||||
{pinnedEntries.map((entry, index) => (
|
||||
<RecordIndexActionMenuBarEntry key={index} entry={entry} />
|
||||
))}
|
||||
<RecordIndexActionMenuBarAllActionsButton />
|
||||
</BottomBar>
|
||||
);
|
||||
};
|
||||
@ -1,76 +0,0 @@
|
||||
import { useActionMenu } from '@/action-menu/hooks/useActionMenu';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
|
||||
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
||||
import { useEffect } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
export const RecordIndexActionMenuEffect = () => {
|
||||
const contextStoreNumberOfSelectedRecords = useRecoilComponentValueV2(
|
||||
contextStoreNumberOfSelectedRecordsComponentState,
|
||||
);
|
||||
|
||||
const actionMenuId = useAvailableComponentInstanceIdOrThrow(
|
||||
ActionMenuComponentInstanceContext,
|
||||
);
|
||||
|
||||
const { openActionBar, closeActionBar } = useActionMenu(actionMenuId);
|
||||
|
||||
// Using closeActionBar here was causing a bug because it goes back to the
|
||||
// previous hotkey scope, and we don't want that here.
|
||||
const setIsBottomBarOpened = useSetRecoilComponentStateV2(
|
||||
isBottomBarOpenedComponentState,
|
||||
getActionBarIdFromActionMenuId(actionMenuId),
|
||||
);
|
||||
|
||||
const isDropdownOpen = useRecoilValue(
|
||||
extractComponentState(
|
||||
isDropdownOpenComponentState,
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuId),
|
||||
),
|
||||
);
|
||||
const { isRightDrawerOpen } = useRightDrawer();
|
||||
|
||||
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
contextStoreNumberOfSelectedRecords > 0 &&
|
||||
!isDropdownOpen &&
|
||||
!isRightDrawerOpen &&
|
||||
!isCommandMenuOpened
|
||||
) {
|
||||
// We only handle opening the ActionMenuBar here, not the Dropdown.
|
||||
// The Dropdown is already managed by sync handlers for events like
|
||||
// right-click to open and click outside to close.
|
||||
openActionBar();
|
||||
}
|
||||
if (contextStoreNumberOfSelectedRecords === 0 && isDropdownOpen) {
|
||||
closeActionBar();
|
||||
}
|
||||
}, [
|
||||
contextStoreNumberOfSelectedRecords,
|
||||
openActionBar,
|
||||
closeActionBar,
|
||||
isDropdownOpen,
|
||||
isRightDrawerOpen,
|
||||
isCommandMenuOpened,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRightDrawerOpen || isCommandMenuOpened) {
|
||||
setIsBottomBarOpened(false);
|
||||
}
|
||||
}, [isRightDrawerOpen, isCommandMenuOpened, setIsBottomBarOpened]);
|
||||
|
||||
return null;
|
||||
};
|
||||
@ -5,40 +5,19 @@ import { ActionMenuConfirmationModals } from '@/action-menu/components/ActionMen
|
||||
import { RecordShowActionMenuButtons } from '@/action-menu/components/RecordShowActionMenuButtons';
|
||||
import { ActionMenuContext } from '@/action-menu/contexts/ActionMenuContext';
|
||||
import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { RecordShowPageBaseHeader } from '~/pages/object-record/RecordShowPageBaseHeader';
|
||||
|
||||
export const RecordShowActionMenu = ({
|
||||
isFavorite,
|
||||
record,
|
||||
objectMetadataItem,
|
||||
objectNameSingular,
|
||||
handleFavoriteButtonClick,
|
||||
}: {
|
||||
isFavorite: boolean;
|
||||
record: ObjectRecord | undefined;
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
objectNameSingular: string;
|
||||
handleFavoriteButtonClick: () => void;
|
||||
}) => {
|
||||
export const RecordShowActionMenu = () => {
|
||||
const contextStoreCurrentObjectMetadataItem = useRecoilComponentValueV2(
|
||||
contextStoreCurrentObjectMetadataItemComponentState,
|
||||
);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const isWorkflowEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsWorkflowEnabled,
|
||||
);
|
||||
|
||||
// TODO: refactor RecordShowPageBaseHeader to use the context store
|
||||
|
||||
return (
|
||||
<>
|
||||
{contextStoreCurrentObjectMetadataItem && (
|
||||
@ -48,19 +27,7 @@ export const RecordShowActionMenu = ({
|
||||
onActionExecutedCallback: () => {},
|
||||
}}
|
||||
>
|
||||
{isCommandMenuV2Enabled ? (
|
||||
<RecordShowActionMenuButtons />
|
||||
) : (
|
||||
<RecordShowPageBaseHeader
|
||||
{...{
|
||||
isFavorite,
|
||||
record,
|
||||
objectMetadataItem,
|
||||
objectNameSingular,
|
||||
handleFavoriteButtonClick,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<ActionMenuConfirmationModals />
|
||||
<RecordActionMenuEntriesSetter />
|
||||
<RecordAgnosticActionMenuEntriesSetter />
|
||||
|
||||
@ -6,17 +6,14 @@ import { getRightDrawerActionMenuDropdownIdFromActionMenuId } from '@/action-men
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { i18n } from '@lingui/core';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { Button, MenuItem, getOsControlSymbol } from 'twenty-ui';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { Button, getOsControlSymbol, MenuItem } from 'twenty-ui';
|
||||
|
||||
export const RightDrawerActionMenuDropdown = () => {
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
@ -31,10 +28,6 @@ export const RightDrawerActionMenuDropdown = () => {
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape, 'ctrl+o,meta+o'],
|
||||
() => {
|
||||
@ -53,9 +46,7 @@ export const RightDrawerActionMenuDropdown = () => {
|
||||
getRightDrawerActionMenuDropdownIdFromActionMenuId(actionMenuId),
|
||||
);
|
||||
},
|
||||
isCommandMenuV2Enabled
|
||||
? AppHotkeyScope.CommandMenuOpen
|
||||
: RightDrawerHotkeyScope.RightDrawer,
|
||||
AppHotkeyScope.CommandMenuOpen,
|
||||
[openDropdown],
|
||||
);
|
||||
|
||||
|
||||
@ -1,141 +0,0 @@
|
||||
import { RecordIndexActionMenuBar } from '@/action-menu/components/RecordIndexActionMenuBar';
|
||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import {
|
||||
ActionMenuEntry,
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { ContextStoreComponentInstanceContext } from '@/context-store/states/contexts/ContextStoreComponentInstanceContext';
|
||||
import { RecordFilterGroupsComponentInstanceContext } from '@/object-record/record-filter-group/states/context/RecordFilterGroupsComponentInstanceContext';
|
||||
import { RecordFiltersComponentInstanceContext } from '@/object-record/record-filter/states/context/RecordFiltersComponentInstanceContext';
|
||||
import { RecordSortsComponentInstanceContext } from '@/object-record/record-sort/states/context/RecordSortsComponentInstanceContext';
|
||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { expect, jest } from '@storybook/jest';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { userEvent, waitFor, within } from '@storybook/test';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { IconTrash, RouterDecorator } from 'twenty-ui';
|
||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
||||
|
||||
const deleteMock = jest.fn();
|
||||
|
||||
const meta: Meta<typeof RecordIndexActionMenuBar> = {
|
||||
title: 'Modules/ActionMenu/RecordIndexActionMenuBar',
|
||||
component: RecordIndexActionMenuBar,
|
||||
decorators: [
|
||||
RouterDecorator,
|
||||
I18nFrontDecorator,
|
||||
(Story) => (
|
||||
<RecordFilterGroupsComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'story-action-menu' }}
|
||||
>
|
||||
<RecordFiltersComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'story-action-menu' }}
|
||||
>
|
||||
<RecordSortsComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'story-action-menu' }}
|
||||
>
|
||||
<ContextStoreComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'story-action-menu' }}
|
||||
>
|
||||
<RecoilRoot
|
||||
initializeState={({ set }) => {
|
||||
set(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId: 'story-action-menu',
|
||||
}),
|
||||
{
|
||||
mode: 'selection',
|
||||
selectedRecordIds: ['1', '2', '3'],
|
||||
},
|
||||
);
|
||||
set(
|
||||
contextStoreNumberOfSelectedRecordsComponentState.atomFamily(
|
||||
{
|
||||
instanceId: 'story-action-menu',
|
||||
},
|
||||
),
|
||||
3,
|
||||
);
|
||||
const map = new Map<string, ActionMenuEntry>();
|
||||
map.set('delete', {
|
||||
isPinned: true,
|
||||
scope: ActionMenuEntryScope.RecordSelection,
|
||||
type: ActionMenuEntryType.Standard,
|
||||
key: 'delete',
|
||||
label: msg`Delete`,
|
||||
position: 0,
|
||||
Icon: IconTrash,
|
||||
onClick: deleteMock,
|
||||
});
|
||||
set(
|
||||
actionMenuEntriesComponentState.atomFamily({
|
||||
instanceId: 'story-action-menu',
|
||||
}),
|
||||
map,
|
||||
);
|
||||
set(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId:
|
||||
getActionBarIdFromActionMenuId('story-action-menu'),
|
||||
}),
|
||||
true,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<ActionMenuComponentInstanceContext.Provider
|
||||
value={{ instanceId: 'story-action-menu' }}
|
||||
>
|
||||
<Story />
|
||||
</ActionMenuComponentInstanceContext.Provider>
|
||||
</RecoilRoot>
|
||||
</ContextStoreComponentInstanceContext.Provider>
|
||||
</RecordSortsComponentInstanceContext.Provider>
|
||||
</RecordFiltersComponentInstanceContext.Provider>
|
||||
</RecordFilterGroupsComponentInstanceContext.Provider>
|
||||
),
|
||||
],
|
||||
args: {
|
||||
actionMenuId: 'story-action-menu',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof RecordIndexActionMenuBar>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
actionMenuId: 'story-action-menu',
|
||||
},
|
||||
};
|
||||
|
||||
export const WithCustomSelection: Story = {
|
||||
args: {
|
||||
actionMenuId: 'story-action-menu',
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const selectionText = await canvas.findByText('3 selected:');
|
||||
expect(selectionText).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
export const WithButtonClicks: Story = {
|
||||
args: {
|
||||
actionMenuId: 'story-action-menu',
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
const deleteButton = await canvas.findByText('Delete');
|
||||
await userEvent.click(deleteButton);
|
||||
await waitFor(() => {
|
||||
expect(deleteMock).toHaveBeenCalled();
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -1,82 +0,0 @@
|
||||
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
|
||||
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react';
|
||||
import { useActionMenu } from '../useActionMenu';
|
||||
|
||||
const openBottomBar = jest.fn();
|
||||
const closeBottomBar = jest.fn();
|
||||
const openDropdown = jest.fn();
|
||||
const closeDropdown = jest.fn();
|
||||
|
||||
jest.mock('@/ui/layout/bottom-bar/hooks/useBottomBar', () => ({
|
||||
useBottomBar: jest.fn(() => ({
|
||||
openBottomBar: openBottomBar,
|
||||
closeBottomBar: closeBottomBar,
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('@/ui/layout/dropdown/hooks/useDropdownV2', () => ({
|
||||
useDropdownV2: jest.fn(() => ({
|
||||
openDropdown: openDropdown,
|
||||
closeDropdown: closeDropdown,
|
||||
})),
|
||||
}));
|
||||
|
||||
describe('useActionMenu', () => {
|
||||
const actionMenuId = 'test-action-menu';
|
||||
const actionBarId = getActionBarIdFromActionMenuId(actionMenuId);
|
||||
const actionMenuDropdownId =
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuId);
|
||||
|
||||
it('should return the correct functions', () => {
|
||||
const { result } = renderHook(() => useActionMenu(actionMenuId));
|
||||
|
||||
expect(result.current).toHaveProperty('openActionMenuDropdown');
|
||||
expect(result.current).toHaveProperty('openActionBar');
|
||||
expect(result.current).toHaveProperty('closeActionBar');
|
||||
expect(result.current).toHaveProperty('closeActionMenuDropdown');
|
||||
});
|
||||
|
||||
it('should call the correct functions when opening action menu dropdown', () => {
|
||||
const { result } = renderHook(() => useActionMenu(actionMenuId));
|
||||
|
||||
act(() => {
|
||||
result.current.openActionMenuDropdown();
|
||||
});
|
||||
|
||||
expect(closeBottomBar).toHaveBeenCalledWith(actionBarId);
|
||||
expect(openDropdown).toHaveBeenCalledWith(actionMenuDropdownId);
|
||||
});
|
||||
|
||||
it('should call the correct functions when opening action bar', () => {
|
||||
const { result } = renderHook(() => useActionMenu(actionMenuId));
|
||||
|
||||
act(() => {
|
||||
result.current.openActionBar();
|
||||
});
|
||||
|
||||
expect(closeDropdown).toHaveBeenCalledWith(actionMenuDropdownId);
|
||||
expect(openBottomBar).toHaveBeenCalledWith(actionBarId);
|
||||
});
|
||||
|
||||
it('should call the correct function when closing action menu dropdown', () => {
|
||||
const { result } = renderHook(() => useActionMenu(actionMenuId));
|
||||
|
||||
act(() => {
|
||||
result.current.closeActionMenuDropdown();
|
||||
});
|
||||
|
||||
expect(closeDropdown).toHaveBeenCalledWith(actionMenuDropdownId);
|
||||
});
|
||||
|
||||
it('should call the correct function when closing action bar', () => {
|
||||
const { result } = renderHook(() => useActionMenu(actionMenuId));
|
||||
|
||||
act(() => {
|
||||
result.current.closeActionBar();
|
||||
});
|
||||
|
||||
expect(closeBottomBar).toHaveBeenCalledWith(actionBarId);
|
||||
});
|
||||
});
|
||||
@ -1,38 +0,0 @@
|
||||
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
|
||||
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
|
||||
import { useBottomBar } from '@/ui/layout/bottom-bar/hooks/useBottomBar';
|
||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
|
||||
export const useActionMenu = (actionMenuId: string) => {
|
||||
const { openDropdown, closeDropdown } = useDropdownV2();
|
||||
const { openBottomBar, closeBottomBar } = useBottomBar();
|
||||
|
||||
const actionBarId = getActionBarIdFromActionMenuId(actionMenuId);
|
||||
const actionMenuDropdownId =
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuId);
|
||||
|
||||
const openActionMenuDropdown = () => {
|
||||
closeBottomBar(actionBarId);
|
||||
openDropdown(actionMenuDropdownId);
|
||||
};
|
||||
|
||||
const openActionBar = () => {
|
||||
closeDropdown(actionMenuDropdownId);
|
||||
openBottomBar(actionBarId);
|
||||
};
|
||||
|
||||
const closeActionMenuDropdown = () => {
|
||||
closeDropdown(actionMenuDropdownId);
|
||||
};
|
||||
|
||||
const closeActionBar = () => {
|
||||
closeBottomBar(actionBarId);
|
||||
};
|
||||
|
||||
return {
|
||||
openActionMenuDropdown,
|
||||
openActionBar,
|
||||
closeActionBar,
|
||||
closeActionMenuDropdown,
|
||||
};
|
||||
};
|
||||
@ -1,7 +1,6 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRef } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconCheck, IconQuestionMark, IconX } from 'twenty-ui';
|
||||
|
||||
import { CalendarEventParticipant } from '@/activities/calendar/types/CalendarEventParticipant';
|
||||
@ -9,7 +8,6 @@ import { ParticipantChip } from '@/activities/components/ParticipantChip';
|
||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||
import { EllipsisDisplay } from '@/ui/field/display/components/EllipsisDisplay';
|
||||
import { ExpandableList } from '@/ui/layout/expandable-list/components/ExpandableList';
|
||||
import { isRightDrawerAnimationCompletedState } from '@/ui/layout/right-drawer/states/isRightDrawerAnimationCompletedState';
|
||||
|
||||
const StyledInlineCellBaseContainer = styled.div`
|
||||
align-items: center;
|
||||
@ -68,9 +66,6 @@ export const CalendarEventParticipantsResponseStatusField = ({
|
||||
participants: CalendarEventParticipant[];
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const isRightDrawerAnimationCompleted = useRecoilValue(
|
||||
isRightDrawerAnimationCompletedState,
|
||||
);
|
||||
|
||||
const Icon = {
|
||||
Yes: <IconCheck stroke={theme.icon.stroke.sm} />,
|
||||
@ -103,9 +98,7 @@ export const CalendarEventParticipantsResponseStatusField = ({
|
||||
</StyledLabelContainer>
|
||||
</StyledLabelAndIconContainer>
|
||||
<StyledDiv ref={participantsContainerRef}>
|
||||
{isRightDrawerAnimationCompleted && (
|
||||
<ExpandableList isChipCountDisplayed>{styledChips}</ExpandableList>
|
||||
)}
|
||||
</StyledDiv>
|
||||
</StyledInlineCellBaseContainer>
|
||||
</StyledPropertyBox>
|
||||
|
||||
@ -6,13 +6,11 @@ import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { CalendarCurrentEventCursor } from '@/activities/calendar/components/CalendarCurrentEventCursor';
|
||||
import { CalendarContext } from '@/activities/calendar/contexts/CalendarContext';
|
||||
import { useOpenCalendarEventRightDrawer } from '@/activities/calendar/right-drawer/hooks/useOpenCalendarEventRightDrawer';
|
||||
import { getCalendarEventEndDate } from '@/activities/calendar/utils/getCalendarEventEndDate';
|
||||
import { getCalendarEventStartDate } from '@/activities/calendar/utils/getCalendarEventStartDate';
|
||||
import { hasCalendarEventEnded } from '@/activities/calendar/utils/hasCalendarEventEnded';
|
||||
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import {
|
||||
Avatar,
|
||||
@ -24,7 +22,6 @@ import {
|
||||
} from 'twenty-ui';
|
||||
import {
|
||||
CalendarChannelVisibility,
|
||||
FeatureFlagKey,
|
||||
TimelineCalendarEvent,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
@ -117,11 +114,7 @@ export const CalendarEventRow = ({
|
||||
const theme = useTheme();
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
const { displayCurrentEventCursor = false } = useContext(CalendarContext);
|
||||
const { openCalendarEventRightDrawer } = useOpenCalendarEventRightDrawer();
|
||||
const { openCalendarEventInCommandMenu } = useCommandMenu();
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const startsAt = getCalendarEventStartDate(calendarEvent);
|
||||
const endsAt = getCalendarEventEndDate(calendarEvent);
|
||||
@ -145,11 +138,7 @@ export const CalendarEventRow = ({
|
||||
onClick={
|
||||
showTitle
|
||||
? () => {
|
||||
if (isCommandMenuV2Enabled) {
|
||||
openCalendarEventInCommandMenu(calendarEvent.id);
|
||||
} else {
|
||||
openCalendarEventRightDrawer(calendarEvent.id);
|
||||
}
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { CalendarEventDetails } from '@/activities/calendar/components/CalendarEventDetails';
|
||||
import { CalendarEventDetailsEffect } from '@/activities/calendar/components/CalendarEventDetailsEffect';
|
||||
import { FIND_ONE_CALENDAR_EVENT_OPERATION_SIGNATURE } from '@/activities/calendar/graphql/operation-signatures/FindOneCalendarEventOperationSignature';
|
||||
import { CalendarEvent } from '@/activities/calendar/types/CalendarEvent';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
||||
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
|
||||
|
||||
export const RightDrawerCalendarEvent = () => {
|
||||
const { upsertRecords } = useUpsertRecordsInStore();
|
||||
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||
|
||||
const { record: calendarEvent } = useFindOneRecord<CalendarEvent>({
|
||||
objectNameSingular:
|
||||
FIND_ONE_CALENDAR_EVENT_OPERATION_SIGNATURE.objectNameSingular,
|
||||
objectRecordId: viewableRecordId ?? '',
|
||||
recordGqlFields: FIND_ONE_CALENDAR_EVENT_OPERATION_SIGNATURE.fields,
|
||||
onCompleted: (record) => upsertRecords([record]),
|
||||
});
|
||||
|
||||
if (!calendarEvent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecordFieldValueSelectorContextProvider>
|
||||
<CalendarEventDetailsEffect record={calendarEvent} />
|
||||
<RecordValueSetterEffect recordId={calendarEvent.id} />
|
||||
<CalendarEventDetails calendarEvent={calendarEvent} />
|
||||
</RecordFieldValueSelectorContextProvider>
|
||||
);
|
||||
};
|
||||
@ -1,33 +0,0 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useOpenCalendarEventRightDrawer } from '@/activities/calendar/right-drawer/hooks/useOpenCalendarEventRightDrawer';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
|
||||
|
||||
describe('useOpenCalendarEventRightDrawer', () => {
|
||||
it('opens the right drawer with the calendar event', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
||||
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||
return {
|
||||
...useOpenCalendarEventRightDrawer(),
|
||||
isRightDrawerOpen,
|
||||
viewableRecordId,
|
||||
};
|
||||
},
|
||||
{ wrapper: RecoilRoot },
|
||||
);
|
||||
|
||||
expect(result.current.isRightDrawerOpen).toBe(false);
|
||||
expect(result.current.viewableRecordId).toBeNull();
|
||||
|
||||
act(() => {
|
||||
result.current.openCalendarEventRightDrawer('1234');
|
||||
});
|
||||
|
||||
expect(result.current.isRightDrawerOpen).toBe(true);
|
||||
expect(result.current.viewableRecordId).toBe('1234');
|
||||
});
|
||||
});
|
||||
@ -1,25 +0,0 @@
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { IconCalendarEvent } from 'twenty-ui';
|
||||
|
||||
export const useOpenCalendarEventRightDrawer = () => {
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
const setViewableRecordId = useSetRecoilState(viewableRecordIdState);
|
||||
|
||||
const openCalendarEventRightDrawer = (calendarEventId: string) => {
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
openRightDrawer(RightDrawerPages.ViewCalendarEvent, {
|
||||
title: 'Calendar Event',
|
||||
Icon: IconCalendarEvent,
|
||||
});
|
||||
setViewableRecordId(calendarEventId);
|
||||
};
|
||||
|
||||
return { openCalendarEventRightDrawer };
|
||||
};
|
||||
@ -1,38 +1,35 @@
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { PartialBlock } from '@blocknote/core';
|
||||
import { useCreateBlockNote } from '@blocknote/react';
|
||||
import { isArray, isNonEmptyString } from '@sniptt/guards';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useRecoilCallback, useRecoilState } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { BLOCK_SCHEMA } from '@/activities/blocks/constants/Schema';
|
||||
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
|
||||
import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity';
|
||||
import { canCreateActivityState } from '@/activities/states/canCreateActivityState';
|
||||
import { ActivityEditorHotkeyScope } from '@/activities/types/ActivityEditorHotkeyScope';
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { isNonTextWritingKey } from '@/ui/utilities/hotkey/utils/isNonTextWritingKey';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { BLOCK_SCHEMA } from '@/activities/blocks/constants/Schema';
|
||||
import { ActivityRichTextEditorChangeOnActivityIdEffect } from '@/activities/components/ActivityRichTextEditorChangeOnActivityIdEffect';
|
||||
import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile';
|
||||
import { Note } from '@/activities/types/Note';
|
||||
import { Task } from '@/activities/types/Task';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { BlockEditor } from '@/ui/input/editor/components/BlockEditor';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { PartialBlock } from '@blocknote/core';
|
||||
import '@blocknote/core/fonts/inter.css';
|
||||
import '@blocknote/mantine/style.css';
|
||||
import { useCreateBlockNote } from '@blocknote/react';
|
||||
import '@blocknote/react/style.css';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { isArray, isNonEmptyString } from '@sniptt/guards';
|
||||
|
||||
type ActivityRichTextEditorProps = {
|
||||
activityId: string;
|
||||
@ -50,10 +47,6 @@ export const ActivityRichTextEditor = ({
|
||||
const cache = useApolloClient().cache;
|
||||
const activity = activityInStore as Task | Note | null;
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const { objectMetadataItem: objectMetadataItemActivity } =
|
||||
useObjectMetadataItem({
|
||||
objectNameSingular: activityObjectNameSingular,
|
||||
@ -280,9 +273,7 @@ export const ActivityRichTextEditor = ({
|
||||
editor.setTextCursorPosition(newBlockId, 'end');
|
||||
editor.focus();
|
||||
},
|
||||
isCommandMenuV2Enabled
|
||||
? AppHotkeyScope.CommandMenuOpen
|
||||
: RightDrawerHotkeyScope.RightDrawer,
|
||||
AppHotkeyScope.CommandMenuOpen,
|
||||
[],
|
||||
{
|
||||
preventDefault: false,
|
||||
|
||||
@ -1,54 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { copilotQueryState } from '@/activities/copilot/right-drawer/states/copilotQueryState';
|
||||
import {
|
||||
AutosizeTextInput,
|
||||
AutosizeTextInputVariant,
|
||||
} from '@/ui/input/components/AutosizeTextInput';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: flex-start;
|
||||
overflow-y: auto;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const StyledChatArea = styled.div`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: scroll;
|
||||
padding: ${({ theme }) => theme.spacing(6)};
|
||||
padding-bottom: 0px;
|
||||
`;
|
||||
|
||||
const StyledNewMessageArea = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: ${({ theme }) => theme.spacing(6)};
|
||||
padding-top: 0px;
|
||||
`;
|
||||
|
||||
export const RightDrawerAIChat = () => {
|
||||
const setCopilotQuery = useSetRecoilState(copilotQueryState);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<StyledChatArea>{/* TODO */}</StyledChatArea>
|
||||
<StyledNewMessageArea>
|
||||
<AutosizeTextInput
|
||||
autoFocus
|
||||
placeholder="Ask anything"
|
||||
variant={AutosizeTextInputVariant.Icon}
|
||||
onValidate={(text) => {
|
||||
setCopilotQuery(text);
|
||||
}}
|
||||
/>
|
||||
</StyledNewMessageArea>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -1,18 +0,0 @@
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { IconSparkles } from 'twenty-ui';
|
||||
|
||||
export const useOpenCopilotRightDrawer = () => {
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
return () => {
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
openRightDrawer(RightDrawerPages.Copilot, {
|
||||
title: 'Copilot',
|
||||
Icon: IconSparkles,
|
||||
});
|
||||
};
|
||||
};
|
||||
@ -1,6 +0,0 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
export const copilotQueryState = createState({
|
||||
key: 'activities/copilot-query',
|
||||
defaultValue: '',
|
||||
});
|
||||
@ -1,19 +1,10 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { Avatar, GRAY_SCALE } from 'twenty-ui';
|
||||
|
||||
import { ActivityRow } from '@/activities/components/ActivityRow';
|
||||
import { EmailThreadNotShared } from '@/activities/emails/components/EmailThreadNotShared';
|
||||
import { useEmailThread } from '@/activities/emails/hooks/useEmailThread';
|
||||
import { emailThreadIdWhenEmailThreadWasClosedState } from '@/activities/emails/states/lastViewableEmailThreadIdState';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import {
|
||||
FeatureFlagKey,
|
||||
MessageChannelVisibility,
|
||||
TimelineThread,
|
||||
} from '~/generated/graphql';
|
||||
import { MessageChannelVisibility, TimelineThread } from '~/generated/graphql';
|
||||
import { formatToHumanReadableDate } from '~/utils/date-utils';
|
||||
|
||||
const StyledHeading = styled.div<{ unread: boolean }>`
|
||||
@ -77,11 +68,7 @@ type EmailThreadPreviewProps = {
|
||||
};
|
||||
|
||||
export const EmailThreadPreview = ({ thread }: EmailThreadPreviewProps) => {
|
||||
const { openEmailThread } = useEmailThread();
|
||||
const { openEmailThreadInCommandMenu } = useCommandMenu();
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const visibility = thread.visibility;
|
||||
|
||||
@ -103,48 +90,19 @@ export const EmailThreadPreview = ({ thread }: EmailThreadPreviewProps) => {
|
||||
false,
|
||||
];
|
||||
|
||||
const { isSameEventThanRightDrawerClose } = useRightDrawer();
|
||||
|
||||
const handleThreadClick = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(event: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
|
||||
const clickJustTriggeredEmailDrawerClose =
|
||||
isSameEventThanRightDrawerClose(event.nativeEvent);
|
||||
|
||||
const emailThreadIdWhenEmailThreadWasClosed = snapshot
|
||||
.getLoadable(emailThreadIdWhenEmailThreadWasClosedState)
|
||||
.getValue();
|
||||
|
||||
const handleThreadClick = () => {
|
||||
const canOpen =
|
||||
thread.visibility === MessageChannelVisibility.SHARE_EVERYTHING &&
|
||||
(!clickJustTriggeredEmailDrawerClose ||
|
||||
emailThreadIdWhenEmailThreadWasClosed !== thread.id);
|
||||
thread.visibility === MessageChannelVisibility.SHARE_EVERYTHING;
|
||||
|
||||
if (canOpen) {
|
||||
if (isCommandMenuV2Enabled) {
|
||||
openEmailThreadInCommandMenu(thread.id);
|
||||
} else {
|
||||
openEmailThread(thread.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
[
|
||||
isCommandMenuV2Enabled,
|
||||
isSameEventThanRightDrawerClose,
|
||||
openEmailThread,
|
||||
openEmailThreadInCommandMenu,
|
||||
thread.id,
|
||||
thread.visibility,
|
||||
],
|
||||
);
|
||||
};
|
||||
|
||||
const isDisabled = visibility !== MessageChannelVisibility.SHARE_EVERYTHING;
|
||||
|
||||
return (
|
||||
<ActivityRow
|
||||
onClick={(event) => handleThreadClick(event)}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
<ActivityRow onClick={handleThreadClick} disabled={isDisabled}>
|
||||
<StyledHeading unread={!thread.read}>
|
||||
<StyledParticipantsContainer>
|
||||
<Avatar
|
||||
|
||||
@ -1,68 +0,0 @@
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { RecoilRoot, useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useEmailThread } from '@/activities/emails/hooks/useEmailThread';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
|
||||
|
||||
const viewableEmailThreadId = '1234';
|
||||
|
||||
describe('useEmailThread', () => {
|
||||
it('should open email thread', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const emailThread = useEmailThread();
|
||||
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
||||
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||
|
||||
return { ...emailThread, isRightDrawerOpen, viewableRecordId };
|
||||
},
|
||||
{ wrapper: RecoilRoot },
|
||||
);
|
||||
|
||||
expect(result.current.isRightDrawerOpen).toBe(false);
|
||||
expect(result.current.viewableRecordId).toBeNull();
|
||||
|
||||
act(() => {
|
||||
result.current.openEmailThread(viewableEmailThreadId);
|
||||
});
|
||||
|
||||
expect(result.current.isRightDrawerOpen).toBe(true);
|
||||
expect(result.current.viewableRecordId).toBe(viewableEmailThreadId);
|
||||
});
|
||||
|
||||
it('should close email thread if trying to open the same thread id', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const emailThread = useEmailThread();
|
||||
const [isRightDrawerOpen, setIsRightDrawerOpen] = useRecoilState(
|
||||
isRightDrawerOpenState,
|
||||
);
|
||||
const [viewableRecordId, setViewableRecordId] = useRecoilState(
|
||||
viewableRecordIdState,
|
||||
);
|
||||
|
||||
return {
|
||||
...emailThread,
|
||||
isRightDrawerOpen,
|
||||
viewableRecordId,
|
||||
setIsRightDrawerOpen,
|
||||
setViewableRecordId,
|
||||
};
|
||||
},
|
||||
{ wrapper: RecoilRoot },
|
||||
);
|
||||
|
||||
act(() => {
|
||||
result.current.setIsRightDrawerOpen(true);
|
||||
result.current.setViewableRecordId(viewableEmailThreadId);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
result.current.openEmailThread(viewableEmailThreadId);
|
||||
});
|
||||
|
||||
expect(result.current.isRightDrawerOpen).toBe(false);
|
||||
expect(result.current.viewableRecordId).toBeNull();
|
||||
});
|
||||
});
|
||||
@ -1,36 +0,0 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { useOpenEmailThreadRightDrawer } from '@/activities/emails/right-drawer/hooks/useOpenEmailThreadRightDrawer';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
|
||||
|
||||
export const useEmailThread = () => {
|
||||
const { closeRightDrawer } = useRightDrawer();
|
||||
const openEmailThreadRightDrawer = useOpenEmailThreadRightDrawer();
|
||||
|
||||
const openEmailThread = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(threadId: string) => {
|
||||
const isRightDrawerOpen = snapshot
|
||||
.getLoadable(isRightDrawerOpenState)
|
||||
.getValue();
|
||||
|
||||
const viewableEmailThreadId = snapshot
|
||||
.getLoadable(viewableRecordIdState)
|
||||
.getValue();
|
||||
|
||||
if (isRightDrawerOpen && viewableEmailThreadId === threadId) {
|
||||
set(viewableRecordIdState, null);
|
||||
closeRightDrawer();
|
||||
return;
|
||||
}
|
||||
|
||||
openEmailThreadRightDrawer();
|
||||
set(viewableRecordIdState, threadId);
|
||||
},
|
||||
[closeRightDrawer, openEmailThreadRightDrawer],
|
||||
);
|
||||
|
||||
return { openEmailThread };
|
||||
};
|
||||
@ -1,44 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
import { Button, IconArrowsVertical } from 'twenty-ui';
|
||||
|
||||
import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMessage';
|
||||
import { EmailThreadMessageWithSender } from '@/activities/emails/types/EmailThreadMessageWithSender';
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
padding: 16px 24px;
|
||||
`;
|
||||
|
||||
export const IntermediaryMessages = ({
|
||||
messages,
|
||||
}: {
|
||||
messages: EmailThreadMessageWithSender[];
|
||||
}) => {
|
||||
const [areMessagesOpen, setAreMessagesOpen] = useState(false);
|
||||
|
||||
if (messages.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return areMessagesOpen ? (
|
||||
messages.map((message) => (
|
||||
<EmailThreadMessage
|
||||
key={message.id}
|
||||
sender={message.sender}
|
||||
participants={message.messageParticipants}
|
||||
body={message.text}
|
||||
sentAt={message.receivedAt}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
Icon={IconArrowsVertical}
|
||||
title={`${messages.length} email${messages.length > 1 ? 's' : ''}`}
|
||||
size="small"
|
||||
onClick={() => setAreMessagesOpen(true)}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
);
|
||||
};
|
||||
@ -1,185 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { CustomResolverFetchMoreLoader } from '@/activities/components/CustomResolverFetchMoreLoader';
|
||||
import { EmailLoader } from '@/activities/emails/components/EmailLoader';
|
||||
import { EmailThreadHeader } from '@/activities/emails/components/EmailThreadHeader';
|
||||
import { EmailThreadMessage } from '@/activities/emails/components/EmailThreadMessage';
|
||||
import { IntermediaryMessages } from '@/activities/emails/right-drawer/components/IntermediaryMessages';
|
||||
import { useRightDrawerEmailThread } from '@/activities/emails/right-drawer/hooks/useRightDrawerEmailThread';
|
||||
import { emailThreadIdWhenEmailThreadWasClosedState } from '@/activities/emails/states/lastViewableEmailThreadIdState';
|
||||
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
|
||||
import { messageThreadState } from '@/ui/layout/right-drawer/states/messageThreadState';
|
||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { assertUnreachable } from '@/workflow/utils/assertUnreachable';
|
||||
import { ConnectedAccountProvider } from 'twenty-shared';
|
||||
import { Button, IconArrowBackUp } from 'twenty-ui';
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
height: 85%;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
const StyledButtonContainer = styled.div<{ isMobile: boolean }>`
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border-top: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
height: ${({ isMobile }) => (isMobile ? '100px' : '50px')};
|
||||
padding: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
`;
|
||||
|
||||
export const RightDrawerEmailThread = () => {
|
||||
const setMessageThread = useSetRecoilState(messageThreadState);
|
||||
const isMobile = useIsMobile();
|
||||
const {
|
||||
thread,
|
||||
messages,
|
||||
fetchMoreMessages,
|
||||
threadLoading,
|
||||
messageThreadExternalId,
|
||||
connectedAccountHandle,
|
||||
messageChannelLoading,
|
||||
connectedAccountProvider,
|
||||
lastMessageExternalId,
|
||||
} = useRightDrawerEmailThread();
|
||||
|
||||
useEffect(() => {
|
||||
if (!messages[0]?.messageThread) {
|
||||
return;
|
||||
}
|
||||
setMessageThread(messages[0]?.messageThread);
|
||||
});
|
||||
|
||||
const { useRegisterClickOutsideListenerCallback } = useClickOutsideListener(
|
||||
RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID,
|
||||
);
|
||||
|
||||
useRegisterClickOutsideListenerCallback({
|
||||
callbackId:
|
||||
'EmailThreadClickOutsideCallBack-' + (thread?.id ?? 'no-thread-id'),
|
||||
callbackFunction: useRecoilCallback(
|
||||
({ set }) =>
|
||||
() => {
|
||||
set(
|
||||
emailThreadIdWhenEmailThreadWasClosedState,
|
||||
thread?.id ?? 'no-thread-id',
|
||||
);
|
||||
},
|
||||
[thread],
|
||||
),
|
||||
});
|
||||
|
||||
const messagesCount = messages.length;
|
||||
const is5OrMoreMessages = messagesCount >= 5;
|
||||
const firstMessages = messages.slice(
|
||||
0,
|
||||
is5OrMoreMessages ? 2 : messagesCount - 1,
|
||||
);
|
||||
const intermediaryMessages = is5OrMoreMessages
|
||||
? messages.slice(2, messagesCount - 1)
|
||||
: [];
|
||||
const lastMessage = messages[messagesCount - 1];
|
||||
const subject = messages[0]?.subject;
|
||||
|
||||
const canReply = useMemo(() => {
|
||||
return (
|
||||
connectedAccountHandle &&
|
||||
connectedAccountProvider &&
|
||||
lastMessage &&
|
||||
messageThreadExternalId != null
|
||||
);
|
||||
}, [
|
||||
connectedAccountHandle,
|
||||
connectedAccountProvider,
|
||||
lastMessage,
|
||||
messageThreadExternalId,
|
||||
]);
|
||||
|
||||
const handleReplyClick = () => {
|
||||
if (!canReply) {
|
||||
return;
|
||||
}
|
||||
|
||||
let url: string;
|
||||
switch (connectedAccountProvider) {
|
||||
case ConnectedAccountProvider.MICROSOFT:
|
||||
url = `https://outlook.office.com/mail/deeplink?ItemID=${lastMessageExternalId}`;
|
||||
window.open(url, '_blank');
|
||||
break;
|
||||
case ConnectedAccountProvider.GOOGLE:
|
||||
url = `https://mail.google.com/mail/?authuser=${connectedAccountHandle}#all/${messageThreadExternalId}`;
|
||||
window.open(url, '_blank');
|
||||
break;
|
||||
case null:
|
||||
throw new Error('Account provider not provided');
|
||||
default:
|
||||
assertUnreachable(connectedAccountProvider);
|
||||
}
|
||||
};
|
||||
if (!thread || !messages.length) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<StyledWrapper>
|
||||
<StyledContainer>
|
||||
{threadLoading ? (
|
||||
<EmailLoader loadingText="Loading thread" />
|
||||
) : (
|
||||
<>
|
||||
<EmailThreadHeader
|
||||
subject={subject}
|
||||
lastMessageSentAt={lastMessage.receivedAt}
|
||||
/>
|
||||
{firstMessages.map((message) => (
|
||||
<EmailThreadMessage
|
||||
key={message.id}
|
||||
sender={message.sender}
|
||||
participants={message.messageParticipants}
|
||||
body={message.text}
|
||||
sentAt={message.receivedAt}
|
||||
/>
|
||||
))}
|
||||
<IntermediaryMessages messages={intermediaryMessages} />
|
||||
<EmailThreadMessage
|
||||
key={lastMessage.id}
|
||||
sender={lastMessage.sender}
|
||||
participants={lastMessage.messageParticipants}
|
||||
body={lastMessage.text}
|
||||
sentAt={lastMessage.receivedAt}
|
||||
isExpanded
|
||||
/>
|
||||
<CustomResolverFetchMoreLoader
|
||||
loading={threadLoading}
|
||||
onLastRowVisible={fetchMoreMessages}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</StyledContainer>
|
||||
{canReply && !messageChannelLoading && (
|
||||
<StyledButtonContainer isMobile={isMobile}>
|
||||
<Button
|
||||
onClick={handleReplyClick}
|
||||
title="Reply"
|
||||
Icon={IconArrowBackUp}
|
||||
disabled={!canReply}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
)}
|
||||
</StyledWrapper>
|
||||
);
|
||||
};
|
||||
@ -1,40 +0,0 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
|
||||
import { useOpenEmailThreadRightDrawer } from '@/activities/emails/right-drawer/hooks/useOpenEmailThreadRightDrawer';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { IconMail } from 'twenty-ui';
|
||||
|
||||
const mockOpenRightDrawer = jest.fn();
|
||||
const mockSetHotkeyScope = jest.fn();
|
||||
|
||||
jest.mock('@/ui/layout/right-drawer/hooks/useRightDrawer', () => ({
|
||||
useRightDrawer: () => ({
|
||||
openRightDrawer: mockOpenRightDrawer,
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('@/ui/utilities/hotkey/hooks/useSetHotkeyScope', () => ({
|
||||
useSetHotkeyScope: () => mockSetHotkeyScope,
|
||||
}));
|
||||
|
||||
test('useOpenEmailThreadRightDrawer opens the email thread right drawer', () => {
|
||||
const { result } = renderHook(() => useOpenEmailThreadRightDrawer());
|
||||
|
||||
act(() => {
|
||||
result.current();
|
||||
});
|
||||
|
||||
expect(mockSetHotkeyScope).toHaveBeenCalledWith(
|
||||
RightDrawerHotkeyScope.RightDrawer,
|
||||
{ goto: false },
|
||||
);
|
||||
expect(mockOpenRightDrawer).toHaveBeenCalledWith(
|
||||
RightDrawerPages.ViewEmailThread,
|
||||
{
|
||||
title: 'Email Thread',
|
||||
Icon: IconMail,
|
||||
},
|
||||
);
|
||||
});
|
||||
@ -1,416 +0,0 @@
|
||||
import { renderHook, waitFor } from '@testing-library/react';
|
||||
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import gql from 'graphql-tag';
|
||||
import { generateEmptyJestRecordNode } from '~/testing/jest/generateEmptyJestRecordNode';
|
||||
import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper';
|
||||
import { useRightDrawerEmailThread } from '../useRightDrawerEmailThread';
|
||||
|
||||
const mocks = [
|
||||
{
|
||||
request: {
|
||||
query: gql`
|
||||
query FindOneMessageThread($objectRecordId: ID!) {
|
||||
messageThread(filter: { id: { eq: $objectRecordId } }) {
|
||||
__typename
|
||||
id
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: { objectRecordId: '1' },
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: {
|
||||
messageThread: {
|
||||
id: '1',
|
||||
__typename: 'MessageThread',
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
request: {
|
||||
query: gql`
|
||||
query FindManyMessages(
|
||||
$filter: MessageFilterInput
|
||||
$orderBy: [MessageOrderByInput]
|
||||
$lastCursor: String
|
||||
$limit: Int
|
||||
) {
|
||||
messages(
|
||||
filter: $filter
|
||||
orderBy: $orderBy
|
||||
first: $limit
|
||||
after: $lastCursor
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
createdAt
|
||||
headerMessageId
|
||||
id
|
||||
messageParticipants {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
displayName
|
||||
handle
|
||||
id
|
||||
person {
|
||||
__typename
|
||||
avatarUrl
|
||||
city
|
||||
companyId
|
||||
createdAt
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
context
|
||||
}
|
||||
deletedAt
|
||||
emails {
|
||||
primaryEmail
|
||||
additionalEmails
|
||||
}
|
||||
id
|
||||
intro
|
||||
jobTitle
|
||||
linkedinLink {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
name {
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
performanceRating
|
||||
phones {
|
||||
primaryPhoneNumber
|
||||
primaryPhoneCountryCode
|
||||
primaryPhoneCallingCode
|
||||
additionalPhones
|
||||
}
|
||||
position
|
||||
updatedAt
|
||||
whatsapp {
|
||||
primaryPhoneNumber
|
||||
primaryPhoneCountryCode
|
||||
primaryPhoneCallingCode
|
||||
additionalPhones
|
||||
}
|
||||
workPreference
|
||||
xLink {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
}
|
||||
role
|
||||
workspaceMember {
|
||||
__typename
|
||||
avatarUrl
|
||||
colorScheme
|
||||
createdAt
|
||||
dateFormat
|
||||
deletedAt
|
||||
id
|
||||
locale
|
||||
name {
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
timeFormat
|
||||
timeZone
|
||||
updatedAt
|
||||
userEmail
|
||||
userId
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
messageThread {
|
||||
__typename
|
||||
id
|
||||
}
|
||||
receivedAt
|
||||
subject
|
||||
text
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
filter: { messageThreadId: { eq: '1' } },
|
||||
orderBy: [{ receivedAt: 'AscNullsLast' }],
|
||||
lastCursor: undefined,
|
||||
limit: 10,
|
||||
},
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: {
|
||||
messages: {
|
||||
edges: [
|
||||
{
|
||||
node: generateEmptyJestRecordNode({
|
||||
objectNameSingular: 'message',
|
||||
input: {
|
||||
id: '1',
|
||||
text: 'Message 1',
|
||||
createdAt: '2024-10-03T10:20:10.145Z',
|
||||
},
|
||||
}),
|
||||
cursor: '1',
|
||||
},
|
||||
{
|
||||
node: generateEmptyJestRecordNode({
|
||||
objectNameSingular: 'message',
|
||||
input: {
|
||||
id: '2',
|
||||
text: 'Message 2',
|
||||
createdAt: '2024-10-03T10:20:10.145Z',
|
||||
},
|
||||
}),
|
||||
cursor: '2',
|
||||
},
|
||||
],
|
||||
totalCount: 2,
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: '1',
|
||||
endCursor: '2',
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
request: {
|
||||
query: gql`
|
||||
query FindManyMessageParticipants(
|
||||
$filter: MessageParticipantFilterInput
|
||||
$orderBy: [MessageParticipantOrderByInput]
|
||||
$lastCursor: String
|
||||
$limit: Int
|
||||
) {
|
||||
messageParticipants(
|
||||
filter: $filter
|
||||
orderBy: $orderBy
|
||||
first: $limit
|
||||
after: $lastCursor
|
||||
) {
|
||||
edges {
|
||||
node {
|
||||
__typename
|
||||
displayName
|
||||
handle
|
||||
id
|
||||
messageId
|
||||
person {
|
||||
__typename
|
||||
avatarUrl
|
||||
city
|
||||
companyId
|
||||
createdAt
|
||||
createdBy {
|
||||
source
|
||||
workspaceMemberId
|
||||
name
|
||||
context
|
||||
}
|
||||
deletedAt
|
||||
emails {
|
||||
primaryEmail
|
||||
additionalEmails
|
||||
}
|
||||
id
|
||||
intro
|
||||
jobTitle
|
||||
linkedinLink {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
name {
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
performanceRating
|
||||
phones {
|
||||
primaryPhoneNumber
|
||||
primaryPhoneCountryCode
|
||||
primaryPhoneCallingCode
|
||||
additionalPhones
|
||||
}
|
||||
position
|
||||
updatedAt
|
||||
whatsapp {
|
||||
primaryPhoneNumber
|
||||
primaryPhoneCountryCode
|
||||
primaryPhoneCallingCode
|
||||
additionalPhones
|
||||
}
|
||||
workPreference
|
||||
xLink {
|
||||
primaryLinkUrl
|
||||
primaryLinkLabel
|
||||
secondaryLinks
|
||||
}
|
||||
}
|
||||
role
|
||||
workspaceMember {
|
||||
__typename
|
||||
avatarUrl
|
||||
colorScheme
|
||||
createdAt
|
||||
dateFormat
|
||||
deletedAt
|
||||
id
|
||||
locale
|
||||
name {
|
||||
firstName
|
||||
lastName
|
||||
}
|
||||
timeFormat
|
||||
timeZone
|
||||
updatedAt
|
||||
userEmail
|
||||
userId
|
||||
}
|
||||
}
|
||||
cursor
|
||||
}
|
||||
pageInfo {
|
||||
hasNextPage
|
||||
hasPreviousPage
|
||||
startCursor
|
||||
endCursor
|
||||
}
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
filter: { messageId: { in: ['1', '2'] }, role: { eq: 'from' } },
|
||||
orderBy: undefined,
|
||||
lastCursor: undefined,
|
||||
limit: undefined,
|
||||
},
|
||||
},
|
||||
result: jest.fn(() => ({
|
||||
data: {
|
||||
messageParticipants: {
|
||||
edges: [
|
||||
{
|
||||
node: generateEmptyJestRecordNode({
|
||||
objectNameSingular: 'messageParticipant',
|
||||
input: {
|
||||
id: 'messageParticipant-1',
|
||||
role: 'from',
|
||||
messageId: '1',
|
||||
},
|
||||
}),
|
||||
cursor: '1',
|
||||
},
|
||||
{
|
||||
node: generateEmptyJestRecordNode({
|
||||
objectNameSingular: 'messageParticipant',
|
||||
input: {
|
||||
id: 'messageParticipant-2',
|
||||
role: 'from',
|
||||
messageId: '2',
|
||||
},
|
||||
}),
|
||||
cursor: '2',
|
||||
},
|
||||
],
|
||||
totalCount: 2,
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
hasPreviousPage: false,
|
||||
startCursor: '1',
|
||||
endCursor: '2',
|
||||
},
|
||||
},
|
||||
},
|
||||
})),
|
||||
},
|
||||
];
|
||||
|
||||
const Wrapper = getJestMetadataAndApolloMocksWrapper({
|
||||
apolloMocks: mocks,
|
||||
onInitializeRecoilSnapshot: ({ set }) => {
|
||||
set(viewableRecordIdState, '1');
|
||||
},
|
||||
});
|
||||
|
||||
describe('useRightDrawerEmailThread', () => {
|
||||
it('should return correct values', async () => {
|
||||
const mockMessages = [
|
||||
{
|
||||
__typename: 'Message',
|
||||
createdAt: '2024-10-03T10:20:10.145Z',
|
||||
headerMessageId: '',
|
||||
id: '1',
|
||||
messageParticipants: [],
|
||||
messageThread: null,
|
||||
receivedAt: null,
|
||||
sender: {
|
||||
__typename: 'MessageParticipant',
|
||||
displayName: '',
|
||||
handle: '',
|
||||
id: 'messageParticipant-1',
|
||||
messageId: '1',
|
||||
person: null,
|
||||
role: 'from',
|
||||
workspaceMember: null,
|
||||
},
|
||||
subject: '',
|
||||
text: 'Message 1',
|
||||
},
|
||||
{
|
||||
__typename: 'Message',
|
||||
createdAt: '2024-10-03T10:20:10.145Z',
|
||||
headerMessageId: '',
|
||||
id: '2',
|
||||
messageParticipants: [],
|
||||
messageThread: null,
|
||||
receivedAt: null,
|
||||
sender: {
|
||||
__typename: 'MessageParticipant',
|
||||
displayName: '',
|
||||
handle: '',
|
||||
id: 'messageParticipant-2',
|
||||
messageId: '2',
|
||||
person: null,
|
||||
role: 'from',
|
||||
workspaceMember: null,
|
||||
},
|
||||
subject: '',
|
||||
text: 'Message 2',
|
||||
},
|
||||
];
|
||||
|
||||
const { result } = renderHook(() => useRightDrawerEmailThread(), {
|
||||
wrapper: Wrapper,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.thread).toBeDefined();
|
||||
expect(result.current.messages).toEqual(mockMessages);
|
||||
expect(result.current.threadLoading).toBeFalsy();
|
||||
expect(result.current.fetchMoreMessages).toBeInstanceOf(Function);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,18 +0,0 @@
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { IconMail } from 'twenty-ui';
|
||||
|
||||
export const useOpenEmailThreadRightDrawer = () => {
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
return () => {
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
openRightDrawer(RightDrawerPages.ViewEmailThread, {
|
||||
title: 'Email Thread',
|
||||
Icon: IconMail,
|
||||
});
|
||||
};
|
||||
};
|
||||
@ -1,186 +0,0 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { fetchAllThreadMessagesOperationSignatureFactory } from '@/activities/emails/graphql/operation-signatures/factories/fetchAllThreadMessagesOperationSignatureFactory';
|
||||
import { EmailThread } from '@/activities/emails/types/EmailThread';
|
||||
import { EmailThreadMessage } from '@/activities/emails/types/EmailThreadMessage';
|
||||
|
||||
import { MessageChannel } from '@/accounts/types/MessageChannel';
|
||||
import { EmailThreadMessageParticipant } from '@/activities/emails/types/EmailThreadMessageParticipant';
|
||||
import { EmailThreadMessageWithSender } from '@/activities/emails/types/EmailThreadMessageWithSender';
|
||||
import { MessageChannelMessageAssociation } from '@/activities/emails/types/MessageChannelMessageAssociation';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { useUpsertRecordsInStore } from '@/object-record/record-store/hooks/useUpsertRecordsInStore';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export const useRightDrawerEmailThread = () => {
|
||||
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||
const { upsertRecords } = useUpsertRecordsInStore();
|
||||
const [lastMessageId, setLastMessageId] = useState<string | null>(null);
|
||||
const [lastMessageChannelId, setLastMessageChannelId] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [isMessagesFetchComplete, setIsMessagesFetchComplete] = useState(false);
|
||||
|
||||
const { record: thread } = useFindOneRecord<EmailThread>({
|
||||
objectNameSingular: CoreObjectNameSingular.MessageThread,
|
||||
objectRecordId: viewableRecordId ?? '',
|
||||
recordGqlFields: {
|
||||
id: true,
|
||||
},
|
||||
onCompleted: (record) => {
|
||||
upsertRecords([record]);
|
||||
},
|
||||
});
|
||||
|
||||
const FETCH_ALL_MESSAGES_OPERATION_SIGNATURE =
|
||||
fetchAllThreadMessagesOperationSignatureFactory({
|
||||
messageThreadId: viewableRecordId,
|
||||
});
|
||||
|
||||
const {
|
||||
records: messages,
|
||||
loading: messagesLoading,
|
||||
fetchMoreRecords,
|
||||
hasNextPage,
|
||||
} = useFindManyRecords<EmailThreadMessage>({
|
||||
limit: FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.variables.limit,
|
||||
filter: FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.variables.filter,
|
||||
objectNameSingular:
|
||||
FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.objectNameSingular,
|
||||
orderBy: FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.variables.orderBy,
|
||||
recordGqlFields: FETCH_ALL_MESSAGES_OPERATION_SIGNATURE.fields,
|
||||
skip: !viewableRecordId,
|
||||
});
|
||||
|
||||
const fetchMoreMessages = useCallback(() => {
|
||||
if (!messagesLoading && hasNextPage) {
|
||||
fetchMoreRecords();
|
||||
} else if (!hasNextPage) {
|
||||
setIsMessagesFetchComplete(true);
|
||||
}
|
||||
}, [fetchMoreRecords, messagesLoading, hasNextPage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (messages.length > 0 && isMessagesFetchComplete) {
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
setLastMessageId(lastMessage.id);
|
||||
}
|
||||
}, [messages, isMessagesFetchComplete]);
|
||||
|
||||
// TODO: introduce nested filters so we can retrieve the message sender directly from the message query
|
||||
const { records: messageSenders } =
|
||||
useFindManyRecords<EmailThreadMessageParticipant>({
|
||||
filter: {
|
||||
messageId: {
|
||||
in: messages.map(({ id }) => id),
|
||||
},
|
||||
role: {
|
||||
eq: 'from',
|
||||
},
|
||||
},
|
||||
objectNameSingular: CoreObjectNameSingular.MessageParticipant,
|
||||
recordGqlFields: {
|
||||
id: true,
|
||||
role: true,
|
||||
displayName: true,
|
||||
messageId: true,
|
||||
handle: true,
|
||||
person: true,
|
||||
workspaceMember: true,
|
||||
},
|
||||
skip: messages.length === 0,
|
||||
});
|
||||
|
||||
const { records: messageChannelMessageAssociationData } =
|
||||
useFindManyRecords<MessageChannelMessageAssociation>({
|
||||
filter: {
|
||||
messageId: {
|
||||
eq: lastMessageId ?? '',
|
||||
},
|
||||
},
|
||||
objectNameSingular:
|
||||
CoreObjectNameSingular.MessageChannelMessageAssociation,
|
||||
recordGqlFields: {
|
||||
id: true,
|
||||
messageId: true,
|
||||
messageChannelId: true,
|
||||
messageThreadExternalId: true,
|
||||
messageExternalId: true,
|
||||
},
|
||||
skip: !lastMessageId || !isMessagesFetchComplete,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (messageChannelMessageAssociationData.length > 0) {
|
||||
setLastMessageChannelId(
|
||||
messageChannelMessageAssociationData[0].messageChannelId,
|
||||
);
|
||||
}
|
||||
}, [messageChannelMessageAssociationData]);
|
||||
|
||||
const { records: messageChannelData, loading: messageChannelLoading } =
|
||||
useFindManyRecords<MessageChannel>({
|
||||
filter: {
|
||||
id: {
|
||||
eq: lastMessageChannelId ?? '',
|
||||
},
|
||||
},
|
||||
objectNameSingular: CoreObjectNameSingular.MessageChannel,
|
||||
recordGqlFields: {
|
||||
id: true,
|
||||
handle: true,
|
||||
connectedAccount: {
|
||||
id: true,
|
||||
provider: true,
|
||||
},
|
||||
},
|
||||
skip: !lastMessageChannelId,
|
||||
});
|
||||
|
||||
const messageThreadExternalId =
|
||||
messageChannelMessageAssociationData.length > 0
|
||||
? messageChannelMessageAssociationData[0].messageThreadExternalId
|
||||
: null;
|
||||
const lastMessageExternalId =
|
||||
messageChannelMessageAssociationData.length > 0
|
||||
? messageChannelMessageAssociationData[0].messageExternalId
|
||||
: null;
|
||||
const connectedAccountHandle =
|
||||
messageChannelData.length > 0 ? messageChannelData[0].handle : null;
|
||||
|
||||
const messagesWithSender: EmailThreadMessageWithSender[] = messages
|
||||
.map((message) => {
|
||||
const sender = messageSenders.find(
|
||||
(messageSender) => messageSender.messageId === message.id,
|
||||
);
|
||||
if (!sender) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
...message,
|
||||
sender,
|
||||
};
|
||||
})
|
||||
.filter(isDefined);
|
||||
|
||||
const connectedAccount =
|
||||
messageChannelData.length > 0
|
||||
? messageChannelData[0]?.connectedAccount
|
||||
: null;
|
||||
const connectedAccountProvider = connectedAccount?.provider ?? null;
|
||||
return {
|
||||
thread,
|
||||
messages: messagesWithSender,
|
||||
messageThreadExternalId,
|
||||
connectedAccountHandle,
|
||||
connectedAccountProvider,
|
||||
threadLoading: messagesLoading,
|
||||
messageChannelLoading,
|
||||
lastMessageExternalId,
|
||||
fetchMoreMessages,
|
||||
};
|
||||
};
|
||||
@ -1,40 +0,0 @@
|
||||
import { MockedProvider } from '@apollo/client/testing';
|
||||
import { act, renderHook } from '@testing-library/react';
|
||||
import { ReactNode } from 'react';
|
||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
|
||||
const Wrapper = ({ children }: { children: ReactNode }) => (
|
||||
<RecoilRoot>
|
||||
<MockedProvider addTypename={false}>{children}</MockedProvider>
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
describe('useOpenActivityRightDrawer', () => {
|
||||
it('works as expected', () => {
|
||||
const { result } = renderHook(
|
||||
() => {
|
||||
const openActivityRightDrawer = useOpenActivityRightDrawer({
|
||||
objectNameSingular: CoreObjectNameSingular.Task,
|
||||
});
|
||||
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||
return {
|
||||
openActivityRightDrawer,
|
||||
viewableRecordId,
|
||||
};
|
||||
},
|
||||
{
|
||||
wrapper: Wrapper,
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.current.viewableRecordId).toBeNull();
|
||||
act(() => {
|
||||
result.current.openActivityRightDrawer('123');
|
||||
});
|
||||
expect(result.current.viewableRecordId).toBe('123');
|
||||
});
|
||||
});
|
||||
@ -1,63 +0,0 @@
|
||||
import { useRecoilState, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { IconList } from 'twenty-ui';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
export const useOpenActivityRightDrawer = ({
|
||||
objectNameSingular,
|
||||
}: {
|
||||
objectNameSingular: CoreObjectNameSingular;
|
||||
}) => {
|
||||
const { openRightDrawer, isRightDrawerOpen, rightDrawerPage } =
|
||||
useRightDrawer();
|
||||
const [viewableRecordId, setViewableRecordId] = useRecoilState(
|
||||
viewableRecordIdState,
|
||||
);
|
||||
|
||||
const setViewableRecordNameSingular = useSetRecoilState(
|
||||
viewableRecordNameSingularState,
|
||||
);
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const { openRecordInCommandMenu } = useCommandMenu();
|
||||
|
||||
return (activityId: string) => {
|
||||
if (
|
||||
isRightDrawerOpen &&
|
||||
rightDrawerPage === RightDrawerPages.ViewRecord &&
|
||||
viewableRecordId === activityId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCommandMenuV2Enabled) {
|
||||
openRecordInCommandMenu({
|
||||
recordId: activityId,
|
||||
objectNameSingular,
|
||||
isNewRecord: false,
|
||||
});
|
||||
} else {
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
setViewableRecordId(activityId);
|
||||
setViewableRecordNameSingular(objectNameSingular);
|
||||
openRightDrawer(RightDrawerPages.ViewRecord, {
|
||||
title: objectNameSingular,
|
||||
Icon: IconList,
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -1,27 +1,20 @@
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
import { activityTargetableEntityArrayState } from '@/activities/states/activityTargetableEntityArrayState';
|
||||
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||
import { WorkspaceMember } from '@/workspace-member/types/WorkspaceMember';
|
||||
|
||||
import { isUpsertingActivityInDBState } from '@/activities/states/isCreatingActivityInDBState';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { Note } from '@/activities/types/Note';
|
||||
import { NoteTarget } from '@/activities/types/NoteTarget';
|
||||
import { Task } from '@/activities/types/Task';
|
||||
import { TaskTarget } from '@/activities/types/TaskTarget';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
|
||||
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { IconList } from 'twenty-ui';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { ActivityTargetableObject } from '../types/ActivityTargetableEntity';
|
||||
|
||||
export const useOpenCreateActivityDrawer = ({
|
||||
activityObjectNameSingular,
|
||||
@ -30,10 +23,6 @@ export const useOpenCreateActivityDrawer = ({
|
||||
| CoreObjectNameSingular.Note
|
||||
| CoreObjectNameSingular.Task;
|
||||
}) => {
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const { createOneRecord: createOneActivity } = useCreateOneRecord<
|
||||
(Task | Note) & { position: 'first' | 'last' }
|
||||
>({
|
||||
@ -64,10 +53,6 @@ export const useOpenCreateActivityDrawer = ({
|
||||
isUpsertingActivityInDBState,
|
||||
);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const { openRecordInCommandMenu } = useCommandMenu();
|
||||
|
||||
const openCreateActivityDrawer = async ({
|
||||
@ -78,12 +63,6 @@ export const useOpenCreateActivityDrawer = ({
|
||||
customAssignee?: WorkspaceMember;
|
||||
}) => {
|
||||
setIsNewViewableRecordLoading(true);
|
||||
if (!isCommandMenuV2Enabled) {
|
||||
openRightDrawer(RightDrawerPages.ViewRecord, {
|
||||
title: activityObjectNameSingular,
|
||||
Icon: IconList,
|
||||
});
|
||||
}
|
||||
setViewableRecordId(null);
|
||||
setViewableRecordNameSingular(activityObjectNameSingular);
|
||||
|
||||
@ -125,15 +104,11 @@ export const useOpenCreateActivityDrawer = ({
|
||||
setActivityTargetableEntityArray([]);
|
||||
}
|
||||
|
||||
if (isCommandMenuV2Enabled) {
|
||||
openRecordInCommandMenu({
|
||||
recordId: activity.id,
|
||||
objectNameSingular: activityObjectNameSingular,
|
||||
isNewRecord: true,
|
||||
});
|
||||
} else {
|
||||
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
|
||||
}
|
||||
|
||||
setViewableRecordId(activity.id);
|
||||
|
||||
|
||||
@ -5,8 +5,6 @@ import { useMultipleRecordPickerPerformSearch } from '@/object-record/record-pic
|
||||
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
|
||||
import { multipleRecordPickerSearchFilterComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchFilterComponentState';
|
||||
import { multipleRecordPickerSearchableObjectMetadataItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerSearchableObjectMetadataItemsComponentState';
|
||||
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
|
||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
type OpenActivityTargetInlineCellEditModeProps = {
|
||||
@ -15,9 +13,6 @@ type OpenActivityTargetInlineCellEditModeProps = {
|
||||
};
|
||||
|
||||
export const useOpenActivityTargetInlineCellEditMode = () => {
|
||||
const { toggleClickOutsideListener: toggleRightDrawerClickOustideListener } =
|
||||
useClickOutsideListener(RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID);
|
||||
|
||||
const { performSearch: multipleRecordPickerPerformSearch } =
|
||||
useMultipleRecordPickerPerformSearch();
|
||||
|
||||
@ -66,8 +61,6 @@ export const useOpenActivityTargetInlineCellEditMode = () => {
|
||||
'',
|
||||
);
|
||||
|
||||
toggleRightDrawerClickOustideListener(false);
|
||||
|
||||
multipleRecordPickerPerformSearch({
|
||||
multipleRecordPickerInstanceId: recordPickerInstanceId,
|
||||
forceSearchFilter: '',
|
||||
@ -83,7 +76,7 @@ export const useOpenActivityTargetInlineCellEditMode = () => {
|
||||
),
|
||||
});
|
||||
},
|
||||
[multipleRecordPickerPerformSearch, toggleRightDrawerClickOustideListener],
|
||||
[multipleRecordPickerPerformSearch],
|
||||
);
|
||||
|
||||
return { openActivityTargetInlineCellEditMode };
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
||||
import { Note } from '@/activities/types/Note';
|
||||
import { getActivityPreview } from '@/activities/utils/getActivityPreview';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
|
||||
|
||||
@ -68,9 +68,7 @@ export const NoteCard = ({
|
||||
note: Note;
|
||||
isSingleNote: boolean;
|
||||
}) => {
|
||||
const openActivityRightDrawer = useOpenActivityRightDrawer({
|
||||
objectNameSingular: CoreObjectNameSingular.Note,
|
||||
});
|
||||
const { openRecordInCommandMenu } = useCommandMenu();
|
||||
|
||||
const body = getActivityPreview(note?.bodyV2?.blocknote ?? null);
|
||||
|
||||
@ -84,7 +82,12 @@ export const NoteCard = ({
|
||||
return (
|
||||
<StyledCard isSingleNote={isSingleNote}>
|
||||
<StyledCardDetailsContainer
|
||||
onClick={() => openActivityRightDrawer(note.id)}
|
||||
onClick={() =>
|
||||
openRecordInCommandMenu({
|
||||
recordId: note.id,
|
||||
objectNameSingular: CoreObjectNameSingular.Note,
|
||||
})
|
||||
}
|
||||
>
|
||||
<StyledNoteTitle>{note.title ?? 'Task Title'}</StyledNoteTitle>
|
||||
<StyledCardContent>{body}</StyledCardContent>
|
||||
|
||||
@ -7,13 +7,13 @@ import {
|
||||
OverflowingTextWithTooltip,
|
||||
} from 'twenty-ui';
|
||||
|
||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||
import { ActivityTargetsInlineCell } from '@/activities/inline-cell/components/ActivityTargetsInlineCell';
|
||||
import { getActivitySummary } from '@/activities/utils/getActivitySummary';
|
||||
import { beautifyExactDate, hasDatePassed } from '~/utils/date-utils';
|
||||
|
||||
import { ActivityRow } from '@/activities/components/ActivityRow';
|
||||
import { Task } from '@/activities/types/Task';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useFieldContext } from '@/object-record/hooks/useFieldContext';
|
||||
import { useCompleteTask } from '../hooks/useCompleteTask';
|
||||
@ -78,9 +78,7 @@ const StyledCheckboxContainer = styled.div`
|
||||
|
||||
export const TaskRow = ({ task }: { task: Task }) => {
|
||||
const theme = useTheme();
|
||||
const openActivityRightDrawer = useOpenActivityRightDrawer({
|
||||
objectNameSingular: CoreObjectNameSingular.Task,
|
||||
});
|
||||
const { openRecordInCommandMenu } = useCommandMenu();
|
||||
|
||||
const body = getActivitySummary(task?.bodyV2?.blocknote ?? null);
|
||||
|
||||
@ -96,7 +94,10 @@ export const TaskRow = ({ task }: { task: Task }) => {
|
||||
return (
|
||||
<ActivityRow
|
||||
onClick={() => {
|
||||
openActivityRightDrawer(task.id);
|
||||
openRecordInCommandMenu({
|
||||
recordId: task.id,
|
||||
objectNameSingular: CoreObjectNameSingular.Task,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<StyledLeftSideContainer>
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||
import {
|
||||
EventRowDynamicComponentProps,
|
||||
StyledEventRowItemAction,
|
||||
StyledEventRowItemColumn,
|
||||
} from '@/activities/timeline-activities/rows/components/EventRowDynamicComponent';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useGetRecordFromCache } from '@/object-record/cache/hooks/useGetRecordFromCache';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
@ -55,9 +55,7 @@ export const EventRowActivity = ({
|
||||
? event.linkedRecordCachedName
|
||||
: 'Untitled';
|
||||
|
||||
const openActivityRightDrawer = useOpenActivityRightDrawer({
|
||||
objectNameSingular,
|
||||
});
|
||||
const { openRecordInCommandMenu } = useCommandMenu();
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -66,7 +64,12 @@ export const EventRowActivity = ({
|
||||
{`${eventAction} a related ${eventObject}`}
|
||||
</StyledEventRowItemAction>
|
||||
<StyledLinkedActivity
|
||||
onClick={() => openActivityRightDrawer(event.linkedRecordId)}
|
||||
onClick={() =>
|
||||
openRecordInCommandMenu({
|
||||
recordId: event.linkedRecordId,
|
||||
objectNameSingular,
|
||||
})
|
||||
}
|
||||
>
|
||||
{activityTitle}
|
||||
</StyledLinkedActivity>
|
||||
|
||||
@ -24,7 +24,6 @@ export const CommandMenu = () => {
|
||||
|
||||
const {
|
||||
noResults,
|
||||
copilotCommands,
|
||||
matchingStandardActionRecordSelectionCommands,
|
||||
matchingStandardActionObjectCommands,
|
||||
matchingWorkflowRunRecordSelectionCommands,
|
||||
@ -47,10 +46,6 @@ export const CommandMenu = () => {
|
||||
);
|
||||
|
||||
const commandGroups: CommandGroupConfig[] = [
|
||||
{
|
||||
heading: t`Copilot`,
|
||||
items: copilotCommands,
|
||||
},
|
||||
{
|
||||
heading: t`Record Selection`,
|
||||
items: matchingStandardActionRecordSelectionCommands.concat(
|
||||
|
||||
@ -11,7 +11,6 @@ import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchS
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
import { contextStoreCurrentObjectMetadataItemComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
@ -24,11 +23,9 @@ import {
|
||||
Button,
|
||||
IconChevronLeft,
|
||||
IconX,
|
||||
LightIconButton,
|
||||
getOsControlSymbol,
|
||||
useIsMobile,
|
||||
} from 'twenty-ui';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledInputContainer = styled.div`
|
||||
align-items: center;
|
||||
@ -75,13 +72,6 @@ const StyledContentContainer = styled.div`
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledCloseButtonContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
height: 32px;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const StyledCloseButtonWrapper = styled.div<{ isVisible: boolean }>`
|
||||
visibility: ${({ isVisible }) => (isVisible ? 'visible' : 'hidden')};
|
||||
`;
|
||||
@ -110,10 +100,6 @@ export const CommandMenuTopBar = () => {
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const { contextChips } = useCommandMenuContextChips();
|
||||
|
||||
const location = useLocation();
|
||||
@ -127,8 +113,6 @@ export const CommandMenuTopBar = () => {
|
||||
return (
|
||||
<StyledInputContainer>
|
||||
<StyledContentContainer>
|
||||
{isCommandMenuV2Enabled && (
|
||||
<>
|
||||
<AnimatePresence>
|
||||
{commandMenuPage !== CommandMenuPages.Root && (
|
||||
<motion.div
|
||||
@ -154,8 +138,6 @@ export const CommandMenuTopBar = () => {
|
||||
) : (
|
||||
<CommandMenuContextChipGroups contextChips={contextChips} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{(commandMenuPage === CommandMenuPages.Root ||
|
||||
commandMenuPage === CommandMenuPages.SearchRecords) && (
|
||||
<>
|
||||
@ -171,7 +153,6 @@ export const CommandMenuTopBar = () => {
|
||||
</StyledContentContainer>
|
||||
{!isMobile && (
|
||||
<StyledCloseButtonWrapper isVisible={isButtonVisible}>
|
||||
{isCommandMenuV2Enabled ? (
|
||||
<Button
|
||||
Icon={IconX}
|
||||
dataTestId="page-header-close-command-menu-button"
|
||||
@ -182,16 +163,6 @@ export const CommandMenuTopBar = () => {
|
||||
ariaLabel="Close command menu"
|
||||
onClick={closeCommandMenu}
|
||||
/>
|
||||
) : (
|
||||
<StyledCloseButtonContainer>
|
||||
<LightIconButton
|
||||
accent={'tertiary'}
|
||||
size={'medium'}
|
||||
Icon={IconX}
|
||||
onClick={closeCommandMenu}
|
||||
/>
|
||||
</StyledCloseButtonContainer>
|
||||
)}
|
||||
</StyledCloseButtonWrapper>
|
||||
)}
|
||||
</StyledInputContainer>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { RightDrawerAIChat } from '@/activities/copilot/right-drawer/components/RightDrawerAIChat';
|
||||
import { CommandMenu } from '@/command-menu/components/CommandMenu';
|
||||
import { CommandMenuCalendarEventPage } from '@/command-menu/pages/calendar-event/components/CommandMenuCalendarEventPage';
|
||||
import { CommandMenuMessageThreadPage } from '@/command-menu/pages/message-thread/components/CommandMenuMessageThreadPage';
|
||||
@ -19,7 +18,6 @@ export const COMMAND_MENU_PAGES_CONFIG = new Map<
|
||||
[CommandMenuPages.ViewRecord, <CommandMenuRecordPage />],
|
||||
[CommandMenuPages.ViewEmailThread, <CommandMenuMessageThreadPage />],
|
||||
[CommandMenuPages.ViewCalendarEvent, <CommandMenuCalendarEventPage />],
|
||||
[CommandMenuPages.Copilot, <RightDrawerAIChat />],
|
||||
[
|
||||
CommandMenuPages.WorkflowStepSelectTriggerType,
|
||||
<CommandMenuWorkflowSelectTriggerType />,
|
||||
|
||||
@ -3,49 +3,20 @@ import {
|
||||
ActionMenuEntryScope,
|
||||
ActionMenuEntryType,
|
||||
} from '@/action-menu/types/ActionMenuEntry';
|
||||
import { useOpenCopilotRightDrawer } from '@/activities/copilot/right-drawer/hooks/useOpenCopilotRightDrawer';
|
||||
import { copilotQueryState } from '@/activities/copilot/right-drawer/states/copilotQueryState';
|
||||
import { COMMAND_MENU_NAVIGATE_COMMANDS } from '@/command-menu/constants/CommandMenuNavigateCommands';
|
||||
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||
import {
|
||||
Command,
|
||||
CommandScope,
|
||||
CommandType,
|
||||
} from '@/command-menu/types/Command';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { i18n } from '@lingui/core';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { IconSparkles } from 'twenty-ui';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
export const useCommandMenuCommands = () => {
|
||||
const actionMenuEntries = useRecoilComponentValueV2(
|
||||
actionMenuEntriesComponentSelector,
|
||||
);
|
||||
|
||||
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
|
||||
const [deferredCommandMenuSearch] = useDebounce(commandMenuSearch, 300); // 200ms - 500ms
|
||||
|
||||
const isCopilotEnabled = useIsFeatureEnabled(FeatureFlagKey.IsCopilotEnabled);
|
||||
const setCopilotQuery = useSetRecoilState(copilotQueryState);
|
||||
const openCopilotRightDrawer = useOpenCopilotRightDrawer();
|
||||
|
||||
const copilotCommand: Command = {
|
||||
id: 'copilot',
|
||||
to: '', // TODO
|
||||
Icon: IconSparkles,
|
||||
label: 'Open Copilot',
|
||||
type: CommandType.Navigate,
|
||||
onCommandClick: () => {
|
||||
setCopilotQuery(deferredCommandMenuSearch);
|
||||
openCopilotRightDrawer();
|
||||
},
|
||||
};
|
||||
|
||||
const copilotCommands: Command[] = isCopilotEnabled ? [copilotCommand] : [];
|
||||
|
||||
const navigateCommands = Object.values(COMMAND_MENU_NAVIGATE_COMMANDS);
|
||||
|
||||
const actionRecordSelectionCommands: Command[] = actionMenuEntries
|
||||
@ -144,7 +115,6 @@ export const useCommandMenuCommands = () => {
|
||||
}));
|
||||
|
||||
return {
|
||||
copilotCommands,
|
||||
navigateCommands,
|
||||
actionRecordSelectionCommands,
|
||||
actionGlobalCommands,
|
||||
|
||||
@ -8,11 +8,9 @@ import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeybo
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const useCommandMenuHotKeys = () => {
|
||||
const {
|
||||
@ -33,10 +31,6 @@ export const useCommandMenuHotKeys = () => {
|
||||
COMMAND_MENU_COMPONENT_INSTANCE_ID,
|
||||
);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
'ctrl+k,meta+k',
|
||||
() => {
|
||||
@ -71,7 +65,7 @@ export const useCommandMenuHotKeys = () => {
|
||||
useScopedHotkeys(
|
||||
[Key.Backspace, Key.Delete],
|
||||
() => {
|
||||
if (isNonEmptyString(commandMenuSearch) || !isCommandMenuV2Enabled) {
|
||||
if (isNonEmptyString(commandMenuSearch)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -9,7 +9,6 @@ export const useMatchingCommandMenuCommands = ({
|
||||
const { matchCommands } = useMatchCommands({ commandMenuSearch });
|
||||
|
||||
const {
|
||||
copilotCommands,
|
||||
navigateCommands,
|
||||
actionRecordSelectionCommands,
|
||||
actionObjectCommands,
|
||||
@ -49,7 +48,6 @@ export const useMatchingCommandMenuCommands = ({
|
||||
|
||||
return {
|
||||
noResults,
|
||||
copilotCommands,
|
||||
matchingStandardActionRecordSelectionCommands,
|
||||
matchingStandardActionObjectCommands,
|
||||
matchingWorkflowRunRecordSelectionCommands,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useOpenActivityRightDrawer } from '@/activities/hooks/useOpenActivityRightDrawer';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { t } from '@lingui/core/macro';
|
||||
@ -24,13 +24,7 @@ export const useSearchRecords = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const openNoteRightDrawer = useOpenActivityRightDrawer({
|
||||
objectNameSingular: CoreObjectNameSingular.Note,
|
||||
});
|
||||
|
||||
const openTaskRightDrawer = useOpenActivityRightDrawer({
|
||||
objectNameSingular: CoreObjectNameSingular.Task,
|
||||
});
|
||||
const { openRecordInCommandMenu } = useCommandMenu();
|
||||
|
||||
const commands = useMemo(() => {
|
||||
return (globalSearchData?.globalSearch ?? []).map((searchRecord) => {
|
||||
@ -63,14 +57,20 @@ export const useSearchRecords = () => {
|
||||
to: '',
|
||||
onCommandClick: () => {
|
||||
searchRecord.objectSingularName === 'task'
|
||||
? openTaskRightDrawer(searchRecord.recordId)
|
||||
: openNoteRightDrawer(searchRecord.recordId);
|
||||
? openRecordInCommandMenu({
|
||||
recordId: searchRecord.recordId,
|
||||
objectNameSingular: CoreObjectNameSingular.Task,
|
||||
})
|
||||
: openRecordInCommandMenu({
|
||||
recordId: searchRecord.recordId,
|
||||
objectNameSingular: CoreObjectNameSingular.Note,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
return command;
|
||||
});
|
||||
}, [globalSearchData, openTaskRightDrawer, openNoteRightDrawer]);
|
||||
}, [globalSearchData, openRecordInCommandMenu]);
|
||||
|
||||
return {
|
||||
loading,
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import {
|
||||
WorkflowTriggerType,
|
||||
WorkflowWithCurrentVersion,
|
||||
@ -14,10 +12,8 @@ import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/Other
|
||||
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
||||
import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger';
|
||||
import { getTriggerDefaultDefinition } from '@/workflow/workflow-trigger/utils/getTriggerDefaultDefinition';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { MenuItemCommand, useIcons } from 'twenty-ui';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const CommandMenuWorkflowSelectTriggerTypeContent = ({
|
||||
workflow,
|
||||
@ -29,12 +25,8 @@ export const CommandMenuWorkflowSelectTriggerTypeContent = ({
|
||||
|
||||
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
|
||||
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
|
||||
const { openWorkflowEditStepInCommandMenu } = useCommandMenu();
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const handleTriggerTypeClick = ({
|
||||
type,
|
||||
@ -56,18 +48,11 @@ export const CommandMenuWorkflowSelectTriggerTypeContent = ({
|
||||
|
||||
setWorkflowSelectedNode(TRIGGER_STEP_ID);
|
||||
|
||||
if (isCommandMenuV2Enabled) {
|
||||
openWorkflowEditStepInCommandMenu(
|
||||
workflow.id,
|
||||
defaultLabel,
|
||||
getIcon(icon),
|
||||
);
|
||||
} else {
|
||||
openRightDrawer(RightDrawerPages.WorkflowStepEdit, {
|
||||
title: defaultLabel,
|
||||
Icon: getIcon(icon),
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { Button, IconButton, IconHeart, IconHeartOff } from 'twenty-ui';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { Button, IconHeart, IconHeartOff } from 'twenty-ui';
|
||||
|
||||
type PageFavoriteButtonProps = {
|
||||
isFavorite: boolean;
|
||||
@ -13,13 +11,7 @@ export const PageFavoriteButton = ({
|
||||
}: PageFavoriteButtonProps) => {
|
||||
const title = isFavorite ? 'Remove from favorites' : 'Add to favorites';
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isCommandMenuV2Enabled ? (
|
||||
<Button
|
||||
Icon={isFavorite ? IconHeartOff : IconHeart}
|
||||
dataTestId="favorite-button"
|
||||
@ -30,16 +22,5 @@ export const PageFavoriteButton = ({
|
||||
onClick={onClick}
|
||||
ariaLabel={title}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
Icon={IconHeart}
|
||||
size="medium"
|
||||
variant="secondary"
|
||||
data-testid="add-button"
|
||||
accent={isFavorite ? 'danger' : 'default'}
|
||||
onClick={onClick}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage';
|
||||
import { useRecordChipData } from '@/object-record/hooks/useRecordChipData';
|
||||
import { recordIndexOpenRecordInSelector } from '@/object-record/record-index/states/selectors/recordIndexOpenRecordInSelector';
|
||||
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
@ -41,9 +41,7 @@ export const RecordChip = ({
|
||||
|
||||
const { openRecordInCommandMenu } = useCommandMenu();
|
||||
|
||||
const recordIndexOpenRecordIn = useRecoilValue(
|
||||
recordIndexOpenRecordInSelector,
|
||||
);
|
||||
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);
|
||||
|
||||
// TODO temporary until we create a record show page for Workspaces members
|
||||
if (forceDisableClick) {
|
||||
|
||||
@ -24,24 +24,16 @@ import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useDeleteViewFromCurrentState } from '@/views/view-picker/hooks/useDeleteViewFromCurrentState';
|
||||
import { viewPickerReferenceViewIdComponentState } from '@/views/view-picker/states/viewPickerReferenceViewIdComponentState';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const ObjectOptionsDropdownMenuContent = () => {
|
||||
const { t } = useLingui();
|
||||
const {
|
||||
recordIndexId,
|
||||
objectMetadataItem,
|
||||
viewType,
|
||||
onContentChange,
|
||||
closeDropdown,
|
||||
} = useOptionsDropdown();
|
||||
const { recordIndexId, objectMetadataItem, onContentChange, closeDropdown } =
|
||||
useOptionsDropdown();
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
const { currentView } = useGetCurrentViewOnly();
|
||||
@ -70,10 +62,6 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
viewBarId: recordIndexId,
|
||||
});
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const { deleteViewFromCurrentState } = useDeleteViewFromCurrentState();
|
||||
const setViewPickerReferenceViewId = useSetRecoilComponentStateV2(
|
||||
viewPickerReferenceViewIdComponentState,
|
||||
@ -95,8 +83,6 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
{currentView?.name}
|
||||
</DropdownMenuHeader>
|
||||
|
||||
{(isCommandMenuV2Enabled || viewType === ViewType.Kanban) && (
|
||||
<>
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('viewSettings')}
|
||||
@ -106,8 +92,6 @@ export const ObjectOptionsDropdownMenuContent = () => {
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
<DropdownMenuSeparator />
|
||||
</>
|
||||
)}
|
||||
|
||||
<DropdownMenuItemsContainer scrollable={false}>
|
||||
<MenuItem
|
||||
|
||||
@ -15,10 +15,8 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
|
||||
import { useGetCurrentViewOnly } from '@/views/hooks/useGetCurrentViewOnly';
|
||||
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const ObjectOptionsDropdownViewSettingsContent = () => {
|
||||
const { t } = useLingui();
|
||||
@ -41,17 +39,12 @@ export const ObjectOptionsDropdownViewSettingsContent = () => {
|
||||
|
||||
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetContent}>
|
||||
{t`View settings`}
|
||||
</DropdownMenuHeader>
|
||||
<DropdownMenuItemsContainer>
|
||||
{isCommandMenuV2Enabled && (
|
||||
<MenuItem
|
||||
onClick={() => onContentChange('viewSettingsOpenIn')}
|
||||
LeftIcon={
|
||||
@ -67,7 +60,6 @@ export const ObjectOptionsDropdownViewSettingsContent = () => {
|
||||
}
|
||||
hasSubMenu
|
||||
/>
|
||||
)}
|
||||
{viewType === ViewType.Kanban && (
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconBaselineDensitySmall}
|
||||
|
||||
@ -4,7 +4,6 @@ import { useContext, useRef } from 'react';
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
import { useActionMenu } from '@/action-menu/hooks/useActionMenu';
|
||||
import { ActionBarHotkeyScope } from '@/action-menu/types/ActionBarHotKeyScope';
|
||||
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
|
||||
import { RecordBoardHeader } from '@/object-record/record-board/components/RecordBoardHeader';
|
||||
@ -24,6 +23,7 @@ import { currentRecordSortsComponentState } from '@/object-record/record-sort/st
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { isRemoveSortingModalOpenState } from '@/object-record/record-table/states/isRemoveSortingModalOpenState';
|
||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
import { DragSelect } from '@/ui/utilities/drag-select/components/DragSelect';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useClickOutsideListener } from '@/ui/utilities/pointer-event/hooks/useClickOutsideListener';
|
||||
@ -76,10 +76,11 @@ export const RecordBoard = () => {
|
||||
);
|
||||
|
||||
const actionMenuId = getActionMenuIdFromRecordIndexId(recordBoardId);
|
||||
const { closeActionMenuDropdown } = useActionMenu(actionMenuId);
|
||||
|
||||
const { closeDropdown } = useDropdownV2();
|
||||
|
||||
const handleDragSelectionStart = () => {
|
||||
closeActionMenuDropdown();
|
||||
closeDropdown(actionMenuId);
|
||||
|
||||
toggleClickOutsideListener(false);
|
||||
};
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { useActionMenu } from '@/action-menu/hooks/useActionMenu';
|
||||
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
|
||||
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
|
||||
import { getActionMenuIdFromRecordIndexId } from '@/action-menu/utils/getActionMenuIdFromRecordIndexId';
|
||||
@ -8,13 +7,14 @@ import { RecordBoardScopeInternalContext } from '@/object-record/record-board/sc
|
||||
import { isRecordBoardCardSelectedComponentFamilyState } from '@/object-record/record-board/states/isRecordBoardCardSelectedComponentFamilyState';
|
||||
import { isRecordBoardCompactModeActiveComponentState } from '@/object-record/record-board/states/isRecordBoardCompactModeActiveComponentState';
|
||||
import { recordBoardVisibleFieldDefinitionsComponentSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsComponentSelector';
|
||||
import { recordIndexOpenRecordInSelector } from '@/object-record/record-index/states/selectors/recordIndexOpenRecordInSelector';
|
||||
|
||||
import { RecordBoardCardBody } from '@/object-record/record-board/record-board-card/components/RecordBoardCardBody';
|
||||
import { RecordBoardCardHeader } from '@/object-record/record-board/record-board-card/components/RecordBoardCardHeader';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
||||
import { RecordValueSetterEffect } from '@/object-record/record-store/components/RecordValueSetterEffect';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||
import { RecordBoardScrollWrapperContext } from '@/ui/utilities/scroll/contexts/ScrollWrapperContexts';
|
||||
import { useRecoilComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyStateV2';
|
||||
@ -119,11 +119,9 @@ export const RecordBoardCard = ({
|
||||
),
|
||||
);
|
||||
|
||||
const { openActionMenuDropdown } = useActionMenu(actionMenuId);
|
||||
const { openDropdown } = useDropdownV2();
|
||||
|
||||
const recordIndexOpenRecordIn = useRecoilValue(
|
||||
recordIndexOpenRecordInSelector,
|
||||
);
|
||||
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);
|
||||
|
||||
const handleActionMenuDropdown = (event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
@ -132,7 +130,7 @@ export const RecordBoardCard = ({
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
});
|
||||
openActionMenuDropdown();
|
||||
openDropdown(actionMenuDropdownId);
|
||||
};
|
||||
|
||||
const handleCardClick = () => {
|
||||
|
||||
@ -19,7 +19,7 @@ import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata'
|
||||
import { getFieldButtonIcon } from '@/object-record/record-field/utils/getFieldButtonIcon';
|
||||
import { isFieldValueEmpty } from '@/object-record/record-field/utils/isFieldValueEmpty';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { recordIndexOpenRecordInSelector } from '@/object-record/record-index/states/selectors/recordIndexOpenRecordInSelector';
|
||||
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
import { RecordInlineCellEditMode } from '@/object-record/record-inline-cell/components/RecordInlineCellEditMode';
|
||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||
@ -125,9 +125,7 @@ export const RecordBoardCardHeader = ({
|
||||
return [updateEntity, { loading: false }];
|
||||
};
|
||||
|
||||
const recordIndexOpenRecordIn = useRecoilValue(
|
||||
recordIndexOpenRecordInSelector,
|
||||
);
|
||||
const recordIndexOpenRecordIn = useRecoilValue(recordIndexOpenRecordInState);
|
||||
|
||||
return (
|
||||
<RecordBoardCardHeaderContainer showCompactView={showCompactView}>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { useAddNewCard } from '@/object-record/record-board/record-board-column/hooks/useAddNewCard';
|
||||
@ -6,11 +7,8 @@ import { SingleRecordPicker } from '@/object-record/record-picker/single-record-
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||
import { OverlayContainer } from '@/ui/layout/overlay/components/OverlayContainer';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { IconList } from 'twenty-ui';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
export const RecordBoardColumnNewOpportunity = ({
|
||||
@ -34,14 +32,15 @@ export const RecordBoardColumnNewOpportunity = ({
|
||||
const { createOneRecord: createCompany } = useCreateOneRecord({
|
||||
objectNameSingular: CoreObjectNameSingular.Company,
|
||||
});
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
|
||||
const setViewableRecordId = useSetRecoilState(viewableRecordIdState);
|
||||
const setViewableRecordNameSingular = useSetRecoilState(
|
||||
viewableRecordNameSingularState,
|
||||
);
|
||||
|
||||
const createCompanyOpportunityAndOpenRightDrawer = async (
|
||||
const { openRecordInCommandMenu } = useCommandMenu();
|
||||
|
||||
const createCompanyOpportunityAndOpenCommandMenu = async (
|
||||
searchInput?: string,
|
||||
) => {
|
||||
const newRecordId = v4();
|
||||
@ -53,9 +52,9 @@ export const RecordBoardColumnNewOpportunity = ({
|
||||
|
||||
setViewableRecordId(newRecordId);
|
||||
setViewableRecordNameSingular(CoreObjectNameSingular.Company);
|
||||
openRightDrawer(RightDrawerPages.ViewRecord, {
|
||||
title: 'Company',
|
||||
Icon: IconList,
|
||||
openRecordInCommandMenu({
|
||||
recordId: newRecordId,
|
||||
objectNameSingular: CoreObjectNameSingular.Company,
|
||||
});
|
||||
|
||||
if (isDefined(createdCompany)) {
|
||||
@ -74,7 +73,7 @@ export const RecordBoardColumnNewOpportunity = ({
|
||||
company ? handleEntitySelect(position, company) : null
|
||||
}
|
||||
objectNameSingular={CoreObjectNameSingular.Company}
|
||||
onCreate={createCompanyOpportunityAndOpenRightDrawer}
|
||||
onCreate={createCompanyOpportunityAndOpenCommandMenu}
|
||||
/>
|
||||
</OverlayContainer>
|
||||
)}
|
||||
|
||||
@ -9,13 +9,8 @@ import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { IconEye } from 'twenty-ui';
|
||||
import {
|
||||
FeatureFlagKey,
|
||||
FieldMetadataType,
|
||||
RelationDefinitionType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
@ -47,11 +42,7 @@ export const useAddNewRecordAndOpenRightDrawer = ({
|
||||
.nameSingular ?? 'workspaceMember',
|
||||
});
|
||||
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
const { openRecordInCommandMenu } = useCommandMenu();
|
||||
const isCommandMenuEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
if (
|
||||
relationObjectMetadataNameSingular === 'workspaceMember' ||
|
||||
@ -118,17 +109,10 @@ export const useAddNewRecordAndOpenRightDrawer = ({
|
||||
setViewableRecordId(newRecordId);
|
||||
setViewableRecordNameSingular(relationObjectMetadataNameSingular);
|
||||
|
||||
if (isCommandMenuEnabled) {
|
||||
openRecordInCommandMenu({
|
||||
recordId: newRecordId,
|
||||
objectNameSingular: relationObjectMetadataNameSingular,
|
||||
});
|
||||
} else {
|
||||
openRightDrawer(RightDrawerPages.ViewRecord, {
|
||||
title: 'View Record',
|
||||
Icon: IconEye,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -14,13 +14,10 @@ import { useRecordIndexContextOrThrow } from '@/object-record/record-index/conte
|
||||
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
import { SpreadsheetImportProvider } from '@/spreadsheet-import/provider/components/SpreadsheetImportProvider';
|
||||
|
||||
import { RecordIndexActionMenu } from '@/action-menu/components/RecordIndexActionMenu';
|
||||
import { RecordIndexFiltersToContextStoreEffect } from '@/object-record/record-index/components/RecordIndexFiltersToContextStoreEffect';
|
||||
import { RecordIndexTableContainerEffect } from '@/object-record/record-index/components/RecordIndexTableContainerEffect';
|
||||
import { ViewBar } from '@/views/components/ViewBar';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
@ -47,10 +44,6 @@ export const RecordIndexContainer = () => {
|
||||
objectNameSingular,
|
||||
} = useRecordIndexContextOrThrow();
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledContainer>
|
||||
@ -96,9 +89,6 @@ export const RecordIndexContainer = () => {
|
||||
<RecordIndexBoardDataLoaderEffect recordBoardId={recordIndexId} />
|
||||
</StyledContainerWithPadding>
|
||||
)}
|
||||
{!isCommandMenuV2Enabled && (
|
||||
<RecordIndexActionMenu indexId={recordIndexId} />
|
||||
)}
|
||||
</RecordFieldValueSelectorContextProvider>
|
||||
</StyledContainer>
|
||||
</>
|
||||
|
||||
@ -1,20 +1,10 @@
|
||||
import { RecordIndexActionMenu } from '@/action-menu/components/RecordIndexActionMenu';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
|
||||
import { isObjectMetadataReadOnly } from '@/object-metadata/utils/isObjectMetadataReadOnly';
|
||||
import { RecordIndexPageKanbanAddButton } from '@/object-record/record-index/components/RecordIndexPageKanbanAddButton';
|
||||
import { RecordIndexPageTableAddButton } from '@/object-record/record-index/components/RecordIndexPageTableAddButton';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
|
||||
import { PageHeaderOpenCommandMenuButton } from '@/ui/layout/page-header/components/PageHeaderOpenCommandMenuButton';
|
||||
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { ViewType } from '@/views/types/ViewType';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { capitalize, isDefined } from 'twenty-shared';
|
||||
import { capitalize } from 'twenty-shared';
|
||||
import { useIcons } from 'twenty-ui';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
export const RecordIndexPageHeader = () => {
|
||||
const { findObjectMetadataItemByNamePlural } =
|
||||
@ -28,48 +18,14 @@ export const RecordIndexPageHeader = () => {
|
||||
const { getIcon } = useIcons();
|
||||
const Icon = getIcon(objectMetadataItem?.icon);
|
||||
|
||||
const recordIndexViewType = useRecoilValue(recordIndexViewTypeState);
|
||||
|
||||
const { recordIndexId } = useRecordIndexContextOrThrow();
|
||||
|
||||
const numberOfSelectedRecords = useRecoilComponentValueV2(
|
||||
contextStoreNumberOfSelectedRecordsComponentState,
|
||||
);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const isObjectMetadataItemReadOnly =
|
||||
isDefined(objectMetadataItem) &&
|
||||
isObjectMetadataReadOnly(objectMetadataItem);
|
||||
|
||||
const shouldDisplayAddButton =
|
||||
(numberOfSelectedRecords === 0 || !isCommandMenuV2Enabled) &&
|
||||
!isObjectMetadataItemReadOnly &&
|
||||
!isCommandMenuV2Enabled;
|
||||
|
||||
const isTable = recordIndexViewType === ViewType.Table;
|
||||
|
||||
const pageHeaderTitle =
|
||||
objectMetadataItem?.labelPlural ?? capitalize(objectNamePlural);
|
||||
|
||||
return (
|
||||
<PageHeader title={pageHeaderTitle} Icon={Icon}>
|
||||
{shouldDisplayAddButton &&
|
||||
/**
|
||||
* TODO: Logic between Table and Kanban should be merged here when we move some states to record-index
|
||||
*/
|
||||
(isTable ? (
|
||||
<RecordIndexPageTableAddButton />
|
||||
) : (
|
||||
<RecordIndexPageKanbanAddButton />
|
||||
))}
|
||||
|
||||
{isCommandMenuV2Enabled && (
|
||||
<RecordIndexActionMenu indexId={recordIndexId} />
|
||||
)}
|
||||
|
||||
<PageHeaderOpenCommandMenuButton />
|
||||
</PageHeader>
|
||||
);
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
import { hasRecordGroupsComponentSelector } from '@/object-record/record-group/states/selectors/hasRecordGroupsComponentSelector';
|
||||
import { RecordIndexAddRecordInGroupDropdown } from '@/object-record/record-index/components/RecordIndexAddRecordInGroupDropdown';
|
||||
import { RecordIndexPageTableAddButtonNoGroup } from '@/object-record/record-index/components/RecordIndexPageTableAddButtonNoGroup';
|
||||
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
export const RecordIndexPageTableAddButton = () => {
|
||||
const hasRecordGroups = useRecoilComponentValueV2(
|
||||
hasRecordGroupsComponentSelector,
|
||||
);
|
||||
|
||||
if (!hasRecordGroups) {
|
||||
return <RecordIndexPageTableAddButtonNoGroup />;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecordIndexAddRecordInGroupDropdown
|
||||
dropdownId="record-index-page-table-add-button-dropdown"
|
||||
clickableComponent={<PageAddButton />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,24 +0,0 @@
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { useCreateNewTableRecord } from '@/object-record/record-table/hooks/useCreateNewTableRecords';
|
||||
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
|
||||
import { PageHotkeysEffect } from '@/ui/layout/page/components/PageHotkeysEffect';
|
||||
|
||||
export const RecordIndexPageTableAddButtonNoGroup = () => {
|
||||
const { recordIndexId, objectMetadataItem } = useRecordIndexContextOrThrow();
|
||||
|
||||
const { createNewTableRecord } = useCreateNewTableRecord({
|
||||
objectMetadataItem,
|
||||
recordTableId: recordIndexId,
|
||||
});
|
||||
|
||||
const handleCreateNewTableRecord = () => {
|
||||
createNewTableRecord();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHotkeysEffect onAddButtonClick={handleCreateNewTableRecord} />
|
||||
<PageAddButton onClick={handleCreateNewTableRecord} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -1,21 +0,0 @@
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
||||
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
||||
import { checkIfFeatureFlagIsEnabledOnWorkspace } from '@/workspace/utils/checkIfFeatureFlagIsEnabledOnWorkspace';
|
||||
import { selector } from 'recoil';
|
||||
import { FeatureFlagKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const recordIndexOpenRecordInSelector = selector<ViewOpenRecordInType>({
|
||||
key: 'recordIndexOpenRecordInSelector',
|
||||
get: ({ get }) => {
|
||||
const currentWorkspace = get(currentWorkspaceState);
|
||||
const isCommandMenuV2Enabled = checkIfFeatureFlagIsEnabledOnWorkspace(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
currentWorkspace,
|
||||
);
|
||||
|
||||
return isCommandMenuV2Enabled
|
||||
? get(recordIndexOpenRecordInState)
|
||||
: ViewOpenRecordInType.RECORD_PAGE;
|
||||
},
|
||||
});
|
||||
@ -1,20 +1,15 @@
|
||||
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
|
||||
import { useGetStandardObjectIcon } from '@/object-metadata/hooks/useGetStandardObjectIcon';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
import { FieldContext } from '@/object-record/record-field/contexts/FieldContext';
|
||||
import { isFieldValueReadOnly } from '@/object-record/record-field/utils/isFieldValueReadOnly';
|
||||
import { RecordInlineCell } from '@/object-record/record-inline-cell/components/RecordInlineCell';
|
||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||
import { RightDrawerTitleRecordInlineCell } from '@/object-record/record-right-drawer/components/RightDrawerTitleRecordInlineCell';
|
||||
import { useRecordShowContainerActions } from '@/object-record/record-show/hooks/useRecordShowContainerActions';
|
||||
import { useRecordShowContainerData } from '@/object-record/record-show/hooks/useRecordShowContainerData';
|
||||
import { RecordTitleCell } from '@/object-record/record-title-cell/components/RecordTitleCell';
|
||||
import { ShowPageSummaryCard } from '@/ui/layout/show-page/components/ShowPageSummaryCard';
|
||||
import { ShowPageSummaryCardSkeletonLoader } from '@/ui/layout/show-page/components/ShowPageSummaryCardSkeletonLoader';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { FeatureFlagKey, FieldMetadataType } from '~/generated/graphql';
|
||||
import { FieldMetadataType } from '~/generated/graphql';
|
||||
|
||||
type SummaryCardProps = {
|
||||
objectNameSingular: string;
|
||||
@ -51,16 +46,6 @@ export const SummaryCard = ({
|
||||
const { Icon, IconColor } = useGetStandardObjectIcon(objectNameSingular);
|
||||
const isMobile = useIsMobile() || isInRightDrawer;
|
||||
|
||||
const isReadOnly = isFieldValueReadOnly({
|
||||
objectNameSingular,
|
||||
isRecordDeleted: recordFromStore?.isDeleted,
|
||||
contextStoreCurrentViewType: ContextStoreViewType.ShowPage,
|
||||
});
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
if (isNewRightDrawerItemLoading || !isDefined(recordFromStore)) {
|
||||
return <ShowPageSummaryCardSkeletonLoader />;
|
||||
}
|
||||
@ -101,13 +86,7 @@ export const SummaryCard = ({
|
||||
isDisplayModeFixHeight: true,
|
||||
}}
|
||||
>
|
||||
{isCommandMenuV2Enabled ? (
|
||||
<RecordTitleCell sizeVariant="md" />
|
||||
) : isInRightDrawer ? (
|
||||
<RightDrawerTitleRecordInlineCell />
|
||||
) : (
|
||||
<RecordInlineCell readonly={isReadOnly} />
|
||||
)}
|
||||
</FieldContext.Provider>
|
||||
}
|
||||
avatarType={recordIdentifier?.avatarType ?? 'rounded'}
|
||||
|
||||
@ -5,20 +5,16 @@ import { recordIndexOpenRecordInState } from '@/object-record/record-index/state
|
||||
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useOpenRecordTableCellV2';
|
||||
import { useSelectedTableCellEditMode } from '@/object-record/record-table/record-table-cell/hooks/useSelectedTableCellEditMode';
|
||||
import { recordTablePendingRecordIdByGroupComponentFamilyState } from '@/object-record/record-table/states/recordTablePendingRecordIdByGroupComponentFamilyState';
|
||||
import { recordTablePendingRecordIdComponentState } from '@/object-record/record-table/states/recordTablePendingRecordIdComponentState';
|
||||
import { useRecordTitleCell } from '@/object-record/record-title-cell/hooks/useRecordTitleCell';
|
||||
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { useRecoilComponentCallbackStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentCallbackStateV2';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { v4 } from 'uuid';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
|
||||
export const useCreateNewTableRecord = ({
|
||||
@ -34,11 +30,6 @@ export const useCreateNewTableRecord = ({
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const setPendingRecordId = useSetRecoilComponentStateV2(
|
||||
recordTablePendingRecordIdComponentState,
|
||||
recordTableId,
|
||||
);
|
||||
|
||||
const recordTablePendingRecordIdByGroupFamilyState =
|
||||
useRecoilComponentCallbackStateV2(
|
||||
recordTablePendingRecordIdByGroupComponentFamilyState,
|
||||
@ -50,10 +41,6 @@ export const useCreateNewTableRecord = ({
|
||||
|
||||
const { openRecordInCommandMenu } = useCommandMenu();
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const { createOneRecord } = useCreateOneRecord({
|
||||
objectNameSingular: objectMetadataItem.nameSingular,
|
||||
shouldMatchRootQueryFilter: true,
|
||||
@ -68,7 +55,6 @@ export const useCreateNewTableRecord = ({
|
||||
async () => {
|
||||
const recordId = v4();
|
||||
|
||||
if (isCommandMenuV2Enabled) {
|
||||
const recordIndexOpenRecordIn = snapshot
|
||||
.getLoadable(recordIndexOpenRecordInState)
|
||||
.getValue();
|
||||
@ -84,8 +70,7 @@ export const useCreateNewTableRecord = ({
|
||||
|
||||
openRecordTitleCell({
|
||||
recordId,
|
||||
fieldMetadataId:
|
||||
objectMetadataItem.labelIdentifierFieldMetadataId,
|
||||
fieldMetadataId: objectMetadataItem.labelIdentifierFieldMetadataId,
|
||||
});
|
||||
} else {
|
||||
navigate(AppPath.RecordShowPage, {
|
||||
@ -93,39 +78,14 @@ export const useCreateNewTableRecord = ({
|
||||
objectRecordId: recordId,
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setPendingRecordId(recordId);
|
||||
setSelectedTableCellEditMode(-1, 0);
|
||||
setHotkeyScope(
|
||||
DEFAULT_CELL_SCOPE.scope,
|
||||
DEFAULT_CELL_SCOPE.customScopes,
|
||||
);
|
||||
|
||||
if (isDefined(objectMetadataItem.labelIdentifierFieldMetadataId)) {
|
||||
setActiveDropdownFocusIdAndMemorizePrevious(
|
||||
getDropdownFocusIdForRecordField(
|
||||
recordId,
|
||||
objectMetadataItem.labelIdentifierFieldMetadataId,
|
||||
'table-cell',
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
[
|
||||
createOneRecord,
|
||||
isCommandMenuV2Enabled,
|
||||
navigate,
|
||||
objectMetadataItem.labelIdentifierFieldMetadataId,
|
||||
objectMetadataItem.nameSingular,
|
||||
openRecordInCommandMenu,
|
||||
openRecordTitleCell,
|
||||
setActiveDropdownFocusIdAndMemorizePrevious,
|
||||
setHotkeyScope,
|
||||
setPendingRecordId,
|
||||
setSelectedTableCellEditMode,
|
||||
],
|
||||
);
|
||||
const createNewTableRecordInGroup = useRecoilCallback(
|
||||
|
||||
@ -10,8 +10,6 @@ import { SOFT_FOCUS_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-tab
|
||||
import { useLeaveTableFocus } from '@/object-record/record-table/hooks/internal/useLeaveTableFocus';
|
||||
import { useMoveEditModeToTableCellPosition } from '@/object-record/record-table/hooks/internal/useMoveEditModeToCellPosition';
|
||||
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useDragSelect } from '@/ui/utilities/drag-select/hooks/useDragSelect';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
@ -22,7 +20,7 @@ import { isDefined } from 'twenty-shared';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { useOpenFieldInputEditMode } from '@/object-record/record-field/hooks/useOpenFieldInputEditMode';
|
||||
import { useRecordIndexContextOrThrow } from '@/object-record/record-index/contexts/RecordIndexContext';
|
||||
import { recordIndexOpenRecordInSelector } from '@/object-record/record-index/states/selectors/recordIndexOpenRecordInSelector';
|
||||
import { recordIndexOpenRecordInState } from '@/object-record/record-index/states/recordIndexOpenRecordInState';
|
||||
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||
import { RECORD_TABLE_CLICK_OUTSIDE_LISTENER_ID } from '@/object-record/record-table/constants/RecordTableClickOutsideListenerId';
|
||||
import { getDropdownFocusIdForRecordField } from '@/object-record/utils/getDropdownFocusIdForRecordField';
|
||||
@ -30,7 +28,6 @@ import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/drop
|
||||
import { useClickOustideListenerStates } from '@/ui/utilities/pointer-event/hooks/useClickOustideListenerStates';
|
||||
import { ViewOpenRecordInType } from '@/views/types/ViewOpenRecordInType';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { IconList } from 'twenty-ui';
|
||||
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
|
||||
export const DEFAULT_CELL_SCOPE: HotkeyScope = {
|
||||
@ -68,7 +65,6 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
||||
|
||||
const initDraftValue = useInitDraftValueV2();
|
||||
|
||||
const { openRightDrawer } = useRightDrawer();
|
||||
const setViewableRecordId = useSetRecoilState(viewableRecordIdState);
|
||||
const setViewableRecordNameSingular = useSetRecoilState(
|
||||
viewableRecordNameSingularState,
|
||||
@ -124,7 +120,7 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
||||
leaveTableFocus();
|
||||
|
||||
const openRecordIn = snapshot
|
||||
.getLoadable(recordIndexOpenRecordInSelector)
|
||||
.getLoadable(recordIndexOpenRecordInState)
|
||||
.getValue();
|
||||
|
||||
if (openRecordIn === ViewOpenRecordInType.RECORD_PAGE) {
|
||||
@ -145,10 +141,6 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
||||
leaveTableFocus();
|
||||
setViewableRecordId(recordId);
|
||||
setViewableRecordNameSingular(objectNameSingular);
|
||||
openRightDrawer(RightDrawerPages.ViewRecord, {
|
||||
title: objectNameSingular,
|
||||
Icon: IconList,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
@ -204,7 +196,6 @@ export const useOpenRecordTableCellV2 = (tableScopeId: string) => {
|
||||
openRecordInCommandMenu,
|
||||
setViewableRecordId,
|
||||
setViewableRecordNameSingular,
|
||||
openRightDrawer,
|
||||
setHotkeyScope,
|
||||
],
|
||||
);
|
||||
|
||||
@ -2,10 +2,8 @@ import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { ActionMenuComponentInstanceContext } from '@/action-menu/states/contexts/ActionMenuComponentInstanceContext';
|
||||
import { recordIndexActionMenuDropdownPositionComponentState } from '@/action-menu/states/recordIndexActionMenuDropdownPositionComponentState';
|
||||
import { getActionBarIdFromActionMenuId } from '@/action-menu/utils/getActionBarIdFromActionMenuId';
|
||||
import { getActionMenuDropdownIdFromActionMenuId } from '@/action-menu/utils/getActionMenuDropdownIdFromActionMenuId';
|
||||
import { isRowSelectedComponentFamilyState } from '@/object-record/record-table/record-table-row/states/isRowSelectedComponentFamilyState';
|
||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||
import { useSetActiveDropdownFocusIdAndMemorizePrevious } from '@/ui/layout/dropdown/hooks/useSetFocusedDropdownIdAndMemorizePrevious';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
||||
@ -37,10 +35,6 @@ export const useTriggerActionMenuDropdown = ({
|
||||
getActionMenuDropdownIdFromActionMenuId(actionMenuInstanceId),
|
||||
);
|
||||
|
||||
const isActionBarOpenState = isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId: getActionBarIdFromActionMenuId(actionMenuInstanceId),
|
||||
});
|
||||
|
||||
const { setActiveDropdownFocusIdAndMemorizePrevious } =
|
||||
useSetActiveDropdownFocusIdAndMemorizePrevious();
|
||||
|
||||
@ -63,7 +57,6 @@ export const useTriggerActionMenuDropdown = ({
|
||||
set(isRowSelectedFamilyState(recordId), true);
|
||||
}
|
||||
|
||||
set(isActionBarOpenState, false);
|
||||
set(isActionMenuDropdownOpenState, true);
|
||||
|
||||
const actionMenuDropdownId =
|
||||
@ -72,7 +65,6 @@ export const useTriggerActionMenuDropdown = ({
|
||||
setActiveDropdownFocusIdAndMemorizePrevious(actionMenuDropdownId);
|
||||
},
|
||||
[
|
||||
isActionBarOpenState,
|
||||
isActionMenuDropdownOpenState,
|
||||
isRowSelectedFamilyState,
|
||||
recordIndexActionMenuDropdownPositionState,
|
||||
|
||||
@ -3,11 +3,9 @@ import { IconBuildingSkyscraper } from 'twenty-ui';
|
||||
|
||||
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
|
||||
import { SignInBackgroundMockContainer } from '@/sign-in-background-mock/components/SignInBackgroundMockContainer';
|
||||
import { PageAddButton } from '@/ui/layout/page/components/PageAddButton';
|
||||
import { PageBody } from '@/ui/layout/page/components/PageBody';
|
||||
import { PageContainer } from '@/ui/layout/page/components/PageContainer';
|
||||
import { PageHeader } from '@/ui/layout/page/components/PageHeader';
|
||||
import { PageHotkeysEffect } from '@/ui/layout/page/components/PageHotkeysEffect';
|
||||
|
||||
const StyledTableContainer = styled.div`
|
||||
display: flex;
|
||||
@ -18,10 +16,7 @@ const StyledTableContainer = styled.div`
|
||||
export const SignInBackgroundMockPage = () => {
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader title="Companies" Icon={IconBuildingSkyscraper}>
|
||||
<PageHotkeysEffect onAddButtonClick={() => {}} />
|
||||
<PageAddButton />
|
||||
</PageHeader>
|
||||
<PageHeader title="Companies" Icon={IconBuildingSkyscraper} />
|
||||
<PageBody>
|
||||
<RecordFieldValueSelectorContextProvider>
|
||||
<StyledTableContainer>
|
||||
|
||||
@ -1,61 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useBottomBarInternalHotkeyScopeManagement } from '@/ui/layout/bottom-bar/hooks/useBottomBarInternalHotkeyScopeManagement';
|
||||
import { BottomBarInstanceContext } from '@/ui/layout/bottom-bar/states/contexts/BottomBarInstanceContext';
|
||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
|
||||
const StyledContainerActionBar = styled.div`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
bottom: 38px;
|
||||
box-shadow: ${({ theme }) => theme.boxShadow.strong};
|
||||
display: flex;
|
||||
height: 48px;
|
||||
width: max-content;
|
||||
left: 50%;
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
position: absolute;
|
||||
top: auto;
|
||||
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
`;
|
||||
|
||||
type BottomBarProps = {
|
||||
bottomBarId: string;
|
||||
bottomBarHotkeyScopeFromParent: HotkeyScope;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export const BottomBar = ({
|
||||
bottomBarId,
|
||||
bottomBarHotkeyScopeFromParent,
|
||||
children,
|
||||
}: BottomBarProps) => {
|
||||
const isBottomBarOpen = useRecoilComponentValueV2(
|
||||
isBottomBarOpenedComponentState,
|
||||
bottomBarId,
|
||||
);
|
||||
|
||||
useBottomBarInternalHotkeyScopeManagement({
|
||||
bottomBarId,
|
||||
bottomBarHotkeyScopeFromParent,
|
||||
});
|
||||
|
||||
if (!isBottomBarOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<BottomBarInstanceContext.Provider value={{ instanceId: bottomBarId }}>
|
||||
<StyledContainerActionBar data-select-disable className="bottom-bar">
|
||||
{children}
|
||||
</StyledContainerActionBar>
|
||||
</BottomBarInstanceContext.Provider>
|
||||
);
|
||||
};
|
||||
@ -1,63 +0,0 @@
|
||||
import { BottomBar } from '@/ui/layout/bottom-bar/components/BottomBar';
|
||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||
import styled from '@emotion/styled';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { Button, IconPlus } from 'twenty-ui';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
`;
|
||||
|
||||
const meta: Meta<typeof BottomBar> = {
|
||||
title: 'UI/Layout/BottomBar/BottomBar',
|
||||
component: BottomBar,
|
||||
args: {
|
||||
bottomBarId: 'test',
|
||||
bottomBarHotkeyScopeFromParent: { scope: 'test' },
|
||||
children: (
|
||||
<StyledContainer>
|
||||
<Button title="Test 1" Icon={IconPlus} />
|
||||
<Button title="Test 2" Icon={IconPlus} />
|
||||
<Button title="Test 3" Icon={IconPlus} />
|
||||
</StyledContainer>
|
||||
),
|
||||
},
|
||||
argTypes: {
|
||||
bottomBarId: { control: false },
|
||||
bottomBarHotkeyScopeFromParent: { control: false },
|
||||
children: { control: false },
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const Default: StoryObj<typeof BottomBar> = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<RecoilRoot
|
||||
initializeState={({ set }) => {
|
||||
set(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId: 'test',
|
||||
}),
|
||||
true,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Story />
|
||||
</RecoilRoot>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export const Closed: StoryObj<typeof BottomBar> = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<RecoilRoot>
|
||||
<Story />
|
||||
</RecoilRoot>
|
||||
),
|
||||
],
|
||||
};
|
||||
@ -1,87 +0,0 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { bottomBarHotkeyComponentState } from '@/ui/layout/bottom-bar/states/bottomBarHotkeyComponentState';
|
||||
import { isBottomBarOpenedComponentState } from '@/ui/layout/bottom-bar/states/isBottomBarOpenedComponentState';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
export const useBottomBar = () => {
|
||||
const {
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const closeBottomBar = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(specificComponentId: string) => {
|
||||
goBackToPreviousHotkeyScope();
|
||||
set(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId: specificComponentId,
|
||||
}),
|
||||
false,
|
||||
);
|
||||
},
|
||||
[goBackToPreviousHotkeyScope],
|
||||
);
|
||||
|
||||
const openBottomBar = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
(specificComponentId: string, customHotkeyScope?: HotkeyScope) => {
|
||||
const bottomBarHotkeyScope = snapshot
|
||||
.getLoadable(
|
||||
bottomBarHotkeyComponentState.atomFamily({
|
||||
instanceId: specificComponentId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId: specificComponentId,
|
||||
}),
|
||||
true,
|
||||
);
|
||||
|
||||
if (isDefined(customHotkeyScope)) {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
customHotkeyScope.scope,
|
||||
customHotkeyScope.customScopes,
|
||||
);
|
||||
} else if (isDefined(bottomBarHotkeyScope)) {
|
||||
setHotkeyScopeAndMemorizePreviousScope(
|
||||
bottomBarHotkeyScope.scope,
|
||||
bottomBarHotkeyScope.customScopes,
|
||||
);
|
||||
}
|
||||
},
|
||||
[setHotkeyScopeAndMemorizePreviousScope],
|
||||
);
|
||||
|
||||
const toggleBottomBar = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(specificComponentId: string) => {
|
||||
const isBottomBarOpen = snapshot
|
||||
.getLoadable(
|
||||
isBottomBarOpenedComponentState.atomFamily({
|
||||
instanceId: specificComponentId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
if (isBottomBarOpen) {
|
||||
closeBottomBar(specificComponentId);
|
||||
} else {
|
||||
openBottomBar(specificComponentId);
|
||||
}
|
||||
},
|
||||
[closeBottomBar, openBottomBar],
|
||||
);
|
||||
|
||||
return {
|
||||
closeBottomBar,
|
||||
openBottomBar,
|
||||
toggleBottomBar,
|
||||
};
|
||||
};
|
||||
@ -1,27 +0,0 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { bottomBarHotkeyComponentState } from '@/ui/layout/bottom-bar/states/bottomBarHotkeyComponentState';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
|
||||
export const useBottomBarInternalHotkeyScopeManagement = ({
|
||||
bottomBarId,
|
||||
bottomBarHotkeyScopeFromParent,
|
||||
}: {
|
||||
bottomBarId?: string;
|
||||
bottomBarHotkeyScopeFromParent?: HotkeyScope;
|
||||
}) => {
|
||||
const [bottomBarHotkeyScope, setBottomBarHotkeyScope] =
|
||||
useRecoilComponentStateV2(bottomBarHotkeyComponentState, bottomBarId);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDeeplyEqual(bottomBarHotkeyScopeFromParent, bottomBarHotkeyScope)) {
|
||||
setBottomBarHotkeyScope(bottomBarHotkeyScopeFromParent);
|
||||
}
|
||||
}, [
|
||||
bottomBarHotkeyScope,
|
||||
bottomBarHotkeyScopeFromParent,
|
||||
setBottomBarHotkeyScope,
|
||||
]);
|
||||
};
|
||||
@ -1,11 +0,0 @@
|
||||
import { BottomBarInstanceContext } from '@/ui/layout/bottom-bar/states/contexts/BottomBarInstanceContext';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const bottomBarHotkeyComponentState = createComponentStateV2<
|
||||
HotkeyScope | null | undefined
|
||||
>({
|
||||
key: 'bottomBarHotkeyComponentState',
|
||||
defaultValue: null,
|
||||
componentInstanceContext: BottomBarInstanceContext,
|
||||
});
|
||||
@ -1,3 +0,0 @@
|
||||
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
|
||||
|
||||
export const BottomBarInstanceContext = createComponentInstanceContext();
|
||||
@ -1,8 +0,0 @@
|
||||
import { BottomBarInstanceContext } from '@/ui/layout/bottom-bar/states/contexts/BottomBarInstanceContext';
|
||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||
|
||||
export const isBottomBarOpenedComponentState = createComponentStateV2<boolean>({
|
||||
key: 'isBottomBarOpenedComponentState',
|
||||
defaultValue: false,
|
||||
componentInstanceContext: BottomBarInstanceContext,
|
||||
});
|
||||
@ -1,21 +1,12 @@
|
||||
import {
|
||||
AnimatedButton,
|
||||
IconButton,
|
||||
IconDotsVertical,
|
||||
IconX,
|
||||
getOsControlSymbol,
|
||||
useIsMobile,
|
||||
} from 'twenty-ui';
|
||||
import { AnimatedButton, getOsControlSymbol, useIsMobile } from 'twenty-ui';
|
||||
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { isCommandMenuOpenedState } from '@/command-menu/states/isCommandMenuOpenedState';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
const StyledButtonWrapper = styled.div`
|
||||
z-index: 30;
|
||||
@ -108,10 +99,6 @@ export const PageHeaderOpenCommandMenuButton = () => {
|
||||
const { toggleCommandMenu } = useCommandMenu();
|
||||
const isCommandMenuOpened = useRecoilValue(isCommandMenuOpenedState);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const ariaLabel = isCommandMenuOpened
|
||||
@ -122,11 +109,8 @@ export const PageHeaderOpenCommandMenuButton = () => {
|
||||
|
||||
return (
|
||||
<StyledButtonWrapper>
|
||||
{isCommandMenuV2Enabled ? (
|
||||
<AnimatedButton
|
||||
animatedSvg={
|
||||
<AnimatedIcon isCommandMenuOpened={isCommandMenuOpened} />
|
||||
}
|
||||
animatedSvg={<AnimatedIcon isCommandMenuOpened={isCommandMenuOpened} />}
|
||||
className="page-header-command-menu-button"
|
||||
dataTestId="page-header-command-menu-button"
|
||||
size={isMobile ? 'medium' : 'small'}
|
||||
@ -143,16 +127,6 @@ export const PageHeaderOpenCommandMenuButton = () => {
|
||||
ease: 'easeInOut',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
Icon={isCommandMenuOpened ? IconX : IconDotsVertical}
|
||||
size="medium"
|
||||
dataTestId="more-showpage-button"
|
||||
accent="default"
|
||||
variant="secondary"
|
||||
onClick={toggleCommandMenu}
|
||||
/>
|
||||
)}
|
||||
</StyledButtonWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,18 +1,12 @@
|
||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Button, IconButton, IconPlus, useIsMobile } from 'twenty-ui';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { Button, IconPlus, useIsMobile } from 'twenty-ui';
|
||||
|
||||
type PageAddButtonProps = {
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export const PageAddButton = ({ onClick }: PageAddButtonProps) => {
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const hasObjectReadOnlyPermission = useHasObjectReadOnlyPermission();
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
@ -24,8 +18,6 @@ export const PageAddButton = ({ onClick }: PageAddButtonProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{isCommandMenuV2Enabled ? (
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
dataTestId="add-button"
|
||||
@ -36,17 +28,5 @@ export const PageAddButton = ({ onClick }: PageAddButtonProps) => {
|
||||
onClick={onClick}
|
||||
ariaLabel={t`New record`}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
Icon={IconPlus}
|
||||
dataTestId="add-button"
|
||||
size="medium"
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
ariaLabel={t`Add`}
|
||||
onClick={onClick}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,9 +1,7 @@
|
||||
import { ReactNode } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { ReactNode } from 'react';
|
||||
import { MOBILE_VIEWPORT } from 'twenty-ui';
|
||||
|
||||
import { RightDrawer } from '@/ui/layout/right-drawer/components/RightDrawer';
|
||||
|
||||
import { PagePanel } from './PagePanel';
|
||||
|
||||
type PageBodyProps = {
|
||||
@ -45,6 +43,5 @@ export const PageBody = ({ children }: PageBodyProps) => (
|
||||
<StyledLeftContainer>
|
||||
<PagePanel>{children}</PagePanel>
|
||||
</StyledLeftContainer>
|
||||
<RightDrawer />
|
||||
</StyledMainContainer>
|
||||
);
|
||||
|
||||
@ -3,9 +3,6 @@ import styled from '@emotion/styled';
|
||||
import { ReactNode } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import {
|
||||
IconButton,
|
||||
IconChevronDown,
|
||||
IconChevronUp,
|
||||
IconComponent,
|
||||
IconX,
|
||||
LightIconButton,
|
||||
@ -17,8 +14,6 @@ import { NavigationDrawerCollapseButton } from '@/ui/navigation/navigation-drawe
|
||||
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
|
||||
export const PAGE_BAR_MIN_HEIGHT = 40;
|
||||
|
||||
@ -94,11 +89,6 @@ type PageHeaderProps = {
|
||||
title?: ReactNode;
|
||||
hasClosePageButton?: boolean;
|
||||
onClosePage?: () => void;
|
||||
hasPaginationButtons?: boolean;
|
||||
hasPreviousRecord?: boolean;
|
||||
hasNextRecord?: boolean;
|
||||
navigateToPreviousRecord?: () => void;
|
||||
navigateToNextRecord?: () => void;
|
||||
Icon?: IconComponent;
|
||||
children?: ReactNode;
|
||||
};
|
||||
@ -107,9 +97,6 @@ export const PageHeader = ({
|
||||
title,
|
||||
hasClosePageButton,
|
||||
onClosePage,
|
||||
hasPaginationButtons,
|
||||
navigateToPreviousRecord,
|
||||
navigateToNextRecord,
|
||||
Icon,
|
||||
children,
|
||||
}: PageHeaderProps) => {
|
||||
@ -119,10 +106,6 @@ export const PageHeader = ({
|
||||
isNavigationDrawerExpandedState,
|
||||
);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledTopBarContainer>
|
||||
<StyledLeftContainer>
|
||||
@ -141,25 +124,11 @@ export const PageHeader = ({
|
||||
)}
|
||||
|
||||
<StyledTopBarIconStyledTitleContainer>
|
||||
{!isCommandMenuV2Enabled && hasPaginationButtons && (
|
||||
<>
|
||||
<IconButton
|
||||
Icon={IconChevronUp}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
onClick={() => navigateToPreviousRecord?.()}
|
||||
/>
|
||||
<IconButton
|
||||
Icon={IconChevronDown}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
onClick={() => navigateToNextRecord?.()}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{Icon && (
|
||||
<StyledIconContainer>
|
||||
{Icon && <Icon size={theme.icon.size.md} />}
|
||||
<Icon size={theme.icon.size.md} />
|
||||
</StyledIconContainer>
|
||||
)}
|
||||
{title && (
|
||||
<StyledTitleContainer data-testid="top-bar-title">
|
||||
{typeof title === 'string' ? (
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
import { TableHotkeyScope } from '@/object-record/record-table/types/TableHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
|
||||
type PageHotkeysEffectProps = {
|
||||
onAddButtonClick?: () => void;
|
||||
};
|
||||
|
||||
export const PageHotkeysEffect = ({
|
||||
onAddButtonClick,
|
||||
}: PageHotkeysEffectProps) => {
|
||||
useScopedHotkeys('c', () => onAddButtonClick?.(), TableHotkeyScope.Table, [
|
||||
onAddButtonClick,
|
||||
]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
@ -1,94 +0,0 @@
|
||||
import { isRightDrawerAnimationCompletedState } from '@/ui/layout/right-drawer/states/isRightDrawerAnimationCompletedState';
|
||||
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { motion } from 'framer-motion';
|
||||
import { useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
||||
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
||||
|
||||
import { RIGHT_DRAWER_ANIMATION_VARIANTS } from '@/ui/layout/right-drawer/constants/RightDrawerAnimationVariants';
|
||||
import { RightDrawerAnimationVariant } from '@/ui/layout/right-drawer/types/RightDrawerAnimationVariant';
|
||||
import { RightDrawerRouter } from './RightDrawerRouter';
|
||||
|
||||
const StyledContainer = styled(motion.div)<{ isRightDrawerMinimized: boolean }>`
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
border-left: ${({ theme, isRightDrawerMinimized }) =>
|
||||
isRightDrawerMinimized
|
||||
? `1px solid ${theme.border.color.strong}`
|
||||
: `1px solid ${theme.border.color.medium}`};
|
||||
border-top: ${({ theme, isRightDrawerMinimized }) =>
|
||||
isRightDrawerMinimized ? `1px solid ${theme.border.color.strong}` : 'none'};
|
||||
border-top-left-radius: ${({ theme, isRightDrawerMinimized }) =>
|
||||
isRightDrawerMinimized ? theme.border.radius.md : '0'};
|
||||
box-shadow: ${({ theme, isRightDrawerMinimized }) =>
|
||||
isRightDrawerMinimized ? 'none' : theme.boxShadow.light};
|
||||
height: 100dvh;
|
||||
overflow-x: hidden;
|
||||
position: fixed;
|
||||
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 30;
|
||||
|
||||
.modal-backdrop {
|
||||
background: ${({ theme }) => theme.background.overlayTertiary};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledRightDrawer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const RightDrawer = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
||||
|
||||
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
|
||||
|
||||
const setIsRightDrawerAnimationCompleted = useSetRecoilState(
|
||||
isRightDrawerAnimationCompletedState,
|
||||
);
|
||||
|
||||
const rightDrawerPage = useRecoilValue(rightDrawerPageState);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const targetVariantForAnimation: RightDrawerAnimationVariant =
|
||||
!isRightDrawerOpen
|
||||
? 'closed'
|
||||
: isRightDrawerMinimized
|
||||
? 'minimized'
|
||||
: isMobile
|
||||
? 'fullScreen'
|
||||
: 'normal';
|
||||
|
||||
const handleAnimationComplete = () => {
|
||||
setIsRightDrawerAnimationCompleted(isRightDrawerOpen);
|
||||
};
|
||||
|
||||
if (!isDefined(rightDrawerPage)) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledContainer
|
||||
isRightDrawerMinimized={isRightDrawerMinimized}
|
||||
animate={targetVariantForAnimation}
|
||||
variants={RIGHT_DRAWER_ANIMATION_VARIANTS}
|
||||
transition={{ duration: theme.animation.duration.normal }}
|
||||
onAnimationComplete={handleAnimationComplete}
|
||||
>
|
||||
<StyledRightDrawer>
|
||||
{isRightDrawerOpen && <RightDrawerRouter />}
|
||||
</StyledRightDrawer>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -1,84 +0,0 @@
|
||||
import { RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID } from '@/ui/layout/right-drawer/constants/RightDrawerClickOutsideListener';
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||
import { isRightDrawerOpenState } from '@/ui/layout/right-drawer/states/isRightDrawerOpenState';
|
||||
import { rightDrawerCloseEventState } from '@/ui/layout/right-drawer/states/rightDrawerCloseEventsState';
|
||||
import { RightDrawerHotkeyScope } from '@/ui/layout/right-drawer/types/RightDrawerHotkeyScope';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import {
|
||||
ClickOutsideMode,
|
||||
useListenClickOutside,
|
||||
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { workflowReactFlowRefState } from '@/workflow/workflow-diagram/states/workflowReactFlowRefState';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRef } from 'react';
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
const StyledRightDrawerPage = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
export const RightDrawerContainer = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const rightDrawerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
||||
|
||||
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
|
||||
|
||||
const { closeRightDrawer } = useRightDrawer();
|
||||
|
||||
const workflowReactFlowRef = useRecoilValue(workflowReactFlowRefState);
|
||||
|
||||
useListenClickOutside({
|
||||
refs: [
|
||||
rightDrawerRef,
|
||||
...(workflowReactFlowRef ? [workflowReactFlowRef] : []),
|
||||
],
|
||||
excludeClassNames: ['confirmation-modal'],
|
||||
listenerId: RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID,
|
||||
callback: useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
(event) => {
|
||||
const isRightDrawerOpen = snapshot
|
||||
.getLoadable(isRightDrawerOpenState)
|
||||
.getValue();
|
||||
const isRightDrawerMinimized = snapshot
|
||||
.getLoadable(isRightDrawerMinimizedState)
|
||||
.getValue();
|
||||
|
||||
if (isRightDrawerOpen && !isRightDrawerMinimized) {
|
||||
set(rightDrawerCloseEventState, event);
|
||||
|
||||
closeRightDrawer();
|
||||
}
|
||||
},
|
||||
[closeRightDrawer],
|
||||
),
|
||||
mode: ClickOutsideMode.comparePixels,
|
||||
});
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Escape],
|
||||
() => {
|
||||
if (isRightDrawerOpen && !isRightDrawerMinimized) {
|
||||
closeRightDrawer();
|
||||
}
|
||||
},
|
||||
RightDrawerHotkeyScope.RightDrawer,
|
||||
[isRightDrawerOpen, isRightDrawerMinimized],
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledRightDrawerPage ref={rightDrawerRef}>
|
||||
{children}
|
||||
</StyledRightDrawerPage>
|
||||
);
|
||||
};
|
||||
@ -1,68 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { RightDrawerCalendarEvent } from '@/activities/calendar/right-drawer/components/RightDrawerCalendarEvent';
|
||||
import { RightDrawerAIChat } from '@/activities/copilot/right-drawer/components/RightDrawerAIChat';
|
||||
import { RightDrawerEmailThread } from '@/activities/emails/right-drawer/components/RightDrawerEmailThread';
|
||||
import { RightDrawerRecord } from '@/object-record/record-right-drawer/components/RightDrawerRecord';
|
||||
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||
|
||||
import { RightDrawerContainer } from '@/ui/layout/right-drawer/components/RightDrawerContainer';
|
||||
import { RightDrawerTopBar } from '@/ui/layout/right-drawer/components/RightDrawerTopBar';
|
||||
import { RightDrawerWorkflowEditStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowEditStep';
|
||||
import { RightDrawerWorkflowRunViewStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowRunViewStep';
|
||||
import { RightDrawerWorkflowViewStep } from '@/workflow/workflow-steps/components/RightDrawerWorkflowViewStep';
|
||||
import { RightDrawerWorkflowSelectAction } from '@/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectAction';
|
||||
import { RightDrawerWorkflowSelectTriggerType } from '@/workflow/workflow-trigger/components/RightDrawerWorkflowSelectTriggerType';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
||||
import { RightDrawerPages } from '../types/RightDrawerPages';
|
||||
|
||||
const StyledRightDrawerBody = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: calc(
|
||||
100vh - ${({ theme }) => theme.spacing(14)} - 1px
|
||||
); // (-1 for border)
|
||||
//overflow: auto;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const RIGHT_DRAWER_PAGES_CONFIG = {
|
||||
[RightDrawerPages.ViewEmailThread]: <RightDrawerEmailThread />,
|
||||
[RightDrawerPages.ViewCalendarEvent]: <RightDrawerCalendarEvent />,
|
||||
[RightDrawerPages.ViewRecord]: <RightDrawerRecord />,
|
||||
[RightDrawerPages.Copilot]: <RightDrawerAIChat />,
|
||||
[RightDrawerPages.WorkflowStepSelectTriggerType]: (
|
||||
<RightDrawerWorkflowSelectTriggerType />
|
||||
),
|
||||
[RightDrawerPages.WorkflowStepSelectAction]: (
|
||||
<RightDrawerWorkflowSelectAction />
|
||||
),
|
||||
[RightDrawerPages.WorkflowStepEdit]: <RightDrawerWorkflowEditStep />,
|
||||
[RightDrawerPages.WorkflowStepView]: <RightDrawerWorkflowViewStep />,
|
||||
[RightDrawerPages.WorkflowRunStepView]: <RightDrawerWorkflowRunViewStep />,
|
||||
} satisfies Record<RightDrawerPages, JSX.Element>;
|
||||
|
||||
export const RightDrawerRouter = () => {
|
||||
const [rightDrawerPage] = useRecoilState(rightDrawerPageState);
|
||||
|
||||
const rightDrawerPageComponent = isDefined(rightDrawerPage) ? (
|
||||
RIGHT_DRAWER_PAGES_CONFIG[rightDrawerPage]
|
||||
) : (
|
||||
<></>
|
||||
);
|
||||
|
||||
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
|
||||
|
||||
return (
|
||||
<RightDrawerContainer>
|
||||
<RightDrawerTopBar />
|
||||
{!isRightDrawerMinimized && (
|
||||
<StyledRightDrawerBody>
|
||||
{rightDrawerPageComponent}
|
||||
</StyledRightDrawerBody>
|
||||
)}
|
||||
</RightDrawerContainer>
|
||||
);
|
||||
};
|
||||
@ -1,140 +0,0 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { Chip, ChipAccent, ChipSize, useIcons } from 'twenty-ui';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
|
||||
import { isNewViewableRecordLoadingState } from '@/object-record/record-right-drawer/states/isNewViewableRecordLoading';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||
import { RightDrawerTopBarCloseButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarCloseButton';
|
||||
import { RightDrawerTopBarExpandButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarExpandButton';
|
||||
import { RightDrawerTopBarMinimizeButton } from '@/ui/layout/right-drawer/components/RightDrawerTopBarMinimizeButton';
|
||||
import { StyledRightDrawerTopBar } from '@/ui/layout/right-drawer/components/StyledRightDrawerTopBar';
|
||||
import { RIGHT_DRAWER_PAGE_ICONS } from '@/ui/layout/right-drawer/constants/RightDrawerPageIcons';
|
||||
import { RIGHT_DRAWER_PAGE_TITLES } from '@/ui/layout/right-drawer/constants/RightDrawerPageTitles';
|
||||
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||
import { rightDrawerPageState } from '@/ui/layout/right-drawer/states/rightDrawerPageState';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
|
||||
const StyledTopBarWrapper = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledMinimizeTopBarTitleContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
height: 24px;
|
||||
width: 168px;
|
||||
`;
|
||||
|
||||
const StyledMinimizeTopBarTitle = styled.div`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const StyledMinimizeTopBarIcon = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export const RightDrawerTopBar = () => {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const rightDrawerPage = useRecoilValue(rightDrawerPageState);
|
||||
|
||||
const [isRightDrawerMinimized, setIsRightDrawerMinimized] = useRecoilState(
|
||||
isRightDrawerMinimizedState,
|
||||
);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const handleOnclick = () => {
|
||||
if (isRightDrawerMinimized) {
|
||||
setIsRightDrawerMinimized(false);
|
||||
}
|
||||
};
|
||||
|
||||
const { getIcon } = useIcons();
|
||||
|
||||
const viewableRecordNameSingular = useRecoilValue(
|
||||
viewableRecordNameSingularState,
|
||||
);
|
||||
|
||||
const isNewViewableRecordLoading = useRecoilValue(
|
||||
isNewViewableRecordLoadingState,
|
||||
);
|
||||
|
||||
const viewableRecordId = useRecoilValue(viewableRecordIdState);
|
||||
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular: viewableRecordNameSingular ?? 'company',
|
||||
});
|
||||
|
||||
if (!rightDrawerPage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const PageIcon = getIcon(RIGHT_DRAWER_PAGE_ICONS[rightDrawerPage]);
|
||||
|
||||
const ObjectIcon = getIcon(objectMetadataItem.icon);
|
||||
|
||||
const isViewRecordRightDrawerPage =
|
||||
rightDrawerPage === RightDrawerPages.ViewRecord;
|
||||
|
||||
const label = isViewRecordRightDrawerPage
|
||||
? objectMetadataItem.labelSingular
|
||||
: RIGHT_DRAWER_PAGE_TITLES[rightDrawerPage];
|
||||
|
||||
const Icon = isViewRecordRightDrawerPage ? ObjectIcon : PageIcon;
|
||||
|
||||
return (
|
||||
<StyledRightDrawerTopBar
|
||||
onClick={handleOnclick}
|
||||
isRightDrawerMinimized={isRightDrawerMinimized}
|
||||
>
|
||||
{!isRightDrawerMinimized && (
|
||||
<Chip
|
||||
disabled={isNewViewableRecordLoading}
|
||||
label={label}
|
||||
leftComponent={() => <Icon size={theme.icon.size.md} />}
|
||||
size={ChipSize.Large}
|
||||
accent={ChipAccent.TextSecondary}
|
||||
clickable={false}
|
||||
/>
|
||||
)}
|
||||
{isRightDrawerMinimized && (
|
||||
<StyledMinimizeTopBarTitleContainer>
|
||||
<StyledMinimizeTopBarIcon>
|
||||
<Icon size={theme.icon.size.md} />
|
||||
</StyledMinimizeTopBarIcon>
|
||||
<StyledMinimizeTopBarTitle>{label}</StyledMinimizeTopBarTitle>
|
||||
</StyledMinimizeTopBarTitleContainer>
|
||||
)}
|
||||
<StyledTopBarWrapper>
|
||||
{!isMobile && !isRightDrawerMinimized && (
|
||||
<RightDrawerTopBarMinimizeButton />
|
||||
)}
|
||||
|
||||
{!isMobile &&
|
||||
!isRightDrawerMinimized &&
|
||||
isViewRecordRightDrawerPage && (
|
||||
<RightDrawerTopBarExpandButton
|
||||
to={
|
||||
getBasePathToShowPage({
|
||||
objectNameSingular: viewableRecordNameSingular ?? '',
|
||||
}) + viewableRecordId
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<RightDrawerTopBarCloseButton />
|
||||
</StyledTopBarWrapper>
|
||||
</StyledRightDrawerTopBar>
|
||||
);
|
||||
};
|
||||
@ -1,20 +0,0 @@
|
||||
import { IconX, LightIconButton } from 'twenty-ui';
|
||||
|
||||
import { useRightDrawer } from '../hooks/useRightDrawer';
|
||||
|
||||
export const RightDrawerTopBarCloseButton = () => {
|
||||
const { closeRightDrawer } = useRightDrawer();
|
||||
|
||||
const handleButtonClick = () => {
|
||||
closeRightDrawer();
|
||||
};
|
||||
|
||||
return (
|
||||
<LightIconButton
|
||||
Icon={IconX}
|
||||
onClick={handleButtonClick}
|
||||
size="medium"
|
||||
accent="tertiary"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,17 +0,0 @@
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
import { IconExternalLink, LightIconButton, UndecoratedLink } from 'twenty-ui';
|
||||
|
||||
export const RightDrawerTopBarExpandButton = ({ to }: { to: string }) => {
|
||||
const { closeRightDrawer } = useRightDrawer();
|
||||
|
||||
return (
|
||||
<UndecoratedLink to={to}>
|
||||
<LightIconButton
|
||||
size="medium"
|
||||
accent="tertiary"
|
||||
Icon={IconExternalLink}
|
||||
onClick={() => closeRightDrawer()}
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
);
|
||||
};
|
||||
@ -1,21 +0,0 @@
|
||||
import { IconMinus, LightIconButton } from 'twenty-ui';
|
||||
|
||||
import { useRightDrawer } from '@/ui/layout/right-drawer/hooks/useRightDrawer';
|
||||
|
||||
export const RightDrawerTopBarMinimizeButton = () => {
|
||||
const { isRightDrawerMinimized, minimizeRightDrawer, maximizeRightDrawer } =
|
||||
useRightDrawer();
|
||||
|
||||
const handleButtonClick = () => {
|
||||
isRightDrawerMinimized ? maximizeRightDrawer() : minimizeRightDrawer();
|
||||
};
|
||||
|
||||
return (
|
||||
<LightIconButton
|
||||
Icon={IconMinus}
|
||||
onClick={handleButtonClick}
|
||||
size="medium"
|
||||
accent="tertiary"
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -1,22 +0,0 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
export const StyledRightDrawerTopBar = styled.div<{
|
||||
isRightDrawerMinimized: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
color: ${({ theme }) => theme.font.color.secondary};
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: ${({ theme }) => theme.font.size.md};
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
height: ${({ isRightDrawerMinimized }) =>
|
||||
isRightDrawerMinimized ? '40px' : '56px'};
|
||||
justify-content: space-between;
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
cursor: ${({ isRightDrawerMinimized }) =>
|
||||
isRightDrawerMinimized ? 'pointer' : 'default'};
|
||||
`;
|
||||
@ -1,56 +0,0 @@
|
||||
import { expect } from '@storybook/jest';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { RightDrawerTopBar } from '../RightDrawerTopBar';
|
||||
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
||||
import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
import { rightDrawerPageState } from '@/ui/layout/right-drawer/states/rightDrawerPageState';
|
||||
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||
import { useEffect } from 'react';
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
import { IconsProviderDecorator } from '~/testing/decorators/IconsProviderDecorator';
|
||||
import { within } from '@storybook/test';
|
||||
|
||||
const RightDrawerTopBarStateSetterEffect = () => {
|
||||
const setRightDrawerPage = useSetRecoilState(rightDrawerPageState);
|
||||
|
||||
const setIsRightDrawerMinimizedState = useSetRecoilState(
|
||||
isRightDrawerMinimizedState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setRightDrawerPage(RightDrawerPages.ViewRecord);
|
||||
setIsRightDrawerMinimizedState(false);
|
||||
}, [setIsRightDrawerMinimizedState, setRightDrawerPage]);
|
||||
return null;
|
||||
};
|
||||
|
||||
const meta: Meta<typeof RightDrawerTopBar> = {
|
||||
title: 'Modules/Activities/RightDrawer/RightDrawerTopBar',
|
||||
component: RightDrawerTopBar,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div style={{ width: '500px' }}>
|
||||
<Story />
|
||||
<RightDrawerTopBarStateSetterEffect />
|
||||
</div>
|
||||
),
|
||||
IconsProviderDecorator,
|
||||
ComponentWithRouterDecorator,
|
||||
ObjectMetadataItemsDecorator,
|
||||
SnackBarDecorator,
|
||||
],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof RightDrawerTopBar>;
|
||||
|
||||
export const Default: Story = {
|
||||
play: async () => {
|
||||
const canvas = within(document.body);
|
||||
|
||||
expect(await canvas.findByText('Company')).toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
@ -1,32 +0,0 @@
|
||||
import { THEME_COMMON } from 'twenty-ui';
|
||||
|
||||
export const RIGHT_DRAWER_ANIMATION_VARIANTS = {
|
||||
fullScreen: {
|
||||
x: '0%',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
bottom: '0',
|
||||
top: '0',
|
||||
},
|
||||
normal: {
|
||||
x: '0%',
|
||||
width: THEME_COMMON.rightDrawerWidth,
|
||||
height: '100%',
|
||||
bottom: '0',
|
||||
top: '0',
|
||||
},
|
||||
closed: {
|
||||
x: '100%',
|
||||
width: '0',
|
||||
height: '100%',
|
||||
bottom: '0',
|
||||
top: 'auto',
|
||||
},
|
||||
minimized: {
|
||||
x: '0%',
|
||||
width: 220,
|
||||
height: 41,
|
||||
bottom: '0',
|
||||
top: 'auto',
|
||||
},
|
||||
};
|
||||
@ -1,2 +0,0 @@
|
||||
export const RIGHT_DRAWER_CLICK_OUTSIDE_LISTENER_ID =
|
||||
'right-drawer-click-outside-listener';
|
||||
@ -1,13 +0,0 @@
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
|
||||
export const RIGHT_DRAWER_PAGE_ICONS = {
|
||||
[RightDrawerPages.ViewEmailThread]: 'IconMail',
|
||||
[RightDrawerPages.ViewCalendarEvent]: 'IconCalendarEvent',
|
||||
[RightDrawerPages.ViewRecord]: 'Icon123',
|
||||
[RightDrawerPages.Copilot]: 'IconSparkles',
|
||||
[RightDrawerPages.WorkflowStepSelectTriggerType]: 'IconSparkles',
|
||||
[RightDrawerPages.WorkflowStepSelectAction]: 'IconSparkles',
|
||||
[RightDrawerPages.WorkflowStepEdit]: 'IconSparkles',
|
||||
[RightDrawerPages.WorkflowStepView]: 'IconSparkles',
|
||||
[RightDrawerPages.WorkflowRunStepView]: 'IconSparkles',
|
||||
} satisfies Record<RightDrawerPages, string>;
|
||||
@ -1,13 +0,0 @@
|
||||
import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPages';
|
||||
|
||||
export const RIGHT_DRAWER_PAGE_TITLES = {
|
||||
[RightDrawerPages.ViewEmailThread]: 'Email Thread',
|
||||
[RightDrawerPages.ViewCalendarEvent]: 'Calendar Event',
|
||||
[RightDrawerPages.ViewRecord]: 'Record Editor',
|
||||
[RightDrawerPages.Copilot]: 'Copilot',
|
||||
[RightDrawerPages.WorkflowStepSelectTriggerType]: 'Workflow',
|
||||
[RightDrawerPages.WorkflowStepSelectAction]: 'Workflow',
|
||||
[RightDrawerPages.WorkflowStepEdit]: 'Workflow',
|
||||
[RightDrawerPages.WorkflowStepView]: 'Workflow',
|
||||
[RightDrawerPages.WorkflowRunStepView]: 'Workflow',
|
||||
} satisfies Record<RightDrawerPages, string>;
|
||||
@ -1,52 +0,0 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { RecoilRoot, useRecoilValue } from 'recoil';
|
||||
|
||||
import { IconList } from 'twenty-ui';
|
||||
import { isRightDrawerOpenState } from '../../states/isRightDrawerOpenState';
|
||||
import { rightDrawerPageState } from '../../states/rightDrawerPageState';
|
||||
import { RightDrawerPages } from '../../types/RightDrawerPages';
|
||||
import { useRightDrawer } from '../useRightDrawer';
|
||||
|
||||
describe('useRightDrawer', () => {
|
||||
it('Should test the default behavior of useRightDrawer and change the states as the function calls', async () => {
|
||||
const useCombinedHooks = () => {
|
||||
const { openRightDrawer, closeRightDrawer } = useRightDrawer();
|
||||
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
||||
|
||||
const rightDrawerPage = useRecoilValue(rightDrawerPageState);
|
||||
|
||||
return {
|
||||
openRightDrawer,
|
||||
closeRightDrawer,
|
||||
isRightDrawerOpen,
|
||||
rightDrawerPage,
|
||||
};
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useCombinedHooks(), {
|
||||
wrapper: RecoilRoot,
|
||||
});
|
||||
|
||||
expect(result.current.rightDrawerPage).toBeNull();
|
||||
expect(result.current.isRightDrawerOpen).toBeFalsy();
|
||||
expect(result.current.openRightDrawer).toBeInstanceOf(Function);
|
||||
expect(result.current.closeRightDrawer).toBeInstanceOf(Function);
|
||||
|
||||
await act(async () => {
|
||||
result.current.openRightDrawer(RightDrawerPages.ViewRecord, {
|
||||
title: 'Company',
|
||||
Icon: IconList,
|
||||
});
|
||||
});
|
||||
|
||||
expect(result.current.rightDrawerPage).toEqual(RightDrawerPages.ViewRecord);
|
||||
expect(result.current.isRightDrawerOpen).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
result.current.closeRightDrawer();
|
||||
});
|
||||
|
||||
expect(result.current.isRightDrawerOpen).toBeFalsy();
|
||||
});
|
||||
});
|
||||
@ -1,114 +0,0 @@
|
||||
import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
|
||||
import { isRightDrawerMinimizedState } from '@/ui/layout/right-drawer/states/isRightDrawerMinimizedState';
|
||||
import { rightDrawerCloseEventState } from '@/ui/layout/right-drawer/states/rightDrawerCloseEventsState';
|
||||
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
|
||||
import { mapRightDrawerPageToCommandMenuPage } from '@/ui/layout/right-drawer/utils/mapRightDrawerPageToCommandMenuPage';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { IconComponent } from 'twenty-ui';
|
||||
import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { isRightDrawerOpenState } from '../states/isRightDrawerOpenState';
|
||||
import { rightDrawerPageState } from '../states/rightDrawerPageState';
|
||||
import { RightDrawerPages } from '../types/RightDrawerPages';
|
||||
|
||||
export const useRightDrawer = () => {
|
||||
const isRightDrawerOpen = useRecoilValue(isRightDrawerOpenState);
|
||||
const isRightDrawerMinimized = useRecoilValue(isRightDrawerMinimizedState);
|
||||
|
||||
const rightDrawerPage = useRecoilValue(rightDrawerPageState);
|
||||
|
||||
const isCommandMenuV2Enabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IsCommandMenuV2Enabled,
|
||||
);
|
||||
|
||||
const { navigateCommandMenu } = useCommandMenu();
|
||||
|
||||
const openRightDrawer = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(
|
||||
rightDrawerPage: RightDrawerPages,
|
||||
commandMenuPageInfo: {
|
||||
title: string;
|
||||
Icon: IconComponent;
|
||||
},
|
||||
) => {
|
||||
if (isCommandMenuV2Enabled) {
|
||||
const commandMenuPage =
|
||||
mapRightDrawerPageToCommandMenuPage(rightDrawerPage);
|
||||
|
||||
navigateCommandMenu({
|
||||
page: commandMenuPage,
|
||||
pageTitle: commandMenuPageInfo.title,
|
||||
pageIcon: commandMenuPageInfo.Icon,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
set(rightDrawerPageState, rightDrawerPage);
|
||||
set(isRightDrawerOpenState, true);
|
||||
set(isRightDrawerMinimizedState, false);
|
||||
},
|
||||
[isCommandMenuV2Enabled, navigateCommandMenu],
|
||||
);
|
||||
|
||||
const closeRightDrawer = useRecoilCallback(
|
||||
({ set }) =>
|
||||
(args?: { emitCloseEvent?: boolean }) => {
|
||||
set(isRightDrawerOpenState, false);
|
||||
set(isRightDrawerMinimizedState, false);
|
||||
if (isDefined(args?.emitCloseEvent) && args?.emitCloseEvent) {
|
||||
emitRightDrawerCloseEvent();
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const minimizeRightDrawer = useRecoilCallback(
|
||||
({ set }) =>
|
||||
() => {
|
||||
set(isRightDrawerOpenState, true);
|
||||
set(isRightDrawerMinimizedState, true);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const maximizeRightDrawer = useRecoilCallback(
|
||||
({ set }) =>
|
||||
() => {
|
||||
set(isRightDrawerMinimizedState, false);
|
||||
set(isRightDrawerOpenState, true);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const isSameEventThanRightDrawerClose = useRecoilCallback(
|
||||
({ snapshot }) =>
|
||||
(event: MouseEvent | TouchEvent) => {
|
||||
const rightDrawerCloseEvent = snapshot
|
||||
.getLoadable(rightDrawerCloseEventState)
|
||||
.getValue();
|
||||
|
||||
const isSameEvent =
|
||||
rightDrawerCloseEvent?.target === event.target &&
|
||||
rightDrawerCloseEvent?.timeStamp === event.timeStamp;
|
||||
|
||||
return isSameEvent;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return {
|
||||
rightDrawerPage,
|
||||
isRightDrawerOpen,
|
||||
isRightDrawerMinimized,
|
||||
openRightDrawer,
|
||||
closeRightDrawer,
|
||||
minimizeRightDrawer,
|
||||
maximizeRightDrawer,
|
||||
isSameEventThanRightDrawerClose,
|
||||
};
|
||||
};
|
||||
@ -1,6 +0,0 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
export const isRightDrawerAnimationCompletedState = createState<boolean>({
|
||||
key: 'isRightDrawerAnimationCompletedState',
|
||||
defaultValue: false,
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
export const isRightDrawerMinimizedState = createState<boolean>({
|
||||
key: 'ui/layout/is-right-drawer-minimized',
|
||||
defaultValue: false,
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
export const isRightDrawerOpenState = createState<boolean>({
|
||||
key: 'ui/layout/is-right-drawer-open',
|
||||
defaultValue: false,
|
||||
});
|
||||
@ -1,8 +0,0 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
import { MessageThread } from '@/activities/emails/types/MessageThread';
|
||||
|
||||
export const messageThreadState = createState<MessageThread | null>({
|
||||
key: 'messageThreadState',
|
||||
defaultValue: null,
|
||||
});
|
||||
@ -1,6 +0,0 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
export const rightDrawerCloseEventState = createState<Event | null>({
|
||||
key: 'rightDrawerCloseEventState',
|
||||
defaultValue: null,
|
||||
});
|
||||
@ -1,9 +0,0 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
import { RightDrawerTopBarDropdownButtons } from '@/ui/layout/right-drawer/types/RightDrawerTopBarDropdownButtons';
|
||||
|
||||
export const rightDrawerTopBarDropdownButtonState =
|
||||
createState<RightDrawerTopBarDropdownButtons | null>({
|
||||
key: 'rightDrawerTopBarDropdownButtonState',
|
||||
defaultValue: null,
|
||||
});
|
||||
@ -1,8 +0,0 @@
|
||||
import { createState } from '@ui/utilities/state/utils/createState';
|
||||
|
||||
import { RightDrawerPages } from '../types/RightDrawerPages';
|
||||
|
||||
export const rightDrawerPageState = createState<RightDrawerPages | null>({
|
||||
key: 'ui/layout/right-drawer-page',
|
||||
defaultValue: null,
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user