mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): iterate on builder (WIP)
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
import type { ContainerElement } from 'features/nodes/types/workflow';
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
export const ContainerContext = createContext<ContainerElement['data'] | null>(null);
|
||||
|
||||
export const useContainerContext = () => {
|
||||
const containerDirection = useContext(ContainerContext);
|
||||
return containerDirection;
|
||||
};
|
||||
@@ -1,63 +1,120 @@
|
||||
import { Flex, type SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { ContainerContext } from 'features/nodes/components/sidePanel/builder/ContainerContext';
|
||||
import { Flex, IconButton, type SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { ContainerContext, DepthContext } from 'features/nodes/components/sidePanel/builder/contexts';
|
||||
import { DividerElementComponent } from 'features/nodes/components/sidePanel/builder/DividerElementComponent';
|
||||
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';
|
||||
import { TextElementComponent } from 'features/nodes/components/sidePanel/builder/TextElementComponent';
|
||||
import { useElement } from 'features/nodes/store/workflowSlice';
|
||||
import { formElementAdded, selectWorkflowFormMode, useElement } from 'features/nodes/store/workflowSlice';
|
||||
import type { ContainerElement } from 'features/nodes/types/workflow';
|
||||
import {
|
||||
container,
|
||||
CONTAINER_CLASS_NAME,
|
||||
DIVIDER_CLASS_NAME,
|
||||
isContainerElement,
|
||||
isDividerElement,
|
||||
isHeadingElement,
|
||||
isNodeFieldElement,
|
||||
isTextElement,
|
||||
} from 'features/nodes/types/workflow';
|
||||
import { memo } from 'react';
|
||||
import { memo, useCallback, useContext } from 'react';
|
||||
import { PiPlusBold } from 'react-icons/pi';
|
||||
import type { Equals } from 'tsafe';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
const sx: SystemStyleObject = {
|
||||
gap: 4,
|
||||
flex: '1 1 0',
|
||||
'&[data-container-direction="column"]': {
|
||||
flexDir: 'column',
|
||||
'> :last-child': {
|
||||
flex: '1 0 0',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
},
|
||||
'&[data-container-direction="row"]': {
|
||||
'> *': {
|
||||
flex: '1 1 0',
|
||||
},
|
||||
},
|
||||
[`& > .${DIVIDER_CLASS_NAME}`]: {
|
||||
flex: '0 0 1px',
|
||||
flexDir: 'row',
|
||||
},
|
||||
};
|
||||
|
||||
export const ContainerElementComponent = memo(({ id }: { id: string }) => {
|
||||
const el = useElement(id);
|
||||
const mode = useAppSelector(selectWorkflowFormMode);
|
||||
|
||||
if (!el || !isContainerElement(el)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { children, direction } = el.data;
|
||||
if (mode === 'view') {
|
||||
return <ContainerElementComponentViewMode el={el} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ContainerContext.Provider value={el.data}>
|
||||
<Flex id={id} className={CONTAINER_CLASS_NAME} sx={sx} data-container-direction={direction}>
|
||||
{children.map((childId) => (
|
||||
<FormElementComponent key={childId} id={childId} />
|
||||
))}
|
||||
</Flex>
|
||||
</ContainerContext.Provider>
|
||||
);
|
||||
// mode === 'edit'
|
||||
return <ContainerElementComponentEditMode el={el} />;
|
||||
});
|
||||
ContainerElementComponent.displayName = 'ContainerElementComponent';
|
||||
|
||||
export const ContainerElementComponentViewMode = memo(({ el }: { el: ContainerElement }) => {
|
||||
const depth = useContext(DepthContext);
|
||||
const { id, data } = el;
|
||||
const { children, direction } = data;
|
||||
|
||||
return (
|
||||
<DepthContext.Provider value={depth + 1}>
|
||||
<ContainerContext.Provider value={data}>
|
||||
<Flex id={id} className={CONTAINER_CLASS_NAME} sx={sx} data-container-direction={direction}>
|
||||
{children.map((childId) => (
|
||||
<FormElementComponent key={childId} id={childId} />
|
||||
))}
|
||||
</Flex>
|
||||
</ContainerContext.Provider>{' '}
|
||||
</DepthContext.Provider>
|
||||
);
|
||||
});
|
||||
ContainerElementComponentViewMode.displayName = 'ContainerElementComponentViewMode';
|
||||
|
||||
export const ContainerElementComponentEditMode = memo(({ el }: { el: ContainerElement }) => {
|
||||
const depth = useContext(DepthContext);
|
||||
|
||||
const { id, data } = el;
|
||||
const { children, direction } = data;
|
||||
|
||||
return (
|
||||
<FormElementEditModeWrapper element={el}>
|
||||
<DepthContext.Provider value={depth + 1}>
|
||||
<ContainerContext.Provider value={data}>
|
||||
<Flex id={id} className={CONTAINER_CLASS_NAME} sx={sx} data-container-direction={direction}>
|
||||
{children.map((childId) => (
|
||||
<FormElementComponent key={childId} id={childId} />
|
||||
))}
|
||||
{direction === 'row' && children.length < 3 && depth < 2 && <AddColumnButton containerId={id} />}
|
||||
{direction === 'column' && depth < 1 && <AddRowButton containerId={id} />}
|
||||
</Flex>
|
||||
</ContainerContext.Provider>
|
||||
</DepthContext.Provider>
|
||||
</FormElementEditModeWrapper>
|
||||
);
|
||||
});
|
||||
ContainerElementComponentEditMode.displayName = 'ContainerElementComponentEditMode';
|
||||
|
||||
const AddColumnButton = ({ containerId }: { containerId: string }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const onClick = useCallback(() => {
|
||||
const element = container('column', []);
|
||||
dispatch(formElementAdded({ element, containerId }));
|
||||
}, [containerId, dispatch]);
|
||||
return (
|
||||
<IconButton onClick={onClick} aria-label="add column" icon={<PiPlusBold />} h="unset" variant="ghost" size="sm" />
|
||||
);
|
||||
};
|
||||
|
||||
const AddRowButton = ({ containerId }: { containerId: string }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const onClick = useCallback(() => {
|
||||
const element = container('row', []);
|
||||
dispatch(formElementAdded({ element, containerId }));
|
||||
}, [containerId, dispatch]);
|
||||
return (
|
||||
<IconButton onClick={onClick} aria-label="add row" icon={<PiPlusBold />} w="unset" variant="ghost" size="sm" />
|
||||
);
|
||||
};
|
||||
|
||||
// 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);
|
||||
|
||||
@@ -1,22 +1,74 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { useElement } from 'features/nodes/store/workflowSlice';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { ContainerContext } from 'features/nodes/components/sidePanel/builder/contexts';
|
||||
import { FormElementEditModeWrapper } from 'features/nodes/components/sidePanel/builder/FormElementEditModeWrapper';
|
||||
import { selectWorkflowFormMode, useElement } from 'features/nodes/store/workflowSlice';
|
||||
import type { DividerElement } from 'features/nodes/types/workflow';
|
||||
import { DIVIDER_CLASS_NAME, isDividerElement } from 'features/nodes/types/workflow';
|
||||
import { memo } from 'react';
|
||||
import { memo, useContext } from 'react';
|
||||
|
||||
const sx: SystemStyleObject = {
|
||||
bg: 'base.700',
|
||||
flexShrink: 0,
|
||||
'&[data-orientation="horizontal"]': {
|
||||
width: '100%',
|
||||
height: '1px',
|
||||
},
|
||||
'&[data-orientation="vertical"]': {
|
||||
height: '100%',
|
||||
width: '1px',
|
||||
},
|
||||
};
|
||||
|
||||
export const DividerElementComponent = memo(({ id }: { id: string }) => {
|
||||
const el = useElement(id);
|
||||
const mode = useAppSelector(selectWorkflowFormMode);
|
||||
|
||||
if (!el || !isDividerElement(el)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return <Flex id={id} className={DIVIDER_CLASS_NAME} sx={sx} />;
|
||||
if (mode === 'view') {
|
||||
return <DividerElementComponentViewMode el={el} />;
|
||||
}
|
||||
|
||||
// mode === 'edit'
|
||||
return <DividerElementComponentEditMode el={el} />;
|
||||
});
|
||||
|
||||
DividerElementComponent.displayName = 'DividerElementComponent';
|
||||
|
||||
export const DividerElementComponentViewMode = memo(({ el }: { el: DividerElement }) => {
|
||||
const container = useContext(ContainerContext);
|
||||
const { id } = el;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
id={id}
|
||||
className={DIVIDER_CLASS_NAME}
|
||||
sx={sx}
|
||||
data-orientation={container?.direction === 'column' ? 'horizontal' : 'vertical'}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
DividerElementComponentViewMode.displayName = 'DividerElementComponentViewMode';
|
||||
|
||||
export const DividerElementComponentEditMode = memo(({ el }: { el: DividerElement }) => {
|
||||
const container = useContext(ContainerContext);
|
||||
const { id } = el;
|
||||
|
||||
return (
|
||||
<FormElementEditModeWrapper element={el}>
|
||||
<Flex
|
||||
id={id}
|
||||
className={DIVIDER_CLASS_NAME}
|
||||
sx={sx}
|
||||
data-orientation={container?.direction === 'column' ? 'horizontal' : 'vertical'}
|
||||
/>
|
||||
</FormElementEditModeWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
DividerElementComponentEditMode.displayName = 'DividerElementComponentEditMode';
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Flex, type FlexProps, IconButton, Spacer, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import { DepthContext } from 'features/nodes/components/sidePanel/builder/contexts';
|
||||
import { formElementRemoved } from 'features/nodes/store/workflowSlice';
|
||||
import { type FormElement, isContainerElement } from 'features/nodes/types/workflow';
|
||||
import { startCase } from 'lodash-es';
|
||||
import { memo, useCallback, useContext } from 'react';
|
||||
import { PiXBold } from 'react-icons/pi';
|
||||
|
||||
export const EDIT_MODE_WRAPPER_CLASS_NAME = getPrefixedId('edit-mode-wrapper', '-');
|
||||
|
||||
const getHeaderBgColor = (depth: number) => {
|
||||
if (depth <= 1) {
|
||||
return 'base.800';
|
||||
}
|
||||
if (depth === 2) {
|
||||
return 'base.750';
|
||||
}
|
||||
return 'base.700';
|
||||
};
|
||||
|
||||
const getHeaderLabel = (el: FormElement) => {
|
||||
if (isContainerElement(el)) {
|
||||
if (el.data.direction === 'column') {
|
||||
return 'Column';
|
||||
}
|
||||
return 'Row';
|
||||
}
|
||||
return startCase(el.type);
|
||||
};
|
||||
|
||||
export const FormElementEditModeWrapper = memo(
|
||||
({ element, children, ...rest }: { element: FormElement } & FlexProps) => {
|
||||
const depth = useContext(DepthContext);
|
||||
const dispatch = useAppDispatch();
|
||||
const removeElement = useCallback(() => {
|
||||
dispatch(formElementRemoved({ id: element.id }));
|
||||
}, [dispatch, element.id]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
className={EDIT_MODE_WRAPPER_CLASS_NAME}
|
||||
flexDir="column"
|
||||
borderWidth={1}
|
||||
borderRadius="base"
|
||||
borderColor="base.750"
|
||||
alignItems="center"
|
||||
justifyContent="flex-start"
|
||||
w="full"
|
||||
h="full"
|
||||
{...rest}
|
||||
>
|
||||
<Flex
|
||||
w="full"
|
||||
ps={2}
|
||||
h={8}
|
||||
bg={getHeaderBgColor(depth)}
|
||||
borderTopRadius="inherit"
|
||||
borderBottomWidth={1}
|
||||
borderColor="inherit"
|
||||
alignItems="center"
|
||||
cursor="grab"
|
||||
>
|
||||
<Text fontWeight="semibold" noOfLines={1} wordBreak="break-all">
|
||||
{getHeaderLabel(element)}
|
||||
</Text>
|
||||
<Spacer />
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={removeElement}
|
||||
icon={<PiXBold />}
|
||||
variant="link"
|
||||
size="sm"
|
||||
alignSelf="stretch"
|
||||
colorScheme="error"
|
||||
/>
|
||||
</Flex>
|
||||
<Flex w="full" p={4} alignItems="center" gap={4}>
|
||||
{children}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
FormElementEditModeWrapper.displayName = 'FormElementEditModeWrapper';
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Flex, Heading } from '@invoke-ai/ui-library';
|
||||
import { useElement } from 'features/nodes/store/workflowSlice';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { FormElementEditModeWrapper } from 'features/nodes/components/sidePanel/builder/FormElementEditModeWrapper';
|
||||
import { selectWorkflowFormMode, useElement } from 'features/nodes/store/workflowSlice';
|
||||
import type { HeadingElement } from 'features/nodes/types/workflow';
|
||||
import { HEADING_CLASS_NAME, isHeadingElement } from 'features/nodes/types/workflow';
|
||||
import { memo } from 'react';
|
||||
|
||||
@@ -13,12 +16,25 @@ const LEVEL_TO_SIZE = {
|
||||
|
||||
export const HeadingElementComponent = memo(({ id }: { id: string }) => {
|
||||
const el = useElement(id);
|
||||
const mode = useAppSelector(selectWorkflowFormMode);
|
||||
|
||||
if (!el || !isHeadingElement(el)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { content, level } = el.data;
|
||||
if (mode === 'view') {
|
||||
return <HeadingElementComponentViewMode el={el} />;
|
||||
}
|
||||
|
||||
// mode === 'edit'
|
||||
return <HeadingElementComponentEditMode el={el} />;
|
||||
});
|
||||
|
||||
HeadingElementComponent.displayName = 'HeadingElementComponent';
|
||||
|
||||
export const HeadingElementComponentViewMode = memo(({ el }: { el: HeadingElement }) => {
|
||||
const { id, data } = el;
|
||||
const { content, level } = data;
|
||||
|
||||
return (
|
||||
<Flex id={id} className={HEADING_CLASS_NAME}>
|
||||
@@ -27,4 +43,19 @@ export const HeadingElementComponent = memo(({ id }: { id: string }) => {
|
||||
);
|
||||
});
|
||||
|
||||
HeadingElementComponent.displayName = 'HeadingElementComponent';
|
||||
HeadingElementComponentViewMode.displayName = 'HeadingElementComponentViewMode';
|
||||
|
||||
export const HeadingElementComponentEditMode = memo(({ el }: { el: HeadingElement }) => {
|
||||
const { id, data } = el;
|
||||
const { content, level } = data;
|
||||
|
||||
return (
|
||||
<FormElementEditModeWrapper element={el}>
|
||||
<Flex id={id} className={HEADING_CLASS_NAME}>
|
||||
<Heading size={LEVEL_TO_SIZE[level]}>{content}</Heading>
|
||||
</Flex>
|
||||
</FormElementEditModeWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
HeadingElementComponentEditMode.displayName = 'HeadingElementComponentEditMode';
|
||||
|
||||
@@ -1,18 +1,34 @@
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { InputFieldGate } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldGate';
|
||||
import { InputFieldViewMode } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewMode';
|
||||
import { useElement } from 'features/nodes/store/workflowSlice';
|
||||
import { FormElementEditModeWrapper } from 'features/nodes/components/sidePanel/builder/FormElementEditModeWrapper';
|
||||
import { selectWorkflowFormMode, useElement } from 'features/nodes/store/workflowSlice';
|
||||
import type { NodeFieldElement } from 'features/nodes/types/workflow';
|
||||
import { isNodeFieldElement, NODE_FIELD_CLASS_NAME } from 'features/nodes/types/workflow';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const NodeFieldElementComponent = memo(({ id }: { id: string }) => {
|
||||
const el = useElement(id);
|
||||
const mode = useAppSelector(selectWorkflowFormMode);
|
||||
|
||||
if (!el || !isNodeFieldElement(el)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { fieldIdentifier } = el.data;
|
||||
if (mode === 'view') {
|
||||
return <NodeFieldElementComponentViewMode el={el} />;
|
||||
}
|
||||
|
||||
// mode === 'edit'
|
||||
return <NodeFieldElementComponentEditMode el={el} />;
|
||||
});
|
||||
|
||||
NodeFieldElementComponent.displayName = 'NodeFieldElementComponent';
|
||||
|
||||
export const NodeFieldElementComponentViewMode = memo(({ el }: { el: NodeFieldElement }) => {
|
||||
const { id, data } = el;
|
||||
const { fieldIdentifier } = data;
|
||||
|
||||
return (
|
||||
<Flex id={id} className={NODE_FIELD_CLASS_NAME}>
|
||||
@@ -23,4 +39,21 @@ export const NodeFieldElementComponent = memo(({ id }: { id: string }) => {
|
||||
);
|
||||
});
|
||||
|
||||
NodeFieldElementComponent.displayName = 'NodeFieldElementComponent';
|
||||
NodeFieldElementComponentViewMode.displayName = 'NodeFieldElementComponentViewMode';
|
||||
|
||||
export const NodeFieldElementComponentEditMode = memo(({ el }: { el: NodeFieldElement }) => {
|
||||
const { id, data } = el;
|
||||
const { fieldIdentifier } = data;
|
||||
|
||||
return (
|
||||
<FormElementEditModeWrapper element={el}>
|
||||
<Flex id={id} className={NODE_FIELD_CLASS_NAME} w='full'>
|
||||
<InputFieldGate nodeId={fieldIdentifier.nodeId} fieldName={fieldIdentifier.fieldName}>
|
||||
<InputFieldViewMode nodeId={fieldIdentifier.nodeId} fieldName={fieldIdentifier.fieldName} />
|
||||
</InputFieldGate>
|
||||
</Flex>
|
||||
</FormElementEditModeWrapper>
|
||||
);
|
||||
});
|
||||
|
||||
NodeFieldElementComponentEditMode.displayName = 'NodeFieldElementComponentEditMode';
|
||||
|
||||
@@ -1,16 +1,31 @@
|
||||
import { Flex, Text } from '@invoke-ai/ui-library';
|
||||
import { useElement } from 'features/nodes/store/workflowSlice';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { FormElementEditModeWrapper } from 'features/nodes/components/sidePanel/builder/FormElementEditModeWrapper';
|
||||
import { selectWorkflowFormMode, useElement } from 'features/nodes/store/workflowSlice';
|
||||
import type { TextElement } from 'features/nodes/types/workflow';
|
||||
import { isTextElement, TEXT_CLASS_NAME } from 'features/nodes/types/workflow';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const TextElementComponent = memo(({ id }: { id: string }) => {
|
||||
const el = useElement(id);
|
||||
const mode = useAppSelector(selectWorkflowFormMode);
|
||||
|
||||
if (!el || !isTextElement(el)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { content, fontSize } = el.data;
|
||||
if (mode === 'view') {
|
||||
return <TextElementComponentViewMode el={el} />;
|
||||
}
|
||||
|
||||
// mode === 'edit'
|
||||
return <TextElementComponentEditMode el={el} />;
|
||||
});
|
||||
TextElementComponent.displayName = 'TextElementComponent';
|
||||
|
||||
export const TextElementComponentViewMode = memo(({ el }: { el: TextElement }) => {
|
||||
const { id, data } = el;
|
||||
const { content, fontSize } = data;
|
||||
|
||||
return (
|
||||
<Flex id={id} className={TEXT_CLASS_NAME}>
|
||||
@@ -18,5 +33,18 @@ export const TextElementComponent = memo(({ id }: { id: string }) => {
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
TextElementComponentViewMode.displayName = 'TextElementComponentViewMode';
|
||||
|
||||
TextElementComponent.displayName = 'TextElementComponent';
|
||||
export const TextElementComponentEditMode = memo(({ el }: { el: TextElement }) => {
|
||||
const { id, data } = el;
|
||||
const { content, fontSize } = data;
|
||||
|
||||
return (
|
||||
<Flex id={id} className={TEXT_CLASS_NAME}>
|
||||
<FormElementEditModeWrapper element={el}>
|
||||
<Text fontSize={fontSize}>{content}</Text>
|
||||
</FormElementEditModeWrapper>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
TextElementComponentEditMode.displayName = 'TextElementComponentEditMode';
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { Button, Flex } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||
import { FormElementComponent } from 'features/nodes/components/sidePanel/builder/ContainerElementComponent';
|
||||
import { formLoaded } from 'features/nodes/store/workflowSlice';
|
||||
import { formLoaded, formModeToggled, selectWorkflowFormMode } from 'features/nodes/store/workflowSlice';
|
||||
import { elements, rootElementId } from 'features/nodes/types/workflow';
|
||||
import { memo, useEffect } from 'react';
|
||||
import { memo, useCallback, useEffect } from 'react';
|
||||
|
||||
export const WorkflowBuilder = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const mode = useAppSelector(selectWorkflowFormMode);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(formLoaded({ elements, rootElementId }));
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<ScrollableContent>
|
||||
<Flex w="full" justifyContent="center">
|
||||
<Flex w="full" maxW={512}>
|
||||
<Flex flexDir="column" w={mode === 'view' ? '768px' : 'min-content'} minW='768px'>
|
||||
<ToggleModeButton />
|
||||
{rootElementId && <FormElementComponent id={rootElementId} />}
|
||||
</Flex>
|
||||
</Flex>
|
||||
@@ -23,3 +26,15 @@ export const WorkflowBuilder = memo(() => {
|
||||
});
|
||||
|
||||
WorkflowBuilder.displayName = 'WorkflowBuilder';
|
||||
|
||||
const ToggleModeButton = memo(() => {
|
||||
const dispatch = useAppDispatch();
|
||||
const mode = useAppSelector(selectWorkflowFormMode);
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(formModeToggled());
|
||||
}, [dispatch]);
|
||||
|
||||
return <Button onClick={onClick}>{mode === 'view' ? 'Edit' : 'View'}</Button>;
|
||||
});
|
||||
ToggleModeButton.displayName = 'ToggleModeButton';
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { ContainerElement } from 'features/nodes/types/workflow';
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const ContainerContext = createContext<ContainerElement['data'] | null>(null);
|
||||
export const DepthContext = createContext<number>(0);
|
||||
@@ -13,7 +13,7 @@ const WorkflowFieldsLinearViewPanel = () => {
|
||||
<Flex layerStyle="first" flexDir="column" w="full" h="full" borderRadius="base" p={2} gap={2}>
|
||||
<Tabs variant="line" display="flex" w="full" h="full" flexDir="column">
|
||||
<TabList>
|
||||
<Tab>Builder</Tab>
|
||||
<Tab>{t('common.builder')}</Tab>
|
||||
<Tab>{t('common.linear')}</Tab>
|
||||
<Tab>{t('common.details')}</Tab>
|
||||
<Tab>JSON</Tab>
|
||||
|
||||
Reference in New Issue
Block a user