Skip to content

Commit

Permalink
Feat(Data Mapper): Handle Node connections and state management (#4990)
Browse files Browse the repository at this point in the history
* smooth node connections

* node handles

* fix connection cases

* more changes

* update node

* update openKeys

* remove redundant func

* merge nodes and applyNodechanges

* update applychanges

* update states and remove redundant files

* stage updates

* merge main
  • Loading branch information
takyyon committed Jun 20, 2024
1 parent 4704eaf commit dd0c2b8
Show file tree
Hide file tree
Showing 19 changed files with 479 additions and 366 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ class DataMapperFileService implements IDataMapperFileService {

public saveMapDefinitionCall = (dataMapDefinition: string, mapMetadata: string) => {
if (this.verbose) {
console.log(`Saved definition: ${dataMapDefinition}`);
console.log(`Saved metadata: ${mapMetadata}`);
console.log('Saved definition: ', dataMapDefinition);
console.log('Saved metadata: ', mapMetadata);
}
};

Expand Down Expand Up @@ -104,8 +104,15 @@ export const DataMapperStandaloneDesignerV2 = () => {
const isLightMode = theme === ThemeType.Light;

return (
<div style={{ flex: '1 1 1px', display: 'flex', flexDirection: 'column', height: '100%' }}>
<div style={{ flex: '0 1 1px' }}>
<div
style={{
flex: '1 1 1px',
display: 'flex',
flexDirection: 'column',
height: '100vh',
}}
>
<div style={{ flex: '0 1 1px', height: '30vh%' }}>
<ThemeProvider theme={isLightMode ? AzureThemeLight : AzureThemeDark}>
<FluentProvider theme={isLightMode ? webLightTheme : webDarkTheme}>
{/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
Expand All @@ -117,7 +124,15 @@ export const DataMapperStandaloneDesignerV2 = () => {
</ThemeProvider>
</div>

<div style={{ flex: '1 1 1px', display: 'flex', flexDirection: 'column' }}>
<div
style={{
flex: '1 1 1px',
display: 'flex',
flexDirection: 'column',
height: '70vh',
overflow: 'hidden',
}}
>
<DataMapperDesignerProviderV2 locale="en-US" theme={theme} options={{}}>
<DataMapDataProviderV2
xsltFilename={xsltFilename}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import type { IDataMapperFileService } from "@microsoft/logic-apps-data-mapper-v2";
import type {
MessageToVsix} from "@microsoft/vscode-extension-logic-apps";
import {
ExtensionCommand
} from "@microsoft/vscode-extension-logic-apps";
import type { IDataMapperFileService } from '@microsoft/logic-apps-data-mapper-v2';
import type { MessageToVsix } from '@microsoft/vscode-extension-logic-apps';
import { ExtensionCommand } from '@microsoft/vscode-extension-logic-apps';

export class DataMapperFileService implements IDataMapperFileService {
private sendMsgToVsix: (msg: MessageToVsix) => void;
Expand All @@ -12,10 +9,7 @@ export class DataMapperFileService implements IDataMapperFileService {
this.sendMsgToVsix = sendMsgToVsix;
}

public saveMapDefinitionCall = (
dataMapDefinition: string,
mapMetadata: string
) => {
public saveMapDefinitionCall = (dataMapDefinition: string, mapMetadata: string) => {
this.sendMsgToVsix({
command: ExtensionCommand.saveDataMapDefinition,
data: dataMapDefinition,
Expand Down
2 changes: 1 addition & 1 deletion libs/data-mapper-v2/src/components/addSchema/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const useStyles = makeStyles({
},
root: {
display: 'flex',
height: '100vh',
height: '100%',
},
fileSelectedDrawer: {
backgroundColor: '#F6FAFE',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { Tree, TreeItem, TreeItemLayout, type TreeItemOpenChangeData, mergeClasses } from '@fluentui/react-components';
import type { SchemaNodeExtended } from '@microsoft/logic-apps-shared';
import { useCallback, useEffect, useRef } from 'react';
import { useStyles } from './styles';
import type { Node } from 'reactflow';
import useNodePosition from './useNodePosition';

type RecursiveTreeProps = {
root: SchemaNodeExtended;
isLeftDirection: boolean;
openKeys: Set<string>;
setOpenKeys: (openKeys: Set<string>) => void;
flattenedScehmaMap: Record<string, SchemaNodeExtended>;
setUpdatedNode: (node: Node) => void;
};

const RecursiveTree = (props: RecursiveTreeProps) => {
const { root, isLeftDirection, openKeys, setOpenKeys, flattenedScehmaMap, setUpdatedNode } = props;
const { key } = root;
const nodeRef = useRef<HTMLDivElement | null>(null);
const styles = useStyles();

const nodePosition = useNodePosition({
key: key,
openKeys: openKeys,
schemaMap: flattenedScehmaMap,
isLeftDirection: isLeftDirection,
nodeX: nodeRef.current?.getBoundingClientRect().x,
nodeY: nodeRef.current?.getBoundingClientRect().y,
});

const onOpenChange = useCallback(
(_e: any, data: TreeItemOpenChangeData) => {
const newOpenKeys = new Set(openKeys);
const value = data.value as string;
if (newOpenKeys.has(value)) {
newOpenKeys.delete(value);
} else {
newOpenKeys.add(value);
}
setOpenKeys(newOpenKeys);
},
[openKeys, setOpenKeys]
);

useEffect(() => {
const nodeId = `reactflow_${isLeftDirection ? 'source' : 'target'}_${root.key}`;

setUpdatedNode({
...{
id: nodeId,
selectable: true,
data: {
...root,
isLeftDirection: isLeftDirection,
},
type: 'schemaNode',
position: nodePosition,
},
});
}, [isLeftDirection, nodePosition, root, setUpdatedNode]);

if (root.children.length === 0) {
return (
<TreeItem itemType="leaf" id={key} value={key} ref={nodeRef}>
<TreeItemLayout className={isLeftDirection ? '' : styles.rightTreeItemLayout}>{root.name}</TreeItemLayout>
</TreeItem>
);
}

return (
<TreeItem itemType="branch" id={key} value={key} ref={nodeRef} open={openKeys.has(key)} onOpenChange={onOpenChange}>
<TreeItemLayout className={mergeClasses(styles.rootNode, isLeftDirection ? '' : styles.rightTreeItemLayout)}>
{root.name}
</TreeItemLayout>
<Tree aria-label="sub-tree">
{root.children.map((child: SchemaNodeExtended, index: number) => (
<span key={`tree-${child.key}-${index}`}>
<RecursiveTree
root={child}
isLeftDirection={isLeftDirection}
openKeys={openKeys}
setOpenKeys={setOpenKeys}
flattenedScehmaMap={flattenedScehmaMap}
setUpdatedNode={setUpdatedNode}
/>
</span>
))}
</Tree>
</TreeItem>
);
};

export default RecursiveTree;
117 changes: 71 additions & 46 deletions libs/data-mapper-v2/src/components/addSchema/tree/SchemaTree.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { type SchemaExtended, SchemaType, type SchemaNodeExtended, equals } from '@microsoft/logic-apps-shared';
import { Tree, TreeItem, TreeItemLayout, mergeClasses, type TreeItemOpenChangeData } from '@fluentui/react-components';
import { type SchemaExtended, SchemaType, equals } from '@microsoft/logic-apps-shared';
import { Tree, mergeClasses } from '@fluentui/react-components';
import { useStyles } from './styles';
import { useEffect, useState, useMemo } from 'react';
import { TreeNode } from './TreeNode';
import { useState, useMemo, useRef, useEffect, useCallback } from 'react';
import { useIntl } from 'react-intl';
import RecursiveTree from './RecursiveTree';
import { flattenSchemaNodeMap } from '../../../utils';
import { type Node, applyNodeChanges, type NodeChange } from 'reactflow';
import type { AppDispatch, RootState } from '../../../core/state/Store';
import { useDispatch, useSelector } from 'react-redux';
import { updateReactFlowNodes } from '../../../core/state/DataMapSlice';

export type SchemaTreeProps = {
schemaType?: SchemaType;
Expand All @@ -12,68 +17,88 @@ export type SchemaTreeProps = {

export const SchemaTree = (props: SchemaTreeProps) => {
const styles = useStyles();
const { schemaType } = props;
const {
schemaType,
schema: { schemaTreeRoot },
} = props;

const isLeftDirection = useMemo(() => equals(schemaType, SchemaType.Source), [schemaType]);
const [openKeys, setOpenKeys] = useState<Record<string, boolean>>({});
const [openKeys, setOpenKeys] = useState<Set<string>>(new Set());
const [updatedNodes, setUpdatedNodes] = useState<Record<string, Node>>({});

const intl = useIntl();
const dispatch = useDispatch<AppDispatch>();
const treeRef = useRef<HTMLDivElement | null>(null);
const flattenedScehmaMap = useMemo(() => flattenSchemaNodeMap(schemaTreeRoot), [schemaTreeRoot]);
const totalNodes = useMemo(() => Object.keys(flattenedScehmaMap).length, [flattenedScehmaMap]);
const { nodes } = useSelector((state: RootState) => state.dataMap.present.curDataMapOperation);

const treeAriaLabel = intl.formatMessage({
defaultMessage: 'Schema tree',
id: 't2Xi1/',
description: 'tree showing schema nodes',
});

const onOpenTreeItem = (_event: any, data: TreeItemOpenChangeData) => {
setOpenKeys((prev) => ({
...prev,
[data.value]: !prev[data.value],
}));
};
const setUpdatedNode = useCallback(
(node: Node) => {
setUpdatedNodes((prev) => ({ ...prev, [node.id]: node }));
},
[setUpdatedNodes]
);

useEffect(() => {
const openKeys: Record<string, boolean> = {};

const setDefaultState = (root: SchemaNodeExtended) => {
if (root.children.length > 0) {
openKeys[root.key] = true;
const keys = Object.keys(updatedNodes);
if (keys.length === totalNodes) {
const currentNodesMap: Record<string, Node> = {};
for (const node of nodes) {
currentNodesMap[node.id] = node;
}

for (const child of root.children) {
setDefaultState(child);
}
};
const nodeChanges: NodeChange[] = [];
for (const key of keys) {
const updatedNode = updatedNodes[key];
const currentNode = currentNodesMap[key];

setDefaultState(props.schema.schemaTreeRoot);
setOpenKeys(openKeys);
}, [props.schema]);
if (updatedNode.position.x < 0 && updatedNode.position.y < 0) {
if (currentNode) {
nodeChanges.push({ id: key, type: 'remove' });
}
} else if (!currentNode) {
nodeChanges.push({ type: 'add', item: updatedNode });
} else if (currentNode.position.x !== updatedNode.position.x && currentNode.position.y !== updatedNode.position.y) {
nodeChanges.push({
id: key,
type: 'position',
position: updatedNode.position,
});
}
}

const displaySchemaTree = (root: SchemaNodeExtended) => {
if (root.children.length === 0) {
return <TreeNode node={root} isLeftDirection={isLeftDirection} id={root.key} text={root.name} isHovered={false} isAdded={false} />;
if (nodeChanges.length > 0) {
const newNodes = applyNodeChanges(nodeChanges, nodes);
setUpdatedNodes({});
dispatch(updateReactFlowNodes(newNodes));
}
}
}, [nodes, updatedNodes, totalNodes, dispatch]);

return (
<TreeItem itemType="branch" open={openKeys[root.key]} value={root.key} onOpenChange={onOpenTreeItem}>
<TreeItemLayout className={mergeClasses(styles.rootNode, isLeftDirection ? '' : styles.rightTreeItemLayout)}>
{root.name}
</TreeItemLayout>
<Tree aria-label="sub-tree">
{root.children.map((child: SchemaNodeExtended, index: number) => (
<span key={`tree-${child.key}-${index}`}>{displaySchemaTree(child)}</span>
))}
</Tree>
</TreeItem>
);
};

return props.schema.schemaTreeRoot ? (
useEffect(() => {
setOpenKeys(new Set<string>(Object.keys(flattenedScehmaMap).filter((key) => flattenedScehmaMap[key].children.length > 0)));
}, [flattenedScehmaMap, setOpenKeys]);
return schemaTreeRoot ? (
<Tree
ref={treeRef}
className={isLeftDirection ? mergeClasses(styles.leftWrapper, styles.wrapper) : mergeClasses(styles.rightWrapper, styles.wrapper)}
aria-label={treeAriaLabel}
>
{displaySchemaTree(props.schema.schemaTreeRoot)}
<RecursiveTree
root={schemaTreeRoot}
isLeftDirection={isLeftDirection}
setOpenKeys={setOpenKeys}
openKeys={openKeys}
flattenedScehmaMap={flattenedScehmaMap}
setUpdatedNode={setUpdatedNode}
/>
</Tree>
) : (
<></>
);
) : null;
};
Loading

0 comments on commit dd0c2b8

Please sign in to comment.