mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): node styling, controls
- custom node controls - fix some types - fix badge colors via colorScheme - style nodes
This commit is contained in:
parent
94a07a8da7
commit
44a653925a
@ -10,7 +10,7 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
IconButton,
|
IconButton,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { FaPlus } from 'react-icons/fa';
|
import { FaEllipsisV, FaPlus } from 'react-icons/fa';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
import { nodeAdded } from '../store/nodesSlice';
|
import { nodeAdded } from '../store/nodesSlice';
|
||||||
import { cloneDeep, map } from 'lodash';
|
import { cloneDeep, map } from 'lodash';
|
||||||
@ -18,6 +18,8 @@ import { RootState } from 'app/store';
|
|||||||
import { useBuildInvocation } from '../hooks/useBuildInvocation';
|
import { useBuildInvocation } from '../hooks/useBuildInvocation';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { makeToast } from 'features/system/hooks/useToastWatcher';
|
import { makeToast } from 'features/system/hooks/useToastWatcher';
|
||||||
|
import { IAIIconButton } from 'exports';
|
||||||
|
import { AnyInvocationType } from 'services/events/types';
|
||||||
|
|
||||||
export const AddNodeMenu = () => {
|
export const AddNodeMenu = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -29,7 +31,7 @@ export const AddNodeMenu = () => {
|
|||||||
const buildInvocation = useBuildInvocation();
|
const buildInvocation = useBuildInvocation();
|
||||||
|
|
||||||
const addNode = useCallback(
|
const addNode = useCallback(
|
||||||
(nodeType: string) => {
|
(nodeType: AnyInvocationType) => {
|
||||||
const invocation = buildInvocation(nodeType);
|
const invocation = buildInvocation(nodeType);
|
||||||
|
|
||||||
if (!invocation) {
|
if (!invocation) {
|
||||||
@ -48,7 +50,11 @@ export const AddNodeMenu = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton as={IconButton} aria-label="Add Node" icon={<FaPlus />} />
|
<MenuButton
|
||||||
|
as={IAIIconButton}
|
||||||
|
aria-label="Add Node"
|
||||||
|
icon={<FaEllipsisV />}
|
||||||
|
/>
|
||||||
<MenuList overflowY="scroll" height={400}>
|
<MenuList overflowY="scroll" height={400}>
|
||||||
{map(invocationTemplates, ({ title, description, type }, key) => {
|
{map(invocationTemplates, ({ title, description, type }, key) => {
|
||||||
return (
|
return (
|
||||||
|
@ -19,11 +19,11 @@ const handleBaseStyles: CSSProperties = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const inputHandleStyles: CSSProperties = {
|
const inputHandleStyles: CSSProperties = {
|
||||||
left: '-1.6rem',
|
left: '-1rem',
|
||||||
};
|
};
|
||||||
|
|
||||||
const outputHandleStyles: CSSProperties = {
|
const outputHandleStyles: CSSProperties = {
|
||||||
right: '-0.6rem',
|
right: '-0.5rem',
|
||||||
};
|
};
|
||||||
|
|
||||||
const requiredConnectionStyles: CSSProperties = {
|
const requiredConnectionStyles: CSSProperties = {
|
||||||
|
@ -20,12 +20,16 @@ import {
|
|||||||
edgesChanged,
|
edgesChanged,
|
||||||
nodesChanged,
|
nodesChanged,
|
||||||
} from '../store/nodesSlice';
|
} from '../store/nodesSlice';
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { InvocationComponent } from './InvocationComponent';
|
import { InvocationComponent } from './InvocationComponent';
|
||||||
import { AddNodeMenu } from './AddNodeMenu';
|
import { AddNodeMenu } from './AddNodeMenu';
|
||||||
import { FieldTypeLegend } from './FieldTypeLegend';
|
import { FieldTypeLegend } from './FieldTypeLegend';
|
||||||
import { Button } from '@chakra-ui/react';
|
import { Button } from '@chakra-ui/react';
|
||||||
import { nodesGraphBuilt } from 'services/thunks/session';
|
import { nodesGraphBuilt } from 'services/thunks/session';
|
||||||
|
import { IAIIconButton } from 'exports';
|
||||||
|
import { InfoIcon } from '@chakra-ui/icons';
|
||||||
|
import { ViewportControls } from './ViewportControls';
|
||||||
|
import NodeGraphOverlay from './NodeGraphOverlay';
|
||||||
|
|
||||||
const nodeTypes = { invocation: InvocationComponent };
|
const nodeTypes = { invocation: InvocationComponent };
|
||||||
|
|
||||||
@ -33,6 +37,9 @@ export const Flow = () => {
|
|||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const nodes = useAppSelector((state: RootState) => state.nodes.nodes);
|
const nodes = useAppSelector((state: RootState) => state.nodes.nodes);
|
||||||
const edges = useAppSelector((state: RootState) => state.nodes.edges);
|
const edges = useAppSelector((state: RootState) => state.nodes.edges);
|
||||||
|
const shouldShowGraphOverlay = useAppSelector(
|
||||||
|
(state: RootState) => state.nodes.shouldShowGraphOverlay
|
||||||
|
);
|
||||||
|
|
||||||
const onNodesChange: OnNodesChange = useCallback(
|
const onNodesChange: OnNodesChange = useCallback(
|
||||||
(changes) => {
|
(changes) => {
|
||||||
@ -95,9 +102,12 @@ export const Flow = () => {
|
|||||||
</Panel>
|
</Panel>
|
||||||
<Panel position="top-right">
|
<Panel position="top-right">
|
||||||
<FieldTypeLegend />
|
<FieldTypeLegend />
|
||||||
|
{shouldShowGraphOverlay && <NodeGraphOverlay />}
|
||||||
|
</Panel>
|
||||||
|
<Panel position="bottom-left">
|
||||||
|
<ViewportControls />
|
||||||
</Panel>
|
</Panel>
|
||||||
<Background />
|
<Background />
|
||||||
<Controls />
|
|
||||||
<MiniMap nodeStrokeWidth={3} zoomable pannable />
|
<MiniMap nodeStrokeWidth={3} zoomable pannable />
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
);
|
);
|
||||||
|
@ -12,7 +12,7 @@ export default function IAINodeHeader(props: IAINodeHeaderProps) {
|
|||||||
const { nodeId, template } = props;
|
const { nodeId, template } = props;
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
borderRadius="sm"
|
borderTopRadius="md"
|
||||||
justifyContent="space-between"
|
justifyContent="space-between"
|
||||||
background="base.700"
|
background="base.700"
|
||||||
px={2}
|
px={2}
|
||||||
@ -20,7 +20,7 @@ export default function IAINodeHeader(props: IAINodeHeaderProps) {
|
|||||||
alignItems="center"
|
alignItems="center"
|
||||||
>
|
>
|
||||||
<Tooltip label={nodeId}>
|
<Tooltip label={nodeId}>
|
||||||
<Heading size="sm" fontWeight={600} color="base.100">
|
<Heading size="xs" fontWeight={600} color="base.100">
|
||||||
{template.current?.title}
|
{template.current?.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -30,7 +30,7 @@ export default function IAINodeHeader(props: IAINodeHeaderProps) {
|
|||||||
hasArrow
|
hasArrow
|
||||||
shouldWrapChildren
|
shouldWrapChildren
|
||||||
>
|
>
|
||||||
<Icon color="base.300" as={FaInfoCircle} />
|
<Icon color="base.300" as={FaInfoCircle} h="min-content" />
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -15,11 +15,13 @@ import {
|
|||||||
HStack,
|
HStack,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Icon,
|
Icon,
|
||||||
|
Divider,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { FieldHandle } from '../FieldHandle';
|
import { FieldHandle } from '../FieldHandle';
|
||||||
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
|
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
|
||||||
import { InputFieldComponent } from '../InputFieldComponent';
|
import { InputFieldComponent } from '../InputFieldComponent';
|
||||||
import { FaInfoCircle } from 'react-icons/fa';
|
import { FaInfoCircle } from 'react-icons/fa';
|
||||||
|
import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
|
||||||
|
|
||||||
interface IAINodeInputProps {
|
interface IAINodeInputProps {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
@ -35,13 +37,8 @@ function IAINodeInput(props: IAINodeInputProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
p={2}
|
|
||||||
key={input.id}
|
key={input.id}
|
||||||
position="relative"
|
position="relative"
|
||||||
borderWidth={1}
|
|
||||||
borderRadius="md"
|
|
||||||
borderLeft="none"
|
|
||||||
borderRight="none"
|
|
||||||
borderColor={
|
borderColor={
|
||||||
!template
|
!template
|
||||||
? 'error.400'
|
? 'error.400'
|
||||||
@ -63,14 +60,14 @@ function IAINodeInput(props: IAINodeInputProps) {
|
|||||||
<>
|
<>
|
||||||
<HStack justifyContent="space-between" alignItems="center">
|
<HStack justifyContent="space-between" alignItems="center">
|
||||||
<HStack>
|
<HStack>
|
||||||
<FormLabel>{template?.title}</FormLabel>
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={template?.description}
|
label={template?.description}
|
||||||
placement="top"
|
placement="top"
|
||||||
hasArrow
|
hasArrow
|
||||||
shouldWrapChildren
|
shouldWrapChildren
|
||||||
|
openDelay={HANDLE_TOOLTIP_OPEN_DELAY}
|
||||||
>
|
>
|
||||||
<Icon color="base.400" as={FaInfoCircle} />
|
<FormLabel>{template?.title}</FormLabel>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</HStack>
|
</HStack>
|
||||||
<InputFieldComponent
|
<InputFieldComponent
|
||||||
@ -114,7 +111,7 @@ export default function IAINodeInputs(props: IAINodeInputsProps) {
|
|||||||
const IAINodeInputsToRender: ReactNode[] = [];
|
const IAINodeInputsToRender: ReactNode[] = [];
|
||||||
const inputSockets = map(inputs);
|
const inputSockets = map(inputs);
|
||||||
|
|
||||||
inputSockets.forEach((inputSocket) => {
|
inputSockets.forEach((inputSocket, index) => {
|
||||||
const inputTemplate = template.current?.inputs[inputSocket.name];
|
const inputTemplate = template.current?.inputs[inputSocket.name];
|
||||||
|
|
||||||
const isConnected = Boolean(
|
const isConnected = Boolean(
|
||||||
@ -126,6 +123,10 @@ export default function IAINodeInputs(props: IAINodeInputsProps) {
|
|||||||
}).length
|
}).length
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (index < inputSockets.length) {
|
||||||
|
IAINodeInputsToRender.push(<Divider />);
|
||||||
|
}
|
||||||
|
|
||||||
IAINodeInputsToRender.push(
|
IAINodeInputsToRender.push(
|
||||||
<IAINodeInput
|
<IAINodeInput
|
||||||
nodeId={nodeId}
|
nodeId={nodeId}
|
||||||
|
@ -5,12 +5,13 @@ export default function IAINodeResizer(props: NodeResizerProps) {
|
|||||||
return (
|
return (
|
||||||
<NodeResizeControl
|
<NodeResizeControl
|
||||||
style={{
|
style={{
|
||||||
position: 'relative',
|
position: 'absolute',
|
||||||
border: 'none',
|
border: 'none',
|
||||||
background: 'none',
|
background: 'transparent',
|
||||||
width: 10,
|
width: 15,
|
||||||
height: 10,
|
height: 15,
|
||||||
top: 10,
|
bottom: 0,
|
||||||
|
right: 0,
|
||||||
}}
|
}}
|
||||||
minWidth={350}
|
minWidth={350}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Box } from '@chakra-ui/react';
|
import { Box } from '@chakra-ui/react';
|
||||||
import { InputFieldTemplate, InputFieldValue } from '../types/types';
|
import { InputFieldTemplate, InputFieldValue } from '../types/types';
|
||||||
import { ArrayInputFieldComponent } from './fields/ArrayInputField.tsx';
|
import { ArrayInputFieldComponent } from './fields/ArrayInputFieldComponent';
|
||||||
import { BooleanInputFieldComponent } from './fields/BooleanInputFieldComponent';
|
import { BooleanInputFieldComponent } from './fields/BooleanInputFieldComponent';
|
||||||
import { EnumInputFieldComponent } from './fields/EnumInputFieldComponent';
|
import { EnumInputFieldComponent } from './fields/EnumInputFieldComponent';
|
||||||
import { ImageInputFieldComponent } from './fields/ImageInputFieldComponent';
|
import { ImageInputFieldComponent } from './fields/ImageInputFieldComponent';
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { NodeProps, NodeResizeControl } from 'reactflow';
|
import { NodeProps, NodeResizeControl } from 'reactflow';
|
||||||
import { Box, Flex, Icon } from '@chakra-ui/react';
|
import { Box, Flex, Icon, useToken } from '@chakra-ui/react';
|
||||||
import { FaExclamationCircle } from 'react-icons/fa';
|
import { FaExclamationCircle } from 'react-icons/fa';
|
||||||
import { InvocationValue } from '../types/types';
|
import { InvocationValue } from '../types/types';
|
||||||
|
|
||||||
import { memo, useRef } from 'react';
|
import { memo, PropsWithChildren, useRef } from 'react';
|
||||||
import { useGetInvocationTemplate } from '../hooks/useInvocationTemplate';
|
import { useGetInvocationTemplate } from '../hooks/useInvocationTemplate';
|
||||||
import IAINodeOutputs from './IAINode/IAINodeOutputs';
|
import IAINodeOutputs from './IAINode/IAINodeOutputs';
|
||||||
import IAINodeInputs from './IAINode/IAINodeInputs';
|
import IAINodeInputs from './IAINode/IAINodeInputs';
|
||||||
@ -11,6 +11,31 @@ import IAINodeHeader from './IAINode/IAINodeHeader';
|
|||||||
import { IoResize } from 'react-icons/io5';
|
import { IoResize } from 'react-icons/io5';
|
||||||
import IAINodeResizer from './IAINode/IAINodeResizer';
|
import IAINodeResizer from './IAINode/IAINodeResizer';
|
||||||
|
|
||||||
|
type InvocationComponentWrapperProps = PropsWithChildren & {
|
||||||
|
selected: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const InvocationComponentWrapper = (props: InvocationComponentWrapperProps) => {
|
||||||
|
const [nodeSelectedOutline, nodeShadow] = useToken('shadows', [
|
||||||
|
'nodeSelectedOutline',
|
||||||
|
'dark-lg',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'relative',
|
||||||
|
borderRadius: 'md',
|
||||||
|
boxShadow: props.selected
|
||||||
|
? `${nodeSelectedOutline}, ${nodeShadow}`
|
||||||
|
: `${nodeShadow}`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
||||||
const { id: nodeId, data, selected } = props;
|
const { id: nodeId, data, selected } = props;
|
||||||
const { type, inputs, outputs } = data;
|
const { type, inputs, outputs } = data;
|
||||||
@ -22,41 +47,31 @@ export const InvocationComponent = memo((props: NodeProps<InvocationValue>) => {
|
|||||||
|
|
||||||
if (!template.current) {
|
if (!template.current) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<InvocationComponentWrapper selected={selected}>
|
||||||
sx={{
|
|
||||||
padding: 4,
|
|
||||||
bg: 'base.800',
|
|
||||||
borderRadius: 'md',
|
|
||||||
boxShadow: 'dark-lg',
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: selected ? 'base.400' : 'transparent',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex sx={{ alignItems: 'center', justifyContent: 'center' }}>
|
<Flex sx={{ alignItems: 'center', justifyContent: 'center' }}>
|
||||||
<Icon color="base.400" boxSize={32} as={FaExclamationCircle}></Icon>
|
<Icon color="base.400" boxSize={32} as={FaExclamationCircle}></Icon>
|
||||||
<IAINodeResizer />
|
<IAINodeResizer />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
</InvocationComponentWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<InvocationComponentWrapper selected={selected}>
|
||||||
|
<IAINodeHeader nodeId={nodeId} template={template} />
|
||||||
|
<Flex
|
||||||
sx={{
|
sx={{
|
||||||
|
flexDirection: 'column',
|
||||||
|
borderBottomRadius: 'md',
|
||||||
bg: 'base.800',
|
bg: 'base.800',
|
||||||
borderRadius: 'md',
|
py: 2,
|
||||||
boxShadow: 'dark-lg',
|
|
||||||
borderWidth: 2,
|
|
||||||
borderColor: selected ? 'base.400' : 'transparent',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Flex flexDirection="column" gap={2}>
|
|
||||||
<IAINodeHeader nodeId={nodeId} template={template} />
|
|
||||||
<IAINodeOutputs nodeId={nodeId} outputs={outputs} template={template} />
|
<IAINodeOutputs nodeId={nodeId} outputs={outputs} template={template} />
|
||||||
<IAINodeInputs nodeId={nodeId} inputs={inputs} template={template} />
|
<IAINodeInputs nodeId={nodeId} inputs={inputs} template={template} />
|
||||||
<IAINodeResizer />
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</Box>
|
<IAINodeResizer />
|
||||||
|
</InvocationComponentWrapper>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,15 +3,8 @@ import { Box } from '@chakra-ui/react';
|
|||||||
import { ReactFlowProvider } from 'reactflow';
|
import { ReactFlowProvider } from 'reactflow';
|
||||||
|
|
||||||
import { Flow } from './Flow';
|
import { Flow } from './Flow';
|
||||||
import { useAppSelector } from 'app/storeHooks';
|
|
||||||
import { RootState } from 'app/store';
|
|
||||||
import { buildNodesGraph } from '../util/nodesGraphBuilder/buildNodesGraph';
|
|
||||||
|
|
||||||
const NodeEditor = () => {
|
const NodeEditor = () => {
|
||||||
const state = useAppSelector((state: RootState) => state);
|
|
||||||
|
|
||||||
const graph = buildNodesGraph(state);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -25,20 +18,6 @@ const NodeEditor = () => {
|
|||||||
<ReactFlowProvider>
|
<ReactFlowProvider>
|
||||||
<Flow />
|
<Flow />
|
||||||
</ReactFlowProvider>
|
</ReactFlowProvider>
|
||||||
<Box
|
|
||||||
as="pre"
|
|
||||||
fontFamily="monospace"
|
|
||||||
position="absolute"
|
|
||||||
top={2}
|
|
||||||
left={2}
|
|
||||||
width="full"
|
|
||||||
height="full"
|
|
||||||
userSelect="none"
|
|
||||||
pointerEvents="none"
|
|
||||||
opacity={0.7}
|
|
||||||
>
|
|
||||||
<Box w="50%">{JSON.stringify(graph, null, 2)}</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
import { RootState } from 'app/store';
|
||||||
|
import { useAppSelector } from 'app/storeHooks';
|
||||||
|
import { buildNodesGraph } from '../util/nodesGraphBuilder/buildNodesGraph';
|
||||||
|
|
||||||
|
export default function NodeGraphOverlay() {
|
||||||
|
const state = useAppSelector((state: RootState) => state);
|
||||||
|
const graph = buildNodesGraph(state);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
as="pre"
|
||||||
|
fontFamily="monospace"
|
||||||
|
position="absolute"
|
||||||
|
top={10}
|
||||||
|
right={2}
|
||||||
|
userSelect="none"
|
||||||
|
opacity={0.7}
|
||||||
|
background="base.800"
|
||||||
|
p={2}
|
||||||
|
maxHeight={500}
|
||||||
|
overflowY="scroll"
|
||||||
|
borderRadius="md"
|
||||||
|
>
|
||||||
|
{JSON.stringify(graph, null, 2)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
import { ButtonGroup } from '@chakra-ui/react';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||||
|
import { IAIIconButton } from 'exports';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { FaCode, FaExpand, FaMinus, FaPlus } from 'react-icons/fa';
|
||||||
|
import { useReactFlow } from 'reactflow';
|
||||||
|
import { shouldShowGraphOverlayChanged } from '../store/nodesSlice';
|
||||||
|
|
||||||
|
export const ViewportControls = () => {
|
||||||
|
const { zoomIn, zoomOut, fitView } = useReactFlow();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const shouldShowGraphOverlay = useAppSelector(
|
||||||
|
(state) => state.nodes.shouldShowGraphOverlay
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClickedZoomIn = useCallback(() => {
|
||||||
|
zoomIn();
|
||||||
|
}, [zoomIn]);
|
||||||
|
|
||||||
|
const handleClickedZoomOut = useCallback(() => {
|
||||||
|
zoomOut();
|
||||||
|
}, [zoomOut]);
|
||||||
|
|
||||||
|
const handleClickedFitView = useCallback(() => {
|
||||||
|
fitView();
|
||||||
|
}, [fitView]);
|
||||||
|
|
||||||
|
const handleClickedToggleGraphOverlay = useCallback(() => {
|
||||||
|
dispatch(shouldShowGraphOverlayChanged(!shouldShowGraphOverlay));
|
||||||
|
}, [shouldShowGraphOverlay, dispatch]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ButtonGroup isAttached orientation="vertical">
|
||||||
|
<IAIIconButton
|
||||||
|
onClick={handleClickedZoomIn}
|
||||||
|
aria-label="Zoom In"
|
||||||
|
icon={<FaPlus />}
|
||||||
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
onClick={handleClickedZoomOut}
|
||||||
|
aria-label="Zoom Out"
|
||||||
|
icon={<FaMinus />}
|
||||||
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
onClick={handleClickedFitView}
|
||||||
|
aria-label="Fit to Viewport"
|
||||||
|
icon={<FaExpand />}
|
||||||
|
/>
|
||||||
|
<IAIIconButton
|
||||||
|
isChecked={shouldShowGraphOverlay}
|
||||||
|
onClick={handleClickedToggleGraphOverlay}
|
||||||
|
aria-label="Show/Hide Graph"
|
||||||
|
icon={<FaCode />}
|
||||||
|
/>
|
||||||
|
</ButtonGroup>
|
||||||
|
);
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
ArrayInputFieldTemplate,
|
ArrayInputFieldTemplate,
|
||||||
ArrayInputFieldValue,
|
ArrayInputFieldValue,
|
||||||
} from 'features/nodes/types';
|
} from 'features/nodes/types/types';
|
||||||
import { FaImage, FaList } from 'react-icons/fa';
|
import { FaImage, FaList } from 'react-icons/fa';
|
||||||
import { FieldComponentProps } from './types';
|
import { FieldComponentProps } from './types';
|
||||||
|
|
@ -4,7 +4,7 @@ import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
|
|||||||
import {
|
import {
|
||||||
BooleanInputFieldTemplate,
|
BooleanInputFieldTemplate,
|
||||||
BooleanInputFieldValue,
|
BooleanInputFieldValue,
|
||||||
} from 'features/nodes/types';
|
} from 'features/nodes/types/types';
|
||||||
import { ChangeEvent } from 'react';
|
import { ChangeEvent } from 'react';
|
||||||
import { FieldComponentProps } from './types';
|
import { FieldComponentProps } from './types';
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
|
|||||||
import {
|
import {
|
||||||
EnumInputFieldTemplate,
|
EnumInputFieldTemplate,
|
||||||
EnumInputFieldValue,
|
EnumInputFieldValue,
|
||||||
} from 'features/nodes/types';
|
} from 'features/nodes/types/types';
|
||||||
import { ChangeEvent } from 'react';
|
import { ChangeEvent } from 'react';
|
||||||
import { FieldComponentProps } from './types';
|
import { FieldComponentProps } from './types';
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
|
|||||||
import {
|
import {
|
||||||
ImageInputFieldTemplate,
|
ImageInputFieldTemplate,
|
||||||
ImageInputFieldValue,
|
ImageInputFieldValue,
|
||||||
} from 'features/nodes/types';
|
} from 'features/nodes/types/types';
|
||||||
import { DragEvent, useCallback, useState } from 'react';
|
import { DragEvent, useCallback, useState } from 'react';
|
||||||
import { FaImage } from 'react-icons/fa';
|
import { FaImage } from 'react-icons/fa';
|
||||||
import { ImageType } from 'services/api';
|
import { ImageType } from 'services/api';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
LatentsInputFieldTemplate,
|
LatentsInputFieldTemplate,
|
||||||
LatentsInputFieldValue,
|
LatentsInputFieldValue,
|
||||||
} from 'features/nodes/types';
|
} from 'features/nodes/types/types';
|
||||||
import { FieldComponentProps } from './types';
|
import { FieldComponentProps } from './types';
|
||||||
|
|
||||||
export const LatentsInputFieldComponent = (
|
export const LatentsInputFieldComponent = (
|
||||||
|
@ -6,7 +6,7 @@ import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
|
|||||||
import {
|
import {
|
||||||
ModelInputFieldTemplate,
|
ModelInputFieldTemplate,
|
||||||
ModelInputFieldValue,
|
ModelInputFieldValue,
|
||||||
} from 'features/nodes/types';
|
} from 'features/nodes/types/types';
|
||||||
import {
|
import {
|
||||||
selectModelsById,
|
selectModelsById,
|
||||||
selectModelsIds,
|
selectModelsIds,
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
FloatInputFieldValue,
|
FloatInputFieldValue,
|
||||||
IntegerInputFieldTemplate,
|
IntegerInputFieldTemplate,
|
||||||
IntegerInputFieldValue,
|
IntegerInputFieldValue,
|
||||||
} from 'features/nodes/types';
|
} from 'features/nodes/types/types';
|
||||||
import { FieldComponentProps } from './types';
|
import { FieldComponentProps } from './types';
|
||||||
|
|
||||||
export const NumberInputFieldComponent = (
|
export const NumberInputFieldComponent = (
|
||||||
|
@ -4,7 +4,7 @@ import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
|
|||||||
import {
|
import {
|
||||||
StringInputFieldTemplate,
|
StringInputFieldTemplate,
|
||||||
StringInputFieldValue,
|
StringInputFieldValue,
|
||||||
} from 'features/nodes/types';
|
} from 'features/nodes/types/types';
|
||||||
import { ChangeEvent } from 'react';
|
import { ChangeEvent } from 'react';
|
||||||
import { FieldComponentProps } from './types';
|
import { FieldComponentProps } from './types';
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ export type NodesState = {
|
|||||||
invocationTemplates: Record<string, InvocationTemplate>;
|
invocationTemplates: Record<string, InvocationTemplate>;
|
||||||
connectionStartParams: OnConnectStartParams | null;
|
connectionStartParams: OnConnectStartParams | null;
|
||||||
lastGraph: Graph | null;
|
lastGraph: Graph | null;
|
||||||
|
shouldShowGraphOverlay: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialNodesState: NodesState = {
|
export const initialNodesState: NodesState = {
|
||||||
@ -33,6 +34,7 @@ export const initialNodesState: NodesState = {
|
|||||||
invocationTemplates: {},
|
invocationTemplates: {},
|
||||||
connectionStartParams: null,
|
connectionStartParams: null,
|
||||||
lastGraph: null,
|
lastGraph: null,
|
||||||
|
shouldShowGraphOverlay: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const nodesSlice = createSlice({
|
const nodesSlice = createSlice({
|
||||||
@ -77,6 +79,9 @@ const nodesSlice = createSlice({
|
|||||||
state.nodes[nodeIndex].data.inputs[fieldName].value = value;
|
state.nodes[nodeIndex].data.inputs[fieldName].value = value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
shouldShowGraphOverlayChanged: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.shouldShowGraphOverlay = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers(builder) {
|
extraReducers(builder) {
|
||||||
builder.addCase(receivedOpenAPISchema.fulfilled, (state, action) => {
|
builder.addCase(receivedOpenAPISchema.fulfilled, (state, action) => {
|
||||||
@ -98,6 +103,7 @@ export const {
|
|||||||
connectionMade,
|
connectionMade,
|
||||||
connectionStarted,
|
connectionStarted,
|
||||||
connectionEnded,
|
connectionEnded,
|
||||||
|
shouldShowGraphOverlayChanged,
|
||||||
} = nodesSlice.actions;
|
} = nodesSlice.actions;
|
||||||
|
|
||||||
export default nodesSlice.reducer;
|
export default nodesSlice.reducer;
|
||||||
|
@ -22,46 +22,55 @@ const getColorTokenCssVariable = (color: string) =>
|
|||||||
|
|
||||||
export const FIELDS: Record<FieldType, FieldUIConfig> = {
|
export const FIELDS: Record<FieldType, FieldUIConfig> = {
|
||||||
integer: {
|
integer: {
|
||||||
|
color: 'red',
|
||||||
colorCssVar: getColorTokenCssVariable('red'),
|
colorCssVar: getColorTokenCssVariable('red'),
|
||||||
title: 'Integer',
|
title: 'Integer',
|
||||||
description: 'Integers are whole numbers, without a decimal point.',
|
description: 'Integers are whole numbers, without a decimal point.',
|
||||||
},
|
},
|
||||||
float: {
|
float: {
|
||||||
|
color: 'orange',
|
||||||
colorCssVar: getColorTokenCssVariable('orange'),
|
colorCssVar: getColorTokenCssVariable('orange'),
|
||||||
title: 'Float',
|
title: 'Float',
|
||||||
description: 'Floats are numbers with a decimal point.',
|
description: 'Floats are numbers with a decimal point.',
|
||||||
},
|
},
|
||||||
string: {
|
string: {
|
||||||
|
color: 'yellow',
|
||||||
colorCssVar: getColorTokenCssVariable('yellow'),
|
colorCssVar: getColorTokenCssVariable('yellow'),
|
||||||
title: 'String',
|
title: 'String',
|
||||||
description: 'Strings are text.',
|
description: 'Strings are text.',
|
||||||
},
|
},
|
||||||
boolean: {
|
boolean: {
|
||||||
|
color: 'green',
|
||||||
colorCssVar: getColorTokenCssVariable('green'),
|
colorCssVar: getColorTokenCssVariable('green'),
|
||||||
title: 'Boolean',
|
title: 'Boolean',
|
||||||
description: 'Booleans are true or false.',
|
description: 'Booleans are true or false.',
|
||||||
},
|
},
|
||||||
enum: {
|
enum: {
|
||||||
|
color: 'blue',
|
||||||
colorCssVar: getColorTokenCssVariable('blue'),
|
colorCssVar: getColorTokenCssVariable('blue'),
|
||||||
title: 'Enum',
|
title: 'Enum',
|
||||||
description: 'Enums are values that may be one of a number of options.',
|
description: 'Enums are values that may be one of a number of options.',
|
||||||
},
|
},
|
||||||
image: {
|
image: {
|
||||||
|
color: 'purple',
|
||||||
colorCssVar: getColorTokenCssVariable('purple'),
|
colorCssVar: getColorTokenCssVariable('purple'),
|
||||||
title: 'Image',
|
title: 'Image',
|
||||||
description: 'Images may be passed between nodes.',
|
description: 'Images may be passed between nodes.',
|
||||||
},
|
},
|
||||||
latents: {
|
latents: {
|
||||||
|
color: 'pink',
|
||||||
colorCssVar: getColorTokenCssVariable('pink'),
|
colorCssVar: getColorTokenCssVariable('pink'),
|
||||||
title: 'Latents',
|
title: 'Latents',
|
||||||
description: 'Latents may be passed between nodes.',
|
description: 'Latents may be passed between nodes.',
|
||||||
},
|
},
|
||||||
model: {
|
model: {
|
||||||
|
color: 'teal',
|
||||||
colorCssVar: getColorTokenCssVariable('teal'),
|
colorCssVar: getColorTokenCssVariable('teal'),
|
||||||
title: 'Model',
|
title: 'Model',
|
||||||
description: 'Models are models.',
|
description: 'Models are models.',
|
||||||
},
|
},
|
||||||
array: {
|
array: {
|
||||||
|
color: 'gray',
|
||||||
colorCssVar: getColorTokenCssVariable('gray'),
|
colorCssVar: getColorTokenCssVariable('gray'),
|
||||||
title: 'Array',
|
title: 'Array',
|
||||||
description: 'TODO: Array type description.',
|
description: 'TODO: Array type description.',
|
||||||
|
@ -39,6 +39,7 @@ export type InvocationTemplate = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type FieldUIConfig = {
|
export type FieldUIConfig = {
|
||||||
|
color: string;
|
||||||
colorCssVar: string;
|
colorCssVar: string;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
@ -53,6 +53,7 @@ export const theme: ThemeOverride = {
|
|||||||
working: `0 0 7px var(--invokeai-colors-working-400)`,
|
working: `0 0 7px var(--invokeai-colors-working-400)`,
|
||||||
error: `0 0 7px var(--invokeai-colors-error-400)`,
|
error: `0 0 7px var(--invokeai-colors-error-400)`,
|
||||||
},
|
},
|
||||||
|
nodeSelectedOutline: `0 0 0 2px var(--invokeai-colors-base-500)`,
|
||||||
},
|
},
|
||||||
colors: {
|
colors: {
|
||||||
...invokeAIThemeColors,
|
...invokeAIThemeColors,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user