491 save the page component instance id for side panel navigation (Part 2) (#10732)

This PR follows #10700, it is the same refactor but for the workflows
pages.

- Duplicates the right drawer workflow pages for the command menu and
replace the states used in these pages by component states
- We store the component instance id upon navigation to restore the
states when we navigate back to a page

There are still states which are not component states inside the
workflow diagram and workflow command menu pages, we should convert them
in a futur refactor.

`closeCommandMenu` was called programmatically in multiple places for
the workflow, I refactored that to only rely on the click outside
listener. This introduced a wiggling bug on the workflow canvas when we
change node selection. This should be fixed in another PR by updating
the canvas animation to take the animation values of the command menu
instead. I'm thinking we could use [motion
values](https://motion.dev/docs/react-motion-value) for this as I told
you @Devessier
This commit is contained in:
Raphaël Bosi
2025-03-07 18:18:24 +01:00
committed by GitHub
parent fc287dac78
commit f45a682249
20 changed files with 597 additions and 49 deletions

View File

@ -1,14 +1,12 @@
import { useRecoilCallback } from 'recoil';
import { useOpenEmailThreadRightDrawer } from '@/activities/emails/right-drawer/hooks/useOpenEmailThreadRightDrawer';
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
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 { closeCommandMenu } = useCommandMenu();
const openEmailThreadRightDrawer = useOpenEmailThreadRightDrawer();
const openEmailThread = useRecoilCallback(
@ -25,14 +23,13 @@ export const useEmailThread = () => {
if (isRightDrawerOpen && viewableEmailThreadId === threadId) {
set(viewableRecordIdState, null);
closeRightDrawer();
closeCommandMenu();
return;
}
openEmailThreadRightDrawer();
set(viewableRecordIdState, threadId);
},
[closeRightDrawer, closeCommandMenu, openEmailThreadRightDrawer],
[closeRightDrawer, openEmailThreadRightDrawer],
);
return { openEmailThread };

View File

@ -23,7 +23,6 @@ import { getRecordIndexIdFromObjectNamePluralAndViewId } from '@/object-record/u
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { workflowReactFlowRefState } from '@/workflow/workflow-diagram/states/workflowReactFlowRefState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
@ -64,15 +63,10 @@ export const CommandMenuContainer = ({
const commandMenuRef = useRef<HTMLDivElement>(null);
const workflowReactFlowRef = useRecoilValue(workflowReactFlowRefState);
useCommandMenuHotKeys();
useListenClickOutside({
refs: [
commandMenuRef,
...(workflowReactFlowRef ? [workflowReactFlowRef] : []),
],
refs: [commandMenuRef],
callback: closeCommandMenu,
listenerId: 'COMMAND_MENU_LISTENER_ID',
hotkeyScope: AppHotkeyScope.CommandMenuOpen,

View File

@ -1,15 +1,15 @@
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 { CommandMenuSearchRecordsPage } from '@/command-menu/pages/components/CommandMenuSearchRecordsPage';
import { CommandMenuMessageThreadPage } from '@/command-menu/pages/message-thread/components/CommandMenuMessageThreadPage';
import { CommandMenuRecordPage } from '@/command-menu/pages/record-page/components/CommandMenuRecordPage';
import { CommandMenuSearchRecordsPage } from '@/command-menu/pages/search/components/CommandMenuSearchRecordsPage';
import { CommandMenuWorkflowSelectAction } from '@/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectAction';
import { CommandMenuWorkflowEditStep } from '@/command-menu/pages/workflow/step/edit/components/CommandMenuWorkflowEditStep';
import { CommandMenuWorkflowRunViewStep } from '@/command-menu/pages/workflow/step/view-run/components/CommandMenuWorkflowRunViewStep';
import { CommandMenuWorkflowViewStep } from '@/command-menu/pages/workflow/step/view/components/CommandMenuWorkflowViewStep';
import { CommandMenuWorkflowSelectTriggerType } from '@/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerType';
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
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';
export const COMMAND_MENU_PAGES_CONFIG = new Map<
CommandMenuPages,
@ -22,14 +22,14 @@ export const COMMAND_MENU_PAGES_CONFIG = new Map<
[CommandMenuPages.Copilot, <RightDrawerAIChat />],
[
CommandMenuPages.WorkflowStepSelectTriggerType,
<RightDrawerWorkflowSelectTriggerType />,
<CommandMenuWorkflowSelectTriggerType />,
],
[
CommandMenuPages.WorkflowStepSelectAction,
<RightDrawerWorkflowSelectAction />,
<CommandMenuWorkflowSelectAction />,
],
[CommandMenuPages.WorkflowStepEdit, <RightDrawerWorkflowEditStep />],
[CommandMenuPages.WorkflowStepView, <RightDrawerWorkflowViewStep />],
[CommandMenuPages.WorkflowRunStepView, <RightDrawerWorkflowRunViewStep />],
[CommandMenuPages.WorkflowStepEdit, <CommandMenuWorkflowEditStep />],
[CommandMenuPages.WorkflowStepView, <CommandMenuWorkflowViewStep />],
[CommandMenuPages.WorkflowRunStepView, <CommandMenuWorkflowRunViewStep />],
[CommandMenuPages.SearchRecords, <CommandMenuSearchRecordsPage />],
]);

View File

@ -6,11 +6,13 @@ import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectab
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
import {
IconBolt,
IconCalendarEvent,
IconComponent,
IconDotsVertical,
IconMail,
IconSearch,
IconSettingsAutomation,
useIcons,
} from 'twenty-ui';
@ -21,6 +23,7 @@ import { useCopyContextStoreStates } from '@/command-menu/hooks/useCopyContextSt
import { useResetContextStoreStates } from '@/command-menu/hooks/useResetContextStoreStates';
import { viewableRecordIdComponentState } from '@/command-menu/pages/record-page/states/viewableRecordIdComponentState';
import { viewableRecordNameSingularComponentState } from '@/command-menu/pages/record-page/states/viewableRecordNameSingularComponentState';
import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState';
import { commandMenuNavigationStackState } from '@/command-menu/states/commandMenuNavigationStackState';
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
@ -393,6 +396,111 @@ export const useCommandMenu = () => {
[getIcon, navigateCommandMenu],
);
const openWorkflowTriggerTypeInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string) => {
const pageId = v4();
set(
workflowIdComponentState.atomFamily({ instanceId: pageId }),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepSelectTriggerType,
pageTitle: t`Trigger Type`,
pageIcon: IconBolt,
pageId,
});
};
},
[navigateCommandMenu],
);
const openWorkflowActionInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string) => {
const pageId = v4();
set(
workflowIdComponentState.atomFamily({ instanceId: pageId }),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepSelectAction,
pageTitle: t`Select Action`,
pageIcon: IconSettingsAutomation,
pageId,
});
};
},
[navigateCommandMenu],
);
const openWorkflowEditStepInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string, title: string, icon: IconComponent) => {
const pageId = v4();
set(
workflowIdComponentState.atomFamily({ instanceId: pageId }),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepEdit,
pageTitle: title,
pageIcon: icon,
pageId,
});
};
},
[navigateCommandMenu],
);
const openWorkflowViewStepInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string, title: string, icon: IconComponent) => {
const pageId = v4();
set(
workflowIdComponentState.atomFamily({ instanceId: pageId }),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowStepView,
pageTitle: title,
pageIcon: icon,
pageId,
});
};
},
[navigateCommandMenu],
);
const openWorkflowViewRunStepInCommandMenu = useRecoilCallback(
({ set }) => {
return (workflowId: string, title: string, icon: IconComponent) => {
const pageId = v4();
set(
workflowIdComponentState.atomFamily({ instanceId: pageId }),
workflowId,
);
navigateCommandMenu({
page: CommandMenuPages.WorkflowRunStepView,
pageTitle: title,
pageIcon: icon,
pageId,
});
};
},
[navigateCommandMenu],
);
const openRecordsSearchPage = () => {
navigateCommandMenu({
page: CommandMenuPages.SearchRecords,
@ -512,5 +620,10 @@ export const useCommandMenu = () => {
setGlobalCommandMenuContext,
openCalendarEventInCommandMenu,
openEmailThreadInCommandMenu,
openWorkflowTriggerTypeInCommandMenu,
openWorkflowActionInCommandMenu,
openWorkflowEditStepInCommandMenu,
openWorkflowViewStepInCommandMenu,
openWorkflowViewRunStepInCommandMenu,
};
};

View File

@ -0,0 +1,16 @@
import { CommandMenuWorkflowSelectActionContent } from '@/command-menu/pages/workflow/action/components/CommandMenuWorkflowSelectActionContent';
import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { isDefined } from 'twenty-shared';
export const CommandMenuWorkflowSelectAction = () => {
const workflowId = useRecoilComponentValueV2(workflowIdComponentState);
const workflow = useWorkflowWithCurrentVersion(workflowId);
if (!isDefined(workflow)) {
return null;
}
return <CommandMenuWorkflowSelectActionContent workflow={workflow} />;
};

View File

@ -0,0 +1,45 @@
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
import { RightDrawerStepListContainer } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepContainer';
import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepTitle';
import { useCreateStep } from '@/workflow/workflow-steps/hooks/useCreateStep';
import { OTHER_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/OtherActions';
import { RECORD_ACTIONS } from '@/workflow/workflow-steps/workflow-actions/constants/RecordActions';
import { MenuItemCommand, useIcons } from 'twenty-ui';
export const CommandMenuWorkflowSelectActionContent = ({
workflow,
}: {
workflow: WorkflowWithCurrentVersion;
}) => {
const { getIcon } = useIcons();
const { createStep } = useCreateStep({
workflow,
});
return (
<RightDrawerStepListContainer>
<RightDrawerWorkflowSelectStepTitle>
Records
</RightDrawerWorkflowSelectStepTitle>
{RECORD_ACTIONS.map((action) => (
<MenuItemCommand
key={action.type}
LeftIcon={getIcon(action.icon)}
text={action.label}
onClick={() => createStep(action.type)}
/>
))}
<RightDrawerWorkflowSelectStepTitle>
Other
</RightDrawerWorkflowSelectStepTitle>
{OTHER_ACTIONS.map((action) => (
<MenuItemCommand
key={action.type}
LeftIcon={getIcon(action.icon)}
text={action.label}
onClick={() => createStep(action.type)}
/>
))}
</RightDrawerStepListContainer>
);
};

View File

@ -0,0 +1,10 @@
import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext';
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
export const workflowIdComponentState = createComponentStateV2<
string | undefined
>({
key: 'command-menu/workflow-id',
defaultValue: undefined,
componentInstanceContext: CommandMenuPageComponentInstanceContext,
});

View File

@ -0,0 +1,23 @@
import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState';
import { CommandMenuWorkflowEditStepContent } from '@/command-menu/pages/workflow/step/edit/components/CommandMenuWorkflowEditStepContent';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { WorkflowVersionComponentInstanceContext } from '@/workflow/states/context/WorkflowVersionComponentInstanceContext';
import { isDefined } from 'twenty-shared';
export const CommandMenuWorkflowEditStep = () => {
const workflowId = useRecoilComponentValueV2(workflowIdComponentState);
const workflow = useWorkflowWithCurrentVersion(workflowId);
if (!isDefined(workflow)) {
return null;
}
return (
<WorkflowVersionComponentInstanceContext.Provider
value={{ instanceId: workflow.currentVersion.id }}
>
<CommandMenuWorkflowEditStepContent workflow={workflow} />
</WorkflowVersionComponentInstanceContext.Provider>
);
};

View File

@ -0,0 +1,30 @@
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
import { useUpdateStep } from '@/workflow/workflow-steps/hooks/useUpdateStep';
import { useUpdateWorkflowVersionTrigger } from '@/workflow/workflow-trigger/hooks/useUpdateWorkflowVersionTrigger';
export const CommandMenuWorkflowEditStepContent = ({
workflow,
}: {
workflow: WorkflowWithCurrentVersion;
}) => {
const flow = useFlowOrThrow();
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
const { updateTrigger } = useUpdateWorkflowVersionTrigger({ workflow });
const { updateStep } = useUpdateStep({
workflow,
});
return (
<WorkflowStepDetail
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
onActionUpdate={updateStep}
onTriggerUpdate={updateTrigger}
/>
);
};

View File

@ -0,0 +1,85 @@
import { ShowPageSubContainerTabListContainer } from '@/ui/layout/show-page/components/ShowPageSubContainerTabListContainer';
import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepInputDetail';
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
import { WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId';
import { getWorkflowRunStepExecutionStatus } from '@/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus';
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
import styled from '@emotion/styled';
import { isDefined } from 'twenty-shared';
import { IconLogin2, IconLogout, IconStepInto } from 'twenty-ui';
const StyledTabListContainer = styled(ShowPageSubContainerTabListContainer)`
background-color: ${({ theme }) => theme.background.secondary};
`;
type TabId = 'node' | 'input' | 'output';
export const CommandMenuWorkflowRunViewStep = () => {
const flow = useFlowOrThrow();
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
const workflowRunId = useWorkflowRunIdOrThrow();
const workflowRun = useWorkflowRun({ workflowRunId });
const { activeTabId } = useTabList<TabId>(
WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
);
const stepExecutionStatus = isDefined(workflowRun)
? getWorkflowRunStepExecutionStatus({
workflowRunOutput: workflowRun.output,
stepId: workflowSelectedNode,
})
: undefined;
const isInputTabDisabled =
workflowSelectedNode === TRIGGER_STEP_ID ||
stepExecutionStatus === 'running' ||
stepExecutionStatus === 'not-executed';
const tabs: SingleTabProps<TabId>[] = [
{ id: 'node', title: 'Node', Icon: IconStepInto },
{
id: 'input',
title: 'Input',
Icon: IconLogin2,
disabled: isInputTabDisabled,
},
{ id: 'output', title: 'Output', Icon: IconLogout },
];
if (!isDefined(workflowRun)) {
return null;
}
return (
<>
<StyledTabListContainer>
<TabList
tabListInstanceId={WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID}
tabs={tabs}
behaveAsLinks={false}
/>
</StyledTabListContainer>
{activeTabId === 'node' ? (
<WorkflowStepDetail
readonly
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
/>
) : null}
{activeTabId === 'input' ? (
<WorkflowRunStepInputDetail stepId={workflowSelectedNode} />
) : null}
</>
);
};

View File

@ -0,0 +1,17 @@
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
export const CommandMenuWorkflowViewStep = () => {
const flow = useFlowOrThrow();
const workflowSelectedNode = useWorkflowSelectedNodeOrThrow();
return (
<WorkflowStepDetail
stepId={workflowSelectedNode}
trigger={flow.trigger}
steps={flow.steps}
readonly
/>
);
};

View File

@ -0,0 +1,16 @@
import { workflowIdComponentState } from '@/command-menu/pages/workflow/states/workflowIdComponentState';
import { CommandMenuWorkflowSelectTriggerTypeContent } from '@/command-menu/pages/workflow/trigger-type/components/CommandMenuWorkflowSelectTriggerTypeContent';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { isDefined } from 'twenty-shared';
export const CommandMenuWorkflowSelectTriggerType = () => {
const workflowId = useRecoilComponentValueV2(workflowIdComponentState);
const workflow = useWorkflowWithCurrentVersion(workflowId);
if (!isDefined(workflow)) {
return null;
}
return <CommandMenuWorkflowSelectTriggerTypeContent workflow={workflow} />;
};

View File

@ -0,0 +1,100 @@
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,
} from '@/workflow/types/Workflow';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import { RightDrawerStepListContainer } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepContainer';
import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepTitle';
import { DATABASE_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/DatabaseTriggerTypes';
import { OTHER_TRIGGER_TYPES } from '@/workflow/workflow-trigger/constants/OtherTriggerTypes';
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,
}: {
workflow: WorkflowWithCurrentVersion;
}) => {
const { getIcon } = useIcons();
const { updateTrigger } = useUpdateWorkflowVersionTrigger({ workflow });
const { activeObjectMetadataItems } = useFilteredObjectMetadataItems();
const { openRightDrawer } = useRightDrawer();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const { openWorkflowEditStepInCommandMenu } = useCommandMenu();
const isCommandMenuV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsCommandMenuV2Enabled,
);
const handleTriggerTypeClick = ({
type,
defaultLabel,
icon,
}: {
type: WorkflowTriggerType;
defaultLabel: string;
icon: string;
}) => {
return async () => {
await updateTrigger(
getTriggerDefaultDefinition({
defaultLabel,
type,
activeObjectMetadataItems,
}),
);
setWorkflowSelectedNode(TRIGGER_STEP_ID);
if (isCommandMenuV2Enabled) {
openWorkflowEditStepInCommandMenu(
workflow.id,
defaultLabel,
getIcon(icon),
);
} else {
openRightDrawer(RightDrawerPages.WorkflowStepEdit, {
title: defaultLabel,
Icon: getIcon(icon),
});
}
};
};
return (
<RightDrawerStepListContainer>
<RightDrawerWorkflowSelectStepTitle>
Data
</RightDrawerWorkflowSelectStepTitle>
{DATABASE_TRIGGER_TYPES.map((action) => (
<MenuItemCommand
key={action.defaultLabel}
LeftIcon={getIcon(action.icon)}
text={action.defaultLabel}
onClick={handleTriggerTypeClick(action)}
/>
))}
<RightDrawerWorkflowSelectStepTitle>
Others
</RightDrawerWorkflowSelectStepTitle>
{OTHER_TRIGGER_TYPES.map((action) => (
<MenuItemCommand
key={action.defaultLabel}
LeftIcon={getIcon(action.icon)}
text={action.defaultLabel}
onClick={handleTriggerTypeClick(action)}
/>
))}
</RightDrawerStepListContainer>
);
};

View File

@ -1,4 +1,3 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
import { useListenRightDrawerClose } from '@/ui/layout/right-drawer/hooks/useListenRightDrawerClose';
import { WorkflowDiagramCustomMarkers } from '@/workflow/workflow-diagram/components/WorkflowDiagramCustomMarkers';
import { useRightDrawerState } from '@/workflow/workflow-diagram/hooks/useRightDrawerState';
@ -209,8 +208,6 @@ export const WorkflowDiagramCanvasBase = ({
);
}, [reactflow, rightDrawerState, rightDrawerWidth]);
const { closeCommandMenu } = useCommandMenu();
return (
<StyledResetReactflowStyles ref={containerRef}>
<WorkflowDiagramCustomMarkers />
@ -251,7 +248,6 @@ export const WorkflowDiagramCanvasBase = ({
nodesFocusable={false}
edgesFocusable={false}
nodesDraggable={false}
onPaneClick={closeCommandMenu}
nodesConnectable={false}
paneClickDistance={10} // Fix small unwanted user dragging does not select node
>

View File

@ -5,6 +5,7 @@ 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 { workflowIdState } from '@/workflow/states/workflowIdState';
import { EMPTY_TRIGGER_STEP_ID } from '@/workflow/workflow-diagram/constants/EmptyTriggerStepId';
import { useStartNodeCreation } from '@/workflow/workflow-diagram/hooks/useStartNodeCreation';
import { useTriggerNodeSelection } from '@/workflow/workflow-diagram/hooks/useTriggerNodeSelection';
@ -15,12 +16,14 @@ import {
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
import { isCreateStepNode } from '@/workflow/workflow-diagram/utils/isCreateStepNode';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useLingui } from '@lingui/react/macro';
import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react';
import { useCallback, useContext } from 'react';
import { useSetRecoilState } from 'recoil';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared';
import { IconBolt, useIcons } from 'twenty-ui';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const WorkflowDiagramCanvasEditableEffect = () => {
const { t } = useLingui();
@ -28,7 +31,10 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
const { startNodeCreation } = useStartNodeCreation();
const { openRightDrawer, closeRightDrawer } = useRightDrawer();
const { closeCommandMenu } = useCommandMenu();
const {
openWorkflowTriggerTypeInCommandMenu,
openWorkflowEditStepInCommandMenu,
} = useCommandMenu();
const setHotkeyScope = useSetHotkeyScope();
@ -40,6 +46,12 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
const { isInRightDrawer } = useContext(ActionMenuContext);
const workflowId = useRecoilValue(workflowIdState);
const isCommandMenuV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsCommandMenuV2Enabled,
);
const handleSelectionChange = useCallback(
({ nodes }: OnSelectionChangeParams) => {
const selectedNode = nodes[0] as WorkflowDiagramNode;
@ -51,12 +63,16 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
if (isClosingStep) {
closeRightDrawer();
closeCommandMenu();
return;
}
const isEmptyTriggerNode = selectedNode.type === EMPTY_TRIGGER_STEP_ID;
if (isEmptyTriggerNode) {
if (isCommandMenuV2Enabled && isDefined(workflowId)) {
openWorkflowTriggerTypeInCommandMenu(workflowId);
return;
}
openRightDrawer(RightDrawerPages.WorkflowStepSelectTriggerType, {
title: t`Trigger Type`,
Icon: IconBolt,
@ -74,6 +90,17 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
const selectedNodeData = selectedNode.data as WorkflowDiagramStepNodeData;
setWorkflowSelectedNode(selectedNode.id);
if (isCommandMenuV2Enabled && isDefined(workflowId)) {
openWorkflowEditStepInCommandMenu(
workflowId,
selectedNodeData.name,
getIcon(getWorkflowNodeIconKey(selectedNodeData)),
);
return;
}
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
openRightDrawer(RightDrawerPages.WorkflowStepEdit, {
title: selectedNodeData.name,
@ -82,15 +109,18 @@ export const WorkflowDiagramCanvasEditableEffect = () => {
},
[
isInRightDrawer,
setWorkflowSelectedNode,
setHotkeyScope,
openRightDrawer,
getIcon,
isCommandMenuV2Enabled,
setCommandMenuNavigationStack,
closeRightDrawer,
closeCommandMenu,
workflowId,
openRightDrawer,
t,
openWorkflowTriggerTypeInCommandMenu,
startNodeCreation,
openWorkflowEditStepInCommandMenu,
getIcon,
setWorkflowSelectedNode,
setHotkeyScope,
],
);

View File

@ -3,6 +3,7 @@ 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 { workflowIdState } from '@/workflow/states/workflowIdState';
import { EMPTY_TRIGGER_STEP_ID } from '@/workflow/workflow-diagram/constants/EmptyTriggerStepId';
import { useTriggerNodeSelection } from '@/workflow/workflow-diagram/hooks/useTriggerNodeSelection';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
@ -11,18 +12,25 @@ import {
WorkflowDiagramStepNodeData,
} from '@/workflow/workflow-diagram/types/WorkflowDiagram';
import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWorkflowNodeIconKey';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react';
import { useCallback } from 'react';
import { useSetRecoilState } from 'recoil';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared';
import { useIcons } from 'twenty-ui';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const WorkflowDiagramCanvasReadonlyEffect = () => {
const { getIcon } = useIcons();
const { openRightDrawer, closeRightDrawer } = useRightDrawer();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const setHotkeyScope = useSetHotkeyScope();
const { closeCommandMenu } = useCommandMenu();
const { openWorkflowViewStepInCommandMenu } = useCommandMenu();
const isCommandMenuV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsCommandMenuV2Enabled,
);
const workflowId = useRecoilValue(workflowIdState);
const handleSelectionChange = useCallback(
({ nodes }: OnSelectionChangeParams) => {
@ -31,7 +39,6 @@ export const WorkflowDiagramCanvasReadonlyEffect = () => {
if (isClosingStep || selectedNode.type === EMPTY_TRIGGER_STEP_ID) {
closeRightDrawer();
closeCommandMenu();
return;
}
@ -40,6 +47,15 @@ export const WorkflowDiagramCanvasReadonlyEffect = () => {
const selectedNodeData = selectedNode.data as WorkflowDiagramStepNodeData;
if (isCommandMenuV2Enabled && isDefined(workflowId)) {
openWorkflowViewStepInCommandMenu(
workflowId,
selectedNodeData.name,
getIcon(getWorkflowNodeIconKey(selectedNodeData)),
);
return;
}
openRightDrawer(RightDrawerPages.WorkflowStepView, {
title: selectedNodeData.name,
Icon: getIcon(getWorkflowNodeIconKey(selectedNodeData)),
@ -48,10 +64,12 @@ export const WorkflowDiagramCanvasReadonlyEffect = () => {
[
setWorkflowSelectedNode,
setHotkeyScope,
openRightDrawer,
isCommandMenuV2Enabled,
closeRightDrawer,
closeCommandMenu,
openWorkflowViewStepInCommandMenu,
workflowId,
getIcon,
openRightDrawer,
],
);

View File

@ -5,6 +5,7 @@ import { RightDrawerPages } from '@/ui/layout/right-drawer/types/RightDrawerPage
import { useTabListStates } from '@/ui/layout/tab/hooks/internal/useTabListStates';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
import { workflowIdState } from '@/workflow/states/workflowIdState';
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
import {
WorkflowDiagramNode,
@ -14,18 +15,25 @@ import { getWorkflowNodeIconKey } from '@/workflow/workflow-diagram/utils/getWor
import { WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId';
import { WorkflowRunTabId } from '@/workflow/workflow-steps/types/WorkflowRunTabId';
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { OnSelectionChangeParams, useOnSelectionChange } from '@xyflow/react';
import { useCallback } from 'react';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared';
import { useIcons } from 'twenty-ui';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const WorkflowRunDiagramCanvasEffect = () => {
const { getIcon } = useIcons();
const { openRightDrawer, closeRightDrawer } = useRightDrawer();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const setHotkeyScope = useSetHotkeyScope();
const { closeCommandMenu } = useCommandMenu();
const { openWorkflowViewRunStepInCommandMenu } = useCommandMenu();
const isCommandMenuV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsCommandMenuV2Enabled,
);
const workflowId = useRecoilValue(workflowIdState);
const { activeTabIdState: workflowRunRightDrawerListActiveTabIdState } =
useTabListStates({
@ -57,7 +65,6 @@ export const WorkflowRunDiagramCanvasEffect = () => {
if (isClosingStep) {
closeRightDrawer();
closeCommandMenu();
return;
}
@ -74,6 +81,16 @@ export const WorkflowRunDiagramCanvasEffect = () => {
goBackToFirstWorkflowRunRightDrawerTabIfNeeded();
}
if (isCommandMenuV2Enabled && isDefined(workflowId)) {
openWorkflowViewRunStepInCommandMenu(
workflowId,
selectedNodeData.name,
getIcon(getWorkflowNodeIconKey(selectedNodeData)),
);
return;
}
openRightDrawer(RightDrawerPages.WorkflowRunStepView, {
title: selectedNodeData.name,
Icon: getIcon(getWorkflowNodeIconKey(selectedNodeData)),
@ -82,11 +99,13 @@ export const WorkflowRunDiagramCanvasEffect = () => {
[
setWorkflowSelectedNode,
setHotkeyScope,
isCommandMenuV2Enabled,
workflowId,
openRightDrawer,
getIcon,
closeRightDrawer,
closeCommandMenu,
goBackToFirstWorkflowRunRightDrawerTabIfNeeded,
openWorkflowViewRunStepInCommandMenu,
],
);

View File

@ -1,11 +1,16 @@
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
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 { workflowIdState } from '@/workflow/states/workflowIdState';
import { workflowCreateStepFromParentStepIdState } from '@/workflow/workflow-steps/states/workflowCreateStepFromParentStepIdState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useCallback } from 'react';
import { useSetRecoilState } from 'recoil';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared';
import { IconSettingsAutomation } from 'twenty-ui';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const useStartNodeCreation = () => {
const { openRightDrawer } = useRightDrawer();
@ -13,6 +18,13 @@ export const useStartNodeCreation = () => {
workflowCreateStepFromParentStepIdState,
);
const setHotkeyScope = useSetHotkeyScope();
const { openWorkflowActionInCommandMenu } = useCommandMenu();
const workflowId = useRecoilValue(workflowIdState);
const isCommandMenuV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsCommandMenuV2Enabled,
);
/**
* This function is used in a context where dependencies shouldn't change much.
@ -22,13 +34,25 @@ export const useStartNodeCreation = () => {
(parentNodeId: string) => {
setWorkflowCreateStepFromParentStepId(parentNodeId);
if (isCommandMenuV2Enabled && isDefined(workflowId)) {
openWorkflowActionInCommandMenu(workflowId);
return;
}
setHotkeyScope(RightDrawerHotkeyScope.RightDrawer, { goto: false });
openRightDrawer(RightDrawerPages.WorkflowStepSelectAction, {
title: 'Select Action',
Icon: IconSettingsAutomation,
});
},
[setWorkflowCreateStepFromParentStepId, setHotkeyScope, openRightDrawer],
[
setWorkflowCreateStepFromParentStepId,
isCommandMenuV2Enabled,
openWorkflowActionInCommandMenu,
workflowId,
setHotkeyScope,
openRightDrawer,
],
);
return {

View File

@ -1,3 +1,4 @@
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';
@ -13,8 +14,10 @@ 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 RightDrawerWorkflowSelectTriggerTypeContent = ({
workflow,
@ -28,6 +31,10 @@ export const RightDrawerWorkflowSelectTriggerTypeContent = ({
const { openRightDrawer } = useRightDrawer();
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
const { openWorkflowEditStepInCommandMenu } = useCommandMenu();
const isCommandMenuV2Enabled = useIsFeatureEnabled(
FeatureFlagKey.IsCommandMenuV2Enabled,
);
const handleTriggerTypeClick = ({
type,
@ -49,10 +56,18 @@ export const RightDrawerWorkflowSelectTriggerTypeContent = ({
setWorkflowSelectedNode(TRIGGER_STEP_ID);
openRightDrawer(RightDrawerPages.WorkflowStepEdit, {
title: defaultLabel,
Icon: getIcon(icon),
});
if (isCommandMenuV2Enabled) {
openWorkflowEditStepInCommandMenu(
workflow.id,
defaultLabel,
getIcon(icon),
);
} else {
openRightDrawer(RightDrawerPages.WorkflowStepEdit, {
title: defaultLabel,
Icon: getIcon(icon),
});
}
};
};