feat(ui): organize node utils

This commit is contained in:
psychedelicious
2023-11-27 00:00:44 +11:00
parent 4309f3bd58
commit 8d99113bef
60 changed files with 106 additions and 95 deletions

View File

@@ -0,0 +1,23 @@
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import { CurrentImageNode } from 'features/nodes/types/invocation';
import { XYPosition } from 'reactflow';
import { v4 as uuidv4 } from 'uuid';
export const buildCurrentImageNode = (
position: XYPosition
): CurrentImageNode => {
const nodeId = uuidv4();
const node: CurrentImageNode = {
...SHARED_NODE_PROPERTIES,
id: nodeId,
type: 'current_image',
position,
data: {
id: nodeId,
type: 'current_image',
isOpen: true,
label: 'Current Image',
},
};
return node;
};

View File

@@ -0,0 +1,79 @@
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import {
FieldInputInstance,
FieldOutputInstance,
} from 'features/nodes/types/field';
import {
InvocationNode,
InvocationTemplate,
} from 'features/nodes/types/invocation';
import { buildFieldInputInstance } from 'features/nodes/util/schema/buildFieldInputInstance';
import { reduce } from 'lodash-es';
import { XYPosition } from 'reactflow';
import { v4 as uuidv4 } from 'uuid';
export const buildInvocationNode = (
position: XYPosition,
template: InvocationTemplate
): InvocationNode => {
const nodeId = uuidv4();
const { type } = template;
const inputs = reduce(
template.inputs,
(inputsAccumulator, inputTemplate, inputName) => {
const fieldId = uuidv4();
const inputFieldValue: FieldInputInstance = buildFieldInputInstance(
fieldId,
inputTemplate
);
inputsAccumulator[inputName] = inputFieldValue;
return inputsAccumulator;
},
{} as Record<string, FieldInputInstance>
);
const outputs = reduce(
template.outputs,
(outputsAccumulator, outputTemplate, outputName) => {
const fieldId = uuidv4();
const outputFieldValue: FieldOutputInstance = {
id: fieldId,
name: outputName,
type: outputTemplate.type,
fieldKind: 'output',
};
outputsAccumulator[outputName] = outputFieldValue;
return outputsAccumulator;
},
{} as Record<string, FieldOutputInstance>
);
const node: InvocationNode = {
...SHARED_NODE_PROPERTIES,
id: nodeId,
type: 'invocation',
position,
data: {
id: nodeId,
type,
version: template.version,
label: '',
notes: '',
isOpen: true,
embedWorkflow: false,
isIntermediate: type === 'save_image' ? false : true,
useCache: template.useCache,
inputs,
outputs,
},
};
return node;
};

View File

@@ -0,0 +1,22 @@
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import { NotesNode } from 'features/nodes/types/invocation';
import { XYPosition } from 'reactflow';
import { v4 as uuidv4 } from 'uuid';
export const buildNotesNode = (position: XYPosition): NotesNode => {
const nodeId = uuidv4();
const node: NotesNode = {
...SHARED_NODE_PROPERTIES,
id: nodeId,
type: 'notes',
position,
data: {
id: nodeId,
isOpen: true,
label: 'Notes',
notes: '',
type: 'notes',
},
};
return node;
};

View File

@@ -0,0 +1,20 @@
import { isNil } from 'lodash-es';
import { FieldInputTemplate, FieldOutputTemplate } from '../../types/field';
export const getSortedFilteredFieldNames = (
fields: FieldInputTemplate[] | FieldOutputTemplate[]
): string[] => {
const visibleFields = fields.filter((field) => !field.ui_hidden);
// we want explicitly ordered fields to be before unordered fields; split the list
const orderedFields = visibleFields
.filter((f) => !isNil(f.ui_order))
.sort((a, b) => (a.ui_order ?? 0) - (b.ui_order ?? 0));
const unorderedFields = visibleFields.filter((f) => isNil(f.ui_order));
// concat the lists, and return the field names, skipping `is_intermediate`
return orderedFields
.concat(unorderedFields)
.map((f) => f.name)
.filter((fieldName) => fieldName !== 'is_intermediate');
};

View File

@@ -0,0 +1,68 @@
import { satisfies } from 'compare-versions';
import { NodeUpdateError } from 'features/nodes/types/error';
import {
InvocationNodeData,
InvocationTemplate,
} from 'features/nodes/types/invocation';
import { zParsedSemver } from 'features/nodes/types/semver';
import { cloneDeep, defaultsDeep } from 'lodash-es';
import { Node } from 'reactflow';
import { buildInvocationNode } from './buildInvocationNode';
export const getNeedsUpdate = (
node: Node<InvocationNodeData>,
template: InvocationTemplate
): boolean => {
if (node.data.type !== template.type) {
return true;
}
return node.data.version !== template.version;
}; /**
* Checks if a node may be updated by comparing its major version with the template's major version.
* @param node The node to check.
* @param template The invocation template to check against.
*/
export const getMayUpdateNode = (
node: Node<InvocationNodeData>,
template: InvocationTemplate
): boolean => {
const needsUpdate = getNeedsUpdate(node, template);
if (!needsUpdate || node.data.type !== template.type) {
return false;
}
const templateMajor = zParsedSemver.parse(template.version).major;
return satisfies(node.data.version, `^${templateMajor}`);
}; /**
* Updates a node to the latest version of its template:
* - Create a new node data object with the latest version of the template.
* - Recursively merge new node data object into the node to be updated.
*
* @param node The node to updated.
* @param template The invocation template to update to.
* @throws {NodeUpdateError} If the node is not an invocation node.
*/
export const updateNode = (
node: Node<InvocationNodeData>,
template: InvocationTemplate
): Node<InvocationNodeData> => {
const mayUpdate = getMayUpdateNode(node, template);
if (!mayUpdate || node.data.type !== template.type) {
throw new NodeUpdateError(`Unable to update node ${node.id}`);
}
// Start with a "fresh" node - just as if the user created a new node of this type
const defaults = buildInvocationNode(node.position, template);
// The updateability of a node, via semver comparison, relies on the this kind of recursive merge
// being valid. We rely on the template's major version to be majorly incremented if this kind of
// merge would result in an invalid node.
const clone = cloneDeep(node);
clone.data.version = template.version;
defaultsDeep(clone, defaults); // mutates!
return clone;
};