250 implement restore context button on command menu (#9836)
Closes https://github.com/twentyhq/core-team-issues/issues/250 https://github.com/user-attachments/assets/9c120188-497d-4273-9137-f8d0de3bd884
This commit is contained in:
@ -1,19 +1,24 @@
|
||||
import { CommandGroup } from '@/command-menu/components/CommandGroup';
|
||||
import { CommandMenuDefaultSelectionEffect } from '@/command-menu/components/CommandMenuDefaultSelectionEffect';
|
||||
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
|
||||
import { ResetContextToSelectionCommandButton } from '@/command-menu/components/ResetContextToSelectionCommandButton';
|
||||
import { COMMAND_MENU_SEARCH_BAR_HEIGHT } from '@/command-menu/constants/CommandMenuSearchBarHeight';
|
||||
import { COMMAND_MENU_SEARCH_BAR_PADDING } from '@/command-menu/constants/CommandMenuSearchBarPadding';
|
||||
import { useCommandMenuOnItemClick } from '@/command-menu/hooks/useCommandMenuOnItemClick';
|
||||
import { useMatchingCommandMenuCommands } from '@/command-menu/hooks/useMatchingCommandMenuCommands';
|
||||
import { useResetPreviousCommandMenuContext } from '@/command-menu/hooks/useResetPreviousCommandMenuContext';
|
||||
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||
import { Command } from '@/command-menu/types/Command';
|
||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||
import { SelectableItem } from '@/ui/layout/selectable-list/components/SelectableItem';
|
||||
import { SelectableList } from '@/ui/layout/selectable-list/components/SelectableList';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
@ -60,6 +65,8 @@ export const CommandMenu = () => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const { onItemClick } = useCommandMenuOnItemClick();
|
||||
const { resetPreviousCommandMenuContext } =
|
||||
useResetPreviousCommandMenuContext();
|
||||
|
||||
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
|
||||
|
||||
@ -84,22 +91,33 @@ export const CommandMenu = () => {
|
||||
commandMenuSearch,
|
||||
});
|
||||
|
||||
const selectableItems = copilotCommands
|
||||
.concat(matchingStandardActionRecordSelectionCommands)
|
||||
.concat(matchingWorkflowRunRecordSelectionCommands)
|
||||
.concat(matchingStandardActionGlobalCommands)
|
||||
.concat(matchingWorkflowRunGlobalCommands)
|
||||
.concat(matchingNavigateCommand)
|
||||
.concat(peopleCommands)
|
||||
.concat(companyCommands)
|
||||
.concat(opportunityCommands)
|
||||
.concat(noteCommands)
|
||||
.concat(tasksCommands)
|
||||
.concat(customObjectCommands)
|
||||
const selectableItems: Command[] = copilotCommands
|
||||
.concat(
|
||||
matchingStandardActionRecordSelectionCommands,
|
||||
matchingWorkflowRunRecordSelectionCommands,
|
||||
matchingStandardActionGlobalCommands,
|
||||
matchingWorkflowRunGlobalCommands,
|
||||
matchingNavigateCommand,
|
||||
peopleCommands,
|
||||
companyCommands,
|
||||
opportunityCommands,
|
||||
noteCommands,
|
||||
tasksCommands,
|
||||
customObjectCommands,
|
||||
)
|
||||
.filter(isDefined);
|
||||
|
||||
const previousContextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
|
||||
contextStoreCurrentObjectMetadataIdComponentState,
|
||||
'command-menu-previous',
|
||||
);
|
||||
|
||||
const selectableItemIds = selectableItems.map((item) => item.id);
|
||||
|
||||
if (isNonEmptyString(previousContextStoreCurrentObjectMetadataId)) {
|
||||
selectableItemIds.unshift('reset-context-to-selection');
|
||||
}
|
||||
|
||||
const commandGroups: CommandGroupConfig[] = [
|
||||
{
|
||||
heading: t`Copilot`,
|
||||
@ -168,6 +186,11 @@ export const CommandMenu = () => {
|
||||
selectableItemIdArray={selectableItemIds}
|
||||
hotkeyScope={AppHotkeyScope.CommandMenu}
|
||||
onEnter={(itemId) => {
|
||||
if (itemId === 'reset-context-to-selection') {
|
||||
resetPreviousCommandMenuContext();
|
||||
return;
|
||||
}
|
||||
|
||||
const command = selectableItems.find(
|
||||
(item) => item.id === itemId,
|
||||
);
|
||||
@ -184,6 +207,16 @@ export const CommandMenu = () => {
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isNonEmptyString(
|
||||
previousContextStoreCurrentObjectMetadataId,
|
||||
) && (
|
||||
<CommandGroup heading={t`Context`} key={t`Context`}>
|
||||
<SelectableItem itemId="reset-context-to-selection">
|
||||
<ResetContextToSelectionCommandButton />
|
||||
</SelectableItem>
|
||||
</CommandGroup>
|
||||
)}
|
||||
|
||||
{isNoResults && !isLoading && (
|
||||
<StyledEmpty>No results found</StyledEmpty>
|
||||
)}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
const StyledChip = styled.div`
|
||||
const StyledChip = styled.div<{ variant?: 'default' | 'small' }>`
|
||||
align-items: center;
|
||||
background: ${({ theme }) => theme.background.transparent.light};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
@ -8,7 +8,8 @@ const StyledChip = styled.div`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
height: ${({ theme }) => theme.spacing(8)};
|
||||
height: ${({ theme, variant }) =>
|
||||
variant === 'small' ? theme.spacing(6) : theme.spacing(8)};
|
||||
padding: 0 ${({ theme }) => theme.spacing(2)};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
||||
@ -40,13 +41,15 @@ export const CommandMenuContextChip = ({
|
||||
Icons,
|
||||
text,
|
||||
withIconBackground,
|
||||
variant = 'default',
|
||||
}: {
|
||||
Icons: React.ReactNode[];
|
||||
text?: string;
|
||||
withIconBackground?: boolean;
|
||||
variant?: 'default' | 'small';
|
||||
}) => {
|
||||
return (
|
||||
<StyledChip>
|
||||
<StyledChip variant={variant}>
|
||||
<StyledIconsContainer>
|
||||
{Icons.map((Icon, index) => (
|
||||
<StyledIconWrapper
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
import { CommandMenuContextChip } from '@/command-menu/components/CommandMenuContextChip';
|
||||
import { CommandMenuContextRecordChipAvatars } from '@/command-menu/components/CommandMenuContextRecordChipAvatars';
|
||||
import { getSelectedRecordsContextText } from '@/command-menu/utils/getRecordContextText';
|
||||
import { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore';
|
||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
|
||||
import { capitalize } from 'twenty-shared';
|
||||
|
||||
export const CommandMenuContextRecordChip = ({
|
||||
objectMetadataItemId,
|
||||
instanceId,
|
||||
variant = 'default',
|
||||
}: {
|
||||
objectMetadataItemId: string;
|
||||
instanceId?: string;
|
||||
variant?: 'default' | 'small';
|
||||
}) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItemById({
|
||||
objectId: objectMetadataItemId,
|
||||
@ -17,6 +20,7 @@ export const CommandMenuContextRecordChip = ({
|
||||
const { records, loading, totalCount } =
|
||||
useFindManyRecordsSelectedInContextStore({
|
||||
limit: 3,
|
||||
instanceId,
|
||||
});
|
||||
|
||||
if (loading || !totalCount) {
|
||||
@ -31,17 +35,16 @@ export const CommandMenuContextRecordChip = ({
|
||||
/>
|
||||
));
|
||||
|
||||
const text =
|
||||
totalCount === 1
|
||||
? getObjectRecordIdentifier({ objectMetadataItem, record: records[0] })
|
||||
.name
|
||||
: `${totalCount} ${capitalize(objectMetadataItem.namePlural)}`;
|
||||
|
||||
return (
|
||||
<CommandMenuContextChip
|
||||
text={text}
|
||||
text={getSelectedRecordsContextText(
|
||||
objectMetadataItem,
|
||||
records,
|
||||
totalCount,
|
||||
)}
|
||||
Icons={Avatars}
|
||||
withIconBackground={true}
|
||||
variant={variant}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -14,7 +14,10 @@ export const CommandMenuDefaultSelectionEffect = ({
|
||||
const selectedItemId = useRecoilValue(selectedItemIdState);
|
||||
|
||||
useEffect(() => {
|
||||
if (isDefined(selectedItemId)) {
|
||||
if (
|
||||
isDefined(selectedItemId) &&
|
||||
selectableItemIds.includes(selectedItemId)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import { IconArrowUpRight, IconComponent, MenuItemCommand } from 'twenty-ui';
|
||||
|
||||
import { useCommandMenuOnItemClick } from '@/command-menu/hooks/useCommandMenuOnItemClick';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export type CommandMenuItemProps = {
|
||||
label: string;
|
||||
@ -14,6 +15,7 @@ export type CommandMenuItemProps = {
|
||||
firstHotKey?: string;
|
||||
secondHotKey?: string;
|
||||
shouldCloseCommandMenuOnClick?: boolean;
|
||||
RightComponent?: ReactNode;
|
||||
};
|
||||
|
||||
export const CommandMenuItem = ({
|
||||
@ -25,6 +27,7 @@ export const CommandMenuItem = ({
|
||||
firstHotKey,
|
||||
secondHotKey,
|
||||
shouldCloseCommandMenuOnClick,
|
||||
RightComponent,
|
||||
}: CommandMenuItemProps) => {
|
||||
const { onItemClick } = useCommandMenuOnItemClick();
|
||||
|
||||
@ -49,6 +52,7 @@ export const CommandMenuItem = ({
|
||||
})
|
||||
}
|
||||
isSelected={isSelectedItemId}
|
||||
RightComponent={RightComponent}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
import { CommandMenuContextRecordChip } from '@/command-menu/components/CommandMenuContextRecordChip';
|
||||
import { CommandMenuItem } from '@/command-menu/components/CommandMenuItem';
|
||||
import { useResetPreviousCommandMenuContext } from '@/command-menu/hooks/useResetPreviousCommandMenuContext';
|
||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { IconArrowBackUp, isDefined } from 'twenty-ui';
|
||||
|
||||
export const ResetContextToSelectionCommandButton = () => {
|
||||
const contextStoreTargetedRecordsRule = useRecoilComponentValueV2(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
'command-menu-previous',
|
||||
);
|
||||
|
||||
const contextStoreCurrentObjectMetadataId = useRecoilComponentValueV2(
|
||||
contextStoreCurrentObjectMetadataIdComponentState,
|
||||
'command-menu-previous',
|
||||
);
|
||||
|
||||
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(objectMetadataItem) =>
|
||||
objectMetadataItem.id === contextStoreCurrentObjectMetadataId,
|
||||
);
|
||||
|
||||
const { resetPreviousCommandMenuContext } =
|
||||
useResetPreviousCommandMenuContext();
|
||||
|
||||
if (
|
||||
!isDefined(objectMetadataItem) ||
|
||||
(contextStoreTargetedRecordsRule.mode === 'selection' &&
|
||||
contextStoreTargetedRecordsRule.selectedRecordIds.length === 0)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<CommandMenuItem
|
||||
id="reset-context-to-selection"
|
||||
Icon={IconArrowBackUp}
|
||||
label={t`Reset to`}
|
||||
RightComponent={
|
||||
<CommandMenuContextRecordChip
|
||||
objectMetadataItemId={objectMetadataItem.id}
|
||||
instanceId="command-menu-previous"
|
||||
variant="small"
|
||||
/>
|
||||
}
|
||||
onClick={resetPreviousCommandMenuContext}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -4,19 +4,18 @@ import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchS
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||
import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages';
|
||||
import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
|
||||
import { useResetContextStoreStates } from '@/command-menu/hooks/useResetContextStoreStates';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageTitle';
|
||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { mainContextStoreComponentInstanceIdState } from '@/context-store/states/mainContextStoreComponentInstanceId';
|
||||
import { ContextStoreViewType } from '@/context-store/types/ContextStoreViewType';
|
||||
import { viewableRecordIdState } from '@/object-record/record-right-drawer/states/viewableRecordIdState';
|
||||
import { viewableRecordNameSingularState } from '@/object-record/record-right-drawer/states/viewableRecordNameSingularState';
|
||||
import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
|
||||
@ -33,120 +32,22 @@ export const useCommandMenu = () => {
|
||||
mainContextStoreComponentInstanceIdState,
|
||||
);
|
||||
|
||||
const { copyContextStoreStates } = useCopyContextStoreStates();
|
||||
const { resetContextStoreStates } = useResetContextStoreStates();
|
||||
|
||||
const openCommandMenu = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
({ set }) =>
|
||||
() => {
|
||||
if (isDefined(mainContextStoreComponentInstanceId)) {
|
||||
const contextStoreCurrentObjectMetadataId = snapshot
|
||||
.getLoadable(
|
||||
contextStoreCurrentObjectMetadataIdComponentState.atomFamily({
|
||||
instanceId: mainContextStoreComponentInstanceId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
contextStoreCurrentObjectMetadataIdComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
contextStoreCurrentObjectMetadataId,
|
||||
);
|
||||
|
||||
const contextStoreTargetedRecordsRule = snapshot
|
||||
.getLoadable(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId: mainContextStoreComponentInstanceId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
contextStoreTargetedRecordsRule,
|
||||
);
|
||||
|
||||
const contextStoreNumberOfSelectedRecords = snapshot
|
||||
.getLoadable(
|
||||
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
|
||||
instanceId: mainContextStoreComponentInstanceId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
contextStoreNumberOfSelectedRecords,
|
||||
);
|
||||
|
||||
const contextStoreFilters = snapshot
|
||||
.getLoadable(
|
||||
contextStoreFiltersComponentState.atomFamily({
|
||||
instanceId: mainContextStoreComponentInstanceId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
contextStoreFiltersComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
contextStoreFilters,
|
||||
);
|
||||
|
||||
const contextStoreCurrentViewId = snapshot
|
||||
.getLoadable(
|
||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||
instanceId: mainContextStoreComponentInstanceId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
contextStoreCurrentViewId,
|
||||
);
|
||||
|
||||
const contextStoreCurrentViewType = snapshot
|
||||
.getLoadable(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
instanceId: mainContextStoreComponentInstanceId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
contextStoreCurrentViewType,
|
||||
);
|
||||
}
|
||||
|
||||
const actionMenuEntries = snapshot
|
||||
.getLoadable(
|
||||
actionMenuEntriesComponentState.atomFamily({
|
||||
instanceId: mainContextStoreComponentInstanceId,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
actionMenuEntriesComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
actionMenuEntries,
|
||||
);
|
||||
copyContextStoreStates({
|
||||
instanceIdToCopyFrom: mainContextStoreComponentInstanceId,
|
||||
instanceIdToCopyTo: 'command-menu',
|
||||
});
|
||||
|
||||
set(isCommandMenuOpenedState, true);
|
||||
setHotkeyScopeAndMemorizePreviousScope(AppHotkeyScope.CommandMenuOpen);
|
||||
},
|
||||
[
|
||||
copyContextStoreStates,
|
||||
mainContextStoreComponentInstanceId,
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
],
|
||||
@ -160,57 +61,8 @@ export const useCommandMenu = () => {
|
||||
.getValue();
|
||||
|
||||
if (isCommandMenuOpened) {
|
||||
set(
|
||||
contextStoreCurrentObjectMetadataIdComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
null,
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
{
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [],
|
||||
},
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreFiltersComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
null,
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
null,
|
||||
);
|
||||
|
||||
set(
|
||||
actionMenuEntriesComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
new Map(),
|
||||
);
|
||||
resetContextStoreStates('command-menu');
|
||||
resetContextStoreStates('command-menu-previous');
|
||||
|
||||
set(viewableRecordIdState, null);
|
||||
set(commandMenuPageState, CommandMenuPages.Root);
|
||||
@ -225,7 +77,7 @@ export const useCommandMenu = () => {
|
||||
emitRightDrawerCloseEvent();
|
||||
}
|
||||
},
|
||||
[goBackToPreviousHotkeyScope, resetSelectedItem],
|
||||
[goBackToPreviousHotkeyScope, resetContextStoreStates, resetSelectedItem],
|
||||
);
|
||||
|
||||
const toggleCommandMenu = useRecoilCallback(
|
||||
@ -258,44 +110,59 @@ export const useCommandMenu = () => {
|
||||
[openCommandMenu],
|
||||
);
|
||||
|
||||
const setGlobalCommandMenuContext = useRecoilCallback(({ set }) => {
|
||||
return () => {
|
||||
set(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
{
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [],
|
||||
},
|
||||
);
|
||||
const setGlobalCommandMenuContext = useRecoilCallback(
|
||||
({ set }) => {
|
||||
return () => {
|
||||
copyContextStoreStates({
|
||||
instanceIdToCopyFrom: 'command-menu',
|
||||
instanceIdToCopyTo: 'command-menu-previous',
|
||||
});
|
||||
|
||||
set(
|
||||
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
0,
|
||||
);
|
||||
set(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
{
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [],
|
||||
},
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
null,
|
||||
);
|
||||
set(
|
||||
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
set(commandMenuPageInfoState, {
|
||||
title: undefined,
|
||||
Icon: undefined,
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
set(
|
||||
contextStoreFiltersComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
instanceId: 'command-menu',
|
||||
}),
|
||||
ContextStoreViewType.Table,
|
||||
);
|
||||
|
||||
set(commandMenuPageInfoState, {
|
||||
title: undefined,
|
||||
Icon: undefined,
|
||||
});
|
||||
};
|
||||
},
|
||||
[copyContextStoreStates],
|
||||
);
|
||||
|
||||
return {
|
||||
openCommandMenu,
|
||||
closeCommandMenu,
|
||||
openRecordInCommandMenu,
|
||||
toggleCommandMenu,
|
||||
resetCommandMenuContext: setGlobalCommandMenuContext,
|
||||
setGlobalCommandMenuContext,
|
||||
};
|
||||
};
|
||||
|
||||
@ -2,15 +2,17 @@ import { CommandMenuPages } from '@/command-menu/components/CommandMenuPages';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
import { commandMenuSearchState } from '@/command-menu/states/commandMenuSearchState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { useKeyboardShortcutMenu } from '@/keyboard-shortcut-menu/hooks/useKeyboardShortcutMenu';
|
||||
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 { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { Key } from 'ts-key-enum';
|
||||
|
||||
export const useCommandMenuHotKeys = () => {
|
||||
const { closeCommandMenu, toggleCommandMenu, resetCommandMenuContext } =
|
||||
const { closeCommandMenu, toggleCommandMenu, setGlobalCommandMenuContext } =
|
||||
useCommandMenu();
|
||||
|
||||
const commandMenuSearch = useRecoilValue(commandMenuSearchState);
|
||||
@ -19,6 +21,11 @@ export const useCommandMenuHotKeys = () => {
|
||||
|
||||
const commandMenuPage = useRecoilValue(commandMenuPageState);
|
||||
|
||||
const contextStoreTargetedRecordsRuleComponent = useRecoilComponentValueV2(
|
||||
contextStoreTargetedRecordsRuleComponentState,
|
||||
'command-menu',
|
||||
);
|
||||
|
||||
useScopedHotkeys(
|
||||
'ctrl+k,meta+k',
|
||||
() => {
|
||||
@ -43,9 +50,14 @@ export const useCommandMenuHotKeys = () => {
|
||||
() => {
|
||||
if (
|
||||
commandMenuPage === CommandMenuPages.Root &&
|
||||
!isNonEmptyString(commandMenuSearch)
|
||||
!isNonEmptyString(commandMenuSearch) &&
|
||||
!(
|
||||
contextStoreTargetedRecordsRuleComponent.mode === 'selection' &&
|
||||
contextStoreTargetedRecordsRuleComponent.selectedRecordIds.length ===
|
||||
0
|
||||
)
|
||||
) {
|
||||
resetCommandMenuContext();
|
||||
setGlobalCommandMenuContext();
|
||||
}
|
||||
},
|
||||
AppHotkeyScope.CommandMenuOpen,
|
||||
|
||||
@ -0,0 +1,129 @@
|
||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
export const useCopyContextStoreStates = () => {
|
||||
const copyContextStoreStates = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
({
|
||||
instanceIdToCopyFrom,
|
||||
instanceIdToCopyTo,
|
||||
}: {
|
||||
instanceIdToCopyFrom: string;
|
||||
instanceIdToCopyTo: string;
|
||||
}) => {
|
||||
const contextStoreCurrentObjectMetadataId = snapshot
|
||||
.getLoadable(
|
||||
contextStoreCurrentObjectMetadataIdComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyFrom,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
contextStoreCurrentObjectMetadataIdComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyTo,
|
||||
}),
|
||||
contextStoreCurrentObjectMetadataId,
|
||||
);
|
||||
|
||||
const contextStoreTargetedRecordsRule = snapshot
|
||||
.getLoadable(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyFrom,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyTo,
|
||||
}),
|
||||
contextStoreTargetedRecordsRule,
|
||||
);
|
||||
|
||||
const contextStoreNumberOfSelectedRecords = snapshot
|
||||
.getLoadable(
|
||||
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyFrom,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyTo,
|
||||
}),
|
||||
contextStoreNumberOfSelectedRecords,
|
||||
);
|
||||
|
||||
const contextStoreFilters = snapshot
|
||||
.getLoadable(
|
||||
contextStoreFiltersComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyFrom,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
contextStoreFiltersComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyTo,
|
||||
}),
|
||||
contextStoreFilters,
|
||||
);
|
||||
|
||||
const contextStoreCurrentViewId = snapshot
|
||||
.getLoadable(
|
||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyFrom,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyTo,
|
||||
}),
|
||||
contextStoreCurrentViewId,
|
||||
);
|
||||
|
||||
const contextStoreCurrentViewType = snapshot
|
||||
.getLoadable(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyFrom,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyTo,
|
||||
}),
|
||||
contextStoreCurrentViewType,
|
||||
);
|
||||
|
||||
const actionMenuEntries = snapshot
|
||||
.getLoadable(
|
||||
actionMenuEntriesComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyFrom,
|
||||
}),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
set(
|
||||
actionMenuEntriesComponentState.atomFamily({
|
||||
instanceId: instanceIdToCopyTo,
|
||||
}),
|
||||
actionMenuEntries,
|
||||
);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return { copyContextStoreStates };
|
||||
};
|
||||
@ -0,0 +1,68 @@
|
||||
import { actionMenuEntriesComponentState } from '@/action-menu/states/actionMenuEntriesComponentState';
|
||||
import { contextStoreCurrentObjectMetadataIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataIdComponentState';
|
||||
import { contextStoreCurrentViewIdComponentState } from '@/context-store/states/contextStoreCurrentViewIdComponentState';
|
||||
import { contextStoreCurrentViewTypeComponentState } from '@/context-store/states/contextStoreCurrentViewTypeComponentState';
|
||||
import { contextStoreFiltersComponentState } from '@/context-store/states/contextStoreFiltersComponentState';
|
||||
import { contextStoreNumberOfSelectedRecordsComponentState } from '@/context-store/states/contextStoreNumberOfSelectedRecordsComponentState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
export const useResetContextStoreStates = () => {
|
||||
const resetContextStoreStates = useRecoilCallback(({ set }) => {
|
||||
return (instanceId: string) => {
|
||||
set(
|
||||
contextStoreCurrentObjectMetadataIdComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
null,
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreTargetedRecordsRuleComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
{
|
||||
mode: 'selection',
|
||||
selectedRecordIds: [],
|
||||
},
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreNumberOfSelectedRecordsComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
0,
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreFiltersComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewIdComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
null,
|
||||
);
|
||||
|
||||
set(
|
||||
contextStoreCurrentViewTypeComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
null,
|
||||
);
|
||||
|
||||
set(
|
||||
actionMenuEntriesComponentState.atomFamily({
|
||||
instanceId,
|
||||
}),
|
||||
new Map(),
|
||||
);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return { resetContextStoreStates };
|
||||
};
|
||||
@ -0,0 +1,19 @@
|
||||
import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextStoreAndActionMenuStates';
|
||||
import { useResetContextStoreStates } from '@/command-menu/hooks/useResetContextStoreStates';
|
||||
|
||||
export const useResetPreviousCommandMenuContext = () => {
|
||||
const { copyContextStoreStates } = useCopyContextStoreStates();
|
||||
const { resetContextStoreStates } = useResetContextStoreStates();
|
||||
|
||||
const resetPreviousCommandMenuContext = () => {
|
||||
copyContextStoreStates({
|
||||
instanceIdToCopyFrom: 'command-menu-previous',
|
||||
instanceIdToCopyTo: 'command-menu',
|
||||
});
|
||||
resetContextStoreStates('command-menu-previous');
|
||||
};
|
||||
|
||||
return {
|
||||
resetPreviousCommandMenuContext,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,14 @@
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { getObjectRecordIdentifier } from '@/object-metadata/utils/getObjectRecordIdentifier';
|
||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
import { capitalize } from 'twenty-shared';
|
||||
|
||||
export const getSelectedRecordsContextText = (
|
||||
objectMetadataItem: ObjectMetadataItem,
|
||||
records: ObjectRecord[],
|
||||
totalCount: number,
|
||||
) => {
|
||||
return totalCount === 1
|
||||
? getObjectRecordIdentifier({ objectMetadataItem, record: records[0] }).name
|
||||
: `${totalCount} ${capitalize(objectMetadataItem.namePlural)}`;
|
||||
};
|
||||
@ -53,6 +53,7 @@ export const useFindManyRecordsSelectedInContextStore = ({
|
||||
contextStoreTargetedRecordsRule.mode === 'selection' &&
|
||||
contextStoreTargetedRecordsRule.selectedRecordIds.length === 0,
|
||||
limit,
|
||||
fetchPolicy: 'cache-and-network',
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
@ -39,6 +39,7 @@ export const useFindManyRecords = <T extends ObjectRecord = ObjectRecord>({
|
||||
const { objectMetadataItem } = useObjectMetadataItem({
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const { findManyRecordsQuery } = useFindManyRecordsQuery({
|
||||
objectNameSingular,
|
||||
recordGqlFields,
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
|
||||
import { IconComponent } from '@ui/display';
|
||||
import { useIsMobile } from '@ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { ReactNode } from 'react';
|
||||
import { MenuItemCommandHotKeys } from './MenuItemCommandHotKeys';
|
||||
|
||||
const StyledMenuItemLabelText = styled(StyledMenuItemLabel)`
|
||||
@ -72,6 +73,7 @@ export type MenuItemCommandProps = {
|
||||
className?: string;
|
||||
isSelected?: boolean;
|
||||
onClick?: () => void;
|
||||
RightComponent?: ReactNode;
|
||||
};
|
||||
|
||||
export const MenuItemCommand = ({
|
||||
@ -82,6 +84,7 @@ export const MenuItemCommand = ({
|
||||
className,
|
||||
isSelected,
|
||||
onClick,
|
||||
RightComponent,
|
||||
}: MenuItemCommandProps) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useIsMobile();
|
||||
@ -99,6 +102,7 @@ export const MenuItemCommand = ({
|
||||
</StyledBigIconContainer>
|
||||
)}
|
||||
<StyledMenuItemLabelText>{text}</StyledMenuItemLabelText>
|
||||
{RightComponent}
|
||||
</StyledMenuItemLeftContent>
|
||||
{!isMobile && (
|
||||
<MenuItemCommandHotKeys
|
||||
|
||||
Reference in New Issue
Block a user