Skip to content

Commit 31eb740

Browse files
author
Nico Sammito
authored
Merge pull request #104 from code0-tech/feat/#102
Therefore if a user clicks onto a node the references and the corresponding nodes used inside are highlighted in some sort.
2 parents bada362 + fd00ebc commit 31eb740

4 files changed

Lines changed: 177 additions & 53 deletions

File tree

src/packages/ce/src/flow/components/builder/FlowBuilderComponent.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ReactFlowProvider,
88
useEdgesState,
99
useNodesState,
10-
useReactFlow,
10+
useReactFlow, useStore,
1111
useUpdateNodeInternals,
1212
ViewportPortal
1313
} from "@xyflow/react";
@@ -19,7 +19,7 @@ import {Flow, type Namespace, type NamespaceProject} from "@code0-tech/sagittari
1919
import {LineWobble} from 'ldrs/react'
2020
import 'ldrs/react/LineWobble.css'
2121
import {useFlowNodes} from "@edition/flow/hooks/Flow.nodes.hook";
22-
import {Code0ComponentProps, mergeCode0Props, Spacing, Text} from "@code0-tech/pictor";
22+
import {Code0ComponentProps, mergeCode0Props, Spacing, Text, useService} from "@code0-tech/pictor";
2323
import {FunctionNodeDefaultComponent} from "@edition/function/components/nodes/FunctionNodeDefaultComponent";
2424
import {FunctionNodeGroupComponent} from "@edition/function/components/nodes/FunctionNodeGroupComponent";
2525
import {FunctionNodeTriggerComponent} from "@edition/function/components/nodes/FunctionNodeTriggerComponent";
@@ -28,6 +28,7 @@ import {FlowPanelSizeComponent} from "@edition/flow/components/panels/FlowPanelS
2828
import {FlowPanelLayoutComponent} from "@edition/flow/components/panels/FlowPanelLayoutComponent";
2929
import {FlowPanelControlComponent} from "@edition/flow/components/panels/FlowPanelControlComponent";
3030
import {FlowPanelUpdateComponent} from "@edition/flow/components/panels/FlowPanelUpdateComponent";
31+
import {FileTabsService} from "@code0-tech/pictor/dist/components/file-tabs/FileTabs.service";
3132

