From 22ef5fb18298c69694d50ba0cb17d33f9e9596e7 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Sat, 18 Nov 2023 10:40:19 +1100 Subject: [PATCH] feat(ui): custom field types connection validation In the initial commit, a custom field's original type was added to the *field templates* only as `originalType`. Custom fields' `type` property was `"Custom"`*. This allowed for type safety throughout the UI logic. *Actually, it was `"Unknown"`, but I changed it to custom for clarity. Connection validation logic, however, uses the *field instance* of the node/field. Like the templates, *field instances* with custom types have their `type` set to `"Custom"`, but they didn't have an `originalType` property. As a result, all custom fields could be connected to all other custom fields. To resolve this, we need to add `originalType` to the *field instances*, then switch the validation logic to use this instead of `type`. This ended up needing a bit of fanagling: - If we make `originalType` a required property on field instances, existing workflows will break during connection validation, because they won't have this property. We'd need a new layer of logic to migrate the workflows, adding the new `originalType` property. While this layer is probably needed anyways, typing `originalType` as optional is much simpler. Workflow migration logic can come layer. (Technically, we could remove all references to field types from the workflow files, and let the templates hold all this information. This feels like a significant change and I'm reluctant to do it now.) - Because `originalType` is optional, anywhere we care about the type of a field, we need to use it over `type`. So there are a number of `field.originalType ?? field.type` expressions. This is a bit of a gotcha, we'll need to remember this in the future. - We use `Array.prototype.includes()` often in the workflow editor, e.g. `COLLECTION_TYPES.includes(type)`. In these cases, the const array is of type `FieldType[]`, and `type` is is `FieldType`. Because we now support custom types, the arg `type` is now widened from `FieldType` to `string`. This causes a TS error. This behaviour is somewhat controversial (see https://github.com/microsoft/TypeScript/issues/14520). These expressions are now rewritten as `COLLECTION_TYPES.some((t) => t === type)` to satisfy TS. It's logically equivalent. --- .../flow/AddNodePopover/AddNodePopover.tsx | 10 +++++-- .../connectionLines/CustomConnectionLine.tsx | 9 +++--- .../flow/edges/util/getEdgeColor.ts | 12 ++++++++ .../flow/edges/util/makeEdgeSelector.ts | 4 +-- .../nodes/Invocation/fields/FieldHandle.tsx | 30 +++++++------------ .../features/nodes/hooks/useFieldType.ts.ts | 3 +- .../src/features/nodes/store/nodesSlice.ts | 3 +- .../web/src/features/nodes/store/types.ts | 2 +- .../nodes/store/util/buildNodeData.ts | 1 + .../store/util/findConnectionToValidHandle.ts | 11 +++++-- .../util/makeIsConnectionValidSelector.ts | 4 +-- .../util/validateSourceAndTargetTypes.ts | 22 +++++++------- .../web/src/features/nodes/types/constants.ts | 8 ++--- .../web/src/features/nodes/types/types.ts | 19 ++++++------ .../nodes/util/fieldTemplateBuilders.ts | 12 ++++---- .../features/nodes/util/fieldValueBuilders.ts | 7 ++--- .../src/features/nodes/util/parseSchema.ts | 17 +++++++---- 17 files changed, 98 insertions(+), 76 deletions(-) create mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/edges/util/getEdgeColor.ts diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx index 5ddd1d4ece6..b514474a260 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/AddNodePopover/AddNodePopover.tsx @@ -74,9 +74,13 @@ const AddNodePopover = () => { return some(handles, (handle) => { const sourceType = - handleFilter == 'source' ? fieldFilter : handle.type; + handleFilter == 'source' + ? fieldFilter + : handle.originalType ?? handle.type; const targetType = - handleFilter == 'target' ? fieldFilter : handle.type; + handleFilter == 'target' + ? fieldFilter + : handle.originalType ?? handle.type; return validateSourceAndTargetTypes(sourceType, targetType); }); @@ -111,7 +115,7 @@ const AddNodePopover = () => { data.sort((a, b) => a.label.localeCompare(b.label)); - return { data, t }; + return { data }; }, defaultSelectorOptions ); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/connectionLines/CustomConnectionLine.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/connectionLines/CustomConnectionLine.tsx index a379be7ee28..a14b7b23c6d 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/connectionLines/CustomConnectionLine.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/connectionLines/CustomConnectionLine.tsx @@ -2,18 +2,17 @@ import { createSelector } from '@reduxjs/toolkit'; import { stateSelector } from 'app/store/store'; import { useAppSelector } from 'app/store/storeHooks'; import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar'; -import { FIELDS } from 'features/nodes/types/constants'; import { memo } from 'react'; import { ConnectionLineComponentProps, getBezierPath } from 'reactflow'; +import { getFieldColor } from '../edges/util/getEdgeColor'; const selector = createSelector(stateSelector, ({ nodes }) => { const { shouldAnimateEdges, currentConnectionFieldType, shouldColorEdges } = nodes; - const stroke = - currentConnectionFieldType && shouldColorEdges - ? colorTokenToCssVar(FIELDS[currentConnectionFieldType].color) - : colorTokenToCssVar('base.500'); + const stroke = shouldColorEdges + ? getFieldColor(currentConnectionFieldType) + : colorTokenToCssVar('base.500'); let className = 'react-flow__custom_connection-path'; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/edges/util/getEdgeColor.ts b/invokeai/frontend/web/src/features/nodes/components/flow/edges/util/getEdgeColor.ts new file mode 100644 index 00000000000..99ada97de14 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/components/flow/edges/util/getEdgeColor.ts @@ -0,0 +1,12 @@ +import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar'; +import { FIELDS } from 'features/nodes/types/constants'; +import { FieldType } from 'features/nodes/types/types'; + +export const getFieldColor = (fieldType: FieldType | string | null): string => { + if (!fieldType) { + return colorTokenToCssVar('base.500'); + } + const color = FIELDS[fieldType]?.color; + + return color ? colorTokenToCssVar(color) : colorTokenToCssVar('base.500'); +}; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/edges/util/makeEdgeSelector.ts b/invokeai/frontend/web/src/features/nodes/components/flow/edges/util/makeEdgeSelector.ts index b5dc484eaea..a6a409e1ad2 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/edges/util/makeEdgeSelector.ts +++ b/invokeai/frontend/web/src/features/nodes/components/flow/edges/util/makeEdgeSelector.ts @@ -2,8 +2,8 @@ import { createSelector } from '@reduxjs/toolkit'; import { stateSelector } from 'app/store/store'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar'; -import { FIELDS } from 'features/nodes/types/constants'; import { isInvocationNode } from 'features/nodes/types/types'; +import { getFieldColor } from './getEdgeColor'; export const makeEdgeSelector = ( source: string, @@ -29,7 +29,7 @@ export const makeEdgeSelector = ( const stroke = sourceType && nodes.shouldColorEdges - ? colorTokenToCssVar(FIELDS[sourceType].color) + ? getFieldColor(sourceType) : colorTokenToCssVar('base.500'); return { diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldHandle.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldHandle.tsx index b458f2ca255..849003ffbeb 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldHandle.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldHandle.tsx @@ -1,8 +1,6 @@ import { Tooltip } from '@chakra-ui/react'; -import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar'; import { COLLECTION_TYPES, - FIELDS, HANDLE_TOOLTIP_OPEN_DELAY, MODEL_TYPES, POLYMORPHIC_TYPES, @@ -13,6 +11,7 @@ import { } from 'features/nodes/types/types'; import { CSSProperties, memo, useMemo } from 'react'; import { Handle, HandleType, Position } from 'reactflow'; +import { getFieldColor } from '../../../edges/util/getEdgeColor'; export const handleBaseStyles: CSSProperties = { position: 'absolute', @@ -47,14 +46,14 @@ const FieldHandle = (props: FieldHandleProps) => { isConnectionStartField, connectionError, } = props; - const { name, type, originalType } = fieldTemplate; - const { color: typeColor } = FIELDS[type]; + const { name } = fieldTemplate; + const type = fieldTemplate.originalType ?? fieldTemplate.type; const styles: CSSProperties = useMemo(() => { - const isCollectionType = COLLECTION_TYPES.includes(type); - const isPolymorphicType = POLYMORPHIC_TYPES.includes(type); - const isModelType = MODEL_TYPES.includes(type); - const color = colorTokenToCssVar(typeColor); + const isCollectionType = COLLECTION_TYPES.some((t) => t === type); + const isPolymorphicType = POLYMORPHIC_TYPES.some((t) => t === type); + const isModelType = MODEL_TYPES.some((t) => t === type); + const color = getFieldColor(type); const s: CSSProperties = { backgroundColor: isCollectionType || isPolymorphicType @@ -97,23 +96,14 @@ const FieldHandle = (props: FieldHandleProps) => { isConnectionInProgress, isConnectionStartField, type, - typeColor, ]); const tooltip = useMemo(() => { - if (isConnectionInProgress && isConnectionStartField) { - return originalType; - } if (isConnectionInProgress && connectionError) { - return connectionError ?? originalType; + return connectionError; } - return originalType; - }, [ - connectionError, - isConnectionInProgress, - isConnectionStartField, - originalType, - ]); + return type; + }, [connectionError, isConnectionInProgress, type]); return ( ) => { const fieldType = state.currentConnectionFieldType; diff --git a/invokeai/frontend/web/src/features/nodes/store/types.ts b/invokeai/frontend/web/src/features/nodes/store/types.ts index f6bfa7cad8b..b81dd286d72 100644 --- a/invokeai/frontend/web/src/features/nodes/store/types.ts +++ b/invokeai/frontend/web/src/features/nodes/store/types.ts @@ -21,7 +21,7 @@ export type NodesState = { edges: Edge[]; nodeTemplates: Record; connectionStartParams: OnConnectStartParams | null; - currentConnectionFieldType: FieldType | null; + currentConnectionFieldType: FieldType | string | null; connectionMade: boolean; modifyingEdge: boolean; shouldShowFieldTypeLegend: boolean; diff --git a/invokeai/frontend/web/src/features/nodes/store/util/buildNodeData.ts b/invokeai/frontend/web/src/features/nodes/store/util/buildNodeData.ts index 6cecc8c4098..0efd3d17c6a 100644 --- a/invokeai/frontend/web/src/features/nodes/store/util/buildNodeData.ts +++ b/invokeai/frontend/web/src/features/nodes/store/util/buildNodeData.ts @@ -94,6 +94,7 @@ export const buildNodeData = ( name: outputName, type: outputTemplate.type, fieldKind: 'output', + originalType: outputTemplate.originalType, }; outputsAccumulator[outputName] = outputFieldValue; diff --git a/invokeai/frontend/web/src/features/nodes/store/util/findConnectionToValidHandle.ts b/invokeai/frontend/web/src/features/nodes/store/util/findConnectionToValidHandle.ts index 69386c1f23c..da2026ce570 100644 --- a/invokeai/frontend/web/src/features/nodes/store/util/findConnectionToValidHandle.ts +++ b/invokeai/frontend/web/src/features/nodes/store/util/findConnectionToValidHandle.ts @@ -12,7 +12,7 @@ import { getIsGraphAcyclic } from './getIsGraphAcyclic'; const isValidConnection = ( edges: Edge[], handleCurrentType: HandleType, - handleCurrentFieldType: FieldType, + handleCurrentFieldType: FieldType | string, node: Node, handle: InputFieldValue | OutputFieldValue ) => { @@ -35,7 +35,12 @@ const isValidConnection = ( } } - if (!validateSourceAndTargetTypes(handleCurrentFieldType, handle.type)) { + if ( + !validateSourceAndTargetTypes( + handleCurrentFieldType, + handle.originalType ?? handle.type + ) + ) { isValidConnection = false; } @@ -49,7 +54,7 @@ export const findConnectionToValidHandle = ( handleCurrentNodeId: string, handleCurrentName: string, handleCurrentType: HandleType, - handleCurrentFieldType: FieldType + handleCurrentFieldType: FieldType | string ): Connection | null => { if (node.id === handleCurrentNodeId) { return null; diff --git a/invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts b/invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts index 57dd284b88d..cb7886e57e1 100644 --- a/invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts +++ b/invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts @@ -1,9 +1,9 @@ import { createSelector } from '@reduxjs/toolkit'; import { stateSelector } from 'app/store/store'; -import { getIsGraphAcyclic } from './getIsGraphAcyclic'; import { FieldType } from 'features/nodes/types/types'; import i18n from 'i18next'; import { HandleType } from 'reactflow'; +import { getIsGraphAcyclic } from './getIsGraphAcyclic'; import { validateSourceAndTargetTypes } from './validateSourceAndTargetTypes'; /** @@ -15,7 +15,7 @@ export const makeConnectionErrorSelector = ( nodeId: string, fieldName: string, handleType: HandleType, - fieldType?: FieldType + fieldType?: FieldType | string ) => { return createSelector(stateSelector, (state) => { if (!fieldType) { diff --git a/invokeai/frontend/web/src/features/nodes/store/util/validateSourceAndTargetTypes.ts b/invokeai/frontend/web/src/features/nodes/store/util/validateSourceAndTargetTypes.ts index 2f47e47a787..123cda8e044 100644 --- a/invokeai/frontend/web/src/features/nodes/store/util/validateSourceAndTargetTypes.ts +++ b/invokeai/frontend/web/src/features/nodes/store/util/validateSourceAndTargetTypes.ts @@ -7,8 +7,8 @@ import { import { FieldType } from 'features/nodes/types/types'; export const validateSourceAndTargetTypes = ( - sourceType: FieldType, - targetType: FieldType + sourceType: FieldType | string, + targetType: FieldType | string ) => { // TODO: There's a bug with Collect -> Iterate nodes: // https://github.com/invoke-ai/InvokeAI/issues/3956 @@ -31,17 +31,18 @@ export const validateSourceAndTargetTypes = ( */ const isCollectionItemToNonCollection = - sourceType === 'CollectionItem' && !COLLECTION_TYPES.includes(targetType); + sourceType === 'CollectionItem' && + !COLLECTION_TYPES.some((t) => t === targetType); const isNonCollectionToCollectionItem = targetType === 'CollectionItem' && - !COLLECTION_TYPES.includes(sourceType) && - !POLYMORPHIC_TYPES.includes(sourceType); + !COLLECTION_TYPES.some((t) => t === sourceType) && + !POLYMORPHIC_TYPES.some((t) => t === sourceType); const isAnythingToPolymorphicOfSameBaseType = - POLYMORPHIC_TYPES.includes(targetType) && + POLYMORPHIC_TYPES.some((t) => t === targetType) && (() => { - if (!POLYMORPHIC_TYPES.includes(targetType)) { + if (!POLYMORPHIC_TYPES.some((t) => t === targetType)) { return false; } const baseType = @@ -57,11 +58,12 @@ export const validateSourceAndTargetTypes = ( const isGenericCollectionToAnyCollectionOrPolymorphic = sourceType === 'Collection' && - (COLLECTION_TYPES.includes(targetType) || - POLYMORPHIC_TYPES.includes(targetType)); + (COLLECTION_TYPES.some((t) => t === targetType) || + POLYMORPHIC_TYPES.some((t) => t === targetType)); const isCollectionToGenericCollection = - targetType === 'Collection' && COLLECTION_TYPES.includes(sourceType); + targetType === 'Collection' && + COLLECTION_TYPES.some((t) => t === sourceType); const isIntToFloat = sourceType === 'integer' && targetType === 'float'; diff --git a/invokeai/frontend/web/src/features/nodes/types/constants.ts b/invokeai/frontend/web/src/features/nodes/types/constants.ts index c6b8e2acc4a..93e6fa2948e 100644 --- a/invokeai/frontend/web/src/features/nodes/types/constants.ts +++ b/invokeai/frontend/web/src/features/nodes/types/constants.ts @@ -150,16 +150,16 @@ export const isPolymorphicItemType = ( ): itemType is keyof typeof SINGLE_TO_POLYMORPHIC_MAP => Boolean(itemType && itemType in SINGLE_TO_POLYMORPHIC_MAP); -export const FIELDS: Record = { +export const FIELDS: Record = { Any: { color: 'gray.500', description: 'Any field type is accepted.', title: 'Any', }, - Unknown: { + Custom: { color: 'gray.500', - description: 'Unknown field type is accepted.', - title: 'Unknown', + description: 'A custom field, provided by an external node.', + title: 'Custom', }, MetadataField: { color: 'gray.500', diff --git a/invokeai/frontend/web/src/features/nodes/types/types.ts b/invokeai/frontend/web/src/features/nodes/types/types.ts index 92a18a418d1..d87e45c8073 100644 --- a/invokeai/frontend/web/src/features/nodes/types/types.ts +++ b/invokeai/frontend/web/src/features/nodes/types/types.ts @@ -133,7 +133,7 @@ export const zFieldType = z.enum([ 'UNetField', 'VaeField', 'VaeModelField', - 'Unknown', + 'Custom', ]); export type FieldType = z.infer; @@ -164,6 +164,7 @@ export const zFieldValueBase = z.object({ id: z.string().trim().min(1), name: z.string().trim().min(1), type: zFieldType, + originalType: z.string().optional(), }); export type FieldValueBase = z.infer; @@ -191,7 +192,7 @@ export type OutputFieldTemplate = { type: FieldType; title: string; description: string; - originalType: string; // used for custom types + originalType?: string; // used for custom types } & _OutputField; export const zInputFieldValueBase = zFieldValueBase.extend({ @@ -791,8 +792,8 @@ export const zAnyInputFieldValue = zInputFieldValueBase.extend({ value: z.any().optional(), }); -export const zUnknownInputFieldValue = zInputFieldValueBase.extend({ - type: z.literal('Unknown'), +export const zCustomInputFieldValue = zInputFieldValueBase.extend({ + type: z.literal('Custom'), value: z.any().optional(), }); @@ -853,7 +854,7 @@ export const zInputFieldValue = z.discriminatedUnion('type', [ zMetadataItemPolymorphicInputFieldValue, zMetadataInputFieldValue, zMetadataCollectionInputFieldValue, - zUnknownInputFieldValue, + zCustomInputFieldValue, ]); export type InputFieldValue = z.infer; @@ -864,7 +865,7 @@ export type InputFieldTemplateBase = { description: string; required: boolean; fieldKind: 'input'; - originalType: string; // used for custom types + originalType?: string; // used for custom types } & _InputField; export type AnyInputFieldTemplate = InputFieldTemplateBase & { @@ -872,8 +873,8 @@ export type AnyInputFieldTemplate = InputFieldTemplateBase & { default: undefined; }; -export type UnknownInputFieldTemplate = InputFieldTemplateBase & { - type: 'Unknown'; +export type CustomInputFieldTemplate = InputFieldTemplateBase & { + type: 'Custom'; default: undefined; }; @@ -1274,7 +1275,7 @@ export type InputFieldTemplate = | MetadataInputFieldTemplate | MetadataItemPolymorphicInputFieldTemplate | MetadataCollectionInputFieldTemplate - | UnknownInputFieldTemplate; + | CustomInputFieldTemplate; export const isInputFieldValue = ( field?: InputFieldValue | OutputFieldValue diff --git a/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts b/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts index fe94ec27828..3aa720ef5cc 100644 --- a/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts +++ b/invokeai/frontend/web/src/features/nodes/util/fieldTemplateBuilders.ts @@ -81,7 +81,7 @@ import { T2IAdapterModelInputFieldTemplate, T2IAdapterPolymorphicInputFieldTemplate, UNetInputFieldTemplate, - UnknownInputFieldTemplate, + CustomInputFieldTemplate, VaeInputFieldTemplate, VaeModelInputFieldTemplate, isArraySchemaObject, @@ -982,12 +982,12 @@ const buildSchedulerInputFieldTemplate = ({ return template; }; -const buildUnknownInputFieldTemplate = ({ +const buildCustomInputFieldTemplate = ({ baseField, -}: BuildInputFieldArg): UnknownInputFieldTemplate => { - const template: UnknownInputFieldTemplate = { +}: BuildInputFieldArg): CustomInputFieldTemplate => { + const template: CustomInputFieldTemplate = { ...baseField, - type: 'Unknown', + type: 'Custom', default: undefined, }; @@ -1158,7 +1158,7 @@ const TEMPLATE_BUILDER_MAP: { UNetField: buildUNetInputFieldTemplate, VaeField: buildVaeInputFieldTemplate, VaeModelField: buildVaeModelInputFieldTemplate, - Unknown: buildUnknownInputFieldTemplate, + Custom: buildCustomInputFieldTemplate, }; /** diff --git a/invokeai/frontend/web/src/features/nodes/util/fieldValueBuilders.ts b/invokeai/frontend/web/src/features/nodes/util/fieldValueBuilders.ts index 89d6729f4c3..f8db78ecc39 100644 --- a/invokeai/frontend/web/src/features/nodes/util/fieldValueBuilders.ts +++ b/invokeai/frontend/web/src/features/nodes/util/fieldValueBuilders.ts @@ -60,7 +60,7 @@ const FIELD_VALUE_FALLBACK_MAP: { UNetField: undefined, VaeField: undefined, VaeModelField: undefined, - Unknown: undefined, + Custom: undefined, }; export const buildInputFieldValue = ( @@ -77,10 +77,9 @@ export const buildInputFieldValue = ( type: template.type, label: '', fieldKind: 'input', + originalType: template.originalType, + value: template.default ?? FIELD_VALUE_FALLBACK_MAP[template.type], } as InputFieldValue; - fieldValue.value = - template.default ?? FIELD_VALUE_FALLBACK_MAP[template.type]; - return fieldValue; }; diff --git a/invokeai/frontend/web/src/features/nodes/util/parseSchema.ts b/invokeai/frontend/web/src/features/nodes/util/parseSchema.ts index 30fff76cafc..c9ccf503be2 100644 --- a/invokeai/frontend/web/src/features/nodes/util/parseSchema.ts +++ b/invokeai/frontend/web/src/features/nodes/util/parseSchema.ts @@ -4,7 +4,6 @@ import { reduce, startCase } from 'lodash-es'; import { OpenAPIV3_1 } from 'openapi-types'; import { AnyInvocationType } from 'services/events/types'; import { - FieldType, InputFieldTemplate, InvocationSchemaObject, InvocationTemplate, @@ -150,14 +149,18 @@ export const parseSchema = ( }, `Fallback handling for unknown input field type: ${fieldType}` ); - fieldType = 'Unknown'; + fieldType = 'Custom'; + if (!isFieldType(fieldType)) { + // satisfy TS gods + return inputsAccumulator; + } } const field = buildInputFieldTemplate( schema, property, propertyName, - fieldType as FieldType, // we have already checked that fieldType is a valid FieldType, and forced it to be Unknown if not + fieldType, originalType ); @@ -248,7 +251,11 @@ export const parseSchema = ( { fieldName: propertyName, fieldType, field: parseify(property) }, `Fallback handling for unknown input field type: ${fieldType}` ); - fieldType = 'Unknown'; + fieldType = 'Custom'; + if (!isFieldType(fieldType)) { + // satisfy TS gods + return outputsAccumulator; + } } outputsAccumulator[propertyName] = { @@ -257,7 +264,7 @@ export const parseSchema = ( title: property.title ?? (propertyName ? startCase(propertyName) : ''), description: property.description ?? '', - type: fieldType as FieldType, + type: fieldType, ui_hidden: property.ui_hidden ?? false, ui_type: property.ui_type, ui_order: property.ui_order,