Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(Data Mapper): Update draggable list component and apply suggestions for Error/Warnings panel #6417

Closed
wants to merge 18 commits into from
1 change: 1 addition & 0 deletions libs/data-mapper-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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<TemplateItemProps, CommonProps> & {};
type CustomListItemProps = {
name?: string;
value?: string;
Expand All @@ -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 (
<CustomListItem
name={inputName}
value={inputValue}
remove={removeUnboundedInput}
index={index}
customValueAllowed={inputsFromManifest[0].allowCustomInput}
schemaType={schemaType}
type={inputType}
validateAndCreateConnection={validateAndCreateConnection}
functionData={data}
functionKey={functionKey}
key={`input-${inputName}`}
draggable={commonProps.draggable}
dragHandleProps={dragHandleProps}
/>
);
};

export const CustomListItem = (props: CustomListItemProps) => {
const styles = useStyles();
const {
Expand All @@ -138,7 +39,7 @@ export const CustomListItem = (props: CustomListItemProps) => {
} = props;

return (
<ListItem key={`input-${name}`} className={styles.draggableListItem}>
<div key={`input-${name}`} className={styles.draggableListItem}>
<div className={styles.draggableListContainer}>
<span className={styles.formControl}>
<InputDropdown
Expand All @@ -164,12 +65,6 @@ export const CustomListItem = (props: CustomListItemProps) => {
)}
</span>
</div>
</ListItem>
</div>
);
};

export default class InputListWrapper extends React.Component<InputListProps, {}> {
render() {
return <InputList {...this.props} />;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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<DraggableListProps>) => {
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 (
Expand All @@ -229,23 +286,51 @@ const UnlimitedInputs = (props: {
</div>
</div>
<div className={styles.body} ref={containerRef}>
<DraggableList<TemplateItemProps, CommonProps, any>
list={Object.entries(functionConnection.inputs).map((input, index) => ({
input: input[1],
index,
<SortableTree<DraggableListProps>
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 (
<SimpleTreeItemWrapper
{...treeProps}
ref={treeRef}
showDragHandle={false}
contentClassName={styles.customizedDraggableListItem}
>
<CustomListItem
name={inputName}
value={inputValue}
remove={() => {
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}
/>
</SimpleTreeItemWrapper>
);
})}
/>
<div className={styles.formControlDescription}>
<Button
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,10 @@ export const useStyles = makeStyles({
maxHeight: 'fit-content',
marginTop: '5px',
},
customizedDraggableListItem: {
border: 'unset',
boxSizing: 'unset',
display: 'contents',
padding: '0',
},
});
2 changes: 1 addition & 1 deletion libs/designer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"react-dnd": "16.0.1",
"react-dnd-accessible-backend": "1.0.1",
"react-dnd-html5-backend": "16.0.1",
"react-dnd-multi-backend": "8.0.0",
"react-dnd-multi-backend": "8.1.2",
"react-hotkeys-hook": "4.3.8",
"react-intl": "6.3.0",
"react-markdown": "8.0.5",
Expand Down
Loading
Loading