3233
/**
3334
* Dynamically layouts a tree of nodes and their parameter nodes for a flow-based editor.
@@ -615,12 +616,14 @@ export const FlowBuilderComponent: React.FC<FlowBuilderProps> = (props) => {
615616
const InternalFlowBuilder: React.FC<FlowBuilderProps> = (props) => {
616617
const {flowId, namespaceId, projectId, ...rest} = props
617618

619+
const { setCenter, getInternalNode, getZoom } = useReactFlow();
620+
const fileTabsService = useService(FileTabsService)
621+
618622
const nodeTypes = React.useMemo(() => ({
619623
default: FunctionNodeDefaultComponent,
620624
group: FunctionNodeGroupComponent,
621625
trigger: FunctionNodeTriggerComponent,
622626
}), [])
623-
624627
const edgeTypes = React.useMemo(() => ({
625628
default: FlowBuilderEdgeComponent,
626629
}), [])
@@ -725,6 +728,26 @@ const InternalFlowBuilder: React.FC<FlowBuilderProps> = (props) => {
725728
{...mergeCode0Props("flow", rest)}
726729
data-tree-visibility={showTree}
727730
proOptions={{hideAttribution: true}}
731+
onNodeClick={(_, clickedNode) => {
732+
733+
const node = getInternalNode(clickedNode.id);
734+
735+
if (node && node.measured.width && node.measured.height) {
736+
const centerX = node.internals.positionAbsolute.x + node.measured.width / 2;
737+
const centerY = node.internals.positionAbsolute.y + node.measured.height / 2;
738+
739+
const currentZoom = getZoom();
740+
741+
setCenter(centerX, centerY, {
742+
zoom: Math.max(currentZoom, 1),
743+
duration: 250,
744+
}).then();
745+
}
746+
fileTabsService.activateTab(node?.id ?? "")
747+
}}
748+
onPaneClick={_ => {
749+
fileTabsService.activateTab("")
750+
}}
728751
nodes={nodes}
729752
edges={edges}
730753
panOnDrag={showTree}

src/packages/ce/src/function/components/nodes/FunctionNodeComponent.style.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
border: 1px solid helpers.borderColor();
77
box-shadow: none;
88
text-wrap: nowrap;
9+
position: relative;
10+
transition: opacity .25s ease-in-out;
911

1012
&__handle {
1113
border: none !important;
@@ -26,6 +28,11 @@
2628
}
2729
}
2830

31+
&--notReferenced {
32+
transition: opacity .25s ease-in-out;
33+
opacity: 0.5 !important;
34+
}
35+
2936
&--active {
3037
box-shadow: 0 0 0 1px rgba(variables.$info, .5);
3138
}

src/packages/ce/src/function/components/nodes/FunctionNodeDefaultComponent.tsx

Lines changed: 100 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,24 @@
1-
import {Handle, Node, NodeProps, Position, useReactFlow, useStore} from "@xyflow/react";
1+
import {Handle, Node, NodeProps, Position, useStore} from "@xyflow/react";
22
import React, {CSSProperties, memo} from "react";
33
import "./FunctionNodeComponent.style.scss";
44
import {FunctionNodeComponentProps} from "./FunctionNodeComponent";
55
import {FileTabsService} from "@code0-tech/pictor/dist/components/file-tabs/FileTabs.service";
6-
import {
7-
Badge, Card,
8-
Flex, Text, underlineBySeverity,
9-
useService,
10-
useStore as usePictorStore
11-
} from "@code0-tech/pictor";
6+
import {Badge, Card, Flex, Text, underlineBySeverity, useService, useStore as usePictorStore} from "@code0-tech/pictor";
127
import {useNodeValidation} from "@edition/flow/hooks/NodeValidation.hook";
13-
import {IconNote} from "@tabler/icons-react";
8+
import {IconNote, IconVariable} from "@tabler/icons-react";
149
import {FlowService} from "@edition/flow/services/Flow.service";
1510
import {FunctionService} from "@edition/function/services/Function.service";
1611
import {LiteralBadgeComponent} from "@edition/datatype/components/badges/LiteralBadgeComponent";
1712
import {ReferenceBadgeComponent} from "@edition/datatype/components/badges/ReferenceBadgeComponent";
1813
import {NodeBadgeComponent} from "@edition/datatype/components/badges/NodeBadgeComponent";
1914
import {FunctionFileDefaultComponent} from "@edition/function/components/files/FunctionFileDefaultComponent";
15+
import {NodeFunction} from "@code0-tech/sagittarius-graphql-types";
2016

2117
export type FunctionNodeDefaultComponentProps = NodeProps<Node<FunctionNodeComponentProps>>
2218

2319
export const FunctionNodeDefaultComponent: React.FC<FunctionNodeDefaultComponentProps> = memo((props) => {
24-
const {data, id, width = 0, height = 0} = props
20+
const {data, id} = props
2521

26-
const viewportWidth = useStore(s => s.width);
27-
const viewportHeight = useStore(s => s.height);
28-
const flowInstance = useReactFlow()
2922
const fileTabsService = useService(FileTabsService)
3023
const fileTabsStore = usePictorStore(FileTabsService)
3124
const flowService = useService(FlowService)
@@ -36,6 +29,7 @@ export const FunctionNodeDefaultComponent: React.FC<FunctionNodeDefaultComponent
3629
const node = React.useMemo(() => flowService.getNodeById(data.flowId, data.nodeId), [flowStore, data])
3730
const definition = React.useMemo(() => node ? functionService.getById(node.functionDefinition?.id!!) : undefined, [functionStore, data, node])
3831
const validation = useNodeValidation(data.nodeId, data.flowId)
32+
3933
const activeTabId = React.useMemo(() => {
4034
return fileTabsService.getActiveTab()?.id
4135
}, [fileTabsStore, fileTabsService]);
@@ -110,9 +104,9 @@ export const FunctionNodeDefaultComponent: React.FC<FunctionNodeDefaultComponent
110104
}), [flowStore, functionStore, data, definition])
111105

112106
React.useEffect(() => {
113-
if (!node?.id) return
107+
if (!node?.id || !id) return
114108
fileTabsService.registerTab({
115-
id: node.id,
109+
id: id,
116110
active: false,
117111
closeable: true,
118112
children: <>
@@ -121,7 +115,23 @@ export const FunctionNodeDefaultComponent: React.FC<FunctionNodeDefaultComponent
121115
</>,
122116
content: <FunctionFileDefaultComponent flowId={props.data.flowId} node={node}/>
123117
})
124-
}, [node?.id, definition, data])
118+
}, [])
119+
120+
const isReferenced = React.useMemo(() => {
121+
122+
const activeNode = flowService.getNodeById(data.flowId, activeTabId as NodeFunction['id'])
123+
const isActiveNodeReferencingCurrentNode = activeNode?.parameters?.nodes?.some(p => p?.value?.__typename === "ReferenceValue" && p.value.nodeFunctionId === node?.id)
124+
const hasReferences = activeNode?.parameters?.nodes?.some(p => p?.value?.__typename === "ReferenceValue")
125+
126+
if (isActiveNodeReferencingCurrentNode) {
127+
return true
128+
} else if (hasReferences && !isActiveNodeReferencingCurrentNode && activeTabId !== data.nodeId) {
129+
return false
130+
}
131+
132+
return undefined
133+
134+
}, [flowStore, activeTabId, data.flowId, node])
125135

126136
return (
127137
<Card
@@ -130,19 +140,11 @@ export const FunctionNodeDefaultComponent: React.FC<FunctionNodeDefaultComponent
130140
paddingSize={"xs"}
131141
py={"0.35"}
132142
outline={firstItem.id === id}
133-
borderColor={activeTabId == node?.id ? "info" : undefined}
134-
className={`d-flow-node ${activeTabId == node?.id ? "d-flow-node--active" : undefined}`}
135-
color={"primary"}
136-
onClick={() => {
137-
flowInstance.setViewport({
138-
x: (viewportWidth / 2) + (props.positionAbsoluteX * -1) - (width / 2),
139-
y: (viewportHeight / 2) + (props.positionAbsoluteY * -1) - (height / 2),
140-
zoom: 1
141-
}, {
142-
duration: 250,
143-
})
144-
fileTabsService.activateTab(node?.id!!)
145-
}} style={{position: "relative"}}>
143+
borderColor={activeTabId == id ? "info" : undefined}
144+
className={`d-flow-node ${activeTabId == id ? "d-flow-node--active" : ""} ${isReferenced === false ? "d-flow-node--notReferenced" : ""}`}
145+
color={"primary"} style={{
146+
...(isReferenced === true ? {boxShadow: `0 0 5rem 0 ${withAlpha(data.color, 0.25)}`} : {}),
147+
}}>
146148

147149
<Handle
148150
isConnectable={false}
@@ -161,10 +163,80 @@ export const FunctionNodeDefaultComponent: React.FC<FunctionNodeDefaultComponent
161163
className={"d-flow-node__handle d-flow-node__handle--source"}
162164
position={data.isParameter ? Position.Left : Position.Bottom}
163165
/>
166+
167+
{
168+
isReferenced === true ? (
169+
<div className={"d-flow-node__isReferenced"} style={{
170+
position: "absolute",
171+
top: "50%",
172+
left: firstItem.id === id ? "-0.733rem" : "-0.5rem",
173+
transform: "translate(-100%, -50%)",
174+
display: "flex"
175+
}}>
176+
<IconVariable className={"d-flow-node__isReferenced-icon"} color={data.color} size={13}/>
177+
</div>
178+
) : null
179+
}
180+
181+
164182
<Flex align={"center"} style={{gap: "0.7rem"}}>
165183
<IconNote color={data.color} size={16}/>
166184
<Text size={"md"}>{displayMessage}</Text>
167185
</Flex>
168186
</Card>
169187
);
170188
})
189+
190+
type RGBA = {
191+
r: number
192+
g: number
193+
b: number
194+
a: number
195+
}
196+
197+
const clamp01 = (v: number) => Math.min(Math.max(v, 0), 1)
198+
199+
const parseCssColorToRgba = (color: string): RGBA => {
200+
if (typeof document === "undefined") {
201+
return {r: 0, g: 0, b: 0, a: 1}
202+
}
203+
204+
const el = document.createElement("span")
205+
el.style.color = color
206+
document.body.appendChild(el)
207+
208+
const computed = getComputedStyle(el).color
209+
document.body.removeChild(el)
210+
211+
const match = computed.match(
212+
/rgba?\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)(?:\s*,\s*([\d.]+))?\s*\)/
213+
)
214+
215+
if (!match) {
216+
return {r: 0, g: 0, b: 0, a: 1}
217+
}
218+
219+
return {
220+
r: Math.round(Number(match[1])),
221+
g: Math.round(Number(match[2])),
222+
b: Math.round(Number(match[3])),
223+
a: match[4] !== undefined ? Number(match[4]) : 1,
224+
}
225+
}
226+
227+
const mixColorRgb = (color: string, level: number) => {
228+
const w = clamp01(level * 0.1)
229+
230+
const c1 = parseCssColorToRgba(color)
231+
const c2 = parseCssColorToRgba("#070514")
232+
233+
const mix = (a: number, b: number) =>
234+
Math.round(a * (1 - w) + b * w)
235+
236+
return `rgb(${mix(c1.r, c2.r)}, ${mix(c1.g, c2.g)}, ${mix(c1.b, c2.b)})`
237+
}
238+
239+
const withAlpha = (color: string, alpha: number) => {
240+
const c = parseCssColorToRgba(color)
241+
return `rgba(${c.r}, ${c.g}, ${c.b}, ${clamp01(alpha)})`
242+
}

src/packages/ce/src/function/components/nodes/FunctionNodeTriggerComponent.tsx

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import React, {memo} from "react";
2-
import {Handle, Node, NodeProps, Position, useReactFlow, useStore} from "@xyflow/react";
2+
import {Handle, Node, NodeProps, Position} from "@xyflow/react";
33
import {Badge, Card, Flex, Text, useService, useStore as usePictorStore} from "@code0-tech/pictor";
44
import {FunctionNodeComponentProps} from "@edition/function/components/nodes/FunctionNodeComponent";
55
import {FileTabsService} from "@code0-tech/pictor/dist/components/file-tabs/FileTabs.service";
66
import {FlowTypeService} from "@edition/flowtype/services/FlowType.service";
77
import {FlowService} from "@edition/flow/services/Flow.service";
8-
import {IconBolt} from "@tabler/icons-react";
8+
import {IconBolt, IconVariable} from "@tabler/icons-react";
99
import {FunctionFileTriggerComponent} from "@edition/function/components/files/FunctionFileTriggerComponent";
10+
import {NodeFunction} from "@code0-tech/sagittarius-graphql-types";
1011

1112

1213
export type FunctionNodeTriggerComponentProps = NodeProps<Node<FunctionNodeComponentProps>>
@@ -15,7 +16,7 @@ export const FunctionNodeTriggerComponent: React.FC<FunctionNodeTriggerComponent
1516

1617
const {data, id} = props
1718
const fileTabsService = useService(FileTabsService)
18-
const flowInstance = useReactFlow()
19+
const fileTabsStore = usePictorStore(FileTabsService)
1920
const flowTypeService = useService(FlowTypeService)
2021
const flowTypeStore = usePictorStore(FlowTypeService)
2122
const flowService = useService(FlowService)
@@ -24,15 +25,10 @@ export const FunctionNodeTriggerComponent: React.FC<FunctionNodeTriggerComponent
2425
const flow = React.useMemo(() => flowService.getById(data.flowId), [flowStore, data])
2526
const definition = React.useMemo(() => flow ? flowTypeService.getById(flow.type?.id) : undefined, [flowTypeStore, flow])
2627

27-
const width = props.width ?? 0
28-
const height = props.height ?? 0
29-
const viewportWidth = useStore(s => s.width)
30-
const viewportHeight = useStore(s => s.height)
31-
3228
React.useEffect(() => {
33-
if (!definition?.id || !flow) return
29+
if (!id || !flow) return
3430
fileTabsService.registerTab({
35-
id: definition?.id!!,
31+
id: id,
3632
active: true,
3733
closeable: true,
3834
children: <>
@@ -42,24 +38,36 @@ export const FunctionNodeTriggerComponent: React.FC<FunctionNodeTriggerComponent
4238
content: <FunctionFileTriggerComponent instance={flow}/>,
4339
show: true
4440
})
45-
}, [definition, data.instance, fileTabsService, flow])
41+
}, [])
42+
43+
const activeTabId = React.useMemo(
44+
() => fileTabsService.getActiveTab()?.id,
45+
[fileTabsStore, fileTabsService]
46+
);
47+
48+
const isReferenced = React.useMemo(() => {
49+
50+
const activeNode = flowService.getNodeById(data.flowId, activeTabId as NodeFunction['id'])
51+
const isActiveNodeReferencingCurrentNode = activeNode?.parameters?.nodes?.some(p => p?.value?.__typename === "ReferenceValue" && !p.value.nodeFunctionId)
52+
const hasReferences = activeNode?.parameters?.nodes?.some(p => p?.value?.__typename === "ReferenceValue")
53+
54+
if (isActiveNodeReferencingCurrentNode) {
55+
return true
56+
} else if (hasReferences && !isActiveNodeReferencingCurrentNode && activeTabId !== data.nodeId) {
57+
return false
58+
}
59+
60+
return undefined
61+
62+
}, [flowStore, activeTabId, data.flowId])
4663

4764
return <Card variant={"normal"}
4865
color={"info"}
4966
paddingSize={"xs"}
5067
key={id}
5168
data-flow-refernce={id}
52-
className={`d-flow-node ${fileTabsService.getActiveTab()?.id == definition?.id ? "d-flow-node--active" : undefined}`}
53-
onClick={() => {
54-
flowInstance.setViewport({
55-
x: (viewportWidth / 2) + (props.positionAbsoluteX * -1) - (width / 2),
56-
y: (viewportHeight / 2) + (props.positionAbsoluteY * -1) - (height / 2),
57-
zoom: 1
58-
}, {
59-
duration: 250,
60-
})
61-
fileTabsService.activateTab(definition?.id!!)
62-
}}>
69+
className={`d-flow-node ${fileTabsService.getActiveTab()?.id == id ? "d-flow-node--active" : undefined}`}
70+
style={{...(isReferenced === true ? {boxShadow: `0 0 5rem 0 rgba(112, 255, 178, 0.25)`} : {}),}}>
6371

6472
<Badge color={"info"}
6573
pos={"absolute"}
@@ -76,6 +84,20 @@ export const FunctionNodeTriggerComponent: React.FC<FunctionNodeTriggerComponent
7684
</Text>
7785
</Flex>
7886

87+
{
88+
isReferenced === true ? (
89+
<div className={"d-flow-node__isReferenced"} style={{
90+
position: "absolute",
91+
top: "50%",
92+
left: "-0.5rem",
93+
transform: "translate(-100%, -50%)",
94+
display: "flex"
95+
}}>
96+
<IconVariable className={"d-flow-node__isReferenced-icon"} color={"#70ffb2"} size={13}/>
97+
</div>
98+
) : null
99+
}
100+
79101
<Handle
80102
isConnectable={false}
81103
type="source"

0 commit comments

Comments
 (0)