feat(ui): use new ui_order to sort fields; connection-only fields in grid

This commit is contained in:
psychedelicious 2023-08-22 16:24:19 +10:00
parent cd73085eb9
commit 38b2dedc1d
9 changed files with 119 additions and 29 deletions

View File

@ -1,12 +1,14 @@
import { Flex } from '@chakra-ui/react';
import { Flex, Grid, GridItem } from '@chakra-ui/react';
import { memo } from 'react';
import InvocationNodeFooter from './InvocationNodeFooter';
import InvocationNodeHeader from './InvocationNodeHeader';
import NodeWrapper from '../common/NodeWrapper';
import OutputField from './fields/OutputField';
import InputField from './fields/InputField';
import { useFieldNames } from 'features/nodes/hooks/useFieldNames';
import { useOutputFieldNames } from 'features/nodes/hooks/useOutputFieldNames';
import { useWithFooter } from 'features/nodes/hooks/useWithFooter';
import { useConnectionInputFieldNames } from 'features/nodes/hooks/useConnectionInputFieldNames';
import { useAnyOrDirectInputFieldNames } from 'features/nodes/hooks/useAnyOrDirectInputFieldNames';
type Props = {
nodeId: string;
@ -17,8 +19,9 @@ type Props = {
};
const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
const inputFieldNames = useFieldNames(nodeId, 'input');
const outputFieldNames = useFieldNames(nodeId, 'output');
const inputConnectionFieldNames = useConnectionInputFieldNames(nodeId);
const inputAnyOrDirectFieldNames = useAnyOrDirectInputFieldNames(nodeId);
const outputFieldNames = useOutputFieldNames(nodeId);
const withFooter = useWithFooter(nodeId);
return (
@ -44,14 +47,27 @@ const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
}}
>
<Flex sx={{ flexDir: 'column', px: 2, w: 'full', h: 'full' }}>
{outputFieldNames.map((fieldName) => (
<OutputField
key={`${nodeId}.${fieldName}.output-field`}
nodeId={nodeId}
fieldName={fieldName}
/>
))}
{inputFieldNames.map((fieldName) => (
<Grid gridTemplateColumns="1fr auto" gridAutoRows="1fr">
{inputConnectionFieldNames.map((fieldName, i) => (
<GridItem
gridColumnStart={1}
gridRowStart={i + 1}
key={`${nodeId}.${fieldName}.input-field`}
>
<InputField nodeId={nodeId} fieldName={fieldName} />
</GridItem>
))}
{outputFieldNames.map((fieldName, i) => (
<GridItem
gridColumnStart={2}
gridRowStart={i + 1}
key={`${nodeId}.${fieldName}.output-field`}
>
<OutputField nodeId={nodeId} fieldName={fieldName} />
</GridItem>
))}
</Grid>
{inputAnyOrDirectFieldNames.map((fieldName) => (
<InputField
key={`${nodeId}.${fieldName}.input-field`}
nodeId={nodeId}

View File

@ -31,7 +31,7 @@ const FieldTitle = forwardRef((props: Props, ref) => {
const handleSubmit = useCallback(
async (newTitle: string) => {
if (newTitle === label || newTitle === fieldTemplateTitle) {
if (newTitle && (newTitle === label || newTitle === fieldTemplateTitle)) {
return;
}
setLocalTitle(newTitle || fieldTemplateTitle || 'Unknown Field');

View File

@ -1,10 +1,4 @@
import {
Flex,
FormControl,
FormLabel,
Spacer,
Tooltip,
} from '@chakra-ui/react';
import { Flex, FormControl, FormLabel, Tooltip } from '@chakra-ui/react';
import { useConnectionState } from 'features/nodes/hooks/useConnectionState';
import { useFieldTemplate } from 'features/nodes/hooks/useFieldTemplate';
import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
@ -42,7 +36,6 @@ const OutputField = ({ nodeId, fieldName }: Props) => {
return (
<OutputFieldWrapper shouldDim={shouldDim}>
<Spacer />
<Tooltip
label={
<FieldTooltipContent
@ -90,6 +83,7 @@ const OutputFieldWrapper = memo(
opacity: shouldDim ? 0.5 : 1,
transitionProperty: 'opacity',
transitionDuration: '0.1s',
justifyContent: 'flex-end',
}}
>
{children}

View File

@ -0,0 +1,36 @@
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { map } from 'lodash-es';
import { useMemo } from 'react';
import { isInvocationNode } from '../types/types';
export const useAnyOrDirectInputFieldNames = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return [];
}
const nodeTemplate = nodes.nodeTemplates[node.data.type];
if (!nodeTemplate) {
return [];
}
return map(nodeTemplate.inputs)
.filter((field) => ['any', 'direct'].includes(field.input))
.sort((a, b) => (a.ui_order ?? 0) - (b.ui_order ?? 0))
.map((field) => field.name)
.filter((fieldName) => fieldName !== 'is_intermediate');
},
defaultSelectorOptions
),
[nodeId]
);
const fieldNames = useAppSelector(selector);
return fieldNames;
};

View File

@ -0,0 +1,36 @@
import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { map } from 'lodash-es';
import { useMemo } from 'react';
import { isInvocationNode } from '../types/types';
export const useConnectionInputFieldNames = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
stateSelector,
({ nodes }) => {
const node = nodes.nodes.find((node) => node.id === nodeId);
if (!isInvocationNode(node)) {
return [];
}
const nodeTemplate = nodes.nodeTemplates[node.data.type];
if (!nodeTemplate) {
return [];
}
return map(nodeTemplate.inputs)
.filter((field) => field.input === 'connection')
.sort((a, b) => (a.ui_order ?? 0) - (b.ui_order ?? 0))
.map((field) => field.name)
.filter((fieldName) => fieldName !== 'is_intermediate');
},
defaultSelectorOptions
),
[nodeId]
);
const fieldNames = useAppSelector(selector);
return fieldNames;
};

View File

@ -4,10 +4,9 @@ import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { map } from 'lodash-es';
import { useMemo } from 'react';
import { KIND_MAP } from '../types/constants';
import { isInvocationNode } from '../types/types';
export const useFieldNames = (nodeId: string, kind: 'input' | 'output') => {
export const useOutputFieldNames = (nodeId: string) => {
const selector = useMemo(
() =>
createSelector(
@ -17,13 +16,18 @@ export const useFieldNames = (nodeId: string, kind: 'input' | 'output') => {
if (!isInvocationNode(node)) {
return [];
}
return map(node.data[KIND_MAP[kind]], (field) => field.name).filter(
(fieldName) => fieldName !== 'is_intermediate'
);
const nodeTemplate = nodes.nodeTemplates[node.data.type];
if (!nodeTemplate) {
return [];
}
return map(nodeTemplate.outputs)
.sort((a, b) => (a.ui_order ?? 0) - (b.ui_order ?? 0))
.map((field) => field.name)
.filter((fieldName) => fieldName !== 'is_intermediate');
},
defaultSelectorOptions
),
[kind, nodeId]
[nodeId]
);
const fieldNames = useAppSelector(selector);

View File

@ -203,7 +203,7 @@ export type OutputFieldTemplate = {
type: FieldType;
title: string;
description: string;
};
} & _OutputField;
/**
* Indicates the kind of input(s) this field may have.

View File

@ -467,7 +467,7 @@ export const buildInputFieldTemplate = (
const fieldType = getFieldType(fieldSchema);
// console.log('input fieldType', fieldType);
const { input, ui_hidden, ui_component, ui_type } = fieldSchema;
const { input, ui_hidden, ui_component, ui_type, ui_order } = fieldSchema;
const extra = {
input,
@ -475,6 +475,7 @@ export const buildInputFieldTemplate = (
ui_component,
ui_type,
required: nodeSchema.required?.includes(name) ?? false,
ui_order,
};
const baseField = {

View File

@ -142,6 +142,9 @@ export const parseSchema = (
title: property.title ?? '',
description: property.description ?? '',
type: fieldType,
ui_hidden: property.ui_hidden ?? false,
ui_type: property.ui_type,
ui_order: property.ui_order,
};
return outputsAccumulator;