Form action fast follows (#11242)

- on form action, add trash on hover and fix spacing
- fix dark mode
- second batch of icon fixes

<img width="692" alt="Capture d’écran 2025-03-27 à 18 25 48"
src="https://github.com/user-attachments/assets/963f50d2-12ee-425c-97a3-2ad0c6bb5c57"
/>
This commit is contained in:
Thomas Trompette
2025-03-28 10:11:01 +01:00
committed by GitHub
parent 78943b3370
commit 8d35454dd8
8 changed files with 116 additions and 75 deletions

View File

@ -39,7 +39,6 @@ import {
IconFileImport, IconFileImport,
IconHeart, IconHeart,
IconHeartOff, IconHeartOff,
IconHistory,
IconHistoryToggle, IconHistoryToggle,
IconNoteOff, IconNoteOff,
IconPlayerPause, IconPlayerPause,
@ -49,6 +48,7 @@ import {
IconRotate2, IconRotate2,
IconTrash, IconTrash,
IconTrashX, IconTrashX,
IconVersions,
} from 'twenty-ui'; } from 'twenty-ui';
export const WORKFLOW_ACTIONS_CONFIG: Record< export const WORKFLOW_ACTIONS_CONFIG: Record<
@ -120,7 +120,7 @@ export const WORKFLOW_ACTIONS_CONFIG: Record<
shortLabel: msg`See active version`, shortLabel: msg`See active version`,
isPinned: false, isPinned: false,
position: 4, position: 4,
Icon: IconHistory, Icon: IconVersions,
type: ActionMenuEntryType.Standard, type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection, scope: ActionMenuEntryScope.RecordSelection,
availableOn: [ availableOn: [
@ -150,7 +150,7 @@ export const WORKFLOW_ACTIONS_CONFIG: Record<
shortLabel: msg`See versions`, shortLabel: msg`See versions`,
isPinned: false, isPinned: false,
position: 6, position: 6,
Icon: IconHistory, Icon: IconVersions,
type: ActionMenuEntryType.Standard, type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection, scope: ActionMenuEntryScope.RecordSelection,
availableOn: [ availableOn: [

View File

@ -27,9 +27,9 @@ import {
IconEyeOff, IconEyeOff,
IconHeart, IconHeart,
IconHeartOff, IconHeartOff,
IconHistory,
IconRotate2, IconRotate2,
IconSettingsAutomation, IconSettingsAutomation,
IconVersions,
} from 'twenty-ui'; } from 'twenty-ui';
export const WORKFLOW_RUNS_ACTIONS_CONFIG: Record< export const WORKFLOW_RUNS_ACTIONS_CONFIG: Record<
@ -61,7 +61,7 @@ export const WORKFLOW_RUNS_ACTIONS_CONFIG: Record<
isPinned: true, isPinned: true,
type: ActionMenuEntryType.Standard, type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection, scope: ActionMenuEntryScope.RecordSelection,
Icon: IconHistory, Icon: IconVersions,
availableOn: [ availableOn: [
ActionViewType.SHOW_PAGE, ActionViewType.SHOW_PAGE,
ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION, ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,

View File

@ -30,11 +30,11 @@ import {
IconEyeOff, IconEyeOff,
IconHeart, IconHeart,
IconHeartOff, IconHeartOff,
IconHistory,
IconHistoryToggle, IconHistoryToggle,
IconPencil, IconPencil,
IconRotate2, IconRotate2,
IconSettingsAutomation, IconSettingsAutomation,
IconVersions,
} from 'twenty-ui'; } from 'twenty-ui';
export const WORKFLOW_VERSIONS_ACTIONS_CONFIG: Record< export const WORKFLOW_VERSIONS_ACTIONS_CONFIG: Record<
@ -95,7 +95,7 @@ export const WORKFLOW_VERSIONS_ACTIONS_CONFIG: Record<
position: 4, position: 4,
type: ActionMenuEntryType.Standard, type: ActionMenuEntryType.Standard,
scope: ActionMenuEntryScope.RecordSelection, scope: ActionMenuEntryScope.RecordSelection,
Icon: IconHistory, Icon: IconVersions,
availableOn: [ availableOn: [
ActionViewType.SHOW_PAGE, ActionViewType.SHOW_PAGE,
ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION, ActionViewType.INDEX_PAGE_SINGLE_RECORD_SELECTION,

View File

@ -81,13 +81,18 @@ const StyledIconButtonContainer = styled.div`
} }
`; `;
const StyledAddFieldContainer = styled.div` const StyledAddFieldButtonContainer = styled.div`
display: flex; padding-top: ${({ theme }) => theme.spacing(2)};
`;
const StyledAddFieldButtonContentContainer = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.secondary}; color: ${({ theme }) => theme.font.color.secondary};
display: flex;
font-weight: ${({ theme }) => theme.font.weight.medium}; font-weight: ${({ theme }) => theme.font.weight.medium};
gap: ${({ theme }) => theme.spacing(0.5)};
justify-content: center; justify-content: center;
width: 100%; width: 100%;
gap: ${({ theme }) => theme.spacing(0.5)};
`; `;
export const WorkflowEditActionFormBuilder = ({ export const WorkflowEditActionFormBuilder = ({
@ -106,7 +111,9 @@ export const WorkflowEditActionFormBuilder = ({
const headerType = useActionHeaderTypeOrThrow(action.type); const headerType = useActionHeaderTypeOrThrow(action.type);
const [selectedField, setSelectedField] = useState<string | null>(null); const [selectedField, setSelectedField] = useState<string | null>(null);
const [hoveredField, setHoveredField] = useState<string | null>(null);
const isFieldSelected = (fieldName: string) => selectedField === fieldName; const isFieldSelected = (fieldName: string) => selectedField === fieldName;
const isFieldHovered = (fieldName: string) => hoveredField === fieldName;
const handleFieldClick = (fieldName: string) => { const handleFieldClick = (fieldName: string) => {
if (actionOptions.readonly === true) { if (actionOptions.readonly === true) {
return; return;
@ -177,7 +184,10 @@ export const WorkflowEditActionFormBuilder = ({
<FormFieldInputContainer key={field.id}> <FormFieldInputContainer key={field.id}>
{field.label ? <InputLabel>{field.label}</InputLabel> : null} {field.label ? <InputLabel>{field.label}</InputLabel> : null}
<StyledRowContainer> <StyledRowContainer
onMouseEnter={() => setHoveredField(field.id)}
onMouseLeave={() => setHoveredField(null)}
>
<FormFieldInputRowContainer> <FormFieldInputRowContainer>
<FormFieldInputInputContainer <FormFieldInputInputContainer
hasRightElement={false} hasRightElement={false}
@ -201,29 +211,30 @@ export const WorkflowEditActionFormBuilder = ({
</StyledFieldContainer> </StyledFieldContainer>
</FormFieldInputInputContainer> </FormFieldInputInputContainer>
</FormFieldInputRowContainer> </FormFieldInputRowContainer>
{!actionOptions.readonly && isFieldSelected(field.id) && ( {!actionOptions.readonly &&
<StyledIconButtonContainer> (isFieldSelected(field.id) || isFieldHovered(field.id)) && (
<IconTrash <StyledIconButtonContainer>
size={theme.icon.size.md} <IconTrash
color={theme.font.color.secondary} size={theme.icon.size.md}
onClick={() => { color={theme.font.color.secondary}
const updatedFormData = formData.filter( onClick={() => {
(currentField) => currentField.id !== field.id, const updatedFormData = formData.filter(
); (currentField) => currentField.id !== field.id,
);
setFormData(updatedFormData); setFormData(updatedFormData);
actionOptions.onActionUpdate({ actionOptions.onActionUpdate({
...action, ...action,
settings: { settings: {
...action.settings, ...action.settings,
input: updatedFormData, input: updatedFormData,
}, },
}); });
}} }}
/> />
</StyledIconButtonContainer> </StyledIconButtonContainer>
)} )}
{isFieldSelected(field.id) && ( {isFieldSelected(field.id) && (
<WorkflowEditActionFormFieldSettings <WorkflowEditActionFormFieldSettings
field={field} field={field}
@ -237,44 +248,46 @@ export const WorkflowEditActionFormBuilder = ({
</FormFieldInputContainer> </FormFieldInputContainer>
))} ))}
{!actionOptions.readonly && ( {!actionOptions.readonly && (
<StyledRowContainer> <StyledAddFieldButtonContainer>
<FormFieldInputContainer> <StyledRowContainer>
<FormFieldInputRowContainer> <FormFieldInputContainer>
<FormFieldInputInputContainer <FormFieldInputRowContainer>
hasRightElement={false} <FormFieldInputInputContainer
onClick={() => { hasRightElement={false}
const { label, placeholder, name } = onClick={() => {
getDefaultFormFieldSettings(FieldMetadataType.TEXT); const { label, placeholder, name } =
getDefaultFormFieldSettings(FieldMetadataType.TEXT);
const newField: WorkflowFormActionField = { const newField: WorkflowFormActionField = {
id: v4(), id: v4(),
name, name,
type: FieldMetadataType.TEXT, type: FieldMetadataType.TEXT,
label, label,
placeholder, placeholder,
}; };
setFormData([...formData, newField]); setFormData([...formData, newField]);
actionOptions.onActionUpdate({ actionOptions.onActionUpdate({
...action, ...action,
settings: { settings: {
...action.settings, ...action.settings,
input: [...action.settings.input, newField], input: [...action.settings.input, newField],
}, },
}); });
}} }}
> >
<StyledFieldContainer> <StyledFieldContainer>
<StyledAddFieldContainer> <StyledAddFieldButtonContentContainer>
<IconPlus size={theme.icon.size.sm} /> <IconPlus size={theme.icon.size.sm} />
{t`Add Field`} {t`Add Field`}
</StyledAddFieldContainer> </StyledAddFieldButtonContentContainer>
</StyledFieldContainer> </StyledFieldContainer>
</FormFieldInputInputContainer> </FormFieldInputInputContainer>
</FormFieldInputRowContainer> </FormFieldInputRowContainer>
</FormFieldInputContainer> </FormFieldInputContainer>
</StyledRowContainer> </StyledRowContainer>
</StyledAddFieldButtonContainer>
)} )}
</WorkflowStepBody> </WorkflowStepBody>
</> </>

View File

@ -44,12 +44,14 @@ const StyledSettingsHeader = styled.div`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
display: grid; display: grid;
gap: ${({ theme }) => theme.spacing(1)}; gap: ${({ theme }) => theme.spacing(1)};
padding-inline: ${({ theme }) => theme.spacing(3)}; padding-right: ${({ theme }) => theme.spacing(2)};
padding-left: ${({ theme }) => theme.spacing(3)};
grid-template-columns: 1fr 24px; grid-template-columns: 1fr 24px;
padding-bottom: ${({ theme }) => theme.spacing(3)}; padding-bottom: ${({ theme }) => theme.spacing(3)};
`; `;
const StyledTitleContainer = styled.div` const StyledTitleContainer = styled.div`
color: ${({ theme }) => theme.font.color.primary};
display: flex; display: flex;
flex-direction: row; flex-direction: row;
gap: ${({ theme }) => theme.spacing(1)}; gap: ${({ theme }) => theme.spacing(1)};

View File

@ -14,7 +14,7 @@ export const workflowRunsAllView = (
type: 'table', type: 'table',
key: 'INDEX', key: 'INDEX',
position: 0, position: 0,
icon: 'IconHistory', icon: 'IconHistoryToggle',
openRecordIn: ViewOpenRecordInType.RECORD_PAGE, openRecordIn: ViewOpenRecordInType.RECORD_PAGE,
kanbanFieldMetadataId: '', kanbanFieldMetadataId: '',
filters: [], filters: [],

View File

@ -8,6 +8,7 @@ import {
WorkflowRunStatus, WorkflowRunStatus,
WorkflowRunWorkspaceEntity, WorkflowRunWorkspaceEntity,
} from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity'; } from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service'; import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
import { import {
@ -39,12 +40,36 @@ export class WorkflowRunWorkspaceService {
workflowVersionId, workflowVersionId,
); );
const workflowRepository =
await this.twentyORMManager.getRepository<WorkflowWorkspaceEntity>(
'workflow',
);
const workflow = await workflowRepository.findOne({
where: {
id: workflowVersion.workflowId,
},
});
if (!workflow) {
throw new WorkflowRunException(
'Workflow id is invalid',
WorkflowRunExceptionCode.WORKFLOW_RUN_INVALID,
);
}
const workflowRunCount = await workflowRunRepository.count({
where: {
workflowId: workflow.id,
},
});
return ( return (
await workflowRunRepository.save({ await workflowRunRepository.save({
name: `Execution of ${workflowVersion.name}`, name: `#${workflowRunCount + 1} - ${workflow.name}`,
workflowVersionId, workflowVersionId,
createdBy, createdBy,
workflowId: workflowVersion.workflowId, workflowId: workflow.id,
status: WorkflowRunStatus.NOT_STARTED, status: WorkflowRunStatus.NOT_STARTED,
}) })
).id; ).id;

View File

@ -203,6 +203,7 @@ export {
IconMinus, IconMinus,
IconMoneybag, IconMoneybag,
IconMoodSmile, IconMoodSmile,
IconMoon,
IconMouse2, IconMouse2,
IconNorthStar, IconNorthStar,
IconNoteOff, IconNoteOff,
@ -262,6 +263,9 @@ export {
IconSquareRoundedX, IconSquareRoundedX,
IconStatusChange, IconStatusChange,
IconStepInto, IconStepInto,
IconSun,
IconSunMoon,
IconSwitchHorizontal,
IconTable, IconTable,
IconTag, IconTag,
IconTags, IconTags,
@ -281,16 +285,13 @@ export {
IconUpload, IconUpload,
IconUser, IconUser,
IconUserCircle, IconUserCircle,
IconSwitchHorizontal,
IconUserCog, IconUserCog,
IconUserPin, IconUserPin,
IconUserPlus, IconUserPlus,
IconSunMoon,
IconMoon,
IconSun,
IconUsers, IconUsers,
IconVariable, IconVariable,
IconVariablePlus, IconVariablePlus,
IconVersions,
IconVideo, IconVideo,
IconWand, IconWand,
IconWebhook, IconWebhook,