diff --git a/libs/data-mapper-v2/package.json b/libs/data-mapper-v2/package.json index 80c5a79a1bf..e15e0a1ae34 100644 --- a/libs/data-mapper-v2/package.json +++ b/libs/data-mapper-v2/package.json @@ -18,6 +18,7 @@ "@react-hookz/web": "22.0.0", "@reduxjs/toolkit": "1.8.5", "@xyflow/react": "^12.3.5", + "dnd-kit-sortable-tree": "^0.1.73", "elkjs": "0.9.1", "fuse.js": "6.6.2", "immer": "9.0.15", diff --git a/libs/data-mapper-v2/src/components/functionConfigurationMenu/inputTab/InputList.tsx b/libs/data-mapper-v2/src/components/functionConfigurationMenu/inputTab/InputList.tsx index 205a3f83d72..9802c626802 100644 --- a/libs/data-mapper-v2/src/components/functionConfigurationMenu/inputTab/InputList.tsx +++ b/libs/data-mapper-v2/src/components/functionConfigurationMenu/inputTab/InputList.tsx @@ -1,31 +1,10 @@ -import { useCallback, useMemo } from 'react'; -import type { ConnectionDictionary, InputConnection } from '../../../models/Connection'; -import type { TemplateProps } from 'react-draggable-list'; -import type { FunctionData, FunctionInput } from '../../../models'; -import { useDispatch, useSelector } from 'react-redux'; -import type { RootState } from '../../../core/state/Store'; +import type { FunctionData } from '../../../models'; import { InputDropdown, type InputOptionProps } from '../inputDropdown/InputDropdown'; -import { getInputTypeFromNode, validateAndCreateConnectionInput } from './inputTab'; -import { deleteConnectionFromFunctionMenu, setConnectionInput } from '../../../core/state/DataMapSlice'; -import { getInputName, getInputValue } from '../../../utils/Function.Utils'; import { useStyles } from './styles'; -import { ListItem } from '@fluentui/react-list-preview'; import { Badge, Button } from '@fluentui/react-components'; import { DeleteRegular, ReOrderRegular } from '@fluentui/react-icons'; import type { SchemaType } from '@microsoft/logic-apps-shared'; -import * as React from 'react'; -export type CommonProps = { - functionKey: string; - data: FunctionData; - inputsFromManifest: FunctionInput[]; - connections: ConnectionDictionary; - schemaType: SchemaType; - draggable: boolean; -}; - -export type TemplateItemProps = { input: InputConnection; index: number }; -type InputListProps = TemplateProps & {}; type CustomListItemProps = { name?: string; value?: string; @@ -42,84 +21,6 @@ type CustomListItemProps = { key: string; }; -export const InputList = (props: InputListProps) => { - const connectionDictionary = useSelector((state: RootState) => state.dataMap.present.curDataMapOperation.dataMapConnections); - const sourceSchemaDictionary = useSelector((state: RootState) => state.dataMap.present.curDataMapOperation.flattenedSourceSchema); - const functionNodeDictionary = useSelector((state: RootState) => state.dataMap.present.curDataMapOperation.functionNodes); - const dispatch = useDispatch(); - const { - item: { input, index }, - commonProps, - dragHandleProps, - } = props; - const { functionKey, data, inputsFromManifest, connections, schemaType } = commonProps; - - const inputName = useMemo(() => getInputName(input, connections), [connections, input]); - const inputValue = useMemo(() => getInputValue(input), [input]); - const inputType = useMemo(() => getInputTypeFromNode(input), [input]); - const removeUnboundedInput = useCallback(() => { - const targetNodeReactFlowKey = functionKey; - dispatch( - deleteConnectionFromFunctionMenu({ - targetId: targetNodeReactFlowKey, - inputIndex: index, - }) - ); - }, [dispatch, functionKey, index]); - - const updateInput = useCallback( - (newValue: InputConnection) => { - const targetNodeReactFlowKey = functionKey; - dispatch( - setConnectionInput({ - targetNode: data, - targetNodeReactFlowKey, - inputIndex: index, - input: newValue, - }) - ); - }, - [data, dispatch, functionKey, index] - ); - - const validateAndCreateConnection = useCallback( - (optionValue: string | undefined, option: InputOptionProps | undefined) => { - if (optionValue) { - const input = validateAndCreateConnectionInput( - optionValue, - option, - connectionDictionary, - data, - functionNodeDictionary, - sourceSchemaDictionary - ); - if (input) { - updateInput(input); - } - } - }, - [connectionDictionary, data, functionNodeDictionary, sourceSchemaDictionary, updateInput] - ); - - return ( - - ); -}; - export const CustomListItem = (props: CustomListItemProps) => { const styles = useStyles(); const { @@ -138,7 +39,7 @@ export const CustomListItem = (props: CustomListItemProps) => { } = props; return ( - +
{ )}
- +
); }; - -export default class InputListWrapper extends React.Component { - render() { - return ; - } -} diff --git a/libs/data-mapper-v2/src/components/functionConfigurationMenu/inputTab/inputTab.tsx b/libs/data-mapper-v2/src/components/functionConfigurationMenu/inputTab/inputTab.tsx index 6007d8d059c..4fe5b21aec4 100644 --- a/libs/data-mapper-v2/src/components/functionConfigurationMenu/inputTab/inputTab.tsx +++ b/libs/data-mapper-v2/src/components/functionConfigurationMenu/inputTab/inputTab.tsx @@ -22,12 +22,17 @@ import { newConnectionWillHaveCircularLogic, } from '../../../utils/Connection.Utils'; import { SchemaType, type SchemaNodeDictionary } from '@microsoft/logic-apps-shared'; -import DraggableList from 'react-draggable-list'; -import InputListWrapper, { type TemplateItemProps, type CommonProps } from './InputList'; -import { useCallback, useMemo, useRef } from 'react'; +import { CustomListItem } from './InputList'; +import { forwardRef, useCallback, useMemo, useRef } from 'react'; import { useIntl } from 'react-intl'; import { InputCustomInfoLabel } from './inputCustomInfoLabel'; import { useStyles } from './styles'; +import { SimpleTreeItemWrapper, SortableTree, type TreeItems } from 'dnd-kit-sortable-tree'; + +type DraggableListProps = { + data: InputConnection; + id: number; +}; export const InputTabContents = (props: { func: FunctionData; @@ -163,7 +168,11 @@ const UnlimitedInputs = (props: { functionKey: string; connections: ConnectionDictionary; }) => { - const inputsFromManifest = props.func.inputs; + const { connections, func, functionKey } = props; + const inputsFromManifest = useMemo(() => func.inputs, [func]); + const connectionDictionary = useSelector((state: RootState) => state.dataMap.present.curDataMapOperation.dataMapConnections); + const sourceSchemaDictionary = useSelector((state: RootState) => state.dataMap.present.curDataMapOperation.flattenedSourceSchema); + const functionNodeDictionary = useSelector((state: RootState) => state.dataMap.present.curDataMapOperation.functionNodes); const styles = useStyles(); const dispatch = useDispatch(); const intl = useIntl(); @@ -196,16 +205,64 @@ const UnlimitedInputs = (props: { dispatch(createInputSlotForUnboundedInput(props.functionKey)); }, [dispatch, props.functionKey]); - const onDragMoveEnd = useCallback( - (newList: readonly TemplateItemProps[], _movedItem: TemplateItemProps, _oldIndex: number, _newIndex: number) => { + const onItemsChanged = useCallback( + (items: TreeItems) => { + console.log(items); dispatch( updateFunctionConnectionInputs({ - functionKey: props.functionKey, - inputs: newList.map((item) => item.input), + functionKey: functionKey, + inputs: items.map((item) => item.data), + }) + ); + }, + [dispatch, functionKey] + ); + + const removeUnboundedInput = useCallback( + (index: number) => { + const targetNodeReactFlowKey = functionKey; + dispatch( + deleteConnectionFromFunctionMenu({ + targetId: targetNodeReactFlowKey, + inputIndex: index, + }) + ); + }, + [dispatch, functionKey] + ); + + const updateInput = useCallback( + (newValue: InputConnection, index: number) => { + const targetNodeReactFlowKey = functionKey; + dispatch( + setConnectionInput({ + targetNode: func, + targetNodeReactFlowKey, + inputIndex: index, + input: newValue, }) ); }, - [dispatch, props.functionKey] + [func, dispatch, functionKey] + ); + + const validateAndCreateConnection = useCallback( + (optionValue: string | undefined, option: InputOptionProps | undefined, index: number) => { + if (optionValue) { + const input = validateAndCreateConnectionInput( + optionValue, + option, + connectionDictionary, + func, + functionNodeDictionary, + sourceSchemaDictionary + ); + if (input) { + updateInput(input, index); + } + } + }, + [connectionDictionary, func, functionNodeDictionary, sourceSchemaDictionary, updateInput] ); return ( @@ -229,23 +286,51 @@ const UnlimitedInputs = (props: {
- - list={Object.entries(functionConnection.inputs).map((input, index) => ({ - input: input[1], - index, + + items={(functionConnection.inputs ?? []).map((input, index) => ({ + data: input, + id: index + 1, + canHaveChildren: false, }))} - commonProps={{ - functionKey: props.functionKey, - data: props.func, - inputsFromManifest, - connections: props.connections, - schemaType: SchemaType.Source, - draggable: true, - }} - onMoveEnd={onDragMoveEnd} - itemKey={'index'} - template={InputListWrapper} - container={() => containerRef?.current} + keepGhostInPlace={true} + onItemsChanged={onItemsChanged} + // eslint-disable-next-line react/display-name + TreeItemComponent={forwardRef((treeProps, treeRef) => { + const inputItem = treeProps.item.data; + const index = treeProps.item.id; + const inputName = getInputName(inputItem, connections); + const inputValue = getInputValue(inputItem); + const inputType = getInputTypeFromNode(inputItem); + + return ( + + { + removeUnboundedInput(index - 1); + }} + index={index} + customValueAllowed={inputsFromManifest[0].allowCustomInput} + schemaType={SchemaType.Source} + type={inputType} + validateAndCreateConnection={(optionValue: string | undefined, option: InputOptionProps | undefined) => { + validateAndCreateConnection(optionValue, option, index - 1); + }} + functionData={func} + functionKey={functionKey} + key={`input-${inputName}`} + draggable={true} + dragHandleProps={treeProps.handleProps} + /> + + ); + })} />