feat(ui): hide the root container frame and header

This commit is contained in:
psychedelicious
2025-02-21 09:16:43 +10:00
parent 78832e546a
commit e6db36d0c4
5 changed files with 60 additions and 17 deletions

View File

@@ -1727,7 +1727,6 @@
"emptyRootPlaceholderViewMode": "Click Edit to start building a form for this workflow.",
"emptyRootPlaceholderEditMode": "Drag a form element or node field here to get started.",
"containerPlaceholder": "Empty Container",
"containerPlaceholderDesc": "Drag a form element or node field into this container.",
"headingPlaceholder": "Empty Heading",
"textPlaceholder": "Empty Text",
"workflowBuilderAlphaWarning": "The workflow builder is currently in alpha. There may be breaking changes before the stable release."

View File

@@ -7,6 +7,7 @@ import {
useDepthContext,
} from 'features/nodes/components/sidePanel/builder/contexts';
import { DividerElementComponent } from 'features/nodes/components/sidePanel/builder/DividerElementComponent';
import { useIsRootElement } from 'features/nodes/components/sidePanel/builder/dnd-hooks';
import { FormElementEditModeWrapper } from 'features/nodes/components/sidePanel/builder/FormElementEditModeWrapper';
import { HeadingElementComponent } from 'features/nodes/components/sidePanel/builder/HeadingElementComponent';
import { NodeFieldElementComponent } from 'features/nodes/components/sidePanel/builder/NodeFieldElementComponent';
@@ -83,10 +84,10 @@ const ContainerElementComponentViewMode = memo(({ el }: { el: ContainerElement }
ContainerElementComponentViewMode.displayName = 'ContainerElementComponentViewMode';
const ContainerElementComponentEditMode = memo(({ el }: { el: ContainerElement }) => {
const { t } = useTranslation();
const depth = useDepthContext();
const { id, data } = el;
const { children, layout } = data;
const isRootElement = useIsRootElement(id);
return (
<FormElementEditModeWrapper element={el}>
@@ -96,11 +97,8 @@ const ContainerElementComponentEditMode = memo(({ el }: { el: ContainerElement }
{children.map((childId) => (
<FormElementComponent key={childId} id={childId} />
))}
{children.length === 0 && (
<Flex p={8} w="full" h="full" alignItems="center" justifyContent="center">
<Text variant="subtext">{t('workflows.builder.containerPlaceholderDesc')}</Text>
</Flex>
)}
{children.length === 0 && isRootElement && <RootPlaceholder />}
{children.length === 0 && !isRootElement && <NonRootPlaceholder />}
</Flex>
</ContainerContextProvider>
</DepthContextProvider>
@@ -109,6 +107,26 @@ const ContainerElementComponentEditMode = memo(({ el }: { el: ContainerElement }
});
ContainerElementComponentEditMode.displayName = 'ContainerElementComponentEditMode';
const RootPlaceholder = memo(() => {
const { t } = useTranslation();
return (
<Flex p={8} w="full" h="full" alignItems="center" justifyContent="center">
<Text variant="subtext">{t('workflows.builder.emptyRootPlaceholderEditMode')}</Text>
</Flex>
);
});
RootPlaceholder.displayName = 'RootPlaceholder';
const NonRootPlaceholder = memo(() => {
const { t } = useTranslation();
return (
<Flex p={8} w="full" h="full" alignItems="center" justifyContent="center">
<Text variant="subtext">{t('workflows.builder.containerPlaceholder')}</Text>
</Flex>
);
});
NonRootPlaceholder.displayName = 'NonRootPlaceholder';
// TODO(psyche): Can we move this into a separate file and avoid circular dependencies between it and ContainerElementComponent?
export const FormElementComponent = memo(({ id }: { id: string }) => {
const el = useElement(id);

View File

@@ -20,6 +20,10 @@ const wrapperSx: SystemStyleObject = {
'&[data-element-type="divider"]&[data-layout="row"]': {
flex: '0 1 0',
},
'&[data-is-root="true"]': {
w: 'full',
h: 'full',
},
borderRadius: 'base',
};
@@ -86,10 +90,21 @@ export const FormElementEditModeWrapper = memo(({ element, children }: PropsWith
data-element-type={element.type}
data-layout={containerCtx?.layout}
>
<FormElementEditModeHeader ref={dragHandleRef} element={element} />
<Flex sx={contentWrapperSx} data-depth={depth}>
{children}
</Flex>
{!isRootElement && (
// Non-root elements get the header and content wrapper
<>
<FormElementEditModeHeader ref={dragHandleRef} element={element} />
<Flex sx={contentWrapperSx} data-depth={depth}>
{children}
</Flex>
</>
)}
{isRootElement && (
// But the root does not - helps the builder to look less busy
<Flex ref={dragHandleRef} w="full" h="full">
{children}
</Flex>
)}
</Flex>
<DndListDropIndicator activeDropRegion={activeDropRegion} gap="var(--invoke-space-4)" />
</Flex>

View File

@@ -1,5 +1,6 @@
import { combine } from '@atlaskit/pragmatic-drag-and-drop/combine';
import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { Button, ButtonGroup, Flex, Spacer } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
@@ -7,7 +8,7 @@ import { firefoxDndFix } from 'features/dnd/util';
import { FormElementComponent } from 'features/nodes/components/sidePanel/builder/ContainerElementComponent';
import { buildFormElementDndData, useBuilderDndMonitor } from 'features/nodes/components/sidePanel/builder/dnd-hooks';
import { WorkflowBuilderEditMenu } from 'features/nodes/components/sidePanel/builder/WorkflowBuilderMenu';
import { selectFormRootElementId } from 'features/nodes/store/workflowSlice';
import { selectFormRootElementId, selectIsFormEmpty } from 'features/nodes/store/workflowSlice';
import type { FormElement } from 'features/nodes/types/workflow';
import { buildContainer, buildDivider, buildHeading, buildText } from 'features/nodes/types/workflow';
import { startCase } from 'lodash-es';
@@ -16,9 +17,19 @@ import { memo, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { assert } from 'tsafe';
const sx: SystemStyleObject = {
pt: 3,
'&[data-is-empty="true"]': {
w: 'full',
h: 'full',
pt: 0,
},
};
export const WorkflowBuilder = memo(() => {
const { t } = useTranslation();
const rootElementId = useAppSelector(selectFormRootElementId);
const isFormEmpty = useAppSelector(selectIsFormEmpty);
useBuilderDndMonitor();
return (
@@ -36,7 +47,7 @@ export const WorkflowBuilder = memo(() => {
<WorkflowBuilderEditMenu />
</ButtonGroup>
<ScrollableContent>
<Flex>
<Flex sx={sx} data-is-empty={isFormEmpty}>
<FormElementComponent id={rootElementId} />
</Flex>
</ScrollableContent>

View File

@@ -322,7 +322,7 @@ export const useFormElementDnd = (
draggableRef: RefObject<HTMLElement>,
dragHandleRef: RefObject<HTMLElement>
) => {
const rootElementId = useAppSelector(selectFormRootElementId);
const isRootElement = useIsRootElement(elementId);
const [isDragging, setIsDragging] = useState(false);
const [activeDropRegion, setActiveDropRegion] = useState<CenterOrEdge | null>(null);
const getElement = useGetElement();
@@ -340,7 +340,7 @@ export const useFormElementDnd = (
firefoxDndFix(draggableElement),
draggable({
// Don't allow dragging the root element
canDrag: () => elementId !== rootElementId,
canDrag: () => !isRootElement,
element: draggableElement,
dragHandle: dragHandleElement,
getInitialData: () => {
@@ -356,7 +356,7 @@ export const useFormElementDnd = (
}),
dropTargetForElements({
element: draggableElement,
getIsSticky: () => elementId !== rootElementId,
getIsSticky: () => !isRootElement,
canDrop: ({ source }) =>
isFormElementDndData(source.data) && source.data.element.id !== getElement(elementId).parentId,
getData: ({ input }) => {
@@ -399,7 +399,7 @@ export const useFormElementDnd = (
},
})
);
}, [dragHandleRef, draggableRef, elementId, getAllowedDropRegions, getElement, rootElementId]);
}, [dragHandleRef, draggableRef, elementId, getAllowedDropRegions, getElement, isRootElement]);
return [activeDropRegion, isDragging] as const;
};