Skip to content

Commit 05ea1c7

Browse files
chore(ui): fix circular dep
1 parent 2ba0f92 commit 05ea1c7

File tree

3 files changed

+73
-71
lines changed

3 files changed

+73
-71
lines changed

invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/form-manipulation.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
getIsFormEmpty,
88
removeElement,
99
reparentElement,
10-
validateFormStructure,
1110
} from 'features/nodes/components/sidePanel/builder/form-manipulation';
1211
import type { BuilderForm, ContainerElement } from 'features/nodes/types/workflow';
1312
import {
@@ -16,6 +15,7 @@ import {
1615
getDefaultForm,
1716
isContainerElement,
1817
isNodeFieldElement,
18+
validateFormStructure,
1919
} from 'features/nodes/types/workflow';
2020
import type { Equals } from 'tsafe';
2121
import { assert, AssertionError } from 'tsafe';

invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/form-manipulation.ts

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -246,75 +246,6 @@ export const getAllowedDropRegions = (form: BuilderForm, element: FormElement):
246246
return dropRegions;
247247
};
248248

249-
/**
250-
* Validates the structure of a form.
251-
*
252-
* The form structure is valid if:
253-
* - The root element is a container
254-
* - Starting from the root element, all children referenced are reachable
255-
* - There are no extra elements in the form that are not reachable from the root element
256-
* - The root element has no parentId and is a container
257-
* - Non-root elements have a parentId
258-
* - All parent elements are containers
259-
* - All elements with a parentId are children of their parent
260-
*
261-
* @param form The form to validate
262-
*
263-
* @returns True if the form structure is valid, false otherwise
264-
*/
265-
export const validateFormStructure = (form: BuilderForm): boolean => {
266-
const { elements, rootElementId } = form;
267-
268-
const rootElement = elements[rootElementId];
269-
const isRootElementAContainer = rootElement !== undefined && isContainerElement(rootElement);
270-
271-
const childrenFoundInTree = new Set<string>();
272-
273-
const findChildren = (elementId: string): boolean => {
274-
const element = elements[elementId];
275-
if (!element) {
276-
// Element not found
277-
return false;
278-
}
279-
childrenFoundInTree.add(elementId);
280-
if (element.id === rootElementId) {
281-
// Special handling for root
282-
if (element.parentId !== undefined) {
283-
// Root element must not have a parent
284-
return false;
285-
}
286-
} else {
287-
// Handling for all other elements
288-
if (element.parentId === undefined) {
289-
// Element must have a parent
290-
return false;
291-
}
292-
const parent = elements[element.parentId];
293-
if (!parent) {
294-
// Parent must exist
295-
return false;
296-
}
297-
if (!isContainerElement(parent)) {
298-
// Parent must be a container
299-
return false;
300-
}
301-
if (!parent.data.children.includes(elementId)) {
302-
// Element must be a child of its parent
303-
return false;
304-
}
305-
}
306-
if (isContainerElement(element) && element.data.children.length > 0) {
307-
return element.data.children.every(findChildren);
308-
}
309-
return true;
310-
};
311-
312-
const noMissingChildren = findChildren(rootElementId);
313-
const noExtraElements = Object.keys(elements).length === childrenFoundInTree.size;
314-
315-
return isRootElementAContainer && noMissingChildren && noExtraElements;
316-
};
317-
318249
/**
319250
* Checks if a form is empty.
320251
* A form is empty if it only contains the root element and the root element has no children.

invokeai/frontend/web/src/features/nodes/types/workflow.ts

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { getPrefixedId } from 'features/controlLayers/konva/util';
2-
import { validateFormStructure } from 'features/nodes/components/sidePanel/builder/form-manipulation';
32
import { z } from 'zod';
43

54
import type { FieldType } from './field';
@@ -248,6 +247,78 @@ const zBuilderForm = z.object({
248247

249248
export type BuilderForm = z.infer<typeof zBuilderForm>;
250249

250+
// This validation function needs to be here to avoid circular dependencies. It was intended to be in
251+
// `form-manipulation.ts` and is tested in taht file's tests.
252+
253+
/**
254+
* Validates the structure of a form.
255+
*
256+
* The form structure is valid if:
257+
* - The root element is a container
258+
* - Starting from the root element, all children referenced are reachable
259+
* - There are no extra elements in the form that are not reachable from the root element
260+
* - The root element has no parentId and is a container
261+
* - Non-root elements have a parentId
262+
* - All parent elements are containers
263+
* - All elements with a parentId are children of their parent
264+
*
265+
* @param form The form to validate
266+
*
267+
* @returns True if the form structure is valid, false otherwise
268+
*/
269+
export const validateFormStructure = (form: BuilderForm): boolean => {
270+
const { elements, rootElementId } = form;
271+
272+
const rootElement = elements[rootElementId];
273+
const isRootElementAContainer = rootElement !== undefined && isContainerElement(rootElement);
274+
275+
const childrenFoundInTree = new Set<string>();
276+
277+
const findChildren = (elementId: string): boolean => {
278+
const element = elements[elementId];
279+
if (!element) {
280+
// Element not found
281+
return false;
282+
}
283+
childrenFoundInTree.add(elementId);
284+
if (element.id === rootElementId) {
285+
// Special handling for root
286+
if (element.parentId !== undefined) {
287+
// Root element must not have a parent
288+
return false;
289+
}
290+
} else {
291+
// Handling for all other elements
292+
if (element.parentId === undefined) {
293+
// Element must have a parent
294+
return false;
295+
}
296+
const parent = elements[element.parentId];
297+
if (!parent) {
298+
// Parent must exist
299+
return false;
300+
}
301+
if (!isContainerElement(parent)) {
302+
// Parent must be a container
303+
return false;
304+
}
305+
if (!parent.data.children.includes(elementId)) {
306+
// Element must be a child of its parent
307+
return false;
308+
}
309+
}
310+
if (isContainerElement(element) && element.data.children.length > 0) {
311+
return element.data.children.every(findChildren);
312+
}
313+
return true;
314+
};
315+
316+
const noMissingChildren = findChildren(rootElementId);
317+
const noExtraElements = Object.keys(elements).length === childrenFoundInTree.size;
318+
319+
return isRootElementAContainer && noMissingChildren && noExtraElements;
320+
};
321+
251322
// Need to separate the form vaidation from the schema due to circular dependencies
252323
const zValidatedBuilderForm = zBuilderForm
253324
.catch(getDefaultForm)

0 commit comments

Comments
 (0)