584 Refactor Tabs (#11008)
Closes https://github.com/twentyhq/core-team-issues/issues/584 This PR: - Migrates the component state `activeTabIdComponentState` from the deprecated V1 version to V2. - Allows the active tab state to be preserved during navigation inside the side panel and reset when the side panel is closed. - Allows the active tab state to be preserved when we open a record in full page from the side panel https://github.com/user-attachments/assets/f2329d7a-ea15-4bd8-81dc-e98ce11edbd0 https://github.com/user-attachments/assets/474bffd5-29e0-40ba-97f4-fa5e9be34dc2
This commit is contained in:
@ -1,15 +1,22 @@
|
|||||||
import { CommandMenuActionMenuDropdownHotkeyScope } from '@/action-menu/types/CommandMenuActionMenuDropdownHotkeyScope';
|
import { CommandMenuActionMenuDropdownHotkeyScope } from '@/action-menu/types/CommandMenuActionMenuDropdownHotkeyScope';
|
||||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||||
|
import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext';
|
||||||
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage';
|
import { getLinkToShowPage } from '@/object-metadata/utils/getLinkToShowPage';
|
||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { AppPath } from '@/types/AppPath';
|
import { AppPath } from '@/types/AppPath';
|
||||||
|
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
|
||||||
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
import { AppHotkeyScope } from '@/ui/utilities/hotkey/types/AppHotkeyScope';
|
||||||
|
import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import { useCallback } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Button, IconBrowserMaximize, getOsControlSymbol } from 'twenty-ui';
|
import { Button, IconBrowserMaximize, getOsControlSymbol } from 'twenty-ui';
|
||||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||||
|
|
||||||
const StyledLink = styled(Link)`
|
const StyledLink = styled(Link)`
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
`;
|
`;
|
||||||
@ -25,17 +32,58 @@ export const RecordShowRightDrawerOpenRecordButton = ({
|
|||||||
}: RecordShowRightDrawerOpenRecordButtonProps) => {
|
}: RecordShowRightDrawerOpenRecordButtonProps) => {
|
||||||
const { closeCommandMenu } = useCommandMenu();
|
const { closeCommandMenu } = useCommandMenu();
|
||||||
|
|
||||||
|
const commandMenuPageComponentInstance = useComponentInstanceStateContext(
|
||||||
|
CommandMenuPageComponentInstanceContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const tabListComponentId = getShowPageTabListComponentId({
|
||||||
|
pageId: commandMenuPageComponentInstance?.instanceId,
|
||||||
|
targetObjectId: record.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeTabIdInRightDrawer = useRecoilComponentValueV2(
|
||||||
|
activeTabIdComponentState,
|
||||||
|
tabListComponentId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const tabListComponentIdInRecordPage = getShowPageTabListComponentId({
|
||||||
|
targetObjectId: record.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const setActiveTabIdInRecordPage = useSetRecoilComponentStateV2(
|
||||||
|
activeTabIdComponentState,
|
||||||
|
tabListComponentIdInRecordPage,
|
||||||
|
);
|
||||||
|
|
||||||
const to = getLinkToShowPage(objectNameSingular, record);
|
const to = getLinkToShowPage(objectNameSingular, record);
|
||||||
|
|
||||||
const navigate = useNavigateApp();
|
const navigate = useNavigateApp();
|
||||||
|
|
||||||
const handleOpenRecord = () => {
|
const handleOpenRecord = useCallback(() => {
|
||||||
|
const tabIdToOpen =
|
||||||
|
activeTabIdInRightDrawer === 'home'
|
||||||
|
? objectNameSingular === CoreObjectNameSingular.Note ||
|
||||||
|
objectNameSingular === CoreObjectNameSingular.Task
|
||||||
|
? 'richText'
|
||||||
|
: 'timeline'
|
||||||
|
: activeTabIdInRightDrawer;
|
||||||
|
|
||||||
|
setActiveTabIdInRecordPage(tabIdToOpen);
|
||||||
|
|
||||||
navigate(AppPath.RecordShowPage, {
|
navigate(AppPath.RecordShowPage, {
|
||||||
objectNameSingular,
|
objectNameSingular,
|
||||||
objectRecordId: record.id,
|
objectRecordId: record.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
closeCommandMenu();
|
closeCommandMenu();
|
||||||
};
|
}, [
|
||||||
|
activeTabIdInRightDrawer,
|
||||||
|
closeCommandMenu,
|
||||||
|
navigate,
|
||||||
|
objectNameSingular,
|
||||||
|
record.id,
|
||||||
|
setActiveTabIdInRecordPage,
|
||||||
|
]);
|
||||||
|
|
||||||
useScopedHotkeys(
|
useScopedHotkeys(
|
||||||
['ctrl+Enter,meta+Enter'],
|
['ctrl+Enter,meta+Enter'],
|
||||||
|
|||||||
@ -12,13 +12,13 @@ import {
|
|||||||
|
|
||||||
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
|
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
|
||||||
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
import { useOpenCreateActivityDrawer } from '@/activities/hooks/useOpenCreateActivityDrawer';
|
||||||
import { TASKS_TAB_LIST_COMPONENT_ID } from '@/activities/tasks/constants/TasksTabListComponentId';
|
|
||||||
import { useTasks } from '@/activities/tasks/hooks/useTasks';
|
import { useTasks } from '@/activities/tasks/hooks/useTasks';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
import { Task } from '@/activities/types/Task';
|
import { Task } from '@/activities/types/Task';
|
||||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||||
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
import { useHasObjectReadOnlyPermission } from '@/settings/roles/hooks/useHasObjectReadOnlyPermission';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import groupBy from 'lodash.groupby';
|
import groupBy from 'lodash.groupby';
|
||||||
import { AddTaskButton } from './AddTaskButton';
|
import { AddTaskButton } from './AddTaskButton';
|
||||||
import { TaskList } from './TaskList';
|
import { TaskList } from './TaskList';
|
||||||
@ -45,7 +45,7 @@ export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => {
|
|||||||
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { activeTabId } = useTabList(TASKS_TAB_LIST_COMPONENT_ID);
|
const activeTabId = useRecoilComponentValueV2(activeTabIdComponentState);
|
||||||
|
|
||||||
const isLoading =
|
const isLoading =
|
||||||
(activeTabId !== 'done' && tasksLoading) ||
|
(activeTabId !== 'done' && tasksLoading) ||
|
||||||
|
|||||||
@ -16,7 +16,13 @@ import { viewableRecordIdState } from '@/object-record/record-right-drawer/state
|
|||||||
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
import { useDropdownV2 } from '@/ui/layout/dropdown/hooks/useDropdownV2';
|
||||||
import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
|
import { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
|
||||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||||
|
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
|
||||||
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||||
|
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 { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/constants/WorkflowServerlessFunctionTabListComponentId';
|
||||||
|
import { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/types/WorkflowServerlessFunctionTabId';
|
||||||
import { useRecoilCallback } from 'recoil';
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
export const useCommandMenuCloseAnimationCompleteCleanup = () => {
|
export const useCommandMenuCloseAnimationCompleteCleanup = () => {
|
||||||
@ -29,7 +35,7 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
|
|||||||
const { closeDropdown } = useDropdownV2();
|
const { closeDropdown } = useDropdownV2();
|
||||||
|
|
||||||
const commandMenuCloseAnimationCompleteCleanup = useRecoilCallback(
|
const commandMenuCloseAnimationCompleteCleanup = useRecoilCallback(
|
||||||
({ set }) =>
|
({ snapshot, set }) =>
|
||||||
() => {
|
() => {
|
||||||
closeDropdown(COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID);
|
closeDropdown(COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID);
|
||||||
|
|
||||||
@ -54,6 +60,32 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
|
|||||||
|
|
||||||
emitRightDrawerCloseEvent();
|
emitRightDrawerCloseEvent();
|
||||||
set(isCommandMenuClosingState, false);
|
set(isCommandMenuClosingState, false);
|
||||||
|
set(
|
||||||
|
activeTabIdComponentState.atomFamily({
|
||||||
|
instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
||||||
|
}),
|
||||||
|
WorkflowRunTabId.NODE,
|
||||||
|
);
|
||||||
|
set(
|
||||||
|
activeTabIdComponentState.atomFamily({
|
||||||
|
instanceId: WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
|
||||||
|
}),
|
||||||
|
WorkflowServerlessFunctionTabId.CODE,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const [pageId, morphItem] of snapshot
|
||||||
|
.getLoadable(commandMenuNavigationMorphItemByPageState)
|
||||||
|
.getValue()) {
|
||||||
|
set(
|
||||||
|
activeTabIdComponentState.atomFamily({
|
||||||
|
instanceId: getShowPageTabListComponentId({
|
||||||
|
pageId,
|
||||||
|
targetObjectId: morphItem.recordId,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
closeDropdown,
|
closeDropdown,
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import { commandMenuNavigationStackState } from '@/command-menu/states/commandMe
|
|||||||
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
|
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
|
||||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||||
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
|
import { hasUserSelectedCommandState } from '@/command-menu/states/hasUserSelectedCommandState';
|
||||||
|
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
|
||||||
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
|
|
||||||
export const useCommandMenuHistory = () => {
|
export const useCommandMenuHistory = () => {
|
||||||
@ -47,6 +49,19 @@ export const useCommandMenuHistory = () => {
|
|||||||
const newMorphItems = new Map(currentMorphItems);
|
const newMorphItems = new Map(currentMorphItems);
|
||||||
newMorphItems.delete(removedItem.pageId);
|
newMorphItems.delete(removedItem.pageId);
|
||||||
set(commandMenuNavigationMorphItemByPageState, newMorphItems);
|
set(commandMenuNavigationMorphItemByPageState, newMorphItems);
|
||||||
|
|
||||||
|
const morphItem = currentMorphItems.get(removedItem.pageId);
|
||||||
|
if (isDefined(morphItem)) {
|
||||||
|
set(
|
||||||
|
activeTabIdComponentState.atomFamily({
|
||||||
|
instanceId: getShowPageTabListComponentId({
|
||||||
|
pageId: removedItem.pageId,
|
||||||
|
targetObjectId: morphItem.recordId,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,6 +99,20 @@ export const useCommandMenuHistory = () => {
|
|||||||
.getLoadable(commandMenuNavigationMorphItemByPageState)
|
.getLoadable(commandMenuNavigationMorphItemByPageState)
|
||||||
.getValue();
|
.getValue();
|
||||||
|
|
||||||
|
for (const [pageId, morphItem] of currentMorphItems.entries()) {
|
||||||
|
if (!newNavigationStack.some((item) => item.pageId === pageId)) {
|
||||||
|
set(
|
||||||
|
activeTabIdComponentState.atomFamily({
|
||||||
|
instanceId: getShowPageTabListComponentId({
|
||||||
|
pageId,
|
||||||
|
targetObjectId: morphItem.recordId,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const newMorphItems = new Map(
|
const newMorphItems = new Map(
|
||||||
Array.from(currentMorphItems.entries()).filter(([pageId]) =>
|
Array.from(currentMorphItems.entries()).filter(([pageId]) =>
|
||||||
newNavigationStack.some((item) => item.pageId === pageId),
|
newNavigationStack.some((item) => item.pageId === pageId),
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
|
import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
|
import { useFlowOrThrow } from '@/workflow/hooks/useFlowOrThrow';
|
||||||
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
import { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
||||||
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
import { useWorkflowRunIdOrThrow } from '@/workflow/hooks/useWorkflowRunIdOrThrow';
|
||||||
@ -9,6 +10,10 @@ import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components
|
|||||||
import { WorkflowRunStepOutputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepOutputDetail';
|
import { WorkflowRunStepOutputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepOutputDetail';
|
||||||
import { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
|
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 { WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/constants/WorkflowRunStepSidePanelTabListComponentId';
|
||||||
|
import {
|
||||||
|
WorkflowRunTabId,
|
||||||
|
WorkflowRunTabIdType,
|
||||||
|
} from '@/workflow/workflow-steps/types/WorkflowRunTabId';
|
||||||
import { getWorkflowRunStepExecutionStatus } from '@/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus';
|
import { getWorkflowRunStepExecutionStatus } from '@/workflow/workflow-steps/utils/getWorkflowRunStepExecutionStatus';
|
||||||
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
@ -20,7 +25,7 @@ const StyledTabList = styled(TabList)`
|
|||||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type TabId = 'node' | 'input' | 'output';
|
type TabId = WorkflowRunTabIdType;
|
||||||
|
|
||||||
export const CommandMenuWorkflowRunViewStep = () => {
|
export const CommandMenuWorkflowRunViewStep = () => {
|
||||||
const flow = useFlowOrThrow();
|
const flow = useFlowOrThrow();
|
||||||
@ -29,7 +34,8 @@ export const CommandMenuWorkflowRunViewStep = () => {
|
|||||||
|
|
||||||
const workflowRun = useWorkflowRun({ workflowRunId });
|
const workflowRun = useWorkflowRun({ workflowRunId });
|
||||||
|
|
||||||
const { activeTabId } = useTabList<TabId>(
|
const activeTabId = useRecoilComponentValueV2(
|
||||||
|
activeTabIdComponentState,
|
||||||
WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -46,15 +52,15 @@ export const CommandMenuWorkflowRunViewStep = () => {
|
|||||||
stepExecutionStatus === 'not-executed';
|
stepExecutionStatus === 'not-executed';
|
||||||
|
|
||||||
const tabs: SingleTabProps<TabId>[] = [
|
const tabs: SingleTabProps<TabId>[] = [
|
||||||
{ id: 'node', title: 'Node', Icon: IconStepInto },
|
{ id: WorkflowRunTabId.NODE, title: 'Node', Icon: IconStepInto },
|
||||||
{
|
{
|
||||||
id: 'input',
|
id: WorkflowRunTabId.INPUT,
|
||||||
title: 'Input',
|
title: 'Input',
|
||||||
Icon: IconLogin2,
|
Icon: IconLogin2,
|
||||||
disabled: areInputAndOutputTabsDisabled,
|
disabled: areInputAndOutputTabsDisabled,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'output',
|
id: WorkflowRunTabId.OUTPUT,
|
||||||
title: 'Output',
|
title: 'Output',
|
||||||
Icon: IconLogout,
|
Icon: IconLogout,
|
||||||
disabled: areInputAndOutputTabsDisabled,
|
disabled: areInputAndOutputTabsDisabled,
|
||||||
@ -70,12 +76,12 @@ export const CommandMenuWorkflowRunViewStep = () => {
|
|||||||
value={{ workflowVersionId: workflowRun.workflowVersionId }}
|
value={{ workflowVersionId: workflowRun.workflowVersionId }}
|
||||||
>
|
>
|
||||||
<StyledTabList
|
<StyledTabList
|
||||||
tabListInstanceId={WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID}
|
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
behaveAsLinks={false}
|
behaveAsLinks={false}
|
||||||
|
componentInstanceId={WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{activeTabId === 'node' ? (
|
{activeTabId === WorkflowRunTabId.NODE ? (
|
||||||
<WorkflowStepDetail
|
<WorkflowStepDetail
|
||||||
readonly
|
readonly
|
||||||
stepId={workflowSelectedNode}
|
stepId={workflowSelectedNode}
|
||||||
@ -84,14 +90,14 @@ export const CommandMenuWorkflowRunViewStep = () => {
|
|||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{activeTabId === 'input' ? (
|
{activeTabId === WorkflowRunTabId.INPUT ? (
|
||||||
<WorkflowRunStepInputDetail
|
<WorkflowRunStepInputDetail
|
||||||
key={workflowSelectedNode}
|
key={workflowSelectedNode}
|
||||||
stepId={workflowSelectedNode}
|
stepId={workflowSelectedNode}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{activeTabId === 'output' ? (
|
{activeTabId === WorkflowRunTabId.OUTPUT ? (
|
||||||
<WorkflowRunStepOutputDetail
|
<WorkflowRunStepOutputDetail
|
||||||
key={workflowSelectedNode}
|
key={workflowSelectedNode}
|
||||||
stepId={workflowSelectedNode}
|
stepId={workflowSelectedNode}
|
||||||
|
|||||||
@ -11,7 +11,8 @@ import { SettingsAccountsCalendarChannelsGeneral } from '@/settings/accounts/com
|
|||||||
import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection';
|
import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection';
|
||||||
import { SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID } from '@/settings/accounts/constants/SettingsAccountCalendarChannelsTabListComponentId';
|
import { SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID } from '@/settings/accounts/constants/SettingsAccountCalendarChannelsTabListComponentId';
|
||||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const StyledCalenderContainer = styled.div`
|
const StyledCalenderContainer = styled.div`
|
||||||
@ -19,7 +20,8 @@ const StyledCalenderContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const SettingsAccountsCalendarChannelsContainer = () => {
|
export const SettingsAccountsCalendarChannelsContainer = () => {
|
||||||
const { activeTabId } = useTabList(
|
const activeTabId = useRecoilComponentValueV2(
|
||||||
|
activeTabIdComponentState,
|
||||||
SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID,
|
SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID,
|
||||||
);
|
);
|
||||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||||
@ -63,10 +65,10 @@ export const SettingsAccountsCalendarChannelsContainer = () => {
|
|||||||
{tabs.length > 1 && (
|
{tabs.length > 1 && (
|
||||||
<StyledCalenderContainer>
|
<StyledCalenderContainer>
|
||||||
<TabList
|
<TabList
|
||||||
tabListInstanceId={
|
tabs={tabs}
|
||||||
|
componentInstanceId={
|
||||||
SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID
|
SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID
|
||||||
}
|
}
|
||||||
tabs={tabs}
|
|
||||||
/>
|
/>
|
||||||
</StyledCalenderContainer>
|
</StyledCalenderContainer>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -10,7 +10,8 @@ import { SettingsAccountsMessageChannelDetails } from '@/settings/accounts/compo
|
|||||||
import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection';
|
import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection';
|
||||||
import { SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID } from '@/settings/accounts/constants/SettingsAccountMessageChannelsTabListComponentId';
|
import { SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID } from '@/settings/accounts/constants/SettingsAccountMessageChannelsTabListComponentId';
|
||||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const StyledMessageContainer = styled.div`
|
const StyledMessageContainer = styled.div`
|
||||||
@ -18,7 +19,8 @@ const StyledMessageContainer = styled.div`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const SettingsAccountsMessageChannelsContainer = () => {
|
export const SettingsAccountsMessageChannelsContainer = () => {
|
||||||
const { activeTabId } = useTabList(
|
const activeTabId = useRecoilComponentValueV2(
|
||||||
|
activeTabIdComponentState,
|
||||||
SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID,
|
SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID,
|
||||||
);
|
);
|
||||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||||
@ -62,10 +64,10 @@ export const SettingsAccountsMessageChannelsContainer = () => {
|
|||||||
{tabs.length > 1 && (
|
{tabs.length > 1 && (
|
||||||
<StyledMessageContainer>
|
<StyledMessageContainer>
|
||||||
<TabList
|
<TabList
|
||||||
tabListInstanceId={
|
tabs={tabs}
|
||||||
|
componentInstanceId={
|
||||||
SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID
|
SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID
|
||||||
}
|
}
|
||||||
tabs={tabs}
|
|
||||||
/>
|
/>
|
||||||
</StyledMessageContainer>
|
</StyledMessageContainer>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -37,8 +37,8 @@ export const SettingsAdminContent = () => {
|
|||||||
<>
|
<>
|
||||||
<TabList
|
<TabList
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
tabListInstanceId={SETTINGS_ADMIN_TABS_ID}
|
|
||||||
behaveAsLinks={true}
|
behaveAsLinks={true}
|
||||||
|
componentInstanceId={SETTINGS_ADMIN_TABS_ID}
|
||||||
/>
|
/>
|
||||||
<SettingsAdminTabContent />
|
<SettingsAdminTabContent />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
|
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
|
||||||
import { SettingsAdminWorkspaceContent } from '@/settings/admin-panel/components/SettingsAdminWorkspaceContent';
|
import { SettingsAdminWorkspaceContent } from '@/settings/admin-panel/components/SettingsAdminWorkspaceContent';
|
||||||
import { SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminUserLookupWorkspaceTabsId';
|
|
||||||
import { userLookupResultState } from '@/settings/admin-panel/states/userLookupResultState';
|
import { userLookupResultState } from '@/settings/admin-panel/states/userLookupResultState';
|
||||||
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { TextInput } from '@/ui/input/components/TextInput';
|
import { TextInput } from '@/ui/input/components/TextInput';
|
||||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
|
||||||
import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo';
|
import { DEFAULT_WORKSPACE_LOGO } from '@/ui/navigation/navigation-drawer/constants/DefaultWorkspaceLogo';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
@ -29,6 +27,9 @@ import { useUserLookupAdminPanelMutation } from '~/generated/graphql';
|
|||||||
import { currentUserState } from '@/auth/states/currentUserState';
|
import { currentUserState } from '@/auth/states/currentUserState';
|
||||||
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
|
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
|
||||||
import { SettingsAdminVersionContainer } from '@/settings/admin-panel/components/SettingsAdminVersionContainer';
|
import { SettingsAdminVersionContainer } from '@/settings/admin-panel/components/SettingsAdminVersionContainer';
|
||||||
|
import { SETTINGS_ADMIN_GENERAL_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminGeneralTabsId';
|
||||||
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -49,8 +50,9 @@ export const SettingsAdminGeneral = () => {
|
|||||||
const [userIdentifier, setUserIdentifier] = useState('');
|
const [userIdentifier, setUserIdentifier] = useState('');
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
|
|
||||||
const { activeTabId, setActiveTabId } = useTabList(
|
const [activeTabId, setActiveTabId] = useRecoilComponentStateV2(
|
||||||
SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID,
|
activeTabIdComponentState,
|
||||||
|
'settings-admin-general',
|
||||||
);
|
);
|
||||||
const [userLookupResult, setUserLookupResult] = useRecoilState(
|
const [userLookupResult, setUserLookupResult] = useRecoilState(
|
||||||
userLookupResultState,
|
userLookupResultState,
|
||||||
@ -200,8 +202,8 @@ export const SettingsAdminGeneral = () => {
|
|||||||
<StyledTabListContainer>
|
<StyledTabListContainer>
|
||||||
<TabList
|
<TabList
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
tabListInstanceId={SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID}
|
|
||||||
behaveAsLinks={false}
|
behaveAsLinks={false}
|
||||||
|
componentInstanceId={SETTINGS_ADMIN_GENERAL_TABS_ID}
|
||||||
/>
|
/>
|
||||||
</StyledTabListContainer>
|
</StyledTabListContainer>
|
||||||
|
|
||||||
|
|||||||
@ -3,10 +3,14 @@ import { SettingsAdminGeneral } from '@/settings/admin-panel/components/Settings
|
|||||||
import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAdminTabs';
|
import { SETTINGS_ADMIN_TABS } from '@/settings/admin-panel/constants/SettingsAdminTabs';
|
||||||
import { SETTINGS_ADMIN_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminTabsId';
|
import { SETTINGS_ADMIN_TABS_ID } from '@/settings/admin-panel/constants/SettingsAdminTabsId';
|
||||||
import { SettingsAdminHealthStatus } from '@/settings/admin-panel/health-status/components/SettingsAdminHealthStatus';
|
import { SettingsAdminHealthStatus } from '@/settings/admin-panel/health-status/components/SettingsAdminHealthStatus';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
|
||||||
export const SettingsAdminTabContent = () => {
|
export const SettingsAdminTabContent = () => {
|
||||||
const { activeTabId } = useTabList(SETTINGS_ADMIN_TABS_ID);
|
const activeTabId = useRecoilComponentValueV2(
|
||||||
|
activeTabIdComponentState,
|
||||||
|
SETTINGS_ADMIN_TABS_ID,
|
||||||
|
);
|
||||||
|
|
||||||
switch (activeTabId) {
|
switch (activeTabId) {
|
||||||
case SETTINGS_ADMIN_TABS.GENERAL:
|
case SETTINGS_ADMIN_TABS.GENERAL:
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export const SETTINGS_ADMIN_GENERAL_TABS_ID = 'settings-admin-general-tabs-id';
|
||||||
@ -6,8 +6,9 @@ import { SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/settings/s
|
|||||||
import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope';
|
import { SettingsServerlessFunctionHotkeyScope } from '@/settings/serverless-functions/types/SettingsServerlessFunctionHotKeyScope';
|
||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
import {
|
import {
|
||||||
@ -45,7 +46,8 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
|
|||||||
onChange: (filePath: string, value: string) => void;
|
onChange: (filePath: string, value: string) => void;
|
||||||
setIsCodeValid: (isCodeValid: boolean) => void;
|
setIsCodeValid: (isCodeValid: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { activeTabId } = useTabList(
|
const activeTabId = useRecoilComponentValueV2(
|
||||||
|
activeTabIdComponentState,
|
||||||
SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
|
SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
|
||||||
);
|
);
|
||||||
const TestButton = (
|
const TestButton = (
|
||||||
@ -81,12 +83,12 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
|
|||||||
|
|
||||||
const HeaderTabList = (
|
const HeaderTabList = (
|
||||||
<StyledTabList
|
<StyledTabList
|
||||||
tabListInstanceId={SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID}
|
|
||||||
tabs={files
|
tabs={files
|
||||||
.filter((file) => file.path !== '.env')
|
.filter((file) => file.path !== '.env')
|
||||||
.map((file) => {
|
.map((file) => {
|
||||||
return { id: file.path, title: file.path.split('/').at(-1) || '' };
|
return { id: file.path, title: file.path.split('/').at(-1) || '' };
|
||||||
})}
|
})}
|
||||||
|
componentInstanceId={SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { RecordShowRightDrawerActionMenu } from '@/action-menu/components/RecordShowRightDrawerActionMenu';
|
import { RecordShowRightDrawerActionMenu } from '@/action-menu/components/RecordShowRightDrawerActionMenu';
|
||||||
import { RecordShowRightDrawerOpenRecordButton } from '@/action-menu/components/RecordShowRightDrawerOpenRecordButton';
|
import { RecordShowRightDrawerOpenRecordButton } from '@/action-menu/components/RecordShowRightDrawerOpenRecordButton';
|
||||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||||
|
import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext';
|
||||||
import { CardComponents } from '@/object-record/record-show/components/CardComponents';
|
import { CardComponents } from '@/object-record/record-show/components/CardComponents';
|
||||||
import { FieldsCard } from '@/object-record/record-show/components/FieldsCard';
|
import { FieldsCard } from '@/object-record/record-show/components/FieldsCard';
|
||||||
import { SummaryCard } from '@/object-record/record-show/components/SummaryCard';
|
import { SummaryCard } from '@/object-record/record-show/components/SummaryCard';
|
||||||
@ -9,9 +10,13 @@ import { recordStoreFamilyState } from '@/object-record/record-store/states/reco
|
|||||||
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter';
|
import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter';
|
||||||
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
import { ShowPageLeftContainer } from '@/ui/layout/show-page/components/ShowPageLeftContainer';
|
||||||
|
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
|
||||||
import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
|
import { SingleTabProps, TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
|
import { TabListComponentInstanceContext } from '@/ui/layout/tab/states/contexts/TabListComponentInstanceContext';
|
||||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||||
|
import { useComponentInstanceStateContext } from '@/ui/utilities/state/component-state/hooks/useComponentInstanceStateContext';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
@ -41,8 +46,6 @@ const StyledContentContainer = styled.div<{ isInRightDrawer: boolean }>`
|
|||||||
isInRightDrawer ? theme.spacing(16) : 0};
|
isInRightDrawer ? theme.spacing(16) : 0};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const TAB_LIST_COMPONENT_ID = 'show-page-right-tab-list';
|
|
||||||
|
|
||||||
type ShowPageSubContainerProps = {
|
type ShowPageSubContainerProps = {
|
||||||
layout?: RecordLayout;
|
layout?: RecordLayout;
|
||||||
tabs: SingleTabProps[];
|
tabs: SingleTabProps[];
|
||||||
@ -52,7 +55,6 @@ type ShowPageSubContainerProps = {
|
|||||||
>;
|
>;
|
||||||
isInRightDrawer?: boolean;
|
isInRightDrawer?: boolean;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
isNewRightDrawerItemLoading?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ShowPageSubContainer = ({
|
export const ShowPageSubContainer = ({
|
||||||
@ -62,9 +64,18 @@ export const ShowPageSubContainer = ({
|
|||||||
loading,
|
loading,
|
||||||
isInRightDrawer = false,
|
isInRightDrawer = false,
|
||||||
}: ShowPageSubContainerProps) => {
|
}: ShowPageSubContainerProps) => {
|
||||||
const tabListComponentId = `${TAB_LIST_COMPONENT_ID}-${isInRightDrawer}-${targetableObject.id}`;
|
const commandMenuPageComponentInstance = useComponentInstanceStateContext(
|
||||||
|
CommandMenuPageComponentInstanceContext,
|
||||||
|
);
|
||||||
|
|
||||||
const { activeTabId } = useTabList(tabListComponentId);
|
const tabListComponentId = getShowPageTabListComponentId({
|
||||||
|
pageId: commandMenuPageComponentInstance?.instanceId,
|
||||||
|
targetObjectId: targetableObject.id,
|
||||||
|
});
|
||||||
|
const activeTabId = useRecoilComponentValueV2(
|
||||||
|
activeTabIdComponentState,
|
||||||
|
tabListComponentId,
|
||||||
|
);
|
||||||
|
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
@ -109,7 +120,9 @@ export const ShowPageSubContainer = ({
|
|||||||
layout && !layout.hideSummaryAndFields && !isMobile && !isInRightDrawer;
|
layout && !layout.hideSummaryAndFields && !isMobile && !isInRightDrawer;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<TabListComponentInstanceContext.Provider
|
||||||
|
value={{ instanceId: tabListComponentId }}
|
||||||
|
>
|
||||||
{displaySummaryAndFields && (
|
{displaySummaryAndFields && (
|
||||||
<ShowPageLeftContainer forceMobile={isMobile}>
|
<ShowPageLeftContainer forceMobile={isMobile}>
|
||||||
{summaryCard}
|
{summaryCard}
|
||||||
@ -121,9 +134,9 @@ export const ShowPageSubContainer = ({
|
|||||||
<StyledTabList
|
<StyledTabList
|
||||||
behaveAsLinks={!isInRightDrawer}
|
behaveAsLinks={!isInRightDrawer}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
tabListInstanceId={tabListComponentId}
|
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
isInRightDrawer={isInRightDrawer}
|
isInRightDrawer={isInRightDrawer}
|
||||||
|
componentInstanceId={tabListComponentId}
|
||||||
/>
|
/>
|
||||||
</StyledTabListContainer>
|
</StyledTabListContainer>
|
||||||
{(isMobile || isInRightDrawer) && summaryCard}
|
{(isMobile || isInRightDrawer) && summaryCard}
|
||||||
@ -142,6 +155,6 @@ export const ShowPageSubContainer = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</StyledShowPageRightContainer>
|
</StyledShowPageRightContainer>
|
||||||
</>
|
</TabListComponentInstanceContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1 @@
|
|||||||
|
export const SHOW_PAGE_RIGHT_TAB_LIST = 'show-page-right-tab-list';
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { SHOW_PAGE_RIGHT_TAB_LIST } from '@/ui/layout/show-page/constants/ShowPageTabListComponentId';
|
||||||
|
|
||||||
|
export const getShowPageTabListComponentId = ({
|
||||||
|
pageId,
|
||||||
|
targetObjectId,
|
||||||
|
}: {
|
||||||
|
pageId?: string;
|
||||||
|
targetObjectId: string;
|
||||||
|
}): string => {
|
||||||
|
const id = pageId || targetObjectId;
|
||||||
|
return `${SHOW_PAGE_RIGHT_TAB_LIST}-${id}`;
|
||||||
|
};
|
||||||
@ -1,8 +1,9 @@
|
|||||||
import { TabListFromUrlOptionalEffect } from '@/ui/layout/tab/components/TabListFromUrlOptionalEffect';
|
import { TabListFromUrlOptionalEffect } from '@/ui/layout/tab/components/TabListFromUrlOptionalEffect';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
import { TabListScope } from '@/ui/layout/tab/scopes/TabListScope';
|
import { TabListComponentInstanceContext } from '@/ui/layout/tab/states/contexts/TabListComponentInstanceContext';
|
||||||
import { LayoutCard } from '@/ui/layout/tab/types/LayoutCard';
|
import { LayoutCard } from '@/ui/layout/tab/types/LayoutCard';
|
||||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
@ -21,12 +22,12 @@ export type SingleTabProps<T extends string = string> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type TabListProps = {
|
type TabListProps = {
|
||||||
tabListInstanceId: string;
|
|
||||||
tabs: SingleTabProps[];
|
tabs: SingleTabProps[];
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
behaveAsLinks?: boolean;
|
behaveAsLinks?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
isInRightDrawer?: boolean;
|
isInRightDrawer?: boolean;
|
||||||
|
componentInstanceId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledContainer = styled.div`
|
const StyledContainer = styled.div`
|
||||||
@ -44,15 +45,18 @@ const StyledOuterContainer = styled.div`
|
|||||||
|
|
||||||
export const TabList = ({
|
export const TabList = ({
|
||||||
tabs,
|
tabs,
|
||||||
tabListInstanceId,
|
|
||||||
loading,
|
loading,
|
||||||
behaveAsLinks = true,
|
behaveAsLinks = true,
|
||||||
isInRightDrawer,
|
isInRightDrawer,
|
||||||
className,
|
className,
|
||||||
|
componentInstanceId,
|
||||||
}: TabListProps) => {
|
}: TabListProps) => {
|
||||||
const visibleTabs = tabs.filter((tab) => !tab.hide);
|
const visibleTabs = tabs.filter((tab) => !tab.hide);
|
||||||
|
|
||||||
const { activeTabId, setActiveTabId } = useTabList(tabListInstanceId);
|
const [activeTabId, setActiveTabId] = useRecoilComponentStateV2(
|
||||||
|
activeTabIdComponentState,
|
||||||
|
componentInstanceId,
|
||||||
|
);
|
||||||
|
|
||||||
const initialActiveTabId = activeTabId || visibleTabs[0]?.id || '';
|
const initialActiveTabId = activeTabId || visibleTabs[0]?.id || '';
|
||||||
|
|
||||||
@ -65,17 +69,18 @@ export const TabList = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledOuterContainer>
|
<TabListComponentInstanceContext.Provider
|
||||||
<TabListScope tabListScopeId={tabListInstanceId}>
|
value={{ instanceId: componentInstanceId }}
|
||||||
|
>
|
||||||
|
<StyledOuterContainer>
|
||||||
<TabListFromUrlOptionalEffect
|
<TabListFromUrlOptionalEffect
|
||||||
isInRightDrawer={!!isInRightDrawer}
|
isInRightDrawer={!!isInRightDrawer}
|
||||||
componentInstanceId={tabListInstanceId}
|
|
||||||
tabListIds={tabs.map((tab) => tab.id)}
|
tabListIds={tabs.map((tab) => tab.id)}
|
||||||
/>
|
/>
|
||||||
<ScrollWrapper
|
<ScrollWrapper
|
||||||
defaultEnableYScroll={false}
|
defaultEnableYScroll={false}
|
||||||
contextProviderName="tabList"
|
contextProviderName="tabList"
|
||||||
componentInstanceId={`scroll-wrapper-tab-list-${tabListInstanceId}`}
|
componentInstanceId={`scroll-wrapper-tab-list-${componentInstanceId}`}
|
||||||
>
|
>
|
||||||
<StyledContainer className={className}>
|
<StyledContainer className={className}>
|
||||||
{visibleTabs.map((tab) => (
|
{visibleTabs.map((tab) => (
|
||||||
@ -98,7 +103,7 @@ export const TabList = ({
|
|||||||
))}
|
))}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
</ScrollWrapper>
|
</ScrollWrapper>
|
||||||
</TabListScope>
|
</StyledOuterContainer>
|
||||||
</StyledOuterContainer>
|
</TabListComponentInstanceContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,20 +1,23 @@
|
|||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useLocation } from 'react-router-dom';
|
import { useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
type TabListFromUrlOptionalEffectProps = {
|
type TabListFromUrlOptionalEffectProps = {
|
||||||
componentInstanceId: string;
|
|
||||||
tabListIds: string[];
|
tabListIds: string[];
|
||||||
isInRightDrawer: boolean;
|
isInRightDrawer: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TabListFromUrlOptionalEffect = ({
|
export const TabListFromUrlOptionalEffect = ({
|
||||||
componentInstanceId,
|
|
||||||
tabListIds,
|
tabListIds,
|
||||||
isInRightDrawer,
|
isInRightDrawer,
|
||||||
}: TabListFromUrlOptionalEffectProps) => {
|
}: TabListFromUrlOptionalEffectProps) => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { activeTabId, setActiveTabId } = useTabList(componentInstanceId);
|
const activeTabId = useRecoilComponentValueV2(activeTabIdComponentState);
|
||||||
|
const setActiveTabId = useSetRecoilComponentStateV2(
|
||||||
|
activeTabIdComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
const hash = location.hash.replace('#', '');
|
const hash = location.hash.replace('#', '');
|
||||||
|
|
||||||
|
|||||||
@ -2,8 +2,6 @@ import { Meta, StoryObj } from '@storybook/react';
|
|||||||
import { expect, within } from '@storybook/test';
|
import { expect, within } from '@storybook/test';
|
||||||
import { ComponentWithRouterDecorator, IconCheckbox } from 'twenty-ui';
|
import { ComponentWithRouterDecorator, IconCheckbox } from 'twenty-ui';
|
||||||
|
|
||||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
|
||||||
|
|
||||||
import { TabList } from '../TabList';
|
import { TabList } from '../TabList';
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
@ -39,17 +37,10 @@ const meta: Meta<typeof TabList> = {
|
|||||||
title: 'UI/Layout/Tab/TabList',
|
title: 'UI/Layout/Tab/TabList',
|
||||||
component: TabList,
|
component: TabList,
|
||||||
args: {
|
args: {
|
||||||
tabListInstanceId: 'tab-list-id',
|
|
||||||
tabs: tabs,
|
tabs: tabs,
|
||||||
|
componentInstanceId: 'tab-list',
|
||||||
},
|
},
|
||||||
decorators: [
|
decorators: [ComponentWithRouterDecorator],
|
||||||
(Story) => (
|
|
||||||
<RecoilScope>
|
|
||||||
<Story />
|
|
||||||
</RecoilScope>
|
|
||||||
),
|
|
||||||
ComponentWithRouterDecorator,
|
|
||||||
],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
import { act } from 'react-dom/test-utils';
|
|
||||||
import { renderHook } from '@testing-library/react';
|
|
||||||
import { RecoilRoot } from 'recoil';
|
|
||||||
|
|
||||||
import { useTabList } from '../useTabList';
|
|
||||||
|
|
||||||
describe('useTabList', () => {
|
|
||||||
it('Should update the activeTabId state', async () => {
|
|
||||||
const { result } = renderHook(
|
|
||||||
() => {
|
|
||||||
const { activeTabId, setActiveTabId } = useTabList('TEST_TAB_LIST_ID');
|
|
||||||
|
|
||||||
return {
|
|
||||||
activeTabId,
|
|
||||||
setActiveTabId: setActiveTabId,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
{
|
|
||||||
wrapper: RecoilRoot,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
expect(result.current.setActiveTabId).toBeInstanceOf(Function);
|
|
||||||
expect(result.current.activeTabId).toBeNull();
|
|
||||||
|
|
||||||
act(() => {
|
|
||||||
result.current.setActiveTabId('test-value');
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.current.activeTabId).toBe('test-value');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
import { TabListScopeInternalContext } from '@/ui/layout/tab/scopes/scope-internal-context/TabListScopeInternalContext';
|
|
||||||
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
|
||||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
|
||||||
import { extractComponentState } from '@/ui/utilities/state/component-state/utils/extractComponentState';
|
|
||||||
|
|
||||||
type useTabListStatesProps = {
|
|
||||||
tabListScopeId?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useTabListStates = ({ tabListScopeId }: useTabListStatesProps) => {
|
|
||||||
const scopeId = useAvailableScopeIdOrThrow(
|
|
||||||
TabListScopeInternalContext,
|
|
||||||
tabListScopeId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
scopeId,
|
|
||||||
activeTabIdState: extractComponentState(activeTabIdComponentState, scopeId),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { RecoilState, useRecoilState } from 'recoil';
|
|
||||||
|
|
||||||
import { useTabListStates } from '@/ui/layout/tab/hooks/internal/useTabListStates';
|
|
||||||
|
|
||||||
export const useTabList = <T extends string>(tabListId?: string) => {
|
|
||||||
const { activeTabIdState } = useTabListStates({
|
|
||||||
tabListScopeId: tabListId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [activeTabId, setActiveTabId] = useRecoilState(
|
|
||||||
activeTabIdState as RecoilState<T | null>,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
activeTabId,
|
|
||||||
setActiveTabId,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -1,6 +1,8 @@
|
|||||||
import { createComponentState } from '@/ui/utilities/state/component-state/utils/createComponentState';
|
import { TabListComponentInstanceContext } from '@/ui/layout/tab/states/contexts/TabListComponentInstanceContext';
|
||||||
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
|
||||||
export const activeTabIdComponentState = createComponentState<string | null>({
|
export const activeTabIdComponentState = createComponentStateV2<string | null>({
|
||||||
key: 'activeTabIdComponentState',
|
key: 'activeTabIdComponentState',
|
||||||
defaultValue: null,
|
defaultValue: null,
|
||||||
|
componentInstanceContext: TabListComponentInstanceContext,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
|
||||||
|
|
||||||
|
export const TabListComponentInstanceContext = createComponentInstanceContext();
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
|
import { useWorkflowCommandMenu } from '@/command-menu/hooks/useWorkflowCommandMenu';
|
||||||
import { useTabListStates } from '@/ui/layout/tab/hooks/internal/useTabListStates';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
import { getSnapshotValue } from '@/ui/utilities/state/utils/getSnapshotValue';
|
||||||
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
||||||
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
|
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
|
||||||
@ -24,27 +24,29 @@ export const WorkflowRunDiagramCanvasEffect = () => {
|
|||||||
|
|
||||||
const workflowId = useRecoilValue(workflowIdState);
|
const workflowId = useRecoilValue(workflowIdState);
|
||||||
|
|
||||||
const { activeTabIdState: workflowRunRightDrawerListActiveTabIdState } =
|
|
||||||
useTabListStates({
|
|
||||||
tabListScopeId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
|
||||||
});
|
|
||||||
|
|
||||||
const goBackToFirstWorkflowRunRightDrawerTabIfNeeded = useRecoilCallback(
|
const goBackToFirstWorkflowRunRightDrawerTabIfNeeded = useRecoilCallback(
|
||||||
({ snapshot, set }) =>
|
({ snapshot, set }) =>
|
||||||
() => {
|
() => {
|
||||||
const activeWorkflowRunRightDrawerTab = getSnapshotValue(
|
const activeWorkflowRunRightDrawerTab = getSnapshotValue(
|
||||||
snapshot,
|
snapshot,
|
||||||
workflowRunRightDrawerListActiveTabIdState,
|
activeTabIdComponentState.atomFamily({
|
||||||
|
instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
||||||
|
}),
|
||||||
) as WorkflowRunTabId | null;
|
) as WorkflowRunTabId | null;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
activeWorkflowRunRightDrawerTab === 'input' ||
|
activeWorkflowRunRightDrawerTab === 'input' ||
|
||||||
activeWorkflowRunRightDrawerTab === 'output'
|
activeWorkflowRunRightDrawerTab === 'output'
|
||||||
) {
|
) {
|
||||||
set(workflowRunRightDrawerListActiveTabIdState, 'node');
|
set(
|
||||||
|
activeTabIdComponentState.atomFamily({
|
||||||
|
instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
||||||
|
}),
|
||||||
|
'node',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[workflowRunRightDrawerListActiveTabIdState],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelectionChange = useCallback(
|
const handleSelectionChange = useCallback(
|
||||||
|
|||||||
@ -1,23 +0,0 @@
|
|||||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
|
||||||
import { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext';
|
|
||||||
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
|
||||||
import { RightDrawerWorkflowEditStepContent } from '@/workflow/workflow-steps/components/RightDrawerWorkflowEditStepContent';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
|
|
||||||
export const RightDrawerWorkflowEditStep = () => {
|
|
||||||
const workflowId = useRecoilValue(workflowIdState);
|
|
||||||
const workflow = useWorkflowWithCurrentVersion(workflowId);
|
|
||||||
|
|
||||||
if (!isDefined(workflow)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<WorkflowStepContextProvider
|
|
||||||
value={{ workflowVersionId: workflow.currentVersion.id }}
|
|
||||||
>
|
|
||||||
<RightDrawerWorkflowEditStepContent workflow={workflow} />
|
|
||||||
</WorkflowStepContextProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
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 RightDrawerWorkflowEditStepContent = ({
|
|
||||||
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}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,102 +0,0 @@
|
|||||||
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 { WorkflowStepContextProvider } from '@/workflow/states/context/WorkflowStepContext';
|
|
||||||
import { useWorkflowSelectedNodeOrThrow } from '@/workflow/workflow-diagram/hooks/useWorkflowSelectedNodeOrThrow';
|
|
||||||
import { WorkflowRunStepInputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepInputDetail';
|
|
||||||
import { WorkflowRunStepOutputDetail } from '@/workflow/workflow-steps/components/WorkflowRunStepOutputDetail';
|
|
||||||
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 StyledTabList = styled(TabList)`
|
|
||||||
background-color: ${({ theme }) => theme.background.secondary};
|
|
||||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
type TabId = 'node' | 'input' | 'output';
|
|
||||||
|
|
||||||
export const RightDrawerWorkflowRunViewStep = () => {
|
|
||||||
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 areInputAndOutputTabsDisabled =
|
|
||||||
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: areInputAndOutputTabsDisabled,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'output',
|
|
||||||
title: 'Output',
|
|
||||||
Icon: IconLogout,
|
|
||||||
disabled: areInputAndOutputTabsDisabled,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (!isDefined(workflowRun)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<WorkflowStepContextProvider
|
|
||||||
value={{ workflowVersionId: flow.workflowVersionId }}
|
|
||||||
>
|
|
||||||
<StyledTabList
|
|
||||||
tabListInstanceId={WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID}
|
|
||||||
tabs={tabs}
|
|
||||||
behaveAsLinks={false}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{activeTabId === 'node' ? (
|
|
||||||
<WorkflowStepDetail
|
|
||||||
readonly
|
|
||||||
stepId={workflowSelectedNode}
|
|
||||||
trigger={flow.trigger}
|
|
||||||
steps={flow.steps}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{activeTabId === 'input' ? (
|
|
||||||
<WorkflowRunStepInputDetail
|
|
||||||
key={workflowSelectedNode}
|
|
||||||
stepId={workflowSelectedNode}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{activeTabId === 'output' ? (
|
|
||||||
<WorkflowRunStepOutputDetail
|
|
||||||
key={workflowSelectedNode}
|
|
||||||
stepId={workflowSelectedNode}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</WorkflowStepContextProvider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,213 +0,0 @@
|
|||||||
import { flowState } from '@/workflow/states/flowState';
|
|
||||||
import { workflowRunIdState } from '@/workflow/states/workflowRunIdState';
|
|
||||||
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
|
|
||||||
import { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { Meta, StoryObj } from '@storybook/react';
|
|
||||||
import { expect, userEvent, waitFor, within } from '@storybook/test';
|
|
||||||
import { HttpResponse, graphql } from 'msw';
|
|
||||||
import { useSetRecoilState } from 'recoil';
|
|
||||||
import { ComponentDecorator, RouterDecorator } from 'twenty-ui';
|
|
||||||
import { I18nFrontDecorator } from '~/testing/decorators/I18nFrontDecorator';
|
|
||||||
import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator';
|
|
||||||
import { WorkflowStepDecorator } from '~/testing/decorators/WorkflowStepDecorator';
|
|
||||||
import { WorkspaceDecorator } from '~/testing/decorators/WorkspaceDecorator';
|
|
||||||
import { graphqlMocks } from '~/testing/graphqlMocks';
|
|
||||||
import { oneFailedWorkflowRunQueryResult } from '~/testing/mock-data/workflow-run';
|
|
||||||
import { RightDrawerWorkflowRunViewStep } from '../RightDrawerWorkflowRunViewStep';
|
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 500px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const meta: Meta<typeof RightDrawerWorkflowRunViewStep> = {
|
|
||||||
title: 'Modules/Workflow/RightDrawerWorkflowRunViewStep',
|
|
||||||
component: RightDrawerWorkflowRunViewStep,
|
|
||||||
decorators: [
|
|
||||||
(Story) => (
|
|
||||||
<StyledWrapper>
|
|
||||||
<Story />
|
|
||||||
</StyledWrapper>
|
|
||||||
),
|
|
||||||
I18nFrontDecorator,
|
|
||||||
ComponentDecorator,
|
|
||||||
(Story) => {
|
|
||||||
const setFlow = useSetRecoilState(flowState);
|
|
||||||
const setWorkflowSelectedNode = useSetRecoilState(
|
|
||||||
workflowSelectedNodeState,
|
|
||||||
);
|
|
||||||
const setWorkflowRunId = useSetRecoilState(workflowRunIdState);
|
|
||||||
|
|
||||||
setFlow({
|
|
||||||
workflowVersionId:
|
|
||||||
oneFailedWorkflowRunQueryResult.workflowRun.workflowVersionId,
|
|
||||||
trigger:
|
|
||||||
oneFailedWorkflowRunQueryResult.workflowRun.output.flow.trigger,
|
|
||||||
steps: oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps,
|
|
||||||
});
|
|
||||||
setWorkflowSelectedNode(
|
|
||||||
oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps[0].id,
|
|
||||||
);
|
|
||||||
setWorkflowRunId(oneFailedWorkflowRunQueryResult.workflowRun.id);
|
|
||||||
|
|
||||||
return <Story />;
|
|
||||||
},
|
|
||||||
RouterDecorator,
|
|
||||||
ObjectMetadataItemsDecorator,
|
|
||||||
WorkspaceDecorator,
|
|
||||||
WorkflowStepDecorator,
|
|
||||||
],
|
|
||||||
parameters: {
|
|
||||||
msw: {
|
|
||||||
handlers: [
|
|
||||||
graphql.query('FindOneWorkflowRun', () => {
|
|
||||||
const workflowRunContext =
|
|
||||||
oneFailedWorkflowRunQueryResult.workflowRun.context;
|
|
||||||
|
|
||||||
// Rendering the whole objectMetadata information in the JSON viewer is too long for storybook
|
|
||||||
// so we remove it for the story
|
|
||||||
return HttpResponse.json({
|
|
||||||
data: {
|
|
||||||
...oneFailedWorkflowRunQueryResult,
|
|
||||||
workflowRun: {
|
|
||||||
...oneFailedWorkflowRunQueryResult.workflowRun,
|
|
||||||
context: {
|
|
||||||
...workflowRunContext,
|
|
||||||
trigger: {
|
|
||||||
...workflowRunContext.trigger,
|
|
||||||
objectMetadata: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
...graphqlMocks.handlers,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
|
|
||||||
type Story = StoryObj<typeof RightDrawerWorkflowRunViewStep>;
|
|
||||||
|
|
||||||
export const NodeTab: Story = {};
|
|
||||||
|
|
||||||
export const InputTab: Story = {
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
|
|
||||||
await userEvent.click(await canvas.findByRole('button', { name: 'Input' }));
|
|
||||||
|
|
||||||
expect(await canvas.findByText('Trigger')).toBeVisible();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const InputTabDisabledForTrigger: Story = {
|
|
||||||
decorators: [
|
|
||||||
(Story) => {
|
|
||||||
const setWorkflowSelectedNode = useSetRecoilState(
|
|
||||||
workflowSelectedNodeState,
|
|
||||||
);
|
|
||||||
|
|
||||||
setWorkflowSelectedNode(TRIGGER_STEP_ID);
|
|
||||||
|
|
||||||
return <Story />;
|
|
||||||
},
|
|
||||||
],
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
|
|
||||||
const inputTab = await canvas.findByRole('button', { name: 'Input' });
|
|
||||||
|
|
||||||
expect(inputTab).toBeDisabled();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const InputTabNotExecutedStep: Story = {
|
|
||||||
decorators: [
|
|
||||||
(Story) => {
|
|
||||||
const setWorkflowSelectedNode = useSetRecoilState(
|
|
||||||
workflowSelectedNodeState,
|
|
||||||
);
|
|
||||||
|
|
||||||
setWorkflowSelectedNode(
|
|
||||||
oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps.at(-1)!
|
|
||||||
.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
return <Story />;
|
|
||||||
},
|
|
||||||
],
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
|
|
||||||
const inputTab = await canvas.findByRole('button', { name: 'Input' });
|
|
||||||
|
|
||||||
expect(inputTab).toBeDisabled();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OutputTab: Story = {
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
|
|
||||||
await userEvent.click(
|
|
||||||
await canvas.findByRole('button', { name: 'Output' }),
|
|
||||||
);
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(canvas.queryByText('Create Record')).not.toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(await canvas.findByText('result')).toBeVisible();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OutputTabDisabledForTrigger: Story = {
|
|
||||||
decorators: [
|
|
||||||
(Story) => {
|
|
||||||
const setWorkflowSelectedNode = useSetRecoilState(
|
|
||||||
workflowSelectedNodeState,
|
|
||||||
);
|
|
||||||
|
|
||||||
setWorkflowSelectedNode(TRIGGER_STEP_ID);
|
|
||||||
|
|
||||||
return <Story />;
|
|
||||||
},
|
|
||||||
],
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
|
|
||||||
const outputTab = await canvas.findByRole('button', { name: 'Output' });
|
|
||||||
|
|
||||||
expect(outputTab).toBeDisabled();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const OutputTabNotExecutedStep: Story = {
|
|
||||||
decorators: [
|
|
||||||
(Story) => {
|
|
||||||
const setWorkflowSelectedNode = useSetRecoilState(
|
|
||||||
workflowSelectedNodeState,
|
|
||||||
);
|
|
||||||
|
|
||||||
setWorkflowSelectedNode(
|
|
||||||
oneFailedWorkflowRunQueryResult.workflowRun.output.flow.steps.at(-1)!
|
|
||||||
.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
return <Story />;
|
|
||||||
},
|
|
||||||
],
|
|
||||||
play: async ({ canvasElement }) => {
|
|
||||||
const canvas = within(canvasElement);
|
|
||||||
|
|
||||||
const outputTab = await canvas.findByRole('button', { name: 'Output' });
|
|
||||||
|
|
||||||
expect(outputTab).toBeDisabled();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -1 +1,7 @@
|
|||||||
export type WorkflowRunTabId = 'node' | 'input' | 'output';
|
export type WorkflowRunTabIdType = 'node' | 'input' | 'output';
|
||||||
|
|
||||||
|
export enum WorkflowRunTabId {
|
||||||
|
NODE = 'node',
|
||||||
|
INPUT = 'input',
|
||||||
|
OUTPUT = 'output',
|
||||||
|
}
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
|
||||||
import { workflowIdState } from '@/workflow/states/workflowIdState';
|
|
||||||
import { RightDrawerWorkflowSelectActionContent } from '@/workflow/workflow-steps/workflow-actions/components/RightDrawerWorkflowSelectActionContent';
|
|
||||||
import { useRecoilValue } from 'recoil';
|
|
||||||
import { isDefined } from 'twenty-shared';
|
|
||||||
|
|
||||||
export const RightDrawerWorkflowSelectAction = () => {
|
|
||||||
const workflowId = useRecoilValue(workflowIdState);
|
|
||||||
const workflow = useWorkflowWithCurrentVersion(workflowId);
|
|
||||||
|
|
||||||
if (!isDefined(workflow)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <RightDrawerWorkflowSelectActionContent workflow={workflow} />;
|
|
||||||
};
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
import { WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
|
|
||||||
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';
|
|
||||||
import { RightDrawerStepListContainer } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepContainer';
|
|
||||||
import { RightDrawerWorkflowSelectStepTitle } from '@/workflow/workflow-steps/components/RightDrawerWorkflowSelectStepTitle';
|
|
||||||
|
|
||||||
export const RightDrawerWorkflowSelectActionContent = ({
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -19,11 +19,13 @@ import { InputLabel } from '@/ui/input/components/InputLabel';
|
|||||||
import { TextArea } from '@/ui/input/components/TextArea';
|
import { TextArea } from '@/ui/input/components/TextArea';
|
||||||
import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter';
|
import { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter';
|
||||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState';
|
import { serverlessFunctionTestDataFamilyState } from '@/workflow/states/serverlessFunctionTestDataFamilyState';
|
||||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||||
import { WorkflowEditActionServerlessFunctionFields } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionServerlessFunctionFields';
|
import { WorkflowEditActionServerlessFunctionFields } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionServerlessFunctionFields';
|
||||||
import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/constants/WorkflowServerlessFunctionTabListComponentId';
|
import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/constants/WorkflowServerlessFunctionTabListComponentId';
|
||||||
|
import { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/types/WorkflowServerlessFunctionTabId';
|
||||||
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
||||||
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/utils/getWrongExportedFunctionMarkers';
|
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/utils/getWrongExportedFunctionMarkers';
|
||||||
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||||
@ -77,8 +79,10 @@ export const WorkflowEditActionServerlessFunction = ({
|
|||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
const serverlessFunctionId = action.settings.input.serverlessFunctionId;
|
const serverlessFunctionId = action.settings.input.serverlessFunctionId;
|
||||||
const tabListId = `${WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID}_${serverlessFunctionId}`;
|
const activeTabId = useRecoilComponentValueV2(
|
||||||
const { activeTabId } = useTabList(tabListId);
|
activeTabIdComponentState,
|
||||||
|
WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
|
||||||
|
);
|
||||||
const { updateOneServerlessFunction } =
|
const { updateOneServerlessFunction } =
|
||||||
useUpdateOneServerlessFunction(serverlessFunctionId);
|
useUpdateOneServerlessFunction(serverlessFunctionId);
|
||||||
const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion();
|
const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion();
|
||||||
@ -267,8 +271,12 @@ export const WorkflowEditActionServerlessFunction = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ id: 'code', title: 'Code', Icon: IconCode },
|
{ id: WorkflowServerlessFunctionTabId.CODE, title: 'Code', Icon: IconCode },
|
||||||
{ id: 'test', title: 'Test', Icon: IconPlayerPlay },
|
{
|
||||||
|
id: WorkflowServerlessFunctionTabId.TEST,
|
||||||
|
title: 'Test',
|
||||||
|
Icon: IconPlayerPlay,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -284,9 +292,11 @@ export const WorkflowEditActionServerlessFunction = ({
|
|||||||
!loading && (
|
!loading && (
|
||||||
<StyledContainer>
|
<StyledContainer>
|
||||||
<StyledTabList
|
<StyledTabList
|
||||||
tabListInstanceId={tabListId}
|
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
behaveAsLinks={false}
|
behaveAsLinks={false}
|
||||||
|
componentInstanceId={
|
||||||
|
WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
<WorkflowStepHeader
|
<WorkflowStepHeader
|
||||||
onTitleChange={(newName: string) => {
|
onTitleChange={(newName: string) => {
|
||||||
@ -299,7 +309,7 @@ export const WorkflowEditActionServerlessFunction = ({
|
|||||||
disabled={actionOptions.readonly}
|
disabled={actionOptions.readonly}
|
||||||
/>
|
/>
|
||||||
<WorkflowStepBody>
|
<WorkflowStepBody>
|
||||||
{activeTabId === 'code' && (
|
{activeTabId === WorkflowServerlessFunctionTabId.CODE && (
|
||||||
<>
|
<>
|
||||||
<WorkflowEditActionServerlessFunctionFields
|
<WorkflowEditActionServerlessFunctionFields
|
||||||
functionInput={functionInput}
|
functionInput={functionInput}
|
||||||
@ -323,7 +333,7 @@ export const WorkflowEditActionServerlessFunction = ({
|
|||||||
</StyledCodeEditorContainer>
|
</StyledCodeEditorContainer>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{activeTabId === 'test' && (
|
{activeTabId === WorkflowServerlessFunctionTabId.TEST && (
|
||||||
<>
|
<>
|
||||||
<WorkflowEditActionServerlessFunctionFields
|
<WorkflowEditActionServerlessFunctionFields
|
||||||
functionInput={serverlessFunctionTestData.input}
|
functionInput={serverlessFunctionTestData.input}
|
||||||
@ -352,7 +362,7 @@ export const WorkflowEditActionServerlessFunction = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</WorkflowStepBody>
|
</WorkflowStepBody>
|
||||||
{activeTabId === 'test' && (
|
{activeTabId === WorkflowServerlessFunctionTabId.TEST && (
|
||||||
<RightDrawerFooter
|
<RightDrawerFooter
|
||||||
actions={[
|
actions={[
|
||||||
<CmdEnterActionButton
|
<CmdEnterActionButton
|
||||||
|
|||||||
@ -0,0 +1,6 @@
|
|||||||
|
export type WorkflowServerlessFunctionTabIdType = 'code' | 'test';
|
||||||
|
|
||||||
|
export enum WorkflowServerlessFunctionTabId {
|
||||||
|
CODE = 'code',
|
||||||
|
TEST = 'test',
|
||||||
|
}
|
||||||
@ -10,10 +10,11 @@ import {
|
|||||||
import { isBaseOutputSchema } from '@/workflow/workflow-variables/utils/isBaseOutputSchema';
|
import { isBaseOutputSchema } from '@/workflow/workflow-variables/utils/isBaseOutputSchema';
|
||||||
import { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isRecordOutputSchema';
|
import { isRecordOutputSchema } from '@/workflow/workflow-variables/utils/isRecordOutputSchema';
|
||||||
|
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||||
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
import { workflowDiagramTriggerNodeSelectionState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionState';
|
import { workflowDiagramTriggerNodeSelectionState } from '@/workflow/workflow-diagram/states/workflowDiagramTriggerNodeSelectionState';
|
||||||
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
|
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
|
||||||
import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/constants/WorkflowServerlessFunctionTabListComponentId';
|
|
||||||
import { getCurrentSubStepFromPath } from '@/workflow/workflow-variables/utils/getCurrentSubStepFromPath';
|
import { getCurrentSubStepFromPath } from '@/workflow/workflow-variables/utils/getCurrentSubStepFromPath';
|
||||||
import { getStepHeaderLabel } from '@/workflow/workflow-variables/utils/getStepHeaderLabel';
|
import { getStepHeaderLabel } from '@/workflow/workflow-variables/utils/getStepHeaderLabel';
|
||||||
import { isLinkOutputSchema } from '@/workflow/workflow-variables/utils/isLinkOutputSchema';
|
import { isLinkOutputSchema } from '@/workflow/workflow-variables/utils/isLinkOutputSchema';
|
||||||
@ -26,7 +27,6 @@ import {
|
|||||||
OverflowingTextWithTooltip,
|
OverflowingTextWithTooltip,
|
||||||
useIcons,
|
useIcons,
|
||||||
} from 'twenty-ui';
|
} from 'twenty-ui';
|
||||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
|
||||||
|
|
||||||
type WorkflowVariablesDropdownFieldItemsProps = {
|
type WorkflowVariablesDropdownFieldItemsProps = {
|
||||||
step: StepOutputSchema;
|
step: StepOutputSchema;
|
||||||
@ -43,8 +43,8 @@ export const WorkflowVariablesDropdownFieldItems = ({
|
|||||||
const [searchInputValue, setSearchInputValue] = useState('');
|
const [searchInputValue, setSearchInputValue] = useState('');
|
||||||
const { getIcon } = useIcons();
|
const { getIcon } = useIcons();
|
||||||
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
|
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
|
||||||
const { setActiveTabId } = useTabList(
|
const setActiveTabId = useSetRecoilComponentStateV2(
|
||||||
WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
|
activeTabIdComponentState,
|
||||||
);
|
);
|
||||||
const setWorkflowDiagramTriggerNodeSelection = useSetRecoilState(
|
const setWorkflowDiagramTriggerNodeSelection = useSetRecoilState(
|
||||||
workflowDiagramTriggerNodeSelectionState,
|
workflowDiagramTriggerNodeSelectionState,
|
||||||
|
|||||||
@ -12,12 +12,13 @@ import { AppPath } from '@/types/AppPath';
|
|||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
|
||||||
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
|
import { isAdvancedModeEnabledState } from '@/ui/navigation/navigation-drawer/states/isAdvancedModeEnabledState';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { isDefined } from 'twenty-shared';
|
import { isDefined } from 'twenty-shared';
|
||||||
import {
|
import {
|
||||||
@ -68,7 +69,8 @@ export const SettingsObjectDetailPage = () => {
|
|||||||
findActiveObjectMetadataItemByNamePlural(objectNamePlural) ??
|
findActiveObjectMetadataItemByNamePlural(objectNamePlural) ??
|
||||||
findActiveObjectMetadataItemByNamePlural(updatedObjectNamePlural);
|
findActiveObjectMetadataItemByNamePlural(updatedObjectNamePlural);
|
||||||
|
|
||||||
const { activeTabId } = useTabList(
|
const activeTabId = useRecoilComponentValueV2(
|
||||||
|
activeTabIdComponentState,
|
||||||
SETTINGS_OBJECT_DETAIL_TABS.COMPONENT_INSTANCE_ID,
|
SETTINGS_OBJECT_DETAIL_TABS.COMPONENT_INSTANCE_ID,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -169,11 +171,10 @@ export const SettingsObjectDetailPage = () => {
|
|||||||
>
|
>
|
||||||
<SettingsPageContainer>
|
<SettingsPageContainer>
|
||||||
<TabList
|
<TabList
|
||||||
tabListInstanceId={
|
tabs={tabs}
|
||||||
|
componentInstanceId={
|
||||||
SETTINGS_OBJECT_DETAIL_TABS.COMPONENT_INSTANCE_ID
|
SETTINGS_OBJECT_DETAIL_TABS.COMPONENT_INSTANCE_ID
|
||||||
}
|
}
|
||||||
tabs={tabs}
|
|
||||||
className="tab-list"
|
|
||||||
/>
|
/>
|
||||||
<StyledContentContainer>
|
<StyledContentContainer>
|
||||||
{renderActiveTabContent()}
|
{renderActiveTabContent()}
|
||||||
|
|||||||
@ -10,7 +10,8 @@ import { RoleSettings } from '@/settings/roles/role-settings/components/RoleSett
|
|||||||
import { SettingsPath } from '@/types/SettingsPath';
|
import { SettingsPath } from '@/types/SettingsPath';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { useGetRolesQuery } from '~/generated/graphql';
|
import { useGetRolesQuery } from '~/generated/graphql';
|
||||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
@ -33,9 +34,7 @@ export const SettingsRoleEdit = () => {
|
|||||||
|
|
||||||
const role = rolesData?.getRoles.find((r) => r.id === roleId);
|
const role = rolesData?.getRoles.find((r) => r.id === roleId);
|
||||||
|
|
||||||
const { activeTabId } = useTabList(
|
const activeTabId = useRecoilComponentValueV2(activeTabIdComponentState);
|
||||||
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID,
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!rolesLoading && !role) {
|
if (!rolesLoading && !role) {
|
||||||
@ -101,9 +100,11 @@ export const SettingsRoleEdit = () => {
|
|||||||
{!rolesLoading && role ? (
|
{!rolesLoading && role ? (
|
||||||
<SettingsPageContainer>
|
<SettingsPageContainer>
|
||||||
<TabList
|
<TabList
|
||||||
tabListInstanceId={SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID}
|
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
className="tab-list"
|
className="tab-list"
|
||||||
|
componentInstanceId={
|
||||||
|
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{renderActiveTabContent()}
|
{renderActiveTabContent()}
|
||||||
</SettingsPageContainer>
|
</SettingsPageContainer>
|
||||||
|
|||||||
@ -14,7 +14,8 @@ import { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/Snac
|
|||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||||
import { TabList } from '@/ui/layout/tab/components/TabList';
|
import { TabList } from '@/ui/layout/tab/components/TabList';
|
||||||
import { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||||
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
@ -26,12 +27,15 @@ import { FeatureFlagKey } from '~/generated/graphql';
|
|||||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||||
|
|
||||||
const TAB_LIST_COMPONENT_ID = 'serverless-function-detail';
|
const SERVERLESS_FUNCTION_DETAIL_ID = 'serverless-function-detail';
|
||||||
|
|
||||||
export const SettingsServerlessFunctionDetail = () => {
|
export const SettingsServerlessFunctionDetail = () => {
|
||||||
const { serverlessFunctionId = '' } = useParams();
|
const { serverlessFunctionId = '' } = useParams();
|
||||||
const { enqueueSnackBar } = useSnackBar();
|
const { enqueueSnackBar } = useSnackBar();
|
||||||
const { activeTabId, setActiveTabId } = useTabList(TAB_LIST_COMPONENT_ID);
|
const [activeTabId, setActiveTabId] = useRecoilComponentStateV2(
|
||||||
|
activeTabIdComponentState,
|
||||||
|
SERVERLESS_FUNCTION_DETAIL_ID,
|
||||||
|
);
|
||||||
const [isCodeValid, setIsCodeValid] = useState(true);
|
const [isCodeValid, setIsCodeValid] = useState(true);
|
||||||
const { updateOneServerlessFunction } =
|
const { updateOneServerlessFunction } =
|
||||||
useUpdateOneServerlessFunction(serverlessFunctionId);
|
useUpdateOneServerlessFunction(serverlessFunctionId);
|
||||||
@ -209,9 +213,9 @@ export const SettingsServerlessFunctionDetail = () => {
|
|||||||
>
|
>
|
||||||
<SettingsPageContainer>
|
<SettingsPageContainer>
|
||||||
<TabList
|
<TabList
|
||||||
tabListInstanceId={TAB_LIST_COMPONENT_ID}
|
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
behaveAsLinks={false}
|
behaveAsLinks={false}
|
||||||
|
componentInstanceId={SERVERLESS_FUNCTION_DETAIL_ID}
|
||||||
/>
|
/>
|
||||||
{renderActiveTabContent()}
|
{renderActiveTabContent()}
|
||||||
</SettingsPageContainer>
|
</SettingsPageContainer>
|
||||||
|
|||||||
Reference in New Issue
Block a user