From 464749ba13be95e408147efe19c9c98afbd0493e Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 18:24:10 +0100 Subject: [PATCH 001/197] feat: adjust box-shadow style for active state in DFlowFunctionDefaultCard --- .../d-flow/function/DFlowFunctionDefaultCard.style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/d-flow/function/DFlowFunctionDefaultCard.style.scss b/src/components/d-flow/function/DFlowFunctionDefaultCard.style.scss index 41ab7f275..855e63a22 100644 --- a/src/components/d-flow/function/DFlowFunctionDefaultCard.style.scss +++ b/src/components/d-flow/function/DFlowFunctionDefaultCard.style.scss @@ -23,6 +23,6 @@ } &--active { - box-shadow: 0 0 0 2px helpers.borderColor(variables.$info); + box-shadow: 0 0 0 1px rgba(variables.$info, .5); } } \ No newline at end of file From 2ceddf250ee5315d9c9bd80c297d21b3b10b6507 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 18:24:16 +0100 Subject: [PATCH 002/197] feat: remove 'Add next' text from Button in DFlowFunctionSuggestionCard --- src/components/d-flow/function/DFlowFunctionSuggestionCard.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/d-flow/function/DFlowFunctionSuggestionCard.tsx b/src/components/d-flow/function/DFlowFunctionSuggestionCard.tsx index 5ad49e31f..07904adf7 100644 --- a/src/components/d-flow/function/DFlowFunctionSuggestionCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionSuggestionCard.tsx @@ -44,7 +44,6 @@ export const DFlowFunctionSuggestionCard: React.FC - Add next }/> From 2b70ea63b217d093e2d0c2692235fcba762c847a Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 18:24:21 +0100 Subject: [PATCH 003/197] feat: update Badge color and text in DFlowFunctionTriggerCard for clarity --- src/components/d-flow/function/DFlowFunctionTriggerCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx b/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx index 5ba496987..58a5575bf 100644 --- a/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx @@ -33,9 +33,9 @@ export const DFlowFunctionTriggerCard: React.FC = const viewportHeight = useStore(s => s.height) return - START + Starting node { From 7b700b33c60706c768125f5728ab171e307408a5 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 18:38:42 +0100 Subject: [PATCH 004/197] feat: add aliases and displayMessages properties to FunctionDefinitionView --- .../d-flow/function/DFlowFunction.view.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/components/d-flow/function/DFlowFunction.view.ts b/src/components/d-flow/function/DFlowFunction.view.ts index 857e1ad63..dfb1215de 100644 --- a/src/components/d-flow/function/DFlowFunction.view.ts +++ b/src/components/d-flow/function/DFlowFunction.view.ts @@ -9,18 +9,24 @@ import type { export class FunctionDefinitionView { + /** Name of the function */ + private readonly _aliases?: Maybe; /** Time when this FunctionDefinition was created */ private readonly _createdAt?: Maybe; /** Deprecation message of the function */ private readonly _deprecationMessages?: Maybe; /** Description of the function */ private readonly _descriptions?: Maybe; + /** Display message of the function */ + private readonly _displayMessages?: Maybe; /** Documentation of the function */ private readonly _documentations?: Maybe; /** Generic keys of the function */ private readonly _genericKeys?: Maybe>; /** Global ID of this FunctionDefinition */ private readonly _id?: Maybe; + /** Identifier of the function */ + private readonly _identifier?: Maybe; /** Name of the function */ private readonly _names?: Maybe; /** Parameters of the function */ @@ -35,12 +41,15 @@ export class FunctionDefinitionView { private readonly _updatedAt?: Maybe; constructor(object: FunctionDefinition) { + this._aliases = object.aliases; this._createdAt = object.createdAt; this._deprecationMessages = object.deprecationMessages; this._descriptions = object.descriptions; + this._displayMessages = object.displayMessages; this._documentations = object.documentations; this._genericKeys = object.genericKeys; this._id = object.id; + this._identifier = object.identifier; this._names = object.names; this._parameterDefinitions = object.parameterDefinitions?.nodes?.map(definition => new ParameterDefinitionView(definition!!)) ?? undefined; this._returnType = object.returnType; @@ -50,6 +59,10 @@ export class FunctionDefinitionView { } + get aliases(): Maybe | undefined { + return this._aliases; + } + get createdAt(): Maybe | undefined { return this._createdAt; } @@ -62,6 +75,10 @@ export class FunctionDefinitionView { return this._descriptions; } + get displayMessages(): Maybe | undefined { + return this._displayMessages; + } + get documentations(): Maybe | undefined { return this._documentations; } @@ -74,6 +91,10 @@ export class FunctionDefinitionView { return this._id; } + get identifier(): Maybe | undefined { + return this._identifier; + } + get names(): Maybe | undefined { return this._names; } @@ -100,12 +121,15 @@ export class FunctionDefinitionView { json(): FunctionDefinition { return { + aliases: this._aliases, createdAt: this._createdAt, deprecationMessages: this._deprecationMessages, + displayMessages: this._displayMessages, descriptions: this._descriptions, documentations: this._documentations, genericKeys: this._genericKeys, id: this._id, + identifier: this._identifier, names: this._names, parameterDefinitions: this._parameterDefinitions ? { nodes: this._parameterDefinitions.map(definitionView => definitionView.json()!!) From 0eff5ba231bbbdee37154ac63084e8d3aa45389b Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 18:39:44 +0100 Subject: [PATCH 005/197] feat: add identifier property to ParameterDefinition for improved clarity --- src/components/d-flow/function/DFlowFunction.view.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/components/d-flow/function/DFlowFunction.view.ts b/src/components/d-flow/function/DFlowFunction.view.ts index dfb1215de..c8bcc0910 100644 --- a/src/components/d-flow/function/DFlowFunction.view.ts +++ b/src/components/d-flow/function/DFlowFunction.view.ts @@ -154,6 +154,8 @@ export class ParameterDefinitionView { private readonly _documentations?: Maybe; /** Global ID of this ParameterDefinition */ private readonly _id?: Maybe; + /** Identifier of the parameter */ + private readonly _identifier?: Maybe; /** Name of the parameter */ private readonly _names?: Maybe; /** Time when this ParameterDefinition was last updated */ @@ -165,6 +167,7 @@ export class ParameterDefinitionView { this._descriptions = object.descriptions; this._documentations = object.documentations; this._id = object.id; + this._identifier = object.identifier; this._names = object.names; this._updatedAt = object.updatedAt; } @@ -190,6 +193,10 @@ export class ParameterDefinitionView { return this._id; } + get identifier(): Maybe | undefined { + return this._identifier; + } + get names(): Maybe | undefined { return this._names; } From a183d8cfc324b231ba31186d324a0888344ce563 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 18:41:40 +0100 Subject: [PATCH 006/197] feat: add aliases and displayMessages properties to FlowTypeView for enhanced functionality --- src/components/d-flow/type/DFlowType.view.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/components/d-flow/type/DFlowType.view.ts b/src/components/d-flow/type/DFlowType.view.ts index efa9c09dc..f5b98b757 100644 --- a/src/components/d-flow/type/DFlowType.view.ts +++ b/src/components/d-flow/type/DFlowType.view.ts @@ -10,10 +10,14 @@ import type { export class FlowTypeView { + /** Name of the function */ + private readonly _aliases?: Maybe; /** Time when this FlowType was created */ private readonly _createdAt?: Maybe; /** Descriptions of the flow type */ private readonly _descriptions?: Maybe; + /** Display message of the function */ + private readonly _displayMessages?: Maybe; /** Editable status of the flow type */ private readonly _editable?: Maybe; /** Flow type settings of the flow type */ @@ -33,8 +37,10 @@ export class FlowTypeView { constructor(flowType: FlowType) { + this._aliases = flowType.aliases; this._createdAt = flowType.createdAt; this._descriptions = flowType.descriptions; + this._displayMessages = flowType.displayMessages; this._editable = flowType.editable; this._flowTypeSettings = flowType.flowTypeSettings; this._id = flowType.id; @@ -45,6 +51,9 @@ export class FlowTypeView { this._updatedAt = flowType.updatedAt; } + get aliases(): Maybe | undefined { + return this._aliases; + } get createdAt(): Maybe | undefined { return this._createdAt; @@ -54,6 +63,10 @@ export class FlowTypeView { return this._descriptions; } + get displayMessages(): Maybe | undefined { + return this._displayMessages; + } + get editable(): Maybe | undefined { return this._editable; } From 7ad4c9a6391c5bf58dcf6784213f79497a14ffd3 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 18:44:05 +0100 Subject: [PATCH 007/197] feat: add aliases and displayMessages properties to DataTypeView for enhanced functionality --- .../d-flow/data-type/DFlowDataType.view.ts | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/components/d-flow/data-type/DFlowDataType.view.ts b/src/components/d-flow/data-type/DFlowDataType.view.ts index 51d536cce..e8fb3392b 100644 --- a/src/components/d-flow/data-type/DFlowDataType.view.ts +++ b/src/components/d-flow/data-type/DFlowDataType.view.ts @@ -10,8 +10,12 @@ import type { */ export class DataTypeView { + /** Name of the function */ + private readonly _aliases?: Maybe; /** Time when this DataType was created */ private readonly _createdAt?: Maybe; + /** Display message of the function */ + private readonly _displayMessages?: Maybe; /** Generic keys of the datatype */ private readonly _genericKeys?: Maybe>; /** Global ID of this DataType */ @@ -20,32 +24,41 @@ export class DataTypeView { private readonly _identifier?: Maybe; /** Names of the flow type setting */ private readonly _name?: Maybe; - /** The namespace where this datatype belongs to */ - private readonly _runtime?: Maybe; /** Rules of the datatype */ private readonly _rules?: Maybe; + /** The namespace where this datatype belongs to */ + private readonly _runtime?: Maybe; /** Time when this DataType was last updated */ private readonly _updatedAt?: Maybe; /** The type of the datatype */ private readonly _variant?: Maybe; constructor(dataType: DataType) { - this._id = dataType.id - this._createdAt = dataType.createdAt - this._updatedAt = dataType.updatedAt - this._identifier = dataType.identifier - this._name = dataType.name ?? undefined - this._runtime = dataType.runtime ?? undefined - this._variant = dataType.variant - this._genericKeys = dataType.genericKeys ?? undefined - this._rules = dataType.rules ?? undefined + this._aliases = dataType.aliases; + this._createdAt = dataType.createdAt; + this._displayMessages = dataType.displayMessages; + this._genericKeys = dataType.genericKeys; + this._id = dataType.id; + this._identifier = dataType.identifier; + this._name = dataType.name; + this._runtime = dataType.runtime; + this._rules = dataType.rules; + this._updatedAt = dataType.updatedAt; + this._variant = dataType.variant; + } + get aliases(): Maybe | undefined { + return this._aliases; } get createdAt(): Maybe | undefined { return this._createdAt; } + get displayMessages(): Maybe | undefined { + return this._displayMessages; + } + get genericKeys(): Maybe> | undefined { return this._genericKeys; } From b91df75e51c66b69252a4434f911dfc8275f5f3e Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 18:46:18 +0100 Subject: [PATCH 008/197] feat: update styles for DFlowSuggestionSearchInput to improve layout and appearance --- .../d-flow/suggestion/DFlowSuggestionSearchInput.style.scss | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/d-flow/suggestion/DFlowSuggestionSearchInput.style.scss b/src/components/d-flow/suggestion/DFlowSuggestionSearchInput.style.scss index dff4426a3..0dec14c1f 100644 --- a/src/components/d-flow/suggestion/DFlowSuggestionSearchInput.style.scss +++ b/src/components/d-flow/suggestion/DFlowSuggestionSearchInput.style.scss @@ -2,10 +2,9 @@ @use "../../../styles/helpers"; .d-flow-suggestion-search-input { - box-shadow: none !important; + border: none !important; background: none !important; - margin-left: -1 * variables.$xxs; - margin-right: -1 * variables.$xxs; + margin: -1 * variables.$xxs; padding-left: variables.$xxs; padding-right: variables.$xxs; border-radius: 0 !important; From f9d304cd86c1cab51f80e33843f773624023d6c8 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 19:14:40 +0100 Subject: [PATCH 009/197] feat: adjust gap constants in DFlow for improved layout consistency --- src/components/d-flow/DFlow.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/d-flow/DFlow.tsx b/src/components/d-flow/DFlow.tsx index aeb5f18ab..1d495e556 100644 --- a/src/components/d-flow/DFlow.tsx +++ b/src/components/d-flow/DFlow.tsx @@ -34,8 +34,8 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { return {nodes}; } /* Konstanten */ - const V = 75; // vertical gap Node ↕ Node - const H = 75; // horizontal gap Parent → Param + const V = 50; // vertical gap Node ↕ Node + const H = 50; // horizontal gap Parent → Param const PAD = 16; // inner padding einer Group (links+rechts / oben+unten) const EPS = 0.25; // Toleranz gegen Rundungsdrift From 360ff8e75e69730345c72abe2f1500e69e60d875 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 19:14:44 +0100 Subject: [PATCH 010/197] feat: enhance DFlowEdge with gradient support and improved label rendering --- src/components/d-flow/edge/DFlowEdge.tsx | 88 ++++++++++++++++++------ 1 file changed, 66 insertions(+), 22 deletions(-) diff --git a/src/components/d-flow/edge/DFlowEdge.tsx b/src/components/d-flow/edge/DFlowEdge.tsx index b42109152..21ea66e99 100644 --- a/src/components/d-flow/edge/DFlowEdge.tsx +++ b/src/components/d-flow/edge/DFlowEdge.tsx @@ -1,5 +1,12 @@ import {Code0Component} from "../../../utils/types"; -import {BaseEdge, Edge, EdgeLabelRenderer, EdgeProps, getSmoothStepPath, Position} from "@xyflow/react"; +import { + BaseEdge, + Edge, + EdgeLabelRenderer, + EdgeProps, + getSmoothStepPath, + Position +} from "@xyflow/react"; import React, {memo} from "react"; import {Badge} from "../../badge/Badge"; @@ -14,8 +21,17 @@ export interface DFlowEdgeDataProps extends Code0Component { export type DFlowEdgeProps = EdgeProps> export const DFlowEdge: React.FC = memo((props) => { - - const {sourceX, sourceY, targetX, targetY, id, data, ...rest} = props + const { + sourceX, + sourceY, + targetX, + targetY, + id, + data, + label, + style, + ...rest + } = props const [edgePath, labelX, labelY] = getSmoothStepPath({ sourceX, @@ -25,26 +41,54 @@ export const DFlowEdge: React.FC = memo((props) => { targetY, targetPosition: data?.isParameter ? Position.Right : Position.Top, borderRadius: 16, - centerY: data?.isSuggestion ? targetY - 37.5 : targetY - 37.5 + centerY: data?.isSuggestion ? targetY - 25 : targetY - 25 }) - return <> - - {props.label ? ( - -
- - {props.label} - -
-
- ) : null} - - + const color = data?.color ?? "#ffffff" + const gradientId = `dflow-edge-gradient-${id}` + + return ( + <> + {/* Gradient-Definition für genau diese Edge */} + + + {/* Start: volle Farbe */} + + {/* Ende: gleiche Farbe, aber transparent */} + + + + + + {label ? ( + +
+ + {label} + +
+
+ ) : null} + + ) }) \ No newline at end of file From e95092350d77aeeefac52b5d4c530df38536d562 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 22:42:08 +0100 Subject: [PATCH 011/197] feat: update DFlowMiniMap styles to use fit-content width for improved layout --- src/components/d-flow/minimap/DFlowMiniMap.style.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/d-flow/minimap/DFlowMiniMap.style.scss b/src/components/d-flow/minimap/DFlowMiniMap.style.scss index 329c940e4..f60a9f8c4 100644 --- a/src/components/d-flow/minimap/DFlowMiniMap.style.scss +++ b/src/components/d-flow/minimap/DFlowMiniMap.style.scss @@ -8,7 +8,7 @@ @include box.box(variables.$primary); @include helpers.borderRadius(); margin: 0; - width: 100%; + width: fit-content; } > svg { From a5a0d38c068a10433fd4c541c189a47d83b98d46 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 22:42:14 +0100 Subject: [PATCH 012/197] feat: wrap MiniMap in a Panel for improved layout and positioning --- .../d-flow/minimap/DFlowMiniMap.tsx | 54 ++++++++++--------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/src/components/d-flow/minimap/DFlowMiniMap.tsx b/src/components/d-flow/minimap/DFlowMiniMap.tsx index 6560c81d6..486ff5b72 100644 --- a/src/components/d-flow/minimap/DFlowMiniMap.tsx +++ b/src/components/d-flow/minimap/DFlowMiniMap.tsx @@ -1,5 +1,5 @@ import React from "react"; -import {MiniMap, useNodes} from "@xyflow/react"; +import {MiniMap, Panel, useNodes} from "@xyflow/react"; import {FLOW_EDGE_RAINBOW} from "../DFlow.edges.hook"; import "./DFlowMiniMap.style.scss" @@ -7,40 +7,42 @@ export const DFlowMiniMap: React.FC = (props) => { const nodes = useNodes(); - return { + return + { - const node = nodes.find(node => node.id === props1.id) - if (!node) return null + const node = nodes.find(node => node.id === props1.id) + if (!node) return null - if (node.type == "suggestion") return null + if (node.type == "suggestion") return null - if (node.type == "group") { + if (node.type == "group") { - const depth = (node.data as any)?.depth ?? 0; - const color = FLOW_EDGE_RAINBOW[depth % FLOW_EDGE_RAINBOW.length]; + const depth = (node.data as any)?.depth ?? 0; + const color = FLOW_EDGE_RAINBOW[depth % FLOW_EDGE_RAINBOW.length]; + + return + } return - } - - return - }}/> + }}/> + } \ No newline at end of file From f66e7d0eaa57d043c30545517eb4e85aaa5b1c0f Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 22:42:21 +0100 Subject: [PATCH 013/197] feat: enhance DFlowValidation component with improved validation display and styling --- .../d-flow/validation/DFlowValidation.tsx | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/components/d-flow/validation/DFlowValidation.tsx b/src/components/d-flow/validation/DFlowValidation.tsx index 461a635f1..56cc7d519 100644 --- a/src/components/d-flow/validation/DFlowValidation.tsx +++ b/src/components/d-flow/validation/DFlowValidation.tsx @@ -1,12 +1,13 @@ import {Panel} from "@xyflow/react"; import React from "react"; -import {useDFlowValidations} from "./DFlowValidation.hook"; import type {Scalars} from "@code0-tech/sagittarius-graphql-types"; import {Flex} from "../../flex/Flex"; -import {InspectionSeverity} from "../../../utils/inspection"; +import {InspectionSeverity, ValidationResult} from "../../../utils/inspection"; import {Badge} from "../../badge/Badge"; import {IconAlertTriangle, IconExclamationCircle, IconMessageExclamation} from "@tabler/icons-react"; import "./DFlowValidation.style.scss" +import {Text} from "../../text/Text"; +import {useDFlowValidations} from "./DFlowValidation.hook"; export interface DFlowValidationProps { flowId: Scalars['FlowID']['output'] @@ -15,34 +16,34 @@ export interface DFlowValidationProps { export const DFlowValidation: React.FC = (props) => { const {flowId} = props - const validations = useDFlowValidations(flowId) + const validations: ValidationResult[] = useDFlowValidations(flowId) return {(validations?.length ?? 0) > 0 ? (
- + {(validations?.filter(v => v.type === InspectionSeverity.ERROR)?.length ?? 0) > 0 ? ( - + - - {validations?.filter(v => v.type === InspectionSeverity.ERROR)?.length} + + {validations?.filter(v => v.type === InspectionSeverity.ERROR)?.length} ) : null} {(validations?.filter(v => v.type === InspectionSeverity.WARNING)?.length ?? 0) > 0 ? ( - + - + {validations?.filter(v => v.type === InspectionSeverity.WARNING)?.length} ) : null} {(validations?.filter(v => v.type === InspectionSeverity.GRAMMAR)?.length ?? 0) > 0 ? ( - + - + {validations?.filter(v => v.type === InspectionSeverity.GRAMMAR)?.length} From 6793ee59d74c46d78b13b9dca808d2e652a1025a Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 22:42:30 +0100 Subject: [PATCH 014/197] feat: refactor DLayout styles by removing redundant padding and border properties --- src/components/d-layout/DLayout.style.scss | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/components/d-layout/DLayout.style.scss b/src/components/d-layout/DLayout.style.scss index 43d619084..c6955ebb2 100644 --- a/src/components/d-layout/DLayout.style.scss +++ b/src/components/d-layout/DLayout.style.scss @@ -31,16 +31,6 @@ gap: variables.$xl; } -.d-layout__left { - padding-right: variables.$xl; - border-right: 1px solid helpers.borderColor(); -} - -.d-layout__right { - padding-left: variables.$xl; - border-left: 1px solid helpers.borderColor(); -} - .d-layout__left, .d-layout__right { flex: 0 0 auto; From a407d6c65ac77307f29a5e19600869ea6fb96202 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 22:42:36 +0100 Subject: [PATCH 015/197] feat: update DResizable story to enhance layout with new segmented control and improved panel structure --- .../d-resizable/DResizable.stories.tsx | 116 ++++++------------ 1 file changed, 40 insertions(+), 76 deletions(-) diff --git a/src/components/d-resizable/DResizable.stories.tsx b/src/components/d-resizable/DResizable.stories.tsx index 5617480b9..1ce776286 100644 --- a/src/components/d-resizable/DResizable.stories.tsx +++ b/src/components/d-resizable/DResizable.stories.tsx @@ -3,7 +3,14 @@ import {DResizableHandle, DResizablePanel, DResizablePanelGroup} from "./DResiza import React from "react"; import {DFullScreen} from "../d-fullscreen/DFullScreen"; import {Button} from "../button/Button"; -import {IconDatabase, IconHierarchy3, IconSettings, IconTicket} from "@tabler/icons-react"; +import { + IconArrowsVertical, + IconDatabase, + IconHierarchy3, IconLayout, + IconLayoutDistributeHorizontal, IconLayoutDistributeVertical, + IconSettings, + IconTicket +} from "@tabler/icons-react"; import {Flex} from "../flex/Flex"; import {Tooltip, TooltipContent, TooltipPortal, TooltipTrigger} from "../tooltip/Tooltip"; import {FunctionDefinitionView} from "../d-flow/function/DFlowFunction.view"; @@ -27,7 +34,7 @@ import FlowTypeData from "./flow_types.json"; import {useFlowNodes} from "../d-flow/DFlow.nodes.hook"; import {useFlowEdges} from "../d-flow/DFlow.edges.hook"; import {DFlow} from "../d-flow/DFlow"; -import {Background, BackgroundVariant} from "@xyflow/react"; +import {Background, BackgroundVariant, Panel} from "@xyflow/react"; import {DFlowControl} from "../d-flow/control/DFlowControl"; import {DFlowValidation} from "../d-flow/validation/DFlowValidation"; import {DLayout} from "../d-layout/DLayout"; @@ -36,7 +43,8 @@ import { NamespacesProjectsFlowsCreateInput, NamespacesProjectsFlowsCreatePayload, NamespacesProjectsFlowsDeleteInput, NamespacesProjectsFlowsDeletePayload } from "@code0-tech/sagittarius-graphql-types"; -import {DFlowExport} from "../d-flow/export/DFlowExport"; +import {DFlowMiniMap} from "../d-flow"; +import {SegmentedControl, SegmentedControlItem} from "../segmented-control/SegmentedControl"; const meta: Meta = { title: "Dashboard Resizable", @@ -73,7 +81,7 @@ export const Dashboard = () => { const [flowStore, flowService] = useReactiveArrayService(DFlowReactiveServiceExtend, [new FlowView({ id: "gid://sagittarius/Flow/1", type: { - id: "gid://sagittarius/FlowType/867", + id: "gid://sagittarius/TypesFlowType/842", }, name: "de/codezero/examples/REST Flow", settings: { @@ -90,78 +98,22 @@ export const Dashboard = () => { // @ts-ignore const [flowTypeStore, flowTypeService] = useReactiveArrayService(DFlowTypeReactiveService, [...FlowTypeData.map(data => new FlowTypeView(data))]); - return + return - - - - - - - - - All Flows - - - - - - - - - - Issue Management - - - - - - - - - - Database - - - - -
- - - - - - - Settings - - - -
-
}> - }> - - - - - - - - - - - + + + + + + + + + + + + + @@ -180,7 +132,19 @@ const FlowExample = () => { - - {/**/} + + + + + + + + + + + + + + {/**/} } \ No newline at end of file From e2d27cdc67f6fab337cfd19c8b3164647ffd65a7 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 22:42:41 +0100 Subject: [PATCH 016/197] feat: refine DResizable styles by adjusting border properties and handle bar display --- .../d-resizable/DResizable.style.scss | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/components/d-resizable/DResizable.style.scss b/src/components/d-resizable/DResizable.style.scss index 8a545896f..1f983be83 100644 --- a/src/components/d-resizable/DResizable.style.scss +++ b/src/components/d-resizable/DResizable.style.scss @@ -13,7 +13,7 @@ & { @include box.box(variables.$primary); - @include helpers.borderRadius(); + border: none; } &:has(.d-resizable__panel) { @@ -29,8 +29,9 @@ justify-content: center; &[data-panel-group-direction=horizontal] { - width: 1rem; + width: 1px; height: 100%; + background: helpers.borderColor(); } &[data-panel-group-direction=vertical] { @@ -42,25 +43,14 @@ &__handle-bar { z-index: 1; position: relative; - display: block; + display: flex; padding: .25rem; + border-radius: variables.$xs; & { - @include box.box(variables.$white, variables.$white, variables.$white); - @include helpers.borderRadius(); + @include box.box(variables.$secondary); } } - &[data-panel-group-direction=horizontal] * &__handle-bar { - min-height: 3rem; - height: 15%; - } - - &[data-panel-group-direction=vertical] * &__handle-bar { - min-width: 3rem; - min-height: auto; - height: auto; - width: 15%; - } } \ No newline at end of file From c2ecadc772669d5de59d31c4405f6ac396b86acf Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 22:42:46 +0100 Subject: [PATCH 017/197] feat: add grip icon to DResizable handle for improved user interaction --- src/components/d-resizable/DResizable.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/d-resizable/DResizable.tsx b/src/components/d-resizable/DResizable.tsx index 414d9698b..9ca07f4f3 100644 --- a/src/components/d-resizable/DResizable.tsx +++ b/src/components/d-resizable/DResizable.tsx @@ -12,6 +12,7 @@ import { PanelResizeHandleProps } from "react-resizable-panels"; import "./DResizable.style.scss" +import {IconFolder, IconGripVertical} from "@tabler/icons-react"; type DResizablePanelGroupProps = Code0ComponentProps & PanelGroupProps type DResizablePanelProps = Code0ComponentProps & PanelProps @@ -33,7 +34,9 @@ export const DResizableHandle: React.FC = (props) => { return -
+
+ +
} From 1609a628ca8261d77fdcb4b0f27fa19130cb069c Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 22:42:52 +0100 Subject: [PATCH 018/197] feat: enhance SegmentedControl styles with disabled state styling --- .../segmented-control/SegmentedControl.style.scss | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/components/segmented-control/SegmentedControl.style.scss b/src/components/segmented-control/SegmentedControl.style.scss index ed2ef9494..66589ab6d 100644 --- a/src/components/segmented-control/SegmentedControl.style.scss +++ b/src/components/segmented-control/SegmentedControl.style.scss @@ -19,17 +19,18 @@ cursor: pointer; padding: variables.$xxs variables.$xs; + &[data-state="off"] { + background: transparent; + opacity: .6666666666; + } + & { @include box.box(); + @include helpers.disabled(); border-radius: variables.$borderRadius - variables.$xxs; border: none; } - &[data-state="off"] { - background: transparent; - opacity: .6666666666; - } - } } \ No newline at end of file From 319a2db58c02cfc697694171516d0660e51e597e Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 27 Nov 2025 22:42:57 +0100 Subject: [PATCH 019/197] feat: simplify import paths in SegmentedControl component --- src/components/segmented-control/SegmentedControl.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/segmented-control/SegmentedControl.tsx b/src/components/segmented-control/SegmentedControl.tsx index fb4254753..61b0a1405 100644 --- a/src/components/segmented-control/SegmentedControl.tsx +++ b/src/components/segmented-control/SegmentedControl.tsx @@ -1,7 +1,7 @@ import React from "react"; -import {Code0ComponentProps} from "../../utils/types"; +import {Code0ComponentProps} from "../../utils"; import {ToggleGroupSingleProps, ToggleGroupItemProps, Root, Item} from "@radix-ui/react-toggle-group"; -import {mergeCode0Props} from "../../utils/utils"; +import {mergeCode0Props} from "../../utils"; import "./SegmentedControl.style.scss" type SegmentedControlProps = Code0ComponentProps & ToggleGroupSingleProps From 65c65f1c41683ad5fba14a18507924c9346bd50f Mon Sep 17 00:00:00 2001 From: nicosammito Date: Fri, 28 Nov 2025 10:40:41 +0100 Subject: [PATCH 020/197] feat: enhance DFlowControl component with improved button styling and text wrapping --- src/components/d-flow/control/DFlowControl.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/d-flow/control/DFlowControl.tsx b/src/components/d-flow/control/DFlowControl.tsx index 974c04914..8c6063869 100644 --- a/src/components/d-flow/control/DFlowControl.tsx +++ b/src/components/d-flow/control/DFlowControl.tsx @@ -5,6 +5,7 @@ import {Button} from "../../button/Button"; import {IconFocusCentered, IconMinus, IconPlus} from "@tabler/icons-react"; import {Badge} from "../../badge/Badge"; import {Flex} from "../../flex/Flex"; +import {Text} from "../../text/Text"; export const DFlowControl: React.FC = () => { @@ -31,14 +32,13 @@ export const DFlowControl: React.FC = () => { - - - + + + - {getCurrentZoomInPercent()}% + + {getCurrentZoomInPercent()}% + From 71421a66f0311b8e279bd8dd42f5d61374aa8742 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Fri, 28 Nov 2025 10:40:46 +0100 Subject: [PATCH 021/197] feat: update DFlowFunctionTriggerCard to enhance Badge styling with rounded corners --- src/components/d-flow/function/DFlowFunctionTriggerCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx b/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx index 58a5575bf..5b69ec676 100644 --- a/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx @@ -32,8 +32,8 @@ export const DFlowFunctionTriggerCard: React.FC = const viewportWidth = useStore(s => s.width) const viewportHeight = useStore(s => s.height) - return - Starting node + return + Starting node Date: Fri, 28 Nov 2025 10:40:50 +0100 Subject: [PATCH 022/197] feat: add borders to layout sections for improved visual separation --- src/components/d-layout/DLayout.style.scss | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/components/d-layout/DLayout.style.scss b/src/components/d-layout/DLayout.style.scss index c6955ebb2..d8a20df41 100644 --- a/src/components/d-layout/DLayout.style.scss +++ b/src/components/d-layout/DLayout.style.scss @@ -15,7 +15,6 @@ flex-direction: column; width: 100%; height: 100%; - gap: variables.$xl; } .d-layout__top, @@ -28,9 +27,25 @@ flex: 1 1 auto; min-height: 0; width: 100%; - gap: variables.$xl; } +.d-layout__left { + border-right: 1px solid helpers.borderColor(); +} + +.d-layout__right { + border-left: 1px solid helpers.borderColor(); +} + +.d-layout__top { + border-bottom: 1px solid helpers.borderColor(); +} + +.d-layout__bottom { + border-top: 1px solid helpers.borderColor(); +} + + .d-layout__left, .d-layout__right { flex: 0 0 auto; From 3fb5bf850febdfff6c3cad0b823089665d5f14ed Mon Sep 17 00:00:00 2001 From: nicosammito Date: Fri, 28 Nov 2025 10:40:53 +0100 Subject: [PATCH 023/197] feat: enhance DResizable component with new layout and button functionalities --- .../d-resizable/DResizable.stories.tsx | 96 ++++++++++++++----- 1 file changed, 71 insertions(+), 25 deletions(-) diff --git a/src/components/d-resizable/DResizable.stories.tsx b/src/components/d-resizable/DResizable.stories.tsx index 1ce776286..ccf15def1 100644 --- a/src/components/d-resizable/DResizable.stories.tsx +++ b/src/components/d-resizable/DResizable.stories.tsx @@ -2,17 +2,14 @@ import {Meta} from "@storybook/react-vite"; import {DResizableHandle, DResizablePanel, DResizablePanelGroup} from "./DResizable"; import React from "react"; import {DFullScreen} from "../d-fullscreen/DFullScreen"; -import {Button} from "../button/Button"; import { - IconArrowsVertical, - IconDatabase, - IconHierarchy3, IconLayout, - IconLayoutDistributeHorizontal, IconLayoutDistributeVertical, - IconSettings, - IconTicket + IconAi, IconCopy, + IconDatabase, IconFile, + IconFolder, + IconLayout, + IconLayoutDistributeHorizontal, + IconLayoutDistributeVertical, IconMessageChatbot, IconTrash } from "@tabler/icons-react"; -import {Flex} from "../flex/Flex"; -import {Tooltip, TooltipContent, TooltipPortal, TooltipTrigger} from "../tooltip/Tooltip"; import {FunctionDefinitionView} from "../d-flow/function/DFlowFunction.view"; import {useReactiveArrayService} from "../../utils/reactiveArrayService"; import {FileTabsView} from "../file-tabs/FileTabs.view"; @@ -41,10 +38,16 @@ import {DLayout} from "../d-layout/DLayout"; import {DFlowFolder} from "../d-flow/folder/DFlowFolder"; import { NamespacesProjectsFlowsCreateInput, - NamespacesProjectsFlowsCreatePayload, NamespacesProjectsFlowsDeleteInput, NamespacesProjectsFlowsDeletePayload + NamespacesProjectsFlowsCreatePayload, + NamespacesProjectsFlowsDeleteInput, + NamespacesProjectsFlowsDeletePayload } from "@code0-tech/sagittarius-graphql-types"; -import {DFlowMiniMap} from "../d-flow"; import {SegmentedControl, SegmentedControlItem} from "../segmented-control/SegmentedControl"; +import {Flex} from "../flex/Flex"; +import {Button} from "../button/Button"; +import {Text} from "../text/Text"; +import {Tabs, TabsList, TabsTrigger} from "@radix-ui/react-tabs"; +import {ButtonGroup} from "../button-group/ButtonGroup"; const meta: Meta = { title: "Dashboard Resizable", @@ -69,7 +72,8 @@ class DFlowReactiveServiceExtend extends DFlowReactiveService { } } -class DFlowReactiveSuggestionServiceExtend extends DFlowReactiveSuggestionService {} +class DFlowReactiveSuggestionServiceExtend extends DFlowReactiveSuggestionService { +} export const Dashboard = () => { @@ -98,22 +102,51 @@ export const Dashboard = () => { // @ts-ignore const [flowTypeStore, flowTypeService] = useReactiveArrayService(DFlowTypeReactiveService, [...FlowTypeData.map(data => new FlowTypeView(data))]); + const [show, setShow] = React.useState(false); + return - - - - - - - - - - - - - + + + + + + } bottomContent={ + + + + + }> + + + + + + + + + {show && ( + <> + + + + + + )} + + @@ -145,6 +178,19 @@ const FlowExample = () => { + + + + + + + {/**/} } \ No newline at end of file From 4f9df14eef19555ea2ca65c1e452653b5aaf1cd6 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 7 Dec 2025 22:11:21 +0100 Subject: [PATCH 024/197] feat: update flow type ID in DResizable stories for consistency --- src/components/d-resizable/DResizable.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/d-resizable/DResizable.stories.tsx b/src/components/d-resizable/DResizable.stories.tsx index ccf15def1..e50be8c62 100644 --- a/src/components/d-resizable/DResizable.stories.tsx +++ b/src/components/d-resizable/DResizable.stories.tsx @@ -85,7 +85,7 @@ export const Dashboard = () => { const [flowStore, flowService] = useReactiveArrayService(DFlowReactiveServiceExtend, [new FlowView({ id: "gid://sagittarius/Flow/1", type: { - id: "gid://sagittarius/TypesFlowType/842", + id: "gid://sagittarius/FlowType/867", }, name: "de/codezero/examples/REST Flow", settings: { From 38b9cef75b20574c4c6b90a399cf29c7db231241 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 8 Dec 2025 00:30:26 +0100 Subject: [PATCH 025/197] feat: implement DFlowBuilder component and enhance DFlow with flowId prop and layout improvements --- src/components/d-flow/DFlow.tsx | 89 +++++++++++++++---- src/components/d-flow/DFlowBuilder.tsx | 54 +++++++++++ .../d-resizable/DResizable.stories.tsx | 39 +------- 3 files changed, 125 insertions(+), 57 deletions(-) create mode 100644 src/components/d-flow/DFlowBuilder.tsx diff --git a/src/components/d-flow/DFlow.tsx b/src/components/d-flow/DFlow.tsx index 1d495e556..a33b6f305 100644 --- a/src/components/d-flow/DFlow.tsx +++ b/src/components/d-flow/DFlow.tsx @@ -1,16 +1,17 @@ -import {Code0ComponentProps} from "../../utils/types"; +import {Code0ComponentProps, mergeCode0Props} from "../../utils"; import { + Background, + BackgroundVariant, Edge, Node, + Panel, ReactFlow, - ReactFlowProps, ReactFlowProvider, useEdgesState, useNodesState, useUpdateNodeInternals } from "@xyflow/react"; import React from "react"; -import {mergeCode0Props} from "../../utils/utils"; import '@xyflow/react/dist/style.css'; import "./DFlow.style.scss" import {DFlowFunctionDefaultCard} from "./function/DFlowFunctionDefaultCard"; @@ -18,6 +19,21 @@ import {DFlowFunctionGroupCard} from "./function/DFlowFunctionGroupCard"; import {DFlowFunctionSuggestionCard} from "./function/DFlowFunctionSuggestionCard"; import {DFlowFunctionTriggerCard} from "./function/DFlowFunctionTriggerCard"; import {DFlowEdge} from "./edge/DFlowEdge"; +import {DFlowControl} from "./control"; +import {DFlowValidation} from "./validation"; +import {SegmentedControl, SegmentedControlItem} from "../segmented-control/SegmentedControl"; +import { + IconCopy, + IconLayout, + IconLayoutDistributeHorizontal, + IconLayoutDistributeVertical, + IconTrash +} from "@tabler/icons-react"; +import {ButtonGroup} from "../button-group/ButtonGroup"; +import {Button} from "../button/Button"; +import {Flow} from "@code0-tech/sagittarius-graphql-types"; +import {useFlowNodes} from "./DFlow.nodes.hook"; +import {useFlowEdges} from "./DFlow.edges.hook"; /** * Dynamically layouts a tree of nodes and their parameter nodes for a flow-based editor. @@ -505,7 +521,9 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { const getCachedLayoutElements = React.cache(getLayoutElements) -export type DFlowProps = Code0ComponentProps & ReactFlowProps +export interface DFlowProps extends Code0ComponentProps { + flowId: Flow['id'] +} export const DFlow: React.FC = (props) => { return @@ -514,29 +532,31 @@ export const DFlow: React.FC = (props) => { } const InternalDFlow: React.FC = (props) => { - const [nodes, setNodes] = useNodesState(props.nodes!!) - const [edges, setEdges, edgeChangeEvent] = useEdgesState(props.edges!!) - const updateNodeInternals = useUpdateNodeInternals() - - const revalidateHandles = React.useCallback((ids: string[]) => { - requestAnimationFrame(() => { - ids.forEach(id => updateNodeInternals(id)); - }); - }, [updateNodeInternals]) + const {flowId} = props const nodeTypes = { default: DFlowFunctionDefaultCard, group: DFlowFunctionGroupCard, suggestion: DFlowFunctionSuggestionCard, trigger: DFlowFunctionTriggerCard, - ...props.nodeTypes } const edgeTypes = { default: DFlowEdge, - ...props.edgeTypes } + const initialNodes = useFlowNodes(flowId) + const initialEdges = useFlowEdges(flowId) + const [nodes, setNodes] = useNodesState([]) + const [edges, setEdges, edgeChangeEvent] = useEdgesState([]) + const updateNodeInternals = useUpdateNodeInternals() + + const revalidateHandles = React.useCallback((ids: string[]) => { + requestAnimationFrame(() => { + ids.forEach(id => updateNodeInternals(id)); + }); + }, [updateNodeInternals]) + const nodeChangeEvent = React.useCallback((changes: any) => { const changedIds: string[] = Array.from(new Set( changes @@ -570,7 +590,7 @@ const InternalDFlow: React.FC = (props) => { }, [revalidateHandles]); React.useEffect(() => { - const localNodes = props.nodes!!.map(value => { + const localNodes = initialNodes.map(value => { const nodeEls = !value.measured ? document.querySelectorAll("[data-id='" + value.id + "']") : []; return { ...value, @@ -583,11 +603,11 @@ const InternalDFlow: React.FC = (props) => { const layouted = getCachedLayoutElements(localNodes, new Set(localNodes.map(n => n.id))) setNodes(layouted.nodes as Node[]) - setEdges(props.edges as Edge[]) + setEdges(initialEdges as Edge[]) revalidateHandles((layouted.nodes as Node[]).map(n => n.id)) - }, [props.nodes, props.edges, revalidateHandles]) + }, [initialNodes, initialEdges, revalidateHandles]) return ( = (props) => { onInit={(rf) => rf.fitView()} onNodesChange={nodeChangeEvent} onEdgesChange={edgeChangeEvent} + fitView {...mergeCode0Props("flow", props)} nodes={nodes} edges={edges} - /> + > + + + + + + + + + + + + + + + + + + + + + + + + ) } \ No newline at end of file diff --git a/src/components/d-flow/DFlowBuilder.tsx b/src/components/d-flow/DFlowBuilder.tsx new file mode 100644 index 000000000..0034a4a0e --- /dev/null +++ b/src/components/d-flow/DFlowBuilder.tsx @@ -0,0 +1,54 @@ +"use client" + +import React from "react"; +import {Flex} from "../flex/Flex"; +import {Button} from "../button/Button"; +import {IconDatabase, IconFile, IconMessageChatbot} from "@tabler/icons-react"; +import {Text} from "../text/Text"; +import {DResizableHandle, DResizablePanel, DResizablePanelGroup} from "../d-resizable/DResizable"; +import {DFlowFolder} from "./folder"; +import {DFlowTabs} from "./tab/DFlowTabs"; +import {DLayout} from "../d-layout/DLayout"; + +export interface DFlowBuilderProps { + +} + +export const DFlowBuilder: React.FC = (props) => { + return + + + + + } bottomContent={ + + + + + }> + + + + + + + <> + + + + + + + +} \ No newline at end of file diff --git a/src/components/d-resizable/DResizable.stories.tsx b/src/components/d-resizable/DResizable.stories.tsx index e50be8c62..7df186718 100644 --- a/src/components/d-resizable/DResizable.stories.tsx +++ b/src/components/d-resizable/DResizable.stories.tsx @@ -154,43 +154,6 @@ export const Dashboard = () => { const FlowExample = () => { - const initialNodes = useFlowNodes("gid://sagittarius/Flow/1") - const initialEdges = useFlowEdges("gid://sagittarius/Flow/1") - return - - - - - - - - - - - - - - - - - - - - - - - - {/**/} - + return } \ No newline at end of file From 0c6cb374d379c986494675daeb9e8832713ae988 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 8 Dec 2025 17:09:19 +0100 Subject: [PATCH 026/197] feat: optimize flow edge and node hooks with memoization and improved type handling --- src/components/d-flow/DFlow.edges.hook.ts | 361 +++++++++++----------- src/components/d-flow/DFlow.nodes.hook.ts | 287 +++++++++-------- 2 files changed, 332 insertions(+), 316 deletions(-) diff --git a/src/components/d-flow/DFlow.edges.hook.ts b/src/components/d-flow/DFlow.edges.hook.ts index eeb4654aa..678e6b2d3 100644 --- a/src/components/d-flow/DFlow.edges.hook.ts +++ b/src/components/d-flow/DFlow.edges.hook.ts @@ -5,7 +5,7 @@ import {NodeFunctionView} from "./DFlow.view"; import {DFlowFunctionReactiveService} from "./function/DFlowFunction.service"; import {DFlowDataTypeReactiveService} from "./data-type/DFlowDataType.service"; import React from "react"; -import type {DataTypeIdentifier, DataTypeVariant, Flow, Scalars} from "@code0-tech/sagittarius-graphql-types"; +import type {DataTypeIdentifier, Flow, Scalars} from "@code0-tech/sagittarius-graphql-types"; // Deine Primärfarbe als Start, danach harmonisch verteilt export const FLOW_EDGE_RAINBOW: string[] = [ @@ -21,95 +21,67 @@ export const FLOW_EDGE_RAINBOW: string[] = [ export const useFlowEdges = (flowId: Flow['id']): Edge[] => { const flowService = useService(DFlowReactiveService); - const functionService = useService(DFlowFunctionReactiveService); - const dataTypeService = useService(DFlowDataTypeReactiveService); - const flow = flowService.getById(flowId); const flowStore = useStore(DFlowReactiveService) + const functionService = useService(DFlowFunctionReactiveService); const functionStore = useStore(DFlowFunctionReactiveService) + const dataTypeService = useService(DFlowDataTypeReactiveService); const dataTypeStore = useStore(DFlowDataTypeReactiveService) - if (!flow) return []; + const flow = React.useMemo(() => flowService.getById(flowId), [flowId, flowStore]) - /* ------------------------------------------------------------------ */ - const edges: Edge[] = []; + return React.useMemo(() => { + if (!flow) return []; - /** merkt sich für jede Function-Card die Gruppen-IDs, - * **für die wirklich ein Funktions-Wert existiert** */ - const groupsWithValue = new Map(); + /* ------------------------------------------------------------------ */ + const edges: Edge[] = []; - let idCounter = 0; // globale, fortlaufende Id-Vergabe + /** merkt sich für jede Function-Card die Gruppen-IDs, + * **für die wirklich ein Funktions-Wert existiert** */ + const groupsWithValue = new Map(); - const functionCache = new Map>(); - const dataTypeCache = new Map>(); + let idCounter = 0; // globale, fortlaufende Id-Vergabe - const getFunctionDefinitionCached = ( - id: Scalars['FunctionDefinitionID']['output'], - cache = functionCache, - ) => { - if (!cache.has(id)) { - cache.set(id, functionService.getById(id)); - } - return cache.get(id); - }; - - const getDataTypeCached = ( - type: DataTypeIdentifier, - cache = dataTypeCache, - ) => { - if (!cache.has(type)) { - cache.set(type, dataTypeService.getDataType(type)); - } - return cache.get(type); - }; - - /* ------------------------------------------------------------------ */ - const traverse = ( - fn: NodeFunctionView, - parentFnId?: string, // Id der *Function-Card* des Aufrufers - level = 0, // Tiefe ⇒ Farbe aus dem Rainbow-Array, - fnCache = functionCache, - dtCache = dataTypeCache, - ): string => { - - /* ------- Id der aktuellen Function-Card im Diagramm ---------- */ - const fnId = `${fn.id}-${idCounter++}`; - - if (idCounter == 1) { - // erste Function-Card → Verbindung Trigger → Function - edges.push({ - id: `trigger-${fnId}-next`, - source: flow.id as string, // Handle-Bottom des Trigger-Nodes - target: fnId, // Handle-Top der Function-Card - data: { - color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], - isParameter: false - }, - deletable: false, - selectable: false, - }); - } + const functionCache = new Map>(); + const dataTypeCache = new Map>(); - /* ------- vertikale Kante (nextNode) -------------------------- */ - if (parentFnId) { - const startGroups = groupsWithValue.get(parentFnId) ?? []; - - if (startGroups.length > 0) { - startGroups.forEach((gId, idx) => edges.push({ - id: `${gId}-${fnId}-next-${idx}`, - source: gId, // Handle-Bottom der Group-Card - target: fnId, // Handle-Top der Function-Card - data: { - color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], - isParameter: false - }, - deletable: false, - selectable: false, - })); - } else { + const getFunctionDefinitionCached = ( + id: Scalars['FunctionDefinitionID']['output'], + cache = functionCache, + ) => { + if (!cache.has(id)) { + cache.set(id, functionService.getById(id)); + } + return cache.get(id); + }; + + const getDataTypeCached = ( + type: DataTypeIdentifier, + cache = dataTypeCache, + ) => { + if (!cache.has(type)) { + cache.set(type, dataTypeService.getDataType(type)); + } + return cache.get(type); + }; + + /* ------------------------------------------------------------------ */ + const traverse = ( + fn: NodeFunctionView, + parentFnId?: string, // Id der *Function-Card* des Aufrufers + level = 0, // Tiefe ⇒ Farbe aus dem Rainbow-Array, + fnCache = functionCache, + dtCache = dataTypeCache, + ): string => { + + /* ------- Id der aktuellen Function-Card im Diagramm ---------- */ + const fnId = `${fn.id}-${idCounter++}`; + + if (idCounter == 1) { + // erste Function-Card → Verbindung Trigger → Function edges.push({ - id: `${parentFnId}-${fnId}-next`, - source: parentFnId, // Handle-Bottom der Function-Card - target: fnId, // Handle-Top der Function-Card + id: `trigger-${fnId}-next`, + source: flow.id as string, // Handle-Bottom des Trigger-Nodes + target: fnId, // Handle-Top der Function-Card data: { color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], isParameter: false @@ -118,118 +90,149 @@ export const useFlowEdges = (flowId: Flow['id']): Edge[] => { selectable: false, }); } - } - - /* ------- horizontale Kanten für Parameter -------------------- */ - fn.parameters?.forEach((param) => { - const val = param.value; - const def = getFunctionDefinitionCached(fn.functionDefinition?.id!!, fnCache) - ?.parameterDefinitions?.find(p => p.id === param.id); - const paramType = def?.dataTypeIdentifier; - const paramDT = paramType ? getDataTypeCached(paramType, dtCache) : undefined; - - if (!val) return - - /* --- NODE-Parameter → Group-Card ------------------------- */ - if (paramDT?.variant === "NODE") { - const groupId = `${fnId}-group-${idCounter++}`; - /* Verbindung Gruppe → Function-Card (horizontal) */ - edges.push({ - id: `${fnId}-${groupId}-param-${param.id}`, - source: fnId, // FunctionCard (Quelle) - target: groupId, // GroupCard (Ziel – hat Top: target) - deletable: false, - selectable: false, - label: def?.names?.nodes!![0]?.content ?? param.id, - data: { - color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], - isParameter: false, - }, - }); + /* ------- vertikale Kante (nextNode) -------------------------- */ + if (parentFnId) { + const startGroups = groupsWithValue.get(parentFnId) ?? []; + + if (startGroups.length > 0) { + startGroups.forEach((gId, idx) => edges.push({ + id: `${gId}-${fnId}-next-${idx}`, + source: gId, // Handle-Bottom der Group-Card + target: fnId, // Handle-Top der Function-Card + data: { + color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], + isParameter: false + }, + deletable: false, + selectable: false, + })); + } else { + edges.push({ + id: `${parentFnId}-${fnId}-next`, + source: parentFnId, // Handle-Bottom der Function-Card + target: fnId, // Handle-Top der Function-Card + data: { + color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], + isParameter: false + }, + deletable: false, + selectable: false, + }); + } + } - /* existiert ein Funktions-Wert für dieses Param-Feld? */ - if (val && val instanceof NodeFunctionView) { - /* merken: diese Group-Card besitzt Content – das ist - später Startpunkt der next-Kante */ - (groupsWithValue.get(fnId) ?? (groupsWithValue.set(fnId, []), - groupsWithValue.get(fnId)!)) - .push(groupId); + /* ------- horizontale Kanten für Parameter -------------------- */ + fn.parameters?.forEach((param) => { + const val = param.value; + const def = getFunctionDefinitionCached(fn.functionDefinition?.id!!, fnCache) + ?.parameterDefinitions?.find(p => p.id === param.id); + const paramType = def?.dataTypeIdentifier; + const paramDT = paramType ? getDataTypeCached(paramType, dtCache) : undefined; + + if (!val) return + + /* --- NODE-Parameter → Group-Card ------------------------- */ + if (paramDT?.variant === "NODE") { + const groupId = `${fnId}-group-${idCounter++}`; + + /* Verbindung Gruppe → Function-Card (horizontal) */ + edges.push({ + id: `${fnId}-${groupId}-param-${param.id}`, + source: fnId, // FunctionCard (Quelle) + target: groupId, // GroupCard (Ziel – hat Top: target) + deletable: false, + selectable: false, + label: def?.names?.nodes!![0]?.content ?? param.id, + data: { + color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], + isParameter: false, + }, + }); + + /* existiert ein Funktions-Wert für dieses Param-Feld? */ + if (val && val instanceof NodeFunctionView) { + /* merken: diese Group-Card besitzt Content – das ist + später Startpunkt der next-Kante */ + (groupsWithValue.get(fnId) ?? (groupsWithValue.set(fnId, []), + groupsWithValue.get(fnId)!)) + .push(groupId); + + /* rekursiv Funktions-Ast innerhalb der Gruppe */ + traverse(param.value as NodeFunctionView, + undefined, + level + 1, + fnCache, + dtCache); + } + } - /* rekursiv Funktions-Ast innerhalb der Gruppe */ - traverse(param.value as NodeFunctionView, + /* --- anderer Parameter, der selbst eine Function hält ---- */ + else if (val && val instanceof NodeFunctionView) { + const subFnId = traverse(param.value as NodeFunctionView, undefined, level + 1, fnCache, dtCache); - } - } - /* --- anderer Parameter, der selbst eine Function hält ---- */ - else if (val && val instanceof NodeFunctionView) { - const subFnId = traverse(param.value as NodeFunctionView, - undefined, - level + 1, - fnCache, - dtCache); + edges.push({ + id: `${subFnId}-${fnId}-param-${param.id}`, + source: subFnId, + target: fnId, + targetHandle: `param-${param.id}`, + animated: true, + deletable: false, + selectable: false, + data: { + color: FLOW_EDGE_RAINBOW[(level + 1) % FLOW_EDGE_RAINBOW.length], + isParameter: true + }, + }); + } + }); - edges.push({ - id: `${subFnId}-${fnId}-param-${param.id}`, - source: subFnId, - target: fnId, - targetHandle: `param-${param.id}`, - animated: true, - deletable: false, - selectable: false, - data: { - color: FLOW_EDGE_RAINBOW[(level + 1) % FLOW_EDGE_RAINBOW.length], - isParameter: true - }, - }); - } - }); - - /* ------- Rekursion auf nextNode ------------------------------ */ - if (fn.nextNodeId) { - traverse(flow.getNodeById(fn.nextNodeId!!)!!, fnId, level, fnCache, dtCache); // gleiche Ebenentiefe - } else { - // letzte Function-Card im Ast → Add-new-node wie *normale* nextNode behandeln - const suggestionNodeId = `${fnId}-suggestion`; - const startGroups = groupsWithValue.get(fnId) ?? []; - - if (startGroups.length > 0) { - startGroups.forEach((gId, idx) => edges.push({ - id: `${gId}-${suggestionNodeId}-next-${idx}`, - source: gId, // wie bei echter nextNode von Group-Card starten - target: suggestionNodeId, // Ziel ist die Suggestion-Card - data: { - color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], - isSuggestion: true, - }, - deletable: false, - selectable: false, - })); + /* ------- Rekursion auf nextNode ------------------------------ */ + if (fn.nextNodeId) { + traverse(flow.getNodeById(fn.nextNodeId!!)!!, fnId, level, fnCache, dtCache); // gleiche Ebenentiefe } else { - edges.push({ - id: `${fnId}-${suggestionNodeId}-next`, - source: fnId, // Handle-Bottom der Function-Card - target: suggestionNodeId, // Handle-Top der Suggestion-Card - data: { - color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], - isSuggestion: true, - }, - deletable: false, - selectable: false, - }); + // letzte Function-Card im Ast → Add-new-node wie *normale* nextNode behandeln + const suggestionNodeId = `${fnId}-suggestion`; + const startGroups = groupsWithValue.get(fnId) ?? []; + + if (startGroups.length > 0) { + startGroups.forEach((gId, idx) => edges.push({ + id: `${gId}-${suggestionNodeId}-next-${idx}`, + source: gId, // wie bei echter nextNode von Group-Card starten + target: suggestionNodeId, // Ziel ist die Suggestion-Card + data: { + color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], + isSuggestion: true, + }, + deletable: false, + selectable: false, + })); + } else { + edges.push({ + id: `${fnId}-${suggestionNodeId}-next`, + source: fnId, // Handle-Bottom der Function-Card + target: suggestionNodeId, // Handle-Top der Suggestion-Card + data: { + color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], + isSuggestion: true, + }, + deletable: false, + selectable: false, + }); + } } - } - return fnId; - }; + return fnId; + }; - if (flow.startingNodeId) { - traverse(flow.getNodeById(flow.startingNodeId)!!, undefined, 0, functionCache, dataTypeCache); - } + if (flow.startingNodeId) { + traverse(flow.getNodeById(flow.startingNodeId)!!, undefined, 0, functionCache, dataTypeCache); + } - return React.useMemo(() => edges, [flowStore, functionStore, dataTypeStore, edges]); + return edges + }, [flow, flowStore, functionStore, dataTypeStore]); }; \ No newline at end of file diff --git a/src/components/d-flow/DFlow.nodes.hook.ts b/src/components/d-flow/DFlow.nodes.hook.ts index 104d49120..dfdb5d27c 100644 --- a/src/components/d-flow/DFlow.nodes.hook.ts +++ b/src/components/d-flow/DFlow.nodes.hook.ts @@ -1,10 +1,15 @@ -import {useService} from "../../utils/contextStore"; +import {useService, useStore} from "../../utils/contextStore"; import {DFlowReactiveService} from "./DFlow.service"; import {NodeFunctionView} from "./DFlow.view"; import {Node} from "@xyflow/react"; import {DFlowFunctionReactiveService} from "./function/DFlowFunction.service"; import {DFlowDataTypeReactiveService} from "./data-type/DFlowDataType.service"; import type {DataTypeIdentifier, DataTypeVariant, Flow, Scalars} from "@code0-tech/sagittarius-graphql-types"; +import React from "react"; +import {DFlowFunctionDefaultCardDataProps} from "./function/DFlowFunctionDefaultCard"; +import {DFlowFunctionSuggestionCardDataProps} from "./function/DFlowFunctionSuggestionCard"; +import {DFlowFunctionTriggerCardDataProps} from "./function/DFlowFunctionTriggerCard"; +import {DFlowFunctionGroupCardDataProps} from "./function/DFlowFunctionGroupCard"; const packageNodes = new Map([ ['std', 'default'], @@ -90,165 +95,173 @@ const bestMatchValue = (map: Map, input: string): string => { return bestKey !== null ? map.get(bestKey)! : ""; }; -export const useFlowNodes = (flowId: Flow['id']): Node[] => { +// @ts-ignore +export const useFlowNodes = (flowId: Flow['id']): Node[] => { const flowService = useService(DFlowReactiveService); + const flowStore = useStore(DFlowReactiveService); const functionService = useService(DFlowFunctionReactiveService); + const functionStore = useStore(DFlowFunctionReactiveService); const dataTypeService = useService(DFlowDataTypeReactiveService); - const flow = flowService.getById(flowId); + const dataTypeStore = useStore(DFlowDataTypeReactiveService); - if (!flow) return []; + const flow = React.useMemo(() => flowService.getById(flowId), [flowId, flowStore]) - const nodes: Node[] = []; - let idCounter = 0; + return React.useMemo(() => { + if (!flow) return []; - const functionCache = new Map>(); - const dataTypeCache = new Map>(); + // @ts-ignore + const nodes: Node[] = []; + let idCounter = 0; - const getFunctionDefinitionCached = ( - id: Scalars['FunctionDefinitionID']['output'], - cache = functionCache, - ) => { - if (!cache.has(id)) { - cache.set(id, functionService.getById(id)); - } - return cache.get(id); - }; - - const getDataTypeCached = ( - type: DataTypeIdentifier, - cache = dataTypeCache, - ) => { - if (!cache.has(type)) { - cache.set(type, dataTypeService.getDataType(type)); - } - return cache.get(type); - }; - - // Global, strictly increasing group-id used to build the scope PATH ([0], [0,1], [0,2], [0,2,3], ...) - let globalScopeId = 0; - const nextScopeId = () => ++globalScopeId; - - // Global, strictly increasing node index across the entire flow (only real nodes) - let globalNodeIndex = 0; - - //trigger node - nodes.push({ - id: `${flow.id}`, - type: "trigger", - position: { x: 0, y: 0 }, - draggable: false, - data: { - instance: flow, - flowId, - } - }) - - - const traverse = ( - fn: NodeFunctionView, - isParameter = false, - parentId?: string, - depth: number = 0, - scopePath: number[] = [0], - parentGroup?: string, - fnCache = functionCache, - dtCache = dataTypeCache, - ) => { - const id = `${fn.id}-${idCounter++}`; - const index = ++globalNodeIndex; // global node level + const functionCache = new Map>(); + const dataTypeCache = new Map>(); + const getFunctionDefinitionCached = ( + id: Scalars['FunctionDefinitionID']['output'], + cache = functionCache, + ) => { + if (!cache.has(id)) { + cache.set(id, functionService.getById(id)); + } + return cache.get(id); + }; + + const getDataTypeCached = ( + type: DataTypeIdentifier, + cache = dataTypeCache, + ) => { + if (!cache.has(type)) { + cache.set(type, dataTypeService.getDataType(type)); + } + return cache.get(type); + }; + + // Global, strictly increasing group-id used to build the scope PATH ([0], [0,1], [0,2], [0,2,3], ...) + let globalScopeId = 0; + const nextScopeId = () => ++globalScopeId; + + // Global, strictly increasing node index across the entire flow (only real nodes) + let globalNodeIndex = 0; + + //trigger node nodes.push({ - id, - type: bestMatchValue(packageNodes, fn.functionDefinition?.identifier!!), + id: `${flow.id}`, + type: "trigger", position: { x: 0, y: 0 }, draggable: false, - parentId: parentGroup, - extent: parentGroup ? "parent" : undefined, data: { - instance: fn, - isParameter, + instance: flow, flowId, - linkingId: isParameter ? parentId : undefined, - scope: scopePath, // scope is now a PATH (number[]) - depth, // structural depth (0 at root, +1 per group) - index, // global node level - }, - }); - - if (!fn.nextNodeId && !isParameter) { + } + }) + + + const traverse = ( + fn: NodeFunctionView, + isParameter = false, + parentId?: string, + depth: number = 0, + scopePath: number[] = [0], + parentGroup?: string, + fnCache = functionCache, + dtCache = dataTypeCache, + ) => { + const id = `${fn.id}-${idCounter++}`; + const index = ++globalNodeIndex; // global node level + nodes.push({ - id: `${id}-suggestion`, - type: "suggestion", + id, + type: bestMatchValue(packageNodes, fn.functionDefinition?.identifier!!), position: { x: 0, y: 0 }, draggable: false, - extent: parentGroup ? "parent" : undefined, parentId: parentGroup, + extent: parentGroup ? "parent" : undefined, data: { - flowId: flowId, - parentFunction: fn, + instance: fn, + isParameter, + flowId: flowId!!, + linkingId: isParameter ? parentId : undefined, + scope: scopePath, // scope is now a PATH (number[]) + depth, // structural depth (0 at root, +1 per group) + index, // global node level }, }); - } - const definition = getFunctionDefinitionCached(fn.functionDefinition?.id!!, fnCache); - - fn.parameters?.forEach((param) => { - const paramType = definition?.parameterDefinitions!!.find(p => p.id == param.runtimeParameter?.id)?.dataTypeIdentifier; - const paramDataType = paramType ? getDataTypeCached(paramType, dtCache) : undefined; - - if (paramDataType?.variant === "NODE") { - if (param.value && param.value instanceof NodeFunctionView) { - const groupId = `${id}-group-${idCounter++}`; - - // New group: extend scope PATH with a fresh segment and increase depth. - const childScopePath = [...scopePath, nextScopeId()]; - - nodes.push({ - id: groupId, - type: "group", - position: { x: 0, y: 0 }, - draggable: false, - parentId: parentGroup, - extent: parentGroup ? "parent" : undefined, - data: { - isParameter: true, - linkingId: id, - flowId, - depth: depth + 1, - scope: childScopePath, - }, - }); - - // Child function inside the group uses the group's depth and scope PATH. - traverse(param.value as NodeFunctionView, false, undefined, depth + 1, childScopePath, groupId, fnCache, dtCache); + if (!fn.nextNodeId && !isParameter) { + nodes.push({ + id: `${id}-suggestion`, + type: "suggestion", + position: { x: 0, y: 0 }, + draggable: false, + extent: parentGroup ? "parent" : undefined, + parentId: parentGroup, + data: { + flowId: flowId, + parentFunction: fn, + }, + }); + } + + const definition = getFunctionDefinitionCached(fn.functionDefinition?.id!!, fnCache); + + fn.parameters?.forEach((param) => { + const paramType = definition?.parameterDefinitions!!.find(p => p.id == param.runtimeParameter?.id)?.dataTypeIdentifier; + const paramDataType = paramType ? getDataTypeCached(paramType, dtCache) : undefined; + + if (paramDataType?.variant === "NODE") { + if (param.value && param.value instanceof NodeFunctionView) { + const groupId = `${id}-group-${idCounter++}`; + + // New group: extend scope PATH with a fresh segment and increase depth. + const childScopePath = [...scopePath, nextScopeId()]; + + nodes.push({ + id: groupId, + type: "group", + position: { x: 0, y: 0 }, + draggable: false, + parentId: parentGroup, + extent: parentGroup ? "parent" : undefined, + data: { + isParameter: true, + linkingId: id, + flowId: flowId!!, + depth: depth + 1, + scope: childScopePath, + }, + }); + + // Child function inside the group uses the group's depth and scope PATH. + traverse(param.value as NodeFunctionView, false, undefined, depth + 1, childScopePath, groupId, fnCache, dtCache); + } + } else if (param.value && param.value instanceof NodeFunctionView) { + // Functions passed as non-NODE parameters live in the same depth/scope PATH. + traverse(param.value as NodeFunctionView, true, id, depth, scopePath, parentGroup, fnCache, dtCache); } - } else if (param.value && param.value instanceof NodeFunctionView) { - // Functions passed as non-NODE parameters live in the same depth/scope PATH. - traverse(param.value as NodeFunctionView, true, id, depth, scopePath, parentGroup, fnCache, dtCache); + }); + + if (fn.nextNodeId) { + // Linear chain continues in the same depth/scope PATH. + traverse(flow.getNodeById(fn.nextNodeId!!)!!, false, undefined, depth, scopePath, parentGroup, fnCache, dtCache); } - }); + }; - if (fn.nextNodeId) { - // Linear chain continues in the same depth/scope PATH. - traverse(flow.getNodeById(fn.nextNodeId!!)!!, false, undefined, depth, scopePath, parentGroup, fnCache, dtCache); + // Root lane: depth 0, scope path [0] + if (flow.startingNodeId) { + traverse(flow.getNodeById(flow.startingNodeId)!!, false, undefined, 0, [0], undefined, functionCache, dataTypeCache); + } else { + nodes.push({ + id: `${flow.id}-suggestion`, + type: "suggestion", + position: { x: 0, y: 0 }, + draggable: false, + extent: undefined, + data: { + flowId: flowId, + parentFunction: undefined, + }, + }); } - }; - - // Root lane: depth 0, scope path [0] - if (flow.startingNodeId) { - traverse(flow.getNodeById(flow.startingNodeId)!!, false, undefined, 0, [0], undefined, functionCache, dataTypeCache); - } else { - nodes.push({ - id: `${flow.id}-suggestion`, - type: "suggestion", - position: { x: 0, y: 0 }, - draggable: false, - extent: undefined, - data: { - flowId: flowId, - parentFunction: undefined, - }, - }); - } - return nodes; + return nodes; + }, [flow, flowStore, functionStore, dataTypeStore]) }; \ No newline at end of file From 801d3f18e98db16c3620dca33c8217adec054987 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 8 Dec 2025 17:09:26 +0100 Subject: [PATCH 027/197] feat: add optional linkingId prop to DFlowFunctionDefaultCard for enhanced functionality --- src/components/d-flow/function/DFlowFunctionDefaultCard.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx index babbbee96..1ce22a0b1 100644 --- a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx @@ -36,6 +36,7 @@ export interface DFlowFunctionDefaultCardDataProps extends Omit Date: Mon, 8 Dec 2025 17:09:32 +0100 Subject: [PATCH 028/197] feat: extend DFlowFunctionGroupCardProps with new data structure for enhanced functionality --- .../d-flow/function/DFlowFunctionGroupCard.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionGroupCard.tsx b/src/components/d-flow/function/DFlowFunctionGroupCard.tsx index c2386e93d..d6473f1e2 100644 --- a/src/components/d-flow/function/DFlowFunctionGroupCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionGroupCard.tsx @@ -1,11 +1,20 @@ import React, {memo} from "react"; -import {Handle, NodeProps, Position, useStore} from "@xyflow/react"; +import {Handle, Node, NodeProps, Position, useStore} from "@xyflow/react"; import {FLOW_EDGE_RAINBOW} from "../DFlow.edges.hook"; import {Card} from "../../card/Card"; +import {Code0Component} from "../../../utils"; -export interface DFlowFunctionGroupCardProps extends NodeProps { +export interface DFlowFunctionGroupCardDataProps extends Omit, "scope"> { + isParameter: boolean + linkingId: string + flowId: string + depth: number + scope: number[] } +// @ts-ignore +export type DFlowFunctionGroupCardProps = NodeProps> + export const DFlowFunctionGroupCard: React.FC = memo((props) => { const {data, id} = props const depth = (data as any)?.depth ?? 0; From e72b981db410a1b551552037d6e56821bb6822a1 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 8 Dec 2025 17:09:37 +0100 Subject: [PATCH 029/197] feat: optimize update method in reactiveArrayService for improved state handling --- src/utils/reactiveArrayService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/reactiveArrayService.ts b/src/utils/reactiveArrayService.ts index 094397f50..75ff05cfa 100644 --- a/src/utils/reactiveArrayService.ts +++ b/src/utils/reactiveArrayService.ts @@ -50,7 +50,7 @@ export class ReactiveArrayService> implements ArraySe update() { startTransition(() => { - this.access.setState(prev => prev.slice()); + this.access.setState(prev => [...prev]); }) } From d7ca43ac4a18fb43e4d1bea2b7fd0873dd7b3abf Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 8 Dec 2025 19:48:24 +0100 Subject: [PATCH 030/197] feat: refactor layout calculation in DFlow component for improved readability and performance --- src/components/d-flow/DFlow.tsx | 215 ++++++++++++++++---------------- 1 file changed, 108 insertions(+), 107 deletions(-) diff --git a/src/components/d-flow/DFlow.tsx b/src/components/d-flow/DFlow.tsx index a33b6f305..45e43ee1c 100644 --- a/src/components/d-flow/DFlow.tsx +++ b/src/components/d-flow/DFlow.tsx @@ -47,7 +47,7 @@ import {useFlowEdges} from "./DFlow.edges.hook"; */ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { if (!dirtyIds || dirtyIds.size === 0) { - return {nodes}; + return {nodes} } /* Konstanten */ const V = 50; // vertical gap Node ↕ Node @@ -60,72 +60,72 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { let changed = false; // Aktueller Arbeitsstand der Nodes (Styles werden in den Pässen fortgeschrieben) - const work = nodes.map(n => ({...n})); + const work = nodes.map(n => ({...n})) // Relationen einmalig ermitteln (IDs behalten) -------------------------------- - const rfKidIds = new Map(); - const paramIds = new Map(); + const rfKidIds = new Map() + const paramIds = new Map() for (const n of work) { const link = (n.data as any)?.linkingId; if (link) { - let arr = paramIds.get(link); + let arr = paramIds.get(link) if (!arr) { arr = []; - paramIds.set(link, arr); + paramIds.set(link, arr) } - arr.push(n.id); + arr.push(n.id) } if (n.parentId && !link) { - let arr = rfKidIds.get(n.parentId); + let arr = rfKidIds.get(n.parentId) if (!arr) { arr = []; - rfKidIds.set(n.parentId, arr); + rfKidIds.set(n.parentId, arr) } - arr.push(n.id); + arr.push(n.id) } } - const rfKids = new Map(); - const params = new Map(); + const rfKids = new Map() + const params = new Map() - const byId = new Map(work.map(n => [n.id, n])); + const byId = new Map(work.map(n => [n.id, n])) for (const [k, ids] of rfKidIds) { - const arr: Node[] = new Array(ids.length); + const arr: Node[] = new Array(ids.length) for (let i = 0; i < ids.length; i++) arr[i] = byId.get(ids[i])!; - rfKids.set(k, arr); + rfKids.set(k, arr) } for (const [k, ids] of paramIds) { - const arr: Node[] = new Array(ids.length); + const arr: Node[] = new Array(ids.length) for (let i = 0; i < ids.length; i++) arr[i] = byId.get(ids[i])!; - params.set(k, arr); + params.set(k, arr) } - type Size = { w: number; h: number }; - const baseSizes = new Map(); + type Size = { w: number; h: number } + const baseSizes = new Map() for (const n of work) { const styleW = typeof n.style?.width === 'number' ? n.style.width : undefined; const styleH = typeof n.style?.height === 'number' ? n.style.height : undefined; const mw = n.measured?.width && n.measured.width > 0 ? n.measured.width : undefined; const mh = n.measured?.height && n.measured.height > 0 ? n.measured.height : undefined; - baseSizes.set(n.id, {w: styleW ?? mw ?? 200, h: styleH ?? mh ?? 80}); + baseSizes.set(n.id, {w: styleW ?? mw ?? 200, h: styleH ?? mh ?? 80}) } - const sizeCache = new Map(); + const sizeCache = new Map() const size = (n: Node): Size => { if (sizeCache.has(n.id)) return sizeCache.get(n.id)!; if (n.type !== 'group') { const s = baseSizes.get(n.id)!; - sizeCache.set(n.id, s); + sizeCache.set(n.id, s) return s; } const styleW = typeof n.style?.width === 'number' ? n.style.width : undefined; const styleH = typeof n.style?.height === 'number' ? n.style.height : undefined; if (styleW !== undefined && styleH !== undefined) { - const s = {w: styleW, h: styleH}; - sizeCache.set(n.id, s); + const s = {w: styleW, h: styleH} + sizeCache.set(n.id, s) return s; } @@ -134,36 +134,36 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { let wMax = 0; let count = 0; for (const k of kids) { - const ks = size(k); + const ks = size(k) stackH += ks.h; if (ks.w > wMax) wMax = ks.w; count++; } - stackH += V * Math.max(0, count - 1); + stackH += V * Math.max(0, count - 1) const g = { w: wMax + 2 * PAD, h: (count ? stackH : 0) + 2 * PAD, - }; - sizeCache.set(n.id, g); + } + sizeCache.set(n.id, g) return g; - }; + } do { changed = false; pass++; - sizeCache.clear(); - for (const n of work) size(n); + sizeCache.clear() + for (const n of work) size(n) /* ---------- relatives Layout (Zentren in globalen Koordinaten) -------- */ - type Pos = { x: number; y: number }; - const rel = new Map(); + type Pos = { x: number; y: number } + const rel = new Map() // Merker: Unterkante je rechter Spalten-"Band", damit Parameter unterschiedlicher Parents // in derselben Spalte nicht kollidieren. - const columnBottom = new Map(); - const colKey = (x: number) => Math.round(x / 10); // 10px-Buckets gegen Floating-Drift + const columnBottom = new Map() + const colKey = (x: number) => Math.round(x / 10) // 10px-Buckets gegen Floating-Drift const layoutIter = (root: Node, cx: number, cy: number): number => { type Frame = { @@ -190,7 +190,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { kidIndex?: number; curY?: number; bottom?: number; - }; + } const stack: Frame[] = [{node: root, cx, cy, phase: 0}]; let returnBottom = 0; @@ -198,8 +198,8 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { const f = stack[stack.length - 1]; switch (f.phase) { case 0: { - rel.set(f.node.id, {x: f.cx, y: f.cy}); - const {w, h} = size(f.node); + rel.set(f.node.id, {x: f.cx, y: f.cy}) + const {w, h} = size(f.node) f.w = w; f.h = h; @@ -207,16 +207,17 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { const right: Node[] = []; const gParams: Node[] = []; for (const p of paramsOf) { - if (p.type === 'group') gParams.push(p); else right.push(p); + if (p.type === 'group') gParams.push(p) + else right.push(p) } - right.sort((a, b) => (+(a.data as any)?.paramIndex) - (+(b.data as any)?.paramIndex)); - gParams.sort((a, b) => (+(a.data as any)?.paramIndex) - (+(b.data as any)?.paramIndex)); + right.sort((a, b) => (+(a.data as any)?.paramIndex) - (+(b.data as any)?.paramIndex)) + gParams.sort((a, b) => (+(a.data as any)?.paramIndex) - (+(b.data as any)?.paramIndex)) 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); + total += V * Math.max(0, right.length - 1) f.py = f.cy - total / 2; f.rightBottom = f.cy + h / 2; f.rightIndex = 0; @@ -226,10 +227,10 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { case 1: { if (f.rightIndex! < f.right!.length) { const p = f.right![f.rightIndex!]; - const ps = size(p); + 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 key = colKey(px) const occ = columnBottom.get(key) ?? Number.NEGATIVE_INFINITY; const minTop = occ + V; const desiredTop = pcy - ps.h / 2; @@ -239,19 +240,19 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { } f.childKey = key; f.childPs = ps; - stack.push({node: p, cx: px, cy: pcy, phase: 0}); + stack.push({node: p, cx: px, cy: pcy, phase: 0}) f.phase = 10; } else { - f.bottom = Math.max(f.cy + f.h! / 2, f.rightBottom!); + f.bottom = Math.max(f.cy + f.h! / 2, f.rightBottom!) f.phase = 2; } break; } 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); + 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; @@ -261,11 +262,11 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { const gSizes: Size[] = []; let rowW = 0; for (const g of f.gParams) { - const gs = size(g); - gSizes.push(gs); + const gs = size(g) + gSizes.push(gs) rowW += gs.w; } - rowW += H * (f.gParams.length - 1); + rowW += H * (f.gParams.length - 1) f.gSizes = gSizes; f.gx = f.cx - rowW / 2; f.gy = f.bottom! + V; @@ -284,7 +285,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { const gcx = f.gx! + gs.w / 2; const gcy = f.gy! + gs.h / 2; f.gx! += gs.w + H; - stack.push({node: g, cx: gcx, cy: gcy, phase: 0}); + stack.push({node: g, cx: gcx, cy: gcy, phase: 0}) f.childPs = gs; f.phase = 30; } else { @@ -295,7 +296,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { } case 30: { const subBottom = f.lastChildBottom!; - f.rowBottom = Math.max(f.rowBottom!, subBottom); + f.rowBottom = Math.max(f.rowBottom!, subBottom) f.gIndex!++; f.phase = 3; break; @@ -305,7 +306,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { const kidsAll = rfKids.get(f.node.id) ?? []; const kids: Node[] = []; for (const k of kidsAll) { - if (!(k.data as any)?.linkingId) kids.push(k); + if (!(k.data as any)?.linkingId) kids.push(k) } f.kids = kids; f.kidIndex = 0; @@ -319,14 +320,14 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { case 5: { if (f.kidIndex! < f.kids!.length) { const k = f.kids![f.kidIndex!]; - const ks = size(k); + const ks = size(k) const ky = f.curY! + ks.h / 2; - stack.push({node: k, cx: f.cx, cy: ky, phase: 0}); + stack.push({node: k, cx: f.cx, cy: ky, phase: 0}) f.childPs = ks; f.phase = 50; } else { const contentBottom = f.curY! - V; - f.bottom = Math.max(f.bottom!, contentBottom + PAD); + f.bottom = Math.max(f.bottom!, contentBottom + PAD) f.phase = 6; } break; @@ -351,26 +352,26 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { } return returnBottom; - }; + } /* Root-Nodes untereinander stapeln (nur echte Roots, keine Param-Nodes) */ let yCursor = 0; for (const r of work) { if (!(r.data as any)?.linkingId && !r.parentId) { - const b = layoutIter(r, 0, yCursor + size(r).h / 2); + const b = layoutIter(r, 0, yCursor + size(r).h / 2) yCursor = b + V; } } /* ---------- rel (Center) → abs (Top-Left) ----------------------------- */ - const absCenter = new Map(); - for (const n of work) absCenter.set(n.id, rel.get(n.id)!); + const absCenter = new Map() + for (const n of work) absCenter.set(n.id, rel.get(n.id)!) - const absTL_initial = new Map(); + const absTL_initial = new Map() for (const n of work) { - const {w, h} = size(n); + const {w, h} = size(n) const {x, y} = absCenter.get(n.id)!; - absTL_initial.set(n.id, {x: x - w / 2, y: y - h / 2}); + absTL_initial.set(n.id, {x: x - w / 2, y: y - h / 2}) } /* ---------- positions in RF-Koordinaten (Top-Left), ggf. relativ zu Parent */ @@ -383,39 +384,39 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { px -= pTL.x; py -= pTL.y; } - n.position = {x: px, y: py}; + n.position = {x: px, y: py} } - const posById = new Map(); - for (const n of work) posById.set(n.id, n); + const posById = new Map() + for (const n of work) posById.set(n.id, n) /* ---------- Bounding-Korrektur jeder Group ----------------------------- */ const depth = (g: Node) => { let d = 0, p: Node | undefined = g; while (p?.parentId) { d++; - p = posById.get(p.parentId); + p = posById.get(p.parentId) if (!p) break; } return d; - }; + } const groups: Node[] = []; for (const n of work) { - if (n.type === 'group') groups.push(n); + if (n.type === 'group') groups.push(n) } - groups.sort((a, b) => depth(b) - depth(a)); + groups.sort((a, b) => depth(b) - depth(a)) for (const g of groups) { const direct: Node[] = []; for (const k of work) { - if (k.parentId === g.id) direct.push(k); + if (k.parentId === g.id) direct.push(k) } if (!direct.length) { const gw = typeof g.style?.width === 'number' ? g.style.width : 2 * PAD; const gh = typeof g.style?.height === 'number' ? g.style.height : 2 * PAD; - g.style = {...(g.style as React.CSSProperties), width: gw, height: gh}; + g.style = {...(g.style as React.CSSProperties), width: gw, height: gh} continue; } @@ -423,8 +424,8 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { const sw = typeof n.style?.width === 'number' ? n.style.width : undefined; const sh = typeof n.style?.height === 'number' ? n.style.height : undefined; const s = baseSizes.get(n.id)!; - return {w: sw ?? s.w, h: sh ?? s.h}; - }; + return {w: sw ?? s.w, h: sh ?? s.h} + } let minX = Number.POSITIVE_INFINITY, minY = Number.POSITIVE_INFINITY, @@ -432,7 +433,7 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { maxY = Number.NEGATIVE_INFINITY; for (const k of direct) { - const ks = childSize(k); + const ks = childSize(k) if (k.position.x < minX) minX = k.position.x; if (k.position.y < minY) minY = k.position.y; if (k.position.x + ks.w > maxX) maxX = k.position.x + ks.w; @@ -460,47 +461,47 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { changed = true; } - g.measured = {width: newW, height: newH}; - g.style = {...(g.style as React.CSSProperties), width: newW, height: newH}; - baseSizes.set(g.id, {w: newW, h: newH}); + g.measured = {width: newW, height: newH} + g.style = {...(g.style as React.CSSProperties), width: newW, height: newH} + baseSizes.set(g.id, {w: newW, h: newH}) } /* ---------- Param-Group-Row nach Bounding sauber zentrieren ----------- */ // WICHTIG: Größen-Cache invalidieren, da Group-Styles sich geändert haben - sizeCache.clear(); - for (const n of work) size(n); + sizeCache.clear() + for (const n of work) size(n) // Globale Center bleiben in rel; aber Top-Left muss mit NEUEN Größen berechnet werden - const absTL = new Map(); - const absCenterAfter = new Map(); + const absTL = new Map() + const absCenterAfter = new Map() for (const n of work) { - const s = size(n); + const s = size(n) const c = rel.get(n.id)!; // globales Center aus dem Layout-Durchlauf - absCenterAfter.set(n.id, c); - absTL.set(n.id, {x: c.x - s.w / 2, y: c.y - s.h / 2}); + absCenterAfter.set(n.id, c) + absTL.set(n.id, {x: c.x - s.w / 2, y: c.y - s.h / 2}) } for (const parent of work) { const pGroups: Node[] = []; const paramList = params.get(parent.id) ?? []; for (const p of paramList) { - if (p.type === 'group') pGroups.push(p); + if (p.type === 'group') pGroups.push(p) } if (!pGroups.length) continue; const ordered = pGroups.slice().sort((a, b) => (+((a.data as any)?.paramIndex) || 0) - (+((b.data as any)?.paramIndex) || 0) - ); + ) const widths: number[] = []; for (const g of ordered) { const gn = posById.get(g.id)!; const sw = typeof gn.style?.width === 'number' ? gn.style.width : undefined; - widths.push(sw ?? size(gn).w); + widths.push(sw ?? size(gn).w) } let rowW = 0; for (const w of widths) rowW += w; - rowW += H * (ordered.length - 1); + rowW += H * (ordered.length - 1) const pCenterX = absCenterAfter.get(parent.id)!.x; let gx = pCenterX - rowW / 2; @@ -508,15 +509,15 @@ const getLayoutElements = (nodes: Node[], dirtyIds?: Set) => { for (let i = 0; i < ordered.length; i++) { const g = ordered[i]; const gn = posById.get(g.id)!; - const containerTL = gn.parentId ? absTL.get(gn.parentId)! : {x: 0, y: 0}; + const containerTL = gn.parentId ? absTL.get(gn.parentId)! : {x: 0, y: 0} gn.position.x = gx - containerTL.x; gx += widths[i] + H; } } - } while (changed && pass < 5); + } while (changed && pass < 5) - return {nodes: work}; + return {nodes: work} } const getCachedLayoutElements = React.cache(getLayoutElements) @@ -553,8 +554,8 @@ const InternalDFlow: React.FC = (props) => { const revalidateHandles = React.useCallback((ids: string[]) => { requestAnimationFrame(() => { - ids.forEach(id => updateNodeInternals(id)); - }); + ids.forEach(id => updateNodeInternals(id)) + }) }, [updateNodeInternals]) const nodeChangeEvent = React.useCallback((changes: any) => { @@ -562,17 +563,17 @@ const InternalDFlow: React.FC = (props) => { changes .filter((c: any) => c.type === 'dimensions' || c.type === 'position') .map((c: any) => c.id) - )); + )) - const dimensionMap = new Map(); + const dimensionMap = new Map() changes .filter((c: any) => c.type === 'dimensions') - .forEach((c: any) => dimensionMap.set(c.id, c.dimensions)); + .forEach((c: any) => dimensionMap.set(c.id, c.dimensions)) setNodes(prevNodes => { const localNodes = prevNodes.map(node => { if (!dimensionMap.has(node.id)) return node; - const dims = dimensionMap.get(node.id) || {}; + const dims = dimensionMap.get(node.id) || {} return { ...node, measured: { @@ -580,14 +581,14 @@ const InternalDFlow: React.FC = (props) => { height: dims.height ?? node.measured?.height ?? 0, } } as Node; - }); + }) - const layouted = getCachedLayoutElements(localNodes, new Set(changedIds)); + const layouted = getCachedLayoutElements(localNodes, new Set(changedIds)) return layouted.nodes as Node[]; - }); + }) - revalidateHandles(changedIds); - }, [revalidateHandles]); + revalidateHandles(changedIds) + }, [revalidateHandles]) React.useEffect(() => { const localNodes = initialNodes.map(value => { @@ -598,8 +599,8 @@ const InternalDFlow: React.FC = (props) => { width: value.measured?.width ?? (nodeEls[0] as any)?.clientWidth ?? 0, height: value.measured?.height ?? (nodeEls[0] as any)?.clientHeight ?? 0, } - } as Node; - }); + } as unknown as Node + }) const layouted = getCachedLayoutElements(localNodes, new Set(localNodes.map(n => n.id))) setNodes(layouted.nodes as Node[]) From 0aefda801c866ab1e6f7d6d383abe1c88509316c Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 8 Dec 2025 19:48:37 +0100 Subject: [PATCH 031/197] feat: update DFlowFolder styles for improved alignment and spacing --- src/components/d-flow/folder/DFlowFolder.style.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/d-flow/folder/DFlowFolder.style.scss b/src/components/d-flow/folder/DFlowFolder.style.scss index 0205beb6e..4f7f07633 100644 --- a/src/components/d-flow/folder/DFlowFolder.style.scss +++ b/src/components/d-flow/folder/DFlowFolder.style.scss @@ -9,6 +9,8 @@ flex-wrap: nowrap; gap: variables.$xxs; cursor: pointer; + align-items: center; + justify-content: space-between; font-size: variables.$sm; -webkit-touch-callout: none; From b6890ceb18aa11b791c997958917ab60d1657fb0 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 8 Dec 2025 19:48:42 +0100 Subject: [PATCH 032/197] feat: enhance DFlowFolder component with context menu and improved layout --- src/components/d-flow/folder/DFlowFolder.tsx | 141 +++++++++++-------- 1 file changed, 82 insertions(+), 59 deletions(-) diff --git a/src/components/d-flow/folder/DFlowFolder.tsx b/src/components/d-flow/folder/DFlowFolder.tsx index 3ac4ef9a0..9847a821b 100644 --- a/src/components/d-flow/folder/DFlowFolder.tsx +++ b/src/components/d-flow/folder/DFlowFolder.tsx @@ -1,15 +1,20 @@ "use client" import "./DFlowFolder.style.scss" -import React from "react"; -import {Code0Component} from "../../../utils/types"; -import {mergeCode0Props} from "../../../utils/utils"; -import {IconChevronDown, IconChevronRight, IconFolder} from "@tabler/icons-react"; -import type {Scalars} from "@code0-tech/sagittarius-graphql-types"; -import {useService, useStore} from "../../../utils/contextStore"; -import {DFlowReactiveService} from "../DFlow.service"; -import {ScrollArea, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport} from "../../scroll-area/ScrollArea"; -import {FlowView} from "../DFlow.view"; +import React from "react" +import {Code0Component} from "../../../utils/types" +import {mergeCode0Props} from "../../../utils/utils" +import {IconChevronDown, IconChevronRight, IconDots, IconFolder, IconFolderOpen} from "@tabler/icons-react" +import type {Scalars} from "@code0-tech/sagittarius-graphql-types" +import {useService, useStore} from "../../../utils/contextStore" +import {DFlowReactiveService} from "../DFlow.service" +import {ScrollArea, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport} from "../../scroll-area/ScrollArea" +import {FlowView} from "../DFlow.view" +import {Flex} from "../../flex/Flex" +import {Text} from "../../text/Text" +import {Button} from "../../button/Button" +import {ContextMenu, ContextMenuContent, ContextMenuPortal, ContextMenuTrigger} from "@radix-ui/react-context-menu"; +import {MenuContent} from "../../menu/Menu"; export interface DFlowFolderProps { @@ -32,43 +37,43 @@ export interface DFlowFolderItemProps extends Code0Component { export const DFlowFolder: React.FC = (props) => { - const { flowId } = props; + const {flowId} = props - const flowService = useService(DFlowReactiveService); + const flowService = useService(DFlowReactiveService) const flowStore = useStore(DFlowReactiveService) type TreeNode = { - name: string; - path: string; - children: Record; - flow?: FlowView; - }; + name: string + path: string + children: Record + flow?: FlowView + } const normalizePath = (p: string) => - p.replace(/^\/+|\/+$/g, "").split("/").filter(Boolean); + p.replace(/^\/+|\/+$/g, "").split("/").filter(Boolean) const flows = React.useMemo(() => { - const raw = (flowService.values?.() ?? []) as FlowView[]; - return raw.filter(f => !!f?.name); - }, [flowStore]); + const raw = (flowService.values?.() ?? []) as FlowView[] + return raw.filter(f => !!f?.name) + }, [flowStore]) const activePathSegments = React.useMemo(() => { - const active = flows.find(f => f.id === flowId); - if (!active?.name) return []; - return normalizePath(active.name); - }, [flows, flowId]); + const active = flows.find(f => f.id === flowId) + if (!active?.name) return [] + return normalizePath(active.name) + }, [flows, flowId]) const tree = React.useMemo(() => { - const root: TreeNode = { name: "", path: "", children: {} }; + const root: TreeNode = {name: "", path: "", children: {}} for (const flow of flows) { - const segs = normalizePath(flow.name as string); - if (segs.length === 0) continue; + const segs = normalizePath(flow.name as string) + if (segs.length === 0) continue - let cur = root; - let acc = ""; + let cur = root + let acc = "" for (let i = 0; i < segs.length; i++) { - const seg = segs[i]; - acc = acc ? `${acc}/${seg}` : seg; + const seg = segs[i] + acc = acc ? `${acc}/${seg}` : seg if (i === segs.length - 1) { // leaf (Flow) @@ -78,10 +83,10 @@ export const DFlowFolder: React.FC = (props) => { path: acc, children: {}, flow - }; + } } else { // falls es bereits einen Knoten gibt, hänge Flow an - cur.children[seg].flow = flow; + cur.children[seg].flow = flow } } else { // folder @@ -90,29 +95,29 @@ export const DFlowFolder: React.FC = (props) => { name: seg, path: acc, children: {} - }; + } } - cur = cur.children[seg]; + cur = cur.children[seg] } } } - return root; - }, [flows]); + return root + }, [flows]) const isPrefixOfActive = React.useCallback((nodePath: string) => { - if (!nodePath) return false; - const segs = nodePath.split("/").filter(Boolean); - return segs.every((s, i) => activePathSegments[i] === s); - }, [activePathSegments]); + if (!nodePath) return false + const segs = nodePath.split("/").filter(Boolean) + return segs.every((s, i) => activePathSegments[i] === s) + }, [activePathSegments]) const renderChildren = React.useCallback((childrenMap: Record) => { - const nodes = Object.values(childrenMap); + const nodes = Object.values(childrenMap) - const folders = nodes.filter(n => !n.flow); - const items = nodes.filter(n => !!n.flow); + const folders = nodes.filter(n => !n.flow) + const items = nodes.filter(n => !!n.flow) - folders.sort((a, b) => a.name.localeCompare(b.name)); - items.sort((a, b) => a.name.localeCompare(b.name)); + folders.sort((a, b) => a.name.localeCompare(b.name)) + items.sort((a, b) => a.name.localeCompare(b.name)) return ( <> @@ -135,8 +140,8 @@ export const DFlowFolder: React.FC = (props) => { /> ))} - ); - }, [flowId, isPrefixOfActive]); + ) + }, [flowId, isPrefixOfActive]) return ( @@ -146,7 +151,7 @@ export const DFlowFolder: React.FC = (props) => {
- + ) @@ -156,15 +161,24 @@ export const DFlowFolder: React.FC = (props) => { export const DFlowFolderGroup: React.FC = (props) => { const {name, defaultOpen = false, children, ...rest} = props - const [open, setOpen] = React.useState(defaultOpen); + const [open, setOpen] = React.useState(defaultOpen) return
setOpen(prevState => !prevState)} {...mergeCode0Props(`d-folder`, rest)}> - setOpen(prevState => !prevState)} className={"d-folder__status"}> - {open ? : } - - - {name} + + + {open ? : } + + {name} + + + + +
{open ? children : null} @@ -176,9 +190,18 @@ export const DFlowFolderItem: React.FC = (props) => { const {name, icon, active, ...rest} = props - return
- {icon? {icon} : null} - {name} -
+ return + +
+ {icon ? {icon} : null} + {name} +
+
+ + + sd + + +
} From fed47474915a86128a1cfe549dd86655a487a716 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Mon, 8 Dec 2025 19:48:47 +0100 Subject: [PATCH 033/197] feat: simplify depth retrieval in DFlowFunctionGroupCard component --- src/components/d-flow/function/DFlowFunctionGroupCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/d-flow/function/DFlowFunctionGroupCard.tsx b/src/components/d-flow/function/DFlowFunctionGroupCard.tsx index d6473f1e2..d0033d056 100644 --- a/src/components/d-flow/function/DFlowFunctionGroupCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionGroupCard.tsx @@ -17,7 +17,7 @@ export type DFlowFunctionGroupCardProps = NodeProps = memo((props) => { const {data, id} = props - const depth = (data as any)?.depth ?? 0; + const depth = data?.depth ?? 0; const color = FLOW_EDGE_RAINBOW[depth % FLOW_EDGE_RAINBOW.length]; // Align handles with the first node inside this group From 4aa575bc591de653ae507d6339711b95ad085e1f Mon Sep 17 00:00:00 2001 From: nicosammito Date: Tue, 9 Dec 2025 17:04:06 +0100 Subject: [PATCH 034/197] feat: add ContextMenu component with styles and storybook integration --- .../context-menu/ContextMenu.stories.tsx | 9 ++ .../context-menu/ContextMenu.style.scss | 70 +++++++++++ src/components/context-menu/ContextMenu.tsx | 117 ++++++++++++++++++ 3 files changed, 196 insertions(+) create mode 100644 src/components/context-menu/ContextMenu.stories.tsx create mode 100644 src/components/context-menu/ContextMenu.style.scss create mode 100644 src/components/context-menu/ContextMenu.tsx diff --git a/src/components/context-menu/ContextMenu.stories.tsx b/src/components/context-menu/ContextMenu.stories.tsx new file mode 100644 index 000000000..620e2e858 --- /dev/null +++ b/src/components/context-menu/ContextMenu.stories.tsx @@ -0,0 +1,9 @@ +import {ContextMenu} from "./ContextMenu"; +import {Meta} from "@storybook/react-vite"; + +const meta: Meta = { + title: 'Components/ContextMenu', + component: ContextMenu, +} + +export default meta \ No newline at end of file diff --git a/src/components/context-menu/ContextMenu.style.scss b/src/components/context-menu/ContextMenu.style.scss new file mode 100644 index 000000000..feaaca5a3 --- /dev/null +++ b/src/components/context-menu/ContextMenu.style.scss @@ -0,0 +1,70 @@ +@use "../../styles/helpers"; +@use "../../styles/box"; +@use "../../styles/variables"; + +.context-menu { + + &__content, &__sub-content { + padding: variables.$xxs; + position: relative; + box-sizing: border-box; + z-index: 999; + + & { + @include helpers.borderRadius(); + } + } + + &__label { + text-transform: uppercase; + font-size: variables.$xs; + display: flex; + gap: variables.$xxs; + align-items: center; + padding: variables.$xxs variables.$xs; + color: helpers.color(); + + & { + @include helpers.fontStyle(); + } + } + + &__item, &__sub-trigger { + border-radius: variables.$borderRadius - variables.$xxs; + padding: variables.$xxs variables.$xs; + gap: variables.$xs; + cursor: pointer; + width: 100%; + display: flex; + align-items: center; + font-size: variables.$sm; + + & { + @include box.box(variables.$primary, variables.$white, variables.$primary); + @include helpers.noFocusStyle(); + @include helpers.fontStyle(); + @include helpers.disabled(); + border: none; + } + + &:focus, &[data-focus=true] { + @include box.box(variables.$white, variables.$white, variables.$white); + border: none; + width: 100%; + } + } + + &__separator { + border: none; + margin: variables.$xxs 0; + color: rgba(white, .1); + height: 1px; + background-color: rgba(white, .1); + } +} + +@each $name, $color in variables.$colors { + .context-menu__content--#{$name}, .context-menu__sub-content--#{$name} { + @include box.box($color); + } +} \ No newline at end of file diff --git a/src/components/context-menu/ContextMenu.tsx b/src/components/context-menu/ContextMenu.tsx new file mode 100644 index 000000000..db2cf08d7 --- /dev/null +++ b/src/components/context-menu/ContextMenu.tsx @@ -0,0 +1,117 @@ +"use client" + +import React from "react"; +import {Code0ComponentProps, Color, mergeCode0Props} from "../../utils"; +import * as Radix from "@radix-ui/react-context-menu"; +import "./ContextMenu.style.scss" +import {Card} from "../card/Card"; +import {Flex} from "../flex/Flex"; +import {Badge} from "../badge/Badge"; +import {IconArrowDown, IconArrowUp, IconCornerDownLeft} from "@tabler/icons-react"; +import {Spacing} from "../spacing/Spacing"; + +export type ContextMenuProps = Code0ComponentProps & Radix.ContextMenuProps +export type ContextMenuTriggerProps = Code0ComponentProps & Radix.ContextMenuTriggerProps +export type ContextMenuPortalProps = Code0ComponentProps & Radix.ContextMenuPortalProps +export type ContextMenuContentProps = Code0ComponentProps & Radix.ContextMenuContentProps & { + color?: Color +} +export type ContextMenuLabelProps = Code0ComponentProps & Radix.ContextMenuLabelProps +export type ContextMenuItemProps = Code0ComponentProps & Radix.ContextMenuItemProps +export type ContextMenuGroupProps = Code0ComponentProps & Radix.ContextMenuGroupProps +export type ContextMenuSubProps = Code0ComponentProps & Radix.ContextMenuSubProps +export type ContextMenuSubTriggerProps = Code0ComponentProps & Radix.ContextMenuSubTriggerProps +export type ContextMenuSubContentProps = Code0ComponentProps & Radix.ContextMenuSubContentProps & { + color?: Color +} +export type ContextMenuSeparatorProps = Code0ComponentProps & Radix.ContextMenuSeparatorProps +export type ContextMenuArrowProps = Code0ComponentProps & Radix.ContextMenuArrowProps + +export const ContextMenu: React.FC = (props) => { + return +} + +export const ContextMenuTrigger: React.FC = (props) => { + return +} + +export const ContextMenuPortal: React.FC = (props) => { + return +} + +export const ContextMenuContent: React.FC = (props) => { + return + + {props.children} + + + + + + + + + move + + + + + select + + + + +} + +export const ContextMenuLabel: React.FC = (props) => { + return +} + +export const ContextMenuItem: React.FC = (props) => { + return +} + +export const ContextMenuGroup: React.FC = (props) => { + return +} + +export const ContextMenuSub: React.FC = (props) => { + return +} + +export const ContextMenuSubTrigger: React.FC = (props) => { + return +} + +export const ContextMenuSubContent: React.FC = (props) => { + return + + {props.children} + + + + + + + + + move + + + + + select + + + + +} + +export const ContextMenuSeparator: React.FC = (props) => { + return +} + +export const ContextMenuArrow: React.FC = (props) => { + return +} \ No newline at end of file From 978725c949dade5da33b63523e9c513df5438d1c Mon Sep 17 00:00:00 2001 From: nicosammito Date: Tue, 9 Dec 2025 17:04:13 +0100 Subject: [PATCH 035/197] feat: add DFlowFolderContextMenu component with context menu functionality and dialog support --- .../d-flow/folder/DFlowFolderContextMenu.tsx | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 src/components/d-flow/folder/DFlowFolderContextMenu.tsx diff --git a/src/components/d-flow/folder/DFlowFolderContextMenu.tsx b/src/components/d-flow/folder/DFlowFolderContextMenu.tsx new file mode 100644 index 000000000..0c11b8095 --- /dev/null +++ b/src/components/d-flow/folder/DFlowFolderContextMenu.tsx @@ -0,0 +1,147 @@ +import {DFlowFolderProps} from "./DFlowFolder"; +import React from "react"; +import { + ContextMenu, + ContextMenuContent, + ContextMenuItem, + ContextMenuPortal, + ContextMenuSeparator, + ContextMenuSub, + ContextMenuSubContent, + ContextMenuSubTrigger, + ContextMenuTrigger +} from "../../context-menu/ContextMenu"; +import {Flex} from "../../flex/Flex"; +import {Text} from "../../text/Text"; +import {IconChevronRight, IconClipboard, IconCopy, IconEdit, IconTrash} from "@tabler/icons-react"; +import {Dialog, DialogClose, DialogContent, DialogPortal} from "../../dialog/Dialog"; +import {Badge} from "../../badge/Badge"; +import {Button} from "../../button/Button"; +import {FlowView} from "../DFlow.view"; +import {TextInput} from "../../form"; +import {useService, useStore} from "../../../utils"; +import {DFlowTypeReactiveService} from "../type"; +import {DFlowFolderItemPathInput} from "./DFlowFolderItemPathInput"; + +export interface DFlowFolderContextMenuGroupData { + name: string + flows: FlowView[] + type: "group" +} + +export interface DFlowFolderContextMenuItemData { + name: string + flow: FlowView + type: "item" +} + +export interface DFlowFolderContextMenuProps extends DFlowFolderProps { + children: React.ReactNode + contextData: DFlowFolderContextMenuGroupData | DFlowFolderContextMenuItemData +} + +export const DFlowFolderContextMenu: React.FC = (props) => { + + const {children} = props + + const flowTypeService = useService(DFlowTypeReactiveService) + const flowTypeStore = useStore(DFlowTypeReactiveService) + + const flowTypes = React.useMemo(() => flowTypeService.values(), [flowTypeStore]) + const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false) + const [renameDialogOpen, setRenameDialogOpen] = React.useState(false) + + return <> + setDeleteDialogOpen(open)}> + + + + {props.contextData.type == "item" ? "Are you sure you want to remove flow" : "Are you sure you want to remove folder"} {" "} + + {props.contextData.name} + {" "} + {props.contextData.type == "group" ? ", all flows and sub-folders inside " : ""}from the this project? + + + + + + + + + + + + + + setRenameDialogOpen(open)}> + + +
+ +
+ + + + + + + + +
+
+
+ + + + {children} + + + + + + + New flow + + + + + {flowTypes.map(flowType => { + return + {flowType.names?.nodes!![0]?.content ?? flowType.id} + + })} + + + setRenameDialogOpen(true)}> + + Rename + + setDeleteDialogOpen(true)}> + + Delete + + + + + + +} \ No newline at end of file From 4de5ffd7946a5a8b10caafdaf0cc1f0dc64c59eb Mon Sep 17 00:00:00 2001 From: nicosammito Date: Tue, 9 Dec 2025 17:04:20 +0100 Subject: [PATCH 036/197] feat: add @radix-ui/react-context-menu dependency to enhance context menu functionality --- package-lock.json | 76 ++++++++++++++++++++--------------------------- package.json | 3 ++ 2 files changed, 36 insertions(+), 43 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0553bd260..1a5d41899 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,9 @@ "": { "name": "@code0-tech/pictor", "version": "0.0.0", + "dependencies": { + "@radix-ui/react-context-menu": "^2.2.16" + }, "devDependencies": { "@ariakit/react": "^0.4.17", "@babel/plugin-proposal-decorators": "^7.28.0", @@ -1741,7 +1744,6 @@ "version": "1.7.3", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", - "dev": true, "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.10" @@ -1751,7 +1753,6 @@ "version": "1.7.4", "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", - "dev": true, "license": "MIT", "dependencies": { "@floating-ui/core": "^1.7.3", @@ -1762,7 +1763,6 @@ "version": "2.1.6", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", - "dev": true, "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.7.4" @@ -1776,7 +1776,6 @@ "version": "0.2.10", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", - "dev": true, "license": "MIT" }, "node_modules/@isaacs/balanced-match": { @@ -4093,14 +4092,12 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", - "dev": true, "license": "MIT" }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" @@ -4155,7 +4152,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -4182,7 +4178,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", - "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -4198,7 +4193,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", - "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -4210,6 +4204,34 @@ } } }, + "node_modules/@radix-ui/react-context-menu": { + "version": "2.2.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context-menu/-/react-context-menu-2.2.16.tgz", + "integrity": "sha512-O8morBEW+HsVG28gYDZPTrT9UUovQUlJue5YO836tiTJhuIWBm/zQHc7j388sHWtdH/xUZurK9olD2+pcqx5ww==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-dialog": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", @@ -4251,7 +4273,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", - "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -4267,7 +4288,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -4325,7 +4345,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", - "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -4341,7 +4360,6 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -4367,7 +4385,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" @@ -4386,7 +4403,6 @@ "version": "2.1.16", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -4462,7 +4478,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", - "dev": true, "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", @@ -4495,7 +4510,6 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3", @@ -4520,7 +4534,6 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -4545,7 +4558,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-slot": "1.2.3" @@ -4602,7 +4614,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -4666,7 +4677,6 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -4807,7 +4817,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", - "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -4823,7 +4832,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", @@ -4843,7 +4851,6 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" @@ -4862,7 +4869,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" @@ -4900,7 +4906,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", - "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -4932,7 +4937,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/rect": "1.1.1" @@ -4951,7 +4955,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", - "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" @@ -4994,7 +4997,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", - "dev": true, "license": "MIT" }, "node_modules/@rolldown/pluginutils": { @@ -6360,7 +6362,7 @@ "version": "19.2.7", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz", "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", - "dev": true, + "devOptional": true, "license": "MIT", "peer": true, "dependencies": { @@ -7438,7 +7440,6 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -8536,7 +8537,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/d3-color": { @@ -8811,7 +8812,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "dev": true, "license": "MIT" }, "node_modules/diff": { @@ -9595,7 +9595,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -13972,7 +13971,6 @@ "version": "19.2.0", "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", - "dev": true, "license": "MIT", "peer": true, "engines": { @@ -14015,7 +14013,6 @@ "version": "19.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", - "dev": true, "license": "MIT", "peer": true, "dependencies": { @@ -14046,7 +14043,6 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.1.tgz", "integrity": "sha512-HpMh8+oahmIdOuS5aFKKY6Pyog+FNaZV/XyJOq7b4YFwsFHe5yYfdbIalI4k3vU2nSDql7YskmUseHsRrJqIPA==", - "dev": true, "license": "MIT", "dependencies": { "react-remove-scroll-bar": "^2.3.7", @@ -14072,7 +14068,6 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", - "dev": true, "license": "MIT", "dependencies": { "react-style-singleton": "^2.2.2", @@ -14106,7 +14101,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", - "dev": true, "license": "MIT", "dependencies": { "get-nonce": "^1.0.0", @@ -14659,7 +14653,6 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "dev": true, "license": "MIT" }, "node_modules/schema-utils": { @@ -15819,7 +15812,6 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, "license": "0BSD", "peer": true }, @@ -15960,7 +15952,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", - "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -15982,7 +15973,6 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", - "dev": true, "license": "MIT", "dependencies": { "detect-node-es": "^1.1.0", diff --git a/package.json b/package.json index 9948559a7..b75344048 100644 --- a/package.json +++ b/package.json @@ -118,5 +118,8 @@ }, "publishConfig": { "access": "public" + }, + "dependencies": { + "@radix-ui/react-context-menu": "^2.2.16" } } From f6c0ed3b3061b0c7698ddfe9778f3a8a3a831682 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Tue, 9 Dec 2025 17:05:06 +0100 Subject: [PATCH 037/197] feat: update DFlowFolder component to use activeFlowId prop in DResizable story --- src/components/d-resizable/DResizable.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/d-resizable/DResizable.stories.tsx b/src/components/d-resizable/DResizable.stories.tsx index 7df186718..ae468a760 100644 --- a/src/components/d-resizable/DResizable.stories.tsx +++ b/src/components/d-resizable/DResizable.stories.tsx @@ -131,7 +131,7 @@ export const Dashboard = () => { }> - + From 97a4c22b9e6487a085e647887b703ca4a6a8e9f3 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Tue, 9 Dec 2025 17:05:15 +0100 Subject: [PATCH 038/197] feat: enhance DFlowFolder component with context menu integration and activeFlowId prop --- .../d-flow/folder/DFlowFolder.style.scss | 10 +- src/components/d-flow/folder/DFlowFolder.tsx | 111 ++++++++++-------- 2 files changed, 69 insertions(+), 52 deletions(-) diff --git a/src/components/d-flow/folder/DFlowFolder.style.scss b/src/components/d-flow/folder/DFlowFolder.style.scss index 4f7f07633..271fae36f 100644 --- a/src/components/d-flow/folder/DFlowFolder.style.scss +++ b/src/components/d-flow/folder/DFlowFolder.style.scss @@ -29,6 +29,10 @@ background: variables.$primary; } + &[data-state=open] { + @include box.box(variables.$secondary); + border: none; + } &__icon, &__status, &__item-icon { color: variables.$info; @@ -55,7 +59,6 @@ position: absolute; content: ""; left: 5px; - } } @@ -78,5 +81,10 @@ @include helpers.fontStyle(); border: none; } + + &[data-state=open] { + @include box.box(variables.$secondary); + border: none; + } } } \ No newline at end of file diff --git a/src/components/d-flow/folder/DFlowFolder.tsx b/src/components/d-flow/folder/DFlowFolder.tsx index 9847a821b..cc5da34a6 100644 --- a/src/components/d-flow/folder/DFlowFolder.tsx +++ b/src/components/d-flow/folder/DFlowFolder.tsx @@ -2,42 +2,44 @@ import "./DFlowFolder.style.scss" import React from "react" -import {Code0Component} from "../../../utils/types" -import {mergeCode0Props} from "../../../utils/utils" +import {Code0Component, mergeCode0Props, useService, useStore} from "../../../utils" import {IconChevronDown, IconChevronRight, IconDots, IconFolder, IconFolderOpen} from "@tabler/icons-react" -import type {Scalars} from "@code0-tech/sagittarius-graphql-types" -import {useService, useStore} from "../../../utils/contextStore" +import type {FlowType, Scalars} from "@code0-tech/sagittarius-graphql-types" import {DFlowReactiveService} from "../DFlow.service" import {ScrollArea, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport} from "../../scroll-area/ScrollArea" import {FlowView} from "../DFlow.view" import {Flex} from "../../flex/Flex" import {Text} from "../../text/Text" import {Button} from "../../button/Button" -import {ContextMenu, ContextMenuContent, ContextMenuPortal, ContextMenuTrigger} from "@radix-ui/react-context-menu"; -import {MenuContent} from "../../menu/Menu"; +import {DFlowFolderContextMenu} from "./DFlowFolderContextMenu"; export interface DFlowFolderProps { - flowId: Scalars["FlowID"]["output"] + activeFlowId: Scalars["FlowID"]["output"] + onRename?: (flow: FlowView, newName: string) => void + onDelete?: (flow: FlowView) => void + onCreate?: (name: string, type: FlowType['id']) => void + onMove?: (flow: FlowView, newPath: string) => void } -export interface DFlowFolderGroupProps extends Omit, "controls"> { +export interface DFlowFolderGroupProps extends DFlowFolderProps, Omit, "controls"> { name: string children: React.ReactElement | React.ReactElement[] | React.ReactElement | React.ReactElement[] - //defaults to false defaultOpen?: boolean + flows: FlowView[] } -export interface DFlowFolderItemProps extends Code0Component { +export interface DFlowFolderItemProps extends DFlowFolderProps, Code0Component { name: string + path: string icon?: React.ReactNode - //defaults to false active?: boolean + flow: FlowView } export const DFlowFolder: React.FC = (props) => { - const {flowId} = props + const {activeFlowId} = props const flowService = useService(DFlowReactiveService) const flowStore = useStore(DFlowReactiveService) @@ -58,10 +60,10 @@ export const DFlowFolder: React.FC = (props) => { }, [flowStore]) const activePathSegments = React.useMemo(() => { - const active = flows.find(f => f.id === flowId) + const active = flows.find(f => f.id === activeFlowId) if (!active?.name) return [] return normalizePath(active.name) - }, [flows, flowId]) + }, [flows, activeFlowId]) const tree = React.useMemo(() => { const root: TreeNode = {name: "", path: "", children: {}} @@ -125,7 +127,9 @@ export const DFlowFolder: React.FC = (props) => { value.flow!!)} defaultOpen={isPrefixOfActive(folder.path)} + {...props} > {renderChildren(folder.children)} @@ -134,14 +138,16 @@ export const DFlowFolder: React.FC = (props) => { ))} ) - }, [flowId, isPrefixOfActive]) + }, [activeFlowId, isPrefixOfActive]) return ( @@ -160,48 +166,51 @@ export const DFlowFolder: React.FC = (props) => { export const DFlowFolderGroup: React.FC = (props) => { - const {name, defaultOpen = false, children, ...rest} = props + const {name, flows, defaultOpen = false, children, ...rest} = props const [open, setOpen] = React.useState(defaultOpen) - return
-
setOpen(prevState => !prevState)} {...mergeCode0Props(`d-folder`, rest)}> - - - {open ? : } - - {name} - - - - - -
+ return <> + +
setOpen(prevState => !prevState)} {...mergeCode0Props(`d-folder`, rest)}> + + + {open ? : } + + {name} + + + + + +
+
{open ? children : null}
-
+ } export const DFlowFolderItem: React.FC = (props) => { - const {name, icon, active, ...rest} = props + const {name, path, flow, icon, active, ...rest} = props - return - -
- {icon ? {icon} : null} - {name} -
-
- - - sd - - -
-} + return +
+ {icon ? {icon} : null} + {name} +
+
+} From 184b394b4683cebee234278e4e06ee817292a517 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Tue, 9 Dec 2025 17:05:23 +0100 Subject: [PATCH 039/197] feat: generate unique id for node function suggestions in DFlowSuggestion hook --- src/components/d-flow/suggestion/DFlowSuggestion.hook.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/d-flow/suggestion/DFlowSuggestion.hook.tsx b/src/components/d-flow/suggestion/DFlowSuggestion.hook.tsx index 8af6c93e1..0aeb71013 100644 --- a/src/components/d-flow/suggestion/DFlowSuggestion.hook.tsx +++ b/src/components/d-flow/suggestion/DFlowSuggestion.hook.tsx @@ -116,7 +116,6 @@ export const useSuggestions = ( matchingFunctions.forEach(funcDefinition => { const nodeFunctionSuggestion: NodeParameterValue = { __typename: "NodeFunction", - //TODO: generate unique id id: `gid://sagittarius/NodeFunction/${(flow?.nodes?.length ?? 0) + 1}`, functionDefinition: { id: funcDefinition.id, From ff093e660a6e18086c9976844f06b819cfa0539c Mon Sep 17 00:00:00 2001 From: nicosammito Date: Tue, 9 Dec 2025 17:05:30 +0100 Subject: [PATCH 040/197] feat: add DFlowFolderItemPathInput component for enhanced path input with syntax transformation --- .../folder/DFlowFolderItemPathInput.tsx | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/components/d-flow/folder/DFlowFolderItemPathInput.tsx diff --git a/src/components/d-flow/folder/DFlowFolderItemPathInput.tsx b/src/components/d-flow/folder/DFlowFolderItemPathInput.tsx new file mode 100644 index 000000000..bac0f3caf --- /dev/null +++ b/src/components/d-flow/folder/DFlowFolderItemPathInput.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import {InputSuggestion, TextInput, TextInputProps} from "../../form"; +import {InputSyntaxSegment} from "../../form/Input.syntax.hook"; +import {Badge} from "../../badge/Badge"; + +export interface DFlowFolderItemPathInputProps extends TextInputProps { + +} + +export const DFlowFolderItemPathInput: React.FC = (props) => { + + const {...rest} = props + + const transformSyntax = ( + value?: string | null, + _: (InputSuggestion | any)[] = [], + ): InputSyntaxSegment[] => { + + const normalizePath = (s: string) => { + const r = [], b = "" + let buf = b + s.split("").forEach(c => + c === "/" + ? (buf && r.push(buf), buf = "", r.push("/")) + : buf += c + ) + buf && r.push(buf) + return r + }; + const splitValue = normalizePath(value ?? "") + let cursor = 0 + + return splitValue.map((value, index) => { + const segment = { + type: splitValue.length - 1 !== index ? "block" : "text", + start: cursor, + end: cursor + value.length, + visualLength: splitValue.length - 1 !== index ? 1 : value.length, + content: splitValue.length - 1 !== index ? {value} : value, + } + cursor += value.length + return [segment] + }).flat() as InputSyntaxSegment[] + + } + + return +} \ No newline at end of file From 7750f5e44bd0199893fa87033c5778603ce60198 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Tue, 9 Dec 2025 17:05:37 +0100 Subject: [PATCH 041/197] feat: update DFlowFolder component to use activeFlowId prop in DFlowBuilder --- src/components/d-flow/DFlowBuilder.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/d-flow/DFlowBuilder.tsx b/src/components/d-flow/DFlowBuilder.tsx index 0034a4a0e..26e7b53bc 100644 --- a/src/components/d-flow/DFlowBuilder.tsx +++ b/src/components/d-flow/DFlowBuilder.tsx @@ -39,7 +39,7 @@ export const DFlowBuilder: React.FC = (props) => { }> - + From 6e1bf90ff1829eb14c18a26457b235d04de04fab Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 16:36:57 +0100 Subject: [PATCH 042/197] feat: refactor DFlow.edges.hook.ts for improved imports and type handling --- src/components/d-flow/DFlow.edges.hook.ts | 76 +++++++++-------------- 1 file changed, 30 insertions(+), 46 deletions(-) diff --git a/src/components/d-flow/DFlow.edges.hook.ts b/src/components/d-flow/DFlow.edges.hook.ts index 678e6b2d3..f40bf6fd8 100644 --- a/src/components/d-flow/DFlow.edges.hook.ts +++ b/src/components/d-flow/DFlow.edges.hook.ts @@ -1,13 +1,11 @@ -import {useService, useStore} from "../../utils/contextStore"; +import {useService, useStore} from "../../utils"; import {DFlowReactiveService} from "./DFlow.service"; import {Edge} from "@xyflow/react"; -import {NodeFunctionView} from "./DFlow.view"; -import {DFlowFunctionReactiveService} from "./function/DFlowFunction.service"; -import {DFlowDataTypeReactiveService} from "./data-type/DFlowDataType.service"; +import {DFlowFunctionReactiveService} from "./function"; +import {DFlowDataTypeReactiveService} from "./data-type"; import React from "react"; -import type {DataTypeIdentifier, Flow, Scalars} from "@code0-tech/sagittarius-graphql-types"; +import type {DataTypeIdentifier, Flow, NodeFunction, Scalars} from "@code0-tech/sagittarius-graphql-types"; -// Deine Primärfarbe als Start, danach harmonisch verteilt export const FLOW_EDGE_RAINBOW: string[] = [ '#70ffb2', // 0 – Primary (Grün) '#70e2ff', // 1 – Cyan @@ -64,24 +62,21 @@ export const useFlowEdges = (flowId: Flow['id']): Edge[] => { return cache.get(type); }; - /* ------------------------------------------------------------------ */ const traverse = ( - fn: NodeFunctionView, - parentFnId?: string, // Id der *Function-Card* des Aufrufers - level = 0, // Tiefe ⇒ Farbe aus dem Rainbow-Array, + fn: NodeFunction, + parentFnId?: string, + level = 0, fnCache = functionCache, dtCache = dataTypeCache, ): string => { - /* ------- Id der aktuellen Function-Card im Diagramm ---------- */ const fnId = `${fn.id}-${idCounter++}`; if (idCounter == 1) { - // erste Function-Card → Verbindung Trigger → Function edges.push({ id: `trigger-${fnId}-next`, - source: flow.id as string, // Handle-Bottom des Trigger-Nodes - target: fnId, // Handle-Top der Function-Card + source: flow.id as string, + target: fnId, data: { color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], isParameter: false @@ -91,15 +86,14 @@ export const useFlowEdges = (flowId: Flow['id']): Edge[] => { }); } - /* ------- vertikale Kante (nextNode) -------------------------- */ if (parentFnId) { const startGroups = groupsWithValue.get(parentFnId) ?? []; if (startGroups.length > 0) { startGroups.forEach((gId, idx) => edges.push({ id: `${gId}-${fnId}-next-${idx}`, - source: gId, // Handle-Bottom der Group-Card - target: fnId, // Handle-Top der Function-Card + source: gId, + target: fnId, data: { color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], isParameter: false @@ -110,8 +104,8 @@ export const useFlowEdges = (flowId: Flow['id']): Edge[] => { } else { edges.push({ id: `${parentFnId}-${fnId}-next`, - source: parentFnId, // Handle-Bottom der Function-Card - target: fnId, // Handle-Top der Function-Card + source: parentFnId, + target: fnId, data: { color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], isParameter: false @@ -122,25 +116,22 @@ export const useFlowEdges = (flowId: Flow['id']): Edge[] => { } } - /* ------- horizontale Kanten für Parameter -------------------- */ - fn.parameters?.forEach((param) => { - const val = param.value; + fn.parameters?.nodes?.forEach((param) => { + const val = param?.value; const def = getFunctionDefinitionCached(fn.functionDefinition?.id!!, fnCache) - ?.parameterDefinitions?.find(p => p.id === param.id); + ?.parameterDefinitions?.find(p => p.id === param?.id); const paramType = def?.dataTypeIdentifier; const paramDT = paramType ? getDataTypeCached(paramType, dtCache) : undefined; if (!val) return - /* --- NODE-Parameter → Group-Card ------------------------- */ if (paramDT?.variant === "NODE") { const groupId = `${fnId}-group-${idCounter++}`; - /* Verbindung Gruppe → Function-Card (horizontal) */ edges.push({ id: `${fnId}-${groupId}-param-${param.id}`, - source: fnId, // FunctionCard (Quelle) - target: groupId, // GroupCard (Ziel – hat Top: target) + source: fnId, + target: groupId, deletable: false, selectable: false, label: def?.names?.nodes!![0]?.content ?? param.id, @@ -150,26 +141,21 @@ export const useFlowEdges = (flowId: Flow['id']): Edge[] => { }, }); - /* existiert ein Funktions-Wert für dieses Param-Feld? */ - if (val && val instanceof NodeFunctionView) { - /* merken: diese Group-Card besitzt Content – das ist - später Startpunkt der next-Kante */ + + if (val && val.__typename === "NodeFunction") { + (groupsWithValue.get(fnId) ?? (groupsWithValue.set(fnId, []), groupsWithValue.get(fnId)!)) .push(groupId); - /* rekursiv Funktions-Ast innerhalb der Gruppe */ - traverse(param.value as NodeFunctionView, + traverse(param.value as NodeFunction, undefined, level + 1, fnCache, dtCache); } - } - - /* --- anderer Parameter, der selbst eine Function hält ---- */ - else if (val && val instanceof NodeFunctionView) { - const subFnId = traverse(param.value as NodeFunctionView, + } else if (val && val.__typename === "NodeFunction") { + const subFnId = traverse(param.value as NodeFunction, undefined, level + 1, fnCache, @@ -191,19 +177,17 @@ export const useFlowEdges = (flowId: Flow['id']): Edge[] => { } }); - /* ------- Rekursion auf nextNode ------------------------------ */ if (fn.nextNodeId) { - traverse(flow.getNodeById(fn.nextNodeId!!)!!, fnId, level, fnCache, dtCache); // gleiche Ebenentiefe + traverse(flowService.getNodeById(flow.id!!, fn.nextNodeId!!)!!, fnId, level, fnCache, dtCache); // gleiche Ebenentiefe } else { - // letzte Function-Card im Ast → Add-new-node wie *normale* nextNode behandeln const suggestionNodeId = `${fnId}-suggestion`; const startGroups = groupsWithValue.get(fnId) ?? []; if (startGroups.length > 0) { startGroups.forEach((gId, idx) => edges.push({ id: `${gId}-${suggestionNodeId}-next-${idx}`, - source: gId, // wie bei echter nextNode von Group-Card starten - target: suggestionNodeId, // Ziel ist die Suggestion-Card + source: gId, + target: suggestionNodeId, data: { color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], isSuggestion: true, @@ -214,8 +198,8 @@ export const useFlowEdges = (flowId: Flow['id']): Edge[] => { } else { edges.push({ id: `${fnId}-${suggestionNodeId}-next`, - source: fnId, // Handle-Bottom der Function-Card - target: suggestionNodeId, // Handle-Top der Suggestion-Card + source: fnId, + target: suggestionNodeId, data: { color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], isSuggestion: true, @@ -230,7 +214,7 @@ export const useFlowEdges = (flowId: Flow['id']): Edge[] => { }; if (flow.startingNodeId) { - traverse(flow.getNodeById(flow.startingNodeId)!!, undefined, 0, functionCache, dataTypeCache); + traverse(flowService.getNodeById(flow.id!!, flow.startingNodeId!!)!!, undefined, 0, functionCache, dataTypeCache); } return edges From a07ce8da1e598afd952023928d887adefa5e1409 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 16:37:04 +0100 Subject: [PATCH 043/197] feat: refactor DFlow.nodes.hook.ts for improved imports and type handling --- src/components/d-flow/DFlow.nodes.hook.ts | 53 +++++++++++------------ 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/src/components/d-flow/DFlow.nodes.hook.ts b/src/components/d-flow/DFlow.nodes.hook.ts index dfdb5d27c..92c6c42b6 100644 --- a/src/components/d-flow/DFlow.nodes.hook.ts +++ b/src/components/d-flow/DFlow.nodes.hook.ts @@ -1,10 +1,9 @@ -import {useService, useStore} from "../../utils/contextStore"; +import {useService, useStore} from "../../utils"; import {DFlowReactiveService} from "./DFlow.service"; -import {NodeFunctionView} from "./DFlow.view"; import {Node} from "@xyflow/react"; -import {DFlowFunctionReactiveService} from "./function/DFlowFunction.service"; -import {DFlowDataTypeReactiveService} from "./data-type/DFlowDataType.service"; -import type {DataTypeIdentifier, DataTypeVariant, Flow, Scalars} from "@code0-tech/sagittarius-graphql-types"; +import {DFlowFunctionReactiveService} from "./function"; +import {DFlowDataTypeReactiveService} from "./data-type"; +import type {DataTypeIdentifier, Flow, NodeFunction, Scalars} from "@code0-tech/sagittarius-graphql-types"; import React from "react"; import {DFlowFunctionDefaultCardDataProps} from "./function/DFlowFunctionDefaultCard"; import {DFlowFunctionSuggestionCardDataProps} from "./function/DFlowFunctionSuggestionCard"; @@ -96,7 +95,7 @@ const bestMatchValue = (map: Map, input: string): string => { }; // @ts-ignore -export const useFlowNodes = (flowId: Flow['id']): Node[] => { +export const useFlowNodes = (flowId: Flow['id']): Node[] => { const flowService = useService(DFlowReactiveService); const flowStore = useStore(DFlowReactiveService); const functionService = useService(DFlowFunctionReactiveService); @@ -147,7 +146,7 @@ export const useFlowNodes = (flowId: Flow['id']): Node { - const id = `${fn.id}-${idCounter++}`; + const id = `${node.id}-${idCounter++}`; const index = ++globalNodeIndex; // global node level nodes.push({ id, - type: bestMatchValue(packageNodes, fn.functionDefinition?.identifier!!), - position: { x: 0, y: 0 }, + type: bestMatchValue(packageNodes, node.functionDefinition?.identifier!!), + position: {x: 0, y: 0}, draggable: false, parentId: parentGroup, extent: parentGroup ? "parent" : undefined, data: { - instance: fn, + node: node, isParameter, flowId: flowId!!, linkingId: isParameter ? parentId : undefined, @@ -187,29 +186,29 @@ export const useFlowNodes = (flowId: Flow['id']): Node { - const paramType = definition?.parameterDefinitions!!.find(p => p.id == param.runtimeParameter?.id)?.dataTypeIdentifier; + node.parameters?.nodes?.forEach((param) => { + const paramType = definition?.parameterDefinitions!!.find(p => p.id == param?.runtimeParameter?.id)?.dataTypeIdentifier; const paramDataType = paramType ? getDataTypeCached(paramType, dtCache) : undefined; if (paramDataType?.variant === "NODE") { - if (param.value && param.value instanceof NodeFunctionView) { + if (param?.value && param.value.__typename === "NodeFunction") { const groupId = `${id}-group-${idCounter++}`; // New group: extend scope PATH with a fresh segment and increase depth. @@ -218,7 +217,7 @@ export const useFlowNodes = (flowId: Flow['id']): Node Date: Thu, 11 Dec 2025 16:37:10 +0100 Subject: [PATCH 044/197] feat: extend DFlowReactiveService with flow and node management methods --- src/components/d-flow/DFlow.service.ts | 58 ++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/src/components/d-flow/DFlow.service.ts b/src/components/d-flow/DFlow.service.ts index dc22ad765..52c62f80d 100644 --- a/src/components/d-flow/DFlow.service.ts +++ b/src/components/d-flow/DFlow.service.ts @@ -1,22 +1,62 @@ -import {FlowView} from "./DFlow.view"; -import {ReactiveArrayService} from "../../utils/reactiveArrayService"; + +import {ReactiveArrayService} from "../../utils"; import type { - Flow, + Flow, FlowSetting, NamespacesProjectsFlowsCreateInput, NamespacesProjectsFlowsCreatePayload, NamespacesProjectsFlowsDeleteInput, - NamespacesProjectsFlowsDeletePayload + NamespacesProjectsFlowsDeletePayload, NodeFunction, NodeParameter } from "@code0-tech/sagittarius-graphql-types"; -export abstract class DFlowReactiveService extends ReactiveArrayService { - - //TODO: inject UI error handler for toasts - //inject: namespaceId and projectId because the runtimes query needs it +export abstract class DFlowReactiveService extends ReactiveArrayService { - getById(id: Flow['id']): FlowView | undefined { + getById(id: Flow['id']): Flow | undefined { return this.values().find(value => value.id === id); } + deleteById(id: Flow['id']): void { + + } + + renameById(id: Flow['id'], newName: string): void { + + } + + getNodeById(flowId: Flow['id'], nodeId: NodeFunction['id']): NodeFunction | undefined { + return this.getById(flowId)?.nodes?.nodes?.find(node => node?.id === nodeId)!! + } + + deleteNodeById(flowId: Flow['id'], nodeId: NodeFunction['id']): void { + + } + + addNextNodeById(flowId: Flow['id'], nodeId: NodeFunction['id'], nextNode: NodeFunction): void { + const flow = this.getById(flowId) + const index = this.values().findIndex(f => f.id === flowId) + const node = this.getNodeById(flowId, nodeId) + if (!flow || !node) return + flow.nodes?.nodes?.push(nextNode) + node.nextNodeId = nextNode.id + this.set(index, flow) + } + + setStartingNodeById(flowId: Flow['id'], startingNode: NodeFunction): void { + const flow = this.getById(flowId) + if (!flow) return + const index = this.values().findIndex(f => f.id === flowId) + flow.nodes?.nodes?.push(startingNode) + flow.startingNodeId = startingNode.id + this.set(index, flow) + } + + setSettingValue(flowId: Flow['id'], settingId: FlowSetting['id'], value: FlowSetting['value']): void { + + } + + setParameterValue(flowId: Flow['id'], nodeId: NodeFunction['id'], parameterId: NodeParameter['id'], value: NodeParameter['value']): void { + + } + /** Creates a new flow. */ abstract flowCreate(payload: NamespacesProjectsFlowsCreateInput): Promise /** Deletes a namespace project. */ From 95c8bebc3ba51c55de9221a373abc71a40e82793 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 16:37:26 +0100 Subject: [PATCH 045/197] feat: remove DFlow.view.ts --- src/components/d-flow/DFlow.view.ts | 405 ---------------------------- 1 file changed, 405 deletions(-) delete mode 100644 src/components/d-flow/DFlow.view.ts diff --git a/src/components/d-flow/DFlow.view.ts b/src/components/d-flow/DFlow.view.ts deleted file mode 100644 index 74409f2d0..000000000 --- a/src/components/d-flow/DFlow.view.ts +++ /dev/null @@ -1,405 +0,0 @@ -import type { - DataType, - Flow, FlowInput, - FlowSetting, FlowSettingInput, - FlowType, FunctionDefinition, - LiteralValue, - Maybe, - NodeFunction, NodeFunctionInput, - NodeParameter, NodeParameterInput, - NodeParameterValue, - ReferenceValue, - RuntimeParameterDefinition, - Scalars -} from "@code0-tech/sagittarius-graphql-types"; -import {ValidationResult} from "../../utils"; - -export class FlowView { - - /** Time when this Flow was created */ - private readonly _createdAt?: Maybe; - /** Global ID of this Flow */ - private readonly _id?: Maybe; - /** The input data type of the flow */ - private _inputType?: Maybe; - /** Nodes of the flow */ - private _nodes?: NodeFunctionView[]; - /** The return data type of the flow */ - private readonly _returnType?: Maybe; - /** The settings of the flow */ - private readonly _settings?: FlowSettingView[]; - /** The ID of the starting node of the flow */ - private _startingNodeId?: Maybe; - /** The flow type of the flow */ - private readonly _type?: Maybe; - /** Time when this Flow was last updated */ - private readonly _updatedAt?: Maybe; - /** Name of the flow */ - private _name?: Maybe; - - constructor(flow: Flow) { - - this._createdAt = flow.createdAt - this._id = flow.id - this._inputType = flow.inputType - this._nodes = flow.nodes?.nodes?.map(node => new NodeFunctionView(node!!)) - this._returnType = flow.returnType - this._settings = flow.settings?.nodes?.map(setting => new FlowSettingView(setting!!)) - this._startingNodeId = flow.startingNodeId - this._type = flow.type - this._updatedAt = flow.updatedAt - this._name = flow.name - - } - - get createdAt(): Maybe | undefined { - return this._createdAt; - } - - get id(): Maybe | undefined { - return this._id; - } - - get inputType(): Maybe | undefined { - return this._inputType; - } - - get nodes(): NodeFunctionView[] | undefined { - return this._nodes; - } - - get returnType(): Maybe | undefined { - return this._returnType; - } - - get settings(): FlowSettingView[] | undefined { - return this._settings; - } - - get startingNodeId(): Maybe | undefined { - return this._startingNodeId; - } - - get type(): Maybe | undefined { - return this._type; - } - - get updatedAt(): Maybe | undefined { - return this._updatedAt; - } - - get name(): Maybe | undefined { - return this._name; - } - - set inputType(value: Maybe) { - this._inputType = value; - } - - set startingNodeId(value: Maybe) { - this._startingNodeId = value; - } - - set name(value: Maybe) { - this._name = value; - } - - addNode(node: NodeFunctionView): void { - if (!this._nodes) { - this._nodes = []; - } - this._nodes.push(node); - } - - updateNode(updatedNode: NodeFunctionView): void { - if (!this._nodes) { - return; - } - this._nodes = this._nodes.map(node => { - if (node.id === updatedNode.id) { - return updatedNode; - } - return node; - }); - } - - removeNode(nodeId: Scalars['NodeFunctionID']['output']): void { - if (!this._nodes) { - return; - } - this._nodes = this._nodes.filter(node => node.id !== nodeId); - } - - getNodeById(nodeId: Scalars['NodeFunctionID']['output']): NodeFunctionView | undefined { - if (!this._nodes) { - return undefined; - } - return this._nodes.find(node => node.id === nodeId); - } - - json(): Flow { - return { - __typename: "Flow", - createdAt: this._createdAt, - id: this._id, - inputType: this._inputType, - nodes: this._nodes ? { - nodes: this._nodes.map(node => node.json()!!) - } : undefined, - returnType: this._returnType, - settings: this._settings ? { - nodes: this._settings.map(setting => setting.json()!!) - } : undefined, - startingNodeId: this._startingNodeId, - type: this._type, - updatedAt: this._updatedAt, - name: this._name, - } - } - - jsonInput(): FlowInput { - return { - name: this._name, - nodes: this._nodes?.map(node => node.jsonInput()), - settings: this._settings?.map(setting => setting.jsonInput()), - startingNodeId: this._startingNodeId, - type: `gid://sagittarius/FlowType/1` - - } - } -} - -export class NodeFunctionView { - - - /** Time when this NodeFunction was created */ - private readonly _createdAt?: Maybe; - /** Global ID of this NodeFunction */ - private readonly _id?: Maybe; - /** The ID of the next Node Function in the flow */ - private _nextNodeId?: Maybe; - /** The parameters of the Node Function */ - private readonly _parameters?: NodeParameterView[]; - /** The definition of the Node Function */ - private readonly _functionDefinition?: Maybe; - /** Time when this NodeFunction was last updated */ - private readonly _updatedAt?: Maybe; - - - constructor(nodeFunction: NodeFunction) { - this._createdAt = nodeFunction.createdAt - this._id = nodeFunction.id - this._nextNodeId = nodeFunction.nextNodeId - this._functionDefinition = nodeFunction.functionDefinition - this._updatedAt = nodeFunction.updatedAt - this._parameters = nodeFunction.parameters ? nodeFunction.parameters.nodes?.map(param => new NodeParameterView(param!!)) : undefined - } - - get createdAt(): Maybe | undefined { - return this._createdAt; - } - - get id(): Maybe | undefined { - return this._id; - } - - get nextNodeId(): Maybe | undefined { - return this._nextNodeId; - } - - get parameters(): NodeParameterView[] | undefined { - return this._parameters; - } - - get functionDefinition(): Maybe | undefined { - return this._functionDefinition; - } - - get updatedAt(): Maybe | undefined { - return this._updatedAt; - } - - set nextNodeId(value: Maybe) { - this._nextNodeId = value; - } - - deleteNextNode() { - this._nextNodeId = undefined; - } - - json(): NodeFunction | undefined { - return { - __typename: "NodeFunction", - createdAt: this._createdAt, - id: this._id, - nextNodeId: this._nextNodeId, - parameters: this._parameters ? { - nodes: this._parameters.map(param => param.json()!!) - } : undefined, - functionDefinition: this._functionDefinition, - updatedAt: this._updatedAt, - } - } - - jsonInput(): NodeFunctionInput { - return { - nextNodeId: this._nextNodeId, - id: this._id, - parameters: this._parameters ? this._parameters.map(param => param.jsonInput()) : undefined, - runtimeFunctionId: this._functionDefinition?.runtimeFunctionDefinition?.id - } - } -} - -export class NodeParameterView { - - /** Time when this NodeParameter was created */ - private readonly _createdAt?: Maybe; - /** Global ID of this NodeParameter */ - private readonly _id?: Maybe; - /** The definition of the parameter */ - private readonly _runtimeParameter?: Maybe; - /** Time when this NodeParameter was last updated */ - private readonly _updatedAt?: Maybe; - /** The value of the parameter */ - private _value?: LiteralValue | ReferenceValue | NodeFunctionView; - - private _validationResults: ValidationResult[] - - constructor(nodeParameter: NodeParameter) { - this._createdAt = nodeParameter.createdAt - this._id = nodeParameter.id - this._runtimeParameter = nodeParameter.runtimeParameter - this._updatedAt = nodeParameter.updatedAt - if (nodeParameter.value?.__typename === "NodeFunction") { - this._value = new NodeFunctionView(nodeParameter.value as NodeFunction); - } else { - this._value = nodeParameter.value as LiteralValue | ReferenceValue; - } - this._validationResults = [] - - } - - get createdAt(): Maybe | undefined { - return this._createdAt; - } - - get id(): Maybe | undefined { - return this._id; - } - - get runtimeParameter(): Maybe | undefined { - return this._runtimeParameter; - } - - get updatedAt(): Maybe | undefined { - return this._updatedAt; - } - - get value(): LiteralValue | ReferenceValue | NodeFunctionView | undefined { - return this._value; - } - - get validationResults(): ValidationResult[] { - return this._validationResults; - } - - set validationResults(value: ValidationResult[]) { - this._validationResults = value; - } - - set value(value: NodeParameterValue | undefined) { - if (value?.__typename === "NodeFunction") { - this._value = new NodeFunctionView(value as NodeFunction); - } else { - this._value = value as LiteralValue | ReferenceValue; - } - } - - json(): NodeParameter | undefined { - return { - __typename: "NodeParameter", - createdAt: this._createdAt, - id: this._id, - runtimeParameter: this._runtimeParameter, - updatedAt: this._updatedAt, - value: this._value instanceof NodeFunctionView ? this._value.json() : this._value, - } - } - - jsonInput(): NodeParameterInput { - return { - value: this._value instanceof NodeFunctionView ? { - functionValue: this._value.jsonInput() - } : this._value?.__typename === "ReferenceValue" ? { - referenceValue: this._value as ReferenceValue - } : { - literalValue: this._value as LiteralValue - }, - runtimeParameterDefinitionId: this.runtimeParameter?.id - } - } -} - -export class FlowSettingView { - - private readonly _createdAt?: Maybe; - /** The identifier of the flow setting */ - private readonly _flowSettingIdentifier?: Maybe; - /** Global ID of this FlowSetting */ - private readonly _id?: Maybe; - /** Time when this FlowSetting was last updated */ - private readonly _updatedAt?: Maybe; - /** The value of the flow setting */ - private _value?: Maybe; - - constructor(flowSetting: FlowSetting) { - this._createdAt = flowSetting.createdAt - this._flowSettingIdentifier = flowSetting.flowSettingIdentifier - this._id = flowSetting.id - this._value = flowSetting.value - this._updatedAt = flowSetting.updatedAt - } - - get createdAt(): Maybe | undefined { - return this._createdAt; - } - - get flowSettingIdentifier(): Maybe | undefined { - return this._flowSettingIdentifier; - } - - get id(): Maybe | undefined { - return this._id; - } - - get value(): Maybe | undefined { - return this._value; - } - - get updatedAt(): Maybe | undefined { - return this._updatedAt; - } - - set value(value: Maybe) { - this._value = value; - } - - json(): FlowSetting { - return { - __typename: "FlowSetting", - createdAt: this._createdAt, - flowSettingIdentifier: this._flowSettingIdentifier, - id: this._id, - value: this._value, - updatedAt: this._updatedAt - } - } - - jsonInput(): FlowSettingInput { - return { - value: this.value, - flowSettingIdentifier: this.flowSettingIdentifier - } - } -} \ No newline at end of file From 412bbf9d1c1ff6814d6e83346b12fe4e9090033f Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 16:37:31 +0100 Subject: [PATCH 046/197] feat: update DFlowDataTypeReactiveService to use Flow type instead of FlowView --- src/components/d-flow/data-type/DFlowDataType.service.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/d-flow/data-type/DFlowDataType.service.ts b/src/components/d-flow/data-type/DFlowDataType.service.ts index 9864d9668..3d14d61c6 100644 --- a/src/components/d-flow/data-type/DFlowDataType.service.ts +++ b/src/components/d-flow/data-type/DFlowDataType.service.ts @@ -5,7 +5,7 @@ import type { DataTypeIdentifier, DataTypeRule, DataTypeRulesContainsKeyConfig, - DataTypeRulesInputTypesConfig, + DataTypeRulesInputTypesConfig, Flow, GenericMapper, LiteralValue, Maybe, @@ -13,7 +13,6 @@ import type { Scalars } from "@code0-tech/sagittarius-graphql-types"; import {useValidateValue} from "./DFlowDataType.validation.value"; -import {FlowView} from "../DFlow.view"; export abstract class DFlowDataTypeReactiveService extends ReactiveArrayService { @@ -28,7 +27,7 @@ export abstract class DFlowDataTypeReactiveService extends ReactiveArrayService< }); } - getDataTypeFromValue (value: NodeParameterValue, flow?: FlowView): DataTypeView | undefined { + getDataTypeFromValue (value: NodeParameterValue, flow?: Flow): DataTypeView | undefined { if (!value) return undefined @@ -51,7 +50,7 @@ export abstract class DFlowDataTypeReactiveService extends ReactiveArrayService< } - getTypeFromValue (value: NodeParameterValue, flow?: FlowView): Maybe | undefined { + getTypeFromValue (value: NodeParameterValue, flow?: Flow): Maybe | undefined { if (!value) return undefined From 1215ab6374319dea0d00d787178a7214ef40c7f9 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 16:37:35 +0100 Subject: [PATCH 047/197] feat: update validation to use Flow type instead of FlowView --- .../d-flow/data-type/DFlowDataType.validation.value.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/d-flow/data-type/DFlowDataType.validation.value.ts b/src/components/d-flow/data-type/DFlowDataType.validation.value.ts index c871d1c57..51c242d80 100644 --- a/src/components/d-flow/data-type/DFlowDataType.validation.value.ts +++ b/src/components/d-flow/data-type/DFlowDataType.validation.value.ts @@ -1,14 +1,13 @@ -import type {GenericMapper, NodeParameterValue} from "@code0-tech/sagittarius-graphql-types"; +import type {Flow, GenericMapper, NodeParameterValue} from "@code0-tech/sagittarius-graphql-types"; import {RuleMap} from "./rules/DFlowDataTypeRules"; import {DataTypeView} from "./DFlowDataType.view"; import {useService} from "../../../utils/contextStore"; import {DFlowDataTypeReactiveService} from "./DFlowDataType.service"; -import {FlowView} from "../DFlow.view"; export const useValidateValue = ( value: NodeParameterValue, dataType: DataTypeView, - flow?: FlowView, + flow?: Flow, generics?: GenericMapper[], ): boolean => { From 9102d19368d835dd888bef7df59ceac4ba8c30f9 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 16:37:40 +0100 Subject: [PATCH 048/197] feat: update DFlowDataTypeContainsKeyRule to use Flow type instead of FlowView --- .../d-flow/data-type/rules/DFlowDataTypeContainsKeyRule.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/d-flow/data-type/rules/DFlowDataTypeContainsKeyRule.ts b/src/components/d-flow/data-type/rules/DFlowDataTypeContainsKeyRule.ts index 45b7e2b74..f27b374fd 100644 --- a/src/components/d-flow/data-type/rules/DFlowDataTypeContainsKeyRule.ts +++ b/src/components/d-flow/data-type/rules/DFlowDataTypeContainsKeyRule.ts @@ -1,18 +1,17 @@ import {DFlowDataTypeRule, genericMapping, staticImplements} from "./DFlowDataTypeRule"; import {DFlowDataTypeReactiveService} from "../DFlowDataType.service"; import type { - DataTypeRulesContainsKeyConfig, + DataTypeRulesContainsKeyConfig, Flow, GenericCombinationStrategyType, GenericMapper, LiteralValue, NodeParameterValue } from "@code0-tech/sagittarius-graphql-types"; import {useValidateValue} from "../DFlowDataType.validation.value"; -import {FlowView} from "../../DFlow.view"; @staticImplements() export class DFlowDataTypeContainsKeyRule { - public static validate(value: NodeParameterValue, config: DataTypeRulesContainsKeyConfig, generics?: Map, service?: DFlowDataTypeReactiveService, flow?: FlowView): boolean { + public static validate(value: NodeParameterValue, config: DataTypeRulesContainsKeyConfig, generics?: Map, service?: DFlowDataTypeReactiveService, flow?: Flow): boolean { const genericMapper = generics?.get(config?.dataTypeIdentifier?.genericKey!!) const genericTypes = generics?.get(config?.dataTypeIdentifier?.genericKey!!)?.sourceDataTypeIdentifiers From 2963619cb75a9d6577837eb56358bec59c340c76 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 16:37:46 +0100 Subject: [PATCH 049/197] feat: update DFlowDataTypeContainsTypeRule to use Flow type instead of FlowView --- .../d-flow/data-type/rules/DFlowDataTypeContainsTypeRule.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/d-flow/data-type/rules/DFlowDataTypeContainsTypeRule.ts b/src/components/d-flow/data-type/rules/DFlowDataTypeContainsTypeRule.ts index 15fbaf8f9..64d181389 100644 --- a/src/components/d-flow/data-type/rules/DFlowDataTypeContainsTypeRule.ts +++ b/src/components/d-flow/data-type/rules/DFlowDataTypeContainsTypeRule.ts @@ -1,7 +1,7 @@ import {DFlowDataTypeRule, genericMapping, staticImplements} from "./DFlowDataTypeRule"; import {DFlowDataTypeReactiveService} from "../DFlowDataType.service"; import type { - DataTypeRulesContainsKeyConfig, + DataTypeRulesContainsKeyConfig, Flow, GenericCombinationStrategyType, GenericMapper, GenericType, @@ -9,11 +9,10 @@ import type { NodeParameterValue } from "@code0-tech/sagittarius-graphql-types"; import {useValidateValue} from "../DFlowDataType.validation.value"; -import {FlowView} from "../../DFlow.view"; @staticImplements() export class DFlowDataTypeContainsTypeRule { - public static validate(value: NodeParameterValue, config: DataTypeRulesContainsKeyConfig, generics?: Map, service?: DFlowDataTypeReactiveService, flow?: FlowView): boolean { + public static validate(value: NodeParameterValue, config: DataTypeRulesContainsKeyConfig, generics?: Map, service?: DFlowDataTypeReactiveService, flow?: Flow): boolean { const genericMapper = generics?.get(config?.dataTypeIdentifier?.genericKey!!) const genericTypes = generics?.get(config?.dataTypeIdentifier?.genericKey!!)?.sourceDataTypeIdentifiers From 88d92bbe75c2bc2848877f075d8ccd6ff69d5aed Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 16:38:02 +0100 Subject: [PATCH 050/197] feat: update DFlowDataTypeParentRule to use Flow type instead of FlowView --- .../d-flow/data-type/rules/DFlowDataTypeParentRule.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/d-flow/data-type/rules/DFlowDataTypeParentRule.ts b/src/components/d-flow/data-type/rules/DFlowDataTypeParentRule.ts index 295a331e5..34b53c3ff 100644 --- a/src/components/d-flow/data-type/rules/DFlowDataTypeParentRule.ts +++ b/src/components/d-flow/data-type/rules/DFlowDataTypeParentRule.ts @@ -1,9 +1,8 @@ import {DFlowDataTypeRule, staticImplements} from "./DFlowDataTypeRule"; import {DFlowDataTypeReactiveService} from "../DFlowDataType.service"; import {replaceGenericKeysInType} from "../../../../utils/generics"; -import type {DataTypeIdentifier, GenericMapper, NodeParameterValue} from "@code0-tech/sagittarius-graphql-types"; +import type {DataTypeIdentifier, Flow, GenericMapper, NodeParameterValue} from "@code0-tech/sagittarius-graphql-types"; import {useValidateValue} from "../DFlowDataType.validation.value"; -import {FlowView} from "../../DFlow.view"; export interface DFlowDataTypeParentRuleConfig { type: DataTypeIdentifier @@ -11,7 +10,7 @@ export interface DFlowDataTypeParentRuleConfig { @staticImplements() export class DFlowDataTypeParentRule { - public static validate(value: NodeParameterValue, config: DFlowDataTypeParentRuleConfig, generics?: Map, service?: DFlowDataTypeReactiveService, flow?: FlowView): boolean { + public static validate(value: NodeParameterValue, config: DFlowDataTypeParentRuleConfig, generics?: Map, service?: DFlowDataTypeReactiveService, flow?: Flow): boolean { const replacedType = generics ? replaceGenericKeysInType(config.type, generics) : config.type From 235aa089ff675cae0e9bc8cbb33e5fbd1548b93f Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 16:38:06 +0100 Subject: [PATCH 051/197] feat: update DFlowDataTypeReturnTypeRule to use Flow type instead of FlowView --- .../data-type/rules/DFlowDataTypeReturnTypeRule.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/d-flow/data-type/rules/DFlowDataTypeReturnTypeRule.ts b/src/components/d-flow/data-type/rules/DFlowDataTypeReturnTypeRule.ts index 16c415f64..16ba2d4c7 100644 --- a/src/components/d-flow/data-type/rules/DFlowDataTypeReturnTypeRule.ts +++ b/src/components/d-flow/data-type/rules/DFlowDataTypeReturnTypeRule.ts @@ -1,7 +1,7 @@ import {DFlowDataTypeRule, genericMapping, staticImplements} from "./DFlowDataTypeRule"; import {DFlowDataTypeReactiveService} from "../DFlowDataType.service"; import type { - DataTypeRulesReturnTypeConfig, + DataTypeRulesReturnTypeConfig, Flow, GenericCombinationStrategyType, GenericMapper, GenericType, @@ -9,7 +9,6 @@ import type { NodeParameterValue, ReferenceValue } from "@code0-tech/sagittarius-graphql-types"; -import {FlowView} from "../../DFlow.view"; import {useValidateDataType} from "../DFlowDataType.validation.type"; import {useValidateValue} from "../DFlowDataType.validation.value"; @@ -21,7 +20,7 @@ export class DFlowDataTypeReturnTypeRule { config: DataTypeRulesReturnTypeConfig, generics?: Map, service?: DFlowDataTypeReactiveService, - flow?: FlowView + flow?: Flow ): boolean { const genericMapper = generics?.get(config?.dataTypeIdentifier?.genericKey!!) @@ -33,7 +32,6 @@ export class DFlowDataTypeReturnTypeRule { const foundReturnFunction = findReturnNode(value, flow!!) if (!foundReturnFunction) return false - //TODO: only if its really a generic key if (config?.dataTypeIdentifier?.genericKey && !genericMapper && !service?.getDataType(config.dataTypeIdentifier)) return true if (!(service?.getDataType(config.dataTypeIdentifier!!) || genericMapper)) return false @@ -99,12 +97,14 @@ export class DFlowDataTypeReturnTypeRule { } } -const findReturnNode = (n: NodeFunction, flow: FlowView): NodeFunction | undefined => { +const findReturnNode = (n: NodeFunction, flow: Flow): NodeFunction | undefined => { if (n.functionDefinition?.runtimeFunctionDefinition?.identifier === 'RETURN') return n - if (flow.getNodeById(n.nextNodeId!!)) { - const found = findReturnNode(flow.getNodeById(n.nextNodeId!!)!!.json()!!, flow) + const node = flow.nodes?.nodes?.find(node => node?.id === n.nextNodeId) as NodeFunction | undefined + + if (node) { + const found = findReturnNode(node!!, flow) if (found) return found } From 0957e4fba6edd10aadd725f40ea34971aa7303ad Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 16:38:11 +0100 Subject: [PATCH 052/197] feat: update DFlowDataTypeRule to use Flow type instead of FlowView --- src/components/d-flow/data-type/rules/DFlowDataTypeRule.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/d-flow/data-type/rules/DFlowDataTypeRule.ts b/src/components/d-flow/data-type/rules/DFlowDataTypeRule.ts index 626fc1fc2..8959bebf3 100644 --- a/src/components/d-flow/data-type/rules/DFlowDataTypeRule.ts +++ b/src/components/d-flow/data-type/rules/DFlowDataTypeRule.ts @@ -1,9 +1,8 @@ import {DFlowDataTypeReactiveService} from "../DFlowDataType.service"; -import type {GenericMapper, NodeParameterValue} from "@code0-tech/sagittarius-graphql-types"; -import {FlowView} from "../../DFlow.view"; +import type {Flow, GenericMapper, NodeParameterValue} from "@code0-tech/sagittarius-graphql-types"; export interface DFlowDataTypeRule { - validate(value: NodeParameterValue, config: object, generics?: Map, service?: DFlowDataTypeReactiveService, flow?: FlowView): boolean + validate(value: NodeParameterValue, config: object, generics?: Map, service?: DFlowDataTypeReactiveService, flow?: Flow): boolean } export const staticImplements = () => { From 508b0f483255d92fae7a8e313168549b244b4168 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 16:38:50 +0100 Subject: [PATCH 053/197] feat: simplify data extraction in DFlowExport by directly using flow --- src/components/d-flow/export/DFlowExport.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/d-flow/export/DFlowExport.tsx b/src/components/d-flow/export/DFlowExport.tsx index 3253216be..3b57ced34 100644 --- a/src/components/d-flow/export/DFlowExport.tsx +++ b/src/components/d-flow/export/DFlowExport.tsx @@ -23,11 +23,10 @@ export const DFlowExport: React.FC = (props) => { if (!flow) return // Hole JSON-Daten - const data = flow.jsonInput?.() + const data = flow if (!data) return - const json = - typeof data === "string" ? data : JSON.stringify(data, null, 2) + const json = JSON.stringify(data, null, 2) // Blob + Download-Link const blob = new Blob([json], { type: "application/json" }) From 564874c4e874c413ac1e98453e45834ef6ec701f Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 16:38:55 +0100 Subject: [PATCH 054/197] feat: update DFlowFolder to use Flow type instead of FlowView --- src/components/d-flow/folder/DFlowFolder.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/components/d-flow/folder/DFlowFolder.tsx b/src/components/d-flow/folder/DFlowFolder.tsx index cc5da34a6..d75f56e91 100644 --- a/src/components/d-flow/folder/DFlowFolder.tsx +++ b/src/components/d-flow/folder/DFlowFolder.tsx @@ -4,10 +4,9 @@ import "./DFlowFolder.style.scss" import React from "react" import {Code0Component, mergeCode0Props, useService, useStore} from "../../../utils" import {IconChevronDown, IconChevronRight, IconDots, IconFolder, IconFolderOpen} from "@tabler/icons-react" -import type {FlowType, Scalars} from "@code0-tech/sagittarius-graphql-types" +import type {Flow, FlowType, Scalars} from "@code0-tech/sagittarius-graphql-types" import {DFlowReactiveService} from "../DFlow.service" import {ScrollArea, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport} from "../../scroll-area/ScrollArea" -import {FlowView} from "../DFlow.view" import {Flex} from "../../flex/Flex" import {Text} from "../../text/Text" import {Button} from "../../button/Button" @@ -16,17 +15,17 @@ import {DFlowFolderContextMenu} from "./DFlowFolderContextMenu"; export interface DFlowFolderProps { activeFlowId: Scalars["FlowID"]["output"] - onRename?: (flow: FlowView, newName: string) => void - onDelete?: (flow: FlowView) => void + onRename?: (flow: Flow, newName: string) => void + onDelete?: (flow: Flow) => void onCreate?: (name: string, type: FlowType['id']) => void - onMove?: (flow: FlowView, newPath: string) => void + onMove?: (flow: Flow, newPath: string) => void } export interface DFlowFolderGroupProps extends DFlowFolderProps, Omit, "controls"> { name: string children: React.ReactElement | React.ReactElement[] | React.ReactElement | React.ReactElement[] defaultOpen?: boolean - flows: FlowView[] + flows: Flow[] } export interface DFlowFolderItemProps extends DFlowFolderProps, Code0Component { @@ -34,7 +33,7 @@ export interface DFlowFolderItemProps extends DFlowFolderProps, Code0Component = (props) => { @@ -48,14 +47,14 @@ export const DFlowFolder: React.FC = (props) => { name: string path: string children: Record - flow?: FlowView + flow?: Flow } const normalizePath = (p: string) => p.replace(/^\/+|\/+$/g, "").split("/").filter(Boolean) - const flows = React.useMemo(() => { - const raw = (flowService.values?.() ?? []) as FlowView[] + const flows = React.useMemo(() => { + const raw = (flowService.values?.() ?? []) as Flow[] return raw.filter(f => !!f?.name) }, [flowStore]) From 06bd539bc3725c9a238e021deb523ffc3dacfcb6 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 16:40:32 +0100 Subject: [PATCH 055/197] feat: update DFlowFolderContextMenu to use Flow type instead of FlowView --- .../d-flow/folder/DFlowFolderContextMenu.tsx | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/components/d-flow/folder/DFlowFolderContextMenu.tsx b/src/components/d-flow/folder/DFlowFolderContextMenu.tsx index 0c11b8095..df6ae7046 100644 --- a/src/components/d-flow/folder/DFlowFolderContextMenu.tsx +++ b/src/components/d-flow/folder/DFlowFolderContextMenu.tsx @@ -13,25 +13,24 @@ import { } from "../../context-menu/ContextMenu"; import {Flex} from "../../flex/Flex"; import {Text} from "../../text/Text"; -import {IconChevronRight, IconClipboard, IconCopy, IconEdit, IconTrash} from "@tabler/icons-react"; +import {IconChevronRight, IconEdit, IconTrash} from "@tabler/icons-react"; import {Dialog, DialogClose, DialogContent, DialogPortal} from "../../dialog/Dialog"; import {Badge} from "../../badge/Badge"; import {Button} from "../../button/Button"; -import {FlowView} from "../DFlow.view"; -import {TextInput} from "../../form"; import {useService, useStore} from "../../../utils"; import {DFlowTypeReactiveService} from "../type"; import {DFlowFolderItemPathInput} from "./DFlowFolderItemPathInput"; +import {Flow} from "@code0-tech/sagittarius-graphql-types"; export interface DFlowFolderContextMenuGroupData { name: string - flows: FlowView[] + flows: Flow[] type: "group" } export interface DFlowFolderContextMenuItemData { name: string - flow: FlowView + flow: Flow type: "item" } @@ -54,13 +53,15 @@ export const DFlowFolderContextMenu: React.FC = (pr return <> setDeleteDialogOpen(open)}> - + {props.contextData.type == "item" ? "Are you sure you want to remove flow" : "Are you sure you want to remove folder"} {" "} {props.contextData.name} {" "} - {props.contextData.type == "group" ? ", all flows and sub-folders inside " : ""}from the this project? + {props.contextData.type == "group" ? ", all flows and sub-folders inside " : ""}from the this + project? @@ -84,11 +85,13 @@ export const DFlowFolderContextMenu: React.FC = (pr setRenameDialogOpen(open)}> - +
- +
From b0a109c0827a6d537765960f993036472d258a23 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 16:40:53 +0100 Subject: [PATCH 056/197] feat: update DFlowFunctionDefaultCard to use NodeFunction type instead of NodeFunctionView --- .../function/DFlowFunctionDefaultCard.tsx | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx index 1ce22a0b1..d504228b9 100644 --- a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx @@ -1,6 +1,5 @@ import {Code0Component} from "../../../utils/types"; import {Handle, Node, NodeProps, Position, useReactFlow, useStore, useStoreApi} from "@xyflow/react"; -import {NodeFunctionView, NodeParameterView} from "../DFlow.view"; import React, {memo} from "react"; import {Card} from "../../card/Card"; import "./DFlowFunctionDefaultCard.style.scss"; @@ -8,11 +7,12 @@ import CardSection from "../../card/CardSection"; import {Flex} from "../../flex/Flex"; import { IconAlertTriangle, - IconArrowRightCircle, IconChevronDown, IconChevronRight, IconCopy, + IconArrowRightCircle, + IconChevronDown, + IconCopy, IconDots, IconExclamationCircle, IconFileLambdaFilled, - IconLayoutNavbarCollapseFilled, IconMessageExclamation, IconTrash } from "@tabler/icons-react"; @@ -30,10 +30,10 @@ import {DFlowSuggestionMenu} from "../suggestion/DFlowSuggestionMenu"; import {useSuggestions} from "../suggestion/DFlowSuggestion.hook"; import {FileTabsService} from "../../file-tabs/FileTabs.service"; import {DFlowTabDefault} from "../tab/DFlowTabDefault"; -import type {DataTypeVariant, Maybe, Scalars} from "@code0-tech/sagittarius-graphql-types"; +import type {Maybe, NodeFunction, NodeParameter, Scalars} from "@code0-tech/sagittarius-graphql-types"; export interface DFlowFunctionDefaultCardDataProps extends Omit, "scope"> { - instance: NodeFunctionView + node: NodeFunction flowId: Scalars["FlowID"]["output"] isParameter: boolean linkingId?: string @@ -55,16 +55,16 @@ export const DFlowFunctionDefaultCard: React.FC = const flowService = useService(DFlowReactiveService) const functionService = useService(DFlowFunctionReactiveService) const dataTypeService = useService(DFlowDataTypeReactiveService) - const definition = functionService.getById(data.instance.functionDefinition?.id!!) + const definition = functionService.getById(data.node.functionDefinition?.id!!) //TODO: some problems with react memorization here, need to investigate and also with hook calling - const validation = useFunctionValidation(definition!!, data.instance.parameters!!.map(p => p.value!! instanceof NodeFunctionView ? p.value.json()!! : p.value!!), dataTypeService!!, props.data.flowId) + const validation = useFunctionValidation(definition!!, data.node.parameters!.nodes!.map(p => p?.value!!), dataTypeService!!, props.data.flowId) const edges = useStore(s => s.edges); const width = props.width ?? 0 const height = props.height ?? 0 - data.instance.parameters?.forEach(parameter => { + data.node.parameters?.nodes?.forEach(parameter => { const parameterDefinition = definition?.parameterDefinitions!!.find(p => p.id == parameter?.id) - parameter.validationResults = validation ? validation.filter(v => v.parameterId === parameterDefinition?.id) : [] + //parameter.validationResults = validation ? validation.filter(v => v.parameterId === parameterDefinition?.id) : [] }) // Helper, ob zu diesem Parameter eine Edge existiert: @@ -109,7 +109,7 @@ export const DFlowFunctionDefaultCard: React.FC = closeable: true, children: {definition?.names?.nodes!![0]?.content}, content: + nodeLevel={data.index} node={data.node}/> }) }} style={{position: "relative"}}> @@ -138,8 +138,7 @@ export const DFlowFunctionDefaultCard: React.FC = Actions { - data.instance.deleteNextNode() - flowService.update() + flowService.deleteNodeById(data.flowId, data.node.id) }}> Delete node Copy node @@ -194,21 +193,21 @@ export const DFlowFunctionDefaultCard: React.FC =
) : null} - {data.instance.parameters?.some(param => { - const parameter = definition?.parameterDefinitions!!.find(p => p.id == param.id) + {data.node.parameters?.nodes?.some(param => { + const parameter = definition?.parameterDefinitions!!.find(p => p.id == param?.id) const isNodeDataType = dataTypeService.getDataType(parameter?.dataTypeIdentifier!!)?.variant === "NODE"; - return (param.value instanceof NodeFunctionView && !isNodeDataType) || (!param.value) + return (param?.value?.__typename === "NodeFunction" && !isNodeDataType) || (!param?.value) }) ? ( {/* Dynamische Parameter-Eingänge (rechts), nur wenn wirklich verbunden */} - {data.instance.parameters?.map((param: NodeParameterView, index: number) => { + {data.node?.parameters?.nodes?.map((param: NodeParameter, index: number) => { const parameter = definition?.parameterDefinitions!!.find(p => p.id == param.id) const isNodeDataType = dataTypeService.getDataType(parameter?.dataTypeIdentifier!!)?.variant === "NODE"; const result = useSuggestions(parameter?.dataTypeIdentifier ?? undefined, [], props.data.flowId, data.depth, data.scope, data.index) - return (param.value instanceof NodeFunctionView && !isNodeDataType) || (!param.value) ? + return (param.value?.__typename === "NodeFunction" && !isNodeDataType) || (!param.value) ? {parameter?.names?.nodes!![0]?.content ?? param.id} From 5f58ccc64522ce88257e0b08de39341911ed52b3 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 16:40:59 +0100 Subject: [PATCH 057/197] feat: update DFlowFunctionSuggestionCard to use NodeFunction type instead of NodeFunctionView --- .../function/DFlowFunctionSuggestionCard.tsx | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionSuggestionCard.tsx b/src/components/d-flow/function/DFlowFunctionSuggestionCard.tsx index 07904adf7..ede9972d0 100644 --- a/src/components/d-flow/function/DFlowFunctionSuggestionCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionSuggestionCard.tsx @@ -4,7 +4,6 @@ import React, {memo} from "react"; import {Button} from "../../button/Button"; import {IconPlus} from "@tabler/icons-react"; import {useSuggestions} from "../suggestion/DFlowSuggestion.hook"; -import {NodeFunctionView} from "../DFlow.view"; import {useService} from "../../../utils/contextStore"; import {DFlowReactiveService} from "../DFlow.service"; import {DFlowSuggestionMenu} from "../suggestion/DFlowSuggestionMenu"; @@ -12,7 +11,7 @@ import type {Flow, NodeFunction} from "@code0-tech/sagittarius-graphql-types"; export interface DFlowFunctionSuggestionCardDataProps extends Code0Component { flowId: Flow['id'] - parentFunction?: NodeFunctionView + parentFunction?: NodeFunction } // @ts-ignore @@ -25,14 +24,11 @@ export const DFlowFunctionSuggestionCard: React.FC { - const nodeFunction = new NodeFunctionView(suggestion.value as NodeFunction) - if (props.data.parentFunction) { - props.data.parentFunction.nextNodeId = nodeFunction.id!! - } else if (flow) { - flow.startingNodeId = nodeFunction.id!! + if (props.data.parentFunction && suggestion.value.__typename === "NodeFunction") { + flowService.addNextNodeById(flow?.id, props.data.parentFunction?.id, suggestion.value) + } else if (flow && suggestion.value.__typename === "NodeFunction") { + flowService.setStartingNodeById(flow?.id, suggestion.value) } - flow?.addNode(nodeFunction) - flowService.update() }} suggestions={result} triggerContent={ - - - - + ) } \ No newline at end of file From e1e8763e9a194047a6bc1e259f9a1c4d8ed37819 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 18:16:52 +0100 Subject: [PATCH 072/197] feat: add DFlowPanelControl component for managing flow node actions --- .../d-flow/panel/DFlowPanelControl.tsx | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/components/d-flow/panel/DFlowPanelControl.tsx diff --git a/src/components/d-flow/panel/DFlowPanelControl.tsx b/src/components/d-flow/panel/DFlowPanelControl.tsx new file mode 100644 index 000000000..d53e38079 --- /dev/null +++ b/src/components/d-flow/panel/DFlowPanelControl.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import {ButtonGroup} from "../../button-group/ButtonGroup"; +import {Button} from "../../button/Button"; +import {IconCopy, IconTrash} from "@tabler/icons-react"; +import {Panel} from "@xyflow/react"; +import {useService, useStore} from "../../../utils"; +import {FileTabsService} from "../../file-tabs/FileTabs.service"; +import {DFlowReactiveService} from "../DFlow.service"; +import {Flow, NodeFunction} from "@code0-tech/sagittarius-graphql-types"; + +export const DFlowPanelControl: React.FC = () => { + + const fileTabsService = useService(FileTabsService) + const fileTabsStore = useStore(FileTabsService) + const flowService = useService(DFlowReactiveService) + const activeTab = React.useMemo(() => fileTabsService.getActiveTab(), [fileTabsStore]) + + const deleteActiveNode = React.useCallback(() => { + if (!activeTab) return + // @ts-ignore + flowService.deleteNodeById((activeTab.content.props.flowId as Flow['id']), (activeTab.content.props.node.id as NodeFunction['id'])) + }, [activeTab, flowService]) + + //TODO: Add execute flow button functionality + //TODO: disable button if active tab is the trigger node + return + + + + + + +} \ No newline at end of file From 0a26cd729e8d0dde519546a059fd095cb8496382 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 18:16:59 +0100 Subject: [PATCH 073/197] feat: rename DFlowControl to DFlowPanelSize for improved clarity --- .../{control/DFlowControl.tsx => panel/DFlowPanelSize.tsx} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/components/d-flow/{control/DFlowControl.tsx => panel/DFlowPanelSize.tsx} (97%) diff --git a/src/components/d-flow/control/DFlowControl.tsx b/src/components/d-flow/panel/DFlowPanelSize.tsx similarity index 97% rename from src/components/d-flow/control/DFlowControl.tsx rename to src/components/d-flow/panel/DFlowPanelSize.tsx index 8c6063869..293944a7b 100644 --- a/src/components/d-flow/control/DFlowControl.tsx +++ b/src/components/d-flow/panel/DFlowPanelSize.tsx @@ -7,7 +7,7 @@ import {Badge} from "../../badge/Badge"; import {Flex} from "../../flex/Flex"; import {Text} from "../../text/Text"; -export const DFlowControl: React.FC = () => { +export const DFlowPanelSize: React.FC = () => { const viewport = useViewport(); const reactFlow = useReactFlow(); From 6f3ac22efc04cf8da0c56517f955c9aa981b6751 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 18:17:10 +0100 Subject: [PATCH 074/197] feat: rename control module to panel and update exports for consistency --- src/components/d-flow/control/index.ts | 1 - src/components/d-flow/index.ts | 2 +- src/components/d-flow/panel/index.ts | 1 + 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 src/components/d-flow/control/index.ts create mode 100644 src/components/d-flow/panel/index.ts diff --git a/src/components/d-flow/control/index.ts b/src/components/d-flow/control/index.ts deleted file mode 100644 index 4b6220afc..000000000 --- a/src/components/d-flow/control/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./DFlowControl" \ No newline at end of file diff --git a/src/components/d-flow/index.ts b/src/components/d-flow/index.ts index 55740b345..fedd2e3ca 100644 --- a/src/components/d-flow/index.ts +++ b/src/components/d-flow/index.ts @@ -3,7 +3,7 @@ export * from "./DFlow.edges.hook" export * from "./DFlow.nodes.hook" export * from "./DFlow" -export * from "./control/index" +export * from "./panel/index" export * from "./data-type/index" export * from "./folder/index" export * from "./function/index" diff --git a/src/components/d-flow/panel/index.ts b/src/components/d-flow/panel/index.ts new file mode 100644 index 000000000..e29632ff9 --- /dev/null +++ b/src/components/d-flow/panel/index.ts @@ -0,0 +1 @@ +export * from "./DFlowPanelSize" \ No newline at end of file From 9bde188ba5b3f4b1abc33c9ae8a8169b5c22df60 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 18:21:06 +0100 Subject: [PATCH 075/197] feat: remove unused IconCopy import from DFlowPanelControl and format button code --- src/components/d-flow/panel/DFlowPanelControl.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/d-flow/panel/DFlowPanelControl.tsx b/src/components/d-flow/panel/DFlowPanelControl.tsx index d53e38079..e51ecf8c4 100644 --- a/src/components/d-flow/panel/DFlowPanelControl.tsx +++ b/src/components/d-flow/panel/DFlowPanelControl.tsx @@ -1,7 +1,7 @@ import React from "react"; import {ButtonGroup} from "../../button-group/ButtonGroup"; import {Button} from "../../button/Button"; -import {IconCopy, IconTrash} from "@tabler/icons-react"; +import {IconTrash} from "@tabler/icons-react"; import {Panel} from "@xyflow/react"; import {useService, useStore} from "../../../utils"; import {FileTabsService} from "../../file-tabs/FileTabs.service"; @@ -28,7 +28,8 @@ export const DFlowPanelControl: React.FC = () => { - From 30c6163eda0bec760aec7c68fbed784e1ed26ee0 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 18:25:33 +0100 Subject: [PATCH 076/197] feat: refactor deleteById and renameById methods for improved clarity and functionality --- src/components/d-flow/DFlow.service.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/components/d-flow/DFlow.service.ts b/src/components/d-flow/DFlow.service.ts index e0ece5067..8a2d002e7 100644 --- a/src/components/d-flow/DFlow.service.ts +++ b/src/components/d-flow/DFlow.service.ts @@ -14,12 +14,17 @@ export abstract class DFlowReactiveService extends ReactiveArrayService { return this.values().find(value => value.id === id); } - deleteById(id: Flow['id']): void { - + deleteById(flowId: Flow['id']): void { + const index = this.values().findIndex(f => f.id === flowId) + this.delete(index) } - renameById(id: Flow['id'], newName: string): void { - + renameById(flowId: Flow['id'], newName: string): void { + const flow = this.getById(flowId) + const index = this.values().findIndex(f => f.id === flowId) + if (!flow) return + flow.name = newName + this.set(index, flow) } getNodeById(flowId: Flow['id'], nodeId: NodeFunction['id']): NodeFunction | undefined { From c84537722001696ed79e7ddc5ba30747c1b8b547 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 18:37:12 +0100 Subject: [PATCH 077/197] feat: update methods to async for improved handling of asynchronous operations --- src/components/d-flow/DFlow.service.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/d-flow/DFlow.service.ts b/src/components/d-flow/DFlow.service.ts index 8a2d002e7..cbe129ed6 100644 --- a/src/components/d-flow/DFlow.service.ts +++ b/src/components/d-flow/DFlow.service.ts @@ -14,12 +14,12 @@ export abstract class DFlowReactiveService extends ReactiveArrayService { return this.values().find(value => value.id === id); } - deleteById(flowId: Flow['id']): void { + async deleteById(flowId: Flow['id']): Promise { const index = this.values().findIndex(f => f.id === flowId) this.delete(index) } - renameById(flowId: Flow['id'], newName: string): void { + async renameById(flowId: Flow['id'], newName: string): Promise { const flow = this.getById(flowId) const index = this.values().findIndex(f => f.id === flowId) if (!flow) return @@ -31,7 +31,7 @@ export abstract class DFlowReactiveService extends ReactiveArrayService { return this.getById(flowId)?.nodes?.nodes?.find(node => node?.id === nodeId)!! } - deleteNodeById(flowId: Flow['id'], nodeId: NodeFunction['id']): void { + async deleteNodeById(flowId: Flow['id'], nodeId: NodeFunction['id']): Promise { const flow = this.getById(flowId) const node = this.getNodeById(flowId, nodeId) const previousNodes = flow?.nodes?.nodes?.find(n => n?.nextNodeId === nodeId) @@ -49,7 +49,7 @@ export abstract class DFlowReactiveService extends ReactiveArrayService { this.set(index, flow) } - addNextNodeById(flowId: Flow['id'], nodeId: NodeFunction['id'], nextNode: NodeFunction): void { + async addNextNodeById(flowId: Flow['id'], nodeId: NodeFunction['id'], nextNode: NodeFunction): Promise { const flow = this.getById(flowId) const index = this.values().findIndex(f => f.id === flowId) const node = this.getNodeById(flowId, nodeId) @@ -59,7 +59,7 @@ export abstract class DFlowReactiveService extends ReactiveArrayService { this.set(index, flow) } - setStartingNodeById(flowId: Flow['id'], startingNode: NodeFunction): void { + async setStartingNodeById(flowId: Flow['id'], startingNode: NodeFunction): Promise { const flow = this.getById(flowId) if (!flow) return const index = this.values().findIndex(f => f.id === flowId) @@ -68,7 +68,7 @@ export abstract class DFlowReactiveService extends ReactiveArrayService { this.set(index, flow) } - setSettingValue(flowId: Flow['id'], settingId: FlowSetting['id'], value: FlowSetting['value']): void { + async setSettingValue(flowId: Flow['id'], settingId: FlowSetting['id'], value: FlowSetting['value']): Promise { const flow = this.getById(flowId) const index = this.values().findIndex(f => f.id === flowId) if (!flow) return @@ -78,7 +78,7 @@ export abstract class DFlowReactiveService extends ReactiveArrayService { this.set(index, flow) } - setParameterValue(flowId: Flow['id'], nodeId: NodeFunction['id'], parameterId: NodeParameter['id'], value: NodeParameter['value']): void { + async setParameterValue(flowId: Flow['id'], nodeId: NodeFunction['id'], parameterId: NodeParameter['id'], value: NodeParameter['value']): Promise { const flow = this.getById(flowId) const index = this.values().findIndex(f => f.id === flowId) if (!flow) return From fa616f533411768814df7fa4435f4cf1a18938fb Mon Sep 17 00:00:00 2001 From: nicosammito Date: Thu, 11 Dec 2025 23:09:32 +0100 Subject: [PATCH 078/197] feat: add functionality to push NodeFunction to flow nodes on parameter update --- src/components/d-flow/DFlow.service.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/d-flow/DFlow.service.ts b/src/components/d-flow/DFlow.service.ts index cbe129ed6..4cd7d32d5 100644 --- a/src/components/d-flow/DFlow.service.ts +++ b/src/components/d-flow/DFlow.service.ts @@ -87,6 +87,9 @@ export abstract class DFlowReactiveService extends ReactiveArrayService { const parameter = node.parameters?.nodes?.find(p => p?.id === parameterId) if (!parameter) return parameter.value = value + if (value?.__typename === "NodeFunction") { + flow.nodes?.nodes?.push(value) + } this.set(index, flow) } From c4a11a3157f6de702bc3e2b2d6de1ad7feb446e9 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Fri, 12 Dec 2025 13:05:49 +0100 Subject: [PATCH 079/197] feat: simplify DFlowFunctionDefaultCard by removing unused imports and optimizing rendering logic --- .../function/DFlowFunctionDefaultCard.tsx | 230 ++++++------------ 1 file changed, 79 insertions(+), 151 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx index d504228b9..ae4142dd8 100644 --- a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx @@ -1,36 +1,21 @@ import {Code0Component} from "../../../utils/types"; -import {Handle, Node, NodeProps, Position, useReactFlow, useStore, useStoreApi} from "@xyflow/react"; +import {Handle, Node, NodeProps, Position, useReactFlow, useStore} from "@xyflow/react"; import React, {memo} from "react"; import {Card} from "../../card/Card"; import "./DFlowFunctionDefaultCard.style.scss"; -import CardSection from "../../card/CardSection"; import {Flex} from "../../flex/Flex"; -import { - IconAlertTriangle, - IconArrowRightCircle, - IconChevronDown, - IconCopy, - IconDots, - IconExclamationCircle, - IconFileLambdaFilled, - IconMessageExclamation, - IconTrash -} from "@tabler/icons-react"; +import {IconFileLambdaFilled} from "@tabler/icons-react"; import {Text} from "../../text/Text"; -import {Button} from "../../button/Button"; -import {Menu, MenuContent, MenuItem, MenuLabel, MenuPortal, MenuTrigger} from "../../menu/Menu"; -import {Badge} from "../../badge/Badge"; -import {useService} from "../../../utils/contextStore"; +import {useService, useStore as usePictorStore} from "../../../utils/contextStore"; import {DFlowFunctionReactiveService} from "./DFlowFunction.service"; import {useFunctionValidation} from "./DFlowFunction.vaildation.hook"; import {DFlowDataTypeReactiveService} from "../data-type/DFlowDataType.service"; import {InspectionSeverity} from "../../../utils/inspection"; import {DFlowReactiveService} from "../DFlow.service"; -import {DFlowSuggestionMenu} from "../suggestion/DFlowSuggestionMenu"; -import {useSuggestions} from "../suggestion/DFlowSuggestion.hook"; import {FileTabsService} from "../../file-tabs/FileTabs.service"; import {DFlowTabDefault} from "../tab/DFlowTabDefault"; -import type {Maybe, NodeFunction, NodeParameter, Scalars} from "@code0-tech/sagittarius-graphql-types"; +import type {NodeFunction, NodeParameter, Scalars} from "@code0-tech/sagittarius-graphql-types"; +import {Badge} from "../../badge/Badge"; export interface DFlowFunctionDefaultCardDataProps extends Omit, "scope"> { node: NodeFunction @@ -46,29 +31,25 @@ export interface DFlowFunctionDefaultCardDataProps extends Omit> export const DFlowFunctionDefaultCard: React.FC = memo((props) => { - const {data, id} = props; + const {data, id, width = 0, height = 0} = props + const viewportWidth = useStore(s => s.width); const viewportHeight = useStore(s => s.height); const flowInstance = useReactFlow() - const flowStoreApi = useStoreApi() const fileTabsService = useService(FileTabsService) const flowService = useService(DFlowReactiveService) + const flowStore = usePictorStore(DFlowReactiveService) const functionService = useService(DFlowFunctionReactiveService) + const functionStore = usePictorStore(DFlowFunctionReactiveService) const dataTypeService = useService(DFlowDataTypeReactiveService) - const definition = functionService.getById(data.node.functionDefinition?.id!!) - //TODO: some problems with react memorization here, need to investigate and also with hook calling - const validation = useFunctionValidation(definition!!, data.node.parameters!.nodes!.map(p => p?.value!!), dataTypeService!!, props.data.flowId) const edges = useStore(s => s.edges); - const width = props.width ?? 0 - const height = props.height ?? 0 - data.node.parameters?.nodes?.forEach(parameter => { - const parameterDefinition = definition?.parameterDefinitions!!.find(p => p.id == parameter?.id) - //parameter.validationResults = validation ? validation.filter(v => v.parameterId === parameterDefinition?.id) : [] - }) + const definition = React.useMemo(() => functionService.getById(data.node.functionDefinition?.id!!), [functionStore, data]) + const validation = useFunctionValidation(definition!!, data.node.parameters!.nodes!.map(p => p?.value!!), dataTypeService!!, props.data.flowId) + const node = React.useMemo(() => flowService.getNodeById(data.flowId, data.node.id), [flowStore, data]) + - // Helper, ob zu diesem Parameter eine Edge existiert: - function isParamConnected(paramId: Maybe): boolean { + function isParamConnected(paramId: NodeParameter['id']): boolean { return edges.some(e => e.target === id && e.targetHandle === `param-${paramId}` @@ -88,6 +69,54 @@ export const DFlowFunctionDefaultCard: React.FC = return start; }) + const splitTemplate = (str: string) => + str + .split(/(\$\{[^}]+\})/) + .filter(Boolean) + .flatMap(part => + part.startsWith("${") + ? [part.slice(2, -1)] // variable name ohne ${} + : part.split(/(\s*,\s*)/) // Kommas einzeln extrahieren + .filter(Boolean) + .flatMap(p => p.trim() === "," ? [","] : p.trim() ? [p.trim()] : []) + ); + + const displayMessage = React.useMemo(() => splitTemplate(definition?.displayMessages?.nodes!![0]?.content!!).map(item => { + const param = node?.parameters?.nodes?.find(p => { + const parameterDefinition = definition?.parameterDefinitions?.find(pd => pd.id == p?.id) + return parameterDefinition?.identifier == item + }) + + if (param) { + switch (param?.value?.__typename) { + case "LiteralValue": + return + + {String(param?.value?.value)} + + + case "ReferenceValue": + return + + {String(param?.value.node)}-{String(param?.value.depth)}-{String(param?.value.scope)} + + + case "NodeFunction": + return + + {String(functionService.getById(param?.value?.functionDefinition?.id)?.names?.nodes!![0]?.content)} + + + } + return + + {item} + + + } + return " " + String(item) + " " + }), [flowStore, functionStore, data]) + return ( = }) }} style={{position: "relative"}}> - - - - - {definition?.names?.nodes!![0]?.content} - - - { - setTimeout(() => { - flowStoreApi.setState({ - nodesDraggable: !event, - nodesConnectable: !event, - elementsSelectable: !event, - }); - }, 250) // Timeout to ensure the menu is fully opened before changing the state - }}> - - - - - - Actions - { - flowService.deleteNodeById(data.flowId, data.node.id) - }}> Delete node - Copy node - - - - - - - + + + {displayMessage} + + + {node?.parameters?.nodes?.map(param => { + return + + {node?.parameters?.nodes?.map(param => { - return = /> })} - - {/* Ausgang */} Date: Sat, 13 Dec 2025 00:03:25 +0100 Subject: [PATCH 081/197] feat: add conditional styling to Badge component based on badge color variables --- src/components/badge/Badge.style.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/badge/Badge.style.scss b/src/components/badge/Badge.style.scss index d2564a488..1988ae48e 100644 --- a/src/components/badge/Badge.style.scss +++ b/src/components/badge/Badge.style.scss @@ -12,6 +12,12 @@ height: fit-content; vertical-align: middle; + @if (var(--badge-color)) { + background-color: var(--badge-color-background); + border: 1px solid var(--badge-color-border); + color: var(--badge-color); + } + & { @include helpers.fontStyle(); @include helpers.borderRadius() From 9030892c29d0e07599cabd1bba162be7b7840670 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 13 Dec 2025 00:03:31 +0100 Subject: [PATCH 082/197] feat: enhance Badge component with dynamic color styling and utility functions --- src/components/badge/Badge.tsx | 89 +++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 8 deletions(-) diff --git a/src/components/badge/Badge.tsx b/src/components/badge/Badge.tsx index 74b775ef0..f549bf7ed 100644 --- a/src/components/badge/Badge.tsx +++ b/src/components/badge/Badge.tsx @@ -3,18 +3,91 @@ import "./Badge.style.scss" import {Code0Component, Color} from "../../utils/types"; import {mergeCode0Props} from "../../utils/utils"; -export interface BadgeType extends Code0Component{ +export interface BadgeType extends Code0Component { children: React.ReactNode - //defaults to primary - color?: Color + // defaults to primary + color?: Color | string border?: boolean } +type RGBA = { + r: number + g: number + b: number + a: number +} + export const Badge: React.FC = (props) => { - + const {color = "primary", border = false, children, ...args} = props - - return - {children} - + + return ( + + {children} + + ) +} + +/* =========================== + Color utilities + =========================== */ + +const clamp01 = (v: number) => Math.min(Math.max(v, 0), 1) + +const parseCssColorToRgba = (color: string): RGBA => { + if (typeof document === "undefined") { + return {r: 0, g: 0, b: 0, a: 1} + } + + const el = document.createElement("span") + el.style.color = color + document.body.appendChild(el) + + const computed = getComputedStyle(el).color + document.body.removeChild(el) + + const match = computed.match( + /rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?\s*\)/ + ) + + if (!match) { + return {r: 0, g: 0, b: 0, a: 1} + } + + return { + r: Math.round(Number(match[1])), + g: Math.round(Number(match[2])), + b: Math.round(Number(match[3])), + a: match[4] !== undefined ? Number(match[4]) : 1, + } +} + +const mixColorRgb = (color: string, level: number) => { + const w = clamp01(level * 0.1) + + const c1 = parseCssColorToRgba(color) + const c2 = parseCssColorToRgba("#030014") + + const mix = (a: number, b: number) => + Math.round(a * (1 - w) + b * w) + + return `rgb(${mix(c1.r, c2.r)}, ${mix(c1.g, c2.g)}, ${mix(c1.b, c2.b)})` +} + +const withAlpha = (color: string, alpha: number) => { + const c = parseCssColorToRgba(color) + return `rgba(${c.r}, ${c.g}, ${c.b}, ${clamp01(alpha)})` } \ No newline at end of file From 3e8151bf3515334191f3b29618bed6ce6930ccf3 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 13 Dec 2025 00:03:36 +0100 Subject: [PATCH 083/197] feat: implement dynamic edge coloring in flow visualization using hash-based hues --- src/components/d-flow/DFlow.edges.hook.ts | 28 +++++++++++++++-------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/components/d-flow/DFlow.edges.hook.ts b/src/components/d-flow/DFlow.edges.hook.ts index f40bf6fd8..eea8fbf3d 100644 --- a/src/components/d-flow/DFlow.edges.hook.ts +++ b/src/components/d-flow/DFlow.edges.hook.ts @@ -5,16 +5,10 @@ import {DFlowFunctionReactiveService} from "./function"; import {DFlowDataTypeReactiveService} from "./data-type"; import React from "react"; import type {DataTypeIdentifier, Flow, NodeFunction, Scalars} from "@code0-tech/sagittarius-graphql-types"; +import {md5} from "js-md5"; export const FLOW_EDGE_RAINBOW: string[] = [ - '#70ffb2', // 0 – Primary (Grün) - '#70e2ff', // 1 – Cyan - '#709aff', // 2 – Blau - '#a170ff', // 3 – Violett - '#f170ff', // 4 – Magenta - '#ff70b5', // 5 – Pink/Rot - '#ff7070', // 6 – Orange-Rot - '#fff170', // 7 – Gelb + 'rgba(255, 255, 255, 0.25)', // rot ]; export const useFlowEdges = (flowId: Flow['id']): Edge[] => { @@ -127,6 +121,12 @@ export const useFlowEdges = (flowId: Flow['id']): Edge[] => { if (paramDT?.variant === "NODE") { const groupId = `${fnId}-group-${idCounter++}`; + const hash = md5(`${fnId}-param-${JSON.stringify(param)}`) + const hashToHue = (md5: string): number => { + // nimm z.B. 8 Hex-Zeichen = 32 Bit + const int = parseInt(md5.slice(0, 8), 16) + return int % 360 + } edges.push({ id: `${fnId}-${groupId}-param-${param.id}`, @@ -134,9 +134,10 @@ export const useFlowEdges = (flowId: Flow['id']): Edge[] => { target: groupId, deletable: false, selectable: false, + animated: true, label: def?.names?.nodes!![0]?.content ?? param.id, data: { - color: FLOW_EDGE_RAINBOW[level % FLOW_EDGE_RAINBOW.length], + color: `hsl(${hashToHue(hash)}, 100%, 72%)`, isParameter: false, }, }); @@ -161,6 +162,13 @@ export const useFlowEdges = (flowId: Flow['id']): Edge[] => { fnCache, dtCache); + const hash = md5(`${fnId}-param-${JSON.stringify(param)}`) + const hashToHue = (md5: string): number => { + // nimm z.B. 8 Hex-Zeichen = 32 Bit + const int = parseInt(md5.slice(0, 8), 16) + return int % 360 + } + edges.push({ id: `${subFnId}-${fnId}-param-${param.id}`, source: subFnId, @@ -170,7 +178,7 @@ export const useFlowEdges = (flowId: Flow['id']): Edge[] => { deletable: false, selectable: false, data: { - color: FLOW_EDGE_RAINBOW[(level + 1) % FLOW_EDGE_RAINBOW.length], + color: `hsl(${hashToHue(hash)}, 100%, 72%)`, isParameter: true }, }); From 181a7e832f1bd0dc35b4f810c5bd53ab5df8db8b Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 13 Dec 2025 00:03:41 +0100 Subject: [PATCH 084/197] feat: enhance flow service with dynamic node ID generation and improved node handling --- src/components/d-flow/DFlow.service.ts | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/components/d-flow/DFlow.service.ts b/src/components/d-flow/DFlow.service.ts index 4cd7d32d5..9fcd9bb64 100644 --- a/src/components/d-flow/DFlow.service.ts +++ b/src/components/d-flow/DFlow.service.ts @@ -53,7 +53,8 @@ export abstract class DFlowReactiveService extends ReactiveArrayService { const flow = this.getById(flowId) const index = this.values().findIndex(f => f.id === flowId) const node = this.getNodeById(flowId, nodeId) - if (!flow || !node) return + console.log(nextNode, nodeId) + if (!flow || !node || this.getNodeById(flowId, nextNode.id)) return flow.nodes?.nodes?.push(nextNode) node.nextNodeId = nextNode.id this.set(index, flow) @@ -63,8 +64,12 @@ export abstract class DFlowReactiveService extends ReactiveArrayService { const flow = this.getById(flowId) if (!flow) return const index = this.values().findIndex(f => f.id === flowId) - flow.nodes?.nodes?.push(startingNode) - flow.startingNodeId = startingNode.id + const addingIdValue: NodeFunction = { + ...startingNode, + id: `gid://sagittarius/NodeFunction/${(flow.nodes?.nodes?.length ?? 0) + 1}` + } + flow.nodes?.nodes?.push(addingIdValue) + flow.startingNodeId = addingIdValue.id this.set(index, flow) } @@ -86,9 +91,20 @@ export abstract class DFlowReactiveService extends ReactiveArrayService { if (!node) return const parameter = node.parameters?.nodes?.find(p => p?.id === parameterId) if (!parameter) return + if (parameter?.value?.__typename === "NodeFunction") { + // @ts-ignore + flow.nodes!.nodes = flow.nodes!.nodes!.filter(n => n?.id !== parameter.value?.id) + } parameter.value = value if (value?.__typename === "NodeFunction") { - flow.nodes?.nodes?.push(value) + const addingIdValue: NodeFunction = { + ...value, + id: `gid://sagittarius/NodeFunction/${(flow.nodes?.nodes?.length ?? 0) + 1}` + } + flow.nodes?.nodes?.push(addingIdValue) + parameter.value = addingIdValue + } else { + parameter.value = value } this.set(index, flow) } From 9220fdb727429de2f98063bfaf04199fd2225bf4 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 13 Dec 2025 00:03:46 +0100 Subject: [PATCH 085/197] feat: update DFlowEdge component to use primary badge color and simplify centerY calculation --- src/components/d-flow/edge/DFlowEdge.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/d-flow/edge/DFlowEdge.tsx b/src/components/d-flow/edge/DFlowEdge.tsx index 21ea66e99..c72050e60 100644 --- a/src/components/d-flow/edge/DFlowEdge.tsx +++ b/src/components/d-flow/edge/DFlowEdge.tsx @@ -41,7 +41,7 @@ export const DFlowEdge: React.FC = memo((props) => { targetY, targetPosition: data?.isParameter ? Position.Right : Position.Top, borderRadius: 16, - centerY: data?.isSuggestion ? targetY - 25 : targetY - 25 + centerY: targetY - 25, }) const color = data?.color ?? "#ffffff" @@ -83,7 +83,7 @@ export const DFlowEdge: React.FC = memo((props) => { zIndex: 100, }} > - + {label}
From fdb5dec040b96cdab77562071712d42cff217585 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 13 Dec 2025 00:03:51 +0100 Subject: [PATCH 086/197] feat: enhance DFlowFunctionDefaultCard with dynamic tab identification and hash-based badge coloring --- .../function/DFlowFunctionDefaultCard.tsx | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx index 41a6b3e04..db5bfb1e7 100644 --- a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx @@ -16,6 +16,7 @@ import {FileTabsService} from "../../file-tabs/FileTabs.service"; import {DFlowTabDefault} from "../tab/DFlowTabDefault"; import type {NodeFunction, NodeParameter, Scalars} from "@code0-tech/sagittarius-graphql-types"; import {Badge} from "../../badge/Badge"; +import {md5} from "js-md5"; export interface DFlowFunctionDefaultCardDataProps extends Omit, "scope"> { node: NodeFunction @@ -37,6 +38,7 @@ export const DFlowFunctionDefaultCard: React.FC = const viewportHeight = useStore(s => s.height); const flowInstance = useReactFlow() const fileTabsService = useService(FileTabsService) + const fileTabsStore = usePictorStore(FileTabsService) const flowService = useService(DFlowReactiveService) const flowStore = usePictorStore(DFlowReactiveService) const functionService = useService(DFlowFunctionReactiveService) @@ -47,7 +49,9 @@ export const DFlowFunctionDefaultCard: React.FC = const definition = React.useMemo(() => functionService.getById(data.node.functionDefinition?.id!!), [functionStore, data]) const validation = useFunctionValidation(definition!!, data.node.parameters!.nodes!.map(p => p?.value!!), dataTypeService!!, props.data.flowId) const node = React.useMemo(() => flowService.getNodeById(data.flowId, data.node.id), [flowStore, data]) - + const activeTabId = React.useMemo(() => { + return fileTabsStore.find((t: any) => (t as any).active)?.id + }, [fileTabsStore, fileTabsService]); function isParamConnected(paramId: NodeParameter['id']): boolean { return edges.some(e => @@ -102,8 +106,14 @@ export const DFlowFunctionDefaultCard: React.FC = case "NodeFunction": - return - + const hash = md5(`${id}-param-${JSON.stringify(param)}`) + const hashToHue = (md5: string): number => { + // nimm z.B. 8 Hex-Zeichen = 32 Bit + const int = parseInt(md5.slice(0, 8), 16) + return int % 360 + } + return + {String(functionService.getById(param?.value?.functionDefinition?.id)?.names?.nodes!![0]?.content)} @@ -121,8 +131,8 @@ export const DFlowFunctionDefaultCard: React.FC = v.type === InspectionSeverity.ERROR)?.length ?? 0) > 0 ? "error" : "primary"} onClick={() => { flowInstance.setViewport({ From 40f58d351913057ce11fc22268a8b138e7c4451d Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 13 Dec 2025 00:04:00 +0100 Subject: [PATCH 087/197] feat: center handles in DFlowFunctionGroupCard for improved alignment --- src/components/d-flow/function/DFlowFunctionGroupCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionGroupCard.tsx b/src/components/d-flow/function/DFlowFunctionGroupCard.tsx index d0033d056..1d0ed493b 100644 --- a/src/components/d-flow/function/DFlowFunctionGroupCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionGroupCard.tsx @@ -47,7 +47,7 @@ export const DFlowFunctionGroupCard: React.FC = mem className={"d-flow-viewport-default-card__handle d-flow-viewport-default-card__handle--target"} isConnectable={false} draggable={false} - style={{top: "2px", left: handleLeft}} + style={{top: "2px", left: "50%", transform: "translateX(-50%)"}} /> = mem className={"d-flow-viewport-default-card__handle d-flow-viewport-default-card__handle--source"} isConnectable={false} draggable={false} - style={{bottom: "2px", left: handleLeft}} + style={{bottom: "2px", left: "50%", transform: "translateX(-50%)"}} /> ); From cb05c9d684161ee4e3bb173febef76b13f2f80a1 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 13 Dec 2025 00:04:07 +0100 Subject: [PATCH 088/197] feat: update active tab retrieval in DFlowPanelControl for improved accuracy --- src/components/d-flow/panel/DFlowPanelControl.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/d-flow/panel/DFlowPanelControl.tsx b/src/components/d-flow/panel/DFlowPanelControl.tsx index e51ecf8c4..e78dc63e2 100644 --- a/src/components/d-flow/panel/DFlowPanelControl.tsx +++ b/src/components/d-flow/panel/DFlowPanelControl.tsx @@ -13,7 +13,9 @@ export const DFlowPanelControl: React.FC = () => { const fileTabsService = useService(FileTabsService) const fileTabsStore = useStore(FileTabsService) const flowService = useService(DFlowReactiveService) - const activeTab = React.useMemo(() => fileTabsService.getActiveTab(), [fileTabsStore]) + const activeTab = React.useMemo(() => { + return fileTabsStore.find((t: any) => (t as any).active) + }, [fileTabsStore, fileTabsService]); const deleteActiveNode = React.useCallback(() => { if (!activeTab) return From 2258e7b3c1ffcc64ea61b0879fd5d63d69927707 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 13 Dec 2025 00:04:12 +0100 Subject: [PATCH 089/197] feat: optimize flow retrieval in DFlowSuggestion with memoization and improved function matching --- .../suggestion/DFlowSuggestion.hook.tsx | 102 +++++++++--------- 1 file changed, 52 insertions(+), 50 deletions(-) diff --git a/src/components/d-flow/suggestion/DFlowSuggestion.hook.tsx b/src/components/d-flow/suggestion/DFlowSuggestion.hook.tsx index 80ceb4775..bb5430405 100644 --- a/src/components/d-flow/suggestion/DFlowSuggestion.hook.tsx +++ b/src/components/d-flow/suggestion/DFlowSuggestion.hook.tsx @@ -1,4 +1,4 @@ -import {useService} from "../../../utils/contextStore"; +import {useService, useStore} from "../../../utils/contextStore"; import {DFlowReactiveSuggestionService} from "./DFlowSuggestion.service"; import {DFlowDataTypeReactiveService} from "../data-type/DFlowDataType.service"; import {md5} from 'js-md5'; @@ -20,6 +20,7 @@ import type { NodeParameterValue, ReferenceValue } from "@code0-tech/sagittarius-graphql-types"; +import React from "react"; //TODO: deep type search //TODO: calculate FUNCTION_COMBINATION deepness max 2 @@ -37,8 +38,9 @@ export const useSuggestions = ( const suggestionService = useService(DFlowReactiveSuggestionService) const dataTypeService = useService(DFlowDataTypeReactiveService) const flowService = useService(DFlowReactiveService) + const flowStore = useStore(DFlowReactiveService) const functionService = useService(DFlowFunctionReactiveService) - const flow = flowService.getById(flowId) + const flow = React.useMemo(() => flowService?.getById(flowId), [flowStore, flowId]) const dataType = type ? dataTypeService?.getDataType(type) : undefined if (!suggestionService || !dataTypeService) return [] @@ -85,57 +87,57 @@ export const useSuggestions = ( }) } - if (suggestionTypes.includes(DFlowSuggestionType.FUNCTION_COMBINATION)) { - //calculate FUNCTION - //generics to be replaced with GENERIC todo is written on top - const matchingFunctions = functionService.values().filter(funcDefinition => { - if (!type || !resolvedType || !hashedType) return true - if (funcDefinition.runtimeFunctionDefinition?.identifier == "RETURN" && type) return false - if (dataType?.variant === "NODE") return true - if (!funcDefinition.returnType) return false - if (!funcDefinition.genericKeys) return false - const resolvedReturnType = replaceGenericsAndSortType(resolveType(funcDefinition.returnType, dataTypeService), funcDefinition.genericKeys) - return isMatchingType(resolvedType, resolvedReturnType) - }).sort((a, b) => { - const [rA, pA, fA] = a.runtimeFunctionDefinition!!.identifier!!.split("::"); - const [rB, pB, fB] = b.runtimeFunctionDefinition!!.identifier!!.split("::"); - - // Erst runtime vergleichen - const runtimeCmp = rA.localeCompare(rB); - if (runtimeCmp !== 0) return runtimeCmp; - - // Dann package vergleichen - const packageCmp = pA.localeCompare(pB); - if (packageCmp !== 0) return packageCmp; - - // Dann function name - return fA.localeCompare(fB); - }) + } + + if (suggestionTypes.includes(DFlowSuggestionType.FUNCTION_COMBINATION)) { + //calculate FUNCTION + //generics to be replaced with GENERIC todo is written on top + const matchingFunctions = functionService.values().filter(funcDefinition => { + if (!type || !resolvedType || !hashedType) return true + if (funcDefinition.runtimeFunctionDefinition?.identifier == "RETURN" && type) return false + if (dataType?.variant === "NODE") return true + if (!funcDefinition.returnType) return false + if (!funcDefinition.genericKeys) return false + const resolvedReturnType = replaceGenericsAndSortType(resolveType(funcDefinition.returnType, dataTypeService), funcDefinition.genericKeys) + return isMatchingType(resolvedType, resolvedReturnType) + }).sort((a, b) => { + const [rA, pA, fA] = a.runtimeFunctionDefinition!!.identifier!!.split("::"); + const [rB, pB, fB] = b.runtimeFunctionDefinition!!.identifier!!.split("::"); + + // Erst runtime vergleichen + const runtimeCmp = rA.localeCompare(rB); + if (runtimeCmp !== 0) return runtimeCmp; + + // Dann package vergleichen + const packageCmp = pA.localeCompare(pB); + if (packageCmp !== 0) return packageCmp; + + // Dann function name + return fA.localeCompare(fB); + }) - matchingFunctions.forEach(funcDefinition => { - const nodeFunctionSuggestion: NodeParameterValue = { - __typename: "NodeFunction", - id: `gid://sagittarius/NodeFunction/${(flow?.nodes?.nodes?.length ?? 0) + 1}`, - functionDefinition: { - id: funcDefinition.id, - runtimeFunctionDefinition: funcDefinition.runtimeFunctionDefinition - }, - parameters: { - nodes: (funcDefinition.parameterDefinitions?.map(definition => { - return { - id: definition.id, - runtimeParameter: { - id: definition.id - } + matchingFunctions.forEach(funcDefinition => { + const nodeFunctionSuggestion: NodeParameterValue = { + __typename: "NodeFunction", + id: `gid://sagittarius/NodeFunction/${(flow?.nodes?.nodes?.length ?? 0) + 1}`, + functionDefinition: { + id: funcDefinition.id, + runtimeFunctionDefinition: funcDefinition.runtimeFunctionDefinition + }, + parameters: { + nodes: (funcDefinition.parameterDefinitions?.map(definition => { + return { + id: definition.id, + runtimeParameter: { + id: definition.id } - }) ?? []) as Maybe>> - } + } + }) ?? []) as Maybe>> } - const suggestion = new DFlowSuggestion(hashedType || "", [], nodeFunctionSuggestion, DFlowSuggestionType.FUNCTION, [funcDefinition.names?.nodes!![0]?.content as string]) - state.push(suggestion) - }) - } - + } + const suggestion = new DFlowSuggestion(hashedType || "", [], nodeFunctionSuggestion, DFlowSuggestionType.FUNCTION, [funcDefinition.names?.nodes!![0]?.content as string]) + state.push(suggestion) + }) } if (suggestionTypes.includes(DFlowSuggestionType.REF_OBJECT)) { From 3327db00e3c551cf6c9673f57689444552d75b4b Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 13 Dec 2025 00:04:18 +0100 Subject: [PATCH 090/197] feat: update fileTabsService to ensure tab activation triggers state update --- src/components/d-flow/tab/DFlowTabs.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/d-flow/tab/DFlowTabs.tsx b/src/components/d-flow/tab/DFlowTabs.tsx index c13c173a8..ae34844ac 100644 --- a/src/components/d-flow/tab/DFlowTabs.tsx +++ b/src/components/d-flow/tab/DFlowTabs.tsx @@ -40,6 +40,7 @@ export const DFlowTabs = () => { value={activeTabId} onValueChange={(value) => { fileTabsService.activateTab(value); // mutieren reicht; kein .update() nötig, wenn setState benutzt wird + fileTabsService.update() }} > Date: Sat, 13 Dec 2025 00:04:24 +0100 Subject: [PATCH 091/197] feat: initialize fileTabsService with an empty array for improved state management --- src/components/d-resizable/DResizable.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/d-resizable/DResizable.stories.tsx b/src/components/d-resizable/DResizable.stories.tsx index baca8e878..e93a3c0b0 100644 --- a/src/components/d-resizable/DResizable.stories.tsx +++ b/src/components/d-resizable/DResizable.stories.tsx @@ -62,7 +62,7 @@ class DFlowReactiveSuggestionServiceExtend extends DFlowReactiveSuggestionServic export const Dashboard = () => { - const [fileTabsStore, fileTabsService] = useReactiveArrayService(FileTabsService) + const [fileTabsStore, fileTabsService] = useReactiveArrayService(FileTabsService, []) // @ts-ignore const [dataTypeStore, dataTypeService] = useReactiveArrayService(DFlowDataTypeReactiveService, [...DataTypesData.map(data => new DataTypeView(data))]); // @ts-ignore From e030d954f98031ab34b1f071b2c6cec04b2c8f06 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sat, 13 Dec 2025 00:04:29 +0100 Subject: [PATCH 092/197] feat: enhance FileTabs.service with update calls on add and delete methods for improved state management --- src/components/file-tabs/FileTabs.service.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/file-tabs/FileTabs.service.ts b/src/components/file-tabs/FileTabs.service.ts index be34863be..9159337ad 100644 --- a/src/components/file-tabs/FileTabs.service.ts +++ b/src/components/file-tabs/FileTabs.service.ts @@ -46,11 +46,12 @@ export class FileTabsService extends ReactiveArrayService { } super.delete(index); + this.update() } public add(value: FileTabsView) { //if tab with id already exists, do not add it again and just activate the existing one - + console.log("Getting active tab 1", this.access.getState()) if (this.values().some(value1 => value1.id == value.id)) { this.activateTab(value.id!!) return @@ -62,11 +63,11 @@ export class FileTabsService extends ReactiveArrayService { }) } super.add(value); + this.update() } public getActiveTab(): FileTabsView | undefined { - const values = [...this.values()] - return values.reverse().find((item: FileTabsView) => { + return this.values().find((item: FileTabsView) => { return item.active }) } From c36a1b0967b0ad86cb787bd58196a0d18186dcf7 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 00:14:41 +0100 Subject: [PATCH 093/197] feat: extend box styles to include open state for improved accessibility --- src/styles/_box.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles/_box.scss b/src/styles/_box.scss index 93e9d0528..0bbf47023 100644 --- a/src/styles/_box.scss +++ b/src/styles/_box.scss @@ -44,7 +44,7 @@ $borderColor: variables.$secondary ) { - &:active, &:focus, &[aria-selected=true] { + &:active, &:focus, &[aria-selected=true], &[data-state=open] { @include boxActiveStyle($background, $color, $borderColor); } } \ No newline at end of file From d003aaf9141db8b5bd47f6854b71c26499456c69 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 00:14:49 +0100 Subject: [PATCH 094/197] feat: enhance button styles to include open state for improved accessibility --- src/components/button/Button.style.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/button/Button.style.scss b/src/components/button/Button.style.scss index f4df1684a..5d4552979 100644 --- a/src/components/button/Button.style.scss +++ b/src/components/button/Button.style.scss @@ -34,7 +34,7 @@ } } - &:active, &:focus, &[aria-selected=true] { + &:active, &:focus, &[aria-selected=true], &[data-state=open] { @if ($color == variables.$primary) { border-color: rgba(variables.$secondary, 0.2); } @else { @@ -51,7 +51,7 @@ background: helpers.backgroundColor($color, 1.5); } - &:active, &:focus, &[aria-selected=true] { + &:active, &:focus, &[aria-selected=true], &[data-state=open] { background: helpers.backgroundColor($color, 2); } } From ce6395e272f5b08e62c9b1cfe9c4bd1340186432 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 00:14:54 +0100 Subject: [PATCH 095/197] feat: update DFlowFunctionDefaultCard to use color-coded icons based on hash for improved visual distinction --- .../function/DFlowFunctionDefaultCard.tsx | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx index db5bfb1e7..361964091 100644 --- a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx @@ -4,7 +4,7 @@ import React, {memo} from "react"; import {Card} from "../../card/Card"; import "./DFlowFunctionDefaultCard.style.scss"; import {Flex} from "../../flex/Flex"; -import {IconFileLambdaFilled} from "@tabler/icons-react"; +import {IconFile, IconFileLambdaFilled} from "@tabler/icons-react"; import {Text} from "../../text/Text"; import {useService, useStore as usePictorStore} from "../../../utils/contextStore"; import {DFlowFunctionReactiveService} from "./DFlowFunction.service"; @@ -85,6 +85,13 @@ export const DFlowFunctionDefaultCard: React.FC = .flatMap(p => p.trim() === "," ? [","] : p.trim() ? [p.trim()] : []) ); + const colorHash = md5(id) + const hashToHue = (md5: string): number => { + // nimm z.B. 8 Hex-Zeichen = 32 Bit + const int = parseInt(md5.slice(0, 8), 16) + return int % 360 + } + const displayMessage = React.useMemo(() => splitTemplate(definition?.displayMessages?.nodes!![0]?.content!!).map(item => { const param = node?.parameters?.nodes?.find(p => { const parameterDefinition = definition?.parameterDefinitions?.find(pd => pd.id == p?.id) @@ -107,11 +114,6 @@ export const DFlowFunctionDefaultCard: React.FC = case "NodeFunction": const hash = md5(`${id}-param-${JSON.stringify(param)}`) - const hashToHue = (md5: string): number => { - // nimm z.B. 8 Hex-Zeichen = 32 Bit - const int = parseInt(md5.slice(0, 8), 16) - return int % 360 - } return {String(functionService.getById(param?.value?.functionDefinition?.id)?.names?.nodes!![0]?.content)} @@ -146,14 +148,17 @@ export const DFlowFunctionDefaultCard: React.FC = id: id, active: true, closeable: true, - children: {definition?.names?.nodes!![0]?.content}, + children: <> + + {definition?.names?.nodes!![0]?.content} + , content: }) }} style={{position: "relative"}}> - + {displayMessage} From eb5e75642f83f58f4f8c8630e5aef440be9de8f0 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 00:15:05 +0100 Subject: [PATCH 096/197] feat: refactor DFlowTabs to enhance layout and button styles for improved usability --- src/components/d-flow/tab/DFlowTabs.tsx | 34 ++++++++++++++----------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/src/components/d-flow/tab/DFlowTabs.tsx b/src/components/d-flow/tab/DFlowTabs.tsx index ae34844ac..6b0844fc1 100644 --- a/src/components/d-flow/tab/DFlowTabs.tsx +++ b/src/components/d-flow/tab/DFlowTabs.tsx @@ -4,8 +4,11 @@ import {FileTabs, FileTabsContent, FileTabsList, FileTabsTrigger} from "../../fi import React from "react"; import {Menu, MenuContent, MenuItem, MenuPortal, MenuSeparator, MenuTrigger} from "../../menu/Menu"; import {Button} from "../../button/Button"; -import {IconChevronDown, IconDotsVertical} from "@tabler/icons-react"; +import {IconChevronDown, IconDotsVertical, IconPlus} from "@tabler/icons-react"; import {FileTabsView} from "../../file-tabs/FileTabs.view"; +import {Text} from "../../text/Text"; +import {DLayout} from "../../d-layout/DLayout"; +import {ButtonGroup} from "../../button-group/ButtonGroup"; export const DFlowTabs = () => { @@ -43,17 +46,17 @@ export const DFlowTabs = () => { fileTabsService.update() }} > - + - - + {fileTabsStore.map((tab: FileTabsView) => ( { @@ -62,13 +65,14 @@ export const DFlowTabs = () => { {tab.children} ))} + {/*TODO: Add all available nodes*/} - @@ -85,7 +89,7 @@ export const DFlowTabs = () => { - + } > {fileTabsStore.map((tab: FileTabsView, index: number) => ( @@ -98,13 +102,13 @@ export const DFlowTabs = () => { {tab.children} ))} - - - {fileTabsStore.map((tab: FileTabsView) => ( - - {tab.content} - - ))} + }> + {fileTabsStore.map((tab: FileTabsView) => ( + + {tab.content} + + ))} + ); From 057a6a02250915c0a39300930669441cb036e3b6 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 00:15:11 +0100 Subject: [PATCH 097/197] feat: refactor FileTabs styles for improved layout and visual clarity --- src/components/file-tabs/FileTabs.style.scss | 42 +++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/components/file-tabs/FileTabs.style.scss b/src/components/file-tabs/FileTabs.style.scss index 9f4d31c94..37a090b41 100644 --- a/src/components/file-tabs/FileTabs.style.scss +++ b/src/components/file-tabs/FileTabs.style.scss @@ -16,38 +16,31 @@ align-items: center; justify-content: space-between; position: relative; - gap: variables.$xs; padding: variables.$xs; &-content { display: flex; + width: 100%; overflow-x: scroll; flex-wrap: nowrap; - gap: variables.$xxs; + gap: variables.$xs; -ms-overflow-style: none; /* Internet Explorer 10+ */ scrollbar-width: none; /* Firefox, Safari 18.2+, Chromium 121+ */ &::-webkit-scrollbar { display: none; /* Older Safari and Chromium */ } } - - &-controls { - display: flex; - flex-wrap: nowrap; - gap: variables.$xxs; - } } &__trigger { padding: variables.$xxs variables.$xs; display: flex; - gap: variables.$xs; + gap: variables.$xxs; align-items: center; text-wrap: nowrap; margin-bottom: 1px; cursor: pointer; - font-size: variables.$sm; & { @include box.box(variables.$primary); @@ -55,22 +48,25 @@ @include helpers.borderRadius(); @include helpers.fontStyle(); background: transparent; - box-shadow: none; + border: none; + &:after { + position: absolute; + content: ""; + height: 75%; + width: 100%; + left: variables.$xxs; + top: 50%; + transform: translateY(-50%); + border-right: 1px solid helpers.borderColor(); + z-index: -1; + } } &[data-state="active"] { - @include box.box(variables.$secondary); - - .file-tabs__trigger-icon { - opacity: 1; - } + @include box.boxActiveStyle(variables.$secondary); + border: none; } - &:hover, &:active { - .file-tabs__trigger-icon { - opacity: 1; - } - } } &__trigger-icon { @@ -78,19 +74,17 @@ align-items: center; justify-content: center; padding: .1rem; - opacity: 0; cursor: pointer; font-size: unset; & { - @include box.boxHover(variables.$primary); @include helpers.borderRadius(); } } &__content { - padding: variables.$xs + variables.$xs; + padding: variables.$xs; position: relative; } From ce77aaa488166b382d2c8206c206198befb7e0be Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 00:15:19 +0100 Subject: [PATCH 098/197] feat: update FileTabs to improve icon size and scroll area height for better usability --- src/components/file-tabs/FileTabs.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/file-tabs/FileTabs.tsx b/src/components/file-tabs/FileTabs.tsx index bb0224cb3..223b6d20c 100644 --- a/src/components/file-tabs/FileTabs.tsx +++ b/src/components/file-tabs/FileTabs.tsx @@ -12,7 +12,7 @@ import { import {mergeCode0Props} from "../../utils/utils"; import {Code0ComponentProps} from "../../utils/types"; import "./FileTabs.style.scss" -import {IconX} from "@tabler/icons-react"; +import {IconFile, IconX} from "@tabler/icons-react"; import {ScrollArea, ScrollAreaScrollbar, ScrollAreaThumb, ScrollAreaViewport} from "../scroll-area/ScrollArea"; type FileTabsProps = Code0ComponentProps & TabsProps @@ -38,14 +38,14 @@ export const FileTabsTrigger: React.FC = (props) => { data-value={props.value} {...mergeCode0Props("file-tabs__trigger", props) as FileTabsTriggerProps}> {props.children} {props.closable ?
- +
: null} } export const FileTabsContent: React.FC = ({children, ...props}) => { return - + {children} From b10faa0c46b0b072b3852c2e6511befef99faf19 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 17:44:10 +0100 Subject: [PATCH 099/197] feat: add color-coding to nodes based on hash for improved visual distinction --- src/components/d-flow/DFlow.nodes.hook.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/components/d-flow/DFlow.nodes.hook.ts b/src/components/d-flow/DFlow.nodes.hook.ts index 92c6c42b6..0c7b83f0e 100644 --- a/src/components/d-flow/DFlow.nodes.hook.ts +++ b/src/components/d-flow/DFlow.nodes.hook.ts @@ -9,6 +9,7 @@ import {DFlowFunctionDefaultCardDataProps} from "./function/DFlowFunctionDefault import {DFlowFunctionSuggestionCardDataProps} from "./function/DFlowFunctionSuggestionCard"; import {DFlowFunctionTriggerCardDataProps} from "./function/DFlowFunctionTriggerCard"; import {DFlowFunctionGroupCardDataProps} from "./function/DFlowFunctionGroupCard"; +import {md5} from "js-md5"; const packageNodes = new Map([ ['std', 'default'], @@ -214,6 +215,13 @@ export const useFlowNodes = (flowId: Flow['id']): Node { + // nimm z.B. 8 Hex-Zeichen = 32 Bit + const int = parseInt(md5.slice(0, 8), 16) + return int % 360 + } + nodes.push({ id: groupId, type: "group", @@ -227,6 +235,7 @@ export const useFlowNodes = (flowId: Flow['id']): Node Date: Sun, 14 Dec 2025 17:44:16 +0100 Subject: [PATCH 100/197] feat: pass flowId prop to DFlowTabs for improved flow identification --- src/components/d-flow/DFlowBuilder.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/d-flow/DFlowBuilder.tsx b/src/components/d-flow/DFlowBuilder.tsx index 26e7b53bc..dc0fac67a 100644 --- a/src/components/d-flow/DFlowBuilder.tsx +++ b/src/components/d-flow/DFlowBuilder.tsx @@ -47,7 +47,7 @@ export const DFlowBuilder: React.FC = (props) => { - + From db9c81db931f1d5ba16ac3065ebf32633576b8b8 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 17:44:20 +0100 Subject: [PATCH 101/197] feat: update DFlowFunctionDefaultCard to use node ID for color hashing and tab registration --- .../function/DFlowFunctionDefaultCard.tsx | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx index 361964091..90e2b3ba5 100644 --- a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx @@ -85,7 +85,7 @@ export const DFlowFunctionDefaultCard: React.FC = .flatMap(p => p.trim() === "," ? [","] : p.trim() ? [p.trim()] : []) ); - const colorHash = md5(id) + const colorHash = md5(node?.id!!) const hashToHue = (md5: string): number => { // nimm z.B. 8 Hex-Zeichen = 32 Bit const int = parseInt(md5.slice(0, 8), 16) @@ -129,12 +129,26 @@ export const DFlowFunctionDefaultCard: React.FC = return " " + String(item) + " " }), [flowStore, functionStore, data]) + React.useEffect(() => { + fileTabsService.registerTab({ + id: node?.id!!, + active: false, + closeable: true, + children: <> + + {definition?.names?.nodes!![0]?.content} + , + content: + }) + }, [node, data, fileTabsStore]) + return ( v.type === InspectionSeverity.ERROR)?.length ?? 0) > 0 ? "error" : "primary"} onClick={() => { flowInstance.setViewport({ @@ -144,17 +158,7 @@ export const DFlowFunctionDefaultCard: React.FC = }, { duration: 250, }) - fileTabsService.add({ - id: id, - active: true, - closeable: true, - children: <> - - {definition?.names?.nodes!![0]?.content} - , - content: - }) + fileTabsService.activateTab(node?.id!!) }} style={{position: "relative"}}> From 52de646875ac9426be6ed2bb9e38707a45b18b70 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 17:44:24 +0100 Subject: [PATCH 102/197] feat: enhance DFlowFunctionGroupCard with color utilities for improved styling --- .../function/DFlowFunctionGroupCard.tsx | 84 ++++++++++++------- 1 file changed, 54 insertions(+), 30 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionGroupCard.tsx b/src/components/d-flow/function/DFlowFunctionGroupCard.tsx index 1d0ed493b..21cc19a48 100644 --- a/src/components/d-flow/function/DFlowFunctionGroupCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionGroupCard.tsx @@ -10,6 +10,7 @@ export interface DFlowFunctionGroupCardDataProps extends Omit = memo((props) => { const {data, id} = props - const depth = data?.depth ?? 0; - const color = FLOW_EDGE_RAINBOW[depth % FLOW_EDGE_RAINBOW.length]; - - // Align handles with the first node inside this group - const handleLeft = useStore((s) => { - const children = s.nodes.filter((n) => n.parentId === id); - let start: any | undefined = undefined; - children.forEach((n) => { - const idx = (n.data as any)?.index ?? Infinity; - const startIdx = (start?.data as any)?.index ?? Infinity; - if (!start || idx < startIdx) { - start = n; - } - }); - if (start) { - const width = start.measured.width ?? 0; - return start.position.x + width / 2; - } - return undefined; - }) return ( + style={{background: withAlpha(data.color!!, 0.1), border: `2px dashed ${withAlpha(data.color!!, 0.1)}`}}> = mem className={"d-flow-viewport-default-card__handle d-flow-viewport-default-card__handle--source"} isConnectable={false} draggable={false} - style={{bottom: "2px", left: "50%", transform: "translateX(-50%)"}} + style={{bottom: "0px", left: "50%", transform: "translateX(-50%)"}} /> ); }); -const withAlpha = (hex: string, alpha: number) => { - const h = hex.replace('#', ''); - const r = parseInt(h.length === 3 ? h[0] + h[0] : h.slice(0, 2), 16); - const g = parseInt(h.length === 3 ? h[1] + h[1] : h.slice(2, 4), 16); - const b = parseInt(h.length === 3 ? h[2] + h[2] : h.slice(4, 6), 16); - return `rgba(${r}, ${g}, ${b}, ${alpha})`; -}; +/* =========================== + Color utilities + =========================== */ + +const clamp01 = (v: number) => Math.min(Math.max(v, 0), 1) + +const parseCssColorToRgba = (color: string): any => { + if (typeof document === "undefined") { + return {r: 0, g: 0, b: 0, a: 1} + } + + const el = document.createElement("span") + el.style.color = color + document.body.appendChild(el) + + const computed = getComputedStyle(el).color + document.body.removeChild(el) + + const match = computed.match( + /rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?\s*\)/ + ) + + if (!match) { + return {r: 0, g: 0, b: 0, a: 1} + } + + return { + r: Math.round(Number(match[1])), + g: Math.round(Number(match[2])), + b: Math.round(Number(match[3])), + a: match[4] !== undefined ? Number(match[4]) : 1, + } +} + +const mixColorRgb = (color: string, level: number) => { + const w = clamp01(level * 0.1) + + const c1 = parseCssColorToRgba(color) + const c2 = parseCssColorToRgba("#030014") + + const mix = (a: number, b: number) => + Math.round(a * (1 - w) + b * w) + + return `rgb(${mix(c1.r, c2.r)}, ${mix(c1.g, c2.g)}, ${mix(c1.b, c2.b)})` +} + +const withAlpha = (color: string, alpha: number) => { + const c = parseCssColorToRgba(color) + return `rgba(${c.r}, ${c.g}, ${c.b}, ${clamp01(alpha)})` +} From db9cdae89b183bcc8c4a1e6e409592a2d33ded25 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 17:44:27 +0100 Subject: [PATCH 103/197] feat: simplify imports in DFlowFunctionSuggestionCard for cleaner code structure --- .../d-flow/function/DFlowFunctionSuggestionCard.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionSuggestionCard.tsx b/src/components/d-flow/function/DFlowFunctionSuggestionCard.tsx index ede9972d0..3c841c116 100644 --- a/src/components/d-flow/function/DFlowFunctionSuggestionCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionSuggestionCard.tsx @@ -1,10 +1,10 @@ -import {Code0Component} from "../../../utils/types"; +import {Code0Component} from "../../../utils"; import {Handle, Node, NodeProps, Position} from "@xyflow/react"; import React, {memo} from "react"; import {Button} from "../../button/Button"; import {IconPlus} from "@tabler/icons-react"; import {useSuggestions} from "../suggestion/DFlowSuggestion.hook"; -import {useService} from "../../../utils/contextStore"; +import {useService} from "../../../utils"; import {DFlowReactiveService} from "../DFlow.service"; import {DFlowSuggestionMenu} from "../suggestion/DFlowSuggestionMenu"; import type {Flow, NodeFunction} from "@code0-tech/sagittarius-graphql-types"; From 1e754258f46bb985643924e020bff381e9c621b7 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 17:44:31 +0100 Subject: [PATCH 104/197] feat: enhance DFlowFunctionTriggerCard with tab registration and activation improvements --- .../function/DFlowFunctionTriggerCard.tsx | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx b/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx index ce141d10e..74772dadf 100644 --- a/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx @@ -6,7 +6,7 @@ import {useService} from "../../../utils/contextStore"; import {FileTabsService} from "../../file-tabs/FileTabs.service"; import {Card} from "../../card/Card"; import {Flex} from "../../flex/Flex"; -import {IconBolt, IconChevronDown} from "@tabler/icons-react"; +import {IconBolt, IconChevronDown, IconFile} from "@tabler/icons-react"; import {Button} from "../../button/Button"; import {DFlowTabTrigger} from "../tab/DFlowTabTrigger"; import {DFlowTypeReactiveService} from "../type"; @@ -32,12 +32,26 @@ export const DFlowFunctionTriggerCard: React.FC = const viewportWidth = useStore(s => s.width) const viewportHeight = useStore(s => s.height) + React.useEffect(() => { + fileTabsService.registerTab({ + id: definition?.id!!, + active: false, + closeable: true, + children: <> + + {definition?.names?.nodes!![0]?.content} + , + show: false, + content: + }) + }, [definition, data]) + return Starting node { flowInstance.setViewport({ x: (viewportWidth / 2) + (props.positionAbsoluteX * -1) - (width / 2), @@ -46,13 +60,7 @@ export const DFlowFunctionTriggerCard: React.FC = }, { duration: 250, }) - fileTabsService.add({ - id: id, - active: true, - closeable: true, - children: {definition?.names?.nodes!![0]?.content}, - content: - }) + fileTabsService.activateTab(definition?.id!!) }}> From 6bd9a288ad4ce266f26e9978066653d8d79a4217 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 17:44:35 +0100 Subject: [PATCH 105/197] feat: enhance DFlowTabs with flowId prop and improve tab activation logic --- src/components/d-flow/tab/DFlowTabs.tsx | 62 +++++++++++++++++++------ 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/src/components/d-flow/tab/DFlowTabs.tsx b/src/components/d-flow/tab/DFlowTabs.tsx index 6b0844fc1..0e1cb82d0 100644 --- a/src/components/d-flow/tab/DFlowTabs.tsx +++ b/src/components/d-flow/tab/DFlowTabs.tsx @@ -1,24 +1,43 @@ -import {useService, useStore} from "../../../utils/contextStore"; +import {useService, useStore} from "../../../utils"; import {FileTabsService} from "../../file-tabs/FileTabs.service"; import {FileTabs, FileTabsContent, FileTabsList, FileTabsTrigger} from "../../file-tabs/FileTabs"; import React from "react"; import {Menu, MenuContent, MenuItem, MenuPortal, MenuSeparator, MenuTrigger} from "../../menu/Menu"; import {Button} from "../../button/Button"; -import {IconChevronDown, IconDotsVertical, IconPlus} from "@tabler/icons-react"; +import {IconBolt, IconDotsVertical, IconFile, IconPlus} from "@tabler/icons-react"; import {FileTabsView} from "../../file-tabs/FileTabs.view"; -import {Text} from "../../text/Text"; import {DLayout} from "../../d-layout/DLayout"; import {ButtonGroup} from "../../button-group/ButtonGroup"; +import {Flow} from "@code0-tech/sagittarius-graphql-types"; +import {DFlowReactiveService} from "../DFlow.service"; +import {DFlowTypeReactiveService} from "../type"; +import {Text} from "../../text/Text"; +import {DFlowFunctionReactiveService} from "../function"; +import {md5} from "js-md5"; + +export interface DFlowTabsProps { + flowId: Flow['id'] +} -export const DFlowTabs = () => { +export const DFlowTabs: React.FC = (props) => { + + const {flowId} = props const fileTabsService = useService(FileTabsService) const fileTabsStore = useStore(FileTabsService) + const flowService = useService(DFlowReactiveService) + const flowStore = useStore(DFlowReactiveService) + const flowTypeService = useService(DFlowTypeReactiveService) + const flowTypeStore = useStore(DFlowTypeReactiveService) + const functionService = useService(DFlowFunctionReactiveService) + const functionStore = useStore(DFlowFunctionReactiveService) const id = React.useId() + const flow = React.useMemo(() => flowService.getById(flowId), [flowStore]) + const flowType = React.useMemo(() => flowTypeService.getById(flow?.type?.id!!), [flowTypeStore, flow]) const activeTabId = React.useMemo(() => { return fileTabsStore.find((t: any) => (t as any).active)?.id ?? fileTabsService.getActiveTab()?.id; - }, [fileTabsStore, fileTabsService]); + }, [fileTabsStore, fileTabsService]) React.useEffect(() => { setTimeout(() => { @@ -57,15 +76,28 @@ export const DFlowTabs = () => { - {fileTabsStore.map((tab: FileTabsView) => ( - { - fileTabsService.activateTab(tab.id!) - }}> + fileTabsService.activateTab(flowType?.id!!)}> + + {flowType?.names?.nodes!![0]?.content} + + + {fileTabsStore.map((tab: FileTabsView) => { + return tab.show && { + fileTabsService.activateTab(tab.id!) + }}> {tab.children} - ))} - {/*TODO: Add all available nodes*/} + })} + + {fileTabsStore.map((tab: FileTabsView) => { + return !tab.show && { + fileTabsService.activateTab(tab.id!) + }}> + {tab.children} + + })} @@ -92,8 +124,8 @@ export const DFlowTabs = () => { } > - {fileTabsStore.map((tab: FileTabsView, index: number) => ( - { + return tab.show && { > {tab.children} - ))} + })} }> {fileTabsStore.map((tab: FileTabsView) => ( From 0881363d29eada257ac2dea2e99e3a79a9e1cb37 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 17:44:38 +0100 Subject: [PATCH 106/197] feat: add flowId prop to DFlowTabs in DResizable story for improved context --- src/components/d-resizable/DResizable.stories.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/d-resizable/DResizable.stories.tsx b/src/components/d-resizable/DResizable.stories.tsx index e93a3c0b0..8c1b11c8e 100644 --- a/src/components/d-resizable/DResizable.stories.tsx +++ b/src/components/d-resizable/DResizable.stories.tsx @@ -129,7 +129,7 @@ export const Dashboard = () => { <> - + )} From ea1e42acc85458fed9cceb7e9b50b7f7467df18d Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 17:44:42 +0100 Subject: [PATCH 107/197] feat: add getById method and enhance tab registration logic in FileTabs service --- src/components/file-tabs/FileTabs.service.ts | 22 ++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/components/file-tabs/FileTabs.service.ts b/src/components/file-tabs/FileTabs.service.ts index 9159337ad..e90da5a43 100644 --- a/src/components/file-tabs/FileTabs.service.ts +++ b/src/components/file-tabs/FileTabs.service.ts @@ -7,6 +7,10 @@ export class FileTabsService extends ReactiveArrayService { super(store); } + getById(id: string): FileTabsView | undefined { + return this.values().find((item: FileTabsView) => item.id === id) + } + public clearLeft(): void { const index = this.getActiveIndex() this.access.setState(prevState => [...prevState.filter((_, index1) => { @@ -32,7 +36,10 @@ export class FileTabsService extends ReactiveArrayService { }) const tab = this.values().find((item: FileTabsView) => item.id === id); - if (tab) tab.active = true + if (tab) { + tab.active = true + tab.show = true + } this.update() } @@ -49,9 +56,16 @@ export class FileTabsService extends ReactiveArrayService { this.update() } + registerTab(value: FileTabsView) { + if (this.getById(value.id!!)) return + + super.add({...value, show: value.show ?? false}); + this.update() + + } + public add(value: FileTabsView) { - //if tab with id already exists, do not add it again and just activate the existing one - console.log("Getting active tab 1", this.access.getState()) + if (this.values().some(value1 => value1.id == value.id)) { this.activateTab(value.id!!) return @@ -62,7 +76,7 @@ export class FileTabsService extends ReactiveArrayService { item.active = false }) } - super.add(value); + super.add({...value, show: value.show ?? true}); this.update() } From 3b35933f1b01f870f8482c280d5bbcbd1e5601b1 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 17:44:46 +0100 Subject: [PATCH 108/197] feat: add optional show prop to tab interface in FileTabs component --- src/components/file-tabs/FileTabs.view.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/file-tabs/FileTabs.view.ts b/src/components/file-tabs/FileTabs.view.ts index 5071e0d85..f94c9fa61 100644 --- a/src/components/file-tabs/FileTabs.view.ts +++ b/src/components/file-tabs/FileTabs.view.ts @@ -7,4 +7,5 @@ export interface FileTabsView { content: React.ReactNode active: boolean, lastActive?: Date + show?: boolean } \ No newline at end of file From c1cf29ed27d7405652d6ef3d16894710c6af00cd Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 19:13:56 +0100 Subject: [PATCH 109/197] feat: refactor imports and enhance DFlowFunctionDefaultCard with improved dependency management --- .../d-flow/function/DFlowFunctionDefaultCard.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx index 90e2b3ba5..f565cabff 100644 --- a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx @@ -1,16 +1,16 @@ -import {Code0Component} from "../../../utils/types"; +import {Code0Component} from "../../../utils"; import {Handle, Node, NodeProps, Position, useReactFlow, useStore} from "@xyflow/react"; import React, {memo} from "react"; import {Card} from "../../card/Card"; import "./DFlowFunctionDefaultCard.style.scss"; import {Flex} from "../../flex/Flex"; -import {IconFile, IconFileLambdaFilled} from "@tabler/icons-react"; +import {IconFile} from "@tabler/icons-react"; import {Text} from "../../text/Text"; import {useService, useStore as usePictorStore} from "../../../utils/contextStore"; import {DFlowFunctionReactiveService} from "./DFlowFunction.service"; import {useFunctionValidation} from "./DFlowFunction.vaildation.hook"; -import {DFlowDataTypeReactiveService} from "../data-type/DFlowDataType.service"; -import {InspectionSeverity} from "../../../utils/inspection"; +import {DFlowDataTypeReactiveService} from "../data-type"; +import {InspectionSeverity} from "../../../utils"; import {DFlowReactiveService} from "../DFlow.service"; import {FileTabsService} from "../../file-tabs/FileTabs.service"; import {DFlowTabDefault} from "../tab/DFlowTabDefault"; @@ -85,7 +85,7 @@ export const DFlowFunctionDefaultCard: React.FC = .flatMap(p => p.trim() === "," ? [","] : p.trim() ? [p.trim()] : []) ); - const colorHash = md5(node?.id!!) + const colorHash = md5(id) const hashToHue = (md5: string): number => { // nimm z.B. 8 Hex-Zeichen = 32 Bit const int = parseInt(md5.slice(0, 8), 16) @@ -141,10 +141,12 @@ export const DFlowFunctionDefaultCard: React.FC = content: }) - }, [node, data, fileTabsStore]) + }, [node?.id, definition, data, fileTabsService]) return ( Date: Sun, 14 Dec 2025 19:14:02 +0100 Subject: [PATCH 110/197] feat: update DFlowFunctionTriggerCard to activate tab by default and show content --- .../d-flow/function/DFlowFunctionTriggerCard.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx b/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx index 74772dadf..eba00f603 100644 --- a/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionTriggerCard.tsx @@ -35,18 +35,18 @@ export const DFlowFunctionTriggerCard: React.FC = React.useEffect(() => { fileTabsService.registerTab({ id: definition?.id!!, - active: false, + active: true, closeable: true, children: <> {definition?.names?.nodes!![0]?.content} , - show: false, - content: + content: , + show: true }) - }, [definition, data]) + }, [definition?.id, data.instance, fileTabsService, definition?.names?.nodes]) - return + return Starting node Date: Sun, 14 Dec 2025 19:14:06 +0100 Subject: [PATCH 111/197] feat: enhance DFlowTabs with improved tab visibility and context menu structure --- src/components/d-flow/tab/DFlowTabs.tsx | 101 +++++++++++++++++------- 1 file changed, 74 insertions(+), 27 deletions(-) diff --git a/src/components/d-flow/tab/DFlowTabs.tsx b/src/components/d-flow/tab/DFlowTabs.tsx index 0e1cb82d0..cd667abdf 100644 --- a/src/components/d-flow/tab/DFlowTabs.tsx +++ b/src/components/d-flow/tab/DFlowTabs.tsx @@ -2,9 +2,16 @@ import {useService, useStore} from "../../../utils"; import {FileTabsService} from "../../file-tabs/FileTabs.service"; import {FileTabs, FileTabsContent, FileTabsList, FileTabsTrigger} from "../../file-tabs/FileTabs"; import React from "react"; -import {Menu, MenuContent, MenuItem, MenuPortal, MenuSeparator, MenuTrigger} from "../../menu/Menu"; +import {Menu, MenuContent, MenuItem, MenuLabel, MenuPortal, MenuSeparator, MenuTrigger} from "../../menu/Menu"; import {Button} from "../../button/Button"; -import {IconBolt, IconDotsVertical, IconFile, IconPlus} from "@tabler/icons-react"; +import { + IconArrowDown, + IconArrowUp, + IconBolt, + IconCornerDownLeft, + IconDotsVertical, + IconPlus +} from "@tabler/icons-react"; import {FileTabsView} from "../../file-tabs/FileTabs.view"; import {DLayout} from "../../d-layout/DLayout"; import {ButtonGroup} from "../../button-group/ButtonGroup"; @@ -14,6 +21,11 @@ import {DFlowTypeReactiveService} from "../type"; import {Text} from "../../text/Text"; import {DFlowFunctionReactiveService} from "../function"; import {md5} from "js-md5"; +import {Card} from "../../card/Card"; +import {Flex} from "../../flex/Flex"; +import {Badge} from "../../badge/Badge"; +import {Spacing} from "../../spacing/Spacing"; +import {ContextMenuLabel} from "../../context-menu/ContextMenu"; export interface DFlowTabsProps { flowId: Flow['id'] @@ -39,6 +51,19 @@ export const DFlowTabs: React.FC = (props) => { return fileTabsStore.find((t: any) => (t as any).active)?.id ?? fileTabsService.getActiveTab()?.id; }, [fileTabsStore, fileTabsService]) + const triggerTab = React.useMemo(() => { + if (!flowType?.id) return undefined + return fileTabsStore.find((tab: FileTabsView) => tab.id === flowType.id) + }, [fileTabsStore, flowType]) + + const visibleTabs = React.useMemo(() => { + return fileTabsStore.filter((tab: FileTabsView) => tab.show) + }, [fileTabsStore, triggerTab]) + + const hiddenTabs = React.useMemo(() => { + return fileTabsStore.filter((tab: FileTabsView) => !tab.show && tab.id !== triggerTab?.id) + }, [fileTabsStore, triggerTab]) + React.useEffect(() => { setTimeout(() => { const parent = document.querySelector("[data-id=" + '"' + id + '"' + "]") as HTMLDivElement @@ -75,29 +100,49 @@ export const DFlowTabs: React.FC = (props) => { - - fileTabsService.activateTab(flowType?.id!!)}> - - {flowType?.names?.nodes!![0]?.content} - - - {fileTabsStore.map((tab: FileTabsView) => { - return tab.show && { - fileTabsService.activateTab(tab.id!) - }}> - {tab.children} - - })} - - {fileTabsStore.map((tab: FileTabsView) => { - return !tab.show && { - fileTabsService.activateTab(tab.id!) - }}> - {tab.children} - - })} + + + Starting Node + {triggerTab && fileTabsService.activateTab(triggerTab.id!!)}> + {triggerTab.children} + } + + Opened Nodes + {visibleTabs.map((tab: FileTabsView) => ( + { + fileTabsService.activateTab(tab.id!) + }}> + {tab.children} + + ))} + + Available Node + {hiddenTabs.map((tab: FileTabsView) => ( + { + fileTabsService.activateTab(tab.id!) + }}> + {tab.children} + + ))} + + + + + + + + + move + + + + + select + + + @@ -124,12 +169,14 @@ export const DFlowTabs: React.FC = (props) => { } > - {fileTabsStore.map((tab: FileTabsView, index: number) => { + {visibleTabs.map((tab: FileTabsView, index: number) => { return tab.show && fileTabsService.delete(index)} + onClose={() => { + fileTabsService.removeTabById(tab.id!!) + }} > {tab.children} From 9021099d6e2f683bafdb55f8e1f56aaae337c382 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 19:14:10 +0100 Subject: [PATCH 112/197] feat: add removeTabById and deleteById methods to FileTabsService for enhanced tab management --- src/components/file-tabs/FileTabs.service.ts | 68 +++++++++++++++----- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/src/components/file-tabs/FileTabs.service.ts b/src/components/file-tabs/FileTabs.service.ts index e90da5a43..6505ca0bc 100644 --- a/src/components/file-tabs/FileTabs.service.ts +++ b/src/components/file-tabs/FileTabs.service.ts @@ -1,5 +1,6 @@ import {FileTabsView} from "./FileTabs.view"; import {ReactiveArrayService, ReactiveArrayStore} from "../../utils/reactiveArrayService"; +import {startTransition} from "react"; export class FileTabsService extends ReactiveArrayService { @@ -43,6 +44,22 @@ export class FileTabsService extends ReactiveArrayService { this.update() } + removeTabById(id: string) { + const tab = this.getById(id) + const index = this.values().findIndex((item: FileTabsView) => item.id === id) + if (!tab) return + if (tab.active && this.has(index - 1)) { + const previousTab = this.get(index - 1) + if (previousTab.show) this.activateTab(previousTab.id!!) + } else if (tab.active && this.has(index + 1)) { + const nextTab = this.get(index + 1) + if (nextTab.show) this.activateTab(nextTab.id!!) + } + tab.show = false + tab.active = false + this.update() + } + public delete(index: number) { const tab = this.get(index) @@ -56,28 +73,49 @@ export class FileTabsService extends ReactiveArrayService { this.update() } + deleteById(id: string) { + const index = this.values().findIndex((item: FileTabsView) => item.id === id) + + if (index !== -1) { + this.delete(index) + } + } + registerTab(value: FileTabsView) { - if (this.getById(value.id!!)) return + const nextValue = {...value, show: value.show ?? false} - super.add({...value, show: value.show ?? false}); - this.update() + startTransition(() => { + this.access.setState((prevState) => { + const existingIndex = prevState.findIndex((tab) => tab.id === nextValue.id) + + if (existingIndex !== -1) return prevState + return [...prevState, nextValue] + }) + }) } public add(value: FileTabsView) { - - if (this.values().some(value1 => value1.id == value.id)) { - this.activateTab(value.id!!) - return - } - - if (value.active) { - this.values().forEach((item: FileTabsView) => { - item.active = false + const nextValue = {...value, show: value.show ?? true} + + startTransition(() => { + this.access.setState((prevState) => { + const existingIndex = prevState.findIndex((tab) => tab.id === nextValue.id) + const nextState = prevState.map((tab) => ({...tab, active: nextValue.active ? false : tab.active})) + + if (existingIndex !== -1) { + nextState[existingIndex] = { + ...nextState[existingIndex], + ...nextValue, + active: nextValue.active ?? nextState[existingIndex].active, + show: nextValue.show ?? nextState[existingIndex].show, + } + return nextState + } + + return [...nextState, nextValue] }) - } - super.add({...value, show: value.show ?? true}); - this.update() + }) } public getActiveTab(): FileTabsView | undefined { From 0b829675abfa96e8d7301c831b48cbd25e844087 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 19:21:18 +0100 Subject: [PATCH 113/197] feat: enhance DFlowTabs with improved menu structure and alignment for better usability --- src/components/d-flow/tab/DFlowTabs.tsx | 61 ++++++++++++++----------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/src/components/d-flow/tab/DFlowTabs.tsx b/src/components/d-flow/tab/DFlowTabs.tsx index cd667abdf..6268021e7 100644 --- a/src/components/d-flow/tab/DFlowTabs.tsx +++ b/src/components/d-flow/tab/DFlowTabs.tsx @@ -4,28 +4,18 @@ import {FileTabs, FileTabsContent, FileTabsList, FileTabsTrigger} from "../../fi import React from "react"; import {Menu, MenuContent, MenuItem, MenuLabel, MenuPortal, MenuSeparator, MenuTrigger} from "../../menu/Menu"; import {Button} from "../../button/Button"; -import { - IconArrowDown, - IconArrowUp, - IconBolt, - IconCornerDownLeft, - IconDotsVertical, - IconPlus -} from "@tabler/icons-react"; +import {IconArrowDown, IconArrowUp, IconCornerDownLeft, IconDotsVertical, IconPlus} from "@tabler/icons-react"; import {FileTabsView} from "../../file-tabs/FileTabs.view"; import {DLayout} from "../../d-layout/DLayout"; import {ButtonGroup} from "../../button-group/ButtonGroup"; import {Flow} from "@code0-tech/sagittarius-graphql-types"; import {DFlowReactiveService} from "../DFlow.service"; import {DFlowTypeReactiveService} from "../type"; -import {Text} from "../../text/Text"; import {DFlowFunctionReactiveService} from "../function"; -import {md5} from "js-md5"; import {Card} from "../../card/Card"; import {Flex} from "../../flex/Flex"; import {Badge} from "../../badge/Badge"; import {Spacing} from "../../spacing/Spacing"; -import {ContextMenuLabel} from "../../context-menu/ContextMenu"; export interface DFlowTabsProps { flowId: Flow['id'] @@ -100,12 +90,13 @@ export const DFlowTabs: React.FC = (props) => { - + Starting Node - {triggerTab && fileTabsService.activateTab(triggerTab.id!!)}> - {triggerTab.children} - } + {triggerTab && + fileTabsService.activateTab(triggerTab.id!!)}> + {triggerTab.children} + } Opened Nodes {visibleTabs.map((tab: FileTabsView) => ( @@ -127,7 +118,7 @@ export const DFlowTabs: React.FC = (props) => { ))} - + @@ -142,7 +133,7 @@ export const DFlowTabs: React.FC = (props) => { select - + @@ -154,15 +145,33 @@ export const DFlowTabs: React.FC = (props) => { - - fileTabsService.clear()}>Close all tabs - fileTabsService.clearWithoutActive()}>Close other - tabs - - fileTabsService.clearLeft()}>Close all tabs to - left - fileTabsService.clearRight()}>Close all tabs to - right + + + fileTabsService.clear()}>Close all tabs + fileTabsService.clearWithoutActive()}>Close other + tabs + + fileTabsService.clearLeft()}>Close all tabs to + left + fileTabsService.clearRight()}>Close all tabs to + right + + + + + + + + + move + + + + + select + + + From afb955176f09a179839d9b4fba5ce01474a247f1 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 20:10:03 +0100 Subject: [PATCH 114/197] feat: add sourceHandle to edges in DFlow.edges.hook for improved edge identification --- src/components/d-flow/DFlow.edges.hook.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/d-flow/DFlow.edges.hook.ts b/src/components/d-flow/DFlow.edges.hook.ts index eea8fbf3d..62373c306 100644 --- a/src/components/d-flow/DFlow.edges.hook.ts +++ b/src/components/d-flow/DFlow.edges.hook.ts @@ -131,6 +131,7 @@ export const useFlowEdges = (flowId: Flow['id']): Edge[] => { edges.push({ id: `${fnId}-${groupId}-param-${param.id}`, source: fnId, + sourceHandle: `param-${param.id}`, target: groupId, deletable: false, selectable: false, From 33a1f2b860a59f211daf86c62645a3a7f3dda854 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 20:10:10 +0100 Subject: [PATCH 115/197] feat: update DFlowEdge to adjust stepPosition for improved edge rendering --- src/components/d-flow/edge/DFlowEdge.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/d-flow/edge/DFlowEdge.tsx b/src/components/d-flow/edge/DFlowEdge.tsx index c72050e60..a8dcb2ca4 100644 --- a/src/components/d-flow/edge/DFlowEdge.tsx +++ b/src/components/d-flow/edge/DFlowEdge.tsx @@ -41,7 +41,7 @@ export const DFlowEdge: React.FC = memo((props) => { targetY, targetPosition: data?.isParameter ? Position.Right : Position.Top, borderRadius: 16, - centerY: targetY - 25, + stepPosition: 0.5, }) const color = data?.color ?? "#ffffff" From 35bd226fd1e6c0d46c7233099cef06cdf88d6a57 Mon Sep 17 00:00:00 2001 From: nicosammito Date: Sun, 14 Dec 2025 20:10:17 +0100 Subject: [PATCH 116/197] feat: enhance DFlowFunctionDefaultCard with improved handle rendering and parameter definition handling --- .../function/DFlowFunctionDefaultCard.tsx | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx index f565cabff..d229fd861 100644 --- a/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx +++ b/src/components/d-flow/function/DFlowFunctionDefaultCard.tsx @@ -1,4 +1,4 @@ -import {Code0Component} from "../../../utils"; +import {Code0Component, InspectionSeverity} from "../../../utils"; import {Handle, Node, NodeProps, Position, useReactFlow, useStore} from "@xyflow/react"; import React, {memo} from "react"; import {Card} from "../../card/Card"; @@ -10,7 +10,6 @@ import {useService, useStore as usePictorStore} from "../../../utils/contextStor import {DFlowFunctionReactiveService} from "./DFlowFunction.service"; import {useFunctionValidation} from "./DFlowFunction.vaildation.hook"; import {DFlowDataTypeReactiveService} from "../data-type"; -import {InspectionSeverity} from "../../../utils"; import {DFlowReactiveService} from "../DFlow.service"; import {FileTabsService} from "../../file-tabs/FileTabs.service"; import {DFlowTabDefault} from "../tab/DFlowTabDefault"; @@ -44,6 +43,7 @@ export const DFlowFunctionDefaultCard: React.FC = const functionService = useService(DFlowFunctionReactiveService) const functionStore = usePictorStore(DFlowFunctionReactiveService) const dataTypeService = useService(DFlowDataTypeReactiveService) + const dataTypeStore = usePictorStore(DFlowDataTypeReactiveService) const edges = useStore(s => s.edges); const definition = React.useMemo(() => functionService.getById(data.node.functionDefinition?.id!!), [functionStore, data]) @@ -97,6 +97,7 @@ export const DFlowFunctionDefaultCard: React.FC = const parameterDefinition = definition?.parameterDefinitions?.find(pd => pd.id == p?.id) return parameterDefinition?.identifier == item }) + const paramDefinition = definition?.parameterDefinitions?.find(pd => pd.id == param?.id) if (param) { switch (param?.value?.__typename) { @@ -114,10 +115,18 @@ export const DFlowFunctionDefaultCard: React.FC =
case "NodeFunction": const hash = md5(`${id}-param-${JSON.stringify(param)}`) - return + return {String(functionService.getById(param?.value?.functionDefinition?.id)?.names?.nodes!![0]?.content)} + } return @@ -163,11 +172,6 @@ export const DFlowFunctionDefaultCard: React.FC = fileTabsService.activateTab(node?.id!!) }} style={{position: "relative"}}> - - - {displayMessage} - - = position={data.isParameter ? Position.Right : Position.Top} /> - {node?.parameters?.nodes?.map(param => { - const renderHandle = param?.value?.__typename === "NodeFunction" && isParamConnected(param?.id!!) - return renderHandle &&