Skip to content

Commit

Permalink
feat(Flow): decouple flow from node groups
Browse files Browse the repository at this point in the history
  • Loading branch information
plagoa authored and MEsteves22 committed Nov 10, 2023
1 parent cdfaca5 commit 8a5ca52
Show file tree
Hide file tree
Showing 13 changed files with 395 additions and 67 deletions.
23 changes: 18 additions & 5 deletions packages/lab/src/components/Flow/Node/Node.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SyntheticEvent, isValidElement, useState } from "react";
import React, { SyntheticEvent, isValidElement, useState } from "react";

import {
ExtractNames,
Expand All @@ -22,6 +22,13 @@ export { staticClasses as flowNodeClasses };
// TODO How to include here the types from the parent component?
export type HvFlowNodeClasses = ExtractNames<typeof useClasses>;

export type HvFlowNodeDefaults = {
title?: string;
subTitle?: string;
color?: string;
icon?: React.ReactNode;
};

export interface HvFlowNodeProps<T = any>
extends Omit<HvFlowBaseNodeProps<T>, "classes"> {
/** Node description */
Expand All @@ -36,6 +43,8 @@ export interface HvFlowNodeProps<T = any>
expanded?: boolean;
/** Node parameters */
params?: HvFlowNodeParam[];
/** A set of node default values for when there are no groups to fetch this data from. */
nodeDefaults?: HvFlowNodeDefaults;
/** A Jss Object used to override or extend the styles applied to the component. */
classes?: HvFlowNodeClasses | HvFlowBaseNodeProps<T>["classes"];
}
Expand All @@ -53,6 +62,7 @@ export const HvFlowNode = ({
maxVisibleActions = 1,
expanded = false,
params,
nodeDefaults,
classes: classesProp,
children,
...props
Expand All @@ -63,13 +73,16 @@ export const HvFlowNode = ({

const { nodeGroups, nodeTypes, defaultActions } = useFlowContext();
const groupId = nodeTypes?.[type].meta?.groupId;
const subtitle = nodeTypes?.[type].meta?.label;
const groupLabel = groupId && nodeGroups && nodeGroups[groupId].label;
const subtitle = nodeTypes?.[type].meta?.label || nodeDefaults?.subTitle;
const groupLabel =
(groupId && nodeGroups && nodeGroups[groupId].label) || nodeDefaults?.title;

const inputs = nodeTypes?.[type]?.meta?.inputs;
const outputs = nodeTypes?.[type]?.meta?.outputs;
const icon = groupId && nodeGroups && nodeGroups[groupId].icon;
const colorProp = groupId && nodeGroups && nodeGroups[groupId].color;
const icon =
(groupId && nodeGroups && nodeGroups[groupId].icon) || nodeDefaults?.icon;
const colorProp =
(groupId && nodeGroups && nodeGroups[groupId].color) || nodeDefaults?.color;
const color = getColor(colorProp);

const actsVisible = actions?.slice(0, maxVisibleActions);
Expand Down
3 changes: 3 additions & 0 deletions packages/lab/src/components/Flow/Sidebar/Sidebar.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ export const { staticClasses, useClasses } = createClasses("HvFlowSidebar", {
gap: theme.space.sm,
listStyleType: "none",
},
nodeType: {
marginBottom: theme.space.xs,
},
});
113 changes: 76 additions & 37 deletions packages/lab/src/components/Flow/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ import { staticClasses, useClasses } from "./Sidebar.styles";
import { HvFlowSidebarGroup } from "./SidebarGroup";
import { useFlowContext } from "../hooks";
import { buildGroups } from "./utils";
import { HvFlowSidebarGroupItem } from "./SidebarGroup/SidebarGroupItem";
import {
HvFlowDraggableSidebarGroupItem,
HvFlowSidebarGroupItem,
} from "./SidebarGroup/SidebarGroupItem";
import { HvFlowNodeGroup } from "../types";

export { staticClasses as flowSidebarClasses };

Expand All @@ -51,6 +55,8 @@ export interface HvFlowSidebarProps
* More information can be found in the [Dnd Kit documentation](https://docs.dndkit.com/api-documentation/draggable/drag-overlay).
*/
dragOverlayProps?: DragOverlayProps;
/** Props to be applied to the default nodes group. */
defaultGroupProps?: HvFlowNodeGroup;
}

const DEFAULT_LABELS: HvFlowSidebarProps["labels"] = {
Expand All @@ -69,18 +75,20 @@ export const HvFlowSidebar = ({
classes: classesProp,
labels: labelsProps,
dragOverlayProps,
defaultGroupProps,
...others
}: HvFlowSidebarProps) => {
const { classes } = useClasses(classesProp);

const { nodeGroups, nodeTypes, setExpandedNodeGroups } = useFlowContext();

const unfilteredGroups = useMemo(
() => buildGroups(nodeGroups, nodeTypes),
[nodeGroups, nodeTypes]
() => buildGroups(nodeGroups, nodeTypes, defaultGroupProps),
[nodeGroups, nodeTypes, defaultGroupProps]
);

const [groups, setGroups] = useState(unfilteredGroups);
const [ndTypes, setNdTypes] = useState(nodeTypes);
const [draggingLabel, setDraggingLabel] = useState(undefined);

useEffect(() => {
Expand Down Expand Up @@ -114,28 +122,42 @@ export const HvFlowSidebar = ({
});

const handleSearch: HvInputProps["onChange"] = (event, value) => {
const gps = value
? Object.entries(unfilteredGroups).reduce((acc, curr) => {
// Filter nodes by search
const filteredNodes = curr[1].nodes.filter((obj) =>
obj.label.toLocaleLowerCase().includes(value.toLocaleLowerCase())
);
const nodesCount = filteredNodes.length;

// Only show groups with nodes
if (nodesCount > 0) {
acc[curr[0]] = {
...curr[1],
nodes: filteredNodes,
};
}

return acc;
}, {})
: unfilteredGroups;

setGroups(gps);
setExpandedNodeGroups?.(value ? Object.keys(gps) : []);
if (nodeGroups) {
const gps = value
? Object.entries(unfilteredGroups).reduce((acc, curr) => {
// Filter nodes by search
const filteredNodes = curr[1].nodes.filter((obj) =>
obj.label.toLocaleLowerCase().includes(value.toLocaleLowerCase())
);
const nodesCount = filteredNodes.length;

// Only show groups with nodes
if (nodesCount > 0) {
acc[curr[0]] = {
...curr[1],
nodes: filteredNodes,
};
}

return acc;
}, {})
: unfilteredGroups;

setGroups(gps);
setExpandedNodeGroups?.(value ? Object.keys(gps) : []);
} else if (nodeTypes) {
const filteredNodeTypes = {};
for (const [key, node] of Object.entries(nodeTypes)) {
if (
node.meta?.label
.toLocaleLowerCase()
.includes(value.toLocaleLowerCase())
) {
filteredNodeTypes[key] = node;
}
}
setNdTypes(value ? filteredNodeTypes : nodeTypes);
}
};

const handleDebouncedSearch = debounce(handleSearch, 500);
Expand Down Expand Up @@ -172,23 +194,40 @@ export const HvFlowSidebar = ({
onChange={handleDebouncedSearch}
inputProps={{ autoComplete: "off" }}
/>
<ul id={groupsElementId} className={classes.groupsContainer}>
{Object.entries(groups).map((obj) => {
{nodeGroups ? (
<ul id={groupsElementId} className={classes.groupsContainer}>
{Object.entries(groups).map((obj) => {
return (
<HvFlowSidebarGroup
key={obj[0]}
id={obj[0]}
expandButtonProps={{
"aria-label": labels?.expandGroupButtonAriaLabel,
}}
itemProps={{
"aria-roledescription": labels?.itemAriaRoleDescription,
}}
{...obj[1]}
/>
);
})}
</ul>
) : (
ndTypes &&
Object.entries(ndTypes).map((obj) => {
return (
<HvFlowSidebarGroup
<HvFlowDraggableSidebarGroupItem
key={obj[0]}
id={obj[0]}
expandButtonProps={{
"aria-label": labels?.expandGroupButtonAriaLabel,
}}
itemProps={{
"aria-roledescription": labels?.itemAriaRoleDescription,
}}
{...obj[1]}
type={obj[0]}
label={obj[1]?.meta?.label || ""}
data={obj[1]?.meta?.data}
aria-roledescription={labels?.itemAriaRoleDescription}
className={classes.nodeType}
/>
);
})}
</ul>
})
)}
</div>
</div>
</HvDrawer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@ export { staticClasses as flowSidebarGroupClasses };

export type HvFlowSidebarGroupClasses = ExtractNames<typeof useClasses>;

export type HvFlowSidebarGroupNodes = {
export type HvFlowSidebarGroupNode = {
type: string;
label: string;
data?: unknown;
}[];
};

export type HvFlowSidebarGroupNodes = HvFlowSidebarGroupNode[];

export interface HvFlowSidebarGroupProps extends HvFlowNodeGroup {
/** Group id. */
Expand Down
58 changes: 39 additions & 19 deletions packages/lab/src/components/Flow/Sidebar/utils.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,55 @@
import { HvFlowContextValue } from "../FlowContext";
import { HvFlowNodeGroup } from "../types";
import { HvFlowSidebarGroupNodes } from "./SidebarGroup";
import {
HvFlowSidebarGroupNodes,
HvFlowSidebarGroupNode,
} from "./SidebarGroup";

export const buildGroups = (
nodeGroups: HvFlowContextValue["nodeGroups"],
nodeTypes: HvFlowContextValue["nodeTypes"]
nodeTypes: HvFlowContextValue["nodeTypes"],
defaultGroupProps?: HvFlowNodeGroup
): {
[key: string]: HvFlowNodeGroup & { nodes: HvFlowSidebarGroupNodes };
} => {
if (nodeGroups) {
const groups = Object.entries(nodeGroups).reduce((acc, curr) => {
const nodes = nodeTypes
? Object.entries(nodeTypes).reduce(
(accN: HvFlowSidebarGroupNodes, currN) => {
if (currN[1].meta?.groupId === curr[0]) {
accN.push({
type: currN[0],
label: currN[1].meta?.label,
data: currN[1].meta?.data,
});
}
return accN;
},
[]
)
: [];
const nodesWithGroupId: HvFlowSidebarGroupNode[] = [];
const nodesWithoutGroupId: HvFlowSidebarGroupNode[] = [];

if (nodeTypes) {
for (const [nodeType, node] of Object.entries(nodeTypes)) {
if (node.meta?.groupId === curr[0]) {
nodesWithGroupId.push({
type: nodeType,
label: node.meta?.label,
data: node.meta?.data,
});
} else if (!node.meta?.groupId) {
nodesWithoutGroupId.push({
type: nodeType,
label: node.meta?.label || "",
data: node.meta?.data,
});
}
}
}

acc[curr[0]] = {
...curr[1],
nodes,
};
nodes: nodesWithGroupId,
} as HvFlowNodeGroup & { nodes: HvFlowSidebarGroupNodes };

// Create a "Default" group for nodes without a groupId
if (nodesWithoutGroupId.length > 0) {
// @ts-ignore
acc.Default = {
name: "Default",
label: "Default",
nodes: nodesWithoutGroupId,
...defaultGroupProps,
} as HvFlowNodeGroup & { nodes: HvFlowSidebarGroupNodes };
}

return acc;
}, {});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { HvFlowNode } from "@hitachivantara/uikit-react-lab";

export const LineChart = (props) => {
return (
<HvFlowNode
description="LineChart description"
nodeDefaults={{
title: "Line Chart",
subTitle: "Visualization",
color: "cat12_80",
}}
{...props}
/>
);
};

LineChart.meta = {
label: "LineChart",
inputs: [
{
label: "Data",
isMandatory: true,
accepts: ["prediction", "detection"],
},
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { HvFlowNode } from "@hitachivantara/uikit-react-lab";

export const MLModelPrediction = (props) => {
return (
<HvFlowNode
description="Anomaly Prediction description"
nodeDefaults={{
title: "ML Model Prediction",
subTitle: "ML Model Prediction",
color: "cat10_80",
}}
{...props}
/>
);
};

MLModelPrediction.meta = {
label: "ML Model Prediction",
inputs: [
{
label: "Sensor Data",
isMandatory: true,
accepts: ["sensorData"],
},
],
outputs: [
{
label: "Prediction",
isMandatory: true,
provides: "prediction",
},
],
};

0 comments on commit 8a5ca52

Please sign in to comment.