diff --git a/src/packages/ce/src/datatype/components/badges/NodeBadgeComponent.tsx b/src/packages/ce/src/datatype/components/badges/NodeBadgeComponent.tsx index 480d3edf..11415eb8 100644 --- a/src/packages/ce/src/datatype/components/badges/NodeBadgeComponent.tsx +++ b/src/packages/ce/src/datatype/components/badges/NodeBadgeComponent.tsx @@ -16,7 +16,7 @@ export interface NodeBadgeComponentProps extends Omit = (props) => { const params = useParams() - const flowIndex = params.flowId as any as number + const flowIndex = Number(params.flowId) || 1 const flowId: Flow['id'] = `gid://sagittarius/Flow/${flowIndex}` const {value, definition, ...rest} = props diff --git a/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx b/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx index 989964a0..2727aa89 100644 --- a/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx +++ b/src/packages/ce/src/datatype/components/inputs/DataTypeInputComponent.tsx @@ -32,6 +32,9 @@ import {DataTypeGenericInputComponent} from "@edition/datatype/components/inputs import { DataTypeSubFlowInputComponent } from "@edition/datatype/components/inputs/sub-flow/DataTypeSubFlowInputComponent"; +import { + DataTypeListSubFlowInputComponent +} from "@edition/datatype/components/inputs/list-sub-flow/DataTypeListSubFlowInputComponent"; export interface DataTypeInputComponentProps extends Omit, "onChange"> { schema: (NodeSchema | Schema) @@ -123,6 +126,11 @@ export const DataTypeInputComponent: React.FC = (pr schema={schema} suggestions={suggestions} {...rest}/> + case "list-sub-flow": + return default: return = (props) => { + + const {schema, formValidation, title, initialValue, description, suggestions, onChange} = props + + const params = useParams() + const flowService = useService(FlowService) + + const flowIndex = Number(params.flowId) || 1 + const flowId: Flow['id'] = `gid://sagittarius/Flow/${flowIndex}` + + const initialSubFlows: SubFlowValue[] = ((initialValue as LiteralValue)?.__typename === "LiteralValue" + ? ((initialValue as LiteralValue).references ?? []) + : []) + .map(reference => reference.value) + .filter((value): value is SubFlowValue => value?.__typename === "SubFlowValue") + + const [subFlows, setSubFlows] = React.useState(initialSubFlows) + const [dialogOpen, setDialogOpen] = React.useState(false) + const functionSuggestions = useFunctionSuggestions() + + const directMappingSuggestions = React.useMemo(() => { + const inner = schema && "schema" in schema ? (schema as NodeSchema).schema : (schema as Schema | undefined) + const items = (inner as { items?: Schema[] })?.items ?? [] + const seen = new Set() + return items.flatMap(item => item.suggestions ?? []) + .filter((suggest): suggest is SubFlowValue => suggest.__typename === "SubFlowValue") + .filter(suggest => { + const key = suggest.functionDefinition?.id ?? suggest.signature ?? "" + if (seen.has(key)) return false + seen.add(key) + return true + }) + }, [schema]) + + const referenceSuggestions = React.useMemo( + () => (suggestions ?? []).filter(suggest => suggest.__typename !== "LiteralValue"), + [suggestions] + ) + + const onChangeDebounced = useDebouncedCallback((value: LiteralValue | SubFlowValue | NodeFunction | ReferenceValue | null) => { + onChange?.(value) + }, 200) + + const keyOf = (value: SubFlowValue) => value.startingNodeId ?? value.functionDefinition?.id ?? "" + const byKey = new Map(subFlows.map(value => [keyOf(value), value])) + const tags: TagValue[] = subFlows.map(value => ({value: keyOf(value)})) + + const commit = (next: SubFlowValue[]) => { + setSubFlows(next) + + if (next.length === 0) { + formValidation?.setValue?.(null) + onChangeDebounced(null) + return + } + + const references: InlineReferenceValue[] = next.map((value, index) => ({ + __typename: "InlineReferenceValue", + signature: `sub_flow_${index}`, + value + })) + const literal: LiteralValue = { + __typename: "LiteralValue", + value: next.map((_, index) => `\${sub_flow_${index}}`), + references + } + formValidation?.setValue?.(literal) + onChangeDebounced(literal) + } + + return <> + { + if (value?.__typename === "NodeFunction") { + const nodeId = flowService.addNodeById(flowId, value) + value = {__typename: "SubFlowValue", startingNodeId: nodeId} + } + if (value?.__typename !== "SubFlowValue") return + commit([...subFlows, value]) + }}/> + {title} + {description} + { + formValidation?.setValue?.(value) + onChangeDebounced(value) + }} + suggestions={suggestions} + formValidation={formValidation}> + { + const value = byKey.get(matchedText) + return value ? : null + } + } + ]} + formValidation={{...formValidation, setValue: undefined}} + onChange={changed => { + commit(changed + .map(tag => byKey.get(String(tag.value))) + .filter((value): value is SubFlowValue => value !== undefined)) + }} + right={ + { + if (value?.__typename === "SubFlowValue") { + commit([...subFlows, value]) + return + } + if (!value) { + commit([]) + return + } + setSubFlows([]) + formValidation?.setValue?.(value) + onChangeDebounced(value) + }}> + + + } + rightType={"action"}> + + + + + +} diff --git a/src/packages/ce/src/datatype/components/inputs/sub-flow/DataTypeSubFlowInputComponent.tsx b/src/packages/ce/src/datatype/components/inputs/sub-flow/DataTypeSubFlowInputComponent.tsx index 39819ba7..999eebf6 100644 --- a/src/packages/ce/src/datatype/components/inputs/sub-flow/DataTypeSubFlowInputComponent.tsx +++ b/src/packages/ce/src/datatype/components/inputs/sub-flow/DataTypeSubFlowInputComponent.tsx @@ -21,7 +21,7 @@ export const DataTypeSubFlowInputComponent: React.FC suggestions?.findIndex(suggest => { diff --git a/src/packages/ce/src/flow/components/builder/FlowBuilderComponent.tsx b/src/packages/ce/src/flow/components/builder/FlowBuilderComponent.tsx index 7acb112c..04049808 100644 --- a/src/packages/ce/src/flow/components/builder/FlowBuilderComponent.tsx +++ b/src/packages/ce/src/flow/components/builder/FlowBuilderComponent.tsx @@ -58,7 +58,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { const paramIds = new Map() for (const n of nodes) { - const link = (n.data as any)?.parentNodeId + const link = (n.data as any)?.parentNodeId ?? (n.type === "group" ? (n.data as any)?.nodeId : undefined) if (link) { const arr = paramIds.get(link) ?? [] arr.push(n.id) @@ -167,10 +167,6 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { // relatives Layout (Center in globalen Koordinaten) const relCenter = new Map() - // Unterkante je rechter Spalten-"Band", damit Parameter nicht kollidieren - const columnBottom = new Map() - const colKey = (x: number) => Math.round(x / 10) - const layoutIter = (root: Node, cx: number, cy: number): number => { type Frame = { node: Node @@ -181,9 +177,8 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { h?: number right?: Node[] rightIndex?: number - py?: number + rightX?: number rightBottom?: number - childKey?: number childPs?: Size lastChildBottom?: number @@ -225,11 +220,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { f.right = right f.gParams = gParams - let total = 0 - for (const p of right) total += size(p).h - total += V * Math.max(0, right.length - 1) - - f.py = f.cy - total / 2 + f.rightX = f.cx + f.w! / 2 + H f.rightBottom = f.cy + h / 2 f.rightIndex = 0 f.phase = 1 @@ -240,22 +231,13 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { if (f.rightIndex! < f.right!.length) { const p = f.right![f.rightIndex!] const ps = size(p) - const px = f.cx + f.w! / 2 + H + ps.w / 2 - let pcy = f.py! + ps.h / 2 - - const key = colKey(px) - const occ = columnBottom.get(key) ?? Number.NEGATIVE_INFINITY - const minTop = occ + V - const desiredTop = pcy - ps.h / 2 - - if (desiredTop < minTop) { - pcy = minTop + ps.h / 2 - f.py = pcy - ps.h / 2 - } + const pcx = f.rightX! + ps.w / 2 + const pcy = f.cy - f.childKey = key + f.rightX = f.rightX! + ps.w + H + f.rightBottom = Math.max(f.rightBottom!, pcy + ps.h / 2) f.childPs = ps - stack.push({node: p, cx: px, cy: pcy, phase: 0}) + stack.push({node: p, cx: pcx, cy: pcy, phase: 0}) f.phase = 10 } else { f.bottom = Math.max(f.cy + f.h! / 2, f.rightBottom!) @@ -266,12 +248,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { case 10: { const subBottom = f.lastChildBottom! - columnBottom.set( - f.childKey!, - Math.max(columnBottom.get(f.childKey!) ?? Number.NEGATIVE_INFINITY, subBottom) - ) f.rightBottom = Math.max(f.rightBottom!, subBottom) - f.py = Math.max(f.py! + f.childPs!.h + V, subBottom + V) f.rightIndex!++ f.phase = 1 break @@ -290,7 +267,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { f.gSizes = gSizes f.gx = f.cx - rowW / 2 - f.gy = f.bottom! + V + f.gy = f.bottom! + 2 * V f.rowBottom = f.bottom f.gIndex = 0 f.phase = 3 @@ -386,7 +363,8 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { // Root-Nodes stapeln let yCursor = 0 for (const r of nodes) { - if (!(r.data as any)?.parentNodeId && !r.parentId) { + const link = (r.data as any)?.parentNodeId ?? (r.type === "group" ? (r.data as any)?.nodeId : undefined) + if (!link && !r.parentId) { const b = layoutIter(r, 0, yCursor + size(r).h / 2) yCursor = b + V } diff --git a/src/packages/ce/src/flow/components/panels/FlowPanelControlComponent.tsx b/src/packages/ce/src/flow/components/panels/FlowPanelControlComponent.tsx index a3fc0931..bc592407 100644 --- a/src/packages/ce/src/flow/components/panels/FlowPanelControlComponent.tsx +++ b/src/packages/ce/src/flow/components/panels/FlowPanelControlComponent.tsx @@ -35,6 +35,7 @@ import {AIChatComponent} from "@edition/ai/components/AIChatComponent"; import {mapAiGenerationFlowToFlowInput} from "@edition/ai/util/AI.flow.mapper"; import {toast} from "@code0-tech/pictor/dist/components/toast/Toast"; import {useFlowCompareStore} from "@edition/flow/hooks/Flow.compare.hook"; +import {FunctionNodeComponentProps} from "@edition/function/components/nodes/FunctionNodeComponent"; import {FlowView} from "@edition/flow/services/Flow.view"; import {FlowExecuteDialogComponent} from "@edition/flow/components/FlowExecuteDialogComponent"; @@ -67,11 +68,16 @@ export const FlowPanelControlComponent: React.FC //callbacks const deleteActiveNode = React.useCallback(() => { if (!selectedNode) return + const data = selectedNode.data as FunctionNodeComponentProps // @ts-ignore startTransition(async () => { - await flowService.deleteNodeById(flowId, selectedNode?.id as NodeFunction['id']) + if (data?.isParameter && data?.parentNodeId && data?.parameterIndex != null) { + await flowService.removeParameterMapping(flowId, data.parentNodeId, data.parameterIndex, data.referenceSignature) + } else { + await flowService.deleteNodeById(flowId, selectedNode?.id as NodeFunction['id']) + } }) - }, [selectedNode, flowService, flowStore]) + }, [selectedNode, flowService, flowStore, flowId]) const onAIData = React.useCallback((payload: AiGenerateFlowSubscriptionPayload) => { const aiFlow = payload?.flow diff --git a/src/packages/ce/src/flow/hooks/Flow.edges.hook.ts b/src/packages/ce/src/flow/hooks/Flow.edges.hook.ts index deac3544..b11c94f6 100644 --- a/src/packages/ce/src/flow/hooks/Flow.edges.hook.ts +++ b/src/packages/ce/src/flow/hooks/Flow.edges.hook.ts @@ -1,6 +1,6 @@ import {Edge} from "@xyflow/react"; import React from "react"; -import type {Flow, Namespace, NamespaceProject, NodeFunction} from "@code0-tech/sagittarius-graphql-types"; +import type {Flow, Namespace, NamespaceProject, NodeFunction, SubFlowValue} from "@code0-tech/sagittarius-graphql-types"; import {hashToColor, useService, useStore} from "@code0-tech/pictor"; import {FlowService} from "@edition/flow/services/Flow.service"; import {FunctionService} from "@edition/function/services/Function.service"; @@ -95,19 +95,31 @@ export const useEdges = (flowId: Flow['id'], namespaceId?: Namespace['id'], proj const parameterDefinition = functionService.getById(node.functionDefinition?.id!!)?.parameterDefinitions?.nodes?.[index]; if (!parameterValue) return - if (parameterValue && parameterValue.__typename === "SubFlowValue") { - - if (parameterValue.functionDefinition?.id) { + const subFlowValues: { subFlow: SubFlowValue, key: string }[] = + parameterValue.__typename === "SubFlowValue" + ? [{subFlow: parameterValue, key: `${param.id}`}] + : parameterValue.__typename === "LiteralValue" + ? (parameterValue.references ?? []) + .filter(reference => reference?.value?.__typename === "SubFlowValue") + .map((reference, referenceIndex) => ({ + subFlow: reference!.value as SubFlowValue, + key: `${param.id}-${reference?.signature ?? referenceIndex}` + })) + : [] + + subFlowValues.forEach(({subFlow, key}) => { + + if (!subFlow.startingNodeId && subFlow.functionDefinition?.id) { edges.push({ - id: `${node.id}-${param.id}-next`, - source: `${node.id}-${param.id}`, + id: `${node.id}-${key}-next`, + source: `${node.id}-${key}`, target: node.id!, targetHandle: `param`, deletable: false, selectable: false, animated: true, data: { - color: hashToColor(parameterValue?.startingNodeId || parameterValue.functionDefinition?.id || ""), + color: hashToColor(subFlow?.startingNodeId || subFlow.functionDefinition?.id || ""), type: 'parameter', flowId: flowId } @@ -126,7 +138,7 @@ export const useEdges = (flowId: Flow['id'], namespaceId?: Namespace['id'], proj animated: true, label: parameterDefinition?.names!![0]?.content ?? FALLBACK_FUNCTION_PARAMETER_NAME, data: { - color: hashToColor(parameterValue?.startingNodeId || parameterValue.functionDefinition?.id || ""), + color: hashToColor(subFlow?.startingNodeId || subFlow.functionDefinition?.id || ""), type: 'group', flowId: flowId, parentNodeId: parentNode?.id @@ -135,16 +147,14 @@ export const useEdges = (flowId: Flow['id'], namespaceId?: Namespace['id'], proj (groupsWithValue.get(node.id!) ?? (groupsWithValue.set(node.id!, []), groupsWithValue.get(node.id!)!)).push(groupId); - if (parameterValue.startingNodeId) { + if (subFlow.startingNodeId) { traverse( - flowService.getNodeById(flowId, parameterValue.startingNodeId)!, + flowService.getNodeById(flowId, subFlow.startingNodeId)!, node, true ); } - - - } + }) }); if (node.nextNodeId) { diff --git a/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts b/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts index e9ac0b5e..7e6e6f88 100644 --- a/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts +++ b/src/packages/ce/src/flow/hooks/Flow.nodes.hook.ts @@ -1,5 +1,5 @@ import {Node} from "@xyflow/react"; -import type {Flow, Namespace, NamespaceProject, NodeFunction} from "@code0-tech/sagittarius-graphql-types"; +import type {Flow, Namespace, NamespaceProject, NodeFunction, SubFlowValue} from "@code0-tech/sagittarius-graphql-types"; import React from "react"; import {hashToColor, useService, useStore} from "@code0-tech/pictor"; import {FlowService} from "@edition/flow/services/Flow.service"; @@ -78,56 +78,73 @@ export const useFlowNodes = (flowId: Flow["id"], namespaceId?: Namespace["id"], }) } - node.parameters?.nodes?.forEach((param) => { + node.parameters?.nodes?.forEach((param, parameterIndex) => { const value = param?.value - if (!value || value.__typename !== "SubFlowValue") return - - if (value.functionDefinition?.id) { - nodes.push({ - id: `${nodeId}-${param.id}`, - type: functionDefinition && "design" in functionDefinition ? functionDefinition?.design as string : "square", - position: {x: 0, y: 0}, - draggable: false, - parentId: parentGroup, - extent: parentGroup ? "parent" : undefined, - data: { - isParameter: true, - parameterId: param?.id, - parentNodeId: nodeId, - index: globalIndex, - functionId: value.functionDefinition?.id, - flowId: flowId, - color: hashToColor(value?.startingNodeId ?? value?.functionDefinition?.id ?? ""), - schema: [] - }, - }) - return - } - - const groupId = `${nodeId}-group-${groupCounter++}` - - if (!visited.has(groupId)) { - visited.add(groupId) - - nodes.push({ - id: groupId, - type: "group", - position: {x: 0, y: 0}, - draggable: false, - parentId: parentGroup, - extent: parentGroup ? "parent" : undefined, - data: { - isParameter: true, - nodeId: nodeId, - flowId: flowId, - color: hashToColor(value?.startingNodeId ?? ""), - schema: [] - }, - }) - } - - const child = flowService.getNodeById(flowId, value.startingNodeId) - if (child) traverse(child, groupId) + if (!value) return + + const subFlowValues: { subFlow: SubFlowValue, key: string, signature?: string }[] = + value.__typename === "SubFlowValue" + ? [{subFlow: value, key: `${param?.id}`}] + : value.__typename === "LiteralValue" + ? (value.references ?? []) + .filter(reference => reference?.value?.__typename === "SubFlowValue") + .map((reference, index) => ({ + subFlow: reference!.value as SubFlowValue, + key: `${param?.id}-${reference?.signature ?? index}`, + signature: reference?.signature ?? undefined + })) + : [] + + subFlowValues.forEach(({subFlow, key, signature}) => { + if (!subFlow.startingNodeId && subFlow.functionDefinition?.id) { + nodes.push({ + id: `${nodeId}-${key}`, + type: functionDefinition && "design" in functionDefinition ? functionDefinition?.design as string : "square", + position: {x: 0, y: 0}, + draggable: false, + parentId: parentGroup, + extent: parentGroup ? "parent" : undefined, + data: { + isParameter: true, + parameterIndex: parameterIndex, + referenceSignature: signature, + parentNodeId: nodeId, + index: globalIndex, + functionId: subFlow.functionDefinition?.id, + flowId: flowId, + color: hashToColor(subFlow?.startingNodeId ?? subFlow?.functionDefinition?.id ?? ""), + schema: [] + }, + }) + return + } + + const groupId = `${nodeId}-group-${groupCounter++}` + + if (!visited.has(groupId)) { + visited.add(groupId) + + nodes.push({ + id: groupId, + type: "group", + position: {x: 0, y: 0}, + draggable: false, + selectable: false, + parentId: parentGroup, + extent: parentGroup ? "parent" : undefined, + data: { + isParameter: true, + nodeId: nodeId, + flowId: flowId, + color: hashToColor(subFlow?.startingNodeId ?? ""), + schema: [] + }, + }) + } + + const child = flowService.getNodeById(flowId, subFlow.startingNodeId) + if (child) traverse(child, groupId) + }) }) if (node.nextNodeId) { diff --git a/src/packages/ce/src/flow/services/Flow.service.ts b/src/packages/ce/src/flow/services/Flow.service.ts index 3eeded25..6413a27d 100644 --- a/src/packages/ce/src/flow/services/Flow.service.ts +++ b/src/packages/ce/src/flow/services/Flow.service.ts @@ -5,6 +5,7 @@ import { FlowSetting, FlowType, FunctionDefinition, + InlineReferenceValue, LiteralValue, Maybe, Mutation, @@ -107,26 +108,54 @@ export class FlowService extends ReactiveArrayService value.id === id); } - protected removeParameterNode(flow: FlowView, node: NodeParameter): void { - if (node?.value?.__typename === "SubFlowValue") { - const parameterNode = flow?.nodes?.nodes?.find(n => n?.id === (node.value as SubFlowValue)?.startingNodeId) - if (parameterNode) { - flow!.nodes!.nodes = flow!.nodes!.nodes!.filter(n => n?.id !== (node.value as SubFlowValue)?.startingNodeId) - let nextNodeId = parameterNode.nextNodeId - while (nextNodeId) { - const nextNode = flow!.nodes!.nodes!.find(n => n?.id === nextNodeId) - if (nextNode) { - flow!.nodes!.nodes = flow!.nodes!.nodes!.filter(n => n?.id !== nextNodeId) - nextNodeId = nextNode.nextNodeId - } else { - nextNodeId = null - } + protected removeParameterNode(flow: FlowView, node: NodeParameter, keep?: Set): void { + const value = node?.value + if (value?.__typename === "SubFlowValue") { + this.removeSubFlowNodes(flow, value, keep) + } else if (value?.__typename === "LiteralValue") { + (value.references ?? []).forEach(reference => { + if (reference?.value?.__typename === "SubFlowValue") { + this.removeSubFlowNodes(flow, reference.value as SubFlowValue, keep) } - parameterNode.parameters?.nodes?.forEach(p => { - this.removeParameterNode(flow, p!!) - }) + }) + } + } + + private removeSubFlowNodes(flow: FlowView, subFlow: SubFlowValue, keep?: Set): void { + if (subFlow?.startingNodeId && keep?.has(subFlow.startingNodeId)) return + + const parameterNode = flow?.nodes?.nodes?.find(n => n?.id === subFlow?.startingNodeId) + if (!parameterNode) return + + flow!.nodes!.nodes = flow!.nodes!.nodes!.filter(n => n?.id !== subFlow?.startingNodeId) + let nextNodeId = parameterNode.nextNodeId + while (nextNodeId) { + const nextNode = flow!.nodes!.nodes!.find(n => n?.id === nextNodeId) + if (nextNode) { + flow!.nodes!.nodes = flow!.nodes!.nodes!.filter(n => n?.id !== nextNodeId) + nextNodeId = nextNode.nextNodeId + } else { + nextNodeId = null } } + parameterNode.parameters?.nodes?.forEach(p => { + this.removeParameterNode(flow, p!!, keep) + }) + } + + private collectStartingNodeIds(value?: Maybe): Set { + const ids = new Set() + if (!value) return ids + if (value.__typename === "SubFlowValue" && value.startingNodeId) { + ids.add(value.startingNodeId) + } else if (value.__typename === "LiteralValue") { + (value.references ?? []).forEach(reference => { + if (reference?.value?.__typename === "SubFlowValue" && (reference.value as SubFlowValue).startingNodeId) { + ids.add((reference.value as SubFlowValue).startingNodeId!) + } + }) + } + return ids } getNodeById(flowId: FlowView['id'], nodeId: NodeFunction['id']): NodeFunction | undefined { @@ -232,11 +261,40 @@ export class FlowService extends ReactiveArrayService { const flow = this.getById(flowId) const node = this.getNodeById(flowId, nodeId) - const parentNode = flow?.nodes?.nodes?.find(node => node?.parameters?.nodes?.find(p => p?.value?.__typename === "SubFlowValue" && (p.value as SubFlowValue)?.startingNodeId === nodeId)) const previousNodes = flow?.nodes?.nodes?.find(n => n?.nextNodeId === nodeId) const index = this.values().findIndex(f => f.id === flowId) if (!flow || !node) return + let parentNode: Maybe | undefined + let parentParameter: Maybe | undefined + let parentSubFlow: SubFlowValue | undefined + let parentLiteral: LiteralValue | undefined + let parentReference: InlineReferenceValue | undefined + + for (const candidate of flow.nodes?.nodes ?? []) { + for (const parameter of candidate?.parameters?.nodes ?? []) { + const value = parameter?.value + if (value?.__typename === "SubFlowValue" && value.startingNodeId === nodeId) { + parentNode = candidate + parentParameter = parameter + parentSubFlow = value + break + } + if (value?.__typename === "LiteralValue") { + const reference = value.references?.find(r => r?.value?.__typename === "SubFlowValue" && (r.value as SubFlowValue).startingNodeId === nodeId) + if (reference) { + parentNode = candidate + parentParameter = parameter + parentLiteral = value + parentReference = reference + parentSubFlow = reference.value as SubFlowValue + break + } + } + } + if (parentNode) break + } + flow.nodes!.nodes = flow.nodes!.nodes!.filter(n => n?.id !== nodeId) node.parameters?.nodes?.forEach(p => this.removeParameterNode(flow, p!!)) @@ -247,13 +305,44 @@ export class FlowService extends ReactiveArrayService p?.value?.__typename === "SubFlowValue" && (p.value as SubFlowValue)?.startingNodeId === nodeId) - if (parameter && parameter.value?.__typename === "SubFlowValue" && node.nextNodeId) { - parameter.value.startingNodeId = node.nextNodeId - } else if (parameter) { - parameter.value = undefined + if (parentSubFlow) { + if (node.nextNodeId) { + parentSubFlow.startingNodeId = node.nextNodeId + } else if (parentLiteral && parentReference) { + parentLiteral.references = (parentLiteral.references ?? []).filter(r => r !== parentReference) + parentLiteral.value = (parentLiteral.value ?? []).filter((v: unknown) => v !== `\${${parentReference!.signature}}`) + } else if (parentParameter) { + parentParameter.value = undefined + } + } + + flow.editedAt = new Date().toISOString() + + this.set(index, new View(flow)) + await this.syncFlow(flowId) + } + + async removeParameterMapping(flowId: FlowView['id'], parentNodeId: NodeFunction['id'], parameterIndex: number, referenceSignature?: string): Promise { + const flow = this.getById(flowId) + const index = this.values().findIndex(f => f.id === flowId) + if (!flow) return + + const node = flow.nodes?.nodes?.find(n => n?.id === parentNodeId) + const parameter = node?.parameters?.nodes?.[parameterIndex] + if (!parameter) return + + const value = parameter.value + if (value?.__typename === "LiteralValue" && referenceSignature) { + const reference = value.references?.find(r => r?.signature === referenceSignature) + if (reference?.value?.__typename === "SubFlowValue") { + this.removeSubFlowNodes(flow, reference.value as SubFlowValue) } + value.references = (value.references ?? []).filter(r => r?.signature !== referenceSignature) + value.value = (value.value ?? []).filter((v: unknown) => v !== `\${${referenceSignature}}`) + if ((value.references?.length ?? 0) === 0) parameter.value = undefined + } else if (value?.__typename === "SubFlowValue") { + this.removeSubFlowNodes(flow, value) + parameter.value = undefined } flow.editedAt = new Date().toISOString() @@ -407,7 +496,7 @@ export class FlowService extends ReactiveArrayService, Com compareType?: 'added' | 'removed' | 'changed' color: string parentNodeId?: NodeFunction['id'] + parameterIndex?: number + referenceSignature?: string isParameter?: boolean index?: number } \ No newline at end of file diff --git a/src/packages/ce/src/function/components/nodes/FunctionNodeSquareComponent.tsx b/src/packages/ce/src/function/components/nodes/FunctionNodeSquareComponent.tsx index f325f99a..89df41c2 100644 --- a/src/packages/ce/src/function/components/nodes/FunctionNodeSquareComponent.tsx +++ b/src/packages/ce/src/function/components/nodes/FunctionNodeSquareComponent.tsx @@ -2,7 +2,7 @@ import {Handle, Node, NodeProps, Position, useStore} from "@xyflow/react"; import React, {CSSProperties, memo} from "react"; import "./FunctionNodeComponent.style.scss"; import {FunctionNodeComponentProps} from "./FunctionNodeComponent"; -import {Card, Flex, Text, useService, useStore as usePictorStore} from "@code0-tech/pictor"; +import {Card, Flex, getSize, Text, useService, useStore as usePictorStore} from "@code0-tech/pictor"; import {useFlowValidation} from "@edition/flow/hooks/Flow.validation.hook"; import {IconVariable} from "@tabler/icons-react"; import {FlowService} from "@edition/flow/services/Flow.service"; @@ -135,7 +135,14 @@ export const FunctionNodeSquareComponent: React.FC - {definition?.names?.[0]?.content ?? FALLBACK_FUNCTION_NAME} + {definition?.names?.[0]?.content ?? FALLBACK_FUNCTION_NAME} ); }) diff --git a/src/packages/ce/src/function/components/suggestion/SuggestionDialogComponent.tsx b/src/packages/ce/src/function/components/suggestion/SuggestionDialogComponent.tsx index 86a15b04..0317c0e9 100644 --- a/src/packages/ce/src/function/components/suggestion/SuggestionDialogComponent.tsx +++ b/src/packages/ce/src/function/components/suggestion/SuggestionDialogComponent.tsx @@ -114,7 +114,7 @@ export const SuggestionDialogComponent: React.FC const DisplayIcon = icon(suggestion.icon as IconString) return <> - typeof keyword === "string")} display={"block"} my={0.7} style={{boxSizing: "border-box", overflow: "hidden"}} diff --git a/src/packages/core/src/util/playground-mock.ts b/src/packages/core/src/util/playground-mock.ts index 7e72399b..afec1812 100644 --- a/src/packages/core/src/util/playground-mock.ts +++ b/src/packages/core/src/util/playground-mock.ts @@ -167,13 +167,40 @@ const mapFlowType = (flowType: FlowType, id: string, runtimeFlowTypeId: string, } }) -const mapNodeValue = (value?: NodeValue) => { +const mapNodeValue = (value?: NodeValue, functionId: Map = new Map()): Record | null => { const inner = value?.value - if (inner?.oneofKind === "literalValue") return literalValue(inner.literalValue.value) + if (inner?.oneofKind === "literalValue") return { + __typename: "LiteralValue", + value: plainValue(inner.literalValue.value), + references: inner.literalValue.references.map(reference => ({ + __typename: "InlineReferenceValue", + signature: reference.signature, + value: mapNodeValue(reference.value, functionId) + })) + } + if (inner?.oneofKind === "subFlow") { + const executionReference = inner.subFlow.executionReference + if (executionReference.oneofKind === "startingNodeId") return { + __typename: "SubFlowValue", + startingNodeId: gid("NodeFunction", executionReference.startingNodeId) + } + if (executionReference.oneofKind === "function") { + const identifier = executionReference.function.functionIdentifier + return { + __typename: "SubFlowValue", + functionDefinition: { + __typename: "FunctionDefinition", + id: functionId.get(identifier) ?? null, + identifier + } + } + } + return {__typename: "SubFlowValue"} + } return null } -const mapNodeParameter = (parameter: NodeParameter, functionRuntimeId: string, parameterId: Map) => ({ +const mapNodeParameter = (parameter: NodeParameter, functionRuntimeId: string, parameterId: Map, functionId: Map) => ({ __typename: "NodeParameter", id: gid("NodeParameter", parameter.databaseId), createdAt: TIMESTAMP, @@ -185,11 +212,11 @@ const mapNodeParameter = (parameter: NodeParameter, functionRuntimeId: string, p createdAt: TIMESTAMP, updatedAt: TIMESTAMP }, - value: mapNodeValue(parameter.value) + value: mapNodeValue(parameter.value, functionId) }) const mapNodeFunction = (node: NodeFunction, functionId: Map, parameterId: Map) => { - const parameterNodes = node.parameters.map(parameter => mapNodeParameter(parameter, node.runtimeFunctionId, parameterId)) + const parameterNodes = node.parameters.map(parameter => mapNodeParameter(parameter, node.runtimeFunctionId, parameterId, functionId)) return { __typename: "NodeFunction", id: gid("NodeFunction", node.databaseId ?? 0),