mirror of
https://github.com/twentyhq/twenty
synced 2026-04-21 21:47:38 +00:00
## Summary
Continues the Emotion → Linaria migration (PR 4-6 from the [migration
plan](docs/emotion-to-linaria-migration-plan.md)). Migrates **311
files** across four module groups:
| Module | Files |
|---|---|
| command-menu | 53 |
| workflow | 84 |
| page-layout | 84 |
| UI (partial - first ~80 files) | ~80 |
| twenty-ui (TEXT_INPUT_STYLE) | 1 |
| misc (hooks, keyboard-shortcut-menu, file-upload) | ~9 |
### Migration patterns applied
- `import styled from '@emotion/styled'` → `import { styled } from
'@linaria/react'`
- `import { useTheme } from '@emotion/react'` → `import { useContext }
from 'react'` + `import { ThemeContext } from 'twenty-ui/theme'`
- `${({ theme }) => theme.X.Y.Z}` → `${themeCssVariables.X.Y.Z}` (static
CSS variables)
- `theme.spacing(N)` → `themeCssVariables.spacing[N]`
- `styled(motion.div)` → `motion.create(StyledBase)` (11 components)
- `styled(Component)<TypeParams>` → wrapper div approach for non-HTML
elements
- Multi-declaration interpolations split into one CSS property per
interpolation
- Interpolation return types fixed (`&&` → ternary `? : ''`)
- `TEXT_INPUT_STYLE` converted from function to static string constant
(backward compatible)
- Emotion `<Global>` replaced with `useEffect` style injection
- Complex runtime-dependent styles use CSS custom properties via
`style={}` prop
### After this PR
- **Remaining files**: ~400 (object-record: ~160, settings: ~200, UI:
~44)
- **No breaking changes**: CSS variables resolve identically to the
previous Emotion theme values
202 lines
7.1 KiB
TypeScript
202 lines
7.1 KiB
TypeScript
import { useUpdateCommandMenuPageInfo } from '@/command-menu/hooks/useUpdateCommandMenuPageInfo';
|
|
import { useCommandMenuWorkflowIdOrThrow } from '@/command-menu/pages/workflow/hooks/useCommandMenuWorkflowIdOrThrow';
|
|
import { commandMenuWorkflowStepIdComponentState } from '@/command-menu/pages/workflow/states/commandMenuWorkflowStepIdComponentState';
|
|
import { commandMenuPageState } from '@/command-menu/states/commandMenuPageState';
|
|
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
|
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
|
import { TitleInput } from '@/ui/input/components/TitleInput';
|
|
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
|
import { useGetUpdatableWorkflowVersionOrThrow } from '@/workflow/hooks/useGetUpdatableWorkflowVersionOrThrow';
|
|
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
|
|
import { getAgentIdFromStep } from '@/workflow/utils/getAgentIdFromStep';
|
|
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
|
|
import { getWorkflowVisualizerComponentInstanceId } from '@/workflow/utils/getWorkflowVisualizerComponentInstanceId';
|
|
import { useUpdateAgentLabel } from '@/workflow/workflow-steps/hooks/useUpdateAgentLabel';
|
|
import { useUpdateWorkflowVersionStep } from '@/workflow/workflow-steps/hooks/useUpdateWorkflowVersionStep';
|
|
import { getActionIcon } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIcon';
|
|
import { getActionIconColorOrThrow } from '@/workflow/workflow-steps/workflow-actions/utils/getActionIconColorOrThrow';
|
|
import { getTriggerIcon } from '@/workflow/workflow-trigger/utils/getTriggerIcon';
|
|
import { getTriggerIconColor } from '@/workflow/workflow-trigger/utils/getTriggerIconColor';
|
|
import { t } from '@lingui/core/macro';
|
|
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
|
import { useContext, useState } from 'react';
|
|
import { CommandMenuPages } from 'twenty-shared/types';
|
|
import { isDefined } from 'twenty-shared/utils';
|
|
import { TRIGGER_STEP_ID } from 'twenty-shared/workflow';
|
|
import { useIcons } from 'twenty-ui/display';
|
|
import { CommandMenuPageInfoLayout } from './CommandMenuPageInfoLayout';
|
|
import { ThemeContext } from 'twenty-ui/theme';
|
|
|
|
export const CommandMenuWorkflowStepInfo = ({
|
|
commandMenuPageInstanceId,
|
|
}: {
|
|
commandMenuPageInstanceId: string;
|
|
}) => {
|
|
const { theme } = useContext(ThemeContext);
|
|
const { getIcon } = useIcons();
|
|
|
|
const commandMenuPage = useAtomStateValue(commandMenuPageState);
|
|
|
|
const workflowId = useCommandMenuWorkflowIdOrThrow();
|
|
|
|
const commandMenuWorkflowStepId = useAtomComponentStateValue(
|
|
commandMenuWorkflowStepIdComponentState,
|
|
commandMenuPageInstanceId,
|
|
);
|
|
|
|
const workflowWithCurrentVersion = useWorkflowWithCurrentVersion(workflowId);
|
|
|
|
const isReadonly =
|
|
commandMenuPage === CommandMenuPages.WorkflowStepView ||
|
|
commandMenuPage === CommandMenuPages.WorkflowRunStepView;
|
|
|
|
const { updateCommandMenuPageInfo } = useUpdateCommandMenuPageInfo();
|
|
|
|
const instanceId = getWorkflowVisualizerComponentInstanceId({
|
|
recordId: workflowId,
|
|
});
|
|
const { getUpdatableWorkflowVersion } =
|
|
useGetUpdatableWorkflowVersionOrThrow(instanceId);
|
|
|
|
const { updateWorkflowVersionStep } = useUpdateWorkflowVersionStep();
|
|
const { updateOneRecord: updateOneWorkflowVersion } = useUpdateOneRecord();
|
|
|
|
const {
|
|
trigger,
|
|
steps,
|
|
id: workflowVersionId,
|
|
} = workflowWithCurrentVersion?.currentVersion ?? {
|
|
trigger: null,
|
|
steps: null,
|
|
id: undefined,
|
|
};
|
|
|
|
const isTriggerStep = commandMenuWorkflowStepId === TRIGGER_STEP_ID;
|
|
|
|
const stepDefinition =
|
|
isDefined(commandMenuWorkflowStepId) && isDefined(trigger)
|
|
? isTriggerStep || isDefined(steps)
|
|
? getStepDefinitionOrThrow({
|
|
stepId: commandMenuWorkflowStepId,
|
|
trigger,
|
|
steps,
|
|
})
|
|
: undefined
|
|
: undefined;
|
|
|
|
const isTrigger = stepDefinition?.type === 'trigger';
|
|
|
|
const agentId = getAgentIdFromStep(stepDefinition);
|
|
const { updateAgentLabel } = useUpdateAgentLabel(agentId);
|
|
const stepName =
|
|
isDefined(stepDefinition) && isDefined(stepDefinition.definition)
|
|
? isTrigger
|
|
? (stepDefinition.definition.name ??
|
|
(stepDefinition.definition.type === 'MANUAL'
|
|
? t`Launch manually`
|
|
: t`Trigger`))
|
|
: (stepDefinition.definition.name ?? t`Action`)
|
|
: '';
|
|
|
|
const [editedTitle, setEditedTitle] = useState<string | null>(null);
|
|
|
|
const title =
|
|
editedTitle ?? (isDefined(stepName) && stepName !== '' ? stepName : '');
|
|
|
|
const handleTitleChange = (newTitle: string) => {
|
|
setEditedTitle(newTitle);
|
|
};
|
|
|
|
if (
|
|
!isDefined(workflowId) ||
|
|
!isDefined(commandMenuWorkflowStepId) ||
|
|
!isDefined(workflowWithCurrentVersion?.currentVersion) ||
|
|
!isDefined(stepDefinition) ||
|
|
!isDefined(stepDefinition.definition)
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
const headerIcon = isTrigger
|
|
? getTriggerIcon(stepDefinition.definition)
|
|
: getActionIcon(stepDefinition.definition.type);
|
|
|
|
const headerIconColor = isTrigger
|
|
? getTriggerIconColor({
|
|
theme,
|
|
triggerType: stepDefinition.definition.type,
|
|
})
|
|
: getActionIconColorOrThrow({
|
|
theme,
|
|
actionType: stepDefinition.definition.type,
|
|
});
|
|
|
|
const headerType = isTrigger ? t`Trigger` : t`Action`;
|
|
|
|
const Icon = getIcon(headerIcon ?? 'IconDefault');
|
|
|
|
const saveTitle = async () => {
|
|
if (!isDefined(workflowVersionId) || !isDefined(workflowId)) {
|
|
return;
|
|
}
|
|
|
|
updateCommandMenuPageInfo({
|
|
pageTitle: title,
|
|
pageIcon: Icon,
|
|
});
|
|
|
|
const targetWorkflowVersionId = await getUpdatableWorkflowVersion();
|
|
|
|
if (isTrigger) {
|
|
await updateOneWorkflowVersion({
|
|
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
|
|
idToUpdate: targetWorkflowVersionId,
|
|
updateOneRecordInput: {
|
|
trigger: {
|
|
...stepDefinition.definition,
|
|
name: title,
|
|
} as typeof stepDefinition.definition,
|
|
},
|
|
});
|
|
} else {
|
|
await updateWorkflowVersionStep({
|
|
workflowVersionId: targetWorkflowVersionId,
|
|
step: {
|
|
...stepDefinition.definition,
|
|
name: title,
|
|
},
|
|
});
|
|
|
|
if (isDefined(agentId)) {
|
|
await updateAgentLabel(title);
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<CommandMenuPageInfoLayout
|
|
icon={
|
|
headerIcon ? (
|
|
<Icon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
|
|
) : undefined
|
|
}
|
|
iconColor={headerIconColor}
|
|
title={
|
|
<TitleInput
|
|
instanceId={`workflow-step-title-${commandMenuPageInstanceId}`}
|
|
disabled={isReadonly}
|
|
sizeVariant="sm"
|
|
value={title}
|
|
onChange={handleTitleChange}
|
|
placeholder={headerType}
|
|
onEnter={saveTitle}
|
|
onEscape={() => setEditedTitle(null)}
|
|
onClickOutside={saveTitle}
|
|
onTab={saveTitle}
|
|
onShiftTab={saveTitle}
|
|
/>
|
|
}
|
|
label={isTrigger ? t`Trigger` : t`Action`}
|
|
/>
|
|
);
|
|
};
|