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 { 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 { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||
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 { 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 { useCallback } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Button, IconBrowserMaximize, getOsControlSymbol } from 'twenty-ui';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
|
||||
const StyledLink = styled(Link)`
|
||||
text-decoration: none;
|
||||
`;
|
||||
@ -25,17 +32,58 @@ export const RecordShowRightDrawerOpenRecordButton = ({
|
||||
}: RecordShowRightDrawerOpenRecordButtonProps) => {
|
||||
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 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, {
|
||||
objectNameSingular,
|
||||
objectRecordId: record.id,
|
||||
});
|
||||
|
||||
closeCommandMenu();
|
||||
};
|
||||
}, [
|
||||
activeTabIdInRightDrawer,
|
||||
closeCommandMenu,
|
||||
navigate,
|
||||
objectNameSingular,
|
||||
record.id,
|
||||
setActiveTabIdInRecordPage,
|
||||
]);
|
||||
|
||||
useScopedHotkeys(
|
||||
['ctrl+Enter,meta+Enter'],
|
||||
|
||||
@ -12,13 +12,13 @@ import {
|
||||
|
||||
import { SkeletonLoader } from '@/activities/components/SkeletonLoader';
|
||||
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 { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { Task } from '@/activities/types/Task';
|
||||
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
||||
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 { AddTaskButton } from './AddTaskButton';
|
||||
import { TaskList } from './TaskList';
|
||||
@ -45,7 +45,7 @@ export const TaskGroups = ({ targetableObjects }: TaskGroupsProps) => {
|
||||
activityObjectNameSingular: CoreObjectNameSingular.Task,
|
||||
});
|
||||
|
||||
const { activeTabId } = useTabList(TASKS_TAB_LIST_COMPONENT_ID);
|
||||
const activeTabId = useRecoilComponentValueV2(activeTabIdComponentState);
|
||||
|
||||
const isLoading =
|
||||
(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 { emitRightDrawerCloseEvent } from '@/ui/layout/right-drawer/utils/emitRightDrawerCloseEvent';
|
||||
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 { 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';
|
||||
|
||||
export const useCommandMenuCloseAnimationCompleteCleanup = () => {
|
||||
@ -29,7 +35,7 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
|
||||
const { closeDropdown } = useDropdownV2();
|
||||
|
||||
const commandMenuCloseAnimationCompleteCleanup = useRecoilCallback(
|
||||
({ set }) =>
|
||||
({ snapshot, set }) =>
|
||||
() => {
|
||||
closeDropdown(COMMAND_MENU_CONTEXT_CHIP_GROUPS_DROPDOWN_ID);
|
||||
|
||||
@ -54,6 +60,32 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
|
||||
|
||||
emitRightDrawerCloseEvent();
|
||||
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,
|
||||
|
||||
@ -6,6 +6,8 @@ import { commandMenuNavigationStackState } from '@/command-menu/states/commandMe
|
||||
import { commandMenuPageInfoState } from '@/command-menu/states/commandMenuPageInfoState';
|
||||
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
||||
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';
|
||||
|
||||
export const useCommandMenuHistory = () => {
|
||||
@ -47,6 +49,19 @@ export const useCommandMenuHistory = () => {
|
||||
const newMorphItems = new Map(currentMorphItems);
|
||||
newMorphItems.delete(removedItem.pageId);
|
||||
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)
|
||||
.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(
|
||||
Array.from(currentMorphItems.entries()).filter(([pageId]) =>
|
||||
newNavigationStack.some((item) => item.pageId === pageId),
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
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 { useWorkflowRun } from '@/workflow/hooks/useWorkflowRun';
|
||||
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 { WorkflowStepDetail } from '@/workflow/workflow-steps/components/WorkflowStepDetail';
|
||||
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 { TRIGGER_STEP_ID } from '@/workflow/workflow-trigger/constants/TriggerStepId';
|
||||
import styled from '@emotion/styled';
|
||||
@ -20,7 +25,7 @@ const StyledTabList = styled(TabList)`
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type TabId = 'node' | 'input' | 'output';
|
||||
type TabId = WorkflowRunTabIdType;
|
||||
|
||||
export const CommandMenuWorkflowRunViewStep = () => {
|
||||
const flow = useFlowOrThrow();
|
||||
@ -29,7 +34,8 @@ export const CommandMenuWorkflowRunViewStep = () => {
|
||||
|
||||
const workflowRun = useWorkflowRun({ workflowRunId });
|
||||
|
||||
const { activeTabId } = useTabList<TabId>(
|
||||
const activeTabId = useRecoilComponentValueV2(
|
||||
activeTabIdComponentState,
|
||||
WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
||||
);
|
||||
|
||||
@ -46,15 +52,15 @@ export const CommandMenuWorkflowRunViewStep = () => {
|
||||
stepExecutionStatus === 'not-executed';
|
||||
|
||||
const tabs: SingleTabProps<TabId>[] = [
|
||||
{ id: 'node', title: 'Node', Icon: IconStepInto },
|
||||
{ id: WorkflowRunTabId.NODE, title: 'Node', Icon: IconStepInto },
|
||||
{
|
||||
id: 'input',
|
||||
id: WorkflowRunTabId.INPUT,
|
||||
title: 'Input',
|
||||
Icon: IconLogin2,
|
||||
disabled: areInputAndOutputTabsDisabled,
|
||||
},
|
||||
{
|
||||
id: 'output',
|
||||
id: WorkflowRunTabId.OUTPUT,
|
||||
title: 'Output',
|
||||
Icon: IconLogout,
|
||||
disabled: areInputAndOutputTabsDisabled,
|
||||
@ -70,12 +76,12 @@ export const CommandMenuWorkflowRunViewStep = () => {
|
||||
value={{ workflowVersionId: workflowRun.workflowVersionId }}
|
||||
>
|
||||
<StyledTabList
|
||||
tabListInstanceId={WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID}
|
||||
tabs={tabs}
|
||||
behaveAsLinks={false}
|
||||
componentInstanceId={WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID}
|
||||
/>
|
||||
|
||||
{activeTabId === 'node' ? (
|
||||
{activeTabId === WorkflowRunTabId.NODE ? (
|
||||
<WorkflowStepDetail
|
||||
readonly
|
||||
stepId={workflowSelectedNode}
|
||||
@ -84,14 +90,14 @@ export const CommandMenuWorkflowRunViewStep = () => {
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{activeTabId === 'input' ? (
|
||||
{activeTabId === WorkflowRunTabId.INPUT ? (
|
||||
<WorkflowRunStepInputDetail
|
||||
key={workflowSelectedNode}
|
||||
stepId={workflowSelectedNode}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{activeTabId === 'output' ? (
|
||||
{activeTabId === WorkflowRunTabId.OUTPUT ? (
|
||||
<WorkflowRunStepOutputDetail
|
||||
key={workflowSelectedNode}
|
||||
stepId={workflowSelectedNode}
|
||||
|
||||
@ -11,7 +11,8 @@ import { SettingsAccountsCalendarChannelsGeneral } from '@/settings/accounts/com
|
||||
import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection';
|
||||
import { SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID } from '@/settings/accounts/constants/SettingsAccountCalendarChannelsTabListComponentId';
|
||||
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';
|
||||
|
||||
const StyledCalenderContainer = styled.div`
|
||||
@ -19,7 +20,8 @@ const StyledCalenderContainer = styled.div`
|
||||
`;
|
||||
|
||||
export const SettingsAccountsCalendarChannelsContainer = () => {
|
||||
const { activeTabId } = useTabList(
|
||||
const activeTabId = useRecoilComponentValueV2(
|
||||
activeTabIdComponentState,
|
||||
SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID,
|
||||
);
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
@ -63,10 +65,10 @@ export const SettingsAccountsCalendarChannelsContainer = () => {
|
||||
{tabs.length > 1 && (
|
||||
<StyledCalenderContainer>
|
||||
<TabList
|
||||
tabListInstanceId={
|
||||
tabs={tabs}
|
||||
componentInstanceId={
|
||||
SETTINGS_ACCOUNT_CALENDAR_CHANNELS_TAB_LIST_COMPONENT_ID
|
||||
}
|
||||
tabs={tabs}
|
||||
/>
|
||||
</StyledCalenderContainer>
|
||||
)}
|
||||
|
||||
@ -10,7 +10,8 @@ import { SettingsAccountsMessageChannelDetails } from '@/settings/accounts/compo
|
||||
import { SettingsNewAccountSection } from '@/settings/accounts/components/SettingsNewAccountSection';
|
||||
import { SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID } from '@/settings/accounts/constants/SettingsAccountMessageChannelsTabListComponentId';
|
||||
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';
|
||||
|
||||
const StyledMessageContainer = styled.div`
|
||||
@ -18,7 +19,8 @@ const StyledMessageContainer = styled.div`
|
||||
`;
|
||||
|
||||
export const SettingsAccountsMessageChannelsContainer = () => {
|
||||
const { activeTabId } = useTabList(
|
||||
const activeTabId = useRecoilComponentValueV2(
|
||||
activeTabIdComponentState,
|
||||
SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID,
|
||||
);
|
||||
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
|
||||
@ -62,10 +64,10 @@ export const SettingsAccountsMessageChannelsContainer = () => {
|
||||
{tabs.length > 1 && (
|
||||
<StyledMessageContainer>
|
||||
<TabList
|
||||
tabListInstanceId={
|
||||
tabs={tabs}
|
||||
componentInstanceId={
|
||||
SETTINGS_ACCOUNT_MESSAGE_CHANNELS_TAB_LIST_COMPONENT_ID
|
||||
}
|
||||
tabs={tabs}
|
||||
/>
|
||||
</StyledMessageContainer>
|
||||
)}
|
||||
|
||||
@ -37,8 +37,8 @@ export const SettingsAdminContent = () => {
|
||||
<>
|
||||
<TabList
|
||||
tabs={tabs}
|
||||
tabListInstanceId={SETTINGS_ADMIN_TABS_ID}
|
||||
behaveAsLinks={true}
|
||||
componentInstanceId={SETTINGS_ADMIN_TABS_ID}
|
||||
/>
|
||||
<SettingsAdminTabContent />
|
||||
</>
|
||||
|
||||
@ -1,12 +1,10 @@
|
||||
import { canManageFeatureFlagsState } from '@/client-config/states/canManageFeatureFlagsState';
|
||||
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 { SnackBarVariant } from '@/ui/feedback/snack-bar-manager/components/SnackBar';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
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 styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
@ -29,6 +27,9 @@ import { useUserLookupAdminPanelMutation } from '~/generated/graphql';
|
||||
import { currentUserState } from '@/auth/states/currentUserState';
|
||||
import { SettingsAdminTableCard } from '@/settings/admin-panel/components/SettingsAdminTableCard';
|
||||
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`
|
||||
align-items: center;
|
||||
@ -49,8 +50,9 @@ export const SettingsAdminGeneral = () => {
|
||||
const [userIdentifier, setUserIdentifier] = useState('');
|
||||
const { enqueueSnackBar } = useSnackBar();
|
||||
|
||||
const { activeTabId, setActiveTabId } = useTabList(
|
||||
SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID,
|
||||
const [activeTabId, setActiveTabId] = useRecoilComponentStateV2(
|
||||
activeTabIdComponentState,
|
||||
'settings-admin-general',
|
||||
);
|
||||
const [userLookupResult, setUserLookupResult] = useRecoilState(
|
||||
userLookupResultState,
|
||||
@ -200,8 +202,8 @@ export const SettingsAdminGeneral = () => {
|
||||
<StyledTabListContainer>
|
||||
<TabList
|
||||
tabs={tabs}
|
||||
tabListInstanceId={SETTINGS_ADMIN_USER_LOOKUP_WORKSPACE_TABS_ID}
|
||||
behaveAsLinks={false}
|
||||
componentInstanceId={SETTINGS_ADMIN_GENERAL_TABS_ID}
|
||||
/>
|
||||
</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_ID } from '@/settings/admin-panel/constants/SettingsAdminTabsId';
|
||||
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 = () => {
|
||||
const { activeTabId } = useTabList(SETTINGS_ADMIN_TABS_ID);
|
||||
const activeTabId = useRecoilComponentValueV2(
|
||||
activeTabIdComponentState,
|
||||
SETTINGS_ADMIN_TABS_ID,
|
||||
);
|
||||
|
||||
switch (activeTabId) {
|
||||
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 { SettingsPath } from '@/types/SettingsPath';
|
||||
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 { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import styled from '@emotion/styled';
|
||||
import { Key } from 'ts-key-enum';
|
||||
import {
|
||||
@ -45,7 +46,8 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
|
||||
onChange: (filePath: string, value: string) => void;
|
||||
setIsCodeValid: (isCodeValid: boolean) => void;
|
||||
}) => {
|
||||
const { activeTabId } = useTabList(
|
||||
const activeTabId = useRecoilComponentValueV2(
|
||||
activeTabIdComponentState,
|
||||
SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
|
||||
);
|
||||
const TestButton = (
|
||||
@ -81,12 +83,12 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
|
||||
|
||||
const HeaderTabList = (
|
||||
<StyledTabList
|
||||
tabListInstanceId={SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID}
|
||||
tabs={files
|
||||
.filter((file) => file.path !== '.env')
|
||||
.map((file) => {
|
||||
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 { RecordShowRightDrawerOpenRecordButton } from '@/action-menu/components/RecordShowRightDrawerOpenRecordButton';
|
||||
import { ActivityTargetableObject } from '@/activities/types/ActivityTargetableEntity';
|
||||
import { CommandMenuPageComponentInstanceContext } from '@/command-menu/states/contexts/CommandMenuPageComponentInstanceContext';
|
||||
import { CardComponents } from '@/object-record/record-show/components/CardComponents';
|
||||
import { FieldsCard } from '@/object-record/record-show/components/FieldsCard';
|
||||
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 { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter';
|
||||
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 { 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 { 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 { useRecoilState } from 'recoil';
|
||||
|
||||
@ -41,8 +46,6 @@ const StyledContentContainer = styled.div<{ isInRightDrawer: boolean }>`
|
||||
isInRightDrawer ? theme.spacing(16) : 0};
|
||||
`;
|
||||
|
||||
export const TAB_LIST_COMPONENT_ID = 'show-page-right-tab-list';
|
||||
|
||||
type ShowPageSubContainerProps = {
|
||||
layout?: RecordLayout;
|
||||
tabs: SingleTabProps[];
|
||||
@ -52,7 +55,6 @@ type ShowPageSubContainerProps = {
|
||||
>;
|
||||
isInRightDrawer?: boolean;
|
||||
loading: boolean;
|
||||
isNewRightDrawerItemLoading?: boolean;
|
||||
};
|
||||
|
||||
export const ShowPageSubContainer = ({
|
||||
@ -62,9 +64,18 @@ export const ShowPageSubContainer = ({
|
||||
loading,
|
||||
isInRightDrawer = false,
|
||||
}: 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();
|
||||
|
||||
@ -109,7 +120,9 @@ export const ShowPageSubContainer = ({
|
||||
layout && !layout.hideSummaryAndFields && !isMobile && !isInRightDrawer;
|
||||
|
||||
return (
|
||||
<>
|
||||
<TabListComponentInstanceContext.Provider
|
||||
value={{ instanceId: tabListComponentId }}
|
||||
>
|
||||
{displaySummaryAndFields && (
|
||||
<ShowPageLeftContainer forceMobile={isMobile}>
|
||||
{summaryCard}
|
||||
@ -121,9 +134,9 @@ export const ShowPageSubContainer = ({
|
||||
<StyledTabList
|
||||
behaveAsLinks={!isInRightDrawer}
|
||||
loading={loading}
|
||||
tabListInstanceId={tabListComponentId}
|
||||
tabs={tabs}
|
||||
isInRightDrawer={isInRightDrawer}
|
||||
componentInstanceId={tabListComponentId}
|
||||
/>
|
||||
</StyledTabListContainer>
|
||||
{(isMobile || isInRightDrawer) && summaryCard}
|
||||
@ -142,6 +155,6 @@ export const ShowPageSubContainer = ({
|
||||
/>
|
||||
)}
|
||||
</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 { useTabList } from '@/ui/layout/tab/hooks/useTabList';
|
||||
import { TabListScope } from '@/ui/layout/tab/scopes/TabListScope';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab/states/activeTabIdComponentState';
|
||||
import { TabListComponentInstanceContext } from '@/ui/layout/tab/states/contexts/TabListComponentInstanceContext';
|
||||
import { LayoutCard } from '@/ui/layout/tab/types/LayoutCard';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
import styled from '@emotion/styled';
|
||||
import * as React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
@ -21,12 +22,12 @@ export type SingleTabProps<T extends string = string> = {
|
||||
};
|
||||
|
||||
type TabListProps = {
|
||||
tabListInstanceId: string;
|
||||
tabs: SingleTabProps[];
|
||||
loading?: boolean;
|
||||
behaveAsLinks?: boolean;
|
||||
className?: string;
|
||||
isInRightDrawer?: boolean;
|
||||
componentInstanceId: string;
|
||||
};
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
@ -44,15 +45,18 @@ const StyledOuterContainer = styled.div`
|
||||
|
||||
export const TabList = ({
|
||||
tabs,
|
||||
tabListInstanceId,
|
||||
loading,
|
||||
behaveAsLinks = true,
|
||||
isInRightDrawer,
|
||||
className,
|
||||
componentInstanceId,
|
||||
}: TabListProps) => {
|
||||
const visibleTabs = tabs.filter((tab) => !tab.hide);
|
||||
|
||||
const { activeTabId, setActiveTabId } = useTabList(tabListInstanceId);
|
||||
const [activeTabId, setActiveTabId] = useRecoilComponentStateV2(
|
||||
activeTabIdComponentState,
|
||||
componentInstanceId,
|
||||
);
|
||||
|
||||
const initialActiveTabId = activeTabId || visibleTabs[0]?.id || '';
|
||||
|
||||
@ -65,17 +69,18 @@ export const TabList = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledOuterContainer>
|
||||
<TabListScope tabListScopeId={tabListInstanceId}>
|
||||
<TabListComponentInstanceContext.Provider
|
||||
value={{ instanceId: componentInstanceId }}
|
||||
>
|
||||
<StyledOuterContainer>
|
||||
<TabListFromUrlOptionalEffect
|
||||
isInRightDrawer={!!isInRightDrawer}
|
||||
componentInstanceId={tabListInstanceId}
|
||||
tabListIds={tabs.map((tab) => tab.id)}
|
||||
/>
|
||||
<ScrollWrapper
|
||||
defaultEnableYScroll={false}
|
||||
contextProviderName="tabList"
|
||||
componentInstanceId={`scroll-wrapper-tab-list-${tabListInstanceId}`}
|
||||
componentInstanceId={`scroll-wrapper-tab-list-${componentInstanceId}`}
|
||||
>
|
||||
<StyledContainer className={className}>
|
||||
{visibleTabs.map((tab) => (
|
||||
@ -98,7 +103,7 @@ export const TabList = ({
|
||||
))}
|
||||
</StyledContainer>
|
||||
</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 { useLocation } from 'react-router-dom';
|
||||
|
||||
type TabListFromUrlOptionalEffectProps = {
|
||||
componentInstanceId: string;
|
||||
tabListIds: string[];
|
||||
isInRightDrawer: boolean;
|
||||
};
|
||||
|
||||
export const TabListFromUrlOptionalEffect = ({
|
||||
componentInstanceId,
|
||||
tabListIds,
|
||||
isInRightDrawer,
|
||||
}: TabListFromUrlOptionalEffectProps) => {
|
||||
const location = useLocation();
|
||||
const { activeTabId, setActiveTabId } = useTabList(componentInstanceId);
|
||||
const activeTabId = useRecoilComponentValueV2(activeTabIdComponentState);
|
||||
const setActiveTabId = useSetRecoilComponentStateV2(
|
||||
activeTabIdComponentState,
|
||||
);
|
||||
|
||||
const hash = location.hash.replace('#', '');
|
||||
|
||||
|
||||
@ -2,8 +2,6 @@ import { Meta, StoryObj } from '@storybook/react';
|
||||
import { expect, within } from '@storybook/test';
|
||||
import { ComponentWithRouterDecorator, IconCheckbox } from 'twenty-ui';
|
||||
|
||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||
|
||||
import { TabList } from '../TabList';
|
||||
|
||||
const tabs = [
|
||||
@ -39,17 +37,10 @@ const meta: Meta<typeof TabList> = {
|
||||
title: 'UI/Layout/Tab/TabList',
|
||||
component: TabList,
|
||||
args: {
|
||||
tabListInstanceId: 'tab-list-id',
|
||||
tabs: tabs,
|
||||
componentInstanceId: 'tab-list',
|
||||
},
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<RecoilScope>
|
||||
<Story />
|
||||
</RecoilScope>
|
||||
),
|
||||
ComponentWithRouterDecorator,
|
||||
],
|
||||
decorators: [ComponentWithRouterDecorator],
|
||||
};
|
||||
|
||||
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',
|
||||
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 { 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 { workflowIdState } from '@/workflow/states/workflowIdState';
|
||||
import { workflowSelectedNodeState } from '@/workflow/workflow-diagram/states/workflowSelectedNodeState';
|
||||
@ -24,27 +24,29 @@ export const WorkflowRunDiagramCanvasEffect = () => {
|
||||
|
||||
const workflowId = useRecoilValue(workflowIdState);
|
||||
|
||||
const { activeTabIdState: workflowRunRightDrawerListActiveTabIdState } =
|
||||
useTabListStates({
|
||||
tabListScopeId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
||||
});
|
||||
|
||||
const goBackToFirstWorkflowRunRightDrawerTabIfNeeded = useRecoilCallback(
|
||||
({ snapshot, set }) =>
|
||||
() => {
|
||||
const activeWorkflowRunRightDrawerTab = getSnapshotValue(
|
||||
snapshot,
|
||||
workflowRunRightDrawerListActiveTabIdState,
|
||||
activeTabIdComponentState.atomFamily({
|
||||
instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
||||
}),
|
||||
) as WorkflowRunTabId | null;
|
||||
|
||||
if (
|
||||
activeWorkflowRunRightDrawerTab === 'input' ||
|
||||
activeWorkflowRunRightDrawerTab === 'output'
|
||||
) {
|
||||
set(workflowRunRightDrawerListActiveTabIdState, 'node');
|
||||
set(
|
||||
activeTabIdComponentState.atomFamily({
|
||||
instanceId: WORKFLOW_RUN_STEP_SIDE_PANEL_TAB_LIST_COMPONENT_ID,
|
||||
}),
|
||||
'node',
|
||||
);
|
||||
}
|
||||
},
|
||||
[workflowRunRightDrawerListActiveTabIdState],
|
||||
[],
|
||||
);
|
||||
|
||||
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 { RightDrawerFooter } from '@/ui/layout/right-drawer/components/RightDrawerFooter';
|
||||
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 { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
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 { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/types/WorkflowServerlessFunctionTabId';
|
||||
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
||||
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/utils/getWrongExportedFunctionMarkers';
|
||||
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||
@ -77,8 +79,10 @@ export const WorkflowEditActionServerlessFunction = ({
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
const serverlessFunctionId = action.settings.input.serverlessFunctionId;
|
||||
const tabListId = `${WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID}_${serverlessFunctionId}`;
|
||||
const { activeTabId } = useTabList(tabListId);
|
||||
const activeTabId = useRecoilComponentValueV2(
|
||||
activeTabIdComponentState,
|
||||
WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
|
||||
);
|
||||
const { updateOneServerlessFunction } =
|
||||
useUpdateOneServerlessFunction(serverlessFunctionId);
|
||||
const { getUpdatableWorkflowVersion } = useGetUpdatableWorkflowVersion();
|
||||
@ -267,8 +271,12 @@ export const WorkflowEditActionServerlessFunction = ({
|
||||
};
|
||||
|
||||
const tabs = [
|
||||
{ id: 'code', title: 'Code', Icon: IconCode },
|
||||
{ id: 'test', title: 'Test', Icon: IconPlayerPlay },
|
||||
{ id: WorkflowServerlessFunctionTabId.CODE, title: 'Code', Icon: IconCode },
|
||||
{
|
||||
id: WorkflowServerlessFunctionTabId.TEST,
|
||||
title: 'Test',
|
||||
Icon: IconPlayerPlay,
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
@ -284,9 +292,11 @@ export const WorkflowEditActionServerlessFunction = ({
|
||||
!loading && (
|
||||
<StyledContainer>
|
||||
<StyledTabList
|
||||
tabListInstanceId={tabListId}
|
||||
tabs={tabs}
|
||||
behaveAsLinks={false}
|
||||
componentInstanceId={
|
||||
WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID
|
||||
}
|
||||
/>
|
||||
<WorkflowStepHeader
|
||||
onTitleChange={(newName: string) => {
|
||||
@ -299,7 +309,7 @@ export const WorkflowEditActionServerlessFunction = ({
|
||||
disabled={actionOptions.readonly}
|
||||
/>
|
||||
<WorkflowStepBody>
|
||||
{activeTabId === 'code' && (
|
||||
{activeTabId === WorkflowServerlessFunctionTabId.CODE && (
|
||||
<>
|
||||
<WorkflowEditActionServerlessFunctionFields
|
||||
functionInput={functionInput}
|
||||
@ -323,7 +333,7 @@ export const WorkflowEditActionServerlessFunction = ({
|
||||
</StyledCodeEditorContainer>
|
||||
</>
|
||||
)}
|
||||
{activeTabId === 'test' && (
|
||||
{activeTabId === WorkflowServerlessFunctionTabId.TEST && (
|
||||
<>
|
||||
<WorkflowEditActionServerlessFunctionFields
|
||||
functionInput={serverlessFunctionTestData.input}
|
||||
@ -352,7 +362,7 @@ export const WorkflowEditActionServerlessFunction = ({
|
||||
</>
|
||||
)}
|
||||
</WorkflowStepBody>
|
||||
{activeTabId === 'test' && (
|
||||
{activeTabId === WorkflowServerlessFunctionTabId.TEST && (
|
||||
<RightDrawerFooter
|
||||
actions={[
|
||||
<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 { 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 { 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 { getStepHeaderLabel } from '@/workflow/workflow-variables/utils/getStepHeaderLabel';
|
||||
import { isLinkOutputSchema } from '@/workflow/workflow-variables/utils/isLinkOutputSchema';
|
||||
@ -26,7 +27,6 @@ import {
|
||||
OverflowingTextWithTooltip,
|
||||
useIcons,
|
||||
} from 'twenty-ui';
|
||||
import { DropdownMenuHeaderLeftComponent } from '@/ui/layout/dropdown/components/DropdownMenuHeader/internal/DropdownMenuHeaderLeftComponent';
|
||||
|
||||
type WorkflowVariablesDropdownFieldItemsProps = {
|
||||
step: StepOutputSchema;
|
||||
@ -43,8 +43,8 @@ export const WorkflowVariablesDropdownFieldItems = ({
|
||||
const [searchInputValue, setSearchInputValue] = useState('');
|
||||
const { getIcon } = useIcons();
|
||||
const setWorkflowSelectedNode = useSetRecoilState(workflowSelectedNodeState);
|
||||
const { setActiveTabId } = useTabList(
|
||||
WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
|
||||
const setActiveTabId = useSetRecoilComponentStateV2(
|
||||
activeTabIdComponentState,
|
||||
);
|
||||
const setWorkflowDiagramTriggerNodeSelection = useSetRecoilState(
|
||||
workflowDiagramTriggerNodeSelectionState,
|
||||
|
||||
@ -12,12 +12,13 @@ import { AppPath } from '@/types/AppPath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
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 { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import styled from '@emotion/styled';
|
||||
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 { isDefined } from 'twenty-shared';
|
||||
import {
|
||||
@ -68,7 +69,8 @@ export const SettingsObjectDetailPage = () => {
|
||||
findActiveObjectMetadataItemByNamePlural(objectNamePlural) ??
|
||||
findActiveObjectMetadataItemByNamePlural(updatedObjectNamePlural);
|
||||
|
||||
const { activeTabId } = useTabList(
|
||||
const activeTabId = useRecoilComponentValueV2(
|
||||
activeTabIdComponentState,
|
||||
SETTINGS_OBJECT_DETAIL_TABS.COMPONENT_INSTANCE_ID,
|
||||
);
|
||||
|
||||
@ -169,11 +171,10 @@ export const SettingsObjectDetailPage = () => {
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<TabList
|
||||
tabListInstanceId={
|
||||
tabs={tabs}
|
||||
componentInstanceId={
|
||||
SETTINGS_OBJECT_DETAIL_TABS.COMPONENT_INSTANCE_ID
|
||||
}
|
||||
tabs={tabs}
|
||||
className="tab-list"
|
||||
/>
|
||||
<StyledContentContainer>
|
||||
{renderActiveTabContent()}
|
||||
|
||||
@ -10,7 +10,8 @@ import { RoleSettings } from '@/settings/roles/role-settings/components/RoleSett
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
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 { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
@ -33,9 +34,7 @@ export const SettingsRoleEdit = () => {
|
||||
|
||||
const role = rolesData?.getRoles.find((r) => r.id === roleId);
|
||||
|
||||
const { activeTabId } = useTabList(
|
||||
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID,
|
||||
);
|
||||
const activeTabId = useRecoilComponentValueV2(activeTabIdComponentState);
|
||||
|
||||
useEffect(() => {
|
||||
if (!rolesLoading && !role) {
|
||||
@ -101,9 +100,11 @@ export const SettingsRoleEdit = () => {
|
||||
{!rolesLoading && role ? (
|
||||
<SettingsPageContainer>
|
||||
<TabList
|
||||
tabListInstanceId={SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID}
|
||||
tabs={tabs}
|
||||
className="tab-list"
|
||||
componentInstanceId={
|
||||
SETTINGS_ROLE_DETAIL_TABS.COMPONENT_INSTANCE_ID
|
||||
}
|
||||
/>
|
||||
{renderActiveTabContent()}
|
||||
</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 { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
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 { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@ -26,12 +27,15 @@ import { FeatureFlagKey } from '~/generated/graphql';
|
||||
import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
||||
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 = () => {
|
||||
const { serverlessFunctionId = '' } = useParams();
|
||||
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 { updateOneServerlessFunction } =
|
||||
useUpdateOneServerlessFunction(serverlessFunctionId);
|
||||
@ -209,9 +213,9 @@ export const SettingsServerlessFunctionDetail = () => {
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<TabList
|
||||
tabListInstanceId={TAB_LIST_COMPONENT_ID}
|
||||
tabs={tabs}
|
||||
behaveAsLinks={false}
|
||||
componentInstanceId={SERVERLESS_FUNCTION_DETAIL_ID}
|
||||
/>
|
||||
{renderActiveTabContent()}
|
||||
</SettingsPageContainer>
|
||||
|
||||
Reference in New Issue
Block a user