feat(ui): builder empty state (WIP)

This commit is contained in:
psychedelicious
2025-02-06 13:24:21 +11:00
parent 176248a023
commit 1eb491fdaa
4 changed files with 66 additions and 28 deletions

View File

@@ -52,17 +52,15 @@ export const FormElementEditModeHeader = memo(
<Spacer />
{isContainerElement(element) && <ContainerElementSettings element={element} />}
{isNodeFieldElement(element) && <NodeFieldElementSettings element={element} />}
{element.parentId && (
<IconButton
aria-label="delete"
onClick={removeElement}
icon={<PiXBold />}
variant="link"
size="sm"
alignSelf="stretch"
colorScheme="error"
/>
)}
<IconButton
aria-label="delete"
onClick={removeElement}
icon={<PiXBold />}
variant="link"
size="sm"
alignSelf="stretch"
colorScheme="error"
/>
</Flex>
);
})

View File

@@ -64,7 +64,7 @@ export const FormElementEditModeWrapper = memo(({ element, children }: PropsWith
data-layout={containerCtx?.layout}
>
<FormElementEditModeHeader ref={dragHandleRef} element={element} />
<Flex w="full" h="full" p={4} alignItems="center" gap={4}>
<Flex w="full" h="full" p={4} gap={4}>
{children}
</Flex>
</Flex>

View File

@@ -1,6 +1,6 @@
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import { Button, ButtonGroup, Flex } from '@invoke-ai/ui-library';
import { Button, ButtonGroup, Flex, Text } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import { firefoxDndFix } from 'features/dnd/util';
@@ -24,7 +24,7 @@ export const WorkflowBuilder = memo(() => {
return (
<ScrollableContent>
<Flex w="full" justifyContent="center">
<Flex justifyContent="center">
<Flex flexDir="column" w={mode === 'view' ? '512px' : 'min-content'} minW="512px" gap={2}>
<ButtonGroup isAttached={false} justifyContent="center">
<ToggleModeButton />
@@ -35,14 +35,43 @@ export const WorkflowBuilder = memo(() => {
<AddFormElementDndButton type="text" />
</ButtonGroup>
{rootElementId && <FormElementComponent id={rootElementId} />}
{!rootElementId && <EmptyState />}
</Flex>
</Flex>
</ScrollableContent>
);
});
WorkflowBuilder.displayName = 'WorkflowBuilder';
const EmptyState = memo(() => {
const mode = useAppSelector(selectWorkflowFormMode);
const dispatch = useAppDispatch();
const toggleMode = useCallback(() => {
dispatch(formModeToggled());
}, [dispatch]);
const addContainer = useCallback(() => {}, []);
if (mode === 'view') {
return (
<Flex flexDir="column" gap={4} w="full" h="full" justifyContent="center" alignItems="center">
<Text variant="subtext" fontSize="md">
Click Edit to build a form for this workflow.
</Text>
<Button onClick={toggleMode}>Edit</Button>
</Flex>
);
}
<Flex flexDir="column" gap={4} w="full" h="full" justifyContent="center" alignItems="center">
<Text variant="subtext" fontSize="md">
No form elements added. Click a button above to add a form element.
</Text>
</Flex>;
});
EmptyState.displayName = 'EmptyState';
const ToggleModeButton = memo(() => {
const dispatch = useAppDispatch();
const mode = useAppSelector(selectWorkflowFormMode);
@@ -57,7 +86,8 @@ ToggleModeButton.displayName = 'ToggleModeButton';
const useAddFormElementDnd = (
type: Exclude<FormElement['type'], 'node-field' | 'container'> | 'row' | 'column',
draggableRef: RefObject<HTMLElement>
draggableRef: RefObject<HTMLElement>,
isEnabled = true
) => {
const [isDragging, setIsDragging] = useState(false);
@@ -70,6 +100,7 @@ const useAddFormElementDnd = (
firefoxDndFix(draggableElement),
draggable({
element: draggableElement,
canDrag: () => isEnabled,
getInitialData: () => {
if (type === 'row') {
const element = buildContainer('row', []);
@@ -101,17 +132,29 @@ const useAddFormElementDnd = (
},
})
);
}, [draggableRef, type]);
}, [draggableRef, isEnabled, type]);
return isDragging;
};
const AddFormElementDndButton = ({ type }: { type: Parameters<typeof useAddFormElementDnd>[0] }) => {
const draggableRef = useRef<HTMLButtonElement>(null);
const draggableRef = useRef<HTMLDivElement>(null);
const rootElementId = useAppSelector(selectRootElementId);
const isDragging = useAddFormElementDnd(type, draggableRef);
return (
<Button ref={draggableRef} variant="ghost" pointerEvents="auto" opacity={isDragging ? 0.3 : 1}>
<Button
ref={draggableRef}
variant="unstyled"
borderWidth={2}
borderStyle="dashed"
borderRadius="base"
px={4}
py={1}
cursor="grab"
_hover={{ bg: 'base.800' }}
isDisabled={isDragging || (type !== 'row' && type !== 'column' && !rootElementId)}
>
{startCase(type)}
</Button>
);

View File

@@ -22,7 +22,6 @@ import type {
WorkflowV3,
} from 'features/nodes/types/workflow';
import {
buildContainer,
isContainerElement,
isHeadingElement,
isNodeFieldElement,
@@ -53,7 +52,6 @@ const formElementDataChangedReducer = <T extends FormElement>(
};
const getBlankWorkflow = (): Omit<WorkflowV3, 'nodes' | 'edges'> => {
const rootElement = buildContainer('column', []);
return {
name: '',
author: '',
@@ -65,12 +63,7 @@ const getBlankWorkflow = (): Omit<WorkflowV3, 'nodes' | 'edges'> => {
exposedFields: [],
meta: { version: '3.0.0', category: 'user' },
id: undefined,
form: {
elements: {
[rootElement.id]: rootElement,
},
rootElementId: rootElement.id,
},
form: undefined,
};
};
@@ -194,7 +187,11 @@ export const workflowSlice = createSlice({
return;
}
const { id } = action.payload;
recursivelyRemoveElement({ id, formState: state.form });
if (id === state.form.rootElementId) {
state.form = undefined;
} else {
recursivelyRemoveElement({ id, formState: state.form });
}
},
formElementMoved: (state, action: PayloadAction<{ id: string; containerId: string; index?: number }>) => {
if (!state.form) {